Rolled back per the user's request. Three variants were tried on ru-vps
(static key; TLS peer-fingerprint p2p; server mode with push routes and an
inline <ca>). The server side worked each time, but the "OpenVPN for
Android" client consistently failed at config build ("Used 101 tries to
get current version of the profile"), which looks like an app/OS issue
rather than the config.
Repo: remove playbooks/openvpn-phone.yml, its Make target, and the shared
homelab_vpn_client_routes var; restore openvpn-laptop.yml to its prior
state (its pre-existing `become: false` on delegate_to: localhost is noted
in plan.md, left untouched). ru-vps teardown done out of band: unit, tun2,
ufw/nat rules for 9444 and 10.80.0.0/29, and /etc/openvpn/homelab-phone
removed; the site tunnel (homelab-openvpn, tun0) was not touched and is
verified active.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbuZrUoevfBgCpf5DCF4DG
43 KiB
План работ
Активные задачи
Road-warrior OpenVPN для телефона — откачено
2026-09-03: пробовали поднять клиентский OpenVPN для телефона (Android),
чтобы смотреть дашборд вне LAN. Перебрали три варианта на ru-vps (static
key -> TLS peer-fingerprint p2p -> server-режим с push route и <ca>).
На стороне ru-vps всё работало (тоннель вставал, TLS OK), но клиент
«OpenVPN for Android» стабильно падал на «сборка конфигурации / Used 101
tries to get current version of the profile». По решению пользователя всё
откачено: homelab-openvpn-phone, tun2, правила UFW/NAT для 9444 и
10.80.0.0/29, /etc/openvpn/homelab-phone сняты с ru-vps; playbooks/ openvpn-phone.yml, Make-цель и homelab_vpn_client_routes удалены из
репозитория. Сайт-тоннель homelab-openvpn (tun0) не затрагивался.
Если возвращаться: скорее всего проблема на стороне приложения (battery
optimization / сохранение профиля), а не конфига; либо собрать полноценный
PKI через community.crypto и <ca>+remote-cert-tls server, либо
попробовать WireGuard.
Предсуществующий баг, замеченный по ходу и НЕ тронутый: в
openvpn-laptop.yml (в проде не применялся) задачи с delegate_to: localhost используют ключ become: false, который не подавляет
унаследованный ansible_become: true — при запуске упрётся в «sudo: a
password is required». Чинится vars: {ansible_connection: local, ansible_become: false}.
Дашборд-обзор инфраструктуры (Homepage)
Решение от 2026-09-03: поднять стартовую страницу-обзор всей инфры (список сервисов + переходы + виджеты Proxmox/Uptime Kuma).
Выбор: Homepage (gethomepage.dev) вторым compose-стеком на LXC
monitoring (CT 155) рядом с Uptime Kuma; доступ только LAN/OpenVPN, наружу
не публикуется. Конфиг Homepage генерируется из homelab_services —
playbooks/dashboard.yml + шаблоны playbooks/templates/homepage-*.j2,
новый потребитель реестра. Плитка на сервис: href = https://<proxy.domain>
для публичных, иначе http(s)://<ip>:<порт>; беспортовые — в группу Headless
с ICMP ping.
Развёрнуто на CT 155 2026-09-03. Контейнер homelab-homepage (Up healthy),
homelab-homepage.service enabled, http://192.168.1.30:8082/ -> 200. Прогон
make dashboard идемпотентен (третий заход changed=0), make validate и
make lint зелёные. Образ закреплён по digest
sha256:753eeb0c…1e1bdd. Порт 8082 (3000 в модели реестра занят замороженной
Grafana, 3001 — Kuma) -> контейнерный 3000.
Состав:
playbooks/dashboard.yml, шаблоныhomepage-{settings,services,widgets,bookmarks}.yaml.j2,homepage-compose.yml.j2,homepage.env.j2;playbooks/bootstrap-dashboard-pve-token.yml— read-only токенhomepage@pve!dashboard(рольPVEAuditor), пишетDASHBOARD_PVE_*в корневой.env;- реестр: блок
homelab_dashboard_*вgroup_vars/all/services.yml, запись в списках потребителей (services.yml,AGENTS.md,architecture.md); Makefile: целиdashboard,dry-dashboard,bootstrap-dashboard-token.
Оставшиеся шаги оператора:
make bootstrap-dashboard-token— выпустить PVE-токен для виджета Proxmox. (В сессии-агенте был заблокирован auto-mode-классификатором как создание credential; репозиторная часть готова, плейбук идемпотентен и сам допишетDASHBOARD_PVE_*в корневой.env.) После —make dashboardзаново, чтобыhomepage.envподхватил секрет. До этого плитка «Proxmox кластер» показывает ошибку авторизации — не блокер.- Добавить те же три ключа-заглушки в
.env.example(в сессии путь.env*заблокирован в permissions). - В UI Uptime Kuma опубликовать Status Page со slug
homelab— иначе виджет сводки up/down показывает ошибку. Не блокер.
Вывод 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).
Оставшиеся шаги:
- Выдержать паузу (по аналогии с общим инвариантом blue-green - минимум
неделю), затем
pct destroy 142. - Решить судьбу цепочки бэкапов VMID 142 в PBS.
- Не переиспользовать 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не подкрался.
Две ловушки, найденные при этом заходе и почищенные в коде:
--limit <name>-newне перенацеливает play, а обнуляет его — и это общее свойство, а не особенность emergency-bot, как считалось раньше. Проверено--list-hosts: ноль хостов во ВСЕХ play всех плейбуков. Заведён переопределяемый таргетpve_config_target, поведение по умолчанию не изменилось. Подробности —migration-tofu.md, шаг 4.3.9.- 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, которых не было в плане
- Данные gitea никогда не попадали в PBS.
vzdumpпечатаетexcluding bind mount point mp0 ('/opt/gitea/data') from backup (not a volume)— снапшотыct/141содержали только rootfs. Защищал данные лишь offsite-профиль restic. Переезд на volume это чинит: теперь данные в бэкапе. Побочно это самый весомый аргумент за миграцию, которого в плане не было. vzdumpпадает, пока работает Docker с fuse-overlayfs на rootfs в каталоговом хранилище. rootfs наdataснапшоты не поддерживает, vzdump уходит в режимsuspendс rsync и спотыкается оvar/lib/docker/fuse-overlayfs/.../merged: Permission denied. У vaultwarden этого нет — его rootfs наlocal-lvm, там настоящий снапшот. Лечится остановкой сервиса перед бэкапом.- Offsite-профиль restic надо переустанавливать на новом контейнере, если
он выполняется внутри LXC. Сделано для vaultwarden и grimmory, проверено
прогоном юнита. Профиль gitea переписан с ноды внутрь контейнера
(
playbooks/offsite-restic-yadisk.yml), старый таймер на cloud-pc отключён. lost+foundна новом volume ломает restic. Свежая ext4 создаёт этот каталог с владельцем uid 0 хоста, внутри unprivileged LXC онnobody:nogroupи нечитаем; restic отдаёт exit 3, юнит падает каждую ночь, хотя снапшот сохраняется. Добавлен в исключения профиля gitea.- SSH host-ключи gitea перенеслись вместе с данными. Ключ на порту 2222
совпал с боевым (
AAAAC3NzaC1lZDI1NTE5AAAAIDE+iqCj), поэтому у git-клиентовknown_hostsостался валидным. Обновлять пришлось только ключ sshd контейнера на порту 22. - Зависший ControlMaster даёт таймаут на баннере после cutover. Мастер
остаётся живым, но его туннель ведёт в остановленный контейнер, и новые
сессии мультиплексируются поверх мёртвого соединения. Лечится
ssh -O exit <host>или разовым-o ControlPath=none. systemctl start grimmory— no-op. ЮнитType=oneshotсRemainAfterExit=yesуже числится активным, нуженrestart.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 не влияет, не чинилось (можноsystemctl mask polkit.service— ничего от него не зависит).
onboot: 0 на всех остановленных OLD-контейнерах (2026-09-03). Найдено:
132/140/141/142/143/144/145/146/148/149/150 стояли с onboot: 1 и держат
боевые адреса в pct config — ребут любой ноды поднял бы их в конфликт с
живыми NEW (151-160). Снято через pct set <id> --onboot 0. Это временная
мера на окно отката: pct destroy (шаг 4.7) уберёт их целиком через неделю.
Обратимо (--onboot 1) для намеренного продвижения OLD обратно.
Коммиты (2026-09-03). Всё рабочее дерево (накопление сессий 2026-09-02/03)
разложено в 11 тематических коммитов на main (22394cb..d2e1e68),
не запушено. Границы — по инициативам: build-env, .env-в-корень+Make,
tofu scaffold, validate gate, registry-driven backups, вывод memoir-bot,
ru-vps base+кворум+ZeroTier, PBS storage prune, blue-green миграция на Tofu,
фикс VMID в update-плейбуках, docs. Несколько файлов (services.yml,
status.yml, ssh_config) несут вторичные правки других инициатив — отмечено
в телах коммитов.
НЕ сделано (осознанно): уборка раздела 6 migration-tofu.md — удаление
roles/pve_lxc (ещё используется creation-play пяти сервисов батча 2026-09-02
через - role: pve_lxc), стрип/полное гейчение creation plays, правки
architecture.md/legacy-warning.md, расширение validate.yml (features,
devices, mount points). Это отдельная задача; переездом сервисов 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.