Summary

Домашняя инфраструктура - это бесконечный сериал мелких багов. Собрал восемь эпизодов из последних месяцев: у каждого есть причина, которую невозможно угадать, и фикс в одну строчку. Самый обидный - про мост, который Docker тихо переименовал.

Восемь граблей домашнего админа

Эпизод 1. Контейнер есть, пинга нет

Я добавил в свой VPN новый контейнер. Все соседние сервисы открываются из VPN, а он - нет. Адрес правильный, порт правильный, контейнер живой.

Причина нашлась в firewall. У меня есть «охранное» правило: пакеты на адрес этого контейнера принимаются только с определённого сетевого моста. И в правиле стоял имя моста, вот такое: br-3f9a….

Так вот, Docker называет мосты по идентификатору сети. После перезапуска демона systemctl restart docker сеть может пересоздаться, имя моста поменяется, а правило продолжит охранять призрак. Новый мост для него чужой - и всё режется.

Фикс из трёх частей:

  1. Задать мосту постоянное имя: com.docker.network.bridge.name: br-nginx.
  2. Скрипт запуска после рестарта Docker заново определяет мост.
  3. Единая команда start_vpn поднимает всё в правильном порядке.

Эпизод 2. Скрипт, который Linux не видит

Я написал bash-скрипт в Windows-редакторе и залил на сервер. Linux ответил, что такого файла нет. Файл был, но вместо \n в конце строк стояли \r\n. Интерпретатор искал bash\r.

Теперь в репозитории есть правило: для скриптов всегда LF. Заодно я узнал, что sysctl -w нужен не везде, и что скрипт на awk бывает хрупким.

Эпизод 3. Знак доллара в хэше пароля

Положил в .env bcrypt-хэш пароля. Compose ругается на «неизвестные переменные». Bash при source тоже ломается. Виноват символ $ внутри хэша - для оболочки это начало переменной.

Решение: написать безопасный загрузчик переменных окружения, а хэш экранировать.

Эпизод 4. Конфиг, который затёрся дважды

Этот эпизод мне обошёлся дороже всех. Я залил шаблон конфига, и файл сегмента остался без клиентов. Затем awg-quick down записал файл поверх ещё раз, потому что в нём стояло SaveConfig = true, и интерфейс при остановке сохраняет своё (пустое) состояние обратно.

Клиентов спасло то, что все они лежали в базе панели. Из неё я восстановил десять ключей. Вывод: бэкап перед любой заливкой и осторожность с SaveConfig.

Эпизод 5. Сменил подсеть и остался без интернета

Мне понадобилось поменять адресный план: одна подсеть стала другой. Я обновил базу панели и живой интерфейс. Но один забытый конфиг держал старый адрес, и все клиенты потеряли интернет.

Теперь любое изменение адресации - это чек-лист: база, конфиги, интерфейс, правила firewall, DNS.

Эпизод 6. Порты открыты, но снаружи тишина

Я проверял порт снаружи, получал таймаут. Не отказ в соединении, а именно молчание. Молчание почти всегда означает DROP в firewall, а не отсутствие сервиса. В моём случае правило в цепочке FORWARD отбрасывало пакеты моего же периметра.

Итоговая схема: публично открыты только порты 80 и 443, остальное спрятано на внутренних адресах и доступно из админского сегмента.

Эпизод 7. Туннель между серверами «очень узкий»

Канал между двумя серверами оказался мучительно медленным. Подозреваемые были такие:

  • обфускация AmneziaWG на межсерверном канале;
  • один процессорный ядро на одном из узлов;
  • ядро без тюнинга.

Что я сделал:

  • межсерверные туннели перевёл на обычный WireGuard, а обфускацию оставил только клиентам;
  • выставил MTU 1420 на подложке;
  • включил BBR и fq;
  • заменил MASQUERADE на статический SNAT;
  • сделал три параллельных выходных интерфейса.

Честно скажу: итоговых цифр у меня нет. Тесты iperf3 зависали и ничего полезного не показали. Поэтому не обещаю «гигабиты». Делюсь только тем, что проверено логикой, а не измерениями.

Эпизод 8. Панель, которая падает после обновления

В одной из версий образа панели WGDashboard страница настроек падала с ошибкой 401 на служебном запросе. Помог переход на :latest. А заодно я перестал перезаписывать её рабочие файлы из репозитория: у панели своя «первая регистрация», и она обижается.

Что объединяет эти эпизоды

Что сломалосьПочему
Мост DockerПравило привязано к имени, а не к смыслу
Bash-скриптНевидимые символы в конце строки
.envСпециальный символ внутри значения
Конфиг AWGАвтосохранение затирает файл
ПодсетьЗабытое место, где остался старый адрес
ПортыDROP выглядит как «ничего нет»

Правила, которые я себе вывел:

  1. Скрипты должны быть идемпотентными. Запустил дважды - получил то же самое.
  2. Бэкап перед заливкой. Всегда.
  3. Не привязывайте правила к генерируемым именам. Задавайте постоянные.
  4. Состояние клиентов хранится в базе, а не в конфиге. Конфиг - производный артефакт.
  5. Измеряйте, а не предполагайте. Особенно скорость.

Это не мудрость, а шрамы. Но они полезнее любой документации.