From e2d729d33968f277891deef18365ef892025d293 Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 14 Sep 2026 01:04:12 +0300 Subject: [PATCH] =?UTF-8?q?=D0=91=D1=8D=D0=BA=D0=B0=D0=BF:=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B8=D1=81=20backup=20=D0=B2=20compose=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B4=D0=B0=20=D0=B8=20=D1=82=D0=B5=D1=81=D1=82-?= =?UTF-8?q?=D0=BA=D0=BB=D0=BE=D0=BD=D0=B0,=20.env.example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker-compose.yml: сервис backup (образ из реестра, явный environment только с BACKUP_*, тома данных + backup-data), предупреждение про down -v. docker-compose.test.yml: тот же образ локальной сборкой, только локальный репозиторий, без расписания, BACKUP_VPS_HOST принудительно пуст. .env.example: новый блок BACKUP_* (пароль, расписание, хранение, сжатие, SFTP-пользователь fsbackup — системный backup в Debian/Ubuntu уже занят, ключ base64, адрес Pi для скриптов ПК); удалены BACKUP_VPS_KEY и BACKUP_KEEP_LOCAL/REMOTE. build-push: сообщения про третий образ (bake подхватывает сервис сам). #64 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013jBxs9nBCk5nBdTLzcGz91 --- .env.example | 42 ++++++++++++++++++++++++++++-------- docker-compose.test.yml | 24 +++++++++++++++++++++ docker-compose.yml | 48 +++++++++++++++++++++++++++++++++++++++-- scripts/build-push.ps1 | 4 ++-- scripts/build-push.sh | 4 ++-- 5 files changed, 107 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index 82f46cf..50bda92 100644 --- a/.env.example +++ b/.env.example @@ -90,16 +90,40 @@ IMAGE_TAG=latest #APP_MEM_LIMIT=512m #APP_CPUS=1.5 -# ─── БЭКАП (оффсайт-копия на VPS; читает scripts/backup.sh на хосте Pi) ─────── -# Полный бэкап (БД + uploads + achievements) кладётся локально в backups/, и — если задан -# BACKUP_VPS_HOST — копируется на VPS (Pi → VPS push, т.к. Pi за CGNAT). Пусто = только локально. -# Ключ бэкапа — deploy/backup/id_backup (в git НЕ идёт); его pubkey → authorized_keys у backup@VPS. +# ─── БЭКАПЫ (контейнер backup: restic; подробно — deploy/backup/README.md) ──── +# Снимки БД + uploads + achievements по расписанию: локально на Pi (том backup-data) и — +# если задан BACKUP_VPS_HOST — на VPS по SFTP. Шифрование, дедупликация, сжатие — restic. +# +# Пароль шифрования репозиториев. ПОТЕРЯЕТЕ ПАРОЛЬ — НИ ОДИН БЭКАП НЕ ПРОЧИТАТЬ. +# Сохраните его в менеджер паролей. Пусто = бэкапы отключены. Без символа '$'. +# Сгенерировать: python -c "import secrets;print(secrets.token_urlsafe(32))" +BACKUP_PASSWORD= +# Когда делать бэкап и проверку данных (cron: минута час день месяц день_недели; TZ ниже). +BACKUP_SCHEDULE="0 4 * * *" +BACKUP_VERIFY_SCHEDULE="30 5 * * 0" +BACKUP_TZ=Europe/Moscow +# Сколько хранить: последний снимок каждого дня / недели / месяца. +# Именованные снимки (fs-backup run --tag …) и страховочные pre-restore не удаляются. +BACKUP_KEEP_DAILY=14 +BACKUP_KEEP_WEEKLY=8 +BACKUP_KEEP_MONTHLY=12 +# Сжатие restic: auto (быстрее) | max (плотнее) | off. +BACKUP_COMPRESSION=max +# Контейнер помечается unhealthy, если последний успешный бэкап старше стольких часов. +BACKUP_MAX_AGE_HOURS=30 +# Оффсайт-копия на VPS (SFTP). Пусто = только локальная копия на Pi. BACKUP_VPS_HOST= -BACKUP_VPS_USER=backup -BACKUP_VPS_DIR=/srv/fs-backups -BACKUP_VPS_KEY=deploy/backup/id_backup -BACKUP_KEEP_LOCAL=14 -BACKUP_KEEP_REMOTE=30 +BACKUP_VPS_USER=fsbackup +BACKUP_VPS_PORT=22 +BACKUP_VPS_DIR=/srv/fs-backups/restic +# Приватный SSH-ключ для VPS в base64 одной строкой (как TUNNEL_KEY_B64). На ПК ключ лежит +# в deploy/backup/id_backup (в git НЕ идёт), pubkey — в authorized_keys у fsbackup@VPS. +# PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path "deploy\backup\id_backup"))) +BACKUP_SSH_KEY_B64= +# Только для ПК (scripts/fs-backup.ps1 / .sh): как зайти на Pi по SSH и где там лежит +# docker-compose.yml прода. +BACKUP_PI_SSH=pi@192.168.1.10 +BACKUP_PI_DIR=~/forbidden-stars # ─── ПРОЧЕЕ ─────────────────────────────────────────────────────────────────── # Часовой пояс приложения (фикс. смещение в часах; МСК = 3) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index a894668..2c96d6b 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -56,7 +56,31 @@ services: - ./deploy/tunnel/id_tunnel:/key/id_tunnel:ro mem_limit: 64m + # Бэкапы тест-клона: тот же образ, что у прода (сборка локально), но ТОЛЬКО локальный + # репозиторий и без расписания. BACKUP_VPS_HOST принудительно пуст — тестовые данные + # никогда не попадут в прод-репозиторий на VPS. Учебное восстановление: + # .\scripts\fs-backup.ps1 restore-test -File backups\fs_....tar + backup: + build: ./deploy/backup + image: forbidden-stars-backup:test + restart: "no" + environment: + BACKUP_PASSWORD: ${BACKUP_PASSWORD:-test-contour-only} + BACKUP_HOSTNAME: fs-test + BACKUP_SCHEDULE: "" + BACKUP_VERIFY_SCHEDULE: "" + BACKUP_VPS_HOST: "" + BACKUP_COMPRESSION: ${BACKUP_COMPRESSION:-max} + TZ: ${BACKUP_TZ:-Europe/Moscow} + volumes: + - db-data-test:/fs-db + - uploads-data-test:/fs/uploads + - achievements-data-test:/fs/achievements + - backup-data-test:/backup + mem_limit: 384m + volumes: db-data-test: uploads-data-test: achievements-data-test: + backup-data-test: diff --git a/docker-compose.yml b/docker-compose.yml index b0a272a..23697e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,9 +6,13 @@ # (scripts/build-push.*) — на Pi он не используется. # # Запуск/обновление: docker compose up -d (сам тянет свежие образы) -# Логи: docker compose logs -f app (или: ... tunnel) +# Логи: docker compose logs -f app (или: ... tunnel / backup) # Остановка: docker compose down (данные в томах сохраняются) -# Бэкап/restore: scripts/backup.sh / scripts/restore.sh +# Бэкапы: docker compose exec backup fs-backup status (help — все команды) +# инструкция: deploy/backup/README.md +# +# НИКОГДА не выполняйте на проде `docker compose down -v`: флаг -v удаляет тома — и данные +# приложения, и локальный репозиторий бэкапов (останется только копия на VPS). # # Контейнер ВСЕГДА production: APP_ENV форсится здесь и игнорирует значение из .env. # Порты на хост НЕ публикуются — наружу приложение выставляет только сервис tunnel @@ -69,7 +73,47 @@ services: security_opt: - no-new-privileges:true + # Бэкапы (restic): снимки по расписанию в локальный репозиторий (том backup-data) и на VPS + # (SFTP, если задан BACKUP_VPS_HOST). От app не зависит и app не мешает. Переменные — + # только BACKUP_* (секреты приложения сюда не передаются). Всё про настройку и + # восстановление — deploy/backup/README.md. + backup: + build: ./deploy/backup + image: ${IMAGE_REGISTRY:-gitea.arseniev.info/notbigghost}/forbidden-stars-backup:${IMAGE_TAG:-latest} + pull_policy: always + restart: unless-stopped + environment: + BACKUP_PASSWORD: ${BACKUP_PASSWORD:-} # пароль шифрования; пусто = бэкапы отключены + BACKUP_HOSTNAME: fs-prod # имя хоста в снимках (у тест-клона — fs-test) + BACKUP_SCHEDULE: ${BACKUP_SCHEDULE:-0 4 * * *} + BACKUP_VERIFY_SCHEDULE: ${BACKUP_VERIFY_SCHEDULE:-30 5 * * 0} + BACKUP_KEEP_DAILY: ${BACKUP_KEEP_DAILY:-14} + BACKUP_KEEP_WEEKLY: ${BACKUP_KEEP_WEEKLY:-8} + BACKUP_KEEP_MONTHLY: ${BACKUP_KEEP_MONTHLY:-12} + BACKUP_COMPRESSION: ${BACKUP_COMPRESSION:-max} + BACKUP_MAX_AGE_HOURS: ${BACKUP_MAX_AGE_HOURS:-30} + BACKUP_VPS_HOST: ${BACKUP_VPS_HOST:-} + BACKUP_VPS_USER: ${BACKUP_VPS_USER:-fsbackup} + BACKUP_VPS_PORT: ${BACKUP_VPS_PORT:-22} + BACKUP_VPS_DIR: ${BACKUP_VPS_DIR:-/srv/fs-backups/restic} + BACKUP_SSH_KEY_B64: ${BACKUP_SSH_KEY_B64:-} + TZ: ${BACKUP_TZ:-Europe/Moscow} + volumes: + - db-data:/fs-db # живая БД (снимается консистентно) + - uploads-data:/fs/uploads + - achievements-data:/fs/achievements + - backup-data:/backup # локальный репозиторий restic + состояние + mem_limit: ${BACKUP_MEM_LIMIT:-384m} + security_opt: + - no-new-privileges:true + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + volumes: db-data: uploads-data: achievements-data: + backup-data: diff --git a/scripts/build-push.ps1 b/scripts/build-push.ps1 index 262d148..a9f7467 100755 --- a/scripts/build-push.ps1 +++ b/scripts/build-push.ps1 @@ -1,4 +1,4 @@ -# Build prod images (app + tunnel) for linux/arm64 (Raspberry Pi) and push to the registry. +# Build prod images (app + tunnel + backup) for linux/arm64 (Raspberry Pi) and push to the registry. # Run on the DEV machine with Docker Desktop / buildx. Registry and tag come from the root # .env (IMAGE_REGISTRY / IMAGE_TAG) - the same values docker-compose.yml uses. # Log in to the registry once before running: docker login gitea.arseniev.info @@ -16,7 +16,7 @@ if ($LASTEXITCODE -ne 0) { # arm64 emulation on an x86 host (usually already present in Docker Desktop; idempotent). docker run --privileged --rm tonistiigi/binfmt --install arm64 | Out-Null -Write-Host "Building app+tunnel for linux/arm64 and pushing to the registry..." -ForegroundColor Green +Write-Host "Building app+tunnel+backup for linux/arm64 and pushing to the registry..." -ForegroundColor Green docker buildx bake -f docker-compose.yml --builder fsbuilder --set "*.platform=linux/arm64" --push if ($LASTEXITCODE -ne 0) { Write-Host "Build/push FAILED (exit $LASTEXITCODE)." -ForegroundColor Red diff --git a/scripts/build-push.sh b/scripts/build-push.sh index b345818..fde7dc8 100755 --- a/scripts/build-push.sh +++ b/scripts/build-push.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Сборка прод-образов (app + tunnel) под linux/arm64 для Raspberry Pi и пуш в реестр. +# Сборка прод-образов (app + tunnel + backup) под linux/arm64 для Raspberry Pi и пуш в реестр. # Запускается на машине РАЗРАБОТКИ с Docker Desktop/buildx (не на Pi). # Реестр и тег берутся из корневого .env (IMAGE_REGISTRY / IMAGE_TAG) — те же, что в # docker-compose.yml. Перед запуском один раз залогиниться в реестр: @@ -17,7 +17,7 @@ fi # Эмуляция arm64 на x86-хосте (в Docker Desktop обычно уже стоит; команда идемпотентна). docker run --privileged --rm tonistiigi/binfmt --install arm64 >/dev/null -echo "[build-push] Сборка app+tunnel под linux/arm64 и пуш в реестр…" +echo "[build-push] Сборка app+tunnel+backup под linux/arm64 и пуш в реестр…" # bake читает docker-compose.yml: контексты build: и теги image: (с подстановкой из .env). docker buildx bake -f docker-compose.yml --builder fsbuilder --set "*.platform=linux/arm64" --push