Files
infra/docs/ai/plan.md
T
DmitryandClaude Sonnet 5 d2e1e6876a docs: AI project context (docs/ai) and repository documentation refresh
- docs/ai/: stable, repo-verified context - README, architecture, tech-stack,
  edge-cases, plan (confirmed active work only), migration-tofu (the blue-green
  OpenTofu migration runbook and per-service findings), legacy-warning, links.
- AGENTS.md: slimmed to a working contract that points at docs/ai instead of
  restating it; CLAUDE.md is an adapter that @-includes it.
- README.md, ansible/README.md, ansible/roles/README.md,
  roles/lxc_docker_host/README.md: bring wording in line with the current
  control plane (Makefile entry point, registry, tofu, memoir-bot gone).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uoq5AVK8mkBgg83Mq6o5V
2026-09-03 07:06:10 +03:00

36 KiB
Raw Blame History

План работ

Активные задачи

Вывод memoir-bot (CT 142)

Решение от 2026-09-02: сервис не используется и выводится из эксплуатации.

Repository-часть выполнена: удалены playbook, host, запись реестра, VMID из backup job и backup audit, SSH host-блок, проверки status, Prometheus target и no_proxy Uptime Kuma. Оговорка "Memoir Bot строится локально" убрана из документации - теперь единственные исключения из digest pinning это frozen Prometheus и docker-test.

Live-шаги оператора выполнены 2026-09-02: deploy key SecondBrain отозван, monitor в Uptime Kuma снят, pct stop 142 выполнен (подтверждено pct list на mini-pc). Это заодно репетиция удаления старого контейнера перед первым blue-green переездом (migration-tofu.md).

Оставшиеся шаги:

  1. Выдержать паузу (по аналогии с общим инвариантом blue-green - минимум неделю), затем pct destroy 142.
  2. Решить судьбу цепочки бэкапов VMID 142 в PBS.
  3. Не переиспользовать VMID 142 и 192.168.1.26 сразу.

Регрессия переезда: update-плейбуки зашивают старые VMID

Обнаружено 2026-09-02 сразу после cutover.

playbooks/vaultwarden-update.yml и playbooks/grimmory-update.yml теперь берут backup VMID из registry, а не из жёстко прошитых чисел. Это закрывает старую регрессию со «страховочным бэкапом» на остановленном контейнере.

Аудит также нашёл и исправил две проблемы в Gitea: небезопасный импорт legacy provisioning без явного выключателя и запуск homelab-restic-offsite-gitea на cloud-pc вместо gitea (профиль теперь живёт в CT 153). Теперь consumer status следует реальному backup unit на gitea, а homelab-backup-audit-gitea остаётся на cloud-pc.

Обновлено 2026-09-03: adguard-update.yml и mihomo-update.yml изначально читают VMID из реестра (homelab_services['<svc>'].vmid), поэтому после переезда adguard→158 и mihomo→159 они автоматически указывают на новые контейнеры — правок не потребовалось.

make update-vaultwarden, make update-grimmory, make update-gitea, make update-adguard, make update-mihomo и make update-all не запускались во время проверки.

Task 3 — PBS storage-level prune removed declaratively

Выполнено 2026-09-02: storage-level prune-backups на PVE storage pbs удалён декларативно, retention authority остался в PBS prune-pbs.

Локальное недельное PBS-container backup на storage backup с keep-last=2 — намеренное исключение и не трогалось.

Кворум кластера: qdevice не голосует

Обнаружено 2026-09-02. pvecm status: Expected votes: 3, Total votes: 2, флаги узлов A,NV,NMW (NV = Not-Voted), corosync-qdevice: Connect timeout.

Причина: corosync.conf указывает арбитр по публичному адресу ru-vps (host: 157.22.231.198), а UFW пускал 5403/tcp только из 10.122.62.0/24 — сети ZeroTier, выведенной в июле 2026. Арбитр отвалился молча.

Последствие: у двухнодового кластера нет третьего голоса. Кворум держится лишь пока живы обе ноды; отказ любой из них оставляет выжившую с 1 < 2.

