#!/usr/bin/env bash # Daily Postgres backup script. Designed to run from cron on the prod VM. # # Usage (one-time setup on VM): # sudo cp /opt/gendesign/ops/backup.sh /usr/local/bin/gendesign-backup # sudo chmod +x /usr/local/bin/gendesign-backup # crontab -e # # Run daily at 03:30 Moscow time # 30 3 * * * /usr/local/bin/gendesign-backup >> /var/log/gendesign-backup.log 2>&1 # # Optional S3 upload — set these env vars in /etc/default/gendesign-backup # (or as `Environment=` in a systemd timer). Without them, dumps stay local only. # # S3_ENDPOINT=https://s3.ru-1.storage.selcloud.ru # Selectel S3 # S3_BUCKET=gendesign-backups # S3_ACCESS_KEY=... # S3_SECRET_KEY=... # # Retention: deletes local dumps older than 7 days. S3 retention policy should # be configured at the bucket level (lifecycle rule), not in this script. set -euo pipefail # --- config --- COMPOSE_DIR=/opt/gendesign COMPOSE_FILE=docker-compose.prod.yml LOCAL_BACKUP_DIR=/opt/gendesign/backups RETENTION_DAYS=7 # Optional S3 env. Loaded from /etc/default/gendesign-backup if present. [[ -f /etc/default/gendesign-backup ]] && source /etc/default/gendesign-backup # --- run --- mkdir -p "$LOCAL_BACKUP_DIR" TIMESTAMP=$(date +%Y%m%d_%H%M%S) DUMP_FILE="${LOCAL_BACKUP_DIR}/gendesign_${TIMESTAMP}.sql.gz" cd "$COMPOSE_DIR" # Read DB user/name from running compose env. Falls back to "gendesign" if env empty. DB_USER=$(docker compose -f "$COMPOSE_FILE" exec -T postgres printenv POSTGRES_USER 2>/dev/null | tr -d '\r' || true) DB_NAME=$(docker compose -f "$COMPOSE_FILE" exec -T postgres printenv POSTGRES_DB 2>/dev/null | tr -d '\r' || true) DB_USER=${DB_USER:-gendesign} DB_NAME=${DB_NAME:-gendesign} echo "[$(date -Is)] Dumping ${DB_NAME} as ${DB_USER} → ${DUMP_FILE}" docker compose -f "$COMPOSE_FILE" exec -T postgres \ pg_dump --clean --if-exists -U "$DB_USER" "$DB_NAME" \ | gzip -9 > "$DUMP_FILE" DUMP_SIZE=$(stat -c '%s' "$DUMP_FILE") echo "[$(date -Is)] Dump complete: ${DUMP_SIZE} bytes" # --- optional S3 upload --- if [[ -n "${S3_ENDPOINT:-}" && -n "${S3_BUCKET:-}" && -n "${S3_ACCESS_KEY:-}" && -n "${S3_SECRET_KEY:-}" ]]; then echo "[$(date -Is)] Uploading to s3://${S3_BUCKET}/" docker run --rm \ -e AWS_ACCESS_KEY_ID="$S3_ACCESS_KEY" \ -e AWS_SECRET_ACCESS_KEY="$S3_SECRET_KEY" \ -v "$LOCAL_BACKUP_DIR":/backup:ro \ amazon/aws-cli:latest \ --endpoint-url "$S3_ENDPOINT" \ s3 cp "/backup/$(basename "$DUMP_FILE")" "s3://${S3_BUCKET}/" echo "[$(date -Is)] S3 upload OK" else echo "[$(date -Is)] S3 vars not set — backup stays local only" fi # --- prune old local dumps --- find "$LOCAL_BACKUP_DIR" -name 'gendesign_*.sql.gz' -mtime "+${RETENTION_DAYS}" -print -delete echo "[$(date -Is)] Backup done."