Compare commits
24
Commits
cd0d014c99
...
bb141807c9
| 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` для групповых запусков.
|
- `playbook-*.yml` — плейбуки по одному на сервис, `playbook-all-*.yml` для групповых запусков.
|
||||||
- `production.yml` — инвентарь с единственным хостом `server`.
|
- `production.yml` — инвентарь с единственным хостом `server`.
|
||||||
- `vars/*.yml` — переменные приложений и образов, `vars/secrets.yml` — зашифрованные секреты (vault).
|
- `group_vars/all/` — общие переменные для всех хостов: `main.yml` (открытые) и `secrets.yml` (зашифрованные vault). Подхватываются Ansible автоматически, `vars_files` в плейбуках не нужен. Имя `secrets.yml` обязательно: pre-commit-хук и `.crushignore` ищут слово `secret(s)` в имени файла.
|
||||||
- `roles/` — кастомные роли (`eget`, `owner`, `secrets`), галактические роли в `galaxy.roles/`.
|
- `vars/*.yml` — переменные отдельных приложений (homepage, transcriber), подключаются через `vars_files`.
|
||||||
|
- `roles/` — кастомные роли (`eget`, `owner`, `secrets`, `app_image`), галактические роли в `galaxy.roles/`.
|
||||||
- `files/<app>/` — docker-compose шаблоны, конфиги, скрипты бэкапов для каждого сервиса.
|
- `files/<app>/` — docker-compose шаблоны, конфиги, скрипты бэкапов для каждого сервиса.
|
||||||
- `templates/` — общие шаблоны (например `env.template`).
|
- `templates/` — общие шаблоны (например `env.template`).
|
||||||
- `scripts/` — вспомогательные Python-скрипты (SMTP-утилиты для Yandex Cloud Postbox).
|
- `scripts/` — вспомогательные Python-скрипты (SMTP-утилиты для Yandex Cloud Postbox).
|
||||||
@@ -64,7 +65,7 @@ uv run ansible-galaxy install --role-file requirements.yml
|
|||||||
- `playbook-wakapi.yml` — трекинг времени.
|
- `playbook-wakapi.yml` — трекинг времени.
|
||||||
- `playbook-memos.yml` — заметки.
|
- `playbook-memos.yml` — заметки.
|
||||||
- `playbook-outline.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-dashboard.yml` — дашборд со списком сервисов (gethomepage.dev) на `start.vakhrushev.me`, закрыт за Authelia.
|
||||||
- `playbook-rssbridge.yml` — RSS-агрегатор.
|
- `playbook-rssbridge.yml` — RSS-агрегатор.
|
||||||
- `playbook-netdata.yml` — мониторинг.
|
- `playbook-netdata.yml` — мониторинг.
|
||||||
@@ -72,7 +73,7 @@ uv run ansible-galaxy install --role-file requirements.yml
|
|||||||
- `playbook-goaccess.yml` — аналитика веб-логов Caddy в реальном времени.
|
- `playbook-goaccess.yml` — аналитика веб-логов Caddy в реальном времени.
|
||||||
- `playbook-gramps.yml` — генеалогия.
|
- `playbook-gramps.yml` — генеалогия.
|
||||||
- `playbook-calibre.yml` — управление электронными книгами.
|
- `playbook-calibre.yml` — управление электронными книгами.
|
||||||
- `playbook-transcriber.yml` — транскрибация (образ из Yandex Registry).
|
- `playbook-transcriber.yml` — транскрибация (образ собирается локально ролью `app_image`).
|
||||||
- `playbook-wanderer.yml` — пешие маршруты.
|
- `playbook-wanderer.yml` — пешие маршруты.
|
||||||
- `playbook-remembos.yml` — интервальное повторение.
|
- `playbook-remembos.yml` — интервальное повторение.
|
||||||
- `playbook-tuwunel.yml` — Matrix-сервер (Tuwunel) с federation-делегацией на apex-домен.
|
- `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-setup.yml` — системная настройка целиком (system + docker + eget + backups).
|
||||||
- `playbook-all-applications.yml` — деплой всех приложений.
|
- `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>`).
|
- `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/owner` — создаёт системного пользователя/группу для приложения, настраивает SSH-ключи, переменные окружения (~/.env, ~/.bashrc).
|
||||||
- `roles/eget` — скачивает и устанавливает утилиту eget.
|
- `roles/eget` — скачивает и устанавливает утилиту eget.
|
||||||
- `roles/secrets` — управляет vault-зашифрованными файлами секретов для приложений.
|
- `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`.
|
Галактические роли (`galaxy.roles/`): `geerlingguy.security`, `geerlingguy.docker`, `yatesr.timezone`.
|
||||||
|
|
||||||
## Шаблоны и переменные
|
## Шаблоны и переменные
|
||||||
|
|
||||||
- Суффиксы шаблонов: `.template.yml`, `.template.sh`, `.template.cfg`, `.template.conf`, `.template.toml`, `.template` (для файлов без естественного расширения) — рендерятся Ansible модулем `template`. Расширение оригинального формата сохраняется после `.template.` ради подсветки синтаксиса в редакторе.
|
- Суффиксы шаблонов: `.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`).
|
- Большинство приложений определяют переменные inline в плейбуке. Отдельные файлы переменных только у homepage и transcriber (`vars/homepage.yml`, `vars/transcriber.yml`).
|
||||||
- Общие переменные из `vars/secrets.yml`: `application_dir`, `bin_prefix`, `primary_user` и др.
|
- Общие переменные из `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`.
|
- Каждое приложение: `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`.
|
- 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-файлах.
|
- `gitleaks` — поиск секретов в staged-файлах.
|
||||||
- Проверка что секретные файлы зашифрованы vault.
|
- Проверка что секретные файлы зашифрованы 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`).
|
- Отступы: 2 пробела для YAML/Jinja, 4 пробела в остальных файлах (`.editorconfig`).
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ uv run ansible-galaxy install --role-file requirements.yml
|
|||||||
- Безопасность осуществляется с помощью `ufw` и `fail2ban`.
|
- Безопасность осуществляется с помощью `ufw` и `fail2ban`.
|
||||||
- Докер используется для запуска и изоляции приложений. Для загрузки образов настраивается Yandex Docker Registry.
|
- Докер используется для запуска и изоляции приложений. Для загрузки образов настраивается Yandex Docker Registry.
|
||||||
- Выход во внешнюю сеть через proxy server [Caddy](https://caddyserver.com/).
|
- Выход во внешнюю сеть через 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).
|
- Для мониторинга за сервером устанавливается [netdata](https://github.com/netdata/netdata).
|
||||||
|
|
||||||
## Настройка DNS
|
## Настройка DNS
|
||||||
@@ -43,7 +43,7 @@ uv run ansible-galaxy install --role-file requirements.yml
|
|||||||
Деплой приложения через ansible:
|
Деплой приложения через ansible:
|
||||||
|
|
||||||
```bash
|
```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:
|
Или через таску invoke:
|
||||||
@@ -55,5 +55,5 @@ uv run ansible-playbook ansible-playbook -i timeweb.yml --diff playbook-gitea.ym
|
|||||||
## Удаление приложения <name>
|
## Удаление приложения <name>
|
||||||
|
|
||||||
```bash
|
```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]
|
[defaults]
|
||||||
|
inventory = ./production.yml
|
||||||
host_key_checking = True
|
host_key_checking = True
|
||||||
vault_password_file = ./ansible-vault-password-file
|
vault_password_file = ./ansible-vault-password-file
|
||||||
roles_path = ./galaxy.roles
|
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-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-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) | — |
|
| 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-ревью)_
|
- [Убрать группу `docker` у сервисных пользователей](service-users-docker-group.md) — `docker` = root на хосте; ~все app-аккаунты фактически root, изоляция «юзер на сервис» обнуляется _(sec-ревью)_
|
||||||
- [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_
|
- [Изолировать/запаролить сессионный redis Authelia](authelia-redis-isolation.md) — стор сессий SSO в общей сети без пароля; запись в него = hijack SSO всех сервисов _(sec-ревью)_
|
||||||
- [Алерты на проблемные контейнеры](container-alerts.md) — wakapi крутился в restart-loop несколько дней незамеченным; healthcheck в compose + алерты Netdata
|
- [Алерты на проблемные контейнеры](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-ревью)_
|
- [Захардить мониторинг-стек (netdata / dozzle / goaccess)](harden-monitoring-stack.md) — netdata/dozzle: лишние привилегии + docker.sock + доверие Remote-User в общей сети; socket-proxy + monitoring_network _(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-ревью)_
|
||||||
- [Ограничить `/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-ревью)_
|
||||||
- [Закрыть 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-ревью)_
|
||||||
- [Понизить log level Authelia до info](authelia-log-level.md) — `debug` в проде утекает детали аутентификации и OIDC через `docker logs`/dozzle _(sec-ревью)_
|
|
||||||
- [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_
|
- [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_
|
||||||
- [Запретить SSH-вход под root](ssh-disable-root-login.md) — `PermitRootLogin yes` пускает root по ключу; наименьшие привилегии + defense-in-depth (пароль уже off) _(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%
|
- [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
|
- [Синхронизация общих Ansible-ролей между репозиториями](shared-roles-sync.md) — `owner`/`eget`/`secrets` дублируются между репозиториями и дрейфуют; канон в ansible-shared + rsync-таски invoke
|
||||||
- [Уведомления о размере приложений и диске](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_
|
- [Уведомления о размере приложений и заполнении диска](disk-usage-alerts.md) — алерты на заполнение диска и аномальный рост data-томов; сервер ресурсно ограничен _(tududi)_
|
||||||
|
- [Handlers рестарта для оставшихся приложений](handlers-remaining-apps.md) — apprise/dashboard/transcriber/miniflux монтируют конфиг в контейнер, но рестарта нет вовсе; правка конфига не подхватывается
|
||||||
- [Composable-роль `backup`](ansible-backup-role.md) — бэкап — самый чистый шов для extraction (одинаков у всех, различается только список targets) _(ансибл-ревью)_
|
- [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-ревью)_
|
- [Мелкий хардненинг 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-ревью)_
|
- [Мелкий хардненинг контейнеров](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-ревью)_
|
- [Унифицировать 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 _(ансибл-ревью)_
|
- [Причесать роль `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 _(ансибл-ревью)_
|
- [Инвентарь: `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` _(ансибл-ревью)_
|
- [Фоновая зачистка стиля и конфигурации Ansible](ansible-style-nits.md) — sudoers.d, профиль ansible-lint, `ansible.cfg`, кавычки, `cache_valid_time` _(ансибл-ревью)_
|
||||||
- [Logrotate для логов бэкапов](backup-logs-logrotate.md) — логи `backup-all.py`/restic копятся без ротации _(tududi)_
|
- [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` под
|
- **`production.yml`** объявляет хост `server` под `ungrouped:`,
|
||||||
`ungrouped:`, хост-специфичные данные (`application_dir`,
|
хост-специфичные данные (`ansible_host`, `ansible_user`, `ansible_become`)
|
||||||
`mount_external_storage`, `ansible_host`, `ansible_user`) вписаны инлайн.
|
вписаны инлайн. Конвенциональнее — `host_vars/server.yml` и хост в именованной
|
||||||
Конвенциональнее — `host_vars/server.yml` и хост в именованной группе. Два
|
группе, а не в `ungrouped`.
|
||||||
инвентаря с одинаковым именем хоста + `hosts: all` — ошибка `-i` молча уедет не
|
|
||||||
туда. (`timeweb.yml` — артефакт завершённой миграции, заодно решить, нужен ли
|
|
||||||
он ещё.)
|
|
||||||
- `ansible_become: true` глобально — всё бежит под root. Для личного сервера
|
- `ansible_become: true` глобально — всё бежит под root. Для личного сервера
|
||||||
прагматично; точечный `become`/`become_user` ближе к наименьшим привилегиям, но
|
прагматично; точечный `become`/`become_user` ближе к наименьшим привилегиям, но
|
||||||
это низкий приоритет.
|
это низкий приоритет.
|
||||||
|
|
||||||
Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §6.
|
Контекст: [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.
|
`/root/.gramps`), rssbridge, dozzle, homepage и valkey/postgres-сайдкары gramps.
|
||||||
Дотянуть там, где образ позволяет (rssbridge, valkey) — продолжение уже принятой
|
Дотянуть там, где образ позволяет (rssbridge, valkey) — продолжение уже принятой
|
||||||
в проекте практики.
|
в проекте практики.
|
||||||
- **homepage на `:latest`** (`vars/homepage.yml`) — плавающий тег + ночной `docker
|
- ~~**homepage на `:latest`**~~ — **снято**: переход на роль `app_image` дал
|
||||||
image prune` может подтянуть другое содержимое без отката. Перевести на
|
каждому деплою уникальный тег из `BUILD_ID`, плавающего `latest` больше нет
|
||||||
иммутабельный `<sha>-<timestamp>` по образцу transcriber
|
ни у homepage, ни у остальных.
|
||||||
(`vars/transcriber.images.yml`).
|
|
||||||
- **Соглашение о портах**: зафиксировать в AGENTS.md, что новые `ports:` — только
|
- **Соглашение о портах**: зафиксировать в AGENTS.md, что новые `ports:` — только
|
||||||
с `127.0.0.1:`. Docker публикует порты мимо UFW, поэтому дисциплина
|
с `127.0.0.1:`. Docker публикует порты мимо UFW, поэтому дисциплина
|
||||||
localhost-биндинга — единственный контроль (сейчас соблюдается: apprise, wanderer).
|
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` группу оставить для ручных
|
тем — лучше через socket-proxy). За `primary_user` группу оставить для ручных
|
||||||
операций (`inv ssh`).
|
операций (`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.
|
Связано: все playbook-*.yml (owner_extra_groups), roles/owner, playbook-docker.yml.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
Почему всё равно стоит закрыть:
|
Почему всё равно стоит закрыть:
|
||||||
|
|
||||||
- **Наименьшие привилегии.** Ansible ходит под непривилегированным `major` с
|
- **Наименьшие привилегии.** Ansible ходит под непривилегированным `major` с
|
||||||
`become` (`production.yml`/`timeweb.yml`), root-логин для деплоя не нужен вовсе.
|
`become` (`production.yml`), root-логин для деплоя не нужен вовсе.
|
||||||
- **Defense-in-depth.** Если `PasswordAuthentication` когда-нибудь вернётся в
|
- **Defense-in-depth.** Если `PasswordAuthentication` когда-нибудь вернётся в
|
||||||
`yes` (правка, `Match`-блок, чужая роль), `PermitRootLogin yes` тут же откроет
|
`yes` (правка, `Match`-блок, чужая роль), `PermitRootLogin yes` тут же откроет
|
||||||
root по паролю. `prohibit-password` держит root key-only независимо.
|
root по паролю. `prohibit-password` держит root key-only независимо.
|
||||||
@@ -22,5 +22,5 @@
|
|||||||
|
|
||||||
Из sec-ревью (fable, 2026-07-13); мотивировка уточнена после проверки конфига.
|
Из sec-ревью (fable, 2026-07-13); мотивировка уточнена после проверки конфига.
|
||||||
|
|
||||||
Связано: playbook-system.yml, production.yml, timeweb.yml,
|
Связано: playbook-system.yml, production.yml,
|
||||||
galaxy.roles/geerlingguy.security.
|
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:
|
services:
|
||||||
|
|
||||||
homepage_app:
|
homepage_app:
|
||||||
# noinspection ComposeUnknownValues
|
# noinspection ComposeUnknownValues
|
||||||
image: "{{ registry_homepage_nginx_image }}"
|
image: "{{ homepage_image_name }}:{{ app_image_tag }}"
|
||||||
container_name: homepage_app
|
container_name: homepage_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
pull_policy: never
|
||||||
networks:
|
networks:
|
||||||
- "web_proxy_network"
|
- "web_proxy_network"
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ services:
|
|||||||
- "/var/run:/host/var/run:ro"
|
- "/var/run:/host/var/run:ro"
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||||
environment:
|
environment:
|
||||||
PGID: "{{ netdata_docker_group_output.stdout | default(999) }}"
|
PGID: "{{ netdata_docker_gid }}"
|
||||||
NETDATA_EXTRA_DEB_PACKAGES: "fail2ban"
|
NETDATA_EXTRA_DEB_PACKAGES: "fail2ban"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ tier7 = 8
|
|||||||
[telegram]
|
[telegram]
|
||||||
|
|
||||||
# Включить Telegram-бот для ежедневной отправки воспоминаний.
|
# Включить Telegram-бот для ежедневной отправки воспоминаний.
|
||||||
|
# Если api.telegram.org недоступен, приложение всё равно стартует: веб-часть
|
||||||
|
# работает, а бот повторяет авторизацию в фоне, пока не появится связь.
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
# Токен бота, полученный от @BotFather.
|
# Токен бота, полученный от @BotFather.
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
# Образ собирается локально и доставляется на сервер ролью app_image
|
||||||
|
# (см. playbook-remembos.yml): тег — случайный BUILD_ID каждого прогона,
|
||||||
|
# в реестре образа нет, поэтому pull_policy: never.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
remembos_app:
|
remembos_app:
|
||||||
image: "{{ yc_container_registry_repository }}/remembos:v0.2.0"
|
image: "{{ app_name }}:{{ app_image_tag }}"
|
||||||
container_name: remembos_app
|
container_name: remembos_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
pull_policy: never
|
||||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||||
environment:
|
environment:
|
||||||
- PUID={{ owner_create_result.uid }}
|
- PUID={{ owner_create_result.uid }}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
|
# Образ собирается локально и доставляется на сервер ролью app_image
|
||||||
|
# (см. playbook-transcriber.yml): тег — случайный BUILD_ID каждого прогона,
|
||||||
|
# в реестре образа нет, поэтому pull_policy: never.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
transcriber_app:
|
transcriber_app:
|
||||||
# noinspection ComposeUnknownValues
|
# noinspection ComposeUnknownValues
|
||||||
image: "{{ registry_transcriber_image }}"
|
image: "{{ app_name }}:{{ app_image_tag }}"
|
||||||
container_name: transcriber_app
|
container_name: transcriber_app
|
||||||
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
pull_policy: never
|
||||||
volumes:
|
volumes:
|
||||||
- "{{ config_file }}:/config/config.toml:ro"
|
- "{{ config_file }}:/config/config.toml:ro"
|
||||||
- "{{ data_dir }}:/data"
|
- "{{ 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"
|
- name: "Configure apprise application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "apprise"
|
app_name: "apprise"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -42,6 +38,8 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
|
|||||||
+15
-3
@@ -3,8 +3,6 @@
|
|||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
- files/authelia/secrets.yml
|
- files/authelia/secrets.yml
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
@@ -48,6 +46,10 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
# Vault-файл: ansible расшифровывает его при копировании, и без no_log
|
||||||
|
# --diff покажет содержимое открытым текстом.
|
||||||
|
no_log: true
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy configuration file"
|
- name: "Copy configuration file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -56,6 +58,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||||
|
no_log: true
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy gobackup config"
|
- name: "Copy gobackup config"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -89,9 +94,16 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
- name: "Restart application with docker compose"
|
handlers:
|
||||||
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
|
# Только сам authelia: рестарт redis сбросил бы сессии всех сервисов.
|
||||||
|
services:
|
||||||
|
- "authelia_app"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure restic and backup schedule"
|
- name: "Configure restic and backup schedule"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
backup_config_dir: "/etc/backup"
|
backup_config_dir: "/etc/backup"
|
||||||
backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}"
|
backup_config_file: "{{ (backup_config_dir, 'config.toml') | path_join }}"
|
||||||
@@ -40,6 +36,8 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Create backup config file"
|
- name: "Create backup config file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -48,6 +46,8 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Allow user to run the backup script without a password"
|
- name: "Allow user to run the backup script without a password"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
@@ -64,6 +64,8 @@
|
|||||||
owner: root
|
owner: root
|
||||||
group: root
|
group: root
|
||||||
mode: "0700"
|
mode: "0700"
|
||||||
|
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Copy backup all script"
|
- name: "Copy backup all script"
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
|
|||||||
+8
-13
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure bifrost application"
|
- name: "Configure bifrost application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "bifrost"
|
app_name: "bifrost"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -46,7 +42,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
register: config_file_result
|
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||||
|
no_log: true
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -55,7 +53,6 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
register: docker_compose_file_result
|
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
@@ -65,15 +62,13 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
# Bifrost читает config.json только при старте. Если изменился лишь конфиг
|
handlers:
|
||||||
# (а compose — нет), `state: present` не пересоздаёт контейнер, и правки
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
# (ротация ключа, смена модели/правил) не подхватятся — нужен явный рестарт.
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
- name: "Restart docker compose services if config changed but not docker-compose.yml"
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
when:
|
|
||||||
- config_file_result.changed
|
|
||||||
- not docker_compose_file_result.changed
|
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure caddy reverse proxy service"
|
- name: "Configure caddy reverse proxy service"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "caddyproxy"
|
app_name: "caddyproxy"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -78,6 +74,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -103,7 +100,11 @@
|
|||||||
# tags:
|
# tags:
|
||||||
# - run-app
|
# - run-app
|
||||||
|
|
||||||
- name: "Restart application with docker compose"
|
handlers:
|
||||||
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
|
|||||||
+6
-12
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure calibre application"
|
- name: "Configure calibre application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "calibre"
|
app_name: "calibre"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -38,16 +34,14 @@
|
|||||||
- "{{ config_dir }}"
|
- "{{ config_dir }}"
|
||||||
|
|
||||||
- name: "Create backup targets file"
|
- name: "Create backup targets file"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.copy:
|
||||||
path: "{{ base_dir }}/backup-targets"
|
dest: "{{ base_dir }}/backup-targets"
|
||||||
line: "{{ item }}"
|
content: |
|
||||||
create: true
|
{{ books_dir }}
|
||||||
|
{{ config_dir }}
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0750"
|
mode: "0640"
|
||||||
loop:
|
|
||||||
- "{{ books_dir }}"
|
|
||||||
- "{{ config_dir }}"
|
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure dashboard start page"
|
- name: "Configure dashboard start page"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "dashboard"
|
app_name: "dashboard"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure docker parameters"
|
- name: "Configure docker parameters"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
# - name: "Install python docker lib from pip"
|
# - name: "Install python docker lib from pip"
|
||||||
# ansible.builtin.pip:
|
# ansible.builtin.pip:
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure dozzle application"
|
- name: "Configure dozzle application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "dozzle"
|
app_name: "dozzle"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Install eget and pinned binaries"
|
- name: "Install eget and pinned binaries"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
# Версии инструментов: https://github.com/<repo>/releases
|
# Версии инструментов: https://github.com/<repo>/releases
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|||||||
+3
-4
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure gitea application"
|
- name: "Configure gitea application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "gitea"
|
app_name: "gitea"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -52,6 +48,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||||
|
# печатает --diff. Снять, когда они переедут в файлы.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure goaccess application"
|
- name: "Configure goaccess application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "goaccess"
|
app_name: "goaccess"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
|
|||||||
+10
-13
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure gramps application"
|
- name: "Configure gramps application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "gramps"
|
app_name: "gramps"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -59,17 +55,15 @@
|
|||||||
mode: "0750"
|
mode: "0750"
|
||||||
|
|
||||||
- name: "Create backup targets file"
|
- name: "Create backup targets file"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.copy:
|
||||||
path: "{{ base_dir }}/backup-targets"
|
dest: "{{ base_dir }}/backup-targets"
|
||||||
line: "{{ item }}"
|
content: |
|
||||||
create: true
|
{{ data_dir }}
|
||||||
|
{{ media_dir }}
|
||||||
|
{{ backups_dir }}
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0750"
|
mode: "0640"
|
||||||
loop:
|
|
||||||
- "{{ data_dir }}"
|
|
||||||
- "{{ media_dir }}"
|
|
||||||
- "{{ backups_dir }}"
|
|
||||||
|
|
||||||
- name: "Copy rename script"
|
- name: "Copy rename script"
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
@@ -86,6 +80,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||||
|
# печатает --diff. Снять, когда они переедут в файлы.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
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
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
- vars/homepage.yml
|
- vars/homepage.yml
|
||||||
|
|
||||||
tasks:
|
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"
|
- name: "Create user and environment"
|
||||||
ansible.builtin.import_role:
|
ansible.builtin.import_role:
|
||||||
name: owner
|
name: owner
|
||||||
@@ -27,12 +35,6 @@
|
|||||||
loop:
|
loop:
|
||||||
- "{{ base_dir }}"
|
- "{{ 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"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
src: "./files/{{ app_name }}/docker-compose.template.yml"
|
||||||
@@ -46,6 +48,5 @@
|
|||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "present"
|
state: "present"
|
||||||
remove_orphans: true
|
remove_orphans: true
|
||||||
pull: "always"
|
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
+6
-13
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure memos application"
|
- name: "Configure memos application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "memos"
|
app_name: "memos"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -55,16 +51,14 @@
|
|||||||
mode: "0750"
|
mode: "0750"
|
||||||
|
|
||||||
- name: "Create backup targets file"
|
- name: "Create backup targets file"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.copy:
|
||||||
path: "{{ base_dir }}/backup-targets"
|
dest: "{{ base_dir }}/backup-targets"
|
||||||
line: "{{ item }}"
|
content: |
|
||||||
create: true
|
{{ data_dir }}
|
||||||
|
{{ backups_dir }}
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0750"
|
mode: "0640"
|
||||||
loop:
|
|
||||||
- "{{ data_dir }}"
|
|
||||||
- "{{ backups_dir }}"
|
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -73,7 +67,6 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
register: docker_compose_file_result
|
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure miniflux application"
|
- name: "Configure miniflux application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "miniflux"
|
app_name: "miniflux"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
|
|||||||
+21
-13
@@ -2,10 +2,6 @@
|
|||||||
- name: "Install Netdata monitoring service"
|
- name: "Install Netdata monitoring service"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "netdata"
|
app_name: "netdata"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -48,6 +44,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Find all go.d plugin config files"
|
- name: "Find all go.d plugin config files"
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -64,6 +61,7 @@
|
|||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
loop: "{{ go_d_source_files.files }}"
|
loop: "{{ go_d_source_files.files }}"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Find existing go.d config files on server"
|
- name: "Find existing go.d config files on server"
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -77,6 +75,7 @@
|
|||||||
state: absent
|
state: absent
|
||||||
loop: "{{ go_d_existing_files.files }}"
|
loop: "{{ go_d_existing_files.files }}"
|
||||||
when: (item.path | basename) not in (go_d_source_files.files | map(attribute='path') | map('basename') | list)
|
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"
|
- name: "Find all health.d config files"
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -93,6 +92,7 @@
|
|||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
loop: "{{ health_d_source_files.files }}"
|
loop: "{{ health_d_source_files.files }}"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Find existing health.d config files on server"
|
- name: "Find existing health.d config files on server"
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
@@ -106,6 +106,7 @@
|
|||||||
state: absent
|
state: absent
|
||||||
loop: "{{ health_d_existing_files.files }}"
|
loop: "{{ health_d_existing_files.files }}"
|
||||||
when: (item.path | basename) not in (health_d_source_files.files | map(attribute='path') | map('basename') | list)
|
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"
|
- name: "Copy health alarm notify config"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -114,15 +115,18 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Grab docker group id."
|
- name: "Grab docker group info"
|
||||||
ansible.builtin.shell:
|
ansible.builtin.getent:
|
||||||
cmd: |
|
database: "group"
|
||||||
set -o pipefail
|
key: "docker"
|
||||||
grep docker /etc/group | cut -d ':' -f 3
|
|
||||||
executable: /bin/bash
|
# getent_group отдаёт поля строки /etc/group без имени группы,
|
||||||
register: netdata_docker_group_output
|
# то есть [пароль, gid, участники] — gid лежит под индексом 1.
|
||||||
changed_when: netdata_docker_group_output.rc != 0
|
- name: "Store docker group id"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
netdata_docker_gid: "{{ ansible_facts.getent_group['docker'][1] }}"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -140,7 +144,11 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
- name: "Restart application with docker compose"
|
handlers:
|
||||||
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
|
|||||||
+9
-12
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure outline application"
|
- name: "Configure outline application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "outline"
|
app_name: "outline"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -55,16 +51,14 @@
|
|||||||
mode: "0750"
|
mode: "0750"
|
||||||
|
|
||||||
- name: "Create backup targets file"
|
- name: "Create backup targets file"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.copy:
|
||||||
path: "{{ base_dir }}/backup-targets"
|
dest: "{{ base_dir }}/backup-targets"
|
||||||
line: "{{ item }}"
|
content: |
|
||||||
create: true
|
{{ media_dir }}
|
||||||
|
{{ backups_dir }}
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0750"
|
mode: "0640"
|
||||||
loop:
|
|
||||||
- "{{ media_dir }}"
|
|
||||||
- "{{ backups_dir }}"
|
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -73,6 +67,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||||
|
# печатает --diff. Снять, когда они переедут в файлы.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|||||||
+18
-16
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure remembos application"
|
- name: "Configure remembos application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "remembos"
|
app_name: "remembos"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -17,6 +13,16 @@
|
|||||||
config_file: "{{ (config_dir, 'config.toml') | path_join }}"
|
config_file: "{{ (config_dir, 'config.toml') | path_join }}"
|
||||||
|
|
||||||
tasks:
|
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"
|
- name: "Create user and environment"
|
||||||
ansible.builtin.import_role:
|
ansible.builtin.import_role:
|
||||||
name: owner
|
name: owner
|
||||||
@@ -45,7 +51,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
register: config_file_result
|
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||||
|
no_log: true
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -54,13 +62,6 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
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"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
@@ -70,12 +71,13 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- 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:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
state: "restarted"
|
||||||
when:
|
|
||||||
- config_file_result.changed
|
|
||||||
- not docker_compose_file_result.changed
|
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
- name: "Update and upgrade system packages"
|
- name: "Remove user and application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
user_name: "<put-name-here>"
|
user_name: "<put-name-here>"
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure rssbridge application"
|
- name: "Configure rssbridge application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "rssbridge"
|
app_name: "rssbridge"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure base system parameters"
|
- name: "Configure base system parameters"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
apt_packages:
|
apt_packages:
|
||||||
- acl
|
- 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
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
vars_files:
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
- vars/transcriber.yml
|
- vars/transcriber.yml
|
||||||
- vars/transcriber.images.yml
|
|
||||||
|
|
||||||
tasks:
|
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"
|
- name: "Create user and environment"
|
||||||
ansible.builtin.import_role:
|
ansible.builtin.import_role:
|
||||||
name: owner
|
name: owner
|
||||||
@@ -38,12 +45,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
# Vault-файл: ansible расшифровывает его при копировании, и без no_log
|
||||||
- name: "Login to Yandex Container Registry"
|
# --diff покажет содержимое открытым текстом.
|
||||||
community.docker.docker_login:
|
no_log: true
|
||||||
registry_url: "{{ yc_container_registry }}"
|
|
||||||
username: "oauth"
|
|
||||||
password: "{{ yc_oauth_token }}"
|
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
|
|||||||
+9
-12
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure tududi application"
|
- name: "Configure tududi application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "tududi"
|
app_name: "tududi"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -43,16 +39,14 @@
|
|||||||
# ничтожна, а retention (90 дневных / 36 месячных) страхует от редкого
|
# ничтожна, а retention (90 дневных / 36 месячных) страхует от редкого
|
||||||
# несогласованного снимка.
|
# несогласованного снимка.
|
||||||
- name: "Create backup targets file"
|
- name: "Create backup targets file"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.copy:
|
||||||
path: "{{ base_dir }}/backup-targets"
|
dest: "{{ base_dir }}/backup-targets"
|
||||||
line: "{{ item }}"
|
content: |
|
||||||
create: true
|
{{ db_dir }}
|
||||||
|
{{ uploads_dir }}
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0750"
|
mode: "0640"
|
||||||
loop:
|
|
||||||
- "{{ db_dir }}"
|
|
||||||
- "{{ uploads_dir }}"
|
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -61,6 +55,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||||
|
# печатает --diff. Файловых секретов tududi не умеет, снять не выйдет.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|||||||
+5
-11
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure tuwunel matrix server"
|
- name: "Configure tuwunel matrix server"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "tuwunel"
|
app_name: "tuwunel"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -47,15 +43,13 @@
|
|||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: "Create backup targets file"
|
- name: "Create backup targets file"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.copy:
|
||||||
path: "{{ base_dir }}/backup-targets"
|
dest: "{{ base_dir }}/backup-targets"
|
||||||
line: "{{ item }}"
|
content: |
|
||||||
create: true
|
{{ data_dir }}
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0750"
|
mode: "0640"
|
||||||
loop:
|
|
||||||
- "{{ data_dir }}"
|
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure UFW firewall"
|
- name: "Configure UFW firewall"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: "Ensure UFW is installed"
|
- name: "Ensure UFW is installed"
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
- name: "Update and upgrade system packages"
|
- name: "Update and upgrade system packages"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Perform an upgrade of packages
|
- name: Perform an upgrade of packages
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
|
|||||||
+3
-4
@@ -2,10 +2,6 @@
|
|||||||
- name: "Configure wakapi application"
|
- name: "Configure wakapi application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "wakapi"
|
app_name: "wakapi"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -61,6 +57,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||||
|
# печатает --diff. Снять, когда они переедут в файлы.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|||||||
+9
-12
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
- name: "Configure gramps application"
|
- name: "Configure wanderer application"
|
||||||
hosts: all
|
hosts: all
|
||||||
|
|
||||||
vars_files:
|
|
||||||
- vars/secrets.yml
|
|
||||||
- vars/vars.yml
|
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
app_name: "wanderer"
|
app_name: "wanderer"
|
||||||
app_user: "{{ app_name }}"
|
app_user: "{{ app_name }}"
|
||||||
@@ -66,15 +62,13 @@
|
|||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
- name: "Create backup targets file"
|
- name: "Create backup targets file"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.copy:
|
||||||
path: "{{ base_dir }}/backup-targets"
|
dest: "{{ base_dir }}/backup-targets"
|
||||||
line: "{{ item }}"
|
content: |
|
||||||
create: true
|
{{ data_dir }}
|
||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0750"
|
mode: "0640"
|
||||||
loop:
|
|
||||||
- "{{ data_dir }}"
|
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -83,6 +77,9 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
# Секреты стоят прямыми значениями в environment: — без no_log их
|
||||||
|
# печатает --diff. Файловых секретов wanderer не умеет, снять не выйдет.
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
|
|||||||
+1
-3
@@ -2,8 +2,6 @@
|
|||||||
ungrouped:
|
ungrouped:
|
||||||
hosts:
|
hosts:
|
||||||
server:
|
server:
|
||||||
ansible_host: "158.160.46.255"
|
ansible_host: "92.53.105.41"
|
||||||
ansible_user: "major"
|
ansible_user: "major"
|
||||||
ansible_become: true
|
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.exceptions import Exit
|
||||||
from invoke.tasks import task
|
from invoke.tasks import task
|
||||||
|
|
||||||
HOSTS_FILE = "timeweb.yml"
|
HOSTS_FILE = "production.yml"
|
||||||
VARS_FILE = "vars/vars.yml"
|
VARS_FILE = "group_vars/all/main.yml"
|
||||||
AUTHELIA_DOCKER = "docker run --rm -v $PWD:/data authelia/authelia:4.39.4 authelia"
|
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"])
|
@task(aliases=["ee"])
|
||||||
def edit_encrypted(ctx: Context, path: str) -> None:
|
def edit_encrypted(ctx: Context, path: str) -> None:
|
||||||
"""Редактировать vault-файл через ansible-vault edit (без расшифровки на диск)"""
|
"""Редактировать 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 ---
|
# --- Синхронизация общих ролей с каноном ansible-roles ---
|
||||||
@@ -125,7 +134,7 @@ SHARED_ROLES_DIR = os.path.normpath(
|
|||||||
os.path.dirname(os.path.abspath(__file__)), "..", "ansible-roles", "roles"
|
os.path.dirname(os.path.abspath(__file__)), "..", "ansible-roles", "roles"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
SYNCED_ROLES = ["eget"]
|
SYNCED_ROLES = ["eget", "app_image"]
|
||||||
|
|
||||||
|
|
||||||
def _require_canon() -> None:
|
def _require_canon() -> None:
|
||||||
@@ -205,7 +214,7 @@ def roles_push(ctx: Context) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _application_dir() -> str:
|
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 // ""')
|
inv_value = _yq('.ungrouped.hosts.server.application_dir // ""')
|
||||||
if inv_value:
|
if inv_value:
|
||||||
return inv_value
|
return inv_value
|
||||||
@@ -250,7 +259,7 @@ def authelia_validate_config(ctx: Context) -> None:
|
|||||||
"uv run ansible localhost"
|
"uv run ansible localhost"
|
||||||
" --module-name template"
|
" --module-name template"
|
||||||
f' --args "src=files/authelia/configuration.template.yml dest={dest}"'
|
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"
|
" --extra-vars @files/authelia/secrets.yml"
|
||||||
)
|
)
|
||||||
ctx.run(f"{AUTHELIA_DOCKER} validate-config --config /data/{dest}")
|
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_uid: 1008
|
||||||
app_owner_gid: 1009
|
app_owner_gid: 1009
|
||||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||||
docker_registry_prefix: "cr.yandex/crplfk0168i4o8kd7ade"
|
|
||||||
|
|
||||||
homepage_nginx_image: "homepage-nginx:latest"
|
# Имя локально собираемого образа: им приложение тегает сборку (`task image`),
|
||||||
|
# под ним же роль app_image везёт образ на сервер и его находит docker-compose.
|
||||||
# Registry images
|
homepage_image_name: "homepage-nginx"
|
||||||
registry_homepage_nginx_image: "{{ (docker_registry_prefix, homepage_nginx_image) | path_join }}"
|
|
||||||
|
|||||||
@@ -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 }}"
|
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||||
backups_dir: "{{ (base_dir, 'backups') | 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