openspec: change deploy-gotosocial — развертывание GoToSocial (compose+config+backup+docs)

This commit is contained in:
2026-09-06 19:09:50 +00:00
commit d385d8b42e
19 changed files with 1888 additions and 0 deletions
@@ -0,0 +1,47 @@
# GoToSocial Backup — Delta Spec
## ADDED Requirements
### Requirement: Consistent бэкап SQLite + медиа
- **MUST**: Скрипт `/opt/gotosocial/backup.sh` создаёт consistent-бэкап SQLite через `VACUUM INTO` (или `sqlite3 .backup`) — не просто копию файла
- **MUST**: Бэкап включает медиа из `data/storage` (локальные файлы)
- **MUST**: Архив собирается в `/opt/gotosocial/backups/` с именем `gotosocial_<YYYYMMDD_HHMMSS>.tar.gz`
- **MUST**: Секреты (`.env`) бэкапятся отдельным архивом (`.env` в git исключён)
- **SHOULD**: Remote-медиа не бэкапится (пере-фетчится) — по официальной рекомендации
#### Scenario: Создание архива
- GIVEN запущен `bash /opt/gotosocial/backup.sh`, WHEN проверить, THEN файл `gotosocial_<ts>.tar.gz` создан и непустой
- GIVEN архив создан, WHEN `tar tzf <архив>`, THEN содержит `data/sqlite.db` и файлы медиа
### Requirement: Копия на Яндекс.Диск
- **MUST**: Бэкап копируется в `/mnt/yandex-disk/backup/gotosocial-backups/` (mount Яндекс.Диска)
- **MUST**: Если Яндекс.Диск не примонтирован — скрипт сообщает об ошибке и завершается с ненулевым кодом
- **SHOULD**: Копия второго архива (env) также на ЯД
#### Scenario: Копия на ЯД
- GIVEN Яндекс.Диск примонтирован, WHEN скрипт отработал, THEN файл присутствует на `/mnt/yandex-disk/backup/gotosocial-backups/`
- GIVEN диск НЕ примонтирован, WHEN скрипт, THEN exit code != 0 и сообщение об ошибке
### Requirement: Ротация бэкапов
- **MUST**: Локально хранится 7 дней (`find -mtime +7 -delete`)
- **MUST**: На Яндекс.Диске хранится 30 дней (`find -mtime +30 -delete`)
- **SHOULD**: Ротация применяется только к файлам `gotosocial_*`
#### Scenario: Ротация
- GIVEN бэкап старше 7 дней локально, WHEN ротация, THEN файл удалён
- GIVEN бэкап старше 30 дней на ЯД, WHEN ротация, THEN файл удалён
### Requirement: Автоматический запуск (cron/systemd)
- **SHOULD**: Бэкап запускается по расписанию (cron/systemd timer) — например ежедневно
- **SHOULD**: Запуск тихий при успехе (без шума), сообщает только при ошибке
#### Scenario: Запуск по расписанию
- GIVEN настроен таймер/cron, WHEN время запуска, THEN бэкап создан (файл с сегодняшней датой)
## MODIFIED Requirements
(нет)
@@ -0,0 +1,73 @@
# GoToSocial Deploy — Delta Spec
## ADDED Requirements
### Requirement: Инстанс GoToSocial работает в Docker на bigbox
- **MUST**: Сервис GoToSocial запускается как Docker-контейнер из `/opt/gotosocial` через `docker compose`
- **MUST**: Используется образ с фиксированным тегом последнего стабильного релиза (например `superseriousbusiness/gotosocial:v0.22.1`)
- **MUST**: Контейнер имеет volume для данных: `data/sqlite.db` (SQLite) и `data/storage` (медиа)
- **MUST**: Приложение слушает порт внутри контейнера, проброшенный наружу (например `8080:8080`)
- **SHOULD**: Рестарт контейнера настроен (`restart: unless-stopped`)
#### Scenario: Контейнер запущен
- GIVEN контейнер запущен, WHEN `docker ps`, THEN контейнер `gotosocial` в статусе Up
- GIVEN сервис работает, WHEN `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:<port>/`, THEN HTTP 200
### Requirement: Split-domain — host social.dedinit.ru, account-domain dedinit.ru
- **MUST**: В `config.yaml`: `host: social.dedinit.ru`, `account-domain: dedinit.ru`, `protocol: https`
- **MUST**: Не менять `host`/`account-domain` после первого запуска (необратимо)
- **MUST**: На apex `dedinit.ru` (Jino) добавлены редиректы:
- `/.well-known/webfinger` → `https://social.dedinit.ru/.well-known/webfinger`
- `/.well-known/nodeinfo` → `https://social.dedinit.ru/.well-known/nodeinfo`
- `/.well-known/host-meta` → `https://social.dedinit.ru/.well-known/host-meta`
- **SHOULD**: Проверить редиректы после публикации на apex
#### Scenario: Webfinger резолвится
- GIVEN инстанс запущен, WHEN `curl -s -H 'Accept: application/json' 'https://social.dedinit.ru/.well-known/webfinger?resource=acct:estorozhenko@dedinit.ru'`, THEN JSON с actor
- GIVEN apex настроен, WHEN `curl -s -o /dev/null -w '%{http_code}' -L 'https://dedinit.ru/.well-known/webfinger?resource=acct:estorozhenko@dedinit.ru'`, THEN 200 после редиректа
### Requirement: TLS через Caddy на vps02
- **MUST**: Caddy (vps02, host net) имеет блок `social.dedinit.ru { reverse_proxy 10.8.0.2:<port> }`
- **MUST**: Caddy выпускает Let's Encrypt сертификат для social.dedinit.ru автоматически
- **MUST**: GtS доверяет прокси: в `config.yaml` `trusted-proxies` включает IP/подсеть Caddy (например `10.8.0.2`)
- **SHOULD**: Header `Host` и `X-Forwarded-Proto` передаются в GtS
#### Scenario: HTTPS работает
- GIVEN Caddy настроен, WHEN `curl -s -o /dev/null -w '%{http_code}' https://social.dedinit.ru/`, THEN 200
- GIVEN сертификат, WHEN `echo | openssl s_client -connect social.dedinit.ru:443 2>/dev/null | openssl x509 -noout -dates`, THEN даты валидны
### Requirement: SMTP-отправка писем
- **MUST**: `config.yaml` содержит SMTP: `smtp.jino.ru:587`, STARTTLS, логин `social@dedinit.ru` (пароль из `.env`)
- **MUST**: Письма (подтверждение email, уведомления) отправляются через SMTP-сервер
- **SHOULD**: Проверка отправки тестового письма
#### Scenario: Отправка письма
- GIVEN SMTP настроен, WHEN администратор инициирует отправку (например создание пользователя / тест), THEN письмо доставлено на указанный адрес
- GIVEN неверный пароль SMTP, WHEN отправка, THEN в логах GtS ошибка аутентификации (не тихий fail)
### Requirement: Метрики Prometheus
- **MUST**: В `config.yaml`: `metrics-enabled: true`
- **MUST**: Переменные окружения `OTEL_METRICS_PRODUCERS=prometheus`, `OTEL_METRICS_EXPORTER=prometheus`
- **MUST**: Эндпоинт `/metrics` отдаёт метрики в формате Prometheus (Go runtime, Gin HTTP, Bun DB)
- **SHOULD**: Prometheus на bigbox скрейпит `127.0.0.1:<port>/metrics` (добавлен job)
#### Scenario: Эндпоинт метрик
- GIVEN GtS запущен, WHEN `curl -s http://127.0.0.1:<port>/metrics | head`, THEN вывод содержит `go_goroutines`, `gin_`, `bun_` префиксы
- GIVEN Prometheus настроен, WHEN `curl http://127.0.0.1:9090/api/v1/targets`, THEN job `gotosocial` в статусе UP
### Requirement: Логи в JSON для сбора
- **MUST**: `config.yaml`: `log-format: json` (для Promtail/Loki)
- **SHOULD**: Promtail собирает docker-логи контейнера `gotosocial` (label `container=gotosocial` / job `gotosocial`)
#### Scenario: Логи в JSON
- GIVEN контейнер пишет логи, WHEN `docker logs gotosocial | head`, THEN строки в JSON-формате
## MODIFIED Requirements
(нет)