#!/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 "Бэкап завершён."