diff --git a/.gitignore b/.gitignore index 01c0688..8465862 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ .pytest_cache/ .cache/ .env.production +.maintenance.lock .venv/ node_modules/ dist/ diff --git a/README.md b/README.md index f6f5187..c293c9b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Production-контур для домена `rf4spotter.ru`, TLS, секреты Политика минимизации данных и ежедневная dry-run-first очистка описаны в [`docs/data-retention.md`](docs/data-retention.md). Host-side мониторинг контейнеров, readiness, диска, резервных копий и TLS описан в [`docs/production-monitoring.md`](docs/production-monitoring.md). +Ежедневный systemd timer создаёт проверяемую копию до retention-очистки, а production Compose ограничивает рост JSON-логов контейнеров. Фактическое состояние DNS/TLS домена и серверный чек-лист ведутся в [`docs/deployment-status.md`](docs/deployment-status.md). diff --git a/compose.production.yaml b/compose.production.yaml index 8df3afd..a006329 100644 --- a/compose.production.yaml +++ b/compose.production.yaml @@ -1,5 +1,11 @@ name: rf4-spotter +x-logging: &default-logging + driver: json-file + options: + max-size: "10m" + max-file: "5" + services: proxy: image: caddy:2.10.2-alpine @@ -23,6 +29,7 @@ services: condition: service_healthy networks: [edge, backend] security_opt: [no-new-privileges:true] + logging: *default-logging deploy: resources: limits: {cpus: "0.50", memory: 256M} @@ -43,6 +50,7 @@ services: retries: 10 networks: [backend] security_opt: [no-new-privileges:true] + logging: *default-logging deploy: resources: limits: {cpus: "1.00", memory: 1G} @@ -63,6 +71,7 @@ services: retries: 10 networks: [backend] security_opt: [no-new-privileges:true] + logging: *default-logging deploy: resources: limits: {cpus: "0.75", memory: 1G} @@ -106,6 +115,7 @@ services: retries: 12 networks: [backend, edge] security_opt: [no-new-privileges:true] + logging: *default-logging deploy: resources: limits: {cpus: "1.00", memory: 1G} @@ -128,6 +138,7 @@ services: retries: 10 networks: [backend] security_opt: [no-new-privileges:true] + logging: *default-logging deploy: resources: limits: {cpus: "0.75", memory: 512M} @@ -143,6 +154,7 @@ services: - ${BACKUP_DIRECTORY:-./backups}:/backup networks: [backend] security_opt: [no-new-privileges:true] + logging: *default-logging networks: edge: diff --git a/deploy/README.md b/deploy/README.md index fbd7969..6d7d69b 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -109,6 +109,18 @@ docker compose --env-file .env.production -f compose.production.yaml exec -T api Сроки и состав данных описаны в [`docs/data-retention.md`](../docs/data-retention.md). Автоматизацию включайте только после проверки dry-run на рабочем наборе. +После ручной проверки установите ежедневный timer. `maintenance.sh` использует lock от параллельного запуска и применяет retention только после успешного backup: + +```bash +sudo cp deploy/systemd/rf4spotter-maintenance.service /etc/systemd/system/ +sudo cp deploy/systemd/rf4spotter-maintenance.timer /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now rf4spotter-maintenance.timer +systemctl list-timers rf4spotter-maintenance.timer +``` + +Шаблон рассчитан на пользователя `rf4spotter`, каталог `/opt/rf4-spotter` и наличие `flock` из `util-linux`. Пользователь должен иметь доступ к Docker socket и каталогу `BACKUP_ROOT`. Результат каждого запуска хранится в systemd journal. Docker JSON-логи production-сервисов ограничены пятью файлами по 10 МБ на контейнер. + ## 8. Мониторинг После настройки DNS, TLS и первого backup выполните: diff --git a/deploy/maintenance.sh b/deploy/maintenance.sh new file mode 100755 index 0000000..0b4e22c --- /dev/null +++ b/deploy/maintenance.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -eu + +repo=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd) +cd "$repo" +env_file=${COMPOSE_ENV_FILE:-.env.production} +if [ ! -r "$env_file" ]; then + echo "Production env file is not readable: $env_file" >&2 + exit 2 +fi + +backup_root=${BACKUP_ROOT:-$(sed -n 's/^BACKUP_ROOT=//p' "$env_file" | tail -n 1)} +case "$backup_root" in + /*) ;; + *) echo "BACKUP_ROOT must be an absolute path" >&2; exit 2 ;; +esac + +exec 9>"$repo/.maintenance.lock" +if ! flock -n 9; then + echo "Another RF4 Spotter maintenance run is active" >&2 + exit 1 +fi + +export COMPOSE_ENV_FILE="$env_file" +./deploy/backup.sh "$backup_root" +compose="docker compose --env-file $env_file -f compose.production.yaml" +$compose exec -T api python -m app.cli cleanup-retention +$compose exec -T api python -m app.cli cleanup-retention --apply +echo "Daily maintenance completed" diff --git a/deploy/systemd/rf4spotter-maintenance.service b/deploy/systemd/rf4spotter-maintenance.service new file mode 100644 index 0000000..0ac3bc0 --- /dev/null +++ b/deploy/systemd/rf4spotter-maintenance.service @@ -0,0 +1,13 @@ +[Unit] +Description=RF4 Spotter backup and retention maintenance +After=docker.service network-online.target +Requires=docker.service + +[Service] +Type=oneshot +User=rf4spotter +WorkingDirectory=/opt/rf4-spotter +ExecStart=/opt/rf4-spotter/deploy/maintenance.sh + +# Backups can take longer on a growing database and object store. +TimeoutStartSec=2h diff --git a/deploy/systemd/rf4spotter-maintenance.timer b/deploy/systemd/rf4spotter-maintenance.timer new file mode 100644 index 0000000..93ba635 --- /dev/null +++ b/deploy/systemd/rf4spotter-maintenance.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Run RF4 Spotter backup and retention daily + +[Timer] +OnCalendar=*-*-* 03:10:00 +RandomizedDelaySec=10min +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 47d54f8..43aad87 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -58,6 +58,7 @@ - [x] Добавить health/readiness-проверки PostgreSQL, MinIO, API и импорта; отразить их в Compose (`/health` без зависимостей, `/ready` с компонентами и режимом обязательного импорта). - [x] Добавить host-side production monitor контейнеров, `/ready`, диска, свежести/целостности backup и срока TLS; подготовлены systemd timer и runbook, реальный канал уведомлений подключается на сервере. +- [x] Автоматизировать ежедневную цепочку backup → dry-run → retention с блокировкой параллельного запуска; ограничить Docker JSON-логи пятью файлами по 10 МБ на сервис. - [x] Добавить структурированные JSON-логи без пользовательских секретов и персональных технических данных (whitelist полей, redaction, request ID; Uvicorn access-log отключён). - [x] Добавить Gitea Actions CI: backend tests, Astro check/build, E2E и применение всех миграций на чистой PostgreSQL; сохранять логи Compose и Playwright-артефакты при падении (`.gitea/workflows/ci.yml`). - [x] Добавить отдельный тест полного bootstrap: пустые production volumes → миграции `0010` → seed без демо-уловов → readiness → браузерная отправка и проверка moderation API (`deploy/test-production-bootstrap.sh`, 6 сентября 2026). diff --git a/docs/production-monitoring.md b/docs/production-monitoring.md index 5dcc439..b98676c 100644 --- a/docs/production-monitoring.md +++ b/docs/production-monitoring.md @@ -28,13 +28,15 @@ echo $? ```bash sudo cp deploy/systemd/rf4spotter-monitor.service /etc/systemd/system/ sudo cp deploy/systemd/rf4spotter-monitor.timer /etc/systemd/system/ +sudo cp deploy/systemd/rf4spotter-maintenance.service /etc/systemd/system/ +sudo cp deploy/systemd/rf4spotter-maintenance.timer /etc/systemd/system/ sudo systemctl daemon-reload -sudo systemctl enable --now rf4spotter-monitor.timer -systemctl list-timers rf4spotter-monitor.timer +sudo systemctl enable --now rf4spotter-monitor.timer rf4spotter-maintenance.timer +systemctl list-timers 'rf4spotter-*' journalctl -u rf4spotter-monitor.service -n 20 --no-pager ``` -Сам timer записывает результат в journal. Для реального оповещения подключите failed unit к существующему серверному мониторингу либо настройте внешний HTTPS-monitor на `/ready`; уведомления должны приходить минимум по состояниям readiness, disk, backup и TLS. Не передавайте `.env.production` или вывод `docker compose config` внешнему сервису. +Monitor timer записывает результат в journal. Maintenance timer ежедневно сначала создаёт backup, затем выполняет retention и не допускает параллельных запусков. Для реального оповещения подключите failed unit к существующему серверному мониторингу либо настройте внешний HTTPS-monitor на `/ready`; уведомления должны приходить минимум по состояниям readiness, disk, backup и TLS. Не передавайте `.env.production` или вывод `docker compose config` внешнему сервису. ## Реакция