Docs: собрать беклог задач из drafts

This commit is contained in:
av
2026-07-13 19:25:20 +03:00
parent a41f6bca8e
commit 30358de9d9
11 changed files with 238 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
# Беклог
Единый список будущих задач по серверу: то, что уже решили сделать, и идеи,
которые ещё надо обдумать. Это **источник истины по беклогу** — одна задача =
один файл в этом каталоге. Не план реализации: детальные проработки живут в
[`docs/drafts`](../drafts) (беклог даёт сводку и ссылку), а реализованное
переезжает в ADR/спеку и пункт беклога удаляется.
Приоритет — грубая оценка «ценность / стоимость», не обязательство к порядку.
Пункты с пометкой _(ансибл-ревью)_ пришли из ревью плейбуков
[`docs/drafts/ansible-review.md`](../drafts/ansible-review.md) (2026-05-25).
Драфты `timeweb.md` и `timeweb-migration-log.md` — исторические логи уже
завершённой миграции в Timeweb (cutover 2026-05-23), в беклог не входят.
## Высокий
- [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata на restart-loop/unhealthy
- [Рестарт контейнеров через handlers](ansible-handlers-restart.md) — `state: restarted` выполняется безусловно на каждом прогоне (лишний downtime), нет ни одного handler _(ансибл-ревью)_
## Средний
- [Gitea runner on-demand в Yandex Cloud](gitea-runner-on-demand.md) — раннер активен только во время сборки; webhook→Cloud Function стартует ВМ, probe/decide гасят по idle; экономия ~95%
- [Синхронизация общих Ansible-ролей](shared-roles-sync.md) — `owner`/`eget`/`secrets` дублируются между репозиториями и дрейфуют; канон в ansible-shared + rsync-таски invoke
- [Composable-роль `backup`](ansible-backup-role.md) — бэкап — самый чистый шов для extraction (одинаков у всех, различается только список targets) _(ансибл-ревью)_
- [Точечные фиксы идемпотентности и баг имени play](ansible-quick-fixes.md) — быстрые правки: имя play в wanderer, `changed_when` в netdata/eget, backup-targets через template _(ансибл-ревью)_
## Низкий
- [`vars_files` → `group_vars/all/`](ansible-group-vars.md) — убрать повторяющийся boilerplate `vars_files` из всех плейбуков _(ансибл-ревью)_
- [Причесать роль `owner` под конвенции](ansible-owner-role-cleanup.md) — `assert` вместо `fail`+`when`, `loop` вместо `with_*`, добавить `meta`/README _(ансибл-ревью)_
- [Инвентарь: `host_vars`, группы, точечный `become`](ansible-inventory-hostvars.md) — хост-специфику в `host_vars/server.yml`, хост в именованную группу, глобальный root → точечный become _(ансибл-ревью)_
- [Фоновая зачистка стиля и конфигурации](ansible-style-nits.md) — sudoers.d, профиль ansible-lint, `ansible.cfg`, кавычки, `cache_valid_time` _(ансибл-ревью)_
+17
View File
@@ -0,0 +1,17 @@
# Composable-роль `backup`
**Приоритет:** средний
Бэкап — самый чистый шов для extraction: `gobackup.yml` + `backup.sh` +
`backup-targets` + интеграция с restic одинаковы у всех сервисов, различается
только список целей. Вынести в роль `backup` с параметром «список targets» — она
не трогает индивидуальность сервиса. Это правильный размер абстракции (как уже
сделанный `owner`), а **не** мега-роль `docker_app` (та отклонена осознанно:
приложения реально разные, catch-all обрастает `when:`). Обкатать на одном
сервисе, затем раскатать.
Полный контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §1.
Связано: roles/owner (образец), files/<app>/gobackup.template.yml,
files/<app>/backup.template.sh, playbook-backups.yml,
feedback_per_app_playbook_duplication.
+22
View File
@@ -0,0 +1,22 @@
# `vars_files` → `group_vars/all/`
**Приоритет:** низкий
В каждом плейбуке повторяется boilerplate:
```yaml
vars_files:
- vars/secrets.yml
- vars/vars.yml
```
Ansible автоматически подхватывает `group_vars/all.yml` и `group_vars/all/secrets.yml`
(vault) для группы `all`. Перенос `vars/vars.yml``group_vars/all/main.yml` и
`vars/secrets.yml``group_vars/all/vault.yml` убирает `vars_files` из всех
плейбуков. Низкий риск, адаптируется по одному плейбуку за раз. Учесть
`.crushignore`/проверку шифрования vault (маска `*secrets.yml`) и pre-commit-хук
проверки шифрования при переименовании.
Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §2.
Связано: все playbook-*.yml, vars/secrets.yml, vars/vars.yml, lefthook.yml.
+20
View File
@@ -0,0 +1,20 @@
# Рестарт контейнеров через handlers, а не безусловно
**Приоритет:** высокий
Ни в одном плейбуке нет `handlers:`. Вместо этого задача `state: restarted`
выполняется **всегда** — рестартит контейнер на каждом прогоне даже без
изменений (`playbook-caddyproxy.yml:106`, `playbook-netdata.yml:143`,
`playbook-authelia.yml:92`): не идемпотентно, лишний downtime. В
`playbook-gitea.yml` рестарта нет вовсе — несогласованность. Канонический
паттерн: шаблон конфига `notify`-ит handler, который делает
`docker_compose_v2: state: restarted` только при реальном изменении. Внедряется
инкрементально, по одному сервису. Заодно убрать мёртвый
`docker_compose_file_result` в `playbook-memos.yml:76` (регистрируется, нигде не
используется — задумывался под `when`/`notify`).
Топ-приоритет ансибл-ревью. Полный контекст:
[docs/drafts/ansible-review.md](../drafts/ansible-review.md) §3.
Связано: playbook-caddyproxy.yml, playbook-netdata.yml, playbook-authelia.yml,
playbook-gitea.yml, playbook-memos.yml.
@@ -0,0 +1,18 @@
# Инвентарь: `host_vars`, именованные группы, точечный `become`
**Приоритет:** низкий
- **`production.yml` и `timeweb.yml`** оба объявляют хост `server` под
`ungrouped:`, хост-специфичные данные (`application_dir`,
`mount_external_storage`, `ansible_host`, `ansible_user`) вписаны инлайн.
Конвенциональнее — `host_vars/server.yml` и хост в именованной группе. Два
инвентаря с одинаковым именем хоста + `hosts: all` — ошибка `-i` молча уедет не
туда. (`timeweb.yml` — артефакт завершённой миграции, заодно решить, нужен ли
он ещё.)
- `ansible_become: true` глобально — всё бежит под root. Для личного сервера
прагматично; точечный `become`/`become_user` ближе к наименьшим привилегиям, но
это низкий приоритет.
Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §6.
Связано: production.yml, timeweb.yml.
@@ -0,0 +1,20 @@
# Причесать роль `owner` под конвенции
**Приоритет:** низкий
Роль `owner` разошлась по стилю с `eget`/`secrets`:
- **`roles/owner/tasks/main.yml:2-10`** — валидация аргументов через `fail` +
`when`, причём две задачи с **идентичным именем**. `eget` для того же делает
`assert` (`roles/eget/tasks/main.yml:15`). Привести к одному стилю — `assert`
либо декларативный `meta/argument_specs.yml`.
- **`roles/owner/tasks/main.yml:32,53`** — устаревшие `with_items`/`with_dict`;
конвенция — `loop` (`loop: "{{ owner_ssh_keys }}"`,
`loop: "{{ owner_env_dict | dict2items }}"`).
- У `owner` нет `meta/main.yml` и README, тогда как у `eget` и `secrets` есть.
- Имена задач с точкой на конце (`"Prepare env variables."`) — ansible-lint в
строгом профиле это ловит.
Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §5.
Связано: roles/owner, roles/eget (образец), roles/secrets.
+25
View File
@@ -0,0 +1,25 @@
# Точечные фиксы идемпотентности и баг имени play
**Приоритет:** средний
Быстрые пойнтовые правки без структурных изменений:
- **`playbook-wanderer.yml:2`** — play назван `"Configure gramps application"`
при `app_name: "wanderer"` (копипаст из gramps). Поправить имя. _(баг)_
- **`playbook-netdata.yml:118-125`** — `changed_when: ...rc != 0` для read-only
запроса лишён смысла; должно быть `changed_when: false`. Лучше заменить
`shell: grep docker /etc/group` на модуль `ansible.builtin.getent` — уйдёт
`pipefail` и хрупкий парсинг.
- **`playbook-eget.yml:23-78`** — восемь `command` с `changed_when: false`, хотя
реально ставят/обновляют бинарники: прогон всегда «ok», теряется честность
`--diff`. Ставить через роль `eget` (она корректно проверяет версию) или через
проверку версии.
- **`playbook-memos.yml:57-67`** и аналоги — сборка `backup-targets` через
`lineinfile` в цикле не удаляет устаревшие строки при изменении списка; `mode:
"0750"` на файле-списке выглядит как copy-paste. Чище — `template`/`copy:
content` со всем списком.
Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §4, §7.
Связано: playbook-wanderer.yml, playbook-netdata.yml, playbook-eget.yml,
playbook-memos.yml, roles/eget.
+25
View File
@@ -0,0 +1,25 @@
# Фоновая зачистка стиля и конфигурации Ansible
**Приоритет:** низкий
Косметика и мелочи конфигурации, копятся в один проход:
- **sudoers**: `playbook-backups.yml:52-59` правит `/etc/sudoers` через
`lineinfile`. Конвенция — отдельный файл в `/etc/sudoers.d/` (через
`copy`/`template` с `validate: visudo -cf %s`).
- **`.ansible-lint.yml`** содержит только `exclude_paths`, профиль не задан явно,
хотя AGENTS.md заявляет «профиль production». Прописать `profile: production`
либо поправить документацию.
- **`ansible.cfg`** минимален — добавить `stdout_callback = yaml`,
`interpreter_python = auto_silent`, `force_handlers = true` (последнее нужно
вместе с переходом на handlers).
- Несогласованные кавычки и пути (`'directory'` vs `"directory"`,
`src: "./files/..."` vs `src: "files/..."`, одинарные кавычки в
`playbook-all-setup.yml`).
- **`playbook-system.yml:24`** — `apt` без `cache_valid_time`, обновляет кэш
каждый прогон.
Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §8.
Связано: playbook-backups.yml, .ansible-lint.yml, ansible.cfg,
playbook-system.yml, playbook-all-setup.yml.
+17
View File
@@ -0,0 +1,17 @@
# Алерты на проблемные контейнеры
**Приоритет:** высокий
wakapi однажды упал на миграциях и несколько дней крутился в restart-loop —
никто не узнал. Docker считал контейнер «running», пока процесс жив. Нужны два
слоя: (1) healthcheck + `start_period` в compose-шаблонах, чтобы Docker видел
реальное состояние (окно `start_period` даёт миграциям отработать); (2) алерты
через Netdata на restart-loop (счётчик перезапусков растёт) и на
`container_health_status != healthy` дольше M минут, канал нотификаций — один
(Telegram/ntfy — выбрать). restart policy оставляем `unless-stopped` (алерт +
ручное решение вместо `on-failure`, который не встаёт после ребута). Опционально
позже — Uptime Kuma для внешнего HTTP-чека по публичным URL.
Полный план: [docs/drafts/alerts.md](../drafts/alerts.md).
Связано: playbook-netdata.yml, compose-шаблоны всех сервисов, project_server_specs.
+20
View File
@@ -0,0 +1,20 @@
# Gitea runner on-demand в Yandex Cloud
**Приоритет:** средний
Self-hosted раннер Gitea Actions, активный только во время сборки. Сборок ~10/нед
по ~5 мин — ВМ 24/7 даёт утилизацию ~1% (≈$23/мес), on-demand — ≈$1/мес (экономия
~95%). Архитектура: push → Gitea webhook → Cloud Function (HMAC-валидация +
стейт-машина старта) → Compute API стартует ВМ → `act_runner` в docker забирает
джобу; на ВМ probe (телеметрия раз в 30с) + decide (решение раз в 1 мин) гасят ВМ
после idle-окна через Compute REST из metadata-токена. Три слоя страховки от
зависшей ВМ (soft idle-stop, probe-staleness, внешний алерт Cloud Monitoring на
uptime > 24ч). Плейбук `playbook-gitea-runner.yml` + набор invoke-тасков
(`runner-bootstrap`, `runner-deploy-function`, `runner-pl`, ...).
Дизайн проработан целиком (ресурсы YC, секреты, стоимость, принятые риски, план
внедрения из 11 шагов). Открытые вопросы: канал нотификаций, executor (docker),
webhook на PR. Полный дизайн:
[docs/drafts/gitea-runner-on-demand.md](../drafts/gitea-runner-on-demand.md).
Связано: playbook-gitea.yml, roles/owner, vars/secrets.yml, tasks.py.
+21
View File
@@ -0,0 +1,21 @@
# Синхронизация общих Ansible-ролей между репозиториями
**Приоритет:** средний
Серверы управляются изолированными репозиториями (pet-project-server, buckland,
далее — торрент-бокс). Кастомные роли (`owner`, `eget`, `secrets`) концептуально
общие, но физически продублированы и дрейфуют (`owner` в buckland стал мёртвым
кодом). Решение под pets-подход: канон ролей в `~/projects/private/ansible-shared/`,
каждый репозиторий держит закоммиченную копию (она же «пин версии») и
синхронизируется через rsync-таски invoke — `roles-pull` / `roles-push` /
`roles-status`. Подписка на роли явная и пер-серверная (`SYNCED_ROLES` в
`tasks.py`). Обновление роли на сервере — осознанный коммит с читаемым диффом, не
скрытый сайд-эффект. Предохранитель от дрейфа — предупреждение в lefthook.
Отвергнуты: общий `roles_path`, симлинки, submodule/subtree, galaxy-из-git,
монорепо (все ломают самодостаточность или вводят лишнюю петлю).
Подход обсуждён, реализация не начата. Полный дизайн + план внедрения:
[docs/drafts/shared-roles-sync.md](../drafts/shared-roles-sync.md).
Связано: roles/owner, roles/eget, roles/secrets, tasks.py, lefthook.yml,
project_isolated_repos_shared_roles, project_shared_invoke_command_surface.