Решение: чинить ПРЯМОЙ путь нода -> ru-vps:5403, а не заворачивать арбитр в OpenVPN. Туннель терминируется в ovpn-mini (CT 132 на mini-pc), поэтому при падении mini-pc арбитр исчез бы вместе с ним — защищён был бы только отказ cloud-pc. Плюс ovpn-mini стоит в плане на blue-green переезд, и его пересоздание роняло бы кворум.

Сделано: homelab_pve_egress_ip в group_vars/all/main.yml, правила UFW в playbooks/ru-vps-base.yml (открыть 5403 с этого адреса, удалить правило для 10.122.62.0/24), проверка кворума добавлена в playbooks/status.yml — секция CLUSTER QUORUM, чтобы повторный отказ не был снова молчаливым.

ВЫПОЛНЕНО 2026-09-02. После прогона make ru-vps-base: Total votes: 3, флаги узлов сменились с A,NV,NMW на A,V,NMW, qdevice отдаёт голос. Кластер снова имеет арбитра.

Открытым остаётся динамический homelab_pve_egress_ip: адрес зафиксирован статически, при его смене qdevice снова замолчит. Отличие от прошлого раза в том, что теперь это видно в секции CLUSTER QUORUM отчёта make status.

Вывод ZeroTier с ru-vps

Решение от 2026-09-02: выводить полностью. Проверено — ZT-интерфейса на хосте нет, маршрутов через него нет, на ZT-адресах никто не слушает; контейнер zerotier подключён в никуда. Мёртв и ssh-zt22.service (sshd на порту 22 для setup qdevice) — порт 22 не слушает никто.

ВЫПОЛНЕНО 2026-09-02 через playbooks/ru-vps-zerotier-decommission.yml (make zerotier-decommission CONFIRM=1). Контейнер снят, ssh-zt22.service отключён, правила UFW для интерфейса zt6q3dmi2d, 9993/udp, 9001 и подсети 10.122.62.0/24 удалены. Сброшено состояние failed у ssh-zt22.service и фантомного homelab-pve-routes.service. Прогон идемпотентен, публичные сервисы и кворум не пострадали.

Намеренно НЕ удалено: каталог /opt/services/ru-vps/zerotier, данные /opt/data/zerotier, файл /etc/ssh/sshd_config_zt22. docker compose down выполняется без -v, identity узла сохранена. Удаление — отдельный шаг.

Правила UFW для 3128/tcp (squid не запущен), 1080/tcp (danted слушает 1081), 993/tcp и 7892/tcp оставлены: они не наследие ZeroTier. В плейбуке есть переключатель zt_cleanup_unrelated_stale_rules, по умолчанию выключен.

resticprofile-check@profile-default падает еженедельно

Обнаружено 2026-09-02 при чистке упавших юнитов на ru-vps.

Fatal: Please specify repository location (-r or --repository-file)
check on profile 'default': exit status 1

Профиль default в /opt/services/ru-vps/resticprofile/profiles.toml не имеет репозитория — это профиль-заготовка, для которого не должно быть таймера проверки. Реальный profile-services бэкапится и проверяется штатно, так что это шум, а не потеря бэкапов.

Значение: юнит постоянно висит в секции FAILED SYSTEMD UNITS отчёта make status и притупляет внимание к настоящим отказам. Стек resticprofile на ru-vps в Ansible не описан, поэтому чинится либо вручную (systemctl disable --now resticprofile-check@profile-default.timer), либо вместе со взятием стека под управление.

Прочие упавшие юниты на ru-vps — ifup@eth0.service и networking.service; не разбирались, хост при этом полностью работоспособен.

Gitea Actions: документация устарела

Проверено 2026-09-02. Контейнер gitea-runner-gitea-runner-1 на ru-vps работает и опрашивает Gitea, то есть раннер ЗАРЕГИСТРИРОВАН. Утверждения в .gitea/workflows/lint.yml и tech-stack.md об обратном неверны.

CI при этом всё равно не выполняется: у раннера метка ru-vps, а workflow требует runs-on: ubuntu-latest. Отдельный вопрос перед включением CI: раннер монтирует /var/run/docker.sock и /opt/services на запись, то есть любой workflow получает root над публичной VPS.

