Бэкап: сервис backup в compose прода и тест-клона, .env.example

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jBxs9nBCk5nBdTLzcGz91
This commit is contained in:
2026-09-14 01:04:12 +03:00
co-authored by Claude Opus 5
parent f73316f37f
commit e2d729d339
5 changed files with 107 additions and 15 deletions
+33 -9
View File
@@ -90,16 +90,40 @@ IMAGE_TAG=latest
#APP_MEM_LIMIT=512m #APP_MEM_LIMIT=512m
#APP_CPUS=1.5 #APP_CPUS=1.5
# ─── БЭКАП (оффсайт-копия на VPS; читает scripts/backup.sh на хосте Pi) ─────── # ─── БЭКАПЫ (контейнер backup: restic; подробно — deploy/backup/README.md) ────
# Полный бэкап (БД + uploads + achievements) кладётся локально в backups/, и — если задан # Снимки БД + uploads + achievements по расписанию: локально на Pi (том backup-data) и —
# BACKUP_VPS_HOST — копируется на VPS (Pi → VPS push, т.к. Pi за CGNAT). Пусто = только локально. # если задан BACKUP_VPS_HOST — на VPS по SFTP. Шифрование, дедупликация, сжатие — restic.
# Ключ бэкапа — deploy/backup/id_backup (в git НЕ идёт); его pubkey → authorized_keys у backup@VPS. #
# Пароль шифрования репозиториев. ПОТЕРЯЕТЕ ПАРОЛЬ — НИ ОДИН БЭКАП НЕ ПРОЧИТАТЬ.
# Сохраните его в менеджер паролей. Пусто = бэкапы отключены. Без символа '$'.
# Сгенерировать: 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_HOST=
BACKUP_VPS_USER=backup BACKUP_VPS_USER=fsbackup
BACKUP_VPS_DIR=/srv/fs-backups BACKUP_VPS_PORT=22
BACKUP_VPS_KEY=deploy/backup/id_backup BACKUP_VPS_DIR=/srv/fs-backups/restic
BACKUP_KEEP_LOCAL=14 # Приватный SSH-ключ для VPS в base64 одной строкой (как TUNNEL_KEY_B64). На ПК ключ лежит
BACKUP_KEEP_REMOTE=30 # в 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) # Часовой пояс приложения (фикс. смещение в часах; МСК = 3)
+24
View File
@@ -56,7 +56,31 @@ services:
- ./deploy/tunnel/id_tunnel:/key/id_tunnel:ro - ./deploy/tunnel/id_tunnel:/key/id_tunnel:ro
mem_limit: 64m 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: volumes:
db-data-test: db-data-test:
uploads-data-test: uploads-data-test:
achievements-data-test: achievements-data-test:
backup-data-test:
+46 -2
View File
@@ -6,9 +6,13 @@
# (scripts/build-push.*) — на Pi он не используется. # (scripts/build-push.*) — на Pi он не используется.
# #
# Запуск/обновление: docker compose up -d (сам тянет свежие образы) # Запуск/обновление: docker compose up -d (сам тянет свежие образы)
# Логи: docker compose logs -f app (или: ... tunnel) # Логи: docker compose logs -f app (или: ... tunnel / backup)
# Остановка: docker compose down (данные в томах сохраняются) # Остановка: 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. # Контейнер ВСЕГДА production: APP_ENV форсится здесь и игнорирует значение из .env.
# Порты на хост НЕ публикуются — наружу приложение выставляет только сервис tunnel # Порты на хост НЕ публикуются — наружу приложение выставляет только сервис tunnel
@@ -69,7 +73,47 @@ services:
security_opt: security_opt:
- no-new-privileges:true - 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: volumes:
db-data: db-data:
uploads-data: uploads-data:
achievements-data: achievements-data:
backup-data:
+2 -2
View File
@@ -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 # 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. # .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 # 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). # arm64 emulation on an x86 host (usually already present in Docker Desktop; idempotent).
docker run --privileged --rm tonistiigi/binfmt --install arm64 | Out-Null 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 docker buildx bake -f docker-compose.yml --builder fsbuilder --set "*.platform=linux/arm64" --push
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "Build/push FAILED (exit $LASTEXITCODE)." -ForegroundColor Red Write-Host "Build/push FAILED (exit $LASTEXITCODE)." -ForegroundColor Red
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Сборка прод-образов (app + tunnel) под linux/arm64 для Raspberry Pi и пуш в реестр. # Сборка прод-образов (app + tunnel + backup) под linux/arm64 для Raspberry Pi и пуш в реестр.
# Запускается на машине РАЗРАБОТКИ с Docker Desktop/buildx (не на Pi). # Запускается на машине РАЗРАБОТКИ с Docker Desktop/buildx (не на Pi).
# Реестр и тег берутся из корневого .env (IMAGE_REGISTRY / IMAGE_TAG) — те же, что в # Реестр и тег берутся из корневого .env (IMAGE_REGISTRY / IMAGE_TAG) — те же, что в
# docker-compose.yml. Перед запуском один раз залогиниться в реестр: # docker-compose.yml. Перед запуском один раз залогиниться в реестр:
@@ -17,7 +17,7 @@ fi
# Эмуляция arm64 на x86-хосте (в Docker Desktop обычно уже стоит; команда идемпотентна). # Эмуляция arm64 на x86-хосте (в Docker Desktop обычно уже стоит; команда идемпотентна).
docker run --privileged --rm tonistiigi/binfmt --install arm64 >/dev/null 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). # bake читает docker-compose.yml: контексты build: и теги image: (с подстановкой из .env).
docker buildx bake -f docker-compose.yml --builder fsbuilder --set "*.platform=linux/arm64" --push docker buildx bake -f docker-compose.yml --builder fsbuilder --set "*.platform=linux/arm64" --push