Compare commits

...

2 Commits

Author SHA1 Message Date
av 21ccc7ac8c Fix style in ADR
Linting / YAML Lint (push) Has been cancelled
Linting / Ansible Lint (push) Has been cancelled
2026-05-24 15:56:53 +03:00
av 81478c2323 Add legacy ADR 2026-05-24 15:40:28 +03:00
6 changed files with 204 additions and 15 deletions
+50
View File
@@ -0,0 +1,50 @@
# Authelia вместо Keycloak для SSO
- Дата: 2025-05-07
## Контекст
Для SSO/OIDC на сервере стоял Keycloak (заведён годом ранее,
2024-05-25). Проблема — ресурсы: Keycloak съедал больше 500 МБ RAM, что
тяжело для личного сервера с ограниченной оперативной памятью. При этом вся его мощь
избыточна: пользователей меньше десяти, realms / federation / тяжёлый
корпоративный стек не нужны. Изначально взял Keycloak, потому что нужен был
OIDC-сервер для настройки Outline; на тот момент было понятное
руководство по связке OIDC и Keycloak.
Требовался лёгкий по памяти SSO-провайдер с хорошей документацией,
желательно на Go/Rust.
## Рассмотренные варианты
- **Оставить Keycloak.** Отвергнуто: > 500 МБ RAM ради < 10
пользователей, функционал избыточен для личного сервера.
- **Authelia** (выбран). Лёгкая (Go), малое потребление памяти, хорошая
документация. Умеет и OIDC, и forward-auth.
Критерии отбора замены: минимальный расход RAM, хорошая документация,
стек Go/Rust.
## Решение
Заменили Keycloak на Authelia как провайдер аутентификации
(коммиты `a77fefc`, `d1500ea`, `3a23c08`). Authelia используется в трёх
режимах:
- **OIDC** для приложений, которым он нужен (например, Outline).
- **Forward-auth** агент в Caddy — удобно там, где полноценный OIDC
избыточен.
- **Закрытие чувствительных приложений** за единым логином. Раньше для
этого использовался basic auth в Caddy.
## Последствия
- `+` Резко меньше потребление RAM — критично для сервера с дефицитом
памяти.
- `+` Forward-auth закрывает приложения без OIDC проще, чем поднимать
отдельный OIDC-клиент под каждое.
- `+` Единая точка аутентификации вместо разрозненного basic auth в
Caddy.
- `-` Authelia беднее Keycloak по возможностям (нет полноценного интерфейса
управления пользователями, realms, federation) — но для < 10
пользователей это не нужно.
@@ -0,0 +1,42 @@
# Данные приложений на отдельном диске
- Дата: 2025-12-07
## Контекст
Исторически данные приложений лежали прямо в домашних директориях их
системных пользователей (`/home/<app-user>/…`), то есть на системном
диске рядом с ОС. В конце 2025 встал вопрос обновления ОС (Ubuntu 22.04
уже устарела), и стало ясно: пока данные привязаны к системному диску,
любое обновление или пересборка системы рискует этими данными и тяжело
откатывается.
Возникла мысль развязать данные приложений и жизненный цикл ОС.
## Рассмотренные варианты
- **Данные на системном диске** (как было). Просто, но данные связаны с
ОС: обновление/пересборка системы затрагивает и их.
- **Отдельный диск под данные** (выбран). Данные переживают пересборку
ОС, диск можно отцепить от одного сервера и прицепить к другому.
## Решение
Вынесли все данные приложений на отдельный диск, смонтированный в
`/mnt/applications`; каждое приложение держит там свои `data` / `config`
/ `backups`, а `base_dir`/`data_dir` указывают на этот путь
(коммиты `47a6320`, `7e67409`, `ae7c20a`, `8dfd061`).
## Последствия
- `+` Данные развязаны с жизненным циклом ОС — систему можно обновлять
и пересобирать, не трогая данные.
- `+` Диск можно отцепить от старого сервера и прицепить к новому. Это
легло в основу метода обновления ОС
([ADR-2025-12-13](ADR-2025-12-13-os-upgrade-via-server-rebuild.md)).
- `-` Появилась зависимость от монтирования внешнего диска (UUID,
mount-конфигурация): если диск не смонтирован, приложения не
поднимутся. Позже, при переезде в Timeweb, монтирование пришлось
сделать опциональным
([ADR-2026-05-23](ADR-2026-05-23-migrate-to-timeweb.md), фаза 1 — один
диск, фаза 2 — отдельный «холодный» диск под крупные данные).
@@ -0,0 +1,53 @@
# Обновление ОС пересборкой на свежем сервере
- Дата: 2025-12-13
## Контекст
На сервере стояла Ubuntu 22.04, и к концу 2025 пора было обновляться.
Обновлять живую боевую систему «на месте» (`do-release-upgrade`) не
хотелось — это рискованно и тяжело откатывается, если что-то пойдёт не
так на работающем сервере.
## Рассмотренные варианты
- **Обновление «на месте»** (`do-release-upgrade` на живой системе).
Отвергнуто: риск сломать рабочий сервер, нет простого отката.
- **Пересборка на свежем сервере** (выбран). Поднять новый сервер с
целевой ОС, накатать ansible, прицепить диск с данными, развернуть
приложения — старый сервер остаётся нетронутым как точка отката.
Заодно — почистить мусор, накопившийся за время работы прошлого сервера.
## Решение
Обновляем ОС через пересборку на свежем сервере. Метод опирается на три
предпосылки:
- **Деплой без запуска контейнеров.** Сводные плейбуки
(`playbook-all-setup`, `playbook-all-applications`) и тег `run-app`
позволяют раскатать пользователей, каталоги и конфиги, но НЕ запускать
приложения (`--skip-tags run-app`) — данные переносятся в «тихую»
систему (коммиты `5b53cb3`, `48bb8c9`, `67df03e`).
- **Данные на отдельном диске**
([ADR-2025-12-07](ADR-2025-12-07-app-data-on-separate-disk.md)) — диск
с данными прицепляется к новому серверу.
- **Фиксированные uid/gid.** Заранее закрепили uid/gid всех
пользователей приложений (роль `owner`, коммит `c2ea2cd`). Это
критично: иначе при пересоздании пользователей на новом сервере
uid/gid могли бы сдвинуться, и данные приложений на отдельном диске
оказались бы с чужим владельцем.
Порядок: сначала вся подготовка (отдельный диск, перенос данных на него,
фиксация uid/gid), затем пересборка на новом обновлённом сервере. Перенос
прошёл без проблем.
## Последствия
- `+` Обновление ОС без риска для живой системы; откат = вернуться на
старый сервер.
- `+` Получился воспроизводимый процесс миграции — позже переиспользован
при переезде в Timeweb как «холодное переключение» (cold cutover)
([ADR-2026-05-23](ADR-2026-05-23-migrate-to-timeweb.md)).
- `+` Фиксация uid/gid стала постоянным инвариантом проекта.
- `-` Метод требует заранее подготовленных предпосылок (фикс uid/gid +
данные на отдельном диске); без них он не работает.
@@ -0,0 +1,37 @@
# Apprise как шлюз уведомлений
- Дата: 2026-04-04
## Контекст
В первую очередь нужны были уведомления о бэкапах — знать, что ночной
прогон отработал и не сломался. Уведомления слались напрямую в конкретный
канал, привязка была зашита в каждом источнике. Хотелось единый слой,
который абстрагирует каналы доставки — чтобы добавлять или менять канал в
одном месте, а не править каждый источник.
## Рассмотренные варианты
- **Прямая интеграция с каждым каналом** (как было). Каждый источник знает про
конкретный канал; смена канала — правки во многих местах.
- **Apprise** (выбран). Смотрел разные self-hosted шлюзы уведомлений;
apprise выиграл зрелостью и числом готовых интеграций (десятки каналов
из коробки).
## Решение
Подняли apprise отдельным сервисом-шлюзом: источники шлют уведомление по
HTTP в apprise, а он разводит его по настроенным каналам (коммиты
`a0543e1`, `5f619ea`, `6bfb362`). Под ограниченную память сервера apprise
запущен в один воркер (`5e6df11`).
## Последствия
- `+` Каналы доставки абстрагированы за единым шлюзом — добавить или
сменить канал можно в одном месте, не трогая источники.
- `+` Доступ к десяткам интеграций apprise без отдельного кода под
каждую.
- `-` Ещё один сервис в обслуживании (контейнер, память).
- Окупилось при переезде в Timeweb: провайдер заблокировал Telegram, и
переключение уведомлений (сейчас почта, в планах Matrix) локализовано в
шлюзе ([ADR-2026-05-23](ADR-2026-05-23-migrate-to-timeweb.md)).
+14 -11
View File
@@ -4,7 +4,7 @@
## Контекст ## Контекст
`rivendell-v2` жил на VM в Yandex Cloud. Одновременно копились три `rivendell-v2` жил на виртуальной машине в Yandex Cloud. Одновременно копились три
проблемы: проблемы:
- **Цена.** ≈ 2 887 ₽/мес за конфигурацию, которую другие провайдеры - **Цена.** ≈ 2 887 ₽/мес за конфигурацию, которую другие провайдеры
@@ -17,7 +17,7 @@
HDD вместо SSD/NVMe — страдала отзывчивость (Gitea, Outline, тёплый HDD вместо SSD/NVMe — страдала отзывчивость (Gitea, Outline, тёплый
старт контейнеров, restic check/forget). старт контейнеров, restic check/forget).
Это личный сервер — допустим мягкий даунтайм. Это личный сервер — допустимы небольшие простои.
## Рассмотренные варианты ## Рассмотренные варианты
@@ -37,16 +37,18 @@
Рамки решения: Рамки решения:
- Переезжает **только compute** (VM с приложениями). S3 (restic, бекапы), - Переезжает **только сам сервер с приложениями** (compute). S3 (restic, бэкапы),
Container Registry, Postbox SMTP и DNS-зона `vakhrushev.me` остаются Container Registry, Postbox SMTP и DNS-зона `vakhrushev.me` остаются
в Yandex и используются с новой машины. в Yandex и используются с новой машины.
- Стратегия — **cold cutover**: погасить сервисы на источнике, раскатать - Стратегия — **холодное переключение** (cold cutover): погасить сервисы
на источнике, раскатать
ansible на новом сервере без запуска приложений (сохраняя uid/gid), перенести ansible на новом сервере без запуска приложений (сохраняя uid/gid), перенести
данные `rsync`'ом, запустить, переключить DNS. данные `rsync`'ом, запустить, переключить DNS.
- Диск: фаза 1 — один 80 ГБ NVMe (всего 22 ГБ данных, влезает с - Диск: фаза 1 — один 80 ГБ NVMe (всего 22 ГБ данных + 17 ГБ системных, влезает с
запасом). «Холодный» второй диск под крупные данные — отдельная запасом). «Холодный» второй диск под крупные данные — отдельная
фаза 2, не на критическом пути. фаза 2, не на критическом пути.
- Источник не удаляется сразу после cutover: держим «холодным запасным» - Источник не удаляется сразу после переключения: держим «холодным
запасным»
пару недель ради отката. пару недель ради отката.
Детальный план — [`../drafts/timeweb.md`](../drafts/timeweb.md), Детальный план — [`../drafts/timeweb.md`](../drafts/timeweb.md),
@@ -59,7 +61,7 @@
закрыты все три исходные проблемы. закрыты все три исходные проблемы.
- `+` Запас по RAM убирает OOM-риск при всплесках нагрузки. - `+` Запас по RAM убирает OOM-риск при всплесках нагрузки.
- `+` Диверсификация по облакам: раньше сервер и данные были в одном - `+` Диверсификация по облакам: раньше сервер и данные были в одном
аккаунте Yandex Cloud, теперь compute в Timeweb, а бэкапы (S3) — в аккаунте Yandex Cloud, теперь сам сервер в Timeweb, а бэкапы (S3) — в
Yandex. Если заблокируют или потеряем доступ к одному провайдеру, Yandex. Если заблокируют или потеряем доступ к одному провайдеру,
данные остаются доступны через другой. данные остаются доступны через другой.
- `-` Диск меньше (80 ГБ NVMe против 120 ГБ HDD), но сейчас занят - `-` Диск меньше (80 ГБ NVMe против 120 ГБ HDD), но сейчас занят
@@ -67,13 +69,14 @@
- `-` Сохраняется зависимость от Yandex Cloud (S3, Container Registry, - `-` Сохраняется зависимость от Yandex Cloud (S3, Container Registry,
Postbox SMTP, DNS) — переезд её не устраняет. Postbox SMTP, DNS) — переезд её не устраняет.
- `-` Timeweb активно блокирует Telegram (в отличие от YC) — интеграция - `-` Timeweb активно блокирует Telegram (в отличие от YC) — интеграция
отвалилась. Затронуты `transcriber`, `remembos` и нотификации о отвалилась. Затронуты `transcriber`, `remembos` и уведомления о
бэкапах. Ожидаемо; нотификации остались через почту, второй канал бэкапах. Ожидаемо; уведомления остались через почту, второй канал
рассматривается через Matrix. рассматривается через Matrix.
- `-` Из-за тех же блокировок Timeweb перестали обновляться некоторые - `-` Из-за тех же блокировок Timeweb перестали обновляться некоторые
RSS-фиды в `miniflux`. RSS-фиды в `miniflux`.
- `-` Для доступа к `cr.yandex` вне YC появился долгоживущий OAuth-токен - `-` Для доступа к `cr.yandex` вне YC появился долгоживущий OAuth-токен
Яндекса в vault (`yc_oauth_token`) с широким blast radius. При желании Яндекса в vault (`yc_oauth_token`): при утечке он открывает доступ ко
сузить IAM-ключ сервисного аккаунта отдельной итерацией. всему аккаунту Яндекса. Сузить можно IAM-ключом сервисного аккаунта
отдельной итерацией.
- Инвентарь временно раздвоен (`production.yml` + `timeweb.yml`); после - Инвентарь временно раздвоен (`production.yml` + `timeweb.yml`); после
стабилизации источник удаляется, `timeweb.yml``production.yml`. стабилизации источник удаляется, `timeweb.yml``production.yml`.
+8 -4
View File
@@ -61,7 +61,11 @@
Новые сверху. Новые сверху.
| Дата | Запись | Статус | | Дата | Запись | Статус |
| ---------- | ------------------------------------------------------------------------------------- | ------ | | ---------- | ---------------------------------------------------------------------------------------------- | ------ |
| 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) | — |
| 0000-00-00 | [Вести историю решений в виде ADR](ADR-0000-00-00-record-architecture-decisions.md) | — | | 2026-04-04 | [Apprise как шлюз уведомлений](ADR-2026-04-04-apprise-notifications.md) | — |
| 2025-12-13 | [Обновление ОС пересборкой на свежем сервере](ADR-2025-12-13-os-upgrade-via-server-rebuild.md) | — |
| 2025-12-07 | [Данные приложений на отдельном диске](ADR-2025-12-07-app-data-on-separate-disk.md) | — |
| 2025-05-07 | [Authelia вместо Keycloak для SSO](ADR-2025-05-07-authelia-sso.md) | — |
| 0000-00-00 | [Вести историю решений в виде ADR](ADR-0000-00-00-record-architecture-decisions.md) | — |