hermes-ai недостижим по SSH: прозрачный прокси съедает обратный путь

Обнаружено 2026-09-02. CT 147 запущен, sshd слушает *:22, UFW разрешает 22/tcp из 192.168.1.0/24 и 10.78.0.0/30, но хост не отвечает ни с ru-vps, ни с cloud-pc (то есть и из самой LAN). make check показывает его unreachable.

Причина видна в ip rule внутри контейнера:

9002: not from all iif lo lookup 2022

Правило заворачивает в таблицу прозрачного прокси (hermes-tun) всё, что не пришло с lo, включая ответные пакеты входящих соединений. SYN доходит, SYN-ACK уходит в туннель — соединение не устанавливается. Исключения для трафика, пришедшего с eth0, в конфигурации нет.

Побочное следствие: playbooks/pve-hermes-ai.yml больше не может отработать — Ansible не достучится до хоста. Управлять контейнером можно только через pct exec с cloud-pc. Плейбук, судя по всему, отработал один раз и запер себя: включение прокси не рвёт уже установленную сессию, только новые.

Не чиню: сервис признан малополезным и заморожен (см. ниже). Но make check и make status будут показывать его DOWN, и это ожидаемо, а не новая поломка.

Hermes AI (CT 147) - осознанно заморожен

Решение от 2026-09-02: сервис признан малополезным, но контейнер остается. playbooks/pve-hermes-ai.yml разворачивает runtime, Docker и transparent TUN proxy через mihomo, но не разворачивает приложение Hermes - это не недоделка, а принятое состояние. Не предлагать "дописать деплой Hermes" как opportunistic cleanup.

Секреты переехали в корень репозитория

Решение от 2026-09-02. .env и .env.example перенесены из ansible/ в корень: их потребляет не только Ansible, но и OpenTofu, а держать два файла или ходить в соседний подкаталог неудобно.

ENV_FILE в ansible/Makefile теперь абсолютный ($(REPO_ROOT)/.env), поэтому цели работают из любого cwd. Переименование .env.example заведено в индекс git, чтобы файл не потерялся при коммите. Оба пути покрыты .gitignore.

Принято решение хранить пароль root@pam в .env (а не спрашивать его при запуске). Компромисс осознанный: пользователь ansible и так имеет passwordless sudo на нодах, то есть эффективный root в автоматизации уже был; пароль добавляет не новый класс доступа, а секрет с худшими свойствами — он же логин в веб-интерфейс и консоль, его нельзя ограничить по scope и нельзя отозвать иначе, чем сменив пароль root на нодах.

Переменные: PROXMOX_ROOT_USER (по умолчанию root@pam) и PROXMOX_ROOT_PASSWORD. Их читают ТОЛЬКО цели tofu-*; Ansible ими не пользуется. Если пароль не задан или равен replace-me, Tofu идёт токеном ansible@pve. Выбранный режим печатается в stderr перед запуском.

Пилот OpenTofu: граница возможностей API-токена

Выполнено 2026-09-02. Каталог tofu/, цели make tofu-*, подробности и матрица возможностей — в ../../tofu/README.md.

Кратко, проверено на живом кластере контейнером VMID 199:

  • API-токен создаёт LXC со всеми ресурсами, сетью, rootfs, nesting, startup и тегами; plan идемпотентен.
  • Mount point как volume на datastore токеном создаётся. Это снимает вопрос по mp0 у gitea: bind mount каталога хоста требует root@pam, а volume — нет.
  • keyctl, fuse, mount и device_passthrough требуют root@pam (HTTP 403). Это ограничение Proxmox, а не Tofu: Ansible упирается в то же самое, поэтому в репозитории уже есть pct set --features по SSH и правка /etc/pve/lxc/<vmid>.conf.
  • Гибрид Tofu+Ansible требует lifecycle { ignore_changes = [features] }, иначе Tofu откатывает выставленный извне keyctl и ломает Docker в контейнере.
  • Tofu не умеет ProxyJump: вне LAN нужен SSH-туннель, он встроен в цели tofu-*.

