#!/bin/bash # Supersam full DB backup (pg_dump) to S3 (Beget Cloud Storage) # Dumps the complete supabase postgres database and uploads to S3. # Retention: keeps last 30 days. # # Cron: 0 6,18 * * * /opt/supersam/scripts/backup-pgdump-s3.sh >> /var/log/supersam-backup-pgdump.log 2>&1 # Каждая операция (успех или провал) логируется в public.backup_logs. set -euo pipefail # ── Config ──────────────────────────────────────────────────────────────── AWS_ACCESS_KEY_ID="YG4MQNKAPNL65200MBUY" AWS_SECRET_ACCESS_KEY="8mXkFM2VRQ3pN1Nx4mhmJ2jrZoB5YTPUa4CaZh43" S3_ENDPOINT="https://s3.ru1.storage.beget.cloud" S3_BUCKET="02f162ff4a18-supersam-s3" S3_PREFIX="backups" DB_CONTAINER="supabase-db" DB_USER="supabase_admin" DB_NAME="postgres" # Пароль берём из /opt/supabase/docker/.env, фолбэк — известное значение DB_PASS="${POSTGRES_PASSWORD:-$(grep -m1 '^POSTGRES_PASSWORD=' /opt/supabase/docker/.env 2>/dev/null | cut -d= -f2- || true)}" DB_PASS="${DB_PASS:-4fe80bb21c7c3d17a8d8b226adf7a479}" BACKUP_TYPE="pgdump" RETENTION_DAYS=30 # ── Logging в public.backup_logs ────────────────────────────────────────── LOGGED=0 log_backup() { local status="$1" size="$2" bytes="$3" s3_path="$4" tables="$5" printf "INSERT INTO public.backup_logs (backup_type, status, file_size, file_size_bytes, s3_path, tables_included) VALUES ('%s', '%s', '%s', %s, '%s', '%s');\n" \ "${BACKUP_TYPE}" "${status}" "${size}" "${bytes:-0}" "${s3_path}" "${tables}" \ | docker exec -i -e PGPASSWORD="${DB_PASS}" "${DB_CONTAINER}" \ psql -U "${DB_USER}" -d "${DB_NAME}" -q -t -A >/dev/null \ || echo "[$(date)] WARN: не удалось записать backup_logs" LOGGED=1 } on_exit() { local rc=$? if [ "${rc}" -ne 0 ] && [ "${LOGGED}" -eq 0 ]; then echo "[$(date)] FAILED: скрипт завершился с кодом ${rc}" log_backup "failed" "" 0 "" "exit code ${rc}" fi exit "${rc}" } trap on_exit EXIT # ── Runtime ─────────────────────────────────────────────────────────────── TIMESTAMP=$(date +%Y-%m-%d_%H%M) DUMP_FILE="/tmp/supersam-dump-${TIMESTAMP}.sql.gz" S3_KEY="${S3_PREFIX}/pgdump/supersam-db-${TIMESTAMP}.sql.gz" S3_PATH="s3://${S3_BUCKET}/${S3_KEY}" export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY echo "[$(date)] Starting pg_dump backup..." # ── Dump ────────────────────────────────────────────────────────────────── echo "[$(date)] Dumping database..." docker exec -e PGPASSWORD="${DB_PASS}" "${DB_CONTAINER}" \ pg_dump -U "${DB_USER}" -d "${DB_NAME}" --no-owner --no-acl --clean --if-exists \ | gzip -9 > "${DUMP_FILE}" DUMP_SIZE=$(du -h "${DUMP_FILE}" | cut -f1) DUMP_BYTES=$(stat -c%s "${DUMP_FILE}") echo "[$(date)] Dump created: ${DUMP_FILE} (${DUMP_SIZE}, ${DUMP_BYTES} bytes)" # ── Upload via mc ───────────────────────────────────────────────────────── echo "[$(date)] Uploading to S3..." mc cp "${DUMP_FILE}" "beget/${S3_BUCKET}/${S3_KEY}" --quiet 2>&1 echo "[$(date)] Uploaded to ${S3_PATH}" # ── Verify: объект существует в S3 и размер совпадает ───────────────────── S3_BYTES=$(mc stat --json "beget/${S3_BUCKET}/${S3_KEY}" 2>/dev/null | grep -o '"size":[0-9]*' | head -1 | cut -d: -f2 || true) if [ -z "${S3_BYTES}" ]; then echo "[$(date)] FAILED: объект не найден в S3 после загрузки: ${S3_PATH}" log_backup "failed" "${DUMP_SIZE}" "${DUMP_BYTES}" "${S3_PATH}" "upload not verified" exit 1 fi if [ "${S3_BYTES}" != "${DUMP_BYTES}" ]; then echo "[$(date)] WARN: размер в S3 (${S3_BYTES}) не совпадает с локальным (${DUMP_BYTES})" fi # ── Cleanup local ───────────────────────────────────────────────────────── rm -f "${DUMP_FILE}" echo "[$(date)] Local cleanup done" # ── Retention: delete S3 pgdump backups older than RETENTION_DAYS ───────── echo "[$(date)] Cleaning S3 pgdump backups older than ${RETENTION_DAYS} days..." CUTOFF=$(date -d "-${RETENTION_DAYS} days" +%Y-%m-%d) { mc ls "beget/${S3_BUCKET}/${S3_PREFIX}/pgdump/" 2>/dev/null || true; } \ | awk '{print $NF}' \ | while read -r fname; do file_date=$(echo "$fname" | grep -oP '\d{4}-\d{2}-\d{2}' || echo "") if [ -n "$file_date" ] && [ "$file_date" \< "$CUTOFF" ]; then mc rm "beget/${S3_BUCKET}/${S3_PREFIX}/pgdump/${fname}" --quiet 2>/dev/null echo "[$(date)] Deleted old backup: ${fname}" fi done log_backup "success" "${DUMP_SIZE}" "${DUMP_BYTES}" "${S3_PATH}" "all (full pg_dump)" echo "[$(date)] Backup complete."