Бэкап: полный оффсайт-бэкап прода на VPS (БД + uploads + achievements) + восстановление

- scripts/backup.sh: консистентный снимок SQLite (.backup) + архив fs_<TS>.tar.gz с uploads и
  achievements в backups/, локальная ротация; при заданном BACKUP_VPS_HOST — push на VPS (scp
  отдельным ключом/юзером) с удалённой ротацией. Настройки читаются из .env (BACKUP_*).
- scripts/restore.sh: восстановление архива в прод (stop app → очистка тома → docker cp → start).
- .env.example: блок BACKUP_* (host/user/dir/key/keep). .gitignore: id_backup* (ключ бэкапа).
- deploy/pi/README, deploy/vps/README: настройка ключа и юзера backup, /srv/fs-backups, cron, restore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 23:36:58 +03:00
co-authored by Claude Opus 4.8
parent 1e19038b7c
commit 32c74abc43
6 changed files with 155 additions and 17 deletions
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Восстановление прода из архива бэкапа (fs_<TS>.tar.gz): БД + uploads + achievements.
# ДЕСТРУКТИВНО — заменяет текущие данные тома. Сначала проверьте на тест-контуре!
# Использование:
# scripts/restore.sh backups/fs_YYYYmmdd_HHMMSS.tar.gz [--yes]
set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
COMPOSE="docker compose -f $PROJECT_DIR/docker-compose.yml"
SERVICE="app"
ARCHIVE="${1:-}"
CONFIRM="${2:-}"
if [ -z "$ARCHIVE" ] || [ ! -f "$ARCHIVE" ]; then
echo "Использование: $0 <путь к fs_*.tar.gz> [--yes]" >&2
exit 1
fi
if [ "$CONFIRM" != "--yes" ]; then
printf "ВНИМАНИЕ: текущие БД/uploads/achievements будут заменены из %s\nПродолжить? [y/N] " "$ARCHIVE"
read -r ans
case "$ans" in y|Y|yes|да|Да) ;; *) echo "Отменено."; exit 1 ;; esac
fi
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
tar -xzf "$ARCHIVE" -C "$STAGE"
if [ ! -f "$STAGE/forbidden_stars.db" ]; then
echo "ОШИБКА: в архиве нет forbidden_stars.db." >&2
exit 1
fi
echo "[restore] Остановка приложения…"
$COMPOSE stop "$SERVICE"
echo "[restore] Очистка тома (старая БД, WAL/SHM, медиа, титулы)…"
$COMPOSE run --rm --no-deps -T --entrypoint sh "$SERVICE" -c \
"rm -f /data/forbidden_stars.db /data/forbidden_stars.db-wal /data/forbidden_stars.db-shm; rm -rf /data/uploads/* /data/achievements/*"
CID="$($COMPOSE ps -aq "$SERVICE")"
echo "[restore] Запись данных в контейнер…"
docker cp "$STAGE/forbidden_stars.db" "$CID:/data/forbidden_stars.db"
[ -d "$STAGE/uploads" ] && docker cp "$STAGE/uploads/." "$CID:/data/uploads"
[ -d "$STAGE/achievements" ] && docker cp "$STAGE/achievements/." "$CID:/data/achievements"
echo "[restore] Запуск приложения…"
$COMPOSE start "$SERVICE"
echo "[restore] Готово. Проверьте https://forbiddenstars.ru и логи: $COMPOSE logs -f app"