РЕЖИМ ВЫБРАН 2026-09-02: root@pam по паролю. Проверено на пилоте — dev0, features: fuse=1,keyctl=1,nesting=1 и volume mount point выставляются декларативно, повторный plan даёт No changes. Значит при переезде каждого сервиса из его pve-*.yml можно убирать pct set --features и правку /etc/pve/lxc/<vmid>.conf; ignore_changes не нужен.

Токен, принадлежащий root@pam, ограничение НЕ обходит: $authuser при токенной аутентификации равен полному user@realm!tokenname (PVE/HTTPServer.pm:86), а проверка в PVE/LXC.pm:1658 сравнивает строку с root@pam буквально. Права токену добавлять бесполезно. Единственная альтернатива гибриду — пароль root@pam, то есть системный пароль root узлов Proxmox.

Гибридная схема (Tofu создаёт, Ansible доводит по SSH) отвергнута в пользу полной декларативности. Она остаётся запасным вариантом, если пароль root решат из .env убрать.

Пилотный контейнер VMID 199 tofu-pilot снесён (подтверждено 2026-09-02: pct list на cloud-pc его не показывает).

Переход на OpenTofu

Пошаговый план миграции provisioning на Tofu вынесен в отдельный документ: migration-tofu.md. Там же порядок сервисов, специфика каждого и процедура отката.

Сервис №1 emergency-bot переехал 2026-09-02: OLD (VMID 148) остановлен (откат минимум неделю, затем pct destroy), боевой — NEW (VMID 151, tofu/services.tf), адрес не менялся (192.168.1.32). Реестр обновлён (vmid: 151, provisioner: tofu), make validate и make status зелёные. Две находки, важные для следующих сервисов, задокументированы в migration-tofu.md (раздел 5.1) и tofu/README.md: конфигурационная часть сервиса может жить в отдельном playbook с play, таргетящими другие хосты (не подходит под буквальный --limit <name>-new), и провайдер отслеживает cmode через блок console, который нужно объявлять явно с самого начала.

Пакетный переезд сервисов 2-7 (параллельно)

Решение от 2026-09-02: последовательный переезд по одному сервису слишком медленный. Инвариант «один сервис за раз» уточнён в migration-tofu.md: последовательным обязан быть только cutover, а подготовка, создание и конфигурация на временных адресах параллелятся безопасно.

Создано и проверено 2026-09-02. Один пакетный tofu apply (6 to add, 0 to change, 0 to destroy — уже переехавший emergency-bot дрейфа не дал):

Сервис OLD NEW Узел TMPIP
docker-test 145 152 cloud-pc 192.168.1.11
gitea 141 153 cloud-pc 192.168.1.12
vaultwarden 140 154 mini-pc 192.168.1.13
monitoring 146 155 cloud-pc 192.168.1.14
gyro 150 156 mini-pc 192.168.1.15
grimmory 149 157 cloud-pc 192.168.1.16

pct config всех шести сверен с эталонами: cpu, память, swap, диск, features, startup, cmode: shell совпали. Запас на нодах после создания: cloud-pc 12.3 ГиБ RAM и 849 ГиБ на data, mini-pc 8.8 ГиБ RAM и 117 ГиБ на local-lvm. make validate зелёный до и после — новые контейнеры в реестре пока не числятся, это ожидаемо до шага 4.6.

Что подтвердилось на живых контейнерах впервые:

  • /dev/fuse через features.fuse эквивалентен старому обходу. Устройство присутствует в 152, 153, 154, 157 и отсутствует в 156 (у gyro его и не должно быть). Это снимает вопрос, который до сих пор был помечен как непроверенный: пилот проверял device_passthrough только на /dev/net/tun.
  • Bind mount gitea заменён на volume декларативно: mp0: data:153/vm-153-disk-1.raw,mp=/opt/gitea/data,size=32G. Ради этого миграция и затевалась.
  • Пустой features у gyro сохраняется. В pct config 156 строки features нет вообще, nesting не подкрался.

