The Android client ships OpenVPN 2.7, which refuses a --secret config
("Options error: No tls-client or tls-server option"; 2.8 drops it
entirely). Rework the phone instance to a CA-less TLS point-to-point:
two self-signed EC keypairs generated on ru-vps (server.* / client.*),
each side pins the other by SHA256 with peer-fingerprint, data-ciphers
AES-256-GCM. Everything else unchanged (tcp/9444, tun2, 10.80.0.0/30,
NAT via tun0, systemd unit, shared homelab_vpn_client_routes).
Verified on ru-vps 2026-09-03: openvpn 2.6.19 starts clean ("Using
certificate fingerprint to verify peer"), listens on 9444, tun2 up,
make openvpn-phone idempotent (changed=0 on rerun), make lint green.
The regenerated ansible/generated/ada-phone.ovpn (self-contained
cert+key, gitignored) was handed to the operator.
openvpn-laptop.yml is left on static key — not deployed; needs the same
TLS treatment when someone actually uses it.
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. playbooks/openvpn-phone.yml (make openvpn-phone)
поднимает на ru-vps отдельный TLS point-to-point инстанс: порт 9444/tcp,
tun2, сеть 10.80.0.0/30, юнит homelab-openvpn-phone, NAT
10.80.0.0/30 -> LAN через tun0. Профиль (с приватным ключом) —
ansible/generated/ada-phone.ovpn (gitignored).
Крипто: peer-fingerprint, без CA/PKI. На ru-vps генерятся две
самоподписанные EC-пары (server.*, client.*), стороны пинят друг друга
по SHA256-отпечатку (peer-fingerprint), data-ciphers AES-256-GCM. Первая
попытка была на static key (--secret) — Android-клиент несёт OpenVPN 2.7 и
запускать её отказался («No tls-client or tls-server option»; 2.8 уберёт
совсем). Клиент на Android — «OpenVPN for Android» (Arne Schwabe) или
OpenVPN Connect: TLS-конфиг понимают оба.
Прогон идемпотентен (changed=0 на повторе). Проверено: сервис active,
OpenVPN 2.6.19 слушает 9444, tun2 up, Using certificate fingerprint to verify peer, UFW 9444/tcp, NAT-правило на месте, с ru-vps
192.168.1.30:8082 достижим. Маршруты клиента вынесены в
homelab_vpn_client_routes (group_vars/all/main.yml) — общий список с
openvpn-laptop.yml, не весь /24 (домашняя сеть телефона тоже
192.168.1.0/24).
Побочно: в openvpn-laptop.yml и openvpn-phone.yml локальная запись
профиля переведена с become: false на vars: {ansible_connection: local, ansible_become: false} — ключ become: false на delegate_to: localhost
не подавлял унаследованный ansible_become: true (sudo password required).
openvpn-laptop.yml по-прежнему на static key и на живом ru-vps не
применялся — при реальном использовании ему нужен тот же перевод на TLS.
Дашборд-обзор инфраструктуры (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.