supersam/scripts/backup-pgdump-s3.sh

106 lines
5.2 KiB
Bash
Executable File
Raw 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.

#!/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."