Две ловушки, найденные при этом заходе и почищенные в коде:

  1. --limit <name>-new не перенацеливает play, а обнуляет его — и это общее свойство, а не особенность emergency-bot, как считалось раньше. Проверено --list-hosts: ноль хостов во ВСЕХ play всех плейбуков. Заведён переопределяемый таргет pve_config_target, поведение по умолчанию не изменилось. Подробности — migration-tofu.md, шаг 4.3.9.
  2. Inline vars: у группы в inventory-файле проигрывают group_vars/all/. Из-за этого Ansible ходил в новые контейнеры под ansible@ вместо root и получал Permission denied. Транспорт вынесен в group_vars/lxc_migration_new/main.yml.

Попутно roles/uptime_kuma научился не падать на хосте, где замороженного юнита homelab-monitoring никогда не было: заморозка теперь выполняется только при его наличии.

Ещё один потребитель, которого не было в плане: playbooks/vaultwarden-update.yml жёстко зашивает VMID 140, включая vzdump "140". Его нужно поправить на шаге 4.6 вместе с реестром.

Конфигурация на временных адресах (шаг 4.3) выполнена для пяти сервисов. Все прогоны идемпотентны — второй заход даёт changed=0. Боевые контейнеры не затронуты: проверено после прогонов, на каждом боевом хосте активен ровно свой сервис.

Сервис Итог Проверка
docker-test 152 OK Docker active, storage-driver fuse-overlayfs, hello-world проходит, версия Docker совпала с боевой
gitea 153 OK HTTP 200, порт 2222 слушает, volume — отдельная ФС /dev/loop10 ext4 32 ГиБ
vaultwarden 154 OK HTTP 200, контейнер healthy, база свежая 278 КБ без icon_cache
monitoring 155 OK HTTP 302→200, kuma.db создан на месте, /opt/monitoring отсутствует
grimmory 157 OK после починки оба юнита active, health 200, Flyway 144 — как на боевом
gyro 156 OK конфигурация и cutover завершены 2026-09-02

Digest'ы образов на всех пяти совпали с боевыми побайтово.

Баг в pve-grimmory.yml, найденный при этом. Боевой адрес был зашит в конфигурационном play трижды: в ports compose, в правилах GRIMMORY-FILTER и в URL health-check. Первое роняло grimmory.service на любом другом адресе. Третье опаснее: задача выполняется на целевом хосте, поэтому после починки биндинга она уходила бы в БОЕВОЙ grimmory, получала 200 и давала ложно-положительный результат. Исправлено через grimmory_bind_ip: "{{ expected_lan_ip }}"; на боевом хосте рендер не изменился, --check --diff даёт changed=0.

Следствие для cutover. Grimmory — единственный из шести, кто биндится на конкретный адрес. После смены адреса его плейбук обязан быть прогнан заново, иначе compose останется с временным адресом. Добавлено в шаг 4.5.16.

Ещё одно ошибочное утверждение снято. План предупреждал, что roles/gyro пинит SSH host key gitea и после переезда сломается. Проверено дословно: там state: absent — задача УДАЛЯЕТ устаревшую запись, а пинится github.com, и gyro_repo_url ведёт на GitHub. Переезд gitea на gyro не влияет.

Legacy pve-gyro.yml теперь по умолчанию завершает все три plays, когда registry говорит provisioner: tofu; для intentional legacy rollback/recovery нужен явный override -e pve_gyro_legacy_provisioning_enabled=true. Это не normal deployment.

Cutover выполнен 2026-09-02 для пяти сервисов. Все прошли по процедуре раздела 4: свежий бэкап, перенос данных со сверкой, остановка старого контейнера, смена адреса, правка реестра, перегенерация заданий бэкапа.

Сервис OLD → NEW Сверка данных
docker-test 145 → 152 данных нет; Docker на fuse-overlayfs, hello-world проходит
vaultwarden 140 → 154 1 users, 314 ciphers; integrity_check ok
monitoring 146 → 155 kuma.db совпал по sha256 и размеру; редирект на /dashboard
gitea 141 → 153 64 repos, 5 users, 203 actions; integrity_check ok
grimmory 149 → 157 book=31 author=30 book_file=40 category=87 reading_sessions=68; Flyway 142/144 без новых миграций

