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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5e27ba2513
commit
d2e1e6876a
+485
@@ -0,0 +1,485 @@
|
||||
# План работ
|
||||
|
||||
## Активные задачи
|
||||
|
||||
### Вывод 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`](../../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`](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`](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`](../../current-task.md) - исторический план Prometheus
|
||||
monitoring. Он не описывает текущую активную архитектуру: Uptime Kuma активен,
|
||||
Prometheus stack заморожен.
|
||||
- [`../../tasks/grimmory-deployment-plan.md`](../../tasks/grimmory-deployment-plan.md)
|
||||
- план и запись развертывания Grimmory; фактическое состояние проверять по inventory,
|
||||
service registry и playbooks.
|
||||
|
||||
Выявленные риски и deferred work записаны в [`edge-cases.md`](edge-cases.md) и
|
||||
[`legacy-warning.md`](legacy-warning.md), но не считаются утвержденным backlog.
|
||||
Reference in New Issue
Block a user