Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb141807c9
|
||
|
|
0b2a3bd19c
|
||
|
|
6a0d786bdd
|
||
|
|
6d28d361e2
|
||
|
|
1b2bc61cff
|
||
|
|
fad0d78718
|
||
|
|
cb43273ad5
|
||
|
|
d9656b8cdb
|
||
|
|
049ced48f5
|
||
|
|
5dc199f3ce
|
||
|
|
156df94f00
|
||
|
|
d211052914
|
||
|
|
f505e9ebeb
|
||
|
|
b7b1f49c8a
|
||
|
|
fbd0a66f44
|
||
|
|
f9a8c64377
|
||
|
|
3b846536f9
|
||
|
|
0e7300ed0e
|
||
|
|
f3c76ca583
|
||
|
|
72d8a9932e
|
||
|
|
564432755d
|
||
|
|
4d976585ec
|
||
|
|
9c156df0c7
|
||
|
|
1cf660b4fd
|
@@ -2,14 +2,15 @@
|
||||
|
||||
## Обзор
|
||||
|
||||
Ansible-проект для автоматизации личного сервера. Плейбуки разворачивают докеризированные приложения (gitea, authelia, miniflux, wakapi, memos, outline, gramps, calibre, wanderer, remembos, transcriber и др.) через выделенных системных пользователей, Caddy-прокси и Yandex Docker Registry. Секреты управляются через Ansible Vault.
|
||||
Ansible-проект для автоматизации личного сервера. Плейбуки разворачивают докеризированные приложения (gitea, authelia, miniflux, wakapi, memos, outline, gramps, calibre, wanderer, remembos, transcriber и др.) через выделенных системных пользователей и Caddy-прокси. Секреты управляются через Ansible Vault.
|
||||
|
||||
## Структура проекта
|
||||
|
||||
- `playbook-*.yml` — плейбуки по одному на сервис, `playbook-all-*.yml` для групповых запусков.
|
||||
- `production.yml` — инвентарь с единственным хостом `server`.
|
||||
- `vars/*.yml` — переменные приложений и образов, `vars/secrets.yml` — зашифрованные секреты (vault).
|
||||
- `roles/` — кастомные роли (`eget`, `owner`, `secrets`), галактические роли в `galaxy.roles/`.
|
||||
- `group_vars/all/` — общие переменные для всех хостов: `main.yml` (открытые) и `secrets.yml` (зашифрованные vault). Подхватываются Ansible автоматически, `vars_files` в плейбуках не нужен. Имя `secrets.yml` обязательно: pre-commit-хук и `.crushignore` ищут слово `secret(s)` в имени файла.
|
||||
- `vars/*.yml` — переменные отдельных приложений (homepage, transcriber), подключаются через `vars_files`.
|
||||
- `roles/` — кастомные роли (`eget`, `owner`, `secrets`, `app_image`), галактические роли в `galaxy.roles/`.
|
||||
- `files/<app>/` — docker-compose шаблоны, конфиги, скрипты бэкапов для каждого сервиса.
|
||||
- `templates/` — общие шаблоны (например `env.template`).
|
||||
- `scripts/` — вспомогательные Python-скрипты (SMTP-утилиты для Yandex Cloud Postbox).
|
||||
@@ -64,7 +65,7 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
- `playbook-wakapi.yml` — трекинг времени.
|
||||
- `playbook-memos.yml` — заметки.
|
||||
- `playbook-outline.yml` — вики/база знаний.
|
||||
- `playbook-homepage.yml` — кастомная домашняя страничка на apex `vakhrushev.me` (образ из Yandex Registry).
|
||||
- `playbook-homepage.yml` — кастомная домашняя страничка на apex `vakhrushev.me` (образ собирается локально ролью `app_image`).
|
||||
- `playbook-dashboard.yml` — дашборд со списком сервисов (gethomepage.dev) на `start.vakhrushev.me`, закрыт за Authelia.
|
||||
- `playbook-rssbridge.yml` — RSS-агрегатор.
|
||||
- `playbook-netdata.yml` — мониторинг.
|
||||
@@ -72,7 +73,7 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
- `playbook-goaccess.yml` — аналитика веб-логов Caddy в реальном времени.
|
||||
- `playbook-gramps.yml` — генеалогия.
|
||||
- `playbook-calibre.yml` — управление электронными книгами.
|
||||
- `playbook-transcriber.yml` — транскрибация (образ из Yandex Registry).
|
||||
- `playbook-transcriber.yml` — транскрибация (образ собирается локально ролью `app_image`).
|
||||
- `playbook-wanderer.yml` — пешие маршруты.
|
||||
- `playbook-remembos.yml` — интервальное повторение.
|
||||
- `playbook-tuwunel.yml` — Matrix-сервер (Tuwunel) с federation-делегацией на apex-домен.
|
||||
@@ -83,7 +84,6 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
|
||||
- `playbook-all-setup.yml` — системная настройка целиком (system + docker + eget + backups).
|
||||
- `playbook-all-applications.yml` — деплой всех приложений.
|
||||
- `playbook-homepage-registry.yml` / `playbook-transcriber-registry.yml` — загрузка образов в Yandex Registry.
|
||||
- `playbook-remove-user-and-app.yml` — удаление пользователя и приложения (`--extra-vars user_name=<name>`).
|
||||
|
||||
## Роли
|
||||
@@ -91,14 +91,15 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
- `roles/owner` — создаёт системного пользователя/группу для приложения, настраивает SSH-ключи, переменные окружения (~/.env, ~/.bashrc).
|
||||
- `roles/eget` — скачивает и устанавливает утилиту eget.
|
||||
- `roles/secrets` — управляет vault-зашифрованными файлами секретов для приложений.
|
||||
- `roles/app_image` — собирает docker-образ приложения на control-хосте (контракт приложения — `task image` с тегом из `$BUILD_ID`) и везёт его на сервер через `docker save`/`load`, без реестра; отдаёт факт `app_image_tag` для compose. Общая роль, синхронизируется с каноном `ansible-roles` (`inv roles-pull`).
|
||||
|
||||
Галактические роли (`galaxy.roles/`): `geerlingguy.security`, `geerlingguy.docker`, `yatesr.timezone`.
|
||||
|
||||
## Шаблоны и переменные
|
||||
|
||||
- Суффиксы шаблонов: `.template.yml`, `.template.sh`, `.template.cfg`, `.template.conf`, `.template.toml`, `.template` (для файлов без естественного расширения) — рендерятся Ansible модулем `template`. Расширение оригинального формата сохраняется после `.template.` ради подсветки синтаксиса в редакторе.
|
||||
- Большинство приложений определяют переменные inline в плейбуке. Отдельные файлы переменных только у homepage и transcriber (`vars/homepage.yml`, `vars/transcriber.yml` + `vars/transcriber.images.yml`).
|
||||
- Общие переменные из `vars/secrets.yml`: `application_dir`, `bin_prefix`, `primary_user` и др.
|
||||
- Большинство приложений определяют переменные inline в плейбуке. Отдельные файлы переменных только у homepage и transcriber (`vars/homepage.yml`, `vars/transcriber.yml`).
|
||||
- Общие переменные из `group_vars/all/` (`main.yml` + vault-файл `secrets.yml`): `application_dir`, `bin_prefix`, `primary_user` и др. Загружаются автоматически для группы `all`, поэтому плейбуки их не перечисляют. Хост-специфичные значения переопределяются в инвентаре — теперь они имеют приоритет над `group_vars`.
|
||||
- Каждое приложение: `app_name`, `app_user`, `app_owner_uid`, `app_owner_gid`, `base_dir`, `data_dir`.
|
||||
- UID/GID сервисов: новое соглашение — диапазон `11xx`, причём `app_owner_uid == app_owner_gid` (одно число на сервис). Новому приложению берём следующий свободный номер по возрастанию. Старые сервисы ещё сидят на легаси-нумерации `10xx` (часто с разными uid/gid) — их не трогаем, но новые заводим только в `11xx`.
|
||||
|
||||
@@ -114,6 +115,12 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
- `gitleaks` — поиск секретов в staged-файлах.
|
||||
- Проверка что секретные файлы зашифрованы vault.
|
||||
|
||||
## Конвенции
|
||||
|
||||
Договорённости о том, как делать однотипные вещи, живут в [`docs/conventions/`](docs/conventions) — одна конвенция на файл, у каждой статус (рекомендуемая / обязательная) и честный список уже существующих отступлений. Это правила на будущее, в отличие от [`docs/adr/`](docs/adr) (однажды принятые решения, постфактум и неизменяемо) и [`docs/drafts/`](docs/drafts) (черновики и хроника). Перед тем как заводить новое приложение или директорию — заглянуть туда.
|
||||
|
||||
- [Категории директорий приложения](docs/conventions/app-directories.md) — содержимое `base_dir` делится на конфигурацию (восстанавливается плейбуком, бэкап не нужен), данные (создаёт приложение, бэкапить обязательно) и кеш (создаёт приложение, перегенерирует само). Из категорий механически выводится `backup-targets`.
|
||||
|
||||
## Соглашения по коду
|
||||
|
||||
- Отступы: 2 пробела для YAML/Jinja, 4 пробела в остальных файлах (`.editorconfig`).
|
||||
|
||||
@@ -31,7 +31,7 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
- Безопасность осуществляется с помощью `ufw` и `fail2ban`.
|
||||
- Докер используется для запуска и изоляции приложений. Для загрузки образов настраивается Yandex Docker Registry.
|
||||
- Выход во внешнюю сеть через proxy server [Caddy](https://caddyserver.com/).
|
||||
- Чувствительные данные в [secrets.yml](vars/secrets.yml) зашифрованы с помощью Ansible Vault.
|
||||
- Чувствительные данные в [secrets.yml](group_vars/all/secrets.yml) зашифрованы с помощью Ansible Vault.
|
||||
- Для мониторинга за сервером устанавливается [netdata](https://github.com/netdata/netdata).
|
||||
|
||||
## Настройка DNS
|
||||
@@ -43,7 +43,7 @@ uv run ansible-galaxy install --role-file requirements.yml
|
||||
Деплой приложения через ansible:
|
||||
|
||||
```bash
|
||||
uv run ansible-playbook ansible-playbook -i timeweb.yml --diff playbook-gitea.yml
|
||||
uv run ansible-playbook -i production.yml --diff playbook-gitea.yml
|
||||
```
|
||||
|
||||
Или через таску invoke:
|
||||
@@ -55,5 +55,5 @@ uv run ansible-playbook ansible-playbook -i timeweb.yml --diff playbook-gitea.ym
|
||||
## Удаление приложения <name>
|
||||
|
||||
```bash
|
||||
uv run ansible-playbook -i timeweb.yml --diff playbook-remove-user-and-app.yml --extra-vars user_name=<name>
|
||||
uv run ansible-playbook -i production.yml --diff playbook-remove-user-and-app.yml --extra-vars user_name=<name>
|
||||
```
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
[defaults]
|
||||
inventory = ./production.yml
|
||||
host_key_checking = True
|
||||
vault_password_file = ./ansible-vault-password-file
|
||||
roles_path = ./galaxy.roles
|
||||
|
||||
# Выполнять handlers даже если play упал: иначе упавший прогон теряет
|
||||
# накопленные notify, а на следующем конфиг уже не changed — и рестарт,
|
||||
# применяющий правку, не случится никогда.
|
||||
force_handlers = True
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Локальная сборка образов вместо Yandex Container Registry
|
||||
|
||||
- Дата: 2026-07-25
|
||||
|
||||
## Контекст
|
||||
|
||||
Три приложения собственной разработки — homepage, transcriber, remembos —
|
||||
деплоились через Yandex Container Registry. Схема у всех одинаковая: образ
|
||||
собирается локально, пушится в `cr.yandex/...` отдельным плейбуком
|
||||
(`playbook-*-registry.yml`), плейбук деплоя делает `docker_login` с
|
||||
`yc_oauth_token`, а compose тянет образ по registry-пути с тегом, зашитым в git.
|
||||
|
||||
Схема тянула за собой три проблемы.
|
||||
|
||||
**Секрет несопоставимого радиуса.** Ради одного `docker login` в vault лежал
|
||||
OAuth-токен всего аккаунта Yandex Cloud — реестр, виртуалки, биллинг. 24 июля он
|
||||
вместе с ещё 52 переменными ушёл открытым текстом в транскрипт: цена одной такой
|
||||
утечки — весь аккаунт, а не один сервис.
|
||||
|
||||
**Разнесённый деплой.** Сборка жила в `task deploy` репозитория приложения,
|
||||
который сам звал `ansible-playbook`. У transcriber этот таск дополнительно правил
|
||||
`vars/transcriber.images.yml` в чужом репозитории через `yq` и делал там `git
|
||||
commit` с новым тегом. У remembos registry-плейбука не было вовсе — образ пушился
|
||||
руками, а в compose стоял тег `v0.2.0`, вбитый в git. У homepage тег был
|
||||
плавающий `:latest`, то есть ночной `docker image prune` мог подтянуть другое
|
||||
содержимое без возможности отката.
|
||||
|
||||
**Автоматизировать сборку пока нечем.** Стабильного онлайн-раннера для
|
||||
workflow gitea у нас нет (отдельная задача — раннер on-demand), так что уехать в
|
||||
CI сборки не могут. Плюс после переезда сервера в Timeweb реестр остался
|
||||
единственной ниткой, тянущейся в Yandex Cloud.
|
||||
|
||||
## Решение
|
||||
|
||||
Приложения переведены на общую роль `app_image` (канон —
|
||||
`~/projects/private/ansible-roles`, в репозитории лежит завендоренная копия в
|
||||
`roles/`, синхронизация — `inv roles-pull`/`roles-push`). Роль:
|
||||
|
||||
1. генерит случайный `BUILD_ID` — он же тег образа;
|
||||
2. запускает в директории приложения его собственную команду сборки (контракт —
|
||||
`task image`, `BUILD_ID` приходит через окружение), приложение обязано собрать
|
||||
полный образ и затегать его `<app_image_name>:$BUILD_ID`;
|
||||
3. печатает размер архива, везёт его на сервер через `docker save` → `copy` →
|
||||
`docker load`;
|
||||
4. отдаёт факт `app_image_tag`, которым compose-шаблон подставляет образ
|
||||
(`pull_policy: never` — в реестре образа нет).
|
||||
|
||||
Два мотива за этим. Первый — **упростить деплой**, пока нет стабильного
|
||||
онлайн-раннера: теперь `inv pl -- <app>` собирает, доставляет и запускает
|
||||
приложение одной командой, отдельного ритуала в репозитории приложения не
|
||||
осталось. Второй — **сократить набор секретов**: реестр и OAuth-токен выпали из
|
||||
схемы, `yc_oauth_token`, `yc_container_registry` и
|
||||
`yc_container_registry_repository` удалены из vault (коммит `6a0d786`).
|
||||
|
||||
Registry-плейбуки и `vars/transcriber.images.yml` удалены; у remembos заодно
|
||||
снесены gitea-workflow с goreleaser — они существовали только ради пуша образа в
|
||||
`cr.yandex`.
|
||||
|
||||
## Последствия
|
||||
|
||||
- `+` Из деплоя ушли внешний реестр и токен на весь аккаунт облака; в vault на
|
||||
три переменные меньше.
|
||||
- `+` Деплой любого из трёх приложений — одна команда `inv pl -- <app>`;
|
||||
«правка тега в чужом репозитории + коммит» как шаг деплоя исчезла.
|
||||
- `+` Тег каждого деплоя уникален, плавающего `:latest` больше нет. Откат —
|
||||
повторный деплой нужного коммита приложения.
|
||||
- `-` Дедупликации по слоям нет: каждый деплой везёт полный архив. Замеры
|
||||
`docker save`: homepage — 61 МБ, transcriber — 73 МБ, remembos — 21 МБ. Для
|
||||
ручного нечастого деплоя размен принят осознанно.
|
||||
- `-` Для деплоя нужен ноутбук с docker и тулчейнами: с чужой машины
|
||||
задеплоить нельзя, а `playbook-all-applications.yml` теперь собирает три
|
||||
образа за прогон.
|
||||
- `-` На сервере копятся образы от каждого прогона; чистит их ночной
|
||||
`docker image prune -af` (см. `playbook-system.yml`).
|
||||
- Контракт для новых приложений: чтобы деплоиться этой ролью, приложение должно
|
||||
иметь `task image`, тегающий образ из `$BUILD_ID`.
|
||||
- Осталось: `buckland/playbook-trackers.yml` всё ещё ходит в `cr.yandex` со своей
|
||||
копией токена в собственном vault — пока он жив, отзывать токен в облаке рано.
|
||||
@@ -63,6 +63,7 @@
|
||||
|
||||
| Дата | Запись | Статус |
|
||||
| ---------- | ---------------------------------------------------------------------------------------------- | ------ |
|
||||
| 2026-07-25 | [Локальная сборка образов вместо Yandex Container Registry](ADR-2026-07-25-local-image-build.md) | — |
|
||||
| 2026-06-22 | [Разнесение restic-операций на фазы под Intelligent Tiering](ADR-2026-06-22-restic-intelligent-tiering-phases.md) | — |
|
||||
| 2026-05-23 | [Переезд сервера с Yandex Cloud на Timeweb VPS](ADR-2026-05-23-migrate-to-timeweb.md) | — |
|
||||
| 2026-04-04 | [Apprise как шлюз уведомлений](ADR-2026-04-04-apprise-notifications.md) | — |
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Кладбище беклога
|
||||
- 2026-07-25 `secrets-env-to-file` — Секреты приложений через `*_FILE`, а не inline `environment:`. Причина: разложена по приложениям на secrets-file-outline/-wakapi/-authelia/-gramps/-gitea/-tududi; матрица поддержки уехала в docs/drafts/secrets-file-support.md, wanderer отпал — файловых секретов не умеет. Был приоритет: средний.
|
||||
+19
-14
@@ -17,35 +17,40 @@ _(sec-ревью)_ — три независимых security-ревью на м
|
||||
|
||||
## Высокий
|
||||
|
||||
- [Закрыть admin API Caddy](caddy-admin-api-localhost.md) — `admin :2019` слушает всю docker-сеть; любой контейнер может переписать маршрутизацию и снять forward_auth _(sec-ревью)_
|
||||
- [Закрыть admin API Caddy (localhost вместо всей сети)](caddy-admin-api-localhost.md) — `admin :2019` слушает всю docker-сеть; любой контейнер может переписать маршрутизацию и снять forward_auth _(sec-ревью)_
|
||||
- [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_
|
||||
- [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_
|
||||
- [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata
|
||||
- [Рестарт контейнеров через handlers](ansible-handlers-restart.md) — `state: restarted` выполняется безусловно на каждом прогоне (лишний downtime), нет ни одного handler _(ансибл-ревью)_
|
||||
- [Никаких секретов в выводе плейбуков](no-secrets-in-playbook-output.md) — `inv pl` всегда с `--diff`, и каждая задача, рендерящая файл с секретом, печатает его в терминал; `no_log` стоит ровно в одном месте из четырнадцати
|
||||
|
||||
## Средний
|
||||
|
||||
- [Секреты через `*_FILE`, а не inline `environment:`](secrets-env-to-file.md) — outline/wakapi/tududi/gitea/gramps/tuwunel держат секреты в env; видны в `docker inspect` и `/proc` _(sec-ревью)_
|
||||
- [Захардить мониторинг-стек](harden-monitoring-stack.md) — netdata/dozzle: лишние привилегии + docker.sock + доверие Remote-User в общей сети; socket-proxy + monitoring_network _(sec-ревью)_
|
||||
- [Ограничить `/metrics` Miniflux](miniflux-metrics-restrict.md) — `METRICS_ALLOWED_NETWORKS=0.0.0.0/0` + прокси без ограничения пути отдаёт метрики публично _(sec-ревью)_
|
||||
- [Закрыть calibre-web forward-auth](calibre-forward-auth.md) — домен без forward_auth, защита на встроенной авторизации с дефолтом `admin/admin123` _(sec-ревью)_
|
||||
- [Понизить log level Authelia до info](authelia-log-level.md) — `debug` в проде утекает детали аутентификации и OIDC через `docker logs`/dozzle _(sec-ревью)_
|
||||
- [Захардить мониторинг-стек (netdata / dozzle / goaccess)](harden-monitoring-stack.md) — netdata/dozzle: лишние привилегии + docker.sock + доверие Remote-User в общей сети; socket-proxy + monitoring_network _(sec-ревью)_
|
||||
- [Ограничить эндпоинт `/metrics` Miniflux](miniflux-metrics-restrict.md) — `METRICS_ALLOWED_NETWORKS=0.0.0.0/0` + прокси без ограничения пути отдаёт метрики публично _(sec-ревью)_
|
||||
- [Закрыть calibre-web forward-auth и проверить дефолтные креды](calibre-forward-auth.md) — домен без forward_auth, защита на встроенной авторизации с дефолтом `admin/admin123` _(sec-ревью)_
|
||||
- [Понизить log level Authelia с debug до info](authelia-log-level.md) — `debug` в проде утекает детали аутентификации и OIDC через `docker logs`/dozzle _(sec-ревью)_
|
||||
- [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_
|
||||
- [Запретить SSH-вход под root](ssh-disable-root-login.md) — `PermitRootLogin yes` пускает root по ключу; наименьшие привилегии + defense-in-depth (пароль уже off) _(sec-ревью)_
|
||||
- [Убрать устаревший инвентарь после миграции](stale-inventory-cleanup.md) — `production.yml` указывает на мёртвый YC-хост, живой — `timeweb.yml`; имена инвертированы
|
||||
- [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
|
||||
- [Уведомления о размере приложений и диске](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_
|
||||
- [Синхронизация общих Ansible-ролей между репозиториями](shared-roles-sync.md) — `owner`/`eget`/`secrets` дублируются между репозиториями и дрейфуют; канон в ansible-shared + rsync-таски invoke
|
||||
- [Уведомления о размере приложений и заполнении диска](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_
|
||||
- [Handlers рестарта для оставшихся приложений](handlers-remaining-apps.md) — apprise/dashboard/transcriber/miniflux монтируют конфиг в контейнер, но рестарта нет вовсе; правка конфига не подхватывается
|
||||
- [Composable-роль `backup`](ansible-backup-role.md) — бэкап — самый чистый шов для extraction (одинаков у всех, различается только список targets) _(ансибл-ревью)_
|
||||
- [Точечные фиксы идемпотентности и баг имени play](ansible-quick-fixes.md) — быстрые правки: имя play в wanderer, `changed_when` в netdata/eget, backup-targets через template _(ансибл-ревью)_
|
||||
- [Вывести из оборота секреты с широкими правами](narrow-secret-scopes.md) — `yc_oauth_token` (весь аккаунт Yandex Cloud) уже выведен; осталось проставить радиус остальным переменным vault и сузить широкие
|
||||
- [outline: секреты из файлов](secrets-file-outline.md) — умеет generic `<NAME>_FILE` для всех пяти секретов; единственное приложение, где выносится вообще всё, включая пароль БД
|
||||
- [wakapi: секреты из файлов](secrets-file-wakapi.md) — salt, SMTP и OIDC выносятся либо через generic `_FILE`, либо через `config.yml`; для OIDC второй путь надёжнее из-за бага с `=` в значении
|
||||
- [authelia: секреты из файлов](secrets-file-authelia.md) — пять полей закрывает `_FILE`, jwks-ключ и client secret — только template-фильтр; заодно четыре client secret можно перевести в хэш и перестать считать секретами
|
||||
- [gramps: секретный ключ из файла](secrets-file-gramps.md) — entrypoint читает `/app/secret/secret`, если переменная не задана, и этот путь уже смонтирован — самая дешёвая правка из всей серии
|
||||
|
||||
## Низкий
|
||||
|
||||
- [Мелкий хардненинг Caddy](caddy-hardening-nits.md) — HSTS-заголовок, убрать лишний `NET_ADMIN`, убрать leftover-листенер `:29999` _(sec-ревью)_
|
||||
- [Мелкий хардненинг контейнеров](container-hardening-nits.md) — `no-new-privileges`, non-root `user:`, homepage off `:latest`, соглашение о `127.0.0.1:` портах _(sec-ревью)_
|
||||
- [Унифицировать ACL техпанелей Authelia](authelia-acl-unify.md) — rssbridge (SSRF) и dashboard за `one_factor` без subject; свести к `group:admins` + `two_factor` _(sec-ревью)_
|
||||
- [`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` _(ансибл-ревью)_
|
||||
- [Инвентарь: `host_vars`, именованные группы, точечный `become`](ansible-inventory-hostvars.md) — хост-специфику в `host_vars/server.yml`, хост в именованную группу, глобальный root → точечный become _(ансибл-ревью)_
|
||||
- [Фоновая зачистка стиля и конфигурации Ansible](ansible-style-nits.md) — sudoers.d, профиль ansible-lint, `ansible.cfg`, кавычки, `cache_valid_time` _(ансибл-ревью)_
|
||||
- [Logrotate для логов бэкапов](backup-logs-logrotate.md) — логи `backup-all.py`/restic копятся без ротации _(tududi)_
|
||||
- [[idea] Где хранить пароль ansible-vault](vault-password-storage.md) — пароль открытым текстом рядом с репо, `ansible.cfg` подставляет его в любую ansible-команду; вариант — доставать из KeePassXC
|
||||
- [gitea: пароль SMTP из файла](secrets-file-gitea.md) — умеет `GITEA__mailer__PASSWD__FILE`, но пишет значение открытым текстом в `app.ini` — выигрыш только против `docker inspect`
|
||||
- [tududi: секреты в смонтированный `.env`](secrets-file-tududi.md) — файловых секретов не умеет вовсе; единственный путь — смонтировать `.env` под `dotenv`, чтобы значения не попадали в `Config.Env`
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# `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.
|
||||
@@ -1,20 +0,0 @@
|
||||
# Рестарт контейнеров через 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.
|
||||
@@ -2,17 +2,14 @@
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
- **`production.yml` и `timeweb.yml`** оба объявляют хост `server` под
|
||||
`ungrouped:`, хост-специфичные данные (`application_dir`,
|
||||
`mount_external_storage`, `ansible_host`, `ansible_user`) вписаны инлайн.
|
||||
Конвенциональнее — `host_vars/server.yml` и хост в именованной группе. Два
|
||||
инвентаря с одинаковым именем хоста + `hosts: all` — ошибка `-i` молча уедет не
|
||||
туда. (`timeweb.yml` — артефакт завершённой миграции, заодно решить, нужен ли
|
||||
он ещё.)
|
||||
- **`production.yml`** объявляет хост `server` под `ungrouped:`,
|
||||
хост-специфичные данные (`ansible_host`, `ansible_user`, `ansible_become`)
|
||||
вписаны инлайн. Конвенциональнее — `host_vars/server.yml` и хост в именованной
|
||||
группе, а не в `ungrouped`.
|
||||
- `ansible_become: true` глобально — всё бежит под root. Для личного сервера
|
||||
прагматично; точечный `become`/`become_user` ближе к наименьшим привилегиям, но
|
||||
это низкий приоритет.
|
||||
|
||||
Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §6.
|
||||
|
||||
Связано: production.yml, timeweb.yml.
|
||||
Связано: production.yml.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Точечные фиксы идемпотентности и баг имени 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.
|
||||
@@ -9,10 +9,9 @@
|
||||
`/root/.gramps`), rssbridge, dozzle, homepage и valkey/postgres-сайдкары gramps.
|
||||
Дотянуть там, где образ позволяет (rssbridge, valkey) — продолжение уже принятой
|
||||
в проекте практики.
|
||||
- **homepage на `:latest`** (`vars/homepage.yml`) — плавающий тег + ночной `docker
|
||||
image prune` может подтянуть другое содержимое без отката. Перевести на
|
||||
иммутабельный `<sha>-<timestamp>` по образцу transcriber
|
||||
(`vars/transcriber.images.yml`).
|
||||
- ~~**homepage на `:latest`**~~ — **снято**: переход на роль `app_image` дал
|
||||
каждому деплою уникальный тег из `BUILD_ID`, плавающего `latest` больше нет
|
||||
ни у homepage, ни у остальных.
|
||||
- **Соглашение о портах**: зафиксировать в AGENTS.md, что новые `ports:` — только
|
||||
с `127.0.0.1:`. Docker публикует порты мимо UFW, поэтому дисциплина
|
||||
localhost-биндинга — единственный контроль (сейчас соблюдается: apprise, wanderer).
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# Handlers рестарта для оставшихся приложений
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Паттерн `handlers` + `notify` внедрён в caddyproxy, netdata, authelia, bifrost,
|
||||
remembos (см. задачу `ansible-handlers-restart`, закрыта). Остались плейбуки, где
|
||||
конфиг bind-моунтится в контейнер, но рестарта нет **вообще** — правка конфига без
|
||||
изменения docker-compose.yml не подхватывается до следующего пересоздания
|
||||
контейнера:
|
||||
|
||||
- `playbook-apprise.yml` — `server.cfg` → `config_dir:/config`.
|
||||
- `playbook-dashboard.yml` — `config/*.yaml` → `config_dir:/app/config`.
|
||||
- `playbook-transcriber.yml` — `config.toml` bind-моунтится **отдельным файлом**
|
||||
(`{{ config_file }}:/config/config.toml:ro`). Худший случай: `template`
|
||||
перезаписывает файл с новым inode, и контейнер продолжает видеть старый до
|
||||
рестарта.
|
||||
- `playbook-miniflux.yml` — файлы секретов роли `secrets` → `secrets_dir:/secrets:ro`,
|
||||
читаются через `*_FILE` при старте.
|
||||
|
||||
Заодно проверить `playbook-goaccess.yml`: конфиг там не монтируется, но
|
||||
`Dockerfile`/`entrypoint.sh` меняются без изменения compose-файла — надо
|
||||
убедиться, что `build: always` действительно пересоздаёт контейнер.
|
||||
|
||||
Отдельный кандидат: caddy сейчас перезапускается целиком, хотя умеет
|
||||
`caddy reload` — применение Caddyfile без разрыва соединений на реверс-прокси,
|
||||
через который идёт весь трафик. В `playbook-caddyproxy.yml` для этого уже лежит
|
||||
закомментированный блок с `docker_compose_v2_exec`.
|
||||
|
||||
Связано: playbook-apprise.yml, playbook-dashboard.yml, playbook-transcriber.yml,
|
||||
playbook-miniflux.yml, playbook-goaccess.yml, playbook-caddyproxy.yml.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Вывести из оборота секреты с широкими правами
|
||||
|
||||
**Приоритет:** Средний
|
||||
|
||||
Показательный случай — `yc_oauth_token`: OAuth-токен всего аккаунта Yandex Cloud
|
||||
(реестр, виртуалки, биллинг) ради одной операции, `docker login` в плейбуках
|
||||
homepage, transcriber и remembos. 24 июля он вместе с ещё 52 переменными ушёл
|
||||
открытым текстом в транскрипт: цена одной такой утечки — весь аккаунт, а не один
|
||||
сервис. Из этого репозитория он уже выведен (см. ниже), но такой перекос радиуса
|
||||
надо найти и снять у остальных переменных.
|
||||
|
||||
Принцип, к которому идём: секрет даёт доступ ровно к тому, ради чего заведён.
|
||||
Всё, что шире, — либо заменяется на скоупированный доступ (сервисный аккаунт с
|
||||
минимальными ролями вместо токена аккаунта, отдельный ключ на бакет вместо
|
||||
общего), либо выводится из оборота совсем, либо риск принимается явно и
|
||||
записывается.
|
||||
|
||||
Ядро задачи уже закрыто: homepage, transcriber и remembos собирают образы
|
||||
локально ролью `app_image`, `docker_login` из плейбуков ушёл, а `yc_oauth_token`
|
||||
вместе с `yc_container_registry*` удалён из vault (коммит `6a0d786`).
|
||||
|
||||
Дальше — ревизия остального: пройти по `group_vars/all/secrets.yml` и проставить
|
||||
каждой переменной радиус («один сервис» / «весь аккаунт у провайдера» / «все
|
||||
бэкапы»). Кандидаты в широкие видны уже по назначению — ключи S3 для restic,
|
||||
доступ к Postbox, — но разбирать их надо в этой задаче, а не заранее.
|
||||
|
||||
Готово, когда у каждой переменной vault записан радиус, а каждая широкая либо
|
||||
заменена на скоупированную, либо помечена принятым риском с причиной.
|
||||
|
||||
Держать в уме: [gitea-runner-on-demand](gitea-runner-on-demand.md) заводит новый
|
||||
доступ к API Yandex Cloud (старт и остановка ВМ). Если она поедет раньше — сразу
|
||||
сервисный аккаунт с минимальными ролями, а не переиспользование OAuth-токена,
|
||||
иначе задача вернётся тем же текстом.
|
||||
|
||||
Ротация уже утёкших значений — отдельный сюжет, он ведётся в
|
||||
`SECRETS-ROTATION.md` (файл вне git). Здесь речь не о смене значения, а о
|
||||
сокращении того, что это значение открывает.
|
||||
|
||||
Связано: group_vars/all/secrets.yml, playbook-homepage.yml,
|
||||
playbook-transcriber.yml, playbook-remembos.yml,
|
||||
[vault-password-storage](vault-password-storage.md),
|
||||
[справка по файловым секретам](../drafts/secrets-file-support.md).
|
||||
@@ -0,0 +1,93 @@
|
||||
# Никаких секретов в выводе плейбуков
|
||||
|
||||
**Приоритет:** Высокий
|
||||
|
||||
`inv pl` всегда запускает `ansible-playbook --diff`, поэтому любая задача
|
||||
`template`/`copy`, чей целевой файл содержит переменную vault, печатает значение
|
||||
в терминал — целой строкой, при каждом изменении файла. `no_log: true` стоит
|
||||
ровно в одном месте на весь репозиторий (`roles/secrets`), и именно поэтому
|
||||
miniflux деплоится молча, а остальные — нет.
|
||||
|
||||
Это тот же класс, что инцидент 24 июля: секрет попадает не в vault-файл, а в
|
||||
поток вывода, у которого нет никакой модели угроз. Скроллбек терминала,
|
||||
мультиплексор, лог CI, транскрипт агента — всё это места, куда расшифрованному
|
||||
значению попадать не должно, и куда оно попадает бесплатно.
|
||||
|
||||
## Инвентарь (сверено 2026-07-25)
|
||||
|
||||
Задачи без `no_log`, рендерящие файл с секретом:
|
||||
|
||||
- **backups** — `config.template.toml`, `restic-shell.template.sh`,
|
||||
`rclone.template.conf`: пароли репозиториев restic, ключи S3, доступ rclone
|
||||
- **authelia** — `configuration.template.yml`: jwt-секрет, секрет сессий, ключ
|
||||
шифрования хранилища, hmac и jwks OIDC, четыре client secret, SMTP; плюс
|
||||
`users.secrets.yml` — vault-файл копируется как есть, дифф покажет его
|
||||
расшифрованным
|
||||
- **transcriber** — `config.secrets.toml`, тоже vault-файл копией
|
||||
- **bifrost** — `config.template.json`: ключ DeepSeek, ключ шифрования
|
||||
- **apprise** — `server.template.cfg`: токен бота Telegram, SMTP
|
||||
- **remembos** — `config.template.toml`: токены memos и Telegram
|
||||
- **outline, tududi, wakapi, wanderer, gramps, gitea** — секреты в
|
||||
`environment:` docker-compose, см. серию задач `secrets-file-*`
|
||||
|
||||
Чисто: miniflux (образец), memos, calibre, tuwunel, dashboard, caddy, netdata,
|
||||
dozzle, goaccess, rssbridge, homepage.
|
||||
|
||||
`docker_login` в homepage, transcriber и remembos с `yc_oauth_token` в аргументах
|
||||
модуля — снят: приложения собирают образы локально ролью `app_image`, реестра и
|
||||
токена в плейбуках больше нет.
|
||||
|
||||
## Рычаги и их цена
|
||||
|
||||
- **`no_log: true`** на задачу — прячет результат целиком, включая дифф. Так уже
|
||||
сделано в `roles/secrets`. Цена: не видно, что именно изменилось; на отладке
|
||||
снимать руками.
|
||||
- **`diff: false`** на задачу — прячет только содержимое диффа, статус `changed`
|
||||
остаётся виден. Хирургичнее, но не закрывает вывод при падении задачи, поэтому
|
||||
для файлов, состоящих из одних секретов, слабее.
|
||||
- **Структурный вынос** — секрет живёт в отдельном файле, который рендерит роль
|
||||
`secrets` под `no_log`, а основной файл ссылается на путь. Тогда дифф основного
|
||||
файла остаётся полезным. Это серия `secrets-file-*`, но она про `environment:`
|
||||
и не покрывает конфиги.
|
||||
- **Убрать `--diff` из `inv pl`** или выставить глобальный `ANSIBLE_NO_LOG` —
|
||||
режет обзор на всех задачах разом ради десятка проблемных. Не годится.
|
||||
|
||||
## Порядок
|
||||
|
||||
1. ~~`no_log: true` на весь инвентарь выше~~ — **сделано** (коммит `b7b1f49`,
|
||||
2026-07-25): пятнадцать задач-рендеров и три `docker_login`, всего 13
|
||||
плейбуков. Не под `no_log` осталась одна задача — compose miniflux: он тянет
|
||||
из vault только `miniflux_postgres_user` и `miniflux_postgres_database`, а это
|
||||
имя пользователя и имя базы, не секреты (в `secrets_vars` роли `secrets` их
|
||||
нет). Дифф образцового файла оставлен читаемым сознательно.
|
||||
2. Где файл смешанный (compose, конфиг authelia) — вернуться и вынести секреты в
|
||||
отдельные файлы, сняв `no_log` с основного: он нужен ради диффа, который сейчас
|
||||
и теряется. Что именно умеет каждое приложение — матрица в
|
||||
[справке](../drafts/secrets-file-support.md), сверенная по исходникам, и
|
||||
задачи `secrets-file-*` по одной на приложение. Для
|
||||
authelia (конфиг на тысячу строк, дифф там самый ценный) ответ такой: пять
|
||||
полей закрываются `AUTHELIA_..._FILE`, оставшиеся два — только
|
||||
template-фильтром, а четыре `client_secret` штатно хранятся хэшем и тогда
|
||||
вообще перестают быть секретами. Для tududi и wanderer ответа нет: они не
|
||||
умеют читать секреты из файлов, и `no_log` для них — не первый шаг, а
|
||||
единственный.
|
||||
3. Защита от дрейфа: проверка в lefthook или CI — если шаблон ссылается на
|
||||
переменную, которой нет в открытых `group_vars`/`vars`, задача, которая его
|
||||
рендерит, обязана иметь `no_log`. Иначе следующее приложение заведут по
|
||||
образцу без него. Единственное исключение на сегодня — miniflux (см. шаг 1);
|
||||
чище не заводить исключение в правиле, а перенести `miniflux_postgres_user` и
|
||||
`miniflux_postgres_database` из vault в `group_vars/all/main.yml` — секретами
|
||||
они не являются. Правка в зашифрованном файле, поэтому за пользователем.
|
||||
|
||||
Готово, когда первый (создающий все файлы) прогон любого плейбука не печатает ни
|
||||
одного значения из vault, и это держится проверкой, а не вниманием.
|
||||
|
||||
Попутно стоит выяснить, почему молчит правило `no-log-password` в ansible-lint:
|
||||
похоже, оно смотрит на список модулей с параметром `password`, а не на содержимое
|
||||
шаблонов — тогда на него в этой задаче полагаться нельзя.
|
||||
|
||||
Связано: tasks.py (`inv pl`), roles/secrets (образец), files/backups,
|
||||
files/authelia, files/bifrost, files/apprise, files/remembos,
|
||||
files/transcriber/config.secrets.toml,
|
||||
[vault-password-storage](vault-password-storage.md),
|
||||
[narrow-secret-scopes](narrow-secret-scopes.md).
|
||||
@@ -1,17 +0,0 @@
|
||||
# Секреты приложений через `*_FILE`, а не inline `environment:`
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
miniflux сделан правильно — секреты через `*_FILE` + монтирование каталога
|
||||
secrets `:ro` (`files/miniflux/docker-compose.template.yml`). Но outline
|
||||
(DATABASE_URL с паролём, `SECRET_KEY`, `UTILS_SECRET`, OIDC secret, SMTP), wakapi
|
||||
(salt, SMTP), tududi (session secret, OIDC secret, ключ LLM), gitea (SMTP),
|
||||
gramps, tuwunel кладут секреты прямыми значениями в `environment:`. Такие
|
||||
переменные читает любой член группы `docker`, любой процесс с доступом к
|
||||
`/proc/<pid>/environ`, и они попадают в `docker inspect`. Где приложение умеет
|
||||
`*_FILE`/конфиг-файл — постепенно перевести по образцу miniflux.
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13). Связка с [service-users-docker-group](service-users-docker-group.md).
|
||||
|
||||
Связано: files/outline, files/wakapi, files/tududi, files/gitea, files/gramps,
|
||||
files/tuwunel, files/miniflux (образец).
|
||||
@@ -0,0 +1,32 @@
|
||||
# authelia: секреты из файлов
|
||||
|
||||
**Приоритет:** Средний
|
||||
|
||||
В `configuration.template.yml` прямыми значениями стоят семь позиций:
|
||||
jwt-секрет валидации, секрет сессий, ключ шифрования хранилища, hmac-секрет OIDC,
|
||||
приватный jwks-ключ, четыре `client_secret` и пароль SMTP. Файл на тысячу строк —
|
||||
дифф по нему самый ценный в репозитории, и именно его сейчас приходится глушить
|
||||
целиком.
|
||||
|
||||
Пять полей закрываются переменными `AUTHELIA_..._FILE`. Оставшиеся два —
|
||||
`jwks[].key` и `client_secret` клиентов — через `_FILE` невозможны в принципе:
|
||||
валидатор отвергает ключи, содержащие `[]`. Их покрывает template-фильтр
|
||||
(`X_AUTHELIA_CONFIG_FILTERS=template` + `{{ secret "путь" }}`). Механизмы
|
||||
несовместимы в одном поле, поэтому проще выбрать фильтр — он берёт все семь
|
||||
позиций разом, `_FILE` только пять.
|
||||
|
||||
Отдельная и более дешёвая половина задачи: четыре `client_secret` штатно хранятся
|
||||
**хэшем** `$pbkdf2-sha512$...` — плейнтекст с 4.38 вызывает warning валидатора и
|
||||
нужен только при `client_secret_jwt`, чего у наших клиентов нет. После перевода
|
||||
эти поля перестают быть секретами вообще. Генератор уже есть —
|
||||
`inv authelia-gen-secret-and-hash`, проверка — `inv authelia-validate-config`.
|
||||
|
||||
Готово, когда в отрендеренном конфиге не осталось секретов открытым текстом и
|
||||
`no_log` с задачи снят — дифф конфига снова читаем.
|
||||
|
||||
Разбор по каждому полю, версии и ссылки на код:
|
||||
[справка по файловым секретам](../drafts/secrets-file-support.md).
|
||||
Из декомпозиции `secrets-env-to-file` (2026-07-25).
|
||||
|
||||
Связано: files/authelia, tasks.py (`authelia-*`), roles/secrets,
|
||||
[no-secrets-in-playbook-output](no-secrets-in-playbook-output.md).
|
||||
@@ -0,0 +1,24 @@
|
||||
# gitea: пароль SMTP из файла
|
||||
|
||||
**Приоритет:** Низкий
|
||||
|
||||
Единственный секрет gitea в compose — пароль SMTP. Механизм есть:
|
||||
`GITEA__mailer__PASSWD__FILE` работает для любого ключа `app.ini` начиная с 1.20,
|
||||
у нас 1.27.0.
|
||||
|
||||
Приоритет низкий из-за оговорки, которая съедает половину пользы:
|
||||
`environment-to-ini` **записывает содержимое файла в `app.ini` открытым текстом**,
|
||||
то есть пароль всё равно оседает на диске в `<data_dir>/gitea/conf/app.ini`. Это
|
||||
открытая проблема апстрима (#35316), исправления нет. Выигрыш реальный, но
|
||||
частичный — секрет уходит из `environment:`, `docker inspect` и окружения
|
||||
процесса, а с диска не уходит.
|
||||
|
||||
Готово, когда `docker inspect` контейнера gitea не показывает пароль SMTP. Что он
|
||||
остался в `app.ini` — известное ограничение, а не незакрытая часть задачи.
|
||||
|
||||
Механизм и ссылки на код:
|
||||
[справка по файловым секретам](../drafts/secrets-file-support.md).
|
||||
Из декомпозиции `secrets-env-to-file` (2026-07-25).
|
||||
|
||||
Связано: files/gitea, roles/secrets,
|
||||
[no-secrets-in-playbook-output](no-secrets-in-playbook-output.md).
|
||||
@@ -0,0 +1,26 @@
|
||||
# gramps: секретный ключ из файла
|
||||
|
||||
**Приоритет:** Средний
|
||||
|
||||
`GRAMPSWEB_SECRET_KEY` стоит прямым значением в `environment:`. Если переменную
|
||||
не задавать, entrypoint образа читает ключ из файла `/app/secret/secret` — и этот
|
||||
путь **уже смонтирован** в нашем compose как `gramps_secret`. То есть правка
|
||||
сводится к «записать значение в файл и убрать переменную»: самая дешёвая из всей
|
||||
серии. Entrypoint общий у `gramps_app` и `gramps_celery`, оба читают тот же файл,
|
||||
расхождения не будет.
|
||||
|
||||
Пароль SMTP так не выносится: конфиг читается через `from_prefixed_env`, никакой
|
||||
обработки `_FILE` нет. Обходной путь существует (Flask исполняет `config.cfg` как
|
||||
Python, значит значение можно прочитать из файла прямо в нём), но он держится на
|
||||
детали реализации и нигде не обещан — брать только если решим, что оно того
|
||||
стоит. Пока пароль SMTP остаётся в env и закрывается `no_log`.
|
||||
|
||||
Готово, когда `docker inspect` контейнеров gramps не показывает секретный ключ
|
||||
приложения.
|
||||
|
||||
Механизм и ссылки на код:
|
||||
[справка по файловым секретам](../drafts/secrets-file-support.md).
|
||||
Из декомпозиции `secrets-env-to-file` (2026-07-25).
|
||||
|
||||
Связано: files/gramps, roles/secrets,
|
||||
[no-secrets-in-playbook-output](no-secrets-in-playbook-output.md).
|
||||
@@ -0,0 +1,24 @@
|
||||
# outline: секреты из файлов
|
||||
|
||||
**Приоритет:** Средний
|
||||
|
||||
Сейчас в `environment:` стоят прямыми значениями пароль postgres (внутри
|
||||
`DATABASE_URL`), `SECRET_KEY`, `UTILS_SECRET`, секрет OIDC и пароль SMTP. Outline
|
||||
`1.9.2` умеет generic-механизм: пустая переменная читается из `<NAME>_FILE`,
|
||||
поэтому все пять закрываются по образцу miniflux — роль `secrets` + монтирование
|
||||
каталога `:ro`.
|
||||
|
||||
Отдельный шаг внутри задачи — пароль БД: `DATABASE_URL_FILE` выносит **весь** URL
|
||||
одним куском, и чтобы вынести именно пароль, нужен переход на раздельные
|
||||
`DATABASE_HOST/PORT/NAME/USER` (они взаимоисключимы с `DATABASE_URL`). Заодно
|
||||
сайдкар postgres переводится на штатный `POSTGRES_PASSWORD_FILE`.
|
||||
|
||||
Готово, когда `docker inspect` контейнеров outline и его postgres не показывает
|
||||
ни одного секрета, а в compose-шаблоне не осталось jinja-переменных с секретами —
|
||||
и, как следствие, дифф этого шаблона снова можно смотреть без `no_log`.
|
||||
|
||||
Механизм и ссылки на код: [справка по файловым секретам](../drafts/secrets-file-support.md).
|
||||
Из декомпозиции `secrets-env-to-file` (2026-07-25).
|
||||
|
||||
Связано: files/outline, files/miniflux (образец), roles/secrets,
|
||||
[no-secrets-in-playbook-output](no-secrets-in-playbook-output.md).
|
||||
@@ -0,0 +1,27 @@
|
||||
# tududi: секреты в смонтированный `.env`
|
||||
|
||||
**Приоритет:** Низкий
|
||||
|
||||
Tududi `1.2.4` не умеет файловых секретов вообще: session secret, секрет OIDC,
|
||||
ключ LLM и пароль SMTP читаются напрямую из `process.env`, конфиг-файла и
|
||||
CLI-опций нет. Проверено грепом по репозиторию на теге, не по докам.
|
||||
|
||||
Единственный доступный путь — не `env_file:` (при нём значения всё равно попадают
|
||||
в `Config.Env`), а **смонтированный файл**: `backend/app.js` вызывает `dotenv`, а
|
||||
рабочая директория контейнера — `/app/backend`, значит `.env` можно положить туда
|
||||
томом. Тогда секреты не видны в `docker inspect`, но остаются в окружении
|
||||
процесса и в `/proc/<pid>/environ`.
|
||||
|
||||
Приоритет низкий именно поэтому: механизм нештатный (держится на том, что
|
||||
`dotenv` остаётся в prod-зависимостях образа), а закрывает только один вектор из
|
||||
трёх. Если апстрим когда-нибудь добавит `_FILE` — задача переписывается на него.
|
||||
|
||||
Готово, когда `docker inspect` контейнера tududi не показывает ни одного из
|
||||
четырёх секретов.
|
||||
|
||||
Разбор и ссылки на код:
|
||||
[справка по файловым секретам](../drafts/secrets-file-support.md).
|
||||
Из декомпозиции `secrets-env-to-file` (2026-07-25).
|
||||
|
||||
Связано: files/tududi,
|
||||
[no-secrets-in-playbook-output](no-secrets-in-playbook-output.md).
|
||||
@@ -0,0 +1,28 @@
|
||||
# wakapi: секреты из файлов
|
||||
|
||||
**Приоритет:** Средний
|
||||
|
||||
Сейчас в `environment:` прямыми значениями стоят salt паролей и пароль SMTP,
|
||||
а в закомментированном блоке — секрет OIDC. Wakapi `2.17.5` даёт два пути, и
|
||||
выбрать надо один:
|
||||
|
||||
- **generic `_FILE`** — `loadSecretFiles()` разворачивает любую переменную с
|
||||
таким суффиксом. Ближе к образцу miniflux, меньше движений.
|
||||
- **`config.yml`**, смонтированный в `/app/config.yml` — покрывает те же три
|
||||
поля декларативно.
|
||||
|
||||
Для OIDC-секрета второй путь надёжнее: `renameEnvVars()` режет значение по
|
||||
первому `=`, то есть секрет с base64-паддингом через env приедет битым. Там же
|
||||
при включении OIDC надо поправить имена переменных — в 2.17.5 разбирается
|
||||
`WAKAPI_OIDC_PROVIDERS_0_*`, а у нас закомментирован устаревший вариант, который
|
||||
просто молча игнорируется.
|
||||
|
||||
Готово, когда `docker inspect` контейнера wakapi не показывает ни salt, ни пароль
|
||||
SMTP, а дифф шаблона можно смотреть без `no_log`.
|
||||
|
||||
Механизм, версии и ссылки на код:
|
||||
[справка по файловым секретам](../drafts/secrets-file-support.md).
|
||||
Из декомпозиции `secrets-env-to-file` (2026-07-25).
|
||||
|
||||
Связано: files/wakapi, files/miniflux (образец), roles/secrets,
|
||||
[no-secrets-in-playbook-output](no-secrets-in-playbook-output.md).
|
||||
@@ -13,6 +13,8 @@ gitea и т.д.) фактически все имеют root — изоляци
|
||||
тем — лучше через socket-proxy). За `primary_user` группу оставить для ручных
|
||||
операций (`inv ssh`).
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13). Усиливает риск из [secrets-env-to-file](secrets-env-to-file.md).
|
||||
Из sec-ревью (fable, 2026-07-13). Усиливает риск из серии `secrets-file-*`
|
||||
(секреты в `environment:` контейнеров): членство в группе `docker` — как раз тот
|
||||
доступ, которым такие переменные и вычитываются.
|
||||
|
||||
Связано: все playbook-*.yml (owner_extra_groups), roles/owner, playbook-docker.yml.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
Почему всё равно стоит закрыть:
|
||||
|
||||
- **Наименьшие привилегии.** Ansible ходит под непривилегированным `major` с
|
||||
`become` (`production.yml`/`timeweb.yml`), root-логин для деплоя не нужен вовсе.
|
||||
`become` (`production.yml`), root-логин для деплоя не нужен вовсе.
|
||||
- **Defense-in-depth.** Если `PasswordAuthentication` когда-нибудь вернётся в
|
||||
`yes` (правка, `Match`-блок, чужая роль), `PermitRootLogin yes` тут же откроет
|
||||
root по паролю. `prohibit-password` держит root key-only независимо.
|
||||
@@ -22,5 +22,5 @@
|
||||
|
||||
Из sec-ревью (fable, 2026-07-13); мотивировка уточнена после проверки конфига.
|
||||
|
||||
Связано: playbook-system.yml, production.yml, timeweb.yml,
|
||||
Связано: playbook-system.yml, production.yml,
|
||||
galaxy.roles/geerlingguy.security.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Убрать устаревший инвентарь после миграции на Timeweb
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
После переезда на Timeweb (cutover 2026-05-23) имена инвентарей инвертировались:
|
||||
|
||||
- `timeweb.yml` → `92.53.105.41` — **живой** хост, именно его использует `inv pl`
|
||||
(`tasks.py:12` `HOSTS_FILE = "timeweb.yml"`).
|
||||
- `production.yml` → `158.160.46.255` — **мёртвый** старый YC-хост (ВМ
|
||||
остановлена). При этом AGENTS.md всё ещё называет `production.yml` рабочим
|
||||
инвентарём.
|
||||
|
||||
Ловушка: любой прямой запуск `ansible-playbook -i production.yml ...` (как в
|
||||
примерах AGENTS.md) уедет на несуществующий/чужой хост. Плюс `production.yml`
|
||||
содержит `application_dir`/`mount_external_storage`, которых нет в `timeweb.yml` —
|
||||
простой заменой файла эти переменные потеряются.
|
||||
|
||||
Сделать: сделать `production.yml` актуальным инвентарём Timeweb-хоста (перенести
|
||||
недостающие host-vars), удалить `timeweb.yml`, поправить `HOSTS_FILE` в `tasks.py`
|
||||
и примеры в AGENTS.md. Согласуется с задачей
|
||||
[ansible-inventory-hostvars](ansible-inventory-hostvars.md) (host-специфику в
|
||||
`host_vars/server.yml`, хост в именованную группу) — логично делать вместе.
|
||||
|
||||
Связано: production.yml, timeweb.yml, tasks.py, AGENTS.md,
|
||||
docs/drafts/timeweb-migration-log.md, ansible-inventory-hostvars.
|
||||
@@ -0,0 +1,50 @@
|
||||
# [idea] Где хранить пароль ansible-vault
|
||||
|
||||
**Приоритет:** Низкий
|
||||
|
||||
Пароль vault лежит открытым текстом в `./ansible-vault-password-file` (в
|
||||
`.gitignore`, есть `.dist`-заглушка), а `ansible.cfg:4` объявляет
|
||||
`vault_password_file` глобально — значит расшифровка происходит неявно в **любой**
|
||||
ansible-команде, не только в деплое. Из-за этой связки 24 июля
|
||||
`ansible-inventory --list` вывалил в транскрипт 53 из 64 vault-переменных
|
||||
открытым текстом.
|
||||
|
||||
Зацепка: если файл из `vault_password_file` исполняемый, ansible запускает его и
|
||||
берёт пароль из stdout. То есть пароль можно не хранить на диске, а доставать из
|
||||
KeePassXC — `ansible.cfg` при этом не меняется, достаточно `chmod +x`. Сам скрипт
|
||||
секрета не содержит, его можно закоммитить и выкинуть связку `.gitignore` +
|
||||
`.dist`.
|
||||
|
||||
Два способа достать, оба проверены на наличие в системе:
|
||||
|
||||
- **Secret Service** — `secret-tool lookup` к разблокированной базе по D-Bus.
|
||||
Требует `libsecret-tools` (не установлен) и включённой в KeePassXC интеграции с
|
||||
экспортом отдельной группы. Деплой без запросов пароля, при заблокированной базе
|
||||
— fail-closed.
|
||||
- **`keepassxc-cli show -s -a Password <db> <entry>`** — без D-Bus и без новых
|
||||
пакетов (`keepassxc-cli` установлен), но пароль от базы спрашивается на каждый
|
||||
вызов; `inv pl` придётся перевести на `pty=True`, как уже сделано в
|
||||
`authelia-cli` и `vault-edit`.
|
||||
|
||||
**Нерешённый компромисс — ради него задача и заведена.** Вводить пароль руками на
|
||||
каждый деплой неудобно. Но любая автоматизация, которая эту неудобность снимает
|
||||
(разблокированная база, D-Bus, кэш), ровно так же доступна агенту: он найдёт
|
||||
скрипт и вызовет его сам. То есть выбор хранилища **не защищает от агента** — от
|
||||
него защищают только deny-правила в `~/.claude/settings.json`, которые
|
||||
enforcement-слой применяет снаружи. Что смена хранилища действительно даёт:
|
||||
пароля больше нет на диске (а значит в снапшотах restic и в Syncthing), и при
|
||||
заблокированной базе всё падает закрыто для всех.
|
||||
|
||||
Прежде чем решать, стоит честно ответить: от чего именно защищаемся — от
|
||||
случайного дампа, от копии `~` в чужих руках, или от агента. Ответы ведут к разным
|
||||
вариантам, а третий не решается на этом уровне вообще.
|
||||
|
||||
Смежное: серия `secrets-file-*` — тот же вопрос слоем ниже (секреты в
|
||||
`environment:` контейнеров), разбор механизмов в
|
||||
[справке](../drafts/secrets-file-support.md). Отдельно не заведена, но обсуждалась,
|
||||
идея скоупинга: вернуть `group_vars/all/secrets.yml` в `vars_files`, оставив
|
||||
`main.yml` в `group_vars` — это режет потолок утечки с 53 переменных до ~11, ценой
|
||||
возврата boilerplate в плейбуки.
|
||||
|
||||
Связано: ansible.cfg, tasks.py, group_vars/all/secrets.yml,
|
||||
ansible-vault-password-file.dist, .gitignore.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Конвенции
|
||||
|
||||
Склад договорённостей о том, **как делать однотипные вещи** в этом
|
||||
репозитории. Одна конвенция — один файл.
|
||||
|
||||
Конвенция описывает повторяющийся выбор: как называть директории, как
|
||||
раскладывать данные, как оформлять шаблоны. Она отвечает на вопрос «как
|
||||
принято», а не «что здесь происходит».
|
||||
|
||||
Чем отличается от соседей:
|
||||
|
||||
- [`../adr/`](../adr) — **решение**, принятое однажды и постфактум
|
||||
(«почему выбрали Authelia, а не Keycloak»). Запись неизменяема.
|
||||
- [`../drafts/`](../drafts) — оперативная хроника и черновики («что
|
||||
собираюсь сделать»).
|
||||
- `conventions/` — **правило на будущее**, применяемое многократно.
|
||||
Живой документ: правится, когда договорённость меняется.
|
||||
|
||||
## Статус
|
||||
|
||||
Каждая конвенция начинается со строки статуса:
|
||||
|
||||
- **Рекомендуемая** — так стоит делать в новом коде; существующий код
|
||||
переезжает по мере касания, отдельной кампанией не переписывается.
|
||||
- **Обязательная** — нарушение считается ошибкой; по возможности
|
||||
проверяется линтером или хуком, а не вниманием.
|
||||
|
||||
Конвенция без механической проверки держится только на внимании — это
|
||||
нормально для рекомендуемой и плохо для обязательной.
|
||||
|
||||
## Когда заводить
|
||||
|
||||
Когда одно и то же решение принимается третий раз и каждый раз чуть
|
||||
по-другому. Единичный выбор — не конвенция; если он ещё и был спорным,
|
||||
ему место в ADR.
|
||||
|
||||
## Соглашения
|
||||
|
||||
- Имя файла — kebab-case, по теме: `app-directories.md`.
|
||||
- В теле честно перечислены отступления, которые уже есть в коде, —
|
||||
иначе документ описывает не репозиторий, а пожелание.
|
||||
|
||||
## Список
|
||||
|
||||
- [Категории директорий приложения](app-directories.md) — конфигурация /
|
||||
данные / кеш: делим по тому, кто создаёт и что будет при потере.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Категории директорий приложения
|
||||
|
||||
**Статус:** рекомендуемая.
|
||||
|
||||
Директории приложения внутри `base_dir` делим на три категории по
|
||||
принципу создания и ценности содержимого:
|
||||
|
||||
- **конфигурация** — то, что восстанавливается прогоном плейбука, в том
|
||||
числе секреты;
|
||||
- **данные** — то, что генерирует приложение и что нужно бэкапить;
|
||||
- **кеш** — то, что генерирует приложение и что не нужно бэкапить:
|
||||
приложение перегенерирует заново.
|
||||
|
||||
Цель — упростить оперирование данными. Категория сразу отвечает на два
|
||||
вопроса, которые иначе приходится выяснять по коду приложения: **кто
|
||||
создаёт** содержимое и **что будет, если его потерять**.
|
||||
|
||||
## Категории
|
||||
|
||||
| Категория | Директория | Создаёт | Потеря содержимого | Бэкап |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Конфигурация | `config/` | плейбук | `inv pl -- <app>` восстанавливает | не нужен |
|
||||
| Данные | `data/` | приложение | невосполнима | обязателен |
|
||||
| Кеш | `cache/` | приложение | приложение перегенерирует | не нужен |
|
||||
|
||||
Тест на границе данных и кеша: что будет, если сделать `rm -rf` и
|
||||
поднять приложение заново. Поднимется само и наверстает — кеш. Не
|
||||
поднимется или поднимется пустым — данные.
|
||||
|
||||
Конфигурацию бэкапить не только не нужно, но и не стоит: там лежат
|
||||
секреты, а бэкапы уезжают в облако. Источник истины для конфигурации —
|
||||
репозиторий и vault, а не снапшот restic.
|
||||
|
||||
## Практика
|
||||
|
||||
- Переменные плейбука — `config_dir`, `data_dir`, `cache_dir` внутри
|
||||
`base_dir`; директории создаются одной задачей `Create application
|
||||
internal directories`.
|
||||
- Файл `backup-targets` выводится из категорий механически: в него идут
|
||||
данные, не идут конфигурация и кеш.
|
||||
- Монтирование в контейнер: конфигурацию — `:ro`, где приложение это
|
||||
позволяет. Данные и кеш — на запись.
|
||||
- `docker-compose.yml` остаётся в корне `base_dir`: туда смотрит
|
||||
`project_src` модуля `docker_compose_v2`.
|
||||
|
||||
Пример, где категории разведены полностью, — `playbook-gramps.yml`:
|
||||
`data` (база), `media` (файлы), `cache` (миниатюры и кеш отчётов),
|
||||
`backups` (дампы). В `backup-targets` попадают первые три без `cache`.
|
||||
|
||||
## Отступления, которые уже есть
|
||||
|
||||
Конвенция рекомендуемая, поэтому список честный, а не пустой.
|
||||
|
||||
- **`backups/`** — четвёртая директория рядом с тремя категориями.
|
||||
Формально это производное от данных (дамп, который делает gobackup или
|
||||
`pg_dump`), но именно она уезжает в restic, а сырые данные приложения —
|
||||
не всегда. Держим отдельно и относим к данным.
|
||||
- **`media/`** (gramps, outline) — это данные, просто вынесенные в
|
||||
отдельную директорию: крупные файлы отделены от базы, чтобы их можно
|
||||
было двигать между дисками независимо.
|
||||
- **Конфигурация без `config/`** — она есть только у девяти плейбуков из
|
||||
тридцати трёх, у остальных конфиги лежат прямо в `base_dir` рядом с
|
||||
`docker-compose.yml`. Переезд по мере касания приложения, отдельной
|
||||
кампанией не переписываем.
|
||||
- **Легаси-раскладка `data/` под всё** — у части сервисов внутри `data/`
|
||||
лежит и то, что по этой конвенции было бы кешем. Разделять имеет смысл
|
||||
тогда, когда кеш заметен по объёму в бэкапе, а не ради самой схемы.
|
||||
|
||||
## Связано
|
||||
|
||||
- [ADR-2025-12-07](../adr/ADR-2025-12-07-app-data-on-separate-disk.md) —
|
||||
почему всё это лежит на отдельном диске в `/mnt/applications`.
|
||||
- `files/backups/backup-all.py` — оркестратор, читающий `backup-targets`.
|
||||
@@ -0,0 +1,203 @@
|
||||
# Файловые секреты: что умеет каждое приложение
|
||||
|
||||
Дата: 2026-07-25. Статус: справка по итогам проверки исходников (не план работ).
|
||||
|
||||
Проверялись **закреплённые в наших compose-файлах версии**, не `main`. Источник —
|
||||
код парсинга конфига в репозиториях приложений; доки использовались как
|
||||
подтверждение, а не как основание. Из этой справки выросли задачи
|
||||
`secrets-file-*` в беклоге, по одной на приложение.
|
||||
|
||||
## Зачем
|
||||
|
||||
Секреты стоят прямыми значениями в `environment:` docker-compose. Такие
|
||||
переменные читает любой член группы `docker`, любой процесс с доступом к
|
||||
`/proc/<pid>/environ`, и они попадают в `docker inspect`. Цель — там, где
|
||||
приложение умеет, брать секрет из файла.
|
||||
|
||||
Образец — **miniflux**: роль `secrets` кладёт каждую переменную vault в отдельный
|
||||
файл, каталог монтируется `:ro`, compose ссылается на `*_FILE`
|
||||
(`files/miniflux/docker-compose.template.yml`).
|
||||
|
||||
Отдельная и более важная цель — чтобы секреты не печатались в терминал при
|
||||
деплое — этой справкой **не** решается: там работает `no_log`, см. задачу
|
||||
`no-secrets-in-playbook-output`. Для tududi и wanderer `no_log` — единственный
|
||||
доступный ответ.
|
||||
|
||||
## Матрица
|
||||
|
||||
| Приложение | Секрет | Файл | Механизм |
|
||||
|---|---|---|---|
|
||||
| **outline** `1.9.2` | `SECRET_KEY`, `UTILS_SECRET`, OIDC, SMTP | да | generic `<NAME>_FILE` |
|
||||
| | пароль postgres | да, с оговоркой | только вместе с переходом на раздельные `DATABASE_*` |
|
||||
| | пароль сайдкара postgres | да | штатный `POSTGRES_PASSWORD_FILE` |
|
||||
| **wakapi** `2.17.5` | salt, SMTP, OIDC | да | generic `_FILE` либо `config.yml` |
|
||||
| **gitea** `1.27.0` | SMTP | да, частично | `GITEA__mailer__PASSWD__FILE`, но значение оседает в `app.ini` |
|
||||
| **gramps** `26.7.0` | `GRAMPSWEB_SECRET_KEY` | да | файл `/app/secret/secret`, путь уже смонтирован |
|
||||
| | SMTP | нет | только недокументированный трюк с `config.cfg` |
|
||||
| **authelia** `4.39.20` | jwt, session, ключ хранилища, hmac OIDC, SMTP | да | `AUTHELIA_..._FILE` |
|
||||
| | jwks-ключ, `client_secret` × 4 | нет через `_FILE` | только template-фильтр; секреты клиентов лучше хэшировать |
|
||||
| **tududi** `1.2.4` | все четыре | **нет** | обходной путь — смонтированный `.env` |
|
||||
| **wanderer** `0.18.3` | ключ meilisearch, ключ pocketbase | **нет** | ничего |
|
||||
|
||||
## Подробности по приложениям
|
||||
|
||||
### outline `1.9.2`
|
||||
|
||||
`server/utils/environment.ts` оборачивает `process.env` в Proxy: если переменная
|
||||
пуста, читается `<NAME>_FILE` как путь к файлу, содержимое обрезается по
|
||||
пробелам. Весь `server/env.ts` и плагины читают именно через него, поэтому
|
||||
механизм покрывает **любую** переменную, включая OIDC-плагин.
|
||||
|
||||
- Появилось в v1.7.0 (PR [#11906](https://github.com/outline/outline/pull/11906),
|
||||
merged 2026-03-30), в v1.9.0 переписано на ленивый Proxy (PR
|
||||
[#12889](https://github.com/outline/outline/pull/12889)). Проверено бисекцией:
|
||||
в v1.6.1 механизма нет, в v1.7.0 есть.
|
||||
- Конвенция описана в
|
||||
[.env.sample](https://github.com/outline/outline/blob/v1.9.2/.env.sample)
|
||||
строки 4–18, включая правило приоритета: заданы обе — побеждает прямая
|
||||
переменная.
|
||||
- Пароль БД: `DATABASE_URL_FILE` выносит **весь** URL. Чтобы вынести только
|
||||
пароль, нужны раздельные `DATABASE_HOST/PORT/NAME/USER` + `DATABASE_PASSWORD`,
|
||||
они взаимоисключимы с `DATABASE_URL` через `@CannotUseWith`
|
||||
([env.ts L95–L143](https://github.com/outline/outline/blob/v1.9.2/server/env.ts)).
|
||||
- Сайдкар `postgres:16.3` — штатный `POSTGRES_PASSWORD_FILE`.
|
||||
|
||||
### wakapi `2.17.5`
|
||||
|
||||
Два независимых пути.
|
||||
|
||||
- `loadSecretFiles()`
|
||||
([config.go L829](https://github.com/muety/wakapi/blob/2.17.5/config/config.go))
|
||||
— generic: проходит по всему окружению, для любой переменной с суффиксом
|
||||
`_FILE` читает файл, `TrimSpace`, кладёт в базовое имя, файловую снимает.
|
||||
Заданы обе — процесс падает с «both environment variables are set».
|
||||
- Порядок в `Load()`: `loadSecretFiles()` → `renameEnvVars()` → `configor.Load`,
|
||||
поэтому `_FILE` работает и для OIDC-переменных в configor-формате.
|
||||
- История: PR [#679](https://github.com/muety/wakapi/pull/679) (релиз 2.12.1)
|
||||
сделал это в `entrypoint.sh` для пяти переменных; коммит `a45732b2` (релиз
|
||||
2.17.2, переход на distroless) перенёс логику в Go и сделал универсальной.
|
||||
README (L92) перечисляет только три переменные — **список устарел**
|
||||
относительно кода.
|
||||
- `config.yml` покрывает все три поля: `security.password_salt`,
|
||||
`security.oidc[]`, `mail.smtp.password`. Образ кладёт `config.default.yml` в
|
||||
`/app/config.yml`, туда же монтируется свой. Env перекрывает значения из yaml.
|
||||
|
||||
Две ловушки в нашем **закомментированном** OIDC-блоке (не живой баг, но при
|
||||
включении выстрелит):
|
||||
|
||||
1. Имена `WAKAPI_OIDC_PROVIDER_CLIENT_ID` устарели — в 2.17.5 разбирается
|
||||
`WAKAPI_OIDC_PROVIDERS_(\d+)_([A-Z_]+)`, старые молча игнорируются.
|
||||
2. `renameEnvVars()` разбирает строку как `strings.Split(e, "=")` и берёт
|
||||
`parts[1]` — значение обрезается по первому `=`, то есть секрет с
|
||||
base64-паддингом через env поедет битым.
|
||||
|
||||
Оба довода — за `config.yml` для OIDC-секрета.
|
||||
|
||||
### gitea `1.27.0`
|
||||
|
||||
`GITEA__mailer__PASSWD__FILE=/path` — механизм универсальный, работает для любого
|
||||
ключа `app.ini`, хвостовой перевод строки обрезается
|
||||
([config_env.go L100–141](https://github.com/go-gitea/gitea/blob/v1.27.0/modules/setting/config_env.go),
|
||||
PR [#24832](https://github.com/go-gitea/gitea/pull/24832), с 1.20).
|
||||
|
||||
Оговорка, которая режет пользу: `environment-to-ini` **записывает содержимое
|
||||
файла в `app.ini` открытым текстом**, то есть секрет всё равно оседает на
|
||||
хостовом диске в `<data_dir>/gitea/conf/app.ini`. Открытая проблема апстрима
|
||||
[#35316](https://github.com/go-gitea/gitea/issues/35316) (ранее #25316), PR нет.
|
||||
Выигрыш реальный, но частичный: уходит из `environment:`, `docker inspect` и
|
||||
окружения процесса — не с диска.
|
||||
|
||||
### gramps `26.7.0` (gramps-web-api v3.18.0)
|
||||
|
||||
Если `GRAMPSWEB_SECRET_KEY` не задана, entrypoint образа читает ключ из файла
|
||||
`/app/secret/secret` (и генерирует, если файла нет) —
|
||||
[docker-entrypoint.sh](https://github.com/gramps-project/gramps-web-api/blob/v3.18.0/docker-entrypoint.sh).
|
||||
**Этот путь у нас уже смонтирован** как `gramps_secret`. Entrypoint общий у
|
||||
`gramps_app` и `gramps_celery` через YAML merge key, оба читают тот же файл.
|
||||
|
||||
SMTP-пароль файлового источника не имеет: конфиг читается через
|
||||
`app.config.from_prefixed_env(prefix="GRAMPSWEB")`, никакой обработки `_FILE`
|
||||
([app.py L99–121](https://github.com/gramps-project/gramps-web-api/blob/v3.18.0/gramps_webapi/app.py)).
|
||||
Обходной путь: `Dockerfile` задаёт `GRAMPS_API_CONFIG=/app/config/config.cfg`, а
|
||||
`from_envvar()` во Flask — это `from_pyfile()`, то есть **config.cfg исполняется
|
||||
как Python**, и `EMAIL_HOST_PASSWORD = open("/run/secrets/smtp").read().strip()`
|
||||
сработает. Механизм рабочий, но держится на детали реализации Flask и нигде не
|
||||
обещан.
|
||||
|
||||
### authelia `4.39.20`
|
||||
|
||||
`IsSecretKey()` возвращает `false` для любого ключа, содержащего `[]` (элемент
|
||||
списка), и требует, чтобы ключ оканчивался на `key`/`secret`/`password`/`token`/
|
||||
`certificate_chain`
|
||||
([helpers.go](https://github.com/authelia/authelia/blob/v4.39.20/internal/configuration/helpers.go)).
|
||||
Отсюда деление:
|
||||
|
||||
- **Умеют `_FILE`** (5 полей): `identity_validation.reset_password.jwt_secret`,
|
||||
`session.secret`, `storage.encryption_key`,
|
||||
`identity_providers.oidc.hmac_secret`, `notifier.smtp.password`.
|
||||
- **Не умеют** (2 позиции): `identity_providers.oidc.jwks[].key` и
|
||||
`client_secret` четырёх клиентов — оба содержат `[]`.
|
||||
|
||||
Для них есть [template-фильтр](https://www.authelia.com/configuration/methods/files/)
|
||||
(`X_AUTHELIA_CONFIG_FILTERS=template`, с 4.38): `{{ secret "/path" | nindent 10 }}`.
|
||||
Механизмы **несовместимы в одном поле** — если задано и `_FILE`, и значение в
|
||||
конфиге, Authelia падает с `errFmtSecretAlreadyDefined`. Поэтому проще выбрать
|
||||
один: фильтр покрывает все семь позиций, `_FILE` — только пять.
|
||||
|
||||
Отдельно про `client_secret`: штатная форма хранения — PHC-дайджест
|
||||
`$pbkdf2-sha512$310000$...`, плейнтекст с 4.38 вызывает warning валидатора
|
||||
(`errFmtOIDCClientInvalidSecretPlainText`). Плейнтекст обязателен только при
|
||||
`client_secret_jwt` или симметричном шифровании JWT — у наших четырёх клиентов
|
||||
(miniflux, wakapi, tududi — дефолт; outline — `access_token_signed_response_alg:
|
||||
none`) ничего такого нет. То есть эти поля можно **перестать считать секретами**:
|
||||
в конфиге хэш, сам секрет живёт в vault и в конфиге клиентского приложения.
|
||||
Генератор уже есть — `inv authelia-gen-secret-and-hash`.
|
||||
|
||||
### tududi `1.2.4` — не умеет
|
||||
|
||||
Все четыре секрета читаются напрямую из `process.env`
|
||||
([config.js L94, L40](https://github.com/chrisvel/tududi/blob/v1.2.4/backend/config/config.js),
|
||||
[providerConfig.js L82](https://github.com/chrisvel/tududi/blob/v1.2.4/backend/modules/oidc/providerConfig.js),
|
||||
[service.js L20](https://github.com/chrisvel/tududi/blob/v1.2.4/backend/modules/ai-assistant/service.js)).
|
||||
Греп по репозиторию на теге даёт только `DB_FILE` и `FILE_UPLOAD_LIMIT_MB`; ни
|
||||
`readFileSync` для секретов, ни конфиг-файла, ни CLI-опции нет. Собственная дока
|
||||
по OIDC советует «consider Docker secrets» — это совет про хранение, читать файлы
|
||||
приложение не умеет.
|
||||
|
||||
Обходной путь: `backend/app.js` вызывает `require('dotenv').config()`, рабочая
|
||||
директория контейнера — `/app/backend`, значит смонтированный туда `.env`
|
||||
подхватится. Это не per-secret файлы, но значения не попадают в `Config.Env` —
|
||||
в отличие от `env_file:`, где попадают. `dotenv` не перезаписывает уже
|
||||
установленные переменные окружения.
|
||||
|
||||
### wanderer `0.18.3` — не умеет ничего
|
||||
|
||||
- `MEILI_MASTER_KEY` (meilisearch v1.20.0): только CLI `--master-key`, env или
|
||||
инлайн в `config.toml`; `_FILE` нет
|
||||
([option.rs](https://github.com/meilisearch/meilisearch/blob/v1.20.0/crates/meilisearch/src/option.rs)).
|
||||
Апстрим отклонил запрос —
|
||||
[discussion #201](https://github.com/orgs/meilisearch/discussions/201), 2023.
|
||||
Клиентская сторона (`wanderer_db`, `wanderer_web`) тоже читает `os.Getenv`.
|
||||
- `POCKETBASE_ENCRYPTION_KEY` — это **не** штатный механизм pocketbase
|
||||
(`--encryptionEnv`), а собственная переменная wanderer, читается `os.Getenv` в
|
||||
четырёх местах
|
||||
([db/main.go](https://github.com/Flomp/wanderer/blob/v0.18.3/db/main.go)).
|
||||
ENTRYPOINT в exec-форме, подстановку через шелл не сделать. Найденный в поиске
|
||||
`POCKETBASE_ENCRYPTION_KEY_FILE` — фича стороннего образа
|
||||
`adrianmusante/pocketbase`, к `flomp/wanderer-db` отношения не имеет.
|
||||
|
||||
Доступен только `env_file:`, где значения всё равно попадают в `Config.Env`, то
|
||||
есть косметика. Задачи по wanderer не заведено.
|
||||
|
||||
## Docker secrets — покрытия не расширяет
|
||||
|
||||
`secrets:` в compose вне swarm — это bind-mount файла в `/run/secrets/<name>`, то
|
||||
есть **тот же способ доставки**, что и `*_FILE`. Приложение, умеющее только
|
||||
переменную окружения, из `/run/secrets` не прочитает: ограничение в приложении, а
|
||||
не в доставке. На матрицу выше не влияет.
|
||||
|
||||
Что даёт: секрет объявляется на сервис, а не монтируется каталогом. У miniflux
|
||||
сейчас `{{ secrets_dir }}:/secrets:ro` висит на обоих сервисах, и postgres видит
|
||||
все шесть файлов, включая секреты OIDC, хотя нужен ему один пароль. Улучшение
|
||||
поверх образца, а не замена ему. `uid`/`gid`/`mode` вне swarm игнорируются —
|
||||
права остаются с хост-файла, их ставит роль `secrets`.
|
||||
@@ -1,10 +1,15 @@
|
||||
# Образ собирается локально и доставляется на сервер ролью app_image
|
||||
# (см. playbook-homepage.yml): тег — случайный BUILD_ID каждого прогона,
|
||||
# в реестре образа нет, поэтому pull_policy: never.
|
||||
|
||||
services:
|
||||
|
||||
homepage_app:
|
||||
# noinspection ComposeUnknownValues
|
||||
image: "{{ registry_homepage_nginx_image }}"
|
||||
image: "{{ homepage_image_name }}:{{ app_image_tag }}"
|
||||
container_name: homepage_app
|
||||
restart: unless-stopped
|
||||
pull_policy: never
|
||||
networks:
|
||||
- "web_proxy_network"
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
- "/var/run:/host/var/run:ro"
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
environment:
|
||||
PGID: "{{ netdata_docker_group_output.stdout | default(999) }}"
|
||||
PGID: "{{ netdata_docker_gid }}"
|
||||
NETDATA_EXTRA_DEB_PACKAGES: "fail2ban"
|
||||
|
||||
networks:
|
||||
|
||||
@@ -86,6 +86,8 @@ tier7 = 8
|
||||
[telegram]
|
||||
|
||||
# Включить Telegram-бот для ежедневной отправки воспоминаний.
|
||||
# Если api.telegram.org недоступен, приложение всё равно стартует: веб-часть
|
||||
# работает, а бот повторяет авторизацию в фоне, пока не появится связь.
|
||||
enabled = true
|
||||
|
||||
# Токен бота, полученный от @BotFather.
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# Образ собирается локально и доставляется на сервер ролью app_image
|
||||
# (см. playbook-remembos.yml): тег — случайный BUILD_ID каждого прогона,
|
||||
# в реестре образа нет, поэтому pull_policy: never.
|
||||
|
||||
services:
|
||||
|
||||
remembos_app:
|
||||
image: "{{ yc_container_registry_repository }}/remembos:v0.2.0"
|
||||
image: "{{ app_name }}:{{ app_image_tag }}"
|
||||
container_name: remembos_app
|
||||
restart: unless-stopped
|
||||
pull_policy: never
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
environment:
|
||||
- PUID={{ owner_create_result.uid }}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
# Образ собирается локально и доставляется на сервер ролью app_image
|
||||
# (см. playbook-transcriber.yml): тег — случайный BUILD_ID каждого прогона,
|
||||
# в реестре образа нет, поэтому pull_policy: never.
|
||||
|
||||
services:
|
||||
|
||||
transcriber_app:
|
||||
# noinspection ComposeUnknownValues
|
||||
image: "{{ registry_transcriber_image }}"
|
||||
image: "{{ app_name }}:{{ app_image_tag }}"
|
||||
container_name: transcriber_app
|
||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||
restart: unless-stopped
|
||||
pull_policy: never
|
||||
volumes:
|
||||
- "{{ config_file }}:/config/config.toml:ro"
|
||||
- "{{ data_dir }}:/data"
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
34623131346139653664373036613532666565303039363565373735636363386366313233303238
|
||||
6630613730376536656663636234326332353236363439370a333166386530346666303531633666
|
||||
38323538623838623237636339613138383936383232346566333532383863623738333136663364
|
||||
3162386333383463330a643265386164613439643231313765623331376531306466313062363966
|
||||
65616337386232326433623763373464363135353761623430613036623761323965303333623139
|
||||
65633262623261656131393333646361393733323366343833386330663461663435373561626636
|
||||
31303437336431633630623835623633316538333339393866646661313762663134353364373234
|
||||
37326263396362343137626537333936366135663438373034326165376537663233356466356438
|
||||
35653637373864303864626433336634366132653262643731396636323963626363643666666635
|
||||
37393339353331666635626531343835346632363634366437663564376339386230386533323732
|
||||
63623232613137663266386161326333306530336634313666616437623931653232323431326562
|
||||
34323565653761636165303132313964373031626237626232623031656430356535613831373834
|
||||
65356263303364316465356138623737626339333530393661313638333931336331396336613437
|
||||
65323132666132363063333435383032663330653965373334643461303466353235313130363633
|
||||
31633134373732306535333433613839356362396261623364396339363630626636396632656233
|
||||
62333737633332383736626363366463663166306265646365373637333465393530386665363364
|
||||
37313064653434393761383066623638303162376263343130306439623166666366326639616336
|
||||
65386364633865626566373636393537653134333733666531616266636638653836383732383461
|
||||
65363132346563643439666431323934306330336233393363666164353839633534333132333130
|
||||
34303861653662636534373565306537366235663431333532653465643866363335383762333538
|
||||
62373332363265396639626137646334363933366633326462313336336265313739326131343165
|
||||
39336333376137393665316435363433613661393063306638306330313333633430333034373038
|
||||
62626265333433326665316634613562616631643161643930363632666262616431323536363066
|
||||
64353932623332626261326564396538663866623638326432303436356263643162336166636137
|
||||
39383432346365626663373932663134646566323061636235633336386236663639633663326265
|
||||
34353031343465313231336162396232303436366363646131613936303333376463363230636638
|
||||
30356137313035643330343536316138306633323635363066356333623365376430373231366439
|
||||
63633237633866656262313233633565666433626563393730376465623033353738383662323438
|
||||
39356265383161373933636133393534333934373664313962626637366466636132396563613435
|
||||
32653964663433646332616161616436363636363236353262613861643939376239636139626162
|
||||
31313638343765323736353961626238666536623330353537396464363935303439653139306265
|
||||
30323531626133393735633833326562333230323735623465326566393832303234646536626534
|
||||
30386263636537613366343431363262393433663064613631613665353139316639366334366133
|
||||
39353539306662636165346466303132633231666135313734653665303165356533653739396234
|
||||
35356338326631376631376136643930616164396466303733653266613766346334613338623133
|
||||
39326234363161363132643630323662313536386161633033623833613639643030366533353038
|
||||
31396430366236376232316462616138303832343862306361663061656263626466333036613366
|
||||
36386234666363396161333830636430383039356638313830666332663931383937623433613432
|
||||
35386136333830616239623161613538386238383130346435376230373838643339663066316130
|
||||
63663938626662396336396562373537303635343337346637626662353530623639306237313062
|
||||
34313664363838313133346533326137636239346564646661353566646163386663326232643766
|
||||
34613931613063646339313764383562363930386165636134653932393833303633643266666366
|
||||
61346238353636356564376165386339326662643634326431323766313831353039653565386437
|
||||
62343338656466343562643930656430666230303863323238373534633136386336356266666166
|
||||
36373536376263386434613533626130333261333633343439366266386336613665363530376432
|
||||
36383735313566666461323363383530636333616332643637653364373834643337346537626630
|
||||
61346331313237363832623535323936623163653233333562653035306336333831623338356231
|
||||
35313438626632666465353535383434343630306233306238366439623335343536346234616134
|
||||
39353938313536613531336134656262363830323561313730383331323165353930623066613064
|
||||
63646465626233646163303964376532383338633131383065313236616537313064303265383733
|
||||
66663132646130386638306335653531323531306437393364663135306466643365663235643461
|
||||
61633665623532386466623635376331336662666134373366346536363134313037393939346634
|
||||
64616332666630616235383136383536646233353663663634343762346236353533613832336436
|
||||
33643235663366626438373932633866626366323035363835633036393266613163383933343466
|
||||
36303630653731363637616337323030646164306264636237613032303738643938663032663365
|
||||
65333334303030313562636166653766306265636238653161356166616138376665613338396564
|
||||
36383732636331653934636666393861623965386666646630393261303365363839343838383266
|
||||
31616162643662616332363737373865353366343030623839663330343861313032363161306262
|
||||
34316161303432393365376431386165656464393632646534613930643966663330656532363162
|
||||
63366331323634356337366433643262343531376638666536383766343963663839656438353465
|
||||
33653132383336616166313330306233373630393530303161306634663830656433653739303466
|
||||
63373436613663643263343262643536366138333439313538656136633932383065643135303237
|
||||
34636637373863326539653836333430306537353436323832373131343435636233393762376430
|
||||
34633338396138376632653863633935643462346438653066623633323431336230616564613236
|
||||
39376639383837303430323939653435656437303635343335363965313734316134303166613238
|
||||
62616238306363613334313362666365633335336637393332633564653035303034333866623634
|
||||
30613432636539383031633266383166303639326133663065323236316561373461626637333138
|
||||
61376134366335646634623062333633306666663666303464386430626639373663633938396331
|
||||
35643138303561316531363165326230653461356133346531326466323966353330343438306630
|
||||
36613466336638623934373232316635633032343561323535346233633261376235613263316233
|
||||
34663635353732336234623935323631613831336639636338656332633832343238626361636364
|
||||
65636632633534343735343737666237616331353737636332333034393630303133376136663332
|
||||
31613532653566393164663738303138616233623031646665323266313863623761646465356135
|
||||
62303864336538306661383931346364303961366330396639336461306537313465663239623766
|
||||
36366262383662613432356333653765383432316333326535353062376539366434326638336361
|
||||
30653136633362323066643732383636626530363163376230306338353066373732376133313064
|
||||
63356532623331353538623561333364623037396262323864643061333131343061653330343632
|
||||
63386262626431376231326131396164613463383034346362396439393133373666316637346134
|
||||
64343538336332633131333530323462373864633665343439303162366335343234383362346434
|
||||
36356662656530353932616339326233336637396664636464396462376636646432313665323235
|
||||
66376635396436653137383039333932356665623932303433393264656632643632323165386430
|
||||
34613534336632613230343532346364663338613932626333663432396461353638646237613330
|
||||
63376265313536633838663031326133363563353266313464656666356538646333643361646239
|
||||
63346163646164636539333566393865303534646534396366363262353430613463326637336664
|
||||
35373433653336613934316333633565383863623562346663396433646633366130613339656466
|
||||
62346339396438656334333934383432326661643236646537643564346538626533613565303235
|
||||
64623038356162303338663037646137366136306232396535306565356132353830663566643465
|
||||
30373233323930333862633133303663646661323566336238363435376138383066383136393938
|
||||
37356361393334313735306238376134373930313834376566313236376665623339363039626463
|
||||
37306364623863356335373234366266383331326635653730633535363464326431323030303534
|
||||
39303231633664353834656162346531663933666133393462383135313764346564646162666563
|
||||
30633539643432383934363339656465646166326261623465336136333236343030346238653234
|
||||
32376363613864383162666138346565633239336361636435353362306163396133653432306262
|
||||
39663464623636663930643162353537393834653537646536313330376136363230613063383362
|
||||
38656632613834623362353366653766313735343665343661623836313339626433306338306366
|
||||
31376666303734666461663462316339663130623763373034316463643537653839363762373431
|
||||
64373064386564656438393030363135653030626665656337363366353130373237376331663231
|
||||
35663463383530653163323565623234316431336663363136356238383064653739343661653066
|
||||
32613535633631343563313262353063393733363230333236363665643238663338306639623932
|
||||
35376136616464366563386138636564636138623939663535393565653638303731343561313466
|
||||
64303838646538306233653437313663393730653139626166666433396434316239316662316537
|
||||
36333037613761313263396631386636313662653062326630383038656463366430653236333131
|
||||
33323330373833663763303930353339643363373266633734373732343632383138616235386166
|
||||
65343465666264366139376466386366393230376264313935623961373966333964376362356133
|
||||
65386564343332313037323336633465636439356363616261346435633536633331353935653364
|
||||
64616332326239653730343530656466353664373935363062633132326436333538393961346535
|
||||
37303533393036396662326263626432623135336530383939653833636634636135373431336638
|
||||
39353539333436356363623665633533393538393039653230633737653231616363343636353631
|
||||
39303361316565626564313237353738393533646666363362303866386462313633393531393034
|
||||
33633131376230616361323530313231636334653164383833326164393965333532363736373130
|
||||
35646162636538326461363163396362383366623635666162366136623363326266613632613966
|
||||
37663566313338633765366333663331336132636261636536353163306333346431373733396634
|
||||
61393536613634396530306239663365396563636563393038303831396135393831656430663363
|
||||
30346564663765636361613265393862636664646330393962643637353536363063363834663763
|
||||
62303836663963613438313161613963376262323337663166633530626264343034386563633266
|
||||
38633664636362333935663666383135393530643063666162313762316230303230336665383438
|
||||
30386664393239633931383230663464373235343964626534393663666236333933613965336632
|
||||
65633230356565623162396161313465636532333436326430623531646563323434363734623462
|
||||
31313638353237623230326361656533336661396533623566343062303164313566643538626536
|
||||
32366564353863343936376362666666303838663162383139663039353864383961396437306364
|
||||
65666666353335333733333538653336323763326437336338623466346435373239343533316365
|
||||
35393463656533373237363537663234303231396565663230383463336665656562633162366365
|
||||
33303337343464336262616332393465333963653164353063316630396432333264316136323465
|
||||
39376634616632373536623563626561303933346366636239613233303039643866643038343861
|
||||
63613230396632383031316336313862323166373939316362663236333564636437316338626661
|
||||
63656138666137366439343364346164343435326339366530313662626664383665643931303036
|
||||
38383761383964646231383431616265363932623630346162653738663531623236646138663165
|
||||
30323738386664633235353562323638636466353965366638626164656535366661333338393534
|
||||
31343039616562346232363831626136643131353834313161616531613633326637393864393831
|
||||
34383432363230313463623536663432356333386236326335373937613062646634346439613961
|
||||
33353364623363653136646262633963363265636263646466613833623330326434303634333161
|
||||
65636365663133306537626232333939316531353533316231373962313233636130336333323665
|
||||
35626363363432393032656434636635663263643664376134393239633538386438646232326234
|
||||
33623766643863643262636338656131363861396130373438656537653061343134393436653336
|
||||
36376163363133313165663436333563373335653761643236616664373231653632646539396132
|
||||
63336335363631613732373834313566316336373939313639306331383063366536306138646639
|
||||
31396337646465613237663733643762323339626262353862333030313661653231366461303338
|
||||
31313463353934356239323464343036316538643130316463646532633234336239663162636265
|
||||
66376137316235613130656266386661646336613037656563396338393134383835363232383130
|
||||
39373835313533373261613139326130626530383039383936633830373931306262623431396236
|
||||
64326337303236646136336466333764613032663537613935373538373939363965363734613339
|
||||
32316332396433333031356365366638313332313966616231353836316162356664353665386334
|
||||
35343032336664313366383435623632396538633264616637623363316461393561323966326133
|
||||
39306639363264656633613462336564653066633263333132623139643132386130623138333532
|
||||
63626433666566373037333035356231373238363039656562363131333634376138386662343230
|
||||
32613762306235313361396262633666376663323033613339353836363534326232353236613537
|
||||
38656139396265303030373539363766366439326332393130323631346233663436646362363237
|
||||
35303266383763663332656235383836633733333965303066643466376465356533383439643766
|
||||
65646663393632326136653332656164393834343766656333346364343332363032616636623637
|
||||
34313535356133653830343138653366653664353032663537383862316262303733613264343235
|
||||
34313939643639356261653334376533333330303632636266303031343031653432643763383462
|
||||
30646633346239663763646437623866346461386535643338323162363039383531343330383432
|
||||
62613564643966383463376133376538306539646463333338653136346665303836396538336237
|
||||
32373565653061376461303938383139336437666339333762396561613661356464326266303166
|
||||
65613532326438616434396461313130313761613063386633373462646462653065646162656264
|
||||
36313035623662383965643336643830643033323136373066343733363036666332366537336630
|
||||
31613532616135383365376635323037303361393561376166343638336663373532656165346333
|
||||
34383961346431363966356339323030373565313266346436326433633934363964313866323938
|
||||
32636538323838313064303330633436323062313637333764626437393737356337343661616336
|
||||
37323230353931323339366136633334316261646165343430343834326339303238613534363138
|
||||
65303932346435616563653961373561623761313534363732353633353330623030643033363730
|
||||
61383961326632393362633932353065613636313932633966616534383065613636363734306466
|
||||
66363933626561663038376334656534643566316134633338336161653532636239633536313933
|
||||
66616162386538346432323761636134376638336337623539646262393535613263366536366464
|
||||
66643566333162643261653136393331343431646233643130336562336366633664353734373636
|
||||
30633762333366613764346436333737623266343631363330356366663632366565396564613431
|
||||
31363439393463623334653666663636613366633437333237653363396561386233326334613831
|
||||
33643936366566373435653161346566636639633039363939376366366336373437346664656335
|
||||
65333633363336366263366433396661336262373931366639333362393939343031376166393362
|
||||
32633330303861666231393137356639363164303736306631656338373938666630316261393766
|
||||
64663935366265386463646163313336363066353164336631396365386465393933303566313333
|
||||
62336139666337353637646566623537613038636265636537313330666631623864333662663531
|
||||
64353563303165323066376133316266636433633863363039323535623862346663636464353031
|
||||
34333132383661303739333861326230346234626339633336383136333135336464656435633135
|
||||
32396139663737353062623533356638346563653032643465633333373761376362613434656432
|
||||
31623363376461326535386164616665363065623933306435643061666630363862393138663165
|
||||
63646263326665623066336361643836646437353432653963363165656364373531613362316632
|
||||
63633233333764626433353433303835663962393963656536323734643966333930633162636361
|
||||
62626437326437303766373331656232363763326565373535656266383365666336363466396438
|
||||
34356266336132343830336431326638383933393163373130653735306534303364373933646434
|
||||
37396235386162353232646639613531643332303038336330653234633437353464343131633961
|
||||
31666433303238633934646531333736383831376234326530613335346530363962643935656662
|
||||
35393066346136353534393934356466333036323666366263363561353861636538356363376362
|
||||
31376662376566626439386165383934623135613162613339356535643762626662363263653330
|
||||
33643166656634616534343133363030363930616231643535303163633436623463656364316631
|
||||
62333562636533306562376665326338383539313632666435636332353363613534316162373261
|
||||
31626535363334306630316463316264353633643738643939313932376461383833653232366438
|
||||
31323834613565633237353331386331323063653132343639366333613633623062643932346462
|
||||
64326533393438633530666266343435336165346131383330366264613138626630393939333131
|
||||
32343566613730313236626165623935343535656137306263306635653966613165356263653733
|
||||
62643036643430613231663332626431386432653632333538323465333562326339313538373332
|
||||
34663165653833356366353065393161313230666662666638666432663132303334363965346635
|
||||
32386236646366396530303635666539336538313530306462383335653738316364346637623361
|
||||
32646561613539303636613535643966356537353731373034343231373437383432373638333564
|
||||
65643932663566343761313064303265653963643264336230316163366638653537663464663937
|
||||
36303333393833306235636565643831376135373737373562333964306537383332343162656563
|
||||
31653462363734313531663738336430336630313537333039373366666539313362393134333465
|
||||
35636130383936613166316536663735616335373863333564383938323633386262363534306230
|
||||
35386530346539393434643362663961643430333532343032633832366137316362646234663437
|
||||
61363265396264636664663665633038623736646130303430353964653136633131323266396439
|
||||
63623931353330653662383261363431383866663764333037303463306637306262656630393261
|
||||
63393765633137323033316465376464623634363537396135623334633234356437653430643062
|
||||
36323766633162343862643538623364353839333861653862643864666234323965343336643762
|
||||
34666639303061326136613433623130333234303562396235623639353136613032363139636266
|
||||
66643732346339653764343965646263366639306263633837336232346465396535383530326337
|
||||
64396462633330336633633766323932316637376663656632633037356133633331303166663436
|
||||
38333434376534656562346638653130626462313564383366353537653531326335383138353330
|
||||
35656361343039343933316435646232396363326663643133373734616336313237386365346238
|
||||
31643732633630643066323935336432346363346636383239626365373232316230343239643365
|
||||
36383135643830386539336431643635323062346465383930623531663834663437636433376336
|
||||
32393035633534366639316339396632646538346661366564373366653766646263663137373561
|
||||
37306630336566326433643939376134333030653837343638646133346262323935653565363664
|
||||
30653036623638666535623964353738343461303834346535376235646566343761663438626135
|
||||
64643731383535363065333331346263333464623732306566326666643230623231336231643136
|
||||
66666664373334396139356332343564386332626632386332323434326533383063346532623035
|
||||
62303765306132616630323264363666343162313435343737303631346635393636363337316138
|
||||
30653966313563613533616362626263653261666464323664316132666462383334633937646330
|
||||
38646563646130393238663537343739643566346130616461366361643961643730353465366233
|
||||
63333163353532363564323032363162323235353262396338363533306131303934383562656561
|
||||
39366133376134353234653830623061363832643564353936343737303932386638396232303332
|
||||
64373736383538343662613033633564313532323663666333353632383431386239643631333739
|
||||
38303536343233393763386330323730653861333831393464306537316363623936663631616236
|
||||
62633932323831356361616130653266346439643837313339353338663330663136656133383332
|
||||
32646330626161313961633035323432313461643261626130636139646333623861663165626132
|
||||
30303330343632303239303938636662643037646431653662613839663865643638663966663064
|
||||
32616330643030656532626635346138386431383963376262663966313164643236616164356663
|
||||
37396432663463663039623334303235376462353930363939366430346664383431343062386561
|
||||
31336338356537636632663661356561343838373666383862333036386137333336376638613431
|
||||
32623234343065363762383035343462613766626134326165626636336335306239313363356362
|
||||
63663138613632396135323762633964653831663437613763623162336339396639656434323065
|
||||
346439636339643734636633613463343231
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure apprise application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "apprise"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -42,6 +38,8 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||
no_log: true
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
|
||||
+15
-3
@@ -3,8 +3,6 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
- files/authelia/secrets.yml
|
||||
|
||||
vars:
|
||||
@@ -48,6 +46,10 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0600"
|
||||
# Vault-файл: ansible расшифровывает его при копировании, и без no_log
|
||||
# --diff покажет содержимое открытым текстом.
|
||||
no_log: true
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Copy configuration file"
|
||||
ansible.builtin.template:
|
||||
@@ -56,6 +58,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0600"
|
||||
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||
no_log: true
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Copy gobackup config"
|
||||
ansible.builtin.template:
|
||||
@@ -89,9 +94,16 @@
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
- name: "Restart application with docker compose"
|
||||
handlers:
|
||||
# Приложение читает конфиги только при старте, а `state: present` не
|
||||
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||
# правку конфига применяет только явный рестарт.
|
||||
- name: "Restart application"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
# Только сам authelia: рестарт redis сбросил бы сессии всех сервисов.
|
||||
services:
|
||||
- "authelia_app"
|
||||
state: "restarted"
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure restic and backup schedule"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
backup_config_dir: "/etc/backup"
|
||||
backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}"
|
||||
@@ -40,6 +36,8 @@
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||
no_log: true
|
||||
|
||||
- name: "Create backup config file"
|
||||
ansible.builtin.template:
|
||||
@@ -48,6 +46,8 @@
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0640"
|
||||
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||
no_log: true
|
||||
|
||||
- name: "Allow user to run the backup script without a password"
|
||||
ansible.builtin.lineinfile:
|
||||
@@ -64,6 +64,8 @@
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||
no_log: true
|
||||
|
||||
- name: "Copy backup all script"
|
||||
ansible.builtin.copy:
|
||||
|
||||
+8
-13
@@ -2,10 +2,6 @@
|
||||
- name: "Configure bifrost application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "bifrost"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -46,7 +42,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0600"
|
||||
register: config_file_result
|
||||
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||
no_log: true
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -55,7 +53,6 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
register: docker_compose_file_result
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
@@ -65,15 +62,13 @@
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
# Bifrost читает config.json только при старте. Если изменился лишь конфиг
|
||||
# (а compose — нет), `state: present` не пересоздаёт контейнер, и правки
|
||||
# (ротация ключа, смена модели/правил) не подхватятся — нужен явный рестарт.
|
||||
- name: "Restart docker compose services if config changed but not docker-compose.yml"
|
||||
handlers:
|
||||
# Приложение читает конфиги только при старте, а `state: present` не
|
||||
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||
# правку конфига применяет только явный рестарт.
|
||||
- name: "Restart application"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "restarted"
|
||||
when:
|
||||
- config_file_result.changed
|
||||
- not docker_compose_file_result.changed
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure caddy reverse proxy service"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "caddyproxy"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -78,6 +74,7 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -103,7 +100,11 @@
|
||||
# tags:
|
||||
# - run-app
|
||||
|
||||
- name: "Restart application with docker compose"
|
||||
handlers:
|
||||
# Приложение читает конфиги только при старте, а `state: present` не
|
||||
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||
# правку конфига применяет только явный рестарт.
|
||||
- name: "Restart application"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "restarted"
|
||||
|
||||
+6
-12
@@ -2,10 +2,6 @@
|
||||
- name: "Configure calibre application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "calibre"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -38,16 +34,14 @@
|
||||
- "{{ config_dir }}"
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ base_dir }}/backup-targets"
|
||||
content: |
|
||||
{{ books_dir }}
|
||||
{{ config_dir }}
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ books_dir }}"
|
||||
- "{{ config_dir }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure dashboard start page"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "dashboard"
|
||||
app_user: "{{ app_name }}"
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure docker parameters"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
tasks:
|
||||
# - name: "Install python docker lib from pip"
|
||||
# ansible.builtin.pip:
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure dozzle application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "dozzle"
|
||||
app_user: "{{ app_name }}"
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Install eget and pinned binaries"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
# Версии инструментов: https://github.com/<repo>/releases
|
||||
|
||||
tasks:
|
||||
|
||||
+3
-4
@@ -2,10 +2,6 @@
|
||||
- name: "Configure gitea application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "gitea"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -52,6 +48,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||
# печатает --diff. Снять, когда они переедут в файлы.
|
||||
no_log: true
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure goaccess application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "goaccess"
|
||||
app_user: "{{ app_name }}"
|
||||
|
||||
+10
-13
@@ -2,10 +2,6 @@
|
||||
- name: "Configure gramps application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "gramps"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -59,17 +55,15 @@
|
||||
mode: "0750"
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ base_dir }}/backup-targets"
|
||||
content: |
|
||||
{{ data_dir }}
|
||||
{{ media_dir }}
|
||||
{{ backups_dir }}
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ data_dir }}"
|
||||
- "{{ media_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy rename script"
|
||||
ansible.builtin.copy:
|
||||
@@ -86,6 +80,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||
# печатает --diff. Снять, когда они переедут в файлы.
|
||||
no_log: true
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
- name: "Upload local homepage images to registry"
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
- vars/homepage.yml
|
||||
|
||||
tasks:
|
||||
|
||||
- name: "Push web service image to remote registry"
|
||||
community.docker.docker_image:
|
||||
state: present
|
||||
source: local
|
||||
name: "{{ homepage_nginx_image }}"
|
||||
repository: "{{ registry_homepage_nginx_image }}"
|
||||
push: true
|
||||
delegate_to: 127.0.0.1
|
||||
+10
-9
@@ -3,11 +3,19 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
- vars/homepage.yml
|
||||
|
||||
tasks:
|
||||
# Образ собирается на control-хосте самим репозиторием homepage (контракт —
|
||||
# `task image`: сборка сайта astro + nginx-образ) и едет на сервер через
|
||||
# docker save/load, без реестра. Роль выставляет факт app_image_tag.
|
||||
- name: "Build and deliver application image"
|
||||
ansible.builtin.import_role:
|
||||
name: "app_image"
|
||||
vars:
|
||||
app_image_name: "{{ homepage_image_name }}"
|
||||
app_image_src_dir: '{{ (playbook_dir, "..", "homepage") | path_join }}'
|
||||
|
||||
- name: "Create user and environment"
|
||||
ansible.builtin.import_role:
|
||||
name: owner
|
||||
@@ -27,12 +35,6 @@
|
||||
loop:
|
||||
- "{{ base_dir }}"
|
||||
|
||||
- name: "Login to Yandex Container Registry"
|
||||
community.docker.docker_login:
|
||||
registry_url: "{{ yc_container_registry }}"
|
||||
username: "oauth"
|
||||
password: "{{ yc_oauth_token }}"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||
@@ -46,6 +48,5 @@
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
pull: "always"
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
+6
-13
@@ -2,10 +2,6 @@
|
||||
- name: "Configure memos application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "memos"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -55,16 +51,14 @@
|
||||
mode: "0750"
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ base_dir }}/backup-targets"
|
||||
content: |
|
||||
{{ data_dir }}
|
||||
{{ backups_dir }}
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ data_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -73,7 +67,6 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
register: docker_compose_file_result
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure miniflux application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "miniflux"
|
||||
app_user: "{{ app_name }}"
|
||||
|
||||
+21
-13
@@ -2,10 +2,6 @@
|
||||
- name: "Install Netdata monitoring service"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "netdata"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -48,6 +44,7 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Find all go.d plugin config files"
|
||||
ansible.builtin.find:
|
||||
@@ -64,6 +61,7 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
loop: "{{ go_d_source_files.files }}"
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Find existing go.d config files on server"
|
||||
ansible.builtin.find:
|
||||
@@ -77,6 +75,7 @@
|
||||
state: absent
|
||||
loop: "{{ go_d_existing_files.files }}"
|
||||
when: (item.path | basename) not in (go_d_source_files.files | map(attribute='path') | map('basename') | list)
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Find all health.d config files"
|
||||
ansible.builtin.find:
|
||||
@@ -93,6 +92,7 @@
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
loop: "{{ health_d_source_files.files }}"
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Find existing health.d config files on server"
|
||||
ansible.builtin.find:
|
||||
@@ -106,6 +106,7 @@
|
||||
state: absent
|
||||
loop: "{{ health_d_existing_files.files }}"
|
||||
when: (item.path | basename) not in (health_d_source_files.files | map(attribute='path') | map('basename') | list)
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Copy health alarm notify config"
|
||||
ansible.builtin.template:
|
||||
@@ -114,15 +115,18 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Grab docker group id."
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -o pipefail
|
||||
grep docker /etc/group | cut -d ':' -f 3
|
||||
executable: /bin/bash
|
||||
register: netdata_docker_group_output
|
||||
changed_when: netdata_docker_group_output.rc != 0
|
||||
- name: "Grab docker group info"
|
||||
ansible.builtin.getent:
|
||||
database: "group"
|
||||
key: "docker"
|
||||
|
||||
# getent_group отдаёт поля строки /etc/group без имени группы,
|
||||
# то есть [пароль, gid, участники] — gid лежит под индексом 1.
|
||||
- name: "Store docker group id"
|
||||
ansible.builtin.set_fact:
|
||||
netdata_docker_gid: "{{ ansible_facts.getent_group['docker'][1] }}"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -140,7 +144,11 @@
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
- name: "Restart application with docker compose"
|
||||
handlers:
|
||||
# Приложение читает конфиги только при старте, а `state: present` не
|
||||
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||
# правку конфига применяет только явный рестарт.
|
||||
- name: "Restart application"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "restarted"
|
||||
|
||||
+9
-12
@@ -2,10 +2,6 @@
|
||||
- name: "Configure outline application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "outline"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -55,16 +51,14 @@
|
||||
mode: "0750"
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ base_dir }}/backup-targets"
|
||||
content: |
|
||||
{{ media_dir }}
|
||||
{{ backups_dir }}
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ media_dir }}"
|
||||
- "{{ backups_dir }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -73,6 +67,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||
# печатает --diff. Снять, когда они переедут в файлы.
|
||||
no_log: true
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
|
||||
+18
-16
@@ -2,10 +2,6 @@
|
||||
- name: "Configure remembos application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "remembos"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -17,6 +13,16 @@
|
||||
config_file: "{{ (config_dir, 'config.toml') | path_join }}"
|
||||
|
||||
tasks:
|
||||
# Образ собирается на control-хосте самим репозиторием remembos (контракт —
|
||||
# `task image`) и едет на сервер через docker save/load, без реестра.
|
||||
# Роль выставляет факт app_image_tag — его подхватывает docker-compose.
|
||||
- name: "Build and deliver application image"
|
||||
ansible.builtin.import_role:
|
||||
name: "app_image"
|
||||
vars:
|
||||
app_image_name: "{{ app_name }}"
|
||||
app_image_src_dir: '{{ (playbook_dir, "..", "remembos") | path_join }}'
|
||||
|
||||
- name: "Create user and environment"
|
||||
ansible.builtin.import_role:
|
||||
name: owner
|
||||
@@ -45,7 +51,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
register: config_file_result
|
||||
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||
no_log: true
|
||||
notify: "Restart application"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -54,13 +62,6 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
register: docker_compose_file_result
|
||||
|
||||
- name: 'Login to Yandex Container Registry'
|
||||
community.docker.docker_login:
|
||||
registry_url: '{{ yc_container_registry }}'
|
||||
username: 'oauth'
|
||||
password: '{{ yc_oauth_token }}'
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
@@ -70,12 +71,13 @@
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
- name: "Restart docker compose services if config changed but not docker-compose.yml"
|
||||
handlers:
|
||||
# Приложение читает конфиги только при старте, а `state: present` не
|
||||
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||
# правку конфига применяет только явный рестарт.
|
||||
- name: "Restart application"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "restarted"
|
||||
when:
|
||||
- config_file_result.changed
|
||||
- not docker_compose_file_result.changed
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
---
|
||||
- name: "Update and upgrade system packages"
|
||||
- name: "Remove user and application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
user_name: "<put-name-here>"
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure rssbridge application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "rssbridge"
|
||||
app_user: "{{ app_name }}"
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure base system parameters"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
apt_packages:
|
||||
- acl
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
- name: "Upload local transcriber images to registry"
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
- vars/transcriber.yml
|
||||
- vars/transcriber.images.yml
|
||||
|
||||
tasks:
|
||||
|
||||
- name: "Push web service image to remote registry"
|
||||
community.docker.docker_image:
|
||||
state: present
|
||||
source: local
|
||||
name: "{{ transcriber_image }}"
|
||||
repository: "{{ registry_transcriber_image }}"
|
||||
push: true
|
||||
delegate_to: 127.0.0.1
|
||||
@@ -3,12 +3,19 @@
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
- vars/transcriber.yml
|
||||
- vars/transcriber.images.yml
|
||||
|
||||
tasks:
|
||||
# Образ собирается на control-хосте самим репозиторием transcriber (контракт —
|
||||
# `task image`) и едет на сервер через docker save/load, без реестра.
|
||||
# Роль выставляет факт app_image_tag — его подхватывает docker-compose.
|
||||
- name: "Build and deliver application image"
|
||||
ansible.builtin.import_role:
|
||||
name: "app_image"
|
||||
vars:
|
||||
app_image_name: "{{ app_name }}"
|
||||
app_image_src_dir: '{{ (playbook_dir, "..", "transcriber") | path_join }}'
|
||||
|
||||
- name: "Create user and environment"
|
||||
ansible.builtin.import_role:
|
||||
name: owner
|
||||
@@ -38,12 +45,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: "Login to Yandex Container Registry"
|
||||
community.docker.docker_login:
|
||||
registry_url: "{{ yc_container_registry }}"
|
||||
username: "oauth"
|
||||
password: "{{ yc_oauth_token }}"
|
||||
# Vault-файл: ansible расшифровывает его при копировании, и без no_log
|
||||
# --diff покажет содержимое открытым текстом.
|
||||
no_log: true
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
|
||||
+9
-12
@@ -2,10 +2,6 @@
|
||||
- name: "Configure tududi application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "tududi"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -43,16 +39,14 @@
|
||||
# ничтожна, а retention (90 дневных / 36 месячных) страхует от редкого
|
||||
# несогласованного снимка.
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ base_dir }}/backup-targets"
|
||||
content: |
|
||||
{{ db_dir }}
|
||||
{{ uploads_dir }}
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ db_dir }}"
|
||||
- "{{ uploads_dir }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -61,6 +55,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||
# печатает --diff. Файловых секретов tududi не умеет, снять не выйдет.
|
||||
no_log: true
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
|
||||
+5
-11
@@ -2,10 +2,6 @@
|
||||
- name: "Configure tuwunel matrix server"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "tuwunel"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -47,15 +43,13 @@
|
||||
state: absent
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ base_dir }}/backup-targets"
|
||||
content: |
|
||||
{{ data_dir }}
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ data_dir }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Configure UFW firewall"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
tasks:
|
||||
- name: "Ensure UFW is installed"
|
||||
ansible.builtin.apt:
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
- name: "Update and upgrade system packages"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
tasks:
|
||||
- name: Perform an upgrade of packages
|
||||
ansible.builtin.apt:
|
||||
|
||||
+3
-4
@@ -2,10 +2,6 @@
|
||||
- name: "Configure wakapi application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "wakapi"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -61,6 +57,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||
# печатает --diff. Снять, когда они переедут в файлы.
|
||||
no_log: true
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
|
||||
+9
-12
@@ -1,11 +1,7 @@
|
||||
---
|
||||
- name: "Configure gramps application"
|
||||
- name: "Configure wanderer application"
|
||||
hosts: all
|
||||
|
||||
vars_files:
|
||||
- vars/secrets.yml
|
||||
- vars/vars.yml
|
||||
|
||||
vars:
|
||||
app_name: "wanderer"
|
||||
app_user: "{{ app_name }}"
|
||||
@@ -66,15 +62,13 @@
|
||||
state: absent
|
||||
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ base_dir }}/backup-targets"
|
||||
line: "{{ item }}"
|
||||
create: true
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ base_dir }}/backup-targets"
|
||||
content: |
|
||||
{{ data_dir }}
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0750"
|
||||
loop:
|
||||
- "{{ data_dir }}"
|
||||
mode: "0640"
|
||||
|
||||
- name: "Copy docker compose file"
|
||||
ansible.builtin.template:
|
||||
@@ -83,6 +77,9 @@
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||
# печатает --diff. Файловых секретов wanderer не умеет, снять не выйдет.
|
||||
no_log: true
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
|
||||
+1
-3
@@ -2,8 +2,6 @@
|
||||
ungrouped:
|
||||
hosts:
|
||||
server:
|
||||
ansible_host: "158.160.46.255"
|
||||
ansible_host: "92.53.105.41"
|
||||
ansible_user: "major"
|
||||
ansible_become: true
|
||||
application_dir: "/mnt/applications"
|
||||
mount_external_storage: true
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# app_image
|
||||
|
||||
Собирает docker-образ приложения **локально** (на control-хосте) и доставляет
|
||||
его на сервер через `docker save`/`load`. Тулчейн сборки (Go, node, …) на
|
||||
сервере не нужен — туда едет готовый образ.
|
||||
|
||||
Роль — первый кирпичик деплоя: её задача, чтобы на сервере оказался образ
|
||||
`<app_image_name>:<tag>`. Запуском приложения (рендер config и docker-compose,
|
||||
`docker compose up`) занимается остальная часть плейбука — позже это может стать
|
||||
отдельным кирпичиком.
|
||||
|
||||
Тег образа — случайный `BUILD_ID`, который роль генерит на каждый прогон.
|
||||
Дедупликации по содержимому нет: каждый деплой везёт образ и пересоздаёт
|
||||
контейнер. Для ручного нечастого деплоя это осознанный выбор — простота важнее
|
||||
экономии одного рестарта.
|
||||
|
||||
## Контракт с приложением
|
||||
|
||||
Приложение реализует **команду сборки образа** в своей рабочей директории.
|
||||
Роль — инициатор: она вызывает эту команду и передаёт ей случайный `BUILD_ID`
|
||||
через окружение. Команда обязана собрать **полный** образ и затегать его
|
||||
`<app_image_name>:$BUILD_ID`.
|
||||
|
||||
Пример реализации на Taskfile (тег из `$BUILD_ID`, по умолчанию `dev`):
|
||||
|
||||
```yaml
|
||||
image:
|
||||
desc: 'Полный docker-образ. Тег из $BUILD_ID (по умолчанию dev).'
|
||||
deps: [build]
|
||||
cmds:
|
||||
- docker build -t {{.BINARY}}:${BUILD_ID:-dev} .
|
||||
```
|
||||
|
||||
`${BUILD_ID:-dev}` держит команду обратно совместимой: `task image` без
|
||||
переменной по-прежнему собирает `<app>:dev` для локальной работы.
|
||||
|
||||
## Как работает
|
||||
|
||||
1. Роль генерит случайный `BUILD_ID` — это же и есть `app_image_tag`.
|
||||
2. Гоняет `app_image_command` в `app_image_src_dir` → приложение собирает
|
||||
`<app_image_name>:$BUILD_ID`.
|
||||
3. `docker save` образа → печатает размер архива → копирует tar на сервер →
|
||||
`docker load`.
|
||||
4. Отдаёт факт `app_image_tag`, чистит локальный тег и оба tar-файла.
|
||||
|
||||
Размер печатается до начала передачи — она самый долгий шаг деплоя, и по
|
||||
размеру видно, чего ждать (и что образ не распух незаметно).
|
||||
|
||||
Локальный тег на control-хосте снимается по завершении (образ уже на сервере).
|
||||
Старые образы на сервере подчищает `docker image prune -af` по крону (см.
|
||||
`playbook-system.yml`).
|
||||
|
||||
## Переменные
|
||||
|
||||
| Переменная | Обязат. | По умолчанию | Назначение |
|
||||
|---|---|---|---|
|
||||
| `app_image_name` | да | — | имя приложения; под ним тегается и живёт образ |
|
||||
| `app_image_src_dir` | да | — | рабочая директория приложения на control-хосте |
|
||||
| `app_image_command` | нет | `task image` | команда сборки образа (контракт); одна команда через `command`, без shell-синтаксиса |
|
||||
|
||||
## Выход
|
||||
|
||||
- Факт **`app_image_tag`** — тег доставленного образа (= `BUILD_ID`).
|
||||
- На сервере — образ `<app_image_name>:<app_image_tag>`.
|
||||
|
||||
## Пример
|
||||
|
||||
```yaml
|
||||
- name: 'Build and deliver application image'
|
||||
ansible.builtin.import_role:
|
||||
name: app_image
|
||||
vars:
|
||||
app_image_name: 'jellybit'
|
||||
app_image_src_dir: '{{ (playbook_dir, "..", "jellybit") | path_join }}'
|
||||
# далее compose-роль использует app_image_tag
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
# defaults file for app_image
|
||||
|
||||
# Имя приложения/образа (обязательно). Под этим именем приложение тегает
|
||||
# собранный образ (<app_image_name>:<tag>), под ним же образ живёт на сервере.
|
||||
app_image_name: ""
|
||||
|
||||
# Рабочая директория приложения на control-хосте — репозиторий с исходниками
|
||||
# и командой сборки образа. Обязательный параметр: задаётся явно в плейбуке.
|
||||
app_image_src_dir: ""
|
||||
|
||||
# Команда сборки образа — контракт приложения. Запускается в app_image_src_dir,
|
||||
# получает случайный BUILD_ID через окружение и обязана собрать ПОЛНЫЙ образ,
|
||||
# затегав его <app_image_name>:$BUILD_ID. По умолчанию — Taskfile-таск `task image`.
|
||||
# Исполняется через command (без shell): одна команда, без пайпов/&&/VAR=x-префиксов.
|
||||
# Подробности контракта — в README.md роли.
|
||||
app_image_command: "task image"
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: app_image
|
||||
author: "Anton Vakhrushev"
|
||||
description: "Локальная сборка образа приложения и доставка его на сервер через docker save/load" # yamllint disable-line rule:line-length
|
||||
license: "MIT"
|
||||
min_ansible_version: "2.14"
|
||||
platforms:
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- all
|
||||
- name: Debian
|
||||
versions:
|
||||
- all
|
||||
galaxy_tags:
|
||||
- docker
|
||||
- image
|
||||
- build
|
||||
- deploy
|
||||
|
||||
dependencies: []
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
# tasks file for app_image
|
||||
#
|
||||
# Роль-инициатор сборки. Гоняет команду сборки приложения на control-хосте и
|
||||
# доставляет получившийся образ на сервер через docker save/load. Тулчейн сборки
|
||||
# (Go, node, …) на сервере не нужен — туда едет готовый образ.
|
||||
#
|
||||
# Контракт приложения (app_image_command): команда в app_image_src_dir получает
|
||||
# BUILD_ID из окружения и собирает полный образ <app_image_name>:$BUILD_ID.
|
||||
#
|
||||
# Тег образа = случайный BUILD_ID (роль его генерит). Дедупликации по содержимому
|
||||
# нет — каждый деплой везёт образ и пересоздаёт контейнер. Для ручного нечастого
|
||||
# деплоя это осознанный выбор: простота важнее экономии рестарта. Старые образы
|
||||
# на сервере чистит `docker image prune -af` по крону (см. playbook-system.yml).
|
||||
#
|
||||
# Рассчитана на один целевой хост за прогон: сборка делегируется на control-хост,
|
||||
# при нескольких хостах в play повторится на каждый (мы деплоим последовательно).
|
||||
#
|
||||
# Выход: факт app_image_tag (= BUILD_ID) + образ <app_image_name>:<app_image_tag>
|
||||
# на сервере. Тег забирает следующий кирпичик (рендер compose).
|
||||
|
||||
- name: "Validate role input"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
# Формат имени docker-образа: он же идёт в имя tar-файла (vars/main.yml),
|
||||
# поэтому '/' и прочее ломало бы путь и docker-референс.
|
||||
- app_image_name is string and app_image_name is match('^[a-z0-9][a-z0-9._-]*$')
|
||||
- app_image_src_dir is string and app_image_src_dir | length > 0
|
||||
- app_image_command is string and app_image_command | length > 0
|
||||
fail_msg: >-
|
||||
app_image_name — валидное имя docker-образа (^[a-z0-9][a-z0-9._-]*$);
|
||||
app_image_src_dir и app_image_command — непустые строки
|
||||
quiet: true
|
||||
|
||||
- name: "Generate build id (image tag)"
|
||||
ansible.builtin.set_fact:
|
||||
app_image_tag: "{{ lookup('ansible.builtin.password', '/dev/null chars=ascii_lowercase,digits length=16') }}"
|
||||
|
||||
- name: "Build image and deliver it to server"
|
||||
# В --check command-таски пропускаются, tar не создаётся и copy упал бы «нет
|
||||
# файла на контроллере». Пропускаем всю доставку — факт app_image_tag уже
|
||||
# выставлен, поэтому рендер compose ниже честно покажет diff со своим тегом.
|
||||
when: "not ansible_check_mode"
|
||||
block:
|
||||
- name: "Build image locally via app contract"
|
||||
delegate_to: localhost
|
||||
# Сборка идёт от локального юзера. Инвентарь включает become глобально, но
|
||||
# локальная сборка под root не нужна — фиксируем инвариант явно.
|
||||
become: false
|
||||
ansible.builtin.command:
|
||||
cmd: "{{ app_image_command }}"
|
||||
chdir: "{{ app_image_src_dir }}"
|
||||
environment:
|
||||
BUILD_ID: "{{ app_image_tag }}"
|
||||
# Локальная сборка — средство доставки; факт изменения системы (сервера)
|
||||
# отражает загрузка образа ниже.
|
||||
changed_when: false
|
||||
|
||||
- name: "Save image to tar locally"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
ansible.builtin.command:
|
||||
cmd: "docker save --output {{ app_image_local_tar }} {{ app_image_name }}:{{ app_image_tag }}"
|
||||
changed_when: false
|
||||
|
||||
# Копирование tar — самый долгий шаг деплоя, а его длительность видна только
|
||||
# по размеру архива. Печатаем перед стартом передачи, чтобы ожидание было
|
||||
# объяснимым и заметный рост образа не проходил незамеченным.
|
||||
- name: "Measure image tar"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
ansible.builtin.stat:
|
||||
path: "{{ app_image_local_tar }}"
|
||||
get_checksum: false
|
||||
register: app_image_tar_stat
|
||||
|
||||
- name: "Report image size before transfer"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ app_image_name }}:{{ app_image_tag }} —
|
||||
{{ app_image_tar_stat.stat.size | human_readable }} к передаче на сервер
|
||||
|
||||
- name: "Copy image tar to server"
|
||||
ansible.builtin.copy:
|
||||
src: "{{ app_image_local_tar }}"
|
||||
dest: "{{ app_image_remote_tar }}"
|
||||
mode: "0644"
|
||||
|
||||
- name: "Load image on server"
|
||||
ansible.builtin.command:
|
||||
cmd: "docker load --input {{ app_image_remote_tar }}"
|
||||
changed_when: true
|
||||
|
||||
always:
|
||||
- name: "Remove local image tar"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
ansible.builtin.file:
|
||||
path: "{{ app_image_local_tar }}"
|
||||
state: absent
|
||||
|
||||
- name: "Remove remote image tar"
|
||||
ansible.builtin.file:
|
||||
path: "{{ app_image_remote_tar }}"
|
||||
state: absent
|
||||
|
||||
# Локальный тег — временная ручка (на сервер образ уехал через save/load).
|
||||
# Снимаем, чтобы control-хост не копил <app>:<random> от каждого прогона.
|
||||
- name: "Remove transient local image tag"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
ansible.builtin.command:
|
||||
cmd: "docker image rm {{ app_image_name }}:{{ app_image_tag }}"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
# vars file for app_image — внутренние значения роли, не для переопределения.
|
||||
|
||||
# Временный tar с образом для переноса control-хост → сервер. Имя завязано на
|
||||
# имя приложения и тег (= BUILD_ID), чтобы параллельные приложения и прогоны не
|
||||
# топтали файлы друг друга. Референсит app_image_tag (выставляется в tasks до
|
||||
# блока доставки) — vars роли шаблонизируются лениво, факт уже определён.
|
||||
app_image_local_tar: '/tmp/app_image-{{ app_image_name }}-{{ app_image_tag }}.tar'
|
||||
app_image_remote_tar: '/tmp/app_image-{{ app_image_name }}-{{ app_image_tag }}.tar'
|
||||
@@ -9,8 +9,8 @@ from invoke.context import Context
|
||||
from invoke.exceptions import Exit
|
||||
from invoke.tasks import task
|
||||
|
||||
HOSTS_FILE = "timeweb.yml"
|
||||
VARS_FILE = "vars/vars.yml"
|
||||
HOSTS_FILE = "production.yml"
|
||||
VARS_FILE = "group_vars/all/main.yml"
|
||||
AUTHELIA_DOCKER = "docker run --rm -v $PWD:/data authelia/authelia:4.39.4 authelia"
|
||||
|
||||
|
||||
@@ -112,7 +112,16 @@ def decrypt(ctx: Context, file: str) -> None:
|
||||
@task(aliases=["ee"])
|
||||
def edit_encrypted(ctx: Context, path: str) -> None:
|
||||
"""Редактировать vault-файл через ansible-vault edit (без расшифровки на диск)"""
|
||||
ctx.run(f"uv run ansible-vault edit {path}", pty=True)
|
||||
# Намеренно не через ctx.run: invoke заводит свой pty, а внешний терминал
|
||||
# переводит лишь в cbreak — ixon остаётся включённым, и Ctrl+S/Ctrl+Q съедает
|
||||
# драйвер терминала как XOFF/XON, до редактора они не доходят. subprocess
|
||||
# отдаёт редактору настоящий tty, и его raw-режим гасит ixon сам.
|
||||
result = subprocess.run(["uv", "run", "ansible-vault", "edit", path])
|
||||
if result.returncode != 0:
|
||||
raise Exit(
|
||||
f"ansible-vault edit: код возврата {result.returncode}",
|
||||
code=result.returncode,
|
||||
)
|
||||
|
||||
|
||||
# --- Синхронизация общих ролей с каноном ansible-roles ---
|
||||
@@ -125,7 +134,7 @@ SHARED_ROLES_DIR = os.path.normpath(
|
||||
os.path.dirname(os.path.abspath(__file__)), "..", "ansible-roles", "roles"
|
||||
)
|
||||
)
|
||||
SYNCED_ROLES = ["eget"]
|
||||
SYNCED_ROLES = ["eget", "app_image"]
|
||||
|
||||
|
||||
def _require_canon() -> None:
|
||||
@@ -205,7 +214,7 @@ def roles_push(ctx: Context) -> None:
|
||||
|
||||
|
||||
def _application_dir() -> str:
|
||||
"""Чтение application_dir: сначала из inventory (override), затем из vars/vars.yml."""
|
||||
"""Чтение application_dir: сначала из inventory (override), затем из group_vars."""
|
||||
inv_value = _yq('.ungrouped.hosts.server.application_dir // ""')
|
||||
if inv_value:
|
||||
return inv_value
|
||||
@@ -250,7 +259,7 @@ def authelia_validate_config(ctx: Context) -> None:
|
||||
"uv run ansible localhost"
|
||||
" --module-name template"
|
||||
f' --args "src=files/authelia/configuration.template.yml dest={dest}"'
|
||||
" --extra-vars @vars/secrets.yml"
|
||||
" --extra-vars @group_vars/all/secrets.yml"
|
||||
" --extra-vars @files/authelia/secrets.yml"
|
||||
)
|
||||
ctx.run(f"{AUTHELIA_DOCKER} validate-config --config /data/{dest}")
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
ungrouped:
|
||||
hosts:
|
||||
server:
|
||||
ansible_host: "92.53.105.41"
|
||||
ansible_user: "major"
|
||||
ansible_become: true
|
||||
+3
-5
@@ -4,9 +4,7 @@ app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1008
|
||||
app_owner_gid: 1009
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
|
||||
|
||||
homepage_nginx_image: "homepage-nginx:latest"
|
||||
|
||||
# Registry images
|
||||
registry_homepage_nginx_image: "{{ (docker_registry_prefix, homepage_nginx_image) | path_join }}"
|
||||
# Имя локально собираемого образа: им приложение тегает сборку (`task image`),
|
||||
# под ним же роль app_image везёт образ на сервер и его находит docker-compose.
|
||||
homepage_image_name: "homepage-nginx"
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
30616437646339396362313065656236323032383738623765393335333730303638393636326365
|
||||
6662656665353264383330363464633864313638613762610a643338323866623932636339616137
|
||||
61396536316165313762643866353336343263386664393930386332323234333831383762323439
|
||||
3437353835393531350a643032666361666337383562303636373035363533353734656362383165
|
||||
32306332343230653033663539353431636339343139633162336362383464363137656234303838
|
||||
36633963613036613932613734613364613835656361333564633833643662653337323837663038
|
||||
66663265316433646436656235386531303634376464636430386431343765613831333132313666
|
||||
33383637313939386666363663356162356463613637643431626636366133386638643065633463
|
||||
33663432336439666438383837313464653233663932326638653664313536373232343263656362
|
||||
66366234636366326631373466316538653734373537323663646562383730393535383536666538
|
||||
65336435313136393135306137323833323361633133356437393630316532316366633530616132
|
||||
33326462323266343139616464653132373034316336333161643032653538613666636562316537
|
||||
38613031643935383435656365343031343361323364356134626438313433393261383365373832
|
||||
62616262326362306561396662363266316239646266356661386362333534646238303438326262
|
||||
32393762383366336334333336663834633834666663656666383264356462346138393936343932
|
||||
64666131333636316334396562396139623834666432353239366238373266636233636433366539
|
||||
64613862313764353064303039393463326663333765386334386662326638376163333738636432
|
||||
61623330313937383830616635616461376334333332363235303861373061353234383533373963
|
||||
30333966316631616466306533656135313765633033366634663462393330396639333737636132
|
||||
62343136393262306661646334393036376233303533386261333130653465633238663566356438
|
||||
64363832356163366264386135363466623031316133626333356230393063636535373139333063
|
||||
32323736623434346430353532343362643435666439323136383563663066386135643363313765
|
||||
34333865323635343731303763646639343435626432316363353764373337653266383462633030
|
||||
33656261626166633366653861663833663739326361666561373039346436613261663138376566
|
||||
35646137363439363438333435366132383037643135353431623061653739363565393562343965
|
||||
65353932666361623765343465653735393337636364326263366234303461336662626336336631
|
||||
39373231646264646364326533333966333631356636333661386262333032613435356165623839
|
||||
38356639646263376632656436373233613063326433336235363434626666326131353961626566
|
||||
34373462313265643263346330643535613134366366306539316432653964333332636139396238
|
||||
36613065346236633331326230623238373533616531393231396134383462346235386265333836
|
||||
31656566633136636465383537616631353666356465623631306336386136366332303638306635
|
||||
35666262343964333930653865623461306633633936633066616462366137356262343932376230
|
||||
30306233633137376264373938383463373835396532306632616539363132356338313966363664
|
||||
64306363373063323561646439626365363938363936306533626139643637663733353766613237
|
||||
32396566633337646262383138323365373535663333343537383961316139623437626661643162
|
||||
31376138356332373066323130333361663765353839356163353439376131333262643637313265
|
||||
31333432616632396632343135633264333230633963313836346565343562383639306430633338
|
||||
39636264663633353337323035356461613966343637616238303266303039313463393866663436
|
||||
66656631356538646362656366626135393338313761366632616230383866666533643366323439
|
||||
36303538643662303134313838343939616536376531666164663133323933323831343266633835
|
||||
31613265616136366432396365653164666164373535333532663230383733613538383139396566
|
||||
37633066396161383239613362336533646337396632653933363831623032616462383164336436
|
||||
64353930326137306437636264336662343735353737313033316132663132643238616430326333
|
||||
65336238326638636637656330393937333032303966323036643134656532623136303138313930
|
||||
61313733336233633764343330633166353030316632613234613634303136396339396264636630
|
||||
37316434666631376366303430636562326465373139363665363436626131376333653835316530
|
||||
61366663653239323539396166646163646262623761303939396265333835336634666132396634
|
||||
35313235653039373937353164313339343562646330643463626533356439613932613766393337
|
||||
37653835313563633337656639316336323138333863396236636334313730343238613033383164
|
||||
35643462383635666466613666313836636435616536353939633764636466303065316362643737
|
||||
65313561343536393835643461643162356536636536356463616232356365346437333934363132
|
||||
66663233353664366665366365313537633864333134386232323139623637613532343262313838
|
||||
65343934663033636262653337373231616162366563393238383166646338346233616131336533
|
||||
35633262356265356534653533663333313532653336333564363764643939616639366563643432
|
||||
38363163663830636264346535666230363633346430393464643731303665313935366263306635
|
||||
38623032656262313939343366346233646534616139653533306633326363396262656664326132
|
||||
32313636366561366230646536323734383739316332613566346165393562396638393365346232
|
||||
33346339376362393766663664616231626135316332346266633039346239636536383237666334
|
||||
39646538323262333038643565646134336534303061636661306630643631663737353739623631
|
||||
30373630343466616332303361626365613237313136336539303464616662376133386135313362
|
||||
62323630303137396434653965386363353138613031663832373664626633656262646361373330
|
||||
30376537373034633739313365666537643535306231326235663964373565383432306334383661
|
||||
66623166396264383735336230303835303366376338633433653761353531336133363438623931
|
||||
65626237663962666239353339363162663937303936393061633239663362323038353863333061
|
||||
66616132376135326662663332363661616339363561653631363563323831323136636236326632
|
||||
31333235626437363561316533613762336563613762653531316233306337356261616563396233
|
||||
63656232643364383465383638353562313865316335353332656633616566303261653833663666
|
||||
65303834333561633436633933623234346238336161316631616362386637303665633938396336
|
||||
33346337343836376534356534373538343235303236333039316538393866323433366564356565
|
||||
65363630623737633162626265643235623461396163663833636564313333313330613064396437
|
||||
37303234316466666565613838633663653839353966303163376330323964303238393038383732
|
||||
30306336306465663766313933643132393636623066623466643130303465353632626538623165
|
||||
36323839313333376134616131653433316336663363343562336635623337396435663636343633
|
||||
37306334393439633835646635316437346464393532383535383336356435346266373534393732
|
||||
31343335626665326430383131333132303239313562356435333633623733323366313833343431
|
||||
30353466636531653464626462633637306131393037653837383833383664363762363435616436
|
||||
37373762666662663763613661343935333834316332643439376164316564386564336664363461
|
||||
32613561306630356437306536666566353533376466613137323830336361633762636433316235
|
||||
66663166623630653335633963366238343166386361326537666465626238346466326230633961
|
||||
61633565326565646636393937393034333835643663636464316362366431353166646263623330
|
||||
38666431646232373163373361353135323862376366666362313235336431333734323264613265
|
||||
34313330303262323432646634666565343237333431363766376131653138343233303332643636
|
||||
65316232613734663239646131336230343663353136393262646135663734616134366664373361
|
||||
63303932393239326330396637353361333337336165663032356533646637363438323633306430
|
||||
32666266623561343132323730353839316339653464613965376637333132333463303238393838
|
||||
64313332626633383262633362343465326334343739316462383762633334643465386334646634
|
||||
36623963386361633139623533333635663663343865323630356566316639353733393137623339
|
||||
33353363613766653163333633353661616362376130656633323264396162646262376566363437
|
||||
37616261363234316230656362653562643536366239656261616630376431316532373561653231
|
||||
61333438353031353639346532393733616561393536363732323039363034353064353138313836
|
||||
34396564383132373138396466363864323538326538336432313961373663323264343132316537
|
||||
36656435623232623139626139313032333532356561346531303535303235626466383036353832
|
||||
35333931633161386662386564396662356432366164663163353539303638653762393761353063
|
||||
33326666306561363731353435303132326335366334646336353530313264663438346536633030
|
||||
63373136313430306439613666626239316338646537666236363539656338663238373337323464
|
||||
32373565666165623062613338616331326232653966373962636639626166396639643961643539
|
||||
39643732623165653464313861656537373333323566306238356462343462626663616330333830
|
||||
37376337646635346634663461303363643537363633356339393330323565373266336461386361
|
||||
31393632386635653137623434663032336631633065323235656232326639326539313463366230
|
||||
63653235333865333266306162643938643162363766643464653665646131363463326433643662
|
||||
35346138393433646162366164316331366462346332326137343833313762306635396661346231
|
||||
32623230373433646237303635343837343063656639313236306563376164386239346262643930
|
||||
66386465396262353434633937303732653631373766663139306236616130383833383062303338
|
||||
31613434393138333437646432373531623666363763323564663730393565343930323233643334
|
||||
66373665386238623066366166616163663362376463646331373838343863643831376165333765
|
||||
32653561343363666161643762383831356639663737393132616566613230623162616439326562
|
||||
34313836656136323565343636396530663865366332326436303162386535393736656330326534
|
||||
61356538643337666432656232613962393139656336373462353636636233363534643137393031
|
||||
64343965336637663064653563346232323439653437366238353039663932346266626338343038
|
||||
61353064653730636232666661653830373661303162666539646231373230336362653136643137
|
||||
65623662303534316530313739333233623865303866623630663136663761666134666663393437
|
||||
36653331393566353032313036363533383038363937376535626435383939303666316531363266
|
||||
30353235336333376337393531326661303937353266346164663438633062613166656431663834
|
||||
33663539343433623564306638366131303032333033656466646134353036666133346236373639
|
||||
39663136666331613231646639306565306561656439366566653666396133363561643663396632
|
||||
34316461326562663161633630316531613363616264303036616336313939336465366163366262
|
||||
31646539363239346637396561306466353338346461626237333633313131316534323661356663
|
||||
30366434356137333763643937363164393833376261616131336639366231383261333262613637
|
||||
65646262336439626262353934616530343730353436313232393663363237393034316433663038
|
||||
31643261663534303639313433626339303562343036303335363539396234333465326538383062
|
||||
32613266396338646135626462383534323763323835663863646337646139306265633865613933
|
||||
61623866343636653638373032303732616530326265613366616535376435616266326366386435
|
||||
39366139613638333237646164326661323362663564313831386437343838383937326362656538
|
||||
37313032613434646238383064613266623138343166613138353532663231633235653363396162
|
||||
39623733393161343932663361343462393231666638666166663838373161656631303762346461
|
||||
36346263386631663839386261636465326337616331646631656366346338306436313066353735
|
||||
31323431636538613264613536396534363565333939376466353031663939663362646638363261
|
||||
38323139346630633031316532663538613330626365333234313361356266303261376366643534
|
||||
66643166363536313066343064346635616465303266336134393065386262613932653434393831
|
||||
31306165383039643533613439646263316338306562653332323739646335336433646535616362
|
||||
35393634623533343064373137313431616439316432646635343333636432633634623861393032
|
||||
64363135373166326533326235363030363732616263313263336566653634393765306533303834
|
||||
64363035363232663332303262393561626663653430643061326162616535353439316665663561
|
||||
63643939613037303531323231373330333461636438373561313866353865373733373461366333
|
||||
35373534306636396563623433663663353938663766656436346430666635326632356235366664
|
||||
33396237346532613436306531616365646231626138393632356265376337343062616337313665
|
||||
35616166323266393735323964613536303936323763646135383361346536343931323938616464
|
||||
32623363623262306135363732333432616131663537313765343961393464656235666539336561
|
||||
61616631373032393538306138356433303461383235653165363762383161633961386237363338
|
||||
38356235656563393163343062333365643366633962646432613530346433653838313762623938
|
||||
31313563323062323564363035386637653730393133393634383734646533323866396535366331
|
||||
65373539323964383434656138346332633261303861653034336534393266313639663737636362
|
||||
30616165326230633830633264666366323738396433663132316133326166633139613735653866
|
||||
36316330636331663338313431656131363863613438353231366166633034343531303638373431
|
||||
38303061616161353961626364666539303334373965396461346463623065646164656638363130
|
||||
65646563636563643733306165633038623639333761363239646164616531316137393330323632
|
||||
64303238626564626130343331663730643361353765633835366338303031363265363362303534
|
||||
37333963623737323830383764656165613765383036626638323238613464613431363965316236
|
||||
63366364336665653731613637346639376139643539363038396438333463396636346436366564
|
||||
31393464316533363333343763343337366365333033343331623265616336383064666438373662
|
||||
37656239363366633038626366343466353935633832633663356261323237306261313432353830
|
||||
64353734336535636331636634326332373637643238313465306565626532633665333766633933
|
||||
37343831323830393838306436306333363138633634393232363730353561626437323061656536
|
||||
34633535363365303865383961623739393461376233643163643930376163363334393266663839
|
||||
65353039666339613438396563356637343163383738613238323830643534666166663538633961
|
||||
36353064393834643035366433613866386531656536353362656435623562386536386662613935
|
||||
65663464646165376134666163386662363038393132333034316566643366663562643730326663
|
||||
32626538636538393333323732353339306138393031623336333236323335626365366235393136
|
||||
66333763303333396233336561376233633431376234303563303637303662303533313166663463
|
||||
35316464316335333865336432646663376464303235613161333661626363386631313933326261
|
||||
64653133653763396131383737353734646536343361316466303162323161323763663031356633
|
||||
66643737616565303337663866346532363861353938636234333337333737623534663437383866
|
||||
62613364653938626239333165356461333836323764323862393738393237353331306138336231
|
||||
30613366613430363938306439373766616262373838636331383932303439356333623737633634
|
||||
66343366306631336631613730646639336236383136656562316262356364356534633731333134
|
||||
35313433353539353033343862623161653330313138336339326262613531643630623766363334
|
||||
38666563326537613934633132383033363532343834613066356537396661323966623738343132
|
||||
63333263333962336665633738643461376635336632393461383234313261616137333966636231
|
||||
38393933383464353835356330373139313464643733656562356632346465633035326362373664
|
||||
64373130353033306161376339636531336332663739623063383965623335666236646133663762
|
||||
34383263366632363832623666656661653261396463383239323437333161656639313562666238
|
||||
39356361316564393963333039646161656164643264653132653963666362376531626663663362
|
||||
37656231356132616533646132323064333734646433366330333132373666636461316333656237
|
||||
30363936383930313962303332303937326239366139303563303639663938613061303339373836
|
||||
38616331303830363862313639636638393630323161373962343064366664636532663439353935
|
||||
36633035326165663237623036303839383037636532386164353865633430656135646134643738
|
||||
34613236373361326634323633333332633535353030343261393838313635343232363764626233
|
||||
39396437643338313733313434663835336532393062313563653630373931636361656536343637
|
||||
38356364333432363831623934353161623431353863626438306339393062626338633833633233
|
||||
39633639383731646431613536363837653862333965326337626230656631623336316435616639
|
||||
38363063353965383361313464623961616139643166626531646138373261356462633465363962
|
||||
61356235386363306264376334643031613638366366363438623230303738656162393131393765
|
||||
36303138316462643465346632326632653131633234643466656266306330613333656534626233
|
||||
35326262386466363532373763323331323262383635636435346539633033383966376530366666
|
||||
37333766313235333131643135386130313035636536313266323865653265373432323464373061
|
||||
66633639363935663432626132623534346339306662366530386136333366346130306639343161
|
||||
31316161393161303039633130653862326136643563336461666366376535666239306531383566
|
||||
62363238636634616433646365313461336530396263653764633330333232386134663038306166
|
||||
62633937306666633665356237616236383263343165646164343030646137373330623934383435
|
||||
39656633303565376665316639326230336339386364626436383433343366613565303761636237
|
||||
65373165383864663530343037616632363433663765623538643636386464623363366461306330
|
||||
61666137316663323931373532306366643432626530323333626532373061333436663362396638
|
||||
35636638386162343632653439313930346261613863646431623932316262656230303062336463
|
||||
62646433653231363138623232643539376636663738616163653666633132396461666334633332
|
||||
64616462303864313030383037663065653665323832383037646133663235613961383738633561
|
||||
64333265656332653662323434613534313934616434353338656439646131343133353632383863
|
||||
35663637303666633530396263356131333564323239663966333139313336613435333839343762
|
||||
31623966306138636463626534313839353439623061363431366636353362633666626465323730
|
||||
32373937343361623031643466383333636266393166383032393939366331316466336662343161
|
||||
32613763363965636465363261366432376633346234613730663431373765323736316665313131
|
||||
36323431323832616335643134653665643434336166653361343533323765323032323232343835
|
||||
63356265373139386430623564613334666264633665643938643433383237313264643638326462
|
||||
35656134353561666162643633363761326135643665643030396636636533373031643664306133
|
||||
64663162313563656435366365343965323363633264326563376663353963356261636661373561
|
||||
66653963353935386162336332636365313631383066306431633334326538306634636337653838
|
||||
61653762313034616333326231623666653363343564636433356138323736356433393133643831
|
||||
38363634363536343231653739333539336362663866643735343332623631336665333834636331
|
||||
35363638663632356131333335363438313437656133336161393231616636353536356338376534
|
||||
39613635366562656564666132386238363566623033356536333265356533333136623764663336
|
||||
61313030653163613063666566306432333338386633343565316365663238353161336364393834
|
||||
36366166393863633636323433663138666266393732386333306336633163393962383137656339
|
||||
61386663653164383436356564323561346262633163613339643834623633666266353566386333
|
||||
30626261656139376335303461623761656438643062393732333463343261633132623233633439
|
||||
30353830393931386336396130366334623862336234346332313636633832313933303465363534
|
||||
38653334376231326137633836643636383163613736623061326433636361643965316539383832
|
||||
66666361663132663932376438653734653135323464323036393933656635353433336636326666
|
||||
66643939373730346362613361326638393162393137386566623334356265653430343635306636
|
||||
64353938356633336134333938326630653133666363326530643161363734393830643637303766
|
||||
39393261353235326133623066626331316537373061363464663438386231613532623234326237
|
||||
62376463623033613165386639626262333133386438643463613930333332613066386437316134
|
||||
37646664303264386231376261393963323634666431343637316364616130343339396539666534
|
||||
65643937386239653561376632316239376236323366386134303339316566316230323936633635
|
||||
32313432323532363466343238653930363439643332626665333433643863366366316463336265
|
||||
32643433636139373333653833373439626239316434333132376664373534623939306436613665
|
||||
61306265313338353365303262663231306264343866626137613864363536623031666133633766
|
||||
36663839353132643337303737323665643163356134633132343765313231613835333938623761
|
||||
64336637343762356562653335376132626431373237623964633036376261363532643463316262
|
||||
63353530373634303262653132656633396464383134343433376463303364356639646134636436
|
||||
32353162623536363432633136393732613534656531376465336130646364643939323763346361
|
||||
64333639393563636364396236663530343738643363623365323438363236623466663132663166
|
||||
64306262623836666536623434363439393537323632653162623934346438646539643638633933
|
||||
64376136393062643039306535336133346563326466636666626565313366333331343636326532
|
||||
34653564336637346637353536656166353130333635373836643337316331343630353936396438
|
||||
39323065363565626136376235313936366630663638343263623564643165613765343838343436
|
||||
64323635326362313961616634393036643932623133393632346539633036383061396463343461
|
||||
37636432323734643632376531373336383532393531633762626633613466383165393235336433
|
||||
32303263353831336364313066353761353134623831616135666438383662323639626337626635
|
||||
62326637643132393761626364623139666330633264363233613864313965333830303335663761
|
||||
63393733343035623137383563663433626433356130336635313034636533633037653238316563
|
||||
31643033626662363030613838373963376138653232383062646330356136366462313866633139
|
||||
63316139336335316139623231326539326439663933336335323137343866623161
|
||||
@@ -1,2 +0,0 @@
|
||||
---
|
||||
transcriber_image: "transcriber:2fc5a56-1761210134"
|
||||
@@ -10,8 +10,3 @@ config_file: "{{ (config_dir, 'config.toml') | path_join }}"
|
||||
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
backups_dir: "{{ (base_dir, 'backups') | path_join }}"
|
||||
|
||||
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
|
||||
|
||||
# Registry images
|
||||
registry_transcriber_image: "{{ (docker_registry_prefix, transcriber_image) | path_join }}"
|
||||
|
||||
Reference in New Issue
Block a user