Публичные домены снаружи: pass.ada-dev.ru 200, git.ada-dev.ru 200, books.ada-dev.ru health 200 (OPDS отдаёт штатный 401 Basic realm="Booklore OPDS" — это требование авторизации, а не поломка; в базе 2 учётки OPDS). make validate, make lint зелёные, make backup-jobs перегенерировал списки VMID сам из реестра.

Смена адреса выполняется провайдером на месте. Проверено на всех пяти: 0 added, 1 changed, 0 destroyed. Это снимало главный риск — пересоздание контейнера с данными.

Старые контейнеры остановлены и целы: 140, 141, 145, 146, 149. Не удалять минимум неделю, до 2026-09-09.

Находки cutover, которых не было в плане

  1. Данные gitea никогда не попадали в PBS. vzdump печатает excluding bind mount point mp0 ('/opt/gitea/data') from backup (not a volume) — снапшоты ct/141 содержали только rootfs. Защищал данные лишь offsite-профиль restic. Переезд на volume это чинит: теперь данные в бэкапе. Побочно это самый весомый аргумент за миграцию, которого в плане не было.
  2. vzdump падает, пока работает Docker с fuse-overlayfs на rootfs в каталоговом хранилище. rootfs на data снапшоты не поддерживает, vzdump уходит в режим suspend с rsync и спотыкается о var/lib/docker/fuse-overlayfs/.../merged: Permission denied. У vaultwarden этого нет — его rootfs на local-lvm, там настоящий снапшот. Лечится остановкой сервиса перед бэкапом.
  3. Offsite-профиль restic надо переустанавливать на новом контейнере, если он выполняется внутри LXC. Сделано для vaultwarden и grimmory, проверено прогоном юнита. Профиль gitea переписан с ноды внутрь контейнера (playbooks/offsite-restic-yadisk.yml), старый таймер на cloud-pc отключён.
  4. lost+found на новом volume ломает restic. Свежая ext4 создаёт этот каталог с владельцем uid 0 хоста, внутри unprivileged LXC он nobody:nogroup и нечитаем; restic отдаёт exit 3, юнит падает каждую ночь, хотя снапшот сохраняется. Добавлен в исключения профиля gitea.
  5. SSH host-ключи gitea перенеслись вместе с данными. Ключ на порту 2222 совпал с боевым (AAAAC3NzaC1lZDI1NTE5AAAAIDE+iqCj), поэтому у git-клиентов known_hosts остался валидным. Обновлять пришлось только ключ sshd контейнера на порту 22.
  6. Зависший ControlMaster даёт таймаут на баннере после cutover. Мастер остаётся живым, но его туннель ведёт в остановленный контейнер, и новые сессии мультиплексируются поверх мёртвого соединения. Лечится ssh -O exit <host> или разовым -o ControlPath=none.
  7. systemctl start grimmory — no-op. Юнит Type=oneshot с RemainAfterExit=yes уже числится активным, нужен restart.
  8. MYSQL_ROOT_PASSWORD из .env не подошёл к MariaDB (Access denied for user 'root'@'localhost'). Restore выполнен пользователем grimmory, у него ALL PRIVILEGES на свою базу — этого достаточно для DROP/CREATE DATABASE.

Gyro cutover verified

gyro завершён как cutover on 2026-09-02: CT 156 живёт на неизменном production IP 192.168.1.35 и provisioned by Tofu. CT 150 остановлен и удерживается как rollback минимум на неделю; его PBS snapshot от 2026-09-02T19:40:56Z сохранён, цепочку PBS не удаляли. Файл фаервола 156.fw уже копия 150.fw, Datacenter firewall по-прежнему disabled. Timer gyro.timer active, last oneshot success. Данных для переноса не было, и TMPIP-specific bind/re-run не потребовались.

Удаление старого CT или его backup chain не выполнялось.

Пакетный переезд сервисов 8-10: adguard, mihomo, ovpn-mini

Выполнено 2026-09-03 автономно, одной сессией. Этим OpenTofu-переезд всех мигрируемых LXC завершён (осталось: hermes-ai заморожен, pbs не мигрируется).

