Compare commits
5
Commits
7e64d76d02
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5217607b1
|
||
|
|
856bf72560
|
||
|
|
70372ee028
|
||
|
|
16f0712a31
|
||
|
|
3bb85b85f6
|
@@ -17,13 +17,13 @@ _(sec-ревью)_ — три независимых security-ревью на м
|
|||||||
|
|
||||||
## Высокий
|
## Высокий
|
||||||
|
|
||||||
- [Убрать группу `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
|
||||||
- [Никаких секретов в выводе плейбуков](no-secrets-in-playbook-output.md) — `inv pl` всегда с `--diff`, и каждая задача, рендерящая файл с секретом, печатает его в терминал; `no_log` стоит ровно в одном месте из четырнадцати
|
- [Никаких секретов в выводе плейбуков](no-secrets-in-playbook-output.md) — `inv pl` всегда с `--diff`, и каждая задача, рендерящая файл с секретом, печатает его в терминал; `no_log` стоит ровно в одном месте из четырнадцати
|
||||||
|
|
||||||
## Средний
|
## Средний
|
||||||
|
|
||||||
|
- [Дампы бэкапов без группы `docker`](backup-dump-without-docker-group.md) — у miniflux/gitea/outline группа осталась: их backup.sh дампит через `docker compose exec` из-под самого приложения
|
||||||
- [Захардить мониторинг-стек (netdata / dozzle / goaccess)](harden-monitoring-stack.md) — netdata/dozzle: лишние привилегии + docker.sock + доверие Remote-User в общей сети; socket-proxy + monitoring_network _(sec-ревью)_
|
- [Захардить мониторинг-стек (netdata / dozzle / goaccess)](harden-monitoring-stack.md) — netdata/dozzle: лишние привилегии + docker.sock + доверие Remote-User в общей сети; socket-proxy + monitoring_network _(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-ревью)_
|
||||||
- [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_
|
- [Включить автообновления безопасности](security-autoupdates.md) — патчи openssh/libssl только вручную; окна в недели между CVE и фиксом _(sec-ревью)_
|
||||||
@@ -31,7 +31,6 @@ _(sec-ревью)_ — три независимых security-ревью на м
|
|||||||
- [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) _(ансибл-ревью)_
|
||||||
- [Вывести из оборота секреты с широкими правами](narrow-secret-scopes.md) — `yc_oauth_token` (весь аккаунт Yandex Cloud) уже выведен; осталось проставить радиус остальным переменным vault и сузить широкие
|
- [Вывести из оборота секреты с широкими правами](narrow-secret-scopes.md) — `yc_oauth_token` (весь аккаунт Yandex Cloud) уже выведен; осталось проставить радиус остальным переменным vault и сузить широкие
|
||||||
- [outline: секреты из файлов](secrets-file-outline.md) — умеет generic `<NAME>_FILE` для всех пяти секретов; единственное приложение, где выносится вообще всё, включая пароль БД
|
- [outline: секреты из файлов](secrets-file-outline.md) — умеет generic `<NAME>_FILE` для всех пяти секретов; единственное приложение, где выносится вообще всё, включая пароль БД
|
||||||
@@ -41,10 +40,8 @@ _(sec-ревью)_ — три независимых security-ревью на м
|
|||||||
|
|
||||||
## Низкий
|
## Низкий
|
||||||
|
|
||||||
- [Мелкий хардненинг 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-ревью)_
|
||||||
- [Причесать роль `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](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)_
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
# Причесать роль `owner` под конвенции
|
|
||||||
|
|
||||||
**Приоритет:** низкий
|
|
||||||
|
|
||||||
Роль `owner` разошлась по стилю с `eget`/`secrets`:
|
|
||||||
|
|
||||||
- **`roles/owner/tasks/main.yml:2-10`** — валидация аргументов через `fail` +
|
|
||||||
`when`, причём две задачи с **идентичным именем**. `eget` для того же делает
|
|
||||||
`assert` (`roles/eget/tasks/main.yml:15`). Привести к одному стилю — `assert`
|
|
||||||
либо декларативный `meta/argument_specs.yml`.
|
|
||||||
- **`roles/owner/tasks/main.yml:32,53`** — устаревшие `with_items`/`with_dict`;
|
|
||||||
конвенция — `loop` (`loop: "{{ owner_ssh_keys }}"`,
|
|
||||||
`loop: "{{ owner_env_dict | dict2items }}"`).
|
|
||||||
- У `owner` нет `meta/main.yml` и README, тогда как у `eget` и `secrets` есть.
|
|
||||||
- Имена задач с точкой на конце (`"Prepare env variables."`) — ansible-lint в
|
|
||||||
строгом профиле это ловит.
|
|
||||||
|
|
||||||
Контекст: [docs/drafts/ansible-review.md](../drafts/ansible-review.md) §5.
|
|
||||||
|
|
||||||
Связано: roles/owner, roles/eget (образец), roles/secrets.
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Дампы бэкапов без группы `docker` у сервисных пользователей
|
||||||
|
|
||||||
|
**Приоритет:** средний
|
||||||
|
|
||||||
|
Группу `docker` (= root на хосте) убрали у всех сервисных пользователей, кроме
|
||||||
|
трёх: `miniflux`, `gitea`, `outline`. Их `backup.sh` делает `docker compose exec`
|
||||||
|
(pg_dump, `gitea dump`), а `files/backups/backup-all.py` запускает такие скрипты
|
||||||
|
через `su --login <app_user>`, то есть от имени самого приложения — без членства
|
||||||
|
в группе дамп упадёт.
|
||||||
|
|
||||||
|
Дампить снаружи докера нельзя: postgres порт не публикует, `gitea dump` живёт
|
||||||
|
только внутри контейнера.
|
||||||
|
|
||||||
|
Варианты, как развязать:
|
||||||
|
|
||||||
|
- **A. sudo-обёртка на дамп.** Плейбук кладёт `/usr/local/sbin/backup-<app>-dump.sh`
|
||||||
|
(root:root, 0755 — сам app_user его не перепишет) с единственным
|
||||||
|
`docker compose exec …`, в sudoers — `<app_user> ALL=(root) NOPASSWD:` ровно на
|
||||||
|
этот файл, а `backup.sh` дёргает его через `sudo`. Пользователь получает право
|
||||||
|
на одну фиксированную команду вместо права на весь докер. Механизм в проекте
|
||||||
|
уже обкатан: так `playbook-backups.yml` разрешает `primary_user` запускать
|
||||||
|
`backup-all.py`.
|
||||||
|
- **B. root-скрипты в оркестраторе.** Новый контракт в `backup-all.py`: если у
|
||||||
|
приложения есть `backup-root.sh` с владельцем root — запускать его без `su`.
|
||||||
|
Механизм один на всех, но от root пойдёт скрипт целиком, включая
|
||||||
|
`keep-files.py`, и дампы станут root-овыми файлами в директориях приложения.
|
||||||
|
- **C. оставить как есть** — три аккаунта как осознанное исключение,
|
||||||
|
задокументированное в плейбуках.
|
||||||
|
|
||||||
|
Предпочтение на момент записи — A.
|
||||||
|
|
||||||
|
Связано: playbook-miniflux.yml, playbook-gitea.yml, playbook-outline.yml,
|
||||||
|
files/*/backup.template.sh, files/backups/backup-all.py.
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Мелкий хардненинг Caddy
|
|
||||||
|
|
||||||
**Приоритет:** низкий
|
|
||||||
|
|
||||||
- **HSTS**: заголовок `Strict-Transport-Security` не выставляется нигде в
|
|
||||||
`files/caddyproxy/Caddyfile.template`. Одна строка `header
|
|
||||||
Strict-Transport-Security "max-age=31536000"` в общем сниппете (рядом с
|
|
||||||
`access_log`) закрывает downgrade/SSL-strip для всех поддоменов разом.
|
|
||||||
- **Лишний `NET_ADMIN`** (`files/caddyproxy/docker-compose.template.yml`) —
|
|
||||||
reverse-proxy эта capability не нужна (требуется лишь для тюнинга UDP-буферов
|
|
||||||
HTTP/3, работает и без неё с warning). Убрать у edge-контейнера, торчащего в
|
|
||||||
интернет.
|
|
||||||
- **Leftover-листенер `:29999`** в `Caddyfile.template` (site `status.vakhrushev.me,
|
|
||||||
:29999`) — наследие прямого доступа к netdata; убрать, оставив только доменный
|
|
||||||
site-блок.
|
|
||||||
|
|
||||||
Из sec-ревью (fable, 2026-07-13).
|
|
||||||
|
|
||||||
Связано: files/caddyproxy/Caddyfile.template, files/caddyproxy/docker-compose.template.yml.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Handlers рестарта для оставшихся приложений
|
|
||||||
|
|
||||||
**Приоритет:** средний
|
|
||||||
|
|
||||||
Паттерн `handlers` + `notify` внедрён в caddyproxy, netdata, authelia, bifrost,
|
|
||||||
remembos (см. задачу `ansible-handlers-restart`, закрыта). Остались плейбуки, где
|
|
||||||
конфиг bind-моунтится в контейнер, но рестарта нет **вообще** — правка конфига без
|
|
||||||
изменения docker-compose.yml не подхватывается до следующего пересоздания
|
|
||||||
контейнера:
|
|
||||||
|
|
||||||
- `playbook-apprise.yml` — `backups.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.
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Убрать группу `docker` у сервисных пользователей
|
|
||||||
|
|
||||||
**Приоритет:** высокий
|
|
||||||
|
|
||||||
Все приложения заводят системного пользователя с `owner_extra_groups: ["docker"]`
|
|
||||||
(во всех `playbook-*.yml`), но docker.sock реально монтируют только netdata и
|
|
||||||
dozzle. Членство в группе `docker` эквивалентно root на хосте (`docker run -v
|
|
||||||
/:/host`), поэтому выделенные «непривилегированные» аккаунты (authelia, outline,
|
|
||||||
gitea и т.д.) фактически все имеют root — изоляция «пользователь на сервис», ради
|
|
||||||
которой их и заводили, обнуляется. Контейнерами рулит Ansible от root (become),
|
|
||||||
сервисам группа не нужна. Убрать `docker` из `owner_extra_groups` у всех, кроме
|
|
||||||
тех, кому он действительно нужен для самостоятельного управления своим compose (и
|
|
||||||
тем — лучше через socket-proxy). За `primary_user` группу оставить для ручных
|
|
||||||
операций (`inv ssh`).
|
|
||||||
|
|
||||||
Из sec-ревью (fable, 2026-07-13). Усиливает риск из серии `secrets-file-*`
|
|
||||||
(секреты в `environment:` контейнеров): членство в группе `docker` — как раз тот
|
|
||||||
доступ, которым такие переменные и вычитываются.
|
|
||||||
|
|
||||||
Связано: все playbook-*.yml (owner_extra_groups), roles/owner, playbook-docker.yml.
|
|
||||||
@@ -31,10 +31,12 @@ http://:2020 {
|
|||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
# Общие настройки всех сайтов: access-лог и HSTS.
|
# Общие настройки всех сайтов: access-лог и HSTS.
|
||||||
# Mode 644 lets read-only consumers (goaccess and ad-hoc host-side tail)
|
#
|
||||||
# read the file; lumberjack would otherwise default to 0600.
|
# Лог общий для всех сайтов, его разбирает GoAccess. Mode 644 нужен, чтобы
|
||||||
(access_log) {
|
# файл читали потребители на хосте (goaccess и ad-hoc tail): lumberjack иначе
|
||||||
log {
|
# ставит 0600.
|
||||||
|
(common) {
|
||||||
|
log {
|
||||||
output file /var/log/caddy/access.log {
|
output file /var/log/caddy/access.log {
|
||||||
mode 644
|
mode 644
|
||||||
roll_size 100mib
|
roll_size 100mib
|
||||||
@@ -44,6 +46,12 @@ http://:2020 {
|
|||||||
format json
|
format json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# HSTS: браузер, однажды увидевший заголовок, целый год ходит на это имя только
|
||||||
|
# по https, минуя редирект с http, — закрывает downgrade и SSL-strip.
|
||||||
|
# Без includeSubDomains: политика по каждому имени своя, а под vakhrushev.me
|
||||||
|
# есть поддомены не с этого сервера (например photo.vakhrushev.me).
|
||||||
|
header Strict-Transport-Security "max-age=31536000"
|
||||||
|
}
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# Applications
|
# Applications
|
||||||
@@ -52,7 +60,7 @@ http://:2020 {
|
|||||||
vakhrushev.me {
|
vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
# Matrix federation delegation: tells other servers/clients that the
|
# Matrix federation delegation: tells other servers/clients that the
|
||||||
# homeserver for vakhrushev.me lives at matrix.vakhrushev.me.
|
# homeserver for vakhrushev.me lives at matrix.vakhrushev.me.
|
||||||
# https://spec.matrix.org/latest/server-server-api/#server-discovery
|
# https://spec.matrix.org/latest/server-server-api/#server-discovery
|
||||||
@@ -80,7 +88,7 @@ vakhrushev.me {
|
|||||||
start.vakhrushev.me {
|
start.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||||
@@ -94,7 +102,7 @@ start.vakhrushev.me {
|
|||||||
matrix.vakhrushev.me {
|
matrix.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to tuwunel_app:6167
|
to tuwunel_app:6167
|
||||||
}
|
}
|
||||||
@@ -103,14 +111,14 @@ matrix.vakhrushev.me {
|
|||||||
auth.vakhrushev.me {
|
auth.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy authelia_app:9091
|
reverse_proxy authelia_app:9091
|
||||||
}
|
}
|
||||||
|
|
||||||
status.vakhrushev.me {
|
status.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||||
@@ -122,7 +130,7 @@ status.vakhrushev.me, :29999 {
|
|||||||
git.vakhrushev.me {
|
git.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to gitea_app:3000
|
to gitea_app:3000
|
||||||
}
|
}
|
||||||
@@ -131,7 +139,7 @@ git.vakhrushev.me {
|
|||||||
outline.vakhrushev.me {
|
outline.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to outline_app:3000
|
to outline_app:3000
|
||||||
}
|
}
|
||||||
@@ -140,7 +148,7 @@ outline.vakhrushev.me {
|
|||||||
gramps.vakhrushev.me {
|
gramps.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to gramps_app:5000
|
to gramps_app:5000
|
||||||
}
|
}
|
||||||
@@ -149,7 +157,7 @@ gramps.vakhrushev.me {
|
|||||||
miniflux.vakhrushev.me {
|
miniflux.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
# /metrics наружу не отдаём. METRICS_ALLOWED_NETWORKS тут не помогает: за
|
# /metrics наружу не отдаём. METRICS_ALLOWED_NETWORKS тут не помогает: за
|
||||||
# прокси Miniflux видит приватный IP контейнера Caddy, а не адрес клиента.
|
# прокси Miniflux видит приватный IP контейнера Caddy, а не адрес клиента.
|
||||||
# Netdata скрейпит miniflux_app:8080/metrics напрямую по докер-сети, мимо Caddy.
|
# Netdata скрейпит miniflux_app:8080/metrics напрямую по докер-сети, мимо Caddy.
|
||||||
@@ -167,7 +175,7 @@ miniflux.vakhrushev.me {
|
|||||||
wakapi.vakhrushev.me {
|
wakapi.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to wakapi_app:3000
|
to wakapi_app:3000
|
||||||
}
|
}
|
||||||
@@ -176,7 +184,7 @@ wakapi.vakhrushev.me {
|
|||||||
wanderer.vakhrushev.me {
|
wanderer.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to wanderer_web:3000
|
to wanderer_web:3000
|
||||||
}
|
}
|
||||||
@@ -185,7 +193,7 @@ wanderer.vakhrushev.me {
|
|||||||
memos.vakhrushev.me {
|
memos.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to memos_app:5230
|
to memos_app:5230
|
||||||
}
|
}
|
||||||
@@ -196,7 +204,7 @@ memos.vakhrushev.me {
|
|||||||
tududi.vakhrushev.me {
|
tududi.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to tududi_app:3002
|
to tududi_app:3002
|
||||||
}
|
}
|
||||||
@@ -205,7 +213,7 @@ tududi.vakhrushev.me {
|
|||||||
remembos.vakhrushev.me {
|
remembos.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||||
@@ -219,7 +227,7 @@ remembos.vakhrushev.me {
|
|||||||
calibre.vakhrushev.me {
|
calibre.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to calibre_web_app:8083
|
to calibre_web_app:8083
|
||||||
}
|
}
|
||||||
@@ -228,7 +236,7 @@ calibre.vakhrushev.me {
|
|||||||
wanderbase.vakhrushev.me {
|
wanderbase.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||||
@@ -242,7 +250,7 @@ wanderbase.vakhrushev.me {
|
|||||||
rssbridge.vakhrushev.me {
|
rssbridge.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||||
@@ -256,7 +264,7 @@ rssbridge.vakhrushev.me {
|
|||||||
dozzle.vakhrushev.me {
|
dozzle.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name Remote-Filter
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name Remote-Filter
|
||||||
@@ -276,7 +284,7 @@ dozzle.vakhrushev.me {
|
|||||||
{{ goatcounter_sites | join(', ') }} {
|
{{ goatcounter_sites | join(', ') }} {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
reverse_proxy {
|
reverse_proxy {
|
||||||
to goatcounter_app:8080
|
to goatcounter_app:8080
|
||||||
}
|
}
|
||||||
@@ -285,7 +293,7 @@ dozzle.vakhrushev.me {
|
|||||||
goaccess.vakhrushev.me {
|
goaccess.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||||
@@ -305,7 +313,7 @@ goaccess.vakhrushev.me {
|
|||||||
bifrost.vakhrushev.me {
|
bifrost.vakhrushev.me {
|
||||||
tls anwinged@ya.ru
|
tls anwinged@ya.ru
|
||||||
import common
|
import common
|
||||||
|
|
||||||
forward_auth authelia_app:9091 {
|
forward_auth authelia_app:9091 {
|
||||||
uri /api/authz/forward-auth
|
uri /api/authz/forward-auth
|
||||||
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ services:
|
|||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
- "443:443/udp"
|
- "443:443/udp"
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
volumes:
|
volumes:
|
||||||
- "{{ caddy_file_dir }}:/etc/caddy"
|
- "{{ caddy_file_dir }}:/etc/caddy"
|
||||||
- "{{ data_dir }}:/data"
|
- "{{ data_dir }}:/data"
|
||||||
|
|||||||
+12
-1
@@ -27,7 +27,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
@@ -56,6 +55,7 @@
|
|||||||
mode: "0640"
|
mode: "0640"
|
||||||
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
# Файл содержит секреты из vault — без no_log их печатает --diff.
|
||||||
no_log: true
|
no_log: true
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -72,3 +72,14 @@
|
|||||||
remove_orphans: true
|
remove_orphans: true
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ base_dir }}"
|
||||||
|
state: "restarted"
|
||||||
|
tags:
|
||||||
|
- run-app
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create internal application directories"
|
- name: "Create internal application directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
+10
-16
@@ -23,7 +23,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create internal application directories"
|
- name: "Create internal application directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
@@ -74,7 +73,7 @@
|
|||||||
owner: "{{ app_user }}"
|
owner: "{{ app_user }}"
|
||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
notify: "Restart application"
|
notify: "Reload caddy configuration"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -92,21 +91,16 @@
|
|||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
# - name: "Reload caddy"
|
|
||||||
# community.docker.docker_compose_v2_exec:
|
|
||||||
# project_src: '{{ base_dir }}'
|
|
||||||
# service: "{{ service_name }}"
|
|
||||||
# command: caddy reload --config /etc/caddy/Caddyfile
|
|
||||||
# tags:
|
|
||||||
# - run-app
|
|
||||||
|
|
||||||
handlers:
|
handlers:
|
||||||
# Приложение читает конфиги только при старте, а `state: present` не
|
# `state: present` не пересоздаёт контейнер, если docker-compose.yml не
|
||||||
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
# изменился, — правку Caddyfile надо применять отдельно. Через прокси идёт
|
||||||
# правку конфига применяет только явный рестарт.
|
# весь трафик, поэтому не рестарт, а `caddy reload`: конфиг применяется без
|
||||||
- name: "Restart application"
|
# разрыва соединений. Конфиг указываем явно — без --config caddy берёт файл
|
||||||
community.docker.docker_compose_v2:
|
# из своего рабочего каталога и молча перезагружает не то.
|
||||||
|
- name: "Reload caddy configuration"
|
||||||
|
community.docker.docker_compose_v2_exec:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "restarted"
|
service: "{{ service_name }}"
|
||||||
|
command: "caddy reload --config /etc/caddy/Caddyfile"
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
+12
-1
@@ -30,7 +30,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
@@ -51,6 +50,7 @@
|
|||||||
group: "{{ app_user }}"
|
group: "{{ app_user }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
loop: "{{ config_files }}"
|
loop: "{{ config_files }}"
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -68,3 +68,14 @@
|
|||||||
pull: "always"
|
pull: "always"
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# Приложение читает конфиги только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку конфига применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ base_dir }}"
|
||||||
|
state: "restarted"
|
||||||
|
tags:
|
||||||
|
- run-app
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create internal application directories"
|
- name: "Create internal application directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -19,6 +19,10 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
|
# Группа docker = root на хосте, поэтому её не выдаём никому, кроме
|
||||||
|
# приложений, чей backup.sh делает docker compose exec: оркестратор
|
||||||
|
# запускает такие скрипты через su от имени этого пользователя.
|
||||||
|
# См. docs/backlog/backup-dump-without-docker-group.md.
|
||||||
owner_extra_groups: ["docker"]
|
owner_extra_groups: ["docker"]
|
||||||
|
|
||||||
- name: "Create internal application directories"
|
- name: "Create internal application directories"
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create internal application directories"
|
- name: "Create internal application directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
@@ -83,6 +82,9 @@
|
|||||||
- { name: "Dockerfile", mode: "0640" }
|
- { name: "Dockerfile", mode: "0640" }
|
||||||
- { name: "entrypoint.sh", mode: "0750" }
|
- { name: "entrypoint.sh", mode: "0750" }
|
||||||
|
|
||||||
|
# Handler рестарта здесь не нужен: конфиг в контейнер не монтируется, а
|
||||||
|
# правку Dockerfile/entrypoint.sh применяет `build: always` — пересобранный
|
||||||
|
# образ получает новый id, и compose пересоздаёт по нему контейнер (проверено).
|
||||||
- name: "Run application with docker compose"
|
- name: "Run application with docker compose"
|
||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
+23
-1
@@ -21,6 +21,10 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
|
# Группа docker = root на хосте, поэтому её не выдаём никому, кроме
|
||||||
|
# приложений, чей backup.sh делает docker compose exec: оркестратор
|
||||||
|
# запускает такие скрипты через su от имени этого пользователя.
|
||||||
|
# См. docs/backlog/backup-dump-without-docker-group.md.
|
||||||
owner_extra_groups: ["docker"]
|
owner_extra_groups: ["docker"]
|
||||||
|
|
||||||
- name: "Create internal directories"
|
- name: "Create internal directories"
|
||||||
@@ -37,9 +41,13 @@
|
|||||||
- "{{ postgres_data_dir }}"
|
- "{{ postgres_data_dir }}"
|
||||||
- "{{ postgres_backups_dir }}"
|
- "{{ postgres_backups_dir }}"
|
||||||
|
|
||||||
|
# notify на import_role наследуется всеми задачами роли: рестарт нужен,
|
||||||
|
# если изменился любой файл секретов — miniflux читает их через *_FILE
|
||||||
|
# только при старте.
|
||||||
- name: "Copy secrets"
|
- name: "Copy secrets"
|
||||||
ansible.builtin.import_role:
|
ansible.builtin.import_role:
|
||||||
name: secrets
|
name: secrets
|
||||||
|
notify: "Restart application"
|
||||||
vars:
|
vars:
|
||||||
secrets_dest: "{{ secrets_dir }}"
|
secrets_dest: "{{ secrets_dir }}"
|
||||||
secrets_user: "{{ app_user }}"
|
secrets_user: "{{ app_user }}"
|
||||||
@@ -72,7 +80,21 @@
|
|||||||
community.docker.docker_compose_v2:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ base_dir }}"
|
project_src: "{{ base_dir }}"
|
||||||
state: "present"
|
state: "present"
|
||||||
recreate: "always"
|
|
||||||
remove_orphans: true
|
remove_orphans: true
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# Приложение читает секреты только при старте, а `state: present` не
|
||||||
|
# пересоздаёт контейнер, если docker-compose.yml не изменился, — поэтому
|
||||||
|
# правку применяет только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ base_dir }}"
|
||||||
|
# Только сам miniflux: базу дёргать незачем, пароль postgres читается
|
||||||
|
# из тех же секретов лишь при инициализации кластера.
|
||||||
|
services:
|
||||||
|
- "miniflux_app"
|
||||||
|
state: "restarted"
|
||||||
|
tags:
|
||||||
|
- run-app
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create internal application directories"
|
- name: "Create internal application directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
|
# Группа docker = root на хосте, поэтому её не выдаём никому, кроме
|
||||||
|
# приложений, чей backup.sh делает docker compose exec: оркестратор
|
||||||
|
# запускает такие скрипты через su от имени этого пользователя.
|
||||||
|
# См. docs/backlog/backup-dump-without-docker-group.md.
|
||||||
owner_extra_groups: ["docker"]
|
owner_extra_groups: ["docker"]
|
||||||
|
|
||||||
- name: "Create internal directories"
|
- name: "Create internal directories"
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create internal application directories"
|
- name: "Create internal application directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
@@ -48,6 +47,7 @@
|
|||||||
# Vault-файл: ansible расшифровывает его при копировании, и без no_log
|
# Vault-файл: ansible расшифровывает его при копировании, и без no_log
|
||||||
# --diff покажет содержимое открытым текстом.
|
# --diff покажет содержимое открытым текстом.
|
||||||
no_log: true
|
no_log: true
|
||||||
|
notify: "Restart application"
|
||||||
|
|
||||||
- name: "Copy docker compose file"
|
- name: "Copy docker compose file"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -64,3 +64,15 @@
|
|||||||
remove_orphans: true
|
remove_orphans: true
|
||||||
tags:
|
tags:
|
||||||
- run-app
|
- run-app
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# Конфиг монтируется отдельным файлом, а `copy` подменяет его новым inode:
|
||||||
|
# запущенный контейнер продолжает видеть старый файл по старому inode.
|
||||||
|
# Bind-моунт переустанавливается при старте контейнера, поэтому применяет
|
||||||
|
# правку только явный рестарт.
|
||||||
|
- name: "Restart application"
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ base_dir }}"
|
||||||
|
state: "restarted"
|
||||||
|
tags:
|
||||||
|
- run-app
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
owner_name: "{{ app_user }}"
|
owner_name: "{{ app_user }}"
|
||||||
owner_uid: "{{ app_owner_uid }}"
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
owner_gid: "{{ app_owner_gid }}"
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
owner_extra_groups: ["docker"]
|
|
||||||
|
|
||||||
- name: "Create application internal directories"
|
- name: "Create application internal directories"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# owner
|
||||||
|
|
||||||
|
Заводит системного пользователя и группу под одно приложение: фиксированные
|
||||||
|
uid/gid, ssh-ключи и переменные окружения в `~/.env`.
|
||||||
|
|
||||||
|
Один сервис — один пользователь: контейнеры приложения работают от его uid/gid,
|
||||||
|
и файлы в `base_dir` принадлежат ему же. Роль вызывается первой задачей почти
|
||||||
|
каждого `playbook-<app>.yml`.
|
||||||
|
|
||||||
|
## Что делает
|
||||||
|
|
||||||
|
1. **Группа и пользователь.** Создаёт группу `owner_group` с `owner_gid` и
|
||||||
|
пользователя `owner_name` с `owner_uid` и шеллом `/bin/bash`. Номера задаёт
|
||||||
|
плейбук приложения (соглашение по нумерации — в `AGENTS.md`).
|
||||||
|
2. **SSH-ключи.** Прописывает `owner_ssh_keys` в `authorized_keys`.
|
||||||
|
3. **Переменные окружения.** Рендерит `~/.env` из `owner_env` плюс `USER_UID` и
|
||||||
|
`USER_GID` — их подставляют docker-compose шаблоны приложений. В `.bashrc`
|
||||||
|
добавляет строку, подгружающую `~/.env` целиком, и вычищает старые построчные
|
||||||
|
`export`-ы, оставшиеся от прежней схемы.
|
||||||
|
|
||||||
|
## Результат для плейбука
|
||||||
|
|
||||||
|
Роль регистрирует `owner_create_result` — результат модуля `user`. Плейбуки и
|
||||||
|
compose-шаблоны берут оттуда `owner_create_result.uid` и
|
||||||
|
`owner_create_result.group` (это gid) для `user:` в docker-compose.
|
||||||
|
|
||||||
|
## Переменные
|
||||||
|
|
||||||
|
Полный список и значения по умолчанию — в `defaults/main.yml`.
|
||||||
|
|
||||||
|
| Переменная | Обязательная | Описание |
|
||||||
|
| --------------------- | ------------ | --------------------------------------------------- |
|
||||||
|
| `owner_name` | да | Имя пользователя; по умолчанию оно же имя группы |
|
||||||
|
| `owner_uid` | да | Uid пользователя |
|
||||||
|
| `owner_group` | нет | Имя группы, по умолчанию `owner_name` |
|
||||||
|
| `owner_gid` | нет | Gid группы, по умолчанию `owner_uid` |
|
||||||
|
| `owner_extra_groups` | нет | Дополнительные группы; по умолчанию пусто |
|
||||||
|
| `owner_ssh_keys` | нет | Список публичных ключей для `authorized_keys` |
|
||||||
|
| `owner_env` | нет | Словарь переменных окружения, попадающих в `~/.env` |
|
||||||
|
|
||||||
|
## Про группу `docker`
|
||||||
|
|
||||||
|
Членство в группе `docker` равносильно root на хосте (`docker run -v /:/host`),
|
||||||
|
поэтому сервисным пользователям её не выдаём: контейнерами рулит Ansible от
|
||||||
|
root. Исключение — приложения, чей `backup.sh` дампит базу через
|
||||||
|
`docker compose exec`: оркестратор бэкапов запускает такие скрипты от имени
|
||||||
|
самого приложения. См. `docs/backlog/backup-dump-without-docker-group.md`.
|
||||||
|
|
||||||
|
## Пример использования
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: "Create user and environment"
|
||||||
|
ansible.builtin.import_role:
|
||||||
|
name: owner
|
||||||
|
vars:
|
||||||
|
owner_name: "{{ app_user }}"
|
||||||
|
owner_uid: "{{ app_owner_uid }}"
|
||||||
|
owner_gid: "{{ app_owner_gid }}"
|
||||||
|
```
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
role_name: owner
|
||||||
|
author: "Anton Vakhrushev"
|
||||||
|
description: "Системный пользователь приложения: группа, uid/gid, ssh-ключи и переменные окружения"
|
||||||
|
license: "MIT"
|
||||||
|
min_ansible_version: "2.14"
|
||||||
|
platforms:
|
||||||
|
- name: Ubuntu
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
- name: Debian
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
galaxy_tags:
|
||||||
|
- users
|
||||||
|
- system
|
||||||
|
- environment
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
+28
-20
@@ -1,21 +1,24 @@
|
|||||||
---
|
---
|
||||||
- name: 'Check app requirements for user "{{ owner_name }}".'
|
# tasks file for owner
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: You must set owner name.
|
|
||||||
when: not owner_name
|
|
||||||
|
|
||||||
- name: 'Check app requirements for user "{{ owner_name }}".'
|
- name: "Validate owner role arguments"
|
||||||
ansible.builtin.fail:
|
ansible.builtin.assert:
|
||||||
msg: You must set owner uid.
|
that:
|
||||||
when: not owner_uid
|
- owner_name is string and owner_name | length > 0
|
||||||
|
- owner_uid | int > 0
|
||||||
|
- owner_gid | int > 0
|
||||||
|
fail_msg: >-
|
||||||
|
Роль owner требует непустой owner_name и положительные owner_uid/owner_gid
|
||||||
|
(owner_name={{ owner_name }}, owner_uid={{ owner_uid }}, owner_gid={{ owner_gid }})
|
||||||
|
quiet: true
|
||||||
|
|
||||||
- name: 'Create group "{{ owner_group }}".'
|
- name: "Create group {{ owner_group }}"
|
||||||
ansible.builtin.group:
|
ansible.builtin.group:
|
||||||
name: "{{ owner_group }}"
|
name: "{{ owner_group }}"
|
||||||
gid: "{{ owner_gid }}"
|
gid: "{{ owner_gid }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: 'Create user "{{ owner_name }}".'
|
- name: "Create user {{ owner_name }}"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ owner_name }}"
|
name: "{{ owner_name }}"
|
||||||
group: "{{ owner_group }}"
|
group: "{{ owner_group }}"
|
||||||
@@ -24,20 +27,21 @@
|
|||||||
shell: /bin/bash
|
shell: /bin/bash
|
||||||
register: owner_create_result
|
register: owner_create_result
|
||||||
|
|
||||||
- name: 'Set up user ssh keys for user "{{ owner_name }}".'
|
- name: "Set up ssh keys for user {{ owner_name }}"
|
||||||
ansible.posix.authorized_key:
|
ansible.posix.authorized_key:
|
||||||
user: "{{ owner_name }}"
|
user: "{{ owner_name }}"
|
||||||
key: "{{ item }}"
|
key: "{{ item }}"
|
||||||
state: present
|
state: present
|
||||||
with_items: "{{ owner_ssh_keys }}"
|
loop: "{{ owner_ssh_keys }}"
|
||||||
when: owner_ssh_keys | length > 0
|
|
||||||
|
|
||||||
- name: "Prepare env variables."
|
# USER_UID/USER_GID кладём в окружение всегда: их подставляют docker-compose
|
||||||
|
# шаблоны приложений, чтобы контейнер работал от того же пользователя.
|
||||||
|
- name: "Prepare env variables"
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
# yamllint disable-line rule:line-length
|
owner_env_dict: >-
|
||||||
owner_env_dict: '{{ owner_env | combine({"USER_UID": owner_create_result.uid, "USER_GID": owner_create_result.group}) }}'
|
{{ owner_env | combine({"USER_UID": owner_create_result.uid, "USER_GID": owner_create_result.group}) }}
|
||||||
|
|
||||||
- name: 'Set up environment variables for user "{{ owner_name }}".'
|
- name: "Set up environment variables for user {{ owner_name }}"
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: env.template
|
src: env.template
|
||||||
dest: "/home/{{ owner_name }}/.env"
|
dest: "/home/{{ owner_name }}/.env"
|
||||||
@@ -45,14 +49,18 @@
|
|||||||
group: "{{ owner_group }}"
|
group: "{{ owner_group }}"
|
||||||
mode: "0640"
|
mode: "0640"
|
||||||
|
|
||||||
- name: 'Remove from bashrc absent environment variables for user "{{ owner_name }}".'
|
# Раньше переменные писались в .bashrc построчно; теперь источник истины —
|
||||||
|
# ~/.env, поэтому старые строки вычищаем.
|
||||||
|
- name: "Remove from bashrc absent environment variables for user {{ owner_name }}"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: "/home/{{ owner_name }}/.bashrc"
|
path: "/home/{{ owner_name }}/.bashrc"
|
||||||
regexp: "^export {{ item.key }}="
|
regexp: "^export {{ item.key }}="
|
||||||
state: absent
|
state: absent
|
||||||
with_dict: "{{ owner_env_dict }}"
|
loop: "{{ owner_env_dict | dict2items }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.key }}"
|
||||||
|
|
||||||
- name: 'Include in bashrc environment variables for user "{{ owner_name }}".'
|
- name: "Include in bashrc environment variables for user {{ owner_name }}"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: "/home/{{ owner_name }}/.bashrc"
|
path: "/home/{{ owner_name }}/.bashrc"
|
||||||
regexp: "^export \\$\\(grep -v"
|
regexp: "^export \\$\\(grep -v"
|
||||||
|
|||||||
Reference in New Issue
Block a user