food-market/deploy/food-market-backup.sh
nns 7c34bb1abd feat(deploy): авто-бэкап БД+uploads — systemd timer/service + скрипт (P0-6)
food-market-backup.sh: pg_dump -Fc контейнера + tar uploads, ротация 30 дней,
атомарная запись через .tmp+mv. food-market-backup.{service,timer} — ежедневно
03:00 с догоном пропущенных. docs/backup-restore.md — установка таймера, ручной
бэкап, восстановление БД (drop+create / --clean) и uploads, проверка дампа.

Скрипт проверен против food-market-postgres: дамп PGDMP custom-format,
248 TOC, pg_restore --list читает. Установку на prod-vm не делаем — только артефакты.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:49:08 +05:00

68 lines
3.2 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: ежедневный бэкап БД + загруженных файлов с ротацией.
#
# Дампит Postgres (контейнер food-market-postgres) в custom-формат pg_dump
# (-Fc, пригоден для pg_restore с параллелизмом/выборочным восстановлением) и
# архивирует каталог uploads. Удаляет бэкапы старше RETENTION_DAYS дней.
#
# Запускается из systemd-таймера food-market-backup.timer (ежедневно), либо
# вручную. Конфигурируется переменными окружения (значения по умолчанию
# совпадают с deploy/docker-compose.yml):
#
# FM_PG_CONTAINER имя контейнера Postgres (food-market-postgres)
# FM_PG_DB имя БД (food_market)
# FM_PG_USER пользователь БД (food_market)
# FM_BACKUP_DIR куда складывать бэкапы (/opt/food-market-data/backups)
# FM_UPLOADS_DIR каталог изображений (/opt/food-market-data/uploads)
# FM_BACKUP_RETENTION_DAYS срок хранения, дней (30)
#
set -euo pipefail
CONTAINER="${FM_PG_CONTAINER:-food-market-postgres}"
DB="${FM_PG_DB:-food_market}"
DB_USER="${FM_PG_USER:-food_market}"
BACKUP_DIR="${FM_BACKUP_DIR:-/opt/food-market-data/backups}"
UPLOADS_DIR="${FM_UPLOADS_DIR:-/opt/food-market-data/uploads}"
RETENTION_DAYS="${FM_BACKUP_RETENTION_DAYS:-30}"
TS="$(date +%Y%m%d-%H%M%S)"
DB_FILE="$BACKUP_DIR/db-$TS.dump"
UPLOADS_FILE="$BACKUP_DIR/uploads-$TS.tgz"
log() { echo "[$(date -Is)] $*"; }
mkdir -p "$BACKUP_DIR"
if ! docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
log "ОШИБКА: контейнер '$CONTAINER' не запущен — бэкап невозможен." >&2
exit 1
fi
log "Дамп БД '$DB' → $DB_FILE"
# Дамп пишем во временный файл и переименовываем по успеху — частичный/битый
# дамп при падении pg_dump не попадёт в ротацию как валидный.
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
if [ -d "$UPLOADS_DIR" ]; then
log "Архив uploads '$UPLOADS_DIR' → $UPLOADS_FILE"
tar czf "$UPLOADS_FILE.tmp" -C "$(dirname "$UPLOADS_DIR")" "$(basename "$UPLOADS_DIR")" \
&& mv "$UPLOADS_FILE.tmp" "$UPLOADS_FILE"
log "Готово: $(du -h "$UPLOADS_FILE" | cut -f1)"
else
log "Каталог uploads '$UPLOADS_DIR' отсутствует — пропуск."
fi
log "Ротация: удаляю бэкапы старше $RETENTION_DAYS дн."
find "$BACKUP_DIR" -maxdepth 1 -type f -name 'db-*.dump' -mtime +"$RETENTION_DAYS" -print -delete
find "$BACKUP_DIR" -maxdepth 1 -type f -name 'uploads-*.tgz' -mtime +"$RETENTION_DAYS" -print -delete
log "Бэкап завершён."