Compare commits

..

7 Commits

Author SHA1 Message Date
av fe024b3b12 Migration: fix inventory hardcode in tasks.py
Linting / YAML Lint (push) Has been cancelled
Linting / Ansible Lint (push) Has been cancelled
2026-05-22 20:21:36 +03:00
av 8378f0edb0 Migration: expose some public vars 2026-05-22 20:18:45 +03:00
av 48737c1b6d Migration: optional external hdd mount 2026-05-22 20:17:12 +03:00
av 600a30ec11 Migration: add migration log 2026-05-22 20:11:23 +03:00
av 670947fcdf Migration: login to docker registry with oauth key 2026-05-22 20:11:10 +03:00
av 3545905cbd Migration: add draft for timeweb migration 2026-05-22 19:55:54 +03:00
av 893996f0c9 Docs: add docs and drafts 2026-05-22 19:13:05 +03:00
12 changed files with 1182 additions and 193 deletions
+82
View File
@@ -0,0 +1,82 @@
# Алерты на проблемные контейнеры
## Контекст
Случай с wakapi: при старте упали миграции, контейнер встал в restart-loop и
несколько дней крутился по кругу — никто не узнал. Из этого две проблемы:
1. Контейнеры могут бесконечно перезапускаться при ошибке.
2. Нет алертов о таких ситуациях.
## Что есть и что использовать
- **Netdata** — Docker-collector через cgroups + Docker API: состояние,
restart count, healthcheck status. Алерты в `health.d/*.conf`, нотификации
через `health_alarm_notify.conf` (Telegram/Discord/email/ntfy).
- **Dozzle** — только для просмотра логов после факта, нормальных алертов нет.
- **Caddy** — мог бы участвовать в healthcheck снаружи, но это отдельный слой.
## План — три слоя
### 1. Healthchecks в compose (фундамент)
Без них Docker считает контейнер «running», пока процесс жив, — wakapi с
падающими миграциями этому условию удовлетворял. Добавить в каждый
`docker-compose.yml.j2`:
```yaml
healthcheck:
test: ["CMD", "wget", "-q", "-O-", "http://localhost:PORT/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s # окно на миграции — failed-проверки до истечения не считаются
```
`start_period` — ключевая штука для случая wakapi: даём миграциям отработать,
до его истечения healthcheck не убивает контейнер.
### 2. Алерты через Netdata (главное)
Два разных сигнала:
- **Restart loop** — алерт на `docker.container_state` или счётчик
перезапусков (растёт > N за M минут). Это и есть «контейнер крутится по
кругу».
- **Unhealthy** — после healthcheck выше алерт на
`docker.container_health_status != healthy` дольше M минут.
Канал нотификаций: один, проще всего Telegram-бот. Настройка в
`health_alarm_notify.conf`.
### 3. Restart policy — что менять (или не менять)
Скорее **оставить `unless-stopped`**. Альтернативы и их минусы:
- `on-failure:5` — Docker сам остановит после 5 попыток. Минус: после ребута
сервера сервис не поднимется (только `always`/`unless-stopped` встают на
старте докера). Серьёзный регресс для домашнего сервера.
- Внешний sidecar, слушающий `docker events` и останавливающий контейнер
после N рестартов в окне — лишняя сложность ради того, что уже сделает
алерт.
Лучше: алерт пришёл → решаем вручную, остановить или чинить.
## Опционально (вне netdata)
- **Uptime Kuma** — внешний HTTP-чек по публичным URL. Ловит случаи, когда
контейнер «здоров», но прокся/DNS/Caddy сломаны. Свои нотификации, дашборд.
Не дублирует netdata, проверяет с другой стороны.
## Шаги при реализации
1. Добавить healthcheck + start_period в compose-шаблоны (начать с wakapi,
потом по списку).
2. Проверить, что netdata собирает Docker-метрики (collector включён).
3. Настроить один канал нотификаций (Telegram/ntfy/email — выбрать).
4. Написать пару алертов: restart-loop и unhealthy.
## Открытые вопросы
- Какой канал нотификаций использовать.
- Добавлять ли Uptime Kuma сразу или потом.
+285
View File
@@ -0,0 +1,285 @@
# Gitea runner on-demand в Yandex Cloud
## Контекст
В YC планируется развернуть self-hosted раннер для Gitea Actions. Сборки —
несколько раз в неделю, в среднем ~10 в неделю по ~5 минут. ВМ 24/7 даёт
утилизацию в районе 1%, остальное оплачивается впустую.
Цель — раннер активен только во время сборки и небольшого окна простоя
после, без ручных команд от разработчика.
## Архитектура
```
push → Gitea ──webhook──► Cloud Function ──Compute API──► ВМ (раннер)
(HMAC validate, │
start logic) ▼
act_runner (docker)
probe + decide
└─REST self-stop
```
Без API Gateway: функция публикуется напрямую через свой HTTPS-эндпоинт
`https://functions.yandexcloud.net/<id>`. Этот URL вписывается в Gitea
webhook. Аутентификация — HMAC-SHA256 в заголовке `X-Gitea-Signature`,
проверяется внутри функции.
Поток событий:
1. Push в Gitea → System Webhook на URL функции.
2. Функция валидирует HMAC, читает state ВМ, действует по стейт-машине
(см. ниже).
3. ВМ стартует, docker поднимает контейнер `act_runner`, тот подключается
к Gitea и забирает джобу.
4. На ВМ работают probe (раз в 30 сек собирает телеметрию) и decide
(раз в 1 мин принимает решение).
5. После idle-окна decide дёргает Compute REST API на gas самой себя.
## Cloud-side
### Ресурсы в YC
- Один фолдер на старте — общий с Gitea-сервером. Принятый риск: SA
самогашения формально может остановить любую ВМ в фолдере. Перенос в
отдельный фолдер — миграция на потом.
- Два сервисных аккаунта:
- `runner-self-stop` (привязан к ВМ): `compute.instances.stop`,
`compute.instances.get`.
- `runner-starter-fn` (привязан к функции): `compute.instances.start`,
`compute.instances.get`.
- Cloud Function `runner-starter`, runtime Python 3.11, 256 MB, timeout
30 сек. Публичный HTTPS-эндпоинт включён.
- Алерт Cloud Monitoring: `compute.instance.status = RUNNING` дольше 24 ч
подряд → нотификация (канал — на этапе внедрения).
### Bootstrap-скрипты
```
scripts/
├── runner-starter/ # код Cloud Function
│ ├── handler.py # webhook → start, стейт-машина
│ └── requirements.txt
├── runner_bootstrap.py # one-time: создать SA, ВМ, функцию, алерт
└── runner_deploy_function.py # обновить версию функции (yc CLI)
```
Скрипты на Python поверх `yc` CLI (через `subprocess`). Идемпотентность —
проверкой существования ресурсов перед созданием. Terraform не вводим:
ресурсов мало, оверкилл.
### Стейт-машина функции
| State ВМ | Действие |
| ------------------------------------- | ------------------------------------------------------- |
| `RUNNING`, `STARTING`, `RESTARTING` | 200, ничего не делаем |
| `STOPPED` | `instances:start` → 200 |
| `STOPPING` | poll до `STOPPED` (до 25 сек), затем `start` → 200 |
| `PROVISIONING`, `UPDATING` | 503 (временное состояние, retry клиентом) |
| `ERROR`, `CRASHED` | 500 + лог ошибки (нужен человек) |
| `DELETING`, `DELETED` | 500 + лог ошибки (что-то очень не так) |
## Host-side
### ВМ
- 2 vCPU (100%), 4 GB RAM, 25 GB network-hdd.
- Ubuntu 22.04 LTS.
- Без публичного IP при возможности (все исходящие к Gitea — через NAT
или внутренний адрес).
- Привязан SA `runner-self-stop`.
- Регистрация в Gitea Actions делается **один раз** при первой настройке.
Registration token берётся в Site Admin → Actions → Runners, кладётся
в Vault. Плейбук проверяет наличие `.runner` файла на ВМ; если есть —
пропускает регистрацию.
### Плейбук `playbook-gitea-runner.yml`
Стандартная структура проекта:
- `roles/owner` — пользователь `gitea-runner` (uid/gid выделить
отдельные, в группе `docker`).
- `files/gitea-runner/`:
- `docker-compose.template.yml``act_runner` в docker
(`gitea/act_runner:<pinned>`), `restart: unless-stopped`, mount
docker socket для запуска job-контейнеров.
- `act-runner-config.template.yaml` — конфиг раннера.
- `runner-probe.template.py` + `runner-probe.template.service` +
`runner-probe.template.timer`.
- `runner-decide.template.py` + `runner-decide.template.service` +
`runner-decide.template.timer`.
- `samples-logrotate.template.conf` — ротация `samples.log`.
Расширения шаблонов — `.template.<ext>`, не `.j2` (соглашение проекта).
### Раннер в docker
`act_runner` стартует через `docker compose up -d` под пользователем
`gitea-runner`. Поскольку `restart: unless-stopped`, дополнительный
systemd-юнит для самого раннера не нужен — после `Start` ВМ docker
поднимет контейнер автоматически.
Идентификатор контейнера фиксированный (`gitea_runner_app`), чтобы probe
мог исключать его из счёта.
### Probe и decide
Два независимых юнита, телеметрия — append-only лог.
`runner-probe` (timer раз в 30 сек):
```bash
# pseudocode
busy_count=$(docker ps -q | grep -v <runner_container_id> | wc -l)
state=$([ "$busy_count" -gt 0 ] && echo busy || echo idle)
echo "$(date -u +%FT%TZ) $state containers=$busy_count" \
>> /var/lib/runner-idle/samples.log
```
В реальной реализации — Python, фильтр по docker SDK или по результату
`docker ps --format '{{.Names}}'`.
`runner-decide` (timer раз в 1 мин):
1. Читает хвост `samples.log`.
2. Находит `last_busy_at` — timestamp последней `busy`-строки.
3. Находит `last_sample_at` — timestamp последней любой строки.
4. Логика:
- `now - last_sample_at > STALE_THRESHOLD` (5 мин) → **probe сломан**,
не гасим, логируем error. Алерт CM поймает по uptime.
- `now - last_busy_at > IDLE_THRESHOLD` (10 мин) → `instances:stop`
через REST.
- Иначе → ничего.
Параметры (`IDLE_THRESHOLD`, `STALE_THRESHOLD`) — переменные в шаблоне,
тюнятся по эксплуатации.
### Самогашение через REST
Без `yc` CLI. Decide-скрипт получает IAM-токен из metadata-сервиса и
дёргает Compute REST:
```python
TOKEN_URL = "http://169.254.169.254/computeMetadata/v1/instance/" \
"service-accounts/default/token"
ID_URL = "http://169.254.169.254/computeMetadata/v1/instance/id"
HEADERS = {"Metadata-Flavor": "Google"}
token = requests.get(TOKEN_URL, headers=HEADERS).json()["access_token"]
instance_id = requests.get(ID_URL, headers=HEADERS).text
requests.post(
f"https://compute.api.cloud.yandex.net/compute/v1/instances/{instance_id}:stop",
headers={"Authorization": f"Bearer {token}"},
)
```
Никаких файлов с SA-key, никаких зависимостей сверх `python3 +
requests`.
## Страховки от зависшей ВМ
Главный failure mode — probe или decide молча сломались, ВМ работает 24/7.
Слой 1 — soft idle-stop в decide. Нормальная работа.
Слой 2 — probe-staleness в decide. Если `samples.log` не обновляется
дольше `STALE_THRESHOLD` — логируем error, **не гасим** (мог идти
длинный билд). Полагаемся на слой 3.
Слой 3 — внешний алерт через Cloud Monitoring на uptime ВМ > 24 ч. Не
дёргает остановку, только нотификация. Порог высокий, чтобы дни активной
отладки не триггерили его. Если фактически висит сутки — это сигнал
смотреть руками.
Hard-cap по uptime внутри decide **не делаем**: ломает кейс «активно
тестирую несколько часов подряд», когда busy-сэмплы есть и логика идёт
правильно.
## Секреты (Vault, `vars/secrets.yml`)
| Ключ | Назначение |
| --------------------------------- | ----------------------------------------------------- |
| `gitea_runner_registration_token` | одноразовый токен для `act_runner register` |
| `gitea_webhook_secret` | общий с функцией HMAC-секрет для webhook |
| `yc_runner_folder_id` | в каком фолдере живёт ВМ |
| `yc_runner_instance_id` | ID ВМ (заполняется после bootstrap) |
| `yc_runner_function_url` | URL функции для webhook (заполняется после bootstrap) |
## invoke-таски
| Таск | Что делает |
| ----------------------------- | ----------------------------------------------------------------------------- |
| `inv runner-bootstrap` | one-time: создаёт SA, ВМ, функцию, алерт. Идемпотентен. |
| `inv runner-deploy-function` | заливает новую версию `runner-starter`. |
| `inv runner-pl` | up → `ansible-playbook playbook-gitea-runner.yml` → down. С `try/finally`. |
| `inv runner-up` / `down` | ручной старт/стоп ВМ для дебага. |
| `inv runner-status` | state ВМ + хвост `samples.log` (через ssh). |
| `inv runner-ssh` | ssh на ВМ, поднимает её при необходимости. |
`runner-pl` — основной таск, единственный «штатный» путь обновления
конфига ВМ. Если плейбук падает посередине, `finally` всё равно гасит ВМ
(idle-watch её и так бы погасил, но явное лучше).
## Стоимость
Базовая ставка YC (USD, после повышения 1 мая 2026): vCPU 100% =
$0.010164/ч, RAM = $0.002705/ГБ·ч, network-hdd = $0.0000356/ГБ·ч.
Профиль: 10,75 ч активной ВМ в месяц.
| Конфиг (2 vCPU 100%, 4 GB RAM, 25 GB HDD) | $/мес |
| ----------------------------------------- | ----- |
| Compute (vCPU + RAM) при 10,75 ч | ~0.33 |
| Disk (HDD, 24/7) | ~0.64 |
| Cloud Function, Monitoring | 0.00 |
| **Итого** | **~1.0** |
Сравнение: эта же ВМ в режиме 24/7 ≈ $23/мес. Экономия — порядка 95%.
Дальнейшая оптимизация — диск (15 GB вместо 25, ещё ~$0.25/мес). Делать
не сейчас.
## Принятые риски
- **Общий фолдер с другими ВМ.** SA `runner-self-stop` теоретически
может погасить и Gitea-сервер, если тот переедет в YC рядом. Митигация
при появлении такой ВМ — перенос в отдельный фолдер.
- **Холодный старт ~60 сек.** Дизайн заявляет 40, реальность ближе к
60 (Ubuntu boot + docker pull + act_runner connect). Документируем как
«нормальная задержка первой джобы».
- **Регистрационный токен утерян.** При пересоздании диска ВМ нужен
новый токен из Gitea UI. Документируем процесс. Раз в годы — терпимо.
- **Probe сломан, ВМ висит.** Поймает алерт CM, ручное расследование.
## План внедрения
1. Создать в YC: 2 SA, ВМ, дисковый ресурс. Через `inv
runner-bootstrap` или вручную через консоль (выбираем по желанию на
этапе реализации).
2. Прогнать `inv runner-pl` на свежесозданной ВМ. С временно
уменьшенным `IDLE_THRESHOLD` (2 мин вместо 10) — чтобы тестировать
гашение быстро.
3. Зарегистрировать раннер в Gitea руками: получить registration token,
положить в Vault, повторить `inv runner-pl`.
4. Проверить, что раннер появился в Gitea UI и забирает тестовую джобу.
5. Проверить idle-watch: дать ВМ постоять, убедиться, что гасится.
6. Создать функцию `runner-starter` через `inv runner-deploy-function`.
Проверить ручным `yc serverless function invoke`.
7. Прописать System Webhook в Gitea на URL функции, секрет совпадает с
Vault.
8. Тестовый push → end-to-end проверка.
9. Поднять `IDLE_THRESHOLD` обратно до 10 мин.
10. Настроить алерт Cloud Monitoring на uptime > 24 ч.
11. Неделя наблюдения: лог функции, samples.log, uptime ВМ, счёт.
## Открытые вопросы
- **Канал нотификаций** для алерта Cloud Monitoring (Telegram, ntfy,
email) — выбрать на этапе настройки.
- **Тип executor** в act_runner — docker (по умолчанию) или host. Ходим
через docker, host-executor пока не обсуждается.
- **Webhook на pull request** — нужно или только push? По умолчанию
только push. Расширим, если возникнет PR-flow.
- **Перенос ВМ в отдельный фолдер** — когда в общем появится вторая
ВМ. Пока не критично.
+97
View File
@@ -0,0 +1,97 @@
# Журнал миграции в Timeweb
Хронология фактического переезда. План и архитектурные решения —
в [timeweb.md](timeweb.md). Здесь только то, что реально сделано,
с датами.
Новые записи — сверху.
---
## Шаг 4 — условное монтирование внешнего диска (2026-05-22, выполнено)
Задача `Mount external storages` в `playbook-system.yml` теперь
выполняется только при включённом флаге `mount_external_storage`
(default `false`). Сам UUID диска оставлен захардкоженным в
плейбуке — параметризовать не стали, потому что для Timeweb (фаза 1)
монтирование вообще не нужно, а для фазы 2 пока неизвестно, какой
UUID получится у второго диска.
Изменения:
- `playbook-system.yml` — у задачи mount добавлен
`when: mount_external_storage | default(false) | bool`.
- `production.yml` (инвентарь YC) — у хоста `server` добавлен
`mount_external_storage: true`, чтобы текущее поведение
сохранилось.
В будущем `timeweb.yml` просто не будет задавать эту переменную —
mount пропустится, `/mnt/applications` останется обычной директорией
на системном диске.
На фазе 2 (подключение медленного диска в Timeweb) UUID в
`playbook-system.yml` придётся поменять и включить флаг — это
осознанный шаг, не автоматизировано.
Проверено прогоном `inv pl -- system` на текущем сервере (Yandex
Cloud) — задача mount по-прежнему выполняется, `/mnt/applications`
смонтирован, изменений нет.
---
## Шаг 3 — переключение auth на cr.yandex (2026-05-22, выполнено)
Заменена аутентификация в Yandex Container Registry с YC-metadata
service на OAuth-token из vault.
Изменения:
- `files/yandex-docker-registry-auth.sh`**удалён**.
- `playbook-homepage.yml` — задача `ansible.builtin.script:
yandex-docker-registry-auth.sh` заменена на
`community.docker.docker_login` с `username: oauth`, `password:
"{{ yc_oauth_token }}"`.
- `playbook-transcriber.yml` — то же самое.
Локальные push-плейбуки (`playbook-homepage-registry.yml`,
`playbook-transcriber-registry.yml`) не трогал — там нет auth-задачи
в принципе, локальный docker аутентифицируется вручную
(`yc container registry configure-docker` или `docker login`).
Если позже захочется унифицировать — можно добавить тот же
`docker_login` с `delegate_to: 127.0.0.1`.
Проверено прогоном `inv pl -- homepage` и `inv pl -- transcriber` на
текущем сервере (Yandex Cloud) — ошибок нет, контейнеры работают.
Значит и на Timeweb заработает (единственная разница — исходящий IP,
а OAuth-токен в YC принимается извне).
---
## Шаг 2 — OAuth-token для cr.yandex (2026-05-22, выполнено)
В `vars/secrets.yml` добавлена (или обновлена) переменная
`yc_oauth_token` со свежим OAuth-токеном Яндекса. Токен будет
использоваться для логина в `cr.yandex` с новой машины Timeweb
(вместо текущего скрипта `files/yandex-docker-registry-auth.sh`,
который завязан на YC metadata service `169.254.169.254` и
работает только внутри YC).
Сам код переключения на `community.docker.docker_login` пока не
вносится — это следующая итерация. Сейчас токен просто положен в
vault, чтобы не делать этого в день cutover'а под прессом.
---
## Шаг 1 — снижение TTL DNS (2026-05-22, выполнено)
В админке Yandex 360 для зоны `vakhrushev.me` уменьшен TTL
A-записей с **21 600 с (6 ч)** до **1 200 с (20 мин)**. Это даёт
запас по времени на распространение изменений после смены IP в
день cutover'а — старые кэширующие резолверы перестанут отдавать
старый адрес максимум через 20 минут (вместо 6 часов).
Делается **заранее**, потому что само снижение TTL тоже
распространяется по кэшам по правилам старого TTL — то есть после
правки нужно подождать ≥ 6 часов, чтобы новое значение TTL само
успело прижиться. Раньше cutover'а нужно сделать с большим
запасом — день в день не сработает.
+532
View File
@@ -0,0 +1,532 @@
# Миграция сервера в Timeweb
## Контекст и цели
Сервер `rivendell-v2` переезжает с виртуальной машины в Yandex Cloud
(`158.160.46.255`) на VPS в Timeweb.
### Причины переезда
1. **Высокая стоимость.** Тариф в Yandex Cloud обходится в ≈ 2 900 ₽/мес
за конфигурацию, которая в Timeweb стоит ≈ 2 000 ₽/мес и при этом
мощнее по всем параметрам (см. сравнение ниже).
2. **Упор в потолок RAM.** Текущий сервер уже использует ≈ 80 %
доступной памяти на штатной нагрузке (см.
`project_server_specs`). Любой всплеск (миграции БД, индексация
в Outline, бэкап с restic) — и приложения начинают конкурировать
за память, появляются OOM-риски. Дальше расти на этом тарифе
некуда без значительного увеличения цены.
3. **Медленные диски.** Из-за высокой стоимости в YC приходится
использовать дешёвый HDD-том вместо SSD/NVMe — это заметно
снижает отзывчивость приложений (особенно Gitea, Outline,
тёплый старт контейнеров, рестики check/forget). На Timeweb за
меньшие деньги получаем NVMe.
Переезд решает все три проблемы одновременно: дешевле, больше
RAM, быстрее диск.
### Сравнение тарифов
| Параметр | Yandex Cloud | Timeweb Cloud VPS |
| -------------- | ----------------------------------------- | ------------------ |
| CPU | Intel Cascade Lake, vCPU 2, гарантия 50 % | 4 × 3.3 ГГц |
| RAM | 4 ГБ | 8 ГБ |
| Диск | 120 ГБ HDD | 80 ГБ NVMe |
| Публичный IP | да | да |
| **Цена/месяц** | **2 887 ₽** | **1 980 ₽** |
Итого: **907 ₽/мес (≈ −31 %)**, при этом **×2 RAM** (закрывает
причину 2), **×2 ядер**, гарантия CPU 100 % вместо 50 %,
**NVMe вместо HDD** (закрывает причину 3). Минус — диск меньше
(80 ГБ против 120 ГБ HDD), что и стало основанием для фазы 2 с
подключением второго «холодного» диска под крупные данные.
Переезжает **только compute** (VM с приложениями). Остальные сервисы
Yandex Cloud остаются на месте и продолжают использоваться с новой
машины:
- **Container Registry** — `cr.yandex/crplfk0168i4o8kd7ade` для образов
`homepage-nginx` и `transcriber`.
- **Object Storage (S3)** — restic-репозиторий `yandex_cloud_s3`.
- **Postbox SMTP** — `postbox.cloud.yandex.net` (gitea, gramps, wakapi,
outline, authelia, apprise).
- **Yandex 360 / DNS-зона** `vakhrushev.me` — там же управляются записи
и почтовый домен.
Параметры даунтайма — мягкие, это личная машина. Стратегия — «cold
cutover»: остановить сервисы на источнике, раскатать ansible на
target без запуска приложений, перенести данные с сохранением
uid/gid, запустить сервисы на target, переключить DNS.
Конфигурация target — Cloud VPS Timeweb с одним диском **80 ГБ** на
первой фазе. Позднее (отдельной фазой) будет подключён второй
«медленный» диск под крупные данные (`calibre`, бэкапы, возможно
`outline`).
---
> Фактическое выполнение переезда — в отдельном файле
> [timeweb-migration-log.md](timeweb-migration-log.md). Здесь только
> план и архитектурные решения.
---
## Инвентаризация YC-зависимостей в коде
| Компонент | Где | Что делать при переезде |
| --- | --- | --- |
| `production.yml` | `ansible_host: 158.160.46.255`, `ansible_user: major` | Заменить на новый IP/пользователя Timeweb |
| `files/yandex-docker-registry-auth.sh` | Логин в `cr.yandex` через **YC metadata service** (`169.254.169.254`) | **Не работает вне YC.** Перейти на static OAuth-token / IAM-token (новый скрипт + секрет в vault) |
| `playbook-system.yml` (mount-storage) | UUID `3942bffd-…` монтируется в `/mnt/applications` | Фаза 1: отключить mount или сделать UUID переменной vault. Фаза 2 (после подключения медленного диска): включить заново с новым UUID |
| `files/backups/config.template.toml` | `[storage.yandex_cloud_s3]` + `AWS_*` ключи | **Не меняем.** Тот же бакет/ключи продолжают работать. Меняется только `host_name` (для подписи снапшотов и нотификаций) — он уже шаблонится |
| SMTP (`postbox_host/port/user/pass`) | gitea, gramps, wakapi, outline, authelia, apprise | **Не меняем.** Postbox SMTP доступен извне YC по тем же credentials |
| `files/backups/rclone.template.conf` (`pr86keedav`) | WebDAV-копия restic — внешний сервис | **Не меняем** |
| Caddy `tls anwinged@ya.ru` | ACME | Не меняется, ACME перевыпустит сертификаты после смены IP |
Никаких других hardcoded YC-эндпоинтов в плейбуках / шаблонах нет —
SSH, ufw, fail2ban, docker, eget, restic, Caddy полностью переносимы.
---
## UID / GID — критично для rsync
UID/GID каждого приложения зафиксированы в плейбуках и в
`vars/homepage.yml` / `vars/transcriber.yml`. Роль `owner` создаёт
группы и пользователей **с явно указанными gid/uid**
(`roles/owner/tasks/main.yml`). Это значит:
- Если на новой машине **сначала** раскатать все плейбуки (без
запуска приложений), пользователи получатся с теми же uid/gid.
- Тогда `rsync -aAX` (с сохранением owner) корректно ляжет на target.
- Дополнительный maping uid не нужен.
Список приложений с uid/gid (для сверки и для документации):
```
caddyproxy 1010 / 1011
authelia 1011 / 1012
netdata 1012 / 1013
miniflux 1013 / 1014
rssbridge 1014 / 1015
wakapi 1015 / 1016
dozzle 1016 / 1017
transcriber 1017 / 1018
wanderer 1018 / 1019
memos 1019 / 1020
gitea 1005 / 1006
outline 1007 / 1008
homepage 1008 / 1009
gramps 1009 / 1010
calibre 1102 / 1102
remembos 1103 / 1103
apprise 1104 / 1104
tuwunel 1105 / 1105
goaccess 1106 / 1106
```
(Возможные пересечения uid одного приложения и gid другого
существуют, но Linux держит их в разных пространствах имён — не
страшно.)
---
## Подготовка кода проекта
Делается **до** аренды Timeweb-машины, отдельным PR (или сериями
коммитов на отдельной ветке). Цель — чтобы тот же ansible
работал и на источнике, и на target без условных хаков.
### 1. Заменить YC-specific docker registry auth
`files/yandex-docker-registry-auth.sh` сейчас использует metadata
service (`169.254.169.254`). Это работает только внутри YC VM,
поэтому на Timeweb его надо заменить.
**Решение — OAuth-token Яндекса.** Простой и достаточный для
домашнего сервера механизм:
1. Получить OAuth-token в кабинете Яндекса:
<https://oauth.yandex.ru/authorize?response_type=token&client_id=1a6990aa636648e9b2ef855fa7bec2fb>
(стандартный client_id для `yc` CLI, токен с правом доступа к
Container Registry).
2. Положить в `vars/secrets.yml` как `yc_oauth_token` (vault).
3. Переписать `files/yandex-docker-registry-auth.sh` как шаблон
(`.template.sh`) и рендерить через `ansible.builtin.template`
вместо `script:`. Скрипт сводится к:
```sh
#!/usr/bin/env sh
set -eu
echo "{{ yc_oauth_token }}" | \
docker login --username oauth --password-stdin cr.yandex
```
Альтернатива — не рендерить, а передавать токен в скрипт
аргументом или через переменную окружения, чтобы не светить его
в системе.
4. В `playbook-homepage.yml` и `playbook-transcriber.yml` поменять
`ansible.builtin.script:` на `ansible.builtin.template:` +
`ansible.builtin.command:` (либо использовать модуль
`community.docker.docker_login` напрямую с `username: oauth`,
`password: "{{ yc_oauth_token }}"` — это самый чистый вариант,
тогда отдельный скрипт вообще не нужен).
5. То же самое — для локальных push-плейбуков
`playbook-homepage-registry.yml` и
`playbook-transcriber-registry.yml`.
Рекомендую вариант с `community.docker.docker_login` — это убирает
shell-скрипт целиком и сильно проще.
Минусы OAuth-token: токен живёт долго и даёт доступ ко всему
аккаунту Яндекса. Для личного сервера приемлемо; если позже
захочется минимизировать blast radius — заменить на IAM-key
сервисного аккаунта (отдельная итерация после миграции).
Затронутые места: `files/yandex-docker-registry-auth.sh` (удалить
или переписать), `playbook-homepage.yml`, `playbook-transcriber.yml`,
`playbook-homepage-registry.yml`, `playbook-transcriber-registry.yml`,
`vars/secrets.yml` (новый ключ `yc_oauth_token`).
### 2. Сделать опциональным монтирование внешнего диска
Сейчас `playbook-system.yml` жёстко монтирует UUID `3942bffd-…` в
`/mnt/applications`. На Timeweb этого диска нет.
Минимальная правка — вытащить UUID в переменную (`storage_uuid`) и
обернуть mount-задачу `when: storage_uuid is defined`. В
`vars/secrets.yml` или `vars/vars.yml` для текущего сервера задать
UUID, для Timeweb (фаза 1) — не задавать. На фазе 2 (когда придёт
медленный диск) — задать новый UUID.
Альтернатива: вынести параметры в инвентарь
(`production.yml` → `host_vars/server.yml`).
При этом сама директория `/mnt/applications` должна создаваться в
любом случае — playbook уже это делает, надо лишь убедиться, что
задача «Create directory for mount» не зависит от mount-задачи.
### 3. Параметризовать инвентарь
На время перехода — **два отдельных файла**: текущий
`production.yml` остаётся как есть, рядом появляется новый
`timeweb.yml` с настройками Timeweb-машины. Все ansible-команды
во время миграции явно указывают `-i timeweb.yml`. После того, как
переезд закончен и старая машина выключена — `production.yml`
просто удаляется, `timeweb.yml` переименовывается в
`production.yml`.
`tasks.py` использует `yq` для извлечения `ansible_host` / `ansible_user`
из инвентаря (`_yq(".ungrouped.hosts.server…")`) — путь к файлу
зашит константой `HOSTS_FILE = "production.yml"`. Варианты:
- На время миграции временно поменять `HOSTS_FILE = "timeweb.yml"`
в локальном коммите (или через env override), потом откатить — после
переименования всё снова работает.
- Принять, что `inv ssh / zj / btop / login` работают только с
активным сервером (тем, что в `production.yml`), а к старой
машине во время миграции ходим напрямую через `ssh
major@158.160.46.255`.
Первый вариант чище. Достаточно одной строчки правки.
### 4. Прочее
- `README.md` — обновить инструкцию по DNS и упомянуть Timeweb.
- Удалить (или пометить deprecated) yandex-метаданные в комментариях
`yandex-docker-registry-auth.sh`.
- Проверить, что у всех application-плейбуков задача с
`community.docker.docker_compose_v2: state: present` помечена
тегом `run-app` — это позволит раскатывать `--skip-tags run-app`
для подготовки target без запуска контейнеров. Сейчас тег `run-app`
есть в большинстве плейбуков, но надо пройтись и убедиться, что
он покрывает **все** контейнеры (включая calibre, dozzle,
remembos, transcriber, tuwunel, wanderer, memos).
---
## Подготовка target-машины
1. Заказать Cloud VPS в Timeweb:
- Ubuntu LTS (та же мажорная версия, что и сейчас — упростит
совместимость пакетов).
- 4 GB RAM (текущий лимит ≈ 3.8 GiB, см. `project_server_specs`),
можно взять чуть с запасом — 4–6 GB, иначе netdata + tuwunel +
outline начнут давить.
- 2 vCPU.
- SSD 80 ГБ.
- Снять/настроить firewall провайдера (или отключить, т.к. у нас
свой ufw).
2. Создать пользователя с правами sudo (аналог `major`), залить
свой SSH-ключ.
3. Добавить хост в инвентарь как `server` (или временный
`timeweb`), убедиться, что `ansible -m ping` отвечает.
4. Снизить TTL DNS-записей в Yandex 360 до 60300 секунд **за
~2448 часов** до cutover.
---
## Cutover (план дня X)
Предусловия: код выкатан, target-машина пингуется по ansible, TTL
DNS снижены.
### Шаг 1. Финальный бэкап на источнике
```bash
inv ssh
sudo /usr/local/sbin/backup-all.py 2>&1 | tee /tmp/final-backup.log
```
Убедиться, что в логе все приложения отработали успешно и в S3
появился свежий restic-snapshot (на случай отката или потери
данных при rsync).
### Шаг 2. Остановить все приложения на источнике
Аккуратно остановить контейнеры каждого приложения (через
`docker compose down` от соответствующего пользователя или одним
проходом):
```bash
inv ssh
for user in caddyproxy authelia netdata miniflux rssbridge wakapi \
dozzle transcriber wanderer memos gitea outline homepage gramps \
calibre remembos apprise tuwunel goaccess; do
sudo -iu "$user" bash -c "cd /mnt/applications/$user && docker compose down"
done
```
(Можно завести вспомогательный плейбук `playbook-shutdown-all.yml`,
если такое будет часто.)
Проверить `docker ps`, что пусто. Снять флаги cron на бэкап (чтобы
финальный backup не побежал во время миграции):
```bash
sudo systemctl stop cron
```
### Шаг 3. Раскатать инфраструктуру на target БЕЗ запуска приложений
```bash
# 1) системная база
uv run ansible-playbook -i timeweb.yml --diff playbook-all-setup.yml
# 2) приложения (создаём пользователей, каталоги, конфиги,
# но НЕ запускаем контейнеры)
uv run ansible-playbook -i timeweb.yml --diff \
--skip-tags run-app \
playbook-all-applications.yml
```
Цель — после этого на target есть:
- Корректные uid/gid для всех приложений.
- Каталоги `/mnt/applications/<app>/{data,config,backups}`.
- Шаблоны `docker-compose.yml` и application-конфиги — отрендерены
и лежат на месте.
- Docker и сети созданы.
- ufw настроен, fail2ban работает.
### Шаг 4. Перенос данных
Два варианта.
**Вариант A — rsync напрямую (быстрее).** С target-машины тянем
данные со старой:
```bash
sudo rsync -aAX --info=progress2 --delete \
--exclude='lost+found' \
major@158.160.46.255:/mnt/applications/ \
/mnt/applications/
```
`-aAX` сохраняет ACL/xattrs и uid/gid (численные значения).
Каждое приложение можно тянуть отдельно — удобнее наблюдать
прогресс и можно частично пересинхронизировать в случае ошибок.
**Вариант B — restore из restic.** Если по сети источник недоступен
(например, IP уже закрыли) или хочется проверить, что бэкапы вообще
рабочие — восстанавливаемся из YC S3:
```bash
sudo /usr/local/sbin/restic-shell.sh
restic restore latest --target /mnt/applications --path /mnt/applications
```
Рекомендую **A с фолбэком на B**: rsync быстрее и точнее (с
точностью до секунды), restic держим как страховку.
### Шаг 5. Запуск приложений на target
Раскатываем application-плейбуки ещё раз — теперь без `--skip-tags`:
```bash
uv run ansible-playbook -i timeweb.yml --diff \
playbook-all-applications.yml
```
Этот же запуск проверит идемпотентность шаблонов (не должно быть
diff'ов кроме docker-up).
После старта — проверить:
- `docker ps` — все контейнеры в healthy.
- Локально (по IP) `curl http://<target-ip>` — Caddy отвечает (на
редирект, т.к. сертификаты ещё не выпущены под этим IP).
- Логи Caddy — выпуск сертификатов запустится после смены DNS, не
раньше. Это нормально.
### Шаг 6. Переключение DNS
В Yandex 360 admin (`admin.yandex.ru/domains/vakhrushev.me`)
поменять A-записи для всех subdomain'ов на новый IP. Перечень
поддоменов (из `Caddyfile.template`):
```
vakhrushev.me (apex)
matrix.vakhrushev.me
auth.vakhrushev.me
status.vakhrushev.me
git.vakhrushev.me
outline.vakhrushev.me
gramps.vakhrushev.me
miniflux.vakhrushev.me
wakapi.vakhrushev.me
wanderer.vakhrushev.me
memos.vakhrushev.me
remembos.vakhrushev.me
calibre.vakhrushev.me
wanderbase.vakhrushev.me
rssbridge.vakhrushev.me
dozzle.vakhrushev.me
goaccess.vakhrushev.me
```
После смены — подождать пока TTL разойдётся, проверить через
`dig +short <hostname>` с независимой машины.
Caddy сам пойдёт за сертификатами Let's Encrypt — следить за его
логами (`docker logs caddyproxy_app -f`).
### Шаг 7. Проверка после cutover
Чеклист (примерно по приоритету):
- [ ] `vakhrushev.me` отвечает 200, отдаёт homepage.
- [ ] `auth.vakhrushev.me` — Authelia, можно залогиниться.
- [ ] `git.vakhrushev.me` — Gitea, репозитории на месте, ssh-доступ
(порт 2222 в ufw уже открыт).
- [ ] `outline.vakhrushev.me` — открывается, документы на месте.
- [ ] `matrix.vakhrushev.me` — Tuwunel/Element подключается;
federation проверяется через
<https://federationtester.matrix.org/>.
- [ ] `miniflux.vakhrushev.me`, `wakapi.vakhrushev.me`,
`memos.vakhrushev.me`, `gramps.vakhrushev.me`,
`remembos.vakhrushev.me`, `wanderer.vakhrushev.me`,
`calibre.vakhrushev.me`, `rssbridge.vakhrushev.me`,
`dozzle.vakhrushev.me`, `goaccess.vakhrushev.me` —
открываются, данные на месте.
- [ ] Netdata `status.vakhrushev.me` — собирает метрики.
- [ ] Backup-cron — следующий запуск (1:00) проходит успешно,
приходит уведомление в apprise.
- [ ] SMTP — отправить тестовое письмо из gitea/authelia (триггер
reset password).
- [ ] Container Registry — `docker pull cr.yandex/...` на новой
машине проходит (это значит, что наша новая аутентификация
через OAuth/IAM работает).
### Шаг 8. Заморозка источника
Когда всё подтверждено стабильным (≥ 24 часа):
- Остановить и выключить старую VM в YC.
- Подождать неделю-две на случай отката.
- Удалить VM и связанные ресурсы (только compute! S3-бакет с
restic-бэкапами и Container Registry **остаются**).
- Удалить `production.yml`, переименовать `timeweb.yml` →
`production.yml`, откатить временную правку `HOSTS_FILE` в
`tasks.py` (теперь снова `production.yml`). Закоммитить.
---
## Фаза 2: подключение медленного диска
После того как Timeweb-сервер стабилен:
1. Заказать дополнительный «холодный» диск в Timeweb, прицепить
к VPS.
2. Узнать UUID нового устройства (`lsblk -f`).
3. Решить, куда монтировать — варианты:
- Сохранить текущую схему (`/mnt/applications` на медленном
диске целиком). Минус: всё IO приложений уходит на медленный
диск.
- **Лучше:** оставить `/mnt/applications` на быстром SSD,
медленный смонтировать как `/mnt/cold` и под calibre/большие
бэкапы делать bind-mount или поменять `data_dir` у нужных
приложений.
4. Восстановить в `playbook-system.yml` mount-задачу с новым
UUID (через переменную, заведённую на фазе 1).
5. Прогнать `inv pl -- system` с тегом `mount-storage`.
6. Переехать на холодный диск только большие данные. Для calibre
это означает остановить контейнер, `rsync` библиотеки книг,
поправить `data_dir` в `vars`, запустить.
---
## Что НЕ менять во время миграции
Чтобы не накапливать изменения в одном переезде:
- Версии docker-образов всех приложений — те же, что в источнике.
- Конфиги приложений — без правок.
- Restic snapshot policy.
- Apprise/notification каналы.
Любые улучшения (healthchecks из `docs/drafts/alerts.md`,
gitea runner и т.п.) — отдельным циклом после миграции.
---
## Откат
Если на target что-то критично сломалось:
1. DNS возвращаем обратно на старый IP.
2. Старая VM в YC жива и заглушена → стартуем её, поднимаем
сервисы (`docker compose up -d` под каждым пользователем).
3. Изучаем, в чём дело на target, лечим, повторяем cutover.
Поэтому шаг «Заморозка источника» отделён от «удаления» — у нас
есть «горячее запасное» как минимум на пару дней.
---
## Открытые вопросы
На текущей итерации — нет, все ключевые развилки закрыты:
- ~~Auth для cr.yandex~~ → OAuth-token Яндекса (`yc_oauth_token` в
vault, `community.docker.docker_login` в плейбуках).
- ~~Инвентарь~~ → два отдельных файла, после cutover `timeweb.yml`
переименовывается в `production.yml`.
- ~~Регион/TZ Timeweb~~ → совпадает с текущим.
- ~~IP-whitelist в конфигах~~ → отсутствует, смена IP безопасна.
- ~~Объём данных vs 80 ГБ~~ → 22 ГБ всего, из них calibre 16 ГБ;
с запасом влезает в фазе 1, второй диск не на критическом пути.
Возможные вопросы по ходу реализации (выяснятся в процессе):
- Конкретная процедура получения OAuth-token Яндекса (через
`oauth.yandex.ru` или через `yc` CLI).
- Поведение Caddy при первом выпуске сертификатов после смены DNS —
убедиться, что rate-limit Let's Encrypt не упрётся (≈ 17
поддоменов выпускаются сразу, лимит LE — 50 сертификатов в неделю
на registered domain, запас есть).
- Federation Matrix после смены IP — обычно достаточно того, что
apex `vakhrushev.me` отдаёт `.well-known/matrix/server`, но
стоит проверить через `federationtester.matrix.org` сразу после
cutover.
-12
View File
@@ -1,12 +0,0 @@
#!/usr/bin/env sh
# Must be executed for every user
# See https://cloud.yandex.ru/docs/container-registry/tutorials/run-docker-on-vm#run
set -eu
curl --silent --show-error -H Metadata-Flavor:Google 169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token | \
cut -f1 -d',' | \
cut -f2 -d':' | \
tr -d '"' | \
docker login --username iam --password-stdin cr.yandex
+5 -3
View File
@@ -26,9 +26,11 @@
loop:
- "{{ base_dir }}"
- name: "Login to yandex docker registry."
ansible.builtin.script:
cmd: "files/yandex-docker-registry-auth.sh"
- name: "Login to Yandex Container Registry"
community.docker.docker_login:
registry_url: "cr.yandex"
username: "oauth"
password: "{{ yc_oauth_token }}"
- name: "Copy docker compose file"
ansible.builtin.template:
+1
View File
@@ -54,5 +54,6 @@
src: 'UUID=3942bffd-8328-4536-8e88-07926fb17d17'
fstype: ext4
state: mounted
when: mount_external_storage | default(false) | bool
tags:
- mount-storage
+5 -3
View File
@@ -38,9 +38,11 @@
group: "{{ app_user }}"
mode: "0600"
- name: "Login to yandex docker registry."
ansible.builtin.script:
cmd: "files/yandex-docker-registry-auth.sh"
- name: "Login to Yandex Container Registry"
community.docker.docker_login:
registry_url: "cr.yandex"
username: "oauth"
password: "{{ yc_oauth_token }}"
- name: "Copy docker compose file"
ansible.builtin.template:
+1
View File
@@ -5,3 +5,4 @@ ungrouped:
ansible_host: "158.160.46.255"
ansible_user: "major"
ansible_become: true
mount_external_storage: true
+1 -1
View File
@@ -60,7 +60,7 @@ def pl(ctx: Context) -> None:
raise Exit("Укажи хотя бы один плейбук: inv pl -- <name> [name ...]", code=1)
playbooks = [_resolve_playbook(name) for name in names]
ctx.run(
f"uv run ansible-playbook -i production.yml --diff {' '.join(playbooks)}",
f"uv run ansible-playbook -i {HOSTS_FILE} --diff {' '.join(playbooks)}",
pty=True,
)
+162 -174
View File
@@ -1,175 +1,163 @@
$ANSIBLE_VAULT;1.1;AES256
30613937343031343632383733623435366535373231316163393436363636656462326262383565
3032663665323131626263356531633934326639636231620a363635376263333438336331343366
36386337323165333861633062656433313062343764636138663533333639316336306230653732
3331336137616263630a306135333566646434663231383138363966386661643836626561376338
33636362323937386664646630383062613535666431393634316337626564613733313861386238
39316263666662633066633836366236346431313531656339613566303962656165396662326563
65623036333932393739646162353836646562643866396263386232633933326538316637656365
38656562383861613030306635613236646235613436316635386531656666363738396461313263
30653934366537303133613962653137633131323431396266646339376339623034373963666438
66383464303431353962323032316533613138383831343036383230303931326433396333623935
37396432376637373135666236333332383262323931616432343665653836626265376632643765
33303835393863333334653664613337343063313362363136383234666335636565383237656639
34323839613765626231303230616661626530633530333165373535663139643339656438396237
36656134643636643733363336343739616532666130393863666665393138383261353730626565
38306133343463306462656534326431623238336562653433316233383861303032393437316336
32353338613639653735393239333235633565636563313933333763323339656237326162316465
66313034306263343462376632303539656533353265336366613338326439323732623438626162
39393937613836656236383030343436303632363330313734333665643365666138633034323462
66376333386237383666623434636662363338626538353933636632646236393630343739636666
37666531633839363365633863646530396432613166313035353638313463373338313139616133
62383234356665333132613664383931316238353863306538343831363233303862383737373939
37303430303766343366633536643139363366663734326162366434333165613033653666383337
62626538316463343466613065326666396266643661656164376336336532666134613663623163
64316335633839356231393130343938613334393737666663363662356466326235666561653239
39386635616165633063383032666366383861333038373636613663613461316433633562623664
65373536663230356632663133356639323838653431333836376330316162633261333934363335
34383937343063303835626435316534356239316230326566383036646237336238623036323161
62326264636130323965313866616631663039623431363139363462663435323866393437373566
37353463353731303434303435353061633531663464656336306439373238633038343237313133
34333463626261333038363438343034373335346332316430376436656331626664376664323037
32393631663434383265326231353035356333343739386132326435653438306136373237396539
33613462623562343966343933363037326234323836363636313938666534333337646139326533
61633666623936646366643336333339303633643230393465623031643963643635313264353236
65313631663430336262326463663938386630363464386230383766376363373235366438393635
65313232383334666263626662646264393565326164613364313138303638653333653963316561
34346464653637376433356335663930396432386238366132393562393162353235393438633533
62656533316431666463633530653832356263653030326366663932306662613465643638313633
62396562616463313066343832316238386234343537346436623039643132393562303130613331
38653261353132633036623138643338366534396237613333333765653436363032616235373035
35313966623531373636363638383862333935353931653861663966643531383335653739356565
31373937396234616135653765643131666530383030343064366531336135366265633232653433
65396566626232633831343734353432633462343336616135373861303836613463393736306133
64663531643630326432376235386433623365373163366663623632333531623863623663643434
36646134623665633531643732663137613862343666613139336231646564363266343935653263
66653366666635666535636637626134363633336233613732656166373063333237323465616434
39623238636235333866666536346430373735323530633133663937636366663530326465386161
39346466386133656633373438333133303566363233626238366133636333656462373065613863
65363439383163323332383931663833303234326132343462333835323664363461656566393065
38646261323336316239363465343238643132306235613031626438323838653066376561626661
34356530666665323230646436633935343861323638656638323163306236393865366630636236
62386161646131623738333664636361396239643666323837646332383538623734386531313664
65313632343365393130643137353735666565663030383231616231313237323866386336316361
66656165643261653464316639613635323531306362353164373531326461666437303434346233
31383864346233313633353065343236633636386138323761666662373564623234613965323131
36313861316563333262306434663265313237626631396561303236343330633738356666633663
61313663336237653361383963333764336137396666613634313036373564353564643334623363
62353531306532323664376363383938646536393339346666656339393230613362666337663861
37633633653463343430666634643863383438633933343839663865616136363538643061343437
34613037353835613866303230303162396531626663616164343263633261363335313936666339
63383533616530356262363838636466333038656339316364626263383731313464313734613630
62646266666136616632636161363631623362346230643134663664396565323932343462383661
38303663653262333236613833396237663834333139316666343065396137306562613265343863
65323065663862636230636664623132306231366462346432343030376236346465663831623537
63633231333165613731626137656539366131633364623661616136616434306563656139346137
63313032343161623235306230633361666163623061333738383135636664623438323238663631
37613964643931323432353431306564393639386437666539376238643065343738313265373661
61303764646463326632653335323432646436353765633862623838386337623464333839643833
39383961666234363638323735636231623962666461373435633631323530643237656464396465
66623431393461613634373237646636333965396435663563363161626666356638366462373261
37633238323135666136623663653665303832656437663536383236313334313461353032663933
63643164363664663939613635373362376162336262653332663936313737396130366330656532
31653463383132643262613839613962663836376463343661393736633633396164643264653431
30663732303236653165386537653432656266363239373030333630353661666636303730373937
65363237366333376133306437376534636133356238326461333762326563386265363636323831
39343665386262336265383865343563343832623766656534306661326462333561373835366631
39636361623831623533353962633363393531313530363833613962616331653565633733303964
32393433303938323566646264323761633035653231353761643261663839313665663434643834
65356432393431336235306437643861653437643362363839623634333835376636623664616139
66376562633232636431626436653161333137633466313433663433383230636337653535643430
61613032656135323765613837626266313632353661346636643866613138303930346563623738
35613831623565353432336338373465303437623234313736353661353430656661366365373230
33646134356661616164303865623464306339653439613365626261323237623135346537393535
62393465343134626333333462316331656134383362383031353863316632393061333933336362
36326662363833303436663166383365346433323866346462663261333330656666663162383564
35336438643064313833393638323864343237616163383033313966303262326135323335353931
66333938393264323533353231303935346661653835386262306133393065356535643835663665
38363930356530366135313734306464623739376438613430373634396339393864396264303135
61356333636236326566386264353930626564636438616265353939383733663837313233356363
63643835393437336366313030303864306536666638623430356263336234646462383666316431
36313464346266646438383762313138376338323537386635636561656662306533316362396162
38326165633532623933376165643861323735353831363264376162316561613038633961333337
30646461636332623466643033633764333330353832616365376633643263336131313733653139
39646239366261366465333962643565636430393464613866613038333636393362383636343534
61323830616234633364346131336630393965373730343464366166376232346464636263323639
63336464623733363139366665336131653163613833383261376138373032666663356637383832
32313130633363346435383638616236633761616166663339316437353938636636613530383836
64623661366130656439306266343435396334383564353466663339383862313733313931383463
64323237656361383262343735366562623965356636343963363966616333313333646233373464
33383939386262663730316333616663636161356463396362643237356532386162363131626461
37323965313063623463356133626531393339336535303562343530316663613639646531323136
30353732646237623264653963373863363965326338666264306562373932393333633639396131
34303764396330326165636264313532393961303038623031336631653831323337306261333630
37333964376533636132303335653935343932373330373632626235356437636165623436383036
38313565373561393834316532333930356135623439373161643063643738353031353565396330
37656162346433326638353439613666336534336562623633643230636134383931653538616665
32393432383265613237323138386361353934373965306462393666616532653563626232643035
61643732376434633537633663633130313437656166333239633533393334373163333566343430
38633165353637306237316436663235633162353132646562353638333038663636323465633632
34643037623634643534663366633133363030323966313065353333633636646636306565333238
39336662626138306464613461343762316533656433626165323764616535623539336439396663
63393365626235613063613934306132333162646237316364306637346136623061363236383765
37353138363337346530626563366136333635663863313038643537366237633362343136396664
61623237353433333238633163636565386134356565303763336238636366316330666339383365
30323235356633656362353738393234616435663333613364316539636430623262643162313337
36323466303832336530336566343731306362333862663537613339663562623739343636613162
36343563373665376565366266343461643562636630623166626165636337613931653338633862
65393138353661656265666335343263333063653430326532663839383433643966363639643636
61376365363538636235666235623638376334363265626136313536353637386564303936636263
32306239306339656238393864666135613663366332666135663461353366313833376430376263
62613163303964333735396338373737653837666435656130376435376434356462383264636561
34353563316132336663316166663832383939333634316562383634383838336531313731613666
62643231636266353935343539366465376139643834306261623738313432306133653461383738
39396332373364353833626661333634346131396337636235653431616336393666373231383030
32306466613136346265653038636537646330643337663863383562323638616661333037323232
35613138363330353533643064613366343339343032373737306364353135353334336666663732
36343963613636376561666266623537316432666161326331383761323437383738373762643937
30623737643239326261343939663065643265653363633661376265626637643336613635393335
65373565333936333431656331633039323135336236656337343532643939386338663239393065
65666536333732646235633762633032393463663334616165333834653938346230316236353839
34396362386265646261373561636230363962663433303535373035346334353932643365383763
32333239613961346466356562376663613062373162666264633636323833323263333765616563
39646530343962353362363634336336323463623137646531373362353832343335366461646535
38653735316536396438613866326438363036653833366636626130323437623366373833366165
30303636666263323062343931306435363961643838636163366433376436303231316338613034
37393631363632383461373566306365306631396335633432383939336332626237653462393136
34316636643464363634366535333463326533333564633163363062666463343731396231656234
39346333303465363037313063373366373439306333636465636366666437326362626264653033
64613062343538303931646630373565663530336133633032366331626536353237336235633636
63366639366439386530303966323563323862383865356630313636333333393464653762626634
65366231613661313233626239303035323666346236636362393036353839333636343434646266
65653039353966616361363335346565383863616161316134383365616636333732653233383261
65616664343830353861616666616237313532363334653430313437313535666436383338396363
33313436363061306431366332373936633034393733646137636338336431333033343532613531
32613839393232646565663931303530376432376337613762346230646366613935383234313666
61643339353933336434666466623133336637343534303737366162316561366632333335663233
34643036326630306632353438643666623939393033646238353261386231626634303266303530
64643436653234616332623835333165626135613465346162393335353133356233666536313632
65616135666533343839666132623639343565303436623162383738353633613864356535646365
32373337393936393830666365383462333437373539666633386361373135333163393334303235
33663631386566356366666132616265373533373561616564343538303432346562356234336663
32623866396434326264636539323132613239343938353739376539383139313833376563623434
62636334326234313230666662396561393130396137306437393334323561356435343866386636
32656337656439653830653365313031326562643437376538316561653963643232353434313538
64373638616133666463393462643465306565646136643862363162643638343565316139626539
64643939383936313035323936656438313039376635383733633032613165343130663930323166
37343261333332663863366533386335373962323163616564376434636361356438393035656533
30383139323931306232353664636662313036643431663536353035356139643761613235663837
37613133363433356536316466343237613131386536356234343135323861396130663464323236
63636563633031396465663563366263373938373531336239323138653531386535643332653736
61396432356161643663623130656632633862333861656464613432623732656465376236313437
65386630633036636663303633636134343739366562643062343030383138653466326636366266
32633239343039633636313837643432333238366533393061646237626130303934356438633936
38366265656365333338363431643432633463313438633361333764653637623964363732303737
61343137653930353361653364656233343166633162313964306531383834356237343031396137
34366135623530366164646532643636346233353563333031343931643037613463613639356238
36306235336562333935643035313934366339623365616661616461653832336137336464393662
38363433646139646633353162616661323433636531393339643562373538616430363061366330
35333138613136323865346462653761666534343538313033663835653631363631623532663133
64383135326333626438363066633366316364643332623030653230353861633837646362626333
38363236616265313638626263316164323563616237653465353031353734333032323761393761
31333331643161396338653330653537353634306139656536363665643437633433666236356334
64386566623836306666653766626465646664303231613062663862613565393364303233333636
61633631643437373235636133333832646463366633353939383834373362633539333766303661
3132333365333061633665366432346636646564313437333061
37316466386337633433633632333065613137643963356437653030653433353464666636383238
6131616231356633383839653530666333343666316363630a323736656238336633336363656634
35633031623730613966343533393033386538346162333061613135353063323533336135376337
6262346462636361370a356336616330323738383535356238383238656366303833313137613461
39613266616632376433636263653332363661373832316538343530353963363762356534373264
38316134643632386564626662616235316563343833646164336439623035303838316630393030
32636566663835626362666235363432316430633139333935383031303564353633323835616530
39353336366634356530393339633836613563666435323533323731666666656339313431613161
63386639663635393339643965643664303130633234333130663336623738313665366630316539
39373930316434636261646431633666653066373632396136313639633266383036643739323936
63343836666263623966333364616230393338386439616264616437373739373366326430363038
61333237323139336236333366666164313364366663333335646139353061323362313465613865
32376234323936336466356239666162323330323133616166333733613566636635303965316665
61616135333964613862303432323032313533353132303762396539323033636636643539663334
36663866313130303531333961373337316462376564396639656265393663373230623937376337
34656339323864633462633030333730613765653631636233356535643333356661333331303935
33646364636434336162356535393162353533636262333735616463623432653933316230306261
38353766363130333963613830643735663930353162343665323435613435326638363162356438
35666666343165323561396336646264613633323066613665346535313165306435666463353030
33626661326536646264343531653834616563316533386539656431353165633161666366386433
66623662303636393732643330333664373935313932306232306433653038623461393030306562
66663637636534393631633939363037303331383166343561373163666263323563313636326538
31656132376636653336353666316639623230613563333363653965333435323231303161333231
65353539633132373036346233626533356139373938646130376437626532383864623138613463
65373530343563613633323439636638316330663639306438323761306238616539353262326166
61323239666661366665343736323834633065306237643538383238616563353366343936663835
66313066653832636534346136373962653136303435623365376235626463366633623561313231
66343861363336303136666136363531386166316631643565383835333261356237373736386638
32653565623034303939383134613235353237393035336662303432653734636630373235303733
37303234333764303863323733356363353864323038393762373339303239656566363966363832
66373965333362383031636330613637656436653365303930316233396431323338636239333930
62353637333562343534663964636235623363313463613135373639653536636339376435313662
31616465643730336562373463353965616263366137316430303666346532633735333661616337
31303361633437663065326666633437383534366639396661623336643565383336623232666563
64376136303637393330363263666631336461396163373932346138653362326163666331336434
66356232666439633132363537626436613064663562333536303238313834303666363830353632
33633633373738383165363135323232313735393335646238326261643765613061623264343261
37663531396165653838346431666534636365373563366636383165376137396537353031653261
62393437633735366433613166356337303564313538633933633465363331316332656262303439
62383535383666383535326538356431313365663937313065363138636336666631643737343634
61396664383539316261386235643430383436396638303737366632306538636133666239616162
33373066623365353832666139633436343664333434393933646265323132616665623938643061
32663763316639333036343333373632633431373038326666323036333664333332313731313865
33363638616661363763313263623230663735383834353835613861373063666538363764333433
65316333643937366632313335643364333736633936316132313534373862646365323037363963
63353237373730623533633961323038616532643034643939353034383036326461383566346230
61363561666265393536323164323462356237373764306632383034646665613130373030383336
39353235353036323964356139363530636364346666663331643736306361653966636164613830
65353664643733383737663766613965616334356561376538623763613331343334613532386562
33623564636233663234313631623734333934303439383662336665623936643039623039303962
63336433333766376130356339303239626536383364373063663435653236386139613366323938
34653161313061333837373164653238663539326562633237616134623366643039373033353331
31633436323463623661656636383964643362613130336537653731646239313365633662656132
35393262613830323964346662666664346664316662623865663236643065333533313135643063
37343461316564316666343465303539383463613663306136653465383336353232366238316332
31313837323533313863333565643762653362633231333436396238666562363139626336666532
38326331653736393031636264316330643532353862366235613334383266613238326431343066
33666566613061363465376637636636626432383132636330343736653034643965396566373064
37303830393531383131646161613335666631633535383739626130366464313930343262393533
66373137303531623663643339643233373737303831323735623561656165383034373131386639
32333838663264366661616262373439323433386538373261373465343163326438623962333566
30376463646631663330366364626133386131313261396434656336636566323234633864323731
61393864666537656338646462353764356131303836333762333932643130346439356432323231
65373866393263313236353937363031366661666664346362313466356366663336383761623637
66343938303839663630363562353634353938643638373335636530323430656336303963666662
63643031396133646531316162323836616461373863643463663132383764363034663562363365
37383234663939646539643562376532323936616264326533333637323633373330643162623537
63626138616165343938643237313562616234303665303635653830343735326661666235643333
36623333343365613161663663356566656533363736313231336536386339326336373730643630
61386139313064376161303066323162316564313761303734376663653464323138303339323763
35373930366262373936356464613731396136613634663562373737663739613562373333386265
35323161316633373430633833663266633030613430613635356135393161653836366464326236
63373738366661333964623962363035636131656539613139373138643839643138373564333430
34623932396134343762393334373033663132306636633864643235373861386633366639316566
66623439613730353237366366663161363030646663323836653638383330323164333461666334
62333662643631373361643263646537323239643234346333333261623933626166333630356662
61333362313230343134343331303031303634383335313339303363383930373136333336313062
39626532646365663734313462343766333962333266363965373630363831626633623830633733
37633666663361343639366663313237633161353062396337393138613261383935353730353932
32383066386636346433306662326130633662363135313331383530346632356333363238383266
61303138636664303637326137376638333266343132356237383432613238333632356163643232
32333966383164343064643934333765386136323736303138326635663561343964393934316433
37663763653034363039636436346630303664346237616433376462303463633930383430646430
38343762663635373033373135326361393935386566363031366630366561663431333461663332
62393538306163386139656431353235343265373033663530306163316464373432353661343439
35356634323966646263613937663234613431316333656330343765316163626339376430343064
30363335633337623139343464656431353738623230633137633130353430373435303963663333
66646563626336393538613134636162643864316163626361663663313131343063643336396532
36333336386130653537653637653163343031376436383763386163376336623837336365616530
66386262363633353465663664326537343037383838333863366662306131303364626538623763
30376132613861636539393138326131356333613837336365383862383362303233633333396238
34663163363234363564336338656366313931353262303032343334303864386337356437633361
39383366386334303039313439376236663533633035326662626466333539626261643466653966
66383862663635346539396139323138613264373436653338653136366135613962373562373932
62316134636636373264623365613636373132396433373630643531633536333130646130373963
33356538316334336236346532356263363933633933376165666563313161343638656365373137
61656438313063363661316537336538646233326564633934303766383738343130373737653961
32326462626435343363326366663364376431613837646365383933396437383138333665383832
33376563363466333637656233643137333636326535653335393931653063623937373836313863
30363238356466363461313633306565646336633366346461333032643635366336646365643761
37363433623037333838663731376663306134363665383835333164643131646632393639363538
32656539643461653861376436396130316166616265336433356663343235333763636564326464
32663530333030306130366532636439353264303137613366353266363664346433666565303135
34343835623936643631643166636339306231323138356565373663636665303264653161613539
65336230633035336464373431663130383131376430306136333634376435393933623238653136
61633138653339336631313932353264366334616630363464373131376538303630663566653061
30386665383864383838613835613337633937616437376363626263376634643061393937653430
38626539383634616536396665396262633666376438363930383635353936623534353131666333
31616536383662663666333265623032613936383063303636366362346337333337633739393665
62306639376466636161333233656239313964373239336134646434613166646534656466356431
36626162383866643633323938646130366261373366353764646234653236323166363463366435
65636137643639353039616138383265633832373864643438366333393031326363333461376138
65303838626435656634626236663338316632613664646463306561663532353062643637663866
32663236633630383365663235313337373837396531326532333632343736343035643733333262
61633532323330623338393838626333323932376630316239303335313765343039313332316336
32303638356539333136353163396536313830333264613266303139376465613166363730343635
37623532356135613831363137356238373863653933366532346532363130386363616133373764
62353066376631343934356337326465613436363361646431373962656639393566323731316136
30313730386438663261636562643061333664303636373732663464356530336333383739313566
35616566663065313732656532346330636335383664383061323131306530623632353132326432
34313365396430396338363063376534363332306432313531303261353736353138383733346363
37373063666630633632613039353763626165613038616464363333383662356363373133623261
61316130393165633434623334616239666137653831636131393732363330363039306666346363
38353832356138383732306533646133663865663062663862663232323038383532616230386265
38626535313931386533613936343637373064666663336436326464313834333334366264336130
37346438383739313735636139343638373063306162313230366364316136333137653939313032
63303035663465653062613764333434323234323332316365333165383533396430316537623164
37356138623233656436663738393966346662646339626137663533616234653535663663383738
37323564653664653530653533396332333664636261306339663830373730613832363565613363
63333635646339656535366365633638393164663562633562383162373932306233313164386331
37613036323664316237336130366138333863316563666337333464383932353363306231336338
65393537636162316637343262313333386532616632376563333039386131336230316266396265
34626630633038623462346365316438316238663163346136663436393736623464643961303339
35613565393633373861626239663563336633643565383833373632353737393838666161633462
36316634313530663032366430656131373838616161613265633936633930646137646661653738
31356266666162643736363538646464363164633434643161643239333336326262386135633165
65656334313632336138393364356664386237313936626333616638663666396530623464613665
36636537396565343864353465633638613439663663633766356331333036313130383432366136
66383333643938613831386631386537353166653561376161633431306531343033646233343731
34363164316463633133616230316433373966343031353733343762643032356264646631323236
33393231333330303339613634643333346134643035653330343230663035343230626465386339
36323030633037363462343130343333313538393163393263633339323133656239366161323837
30383231323061323538393063316666303534663664623464383634613834326237363532353165
30356561643738373537343831663461623839303363366266356663666664623030333936656436
39616261626464343039623161343234626566623537396264366439666438343739306361383666
33326433383830313933383030393235306261323462323265623862363861653331643736363336
36373561363566346232613631653464613034653438343462383432643962626665303634646230
63656261613463623466393064623534306363663835616138626332356430633165363366343461
66623133666461313934346365623434653433373734376632616435386636613166633935376337
61326437333132306637663436663930396461393131643766643362316434346438316630343833
31343638383339646436363238393634636363303861383266313638623663303732333231316366
37653861663737656631666535383366643265373135396236393936353264613765656534313430
39663431623737346462303035613162393939623239326232326330636562326236323830613238
32303263333864616434316361646162623163306262646131346135613162353833366537666532
39326430306331663736363365386264653634613530376639313336613939363135656638353632
31656430306363396366306539626436356436306233303032623265356337303666363965353964
62373031336438326635386632313365336236303737373832366363333634383835306231623065
62646236643264373036303465663838366565616530306463613563613463323735646430663566
34356263353637386163316639636334616663303935666530396333653633343861393335313966
36383139383835343937393934316330623338646663636466396436353739303464313161376138
61663033623031653361643735366562393431643236636531323061303033313966633838646237
3831653036383565396363663066316432636537376430376165
+11
View File
@@ -1,4 +1,15 @@
---
host_name: "rivendell"
primary_user: "major"
primary_user_uid: 1000
primary_user_gid: 1001
# Directory for all user binaries and scripts
bin_prefix: "/usr/local/bin"
# External disk for application data
application_dir: "/mnt/applications"
apprise_external_port: 8000
apprise_external_url: "http://127.0.0.1:{{ apprise_external_port }}"