Сервис OLD → NEW Узел Боевой IP (не менялся) Свежий PBS перед стартом
adguard 144 → 158 mini-pc 192.168.1.28 ct/144/2026-09-02T21:06:12Z
mihomo 143 → 159 mini-pc 192.168.1.27 ct/143/2026-09-02T21:12:49Z
ovpn-mini 132 → 160 mini-pc 192.168.1.23 ct/132/2026-09-02T21:17:55Z

OLD 144/143/132 остановлены и удерживаются как откат минимум неделю (до ~2026-09-10). PBS-цепочки не трогали; у NEW 158/159/160 сняты первые свои снапшоты (…T21:37-39Z), чтобы backup-audit был чист сразу. make validate, make lint, make tofu-plan (No changes), make openvpn-check (7/7) — зелёные. Внешние домены git/pass/books.ada-dev.ru — 200.

Смена адреса — провайдером на месте (0 added, 1 changed, 0 destroyed), пересоздания контейнера с данными нет — как и на сервисах 2-7.

Специфика и находки перенесены в migration-tofu.md раздел 5 (пункты 8-10). Кратко:

  • adguard: свежий AdGuard на пустом conf/ не проходит health-гейт плейбука (setup-wizard, порт 80 не слушает), поэтому данные заливаются ДО config play, а не после. DNS-провал ~2 мин, ночью; ноды на DNS роутера, контейнеры на 1.1.1.1.
  • mihomo: первый боевой device_passthrough для /dev/net/tun (dev0: path=/dev/net/tun,mode=0660). ru-vps-mihomo-harden.yml повторно прогонять не нужно. Все зависимые ссылки на .27 остаются валидны.
  • ovpn-mini — петля транспорта. -F ansible/ssh_config и make tofu-* ходят к PVE через ru-vps → туннель, который терминирует сам ovpn-mini. pct stop 132 рвёт этот путь и make tofu-apply не достаёт API. Разрыв: поднять шлюз на НОВОМ контейнере ещё на TMPIP (одноразовый плейбук), туннель встаёт → tofu-apply работает → сменить адрес → повторный openvpn-vps-mini.yml (идемпотентен). Прямой путь к нодам во время провала — ssh -o ProxyJump=none <user>@192.168.1.{5,10} из LAN. Совокупный провал туннеля (внешний reverse-proxy) ~10 мин.
  • ssh_config: у ovpn-mini выставлен ProxyJump none (было — через ru-vps). Путь через ru-vps закольцовывался бы на его же туннель.
  • Три creation-play (pve-adguard.yml, pve-mihomo.yml, pve-ovpn-mini.yml) за гейтом provisioner != 'tofu' (meta: end_play, override -e pve_<svc>_legacy_provisioning_enabled=true) — как у pve-gyro.yml. Их handlers иначе сделали бы pct start <OLD> на боевом адресе.
  • Ошибка сессии: диагностический pct start 132 при живом CT 160 дал конфликт IP .23 и провал туннеля на 1-2 мин (восстановилось само). OLD контейнеры трогать нельзя, их pct config держит боевой адрес.
  • Косметика: polkit.service на CT 160 в failed (status=217/USER, минимальный LXC-шаблон). На OpenVPN не влияет.

НЕ сделано (осознанно): уборка раздела 6 migration-tofu.md — удаление roles/pve_lxc (ещё используется creation-play пяти сервисов батча 2026-09-02), стрип creation plays, правки architecture.md/legacy-warning.md, расширение validate.yml. Это отдельная задача; переездом сервисов 8-10 разблокирована частично (все 10 на Tofu), но исполнение — не сейчас.

Obsidian-заметки (HomeLab.md, Notes/Текущее состояние…, Log.md) по этому переезду ещё не обновлены.

Исторические планы

  • ../../current-task.md - исторический план Prometheus monitoring. Он не описывает текущую активную архитектуру: Uptime Kuma активен, Prometheus stack заморожен.
  • ../../tasks/grimmory-deployment-plan.md
    • план и запись развертывания Grimmory; фактическое состояние проверять по inventory, service registry и playbooks.

Выявленные риски и deferred work записаны в edge-cases.md и legacy-warning.md, но не считаются утвержденным backlog.