- 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>
79 lines
3.7 KiB
Bash
Executable file
79 lines
3.7 KiB
Bash
Executable file
#!/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 "Бэкап завершён."
|