food-market/deploy/prod-backup.sh
nns b49abfdbd7
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
feat(prod): запуск admin.food-market.kz — бэкапы (hourly/daily/офсайт), restore-verify, мониторинг, deploy-prod
- prod-backup.sh (tier hourly 48ч / daily 30д + uploads), prod-backup-offsite-pull.sh (DR на dev-vm)
- prod-restore-verify.sh: restore во временную БД + сверка числа строк, дроп
- prod-monitor.sh: api/web/public/БД/диск/5xx → Telegram владельцу (+ --test)
- deploy-prod.sh: guard'ы (prod-postgres, .env chmod600, не dev-пароль, quick-login off,
  SEED_DEMO_DATA=false, issuer), бэкап ДО миграций, только --yes, НЕ в ночных
- systemd таймеры (backup-hourly/daily, monitor, offsite-pull); docs/prod-runbook.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:31:55 +05:00

79 lines
3.7 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# ПРОД food-market: резервная копия БД (pg_dump -Fc) с тирами и офсайт-копией.
#
# Тиры (FM_BACKUP_TIER):
# hourly — каждый час, хранение 48 ч → $FM_BACKUP_DIR/hourly
# daily — ежедневно, хранение 30 дн → $FM_BACKUP_DIR/daily (+ офсайт-копия)
#
# Офсайт: daily-дамп копируется на другой хост/диск (FM_OFFSITE_DEST, scp-назначение
# вида user@host:/path ИЛИ локальный путь на другом диске). Копия ротационно
# хранится FM_OFFSITE_RETENTION_DAYS (по умолчанию 30).
#
# Контейнер БД — food-market-prod-postgres (НЕ shared-postgres с чужими базами!).
set -euo pipefail
CONTAINER="${FM_PG_CONTAINER:-food-market-prod-postgres}"
DB="${FM_PG_DB:-food_market}"
DB_USER="${FM_PG_USER:-food_market}"
BASE_DIR="${FM_BACKUP_DIR:-/opt/food-market-data/backups}"
UPLOADS_DIR="${FM_UPLOADS_DIR:-/opt/food-market-data/uploads}"
TIER="${FM_BACKUP_TIER:-daily}"
OFFSITE_DEST="${FM_OFFSITE_DEST:-}" # напр. nns@192.168.1.193:/home/nns/fm-prod-backups
OFFSITE_RETENTION="${FM_OFFSITE_RETENTION_DAYS:-30}"
case "$TIER" in
hourly) DIR="$BASE_DIR/hourly"; RET_MIN=2880 ;; # 48 ч = 2880 мин
daily) DIR="$BASE_DIR/daily"; RET_MIN=43200 ;; # 30 дн = 43200 мин
*) echo "FM_BACKUP_TIER должен быть hourly|daily (дано: $TIER)" >&2; exit 2 ;;
esac
TS="$(date +%Y%m%d-%H%M%S)"
DB_FILE="$DIR/db-$TS.dump"
log() { echo "[$(date -Is)] [$TIER] $*"; }
mkdir -p "$DIR"
if ! docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
log "ОШИБКА: контейнер '$CONTAINER' не запущен — бэкап невозможен." >&2
exit 1
fi
log "Дамп '$DB' → $DB_FILE"
if docker exec "$CONTAINER" pg_dump -U "$DB_USER" -d "$DB" -Fc > "$DB_FILE.tmp"; then
mv "$DB_FILE.tmp" "$DB_FILE"
log "Готово: $(du -h "$DB_FILE" | cut -f1)"
else
rm -f "$DB_FILE.tmp"; log "ОШИБКА: pg_dump завершился с ошибкой." >&2; exit 1
fi
# uploads архивируем только в daily (меняются реже, чем БД).
if [ "$TIER" = "daily" ] && [ -d "$UPLOADS_DIR" ]; then
UP="$DIR/uploads-$TS.tgz"
tar czf "$UP.tmp" -C "$(dirname "$UPLOADS_DIR")" "$(basename "$UPLOADS_DIR")" && mv "$UP.tmp" "$UP"
log "uploads → $UP ($(du -h "$UP" | cut -f1))"
fi
# Офсайт-копия (только daily).
if [ "$TIER" = "daily" ] && [ -n "$OFFSITE_DEST" ]; then
if [[ "$OFFSITE_DEST" == *:* ]]; then
# удалённый scp-назначение
host="${OFFSITE_DEST%%:*}"; path="${OFFSITE_DEST#*:}"
ssh -o StrictHostKeyChecking=no "$host" "mkdir -p '$path'" 2>/dev/null || true
if scp -o StrictHostKeyChecking=no "$DB_FILE" "$OFFSITE_DEST/" 2>/dev/null; then
log "офсайт-копия → $OFFSITE_DEST"
ssh -o StrictHostKeyChecking=no "$host" "find '$path' -maxdepth 1 -name 'db-*.dump' -mtime +$OFFSITE_RETENTION -delete" 2>/dev/null || true
else
log "ВНИМАНИЕ: офсайт-копия не удалась (scp $OFFSITE_DEST)" >&2
fi
else
mkdir -p "$OFFSITE_DEST"
cp "$DB_FILE" "$OFFSITE_DEST/" && log "офсайт-копия → $OFFSITE_DEST"
find "$OFFSITE_DEST" -maxdepth 1 -name 'db-*.dump' -mtime +"$OFFSITE_RETENTION" -delete 2>/dev/null || true
fi
fi
log "Ротация $DIR: удаляю старше $RET_MIN мин."
find "$DIR" -maxdepth 1 -type f -name 'db-*.dump' -mmin +"$RET_MIN" -print -delete
find "$DIR" -maxdepth 1 -type f -name 'uploads-*.tgz' -mmin +"$RET_MIN" -print -delete
log "Бэкап завершён."