Files
pet-project-server/docs/drafts/timeweb-migration-log.md
T
av 10e1e8187b
Linting / YAML Lint (push) Has been cancelled
Linting / Ansible Lint (push) Has been cancelled
Migration: fix vars in playbooks
2026-05-22 20:52:47 +03:00

238 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Журнал миграции в Timeweb
Хронология фактического переезда. План и архитектурные решения —
в [timeweb.md](timeweb.md). Здесь только то, что реально сделано,
с датами.
Новые записи — сверху.
---
## Шаг 6 — `vars/vars.yml` загружается во всех плейбуках (2026-05-22, выполнено)
Сегодняшний коммит `8378f0e` («Migration: expose some public vars»)
вынес общие переменные (`application_dir`, `host_name`, `primary_user`,
`primary_user_uid`, `primary_user_gid`, `bin_prefix`,
`apprise_external_port`, `apprise_external_url`, `caddy_logs_dir`) из
vault в `vars/vars.yml`. Но большая часть плейбуков загружала только
`vars/secrets.yml` — на текущем сервере они работали лишь потому, что
inventory дублирует `application_dir` как override. На чистом
Timeweb-инвентаре без override они бы упали с undefined.
Прошёлся по всем плейбукам, добавил `- vars/vars.yml` сразу после
`- vars/secrets.yml`:
```
playbook-authelia.yml playbook-netdata.yml
playbook-calibre.yml playbook-outline.yml
playbook-docker.yml playbook-remembos.yml
playbook-dozzle.yml playbook-rssbridge.yml
playbook-eget.yml playbook-transcriber.yml
playbook-gitea.yml playbook-transcriber-registry.yml
playbook-gramps.yml playbook-tuwunel.yml
playbook-homepage.yml playbook-ufw.yml
playbook-homepage-registry.yml playbook-upgrade.yml
playbook-memos.yml playbook-wakapi.yml
playbook-miniflux.yml playbook-wanderer.yml
```
(21 файл — все «обычные» плейбуки, которые ещё не подключали vars.yml.)
Aggregator'ы `playbook-all-applications.yml` и `playbook-all-setup.yml`
не трогал — у них нет собственных `vars_files`, они используют
`import_playbook`, каждый импортируемый плейбук уже сам подключает
`vars.yml`.
`yamllint` чист. Идемпотентность проверить отдельным прогоном.
Проверить прогоном `inv pl -- all-applications` (или хотя бы
`inv pl -- gitea outline miniflux`) на текущем сервере — diff
ожидается пустой.
---
## Шаг 5 — переезд default application_dir на /srv (2026-05-22, выполнено)
`/mnt` по FHS — место для точек монтирования внешних дисков; на
системном диске Timeweb (фаза 1) это семантически неверно. Поменяли
дефолт на `/srv/applications` (FHS: «data for services provided by
this system»), для текущего YC-сервера сделали override в инвентаре.
Изменения:
- `vars/vars.yml``application_dir: "/srv/applications"`
(комментарий обновлён).
- `production.yml`у хоста `server` добавлен override
`application_dir: "/mnt/applications"`.
- `playbook-system.yml` — добавлен `vars/vars.yml` в `vars_files`,
захардкоженный `/mnt/applications` в задачах
`Create directory for mount` и `Mount external storages` заменён
на `{{ application_dir }}`.
- `playbook-remove-user-and-app.yml` — то же самое (`vars/vars.yml`
в `vars_files` + `{{ (application_dir, user_name) | path_join }}`).
- `tasks.py` — новый helper `_application_dir()` читает значение
сначала из inventory (override), затем из `vars/vars.yml`. `login_as_app`
больше не содержит `/mnt/applications`.
Что остаётся хардкодом — только `/mnt/applications` в `production.yml`
как override, и это правильно.
На Timeweb-инвентаре (когда появится) можно либо не задавать
`application_dir` вовсе (применится дефолт `/srv/applications`), либо
задать явно — для читаемости.
Проверить прогоном `inv pl -- system` на текущем сервере (Yandex
Cloud) — ничего не должно поменяться, потому что inventory override
возвращает `/mnt/applications` и mount всё ещё включён. Diff ожидается
пустой.
### Восстановление restic-снапшотов после смены путей
Старые снапшоты записаны с путями `/mnt/applications/<app>`. На
Timeweb данные должны лежать в `/srv/applications/<app>`. У restic
нет встроенного «remap path» при restore, поэтому делается в два
шага: восстановить во временный каталог, затем `rsync` на новое
место с сохранением uid/gid (приложения уже созданы playbook'ом с
теми же uid/gid, см. шаг про подготовку target).
Пример — восстановить gitea на Timeweb-машине:
```bash
sudo /usr/local/sbin/restic-shell.sh
# Распакуем нужную поддиректорию во временный каталог
restic restore latest \
--target /tmp/restic-restore \
--include /mnt/applications/gitea
# Перенесём данные на новый путь, сохранив владельца/группу/ACL/xattr
sudo rsync -aAX --info=progress2 \
/tmp/restic-restore/mnt/applications/gitea/ \
/srv/applications/gitea/
sudo rm -rf /tmp/restic-restore
```
Несколько приложений за один проход:
```bash
restic restore latest \
--target /tmp/restic-restore \
--include /mnt/applications/gitea \
--include /mnt/applications/outline \
--include /mnt/applications/miniflux
for app in gitea outline miniflux; do
sudo rsync -aAX --info=progress2 \
"/tmp/restic-restore/mnt/applications/$app/" \
"/srv/applications/$app/"
done
sudo rm -rf /tmp/restic-restore
```
Альтернатива через `restic mount` (если не хочется промежуточной
копии — данные мапятся как FUSE-FS):
```bash
sudo mkdir -p /mnt/restic-snapshots
restic mount /mnt/restic-snapshots &
sudo rsync -aAX \
/mnt/restic-snapshots/snapshots/latest/mnt/applications/gitea/ \
/srv/applications/gitea/
sudo fusermount -u /mnt/restic-snapshots
```
После переезда новые снапшоты будут записываться уже с путями
`/srv/applications/<app>` — никаких трюков для текущих бэкапов не
нужно.
---
## Шаг 4 — условное монтирование внешнего диска (2026-05-22, выполнено)
Задача `Mount external storages` в `playbook-system.yml` теперь
выполняется только при включённом флаге `mount_external_storage`
(default `false`). Сам UUID диска оставлен захардкоженным в
плейбуке — параметризовать не стали, потому что для Timeweb (фаза 1)
монтирование вообще не нужно, а для фазы 2 пока неизвестно, какой
UUID получится у второго диска.
Изменения:
- `playbook-system.yml` — у задачи mount добавлен
`when: mount_external_storage | default(false) | bool`.
- `production.yml` (инвентарь YC) — у хоста `server` добавлен
`mount_external_storage: true`, чтобы текущее поведение
сохранилось.
В будущем `timeweb.yml` просто не будет задавать эту переменную —
mount пропустится, `/mnt/applications` останется обычной директорией
на системном диске.
На фазе 2 (подключение медленного диска в Timeweb) UUID в
`playbook-system.yml` придётся поменять и включить флаг — это
осознанный шаг, не автоматизировано.
Проверено прогоном `inv pl -- system` на текущем сервере (Yandex
Cloud) — задача mount по-прежнему выполняется, `/mnt/applications`
смонтирован, изменений нет.
---
## Шаг 3 — переключение auth на cr.yandex (2026-05-22, выполнено)
Заменена аутентификация в Yandex Container Registry с YC-metadata
service на OAuth-token из vault.
Изменения:
- `files/yandex-docker-registry-auth.sh`**удалён**.
- `playbook-homepage.yml` — задача `ansible.builtin.script:
yandex-docker-registry-auth.sh` заменена на
`community.docker.docker_login` с `username: oauth`, `password:
"{{ yc_oauth_token }}"`.
- `playbook-transcriber.yml` — то же самое.
Локальные push-плейбуки (`playbook-homepage-registry.yml`,
`playbook-transcriber-registry.yml`) не трогал — там нет auth-задачи
в принципе, локальный docker аутентифицируется вручную
(`yc container registry configure-docker` или `docker login`).
Если позже захочется унифицировать — можно добавить тот же
`docker_login` с `delegate_to: 127.0.0.1`.
Проверено прогоном `inv pl -- homepage` и `inv pl -- transcriber` на
текущем сервере (Yandex Cloud) — ошибок нет, контейнеры работают.
Значит и на Timeweb заработает (единственная разница — исходящий IP,
а OAuth-токен в YC принимается извне).
---
## Шаг 2 — OAuth-token для cr.yandex (2026-05-22, выполнено)
В `vars/secrets.yml` добавлена (или обновлена) переменная
`yc_oauth_token` со свежим OAuth-токеном Яндекса. Токен будет
использоваться для логина в `cr.yandex` с новой машины Timeweb
(вместо текущего скрипта `files/yandex-docker-registry-auth.sh`,
который завязан на YC metadata service `169.254.169.254` и
работает только внутри YC).
Сам код переключения на `community.docker.docker_login` пока не
вносится — это следующая итерация. Сейчас токен просто положен в
vault, чтобы не делать этого в день cutover'а под прессом.
---
## Шаг 1 — снижение TTL DNS (2026-05-22, выполнено)
В админке Yandex 360 для зоны `vakhrushev.me` уменьшен TTL
A-записей с **21 600 с (6 ч)** до **1 200 с (20 мин)**. Это даёт
запас по времени на распространение изменений после смены IP в
день cutover'а — старые кэширующие резолверы перестанут отдавать
старый адрес максимум через 20 минут (вместо 6 часов).
Делается **заранее**, потому что само снижение TTL тоже
распространяется по кэшам по правилам старого TTL — то есть после
правки нужно подождать ≥ 6 часов, чтобы новое значение TTL само
успело прижиться. Раньше cutover'а нужно сделать с большим
запасом — день в день не сработает.