Docs: собрать беклог задач из drafts
This commit is contained in:
@@ -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` _(ансибл-ревью)_
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user