75 lines
2.4 KiB
Bash
Executable File
75 lines
2.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# VYNDR — daily PostgreSQL backup.
|
|
#
|
|
# Runs from Coolify cron (3am ET). Dumps the Supabase database via the
|
|
# provided connection string, gzips it, uploads to Cloudflare R2 via
|
|
# rclone, and prunes local copies older than 7 days. Failures POST to
|
|
# ntfy so we hear about them within the hour.
|
|
#
|
|
# Required environment:
|
|
# SUPABASE_DB_URL — postgres connection string
|
|
# NTFY_PORT — ntfy port for alerts (default: 8080)
|
|
# NTFY_TOPIC — ntfy topic (default: vyndr-admin)
|
|
# R2_REMOTE — rclone remote name targeting R2 (default: r2)
|
|
# R2_BUCKET — R2 bucket path (default: vyndr-backups/daily)
|
|
#
|
|
# Prerequisites on the host:
|
|
# pg_dump (PostgreSQL client tools)
|
|
# gzip
|
|
# rclone configured with R2 credentials (`rclone config`)
|
|
# curl (for ntfy)
|
|
|
|
set -euo pipefail
|
|
|
|
DATE="$(date -u +%Y-%m-%dT%H%M%SZ)"
|
|
BACKUP_DIR="${BACKUP_DIR:-/tmp/vyndr-backups}"
|
|
BACKUP_FILE="${BACKUP_DIR}/vyndr-${DATE}.sql.gz"
|
|
LOG_FILE="${LOG_FILE:-/var/log/vyndr-backup.log}"
|
|
NTFY_PORT="${NTFY_PORT:-8080}"
|
|
NTFY_TOPIC="${NTFY_TOPIC:-vyndr-admin}"
|
|
R2_REMOTE="${R2_REMOTE:-r2}"
|
|
R2_BUCKET="${R2_BUCKET:-vyndr-backups/daily}"
|
|
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
|
|
|
|
log() {
|
|
printf '[%s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" | tee -a "$LOG_FILE"
|
|
}
|
|
|
|
alert() {
|
|
# Fire-and-forget — never fail the script because the alerter is down.
|
|
curl -s --max-time 5 -d "$1" "http://localhost:${NTFY_PORT}/${NTFY_TOPIC}" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
if [[ -z "${SUPABASE_DB_URL:-}" ]]; then
|
|
log "ERROR: SUPABASE_DB_URL is not set"
|
|
alert "VYNDR backup failed: SUPABASE_DB_URL missing"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
log "Starting backup → ${BACKUP_FILE}"
|
|
|
|
if ! pg_dump --no-owner --no-privileges "$SUPABASE_DB_URL" | gzip > "$BACKUP_FILE"; then
|
|
log "ERROR: pg_dump failed"
|
|
alert "VYNDR backup FAILED at ${DATE} — pg_dump"
|
|
exit 1
|
|
fi
|
|
|
|
SIZE="$(du -h "$BACKUP_FILE" | cut -f1)"
|
|
log "Dump complete: ${SIZE}"
|
|
|
|
if ! rclone copy "$BACKUP_FILE" "${R2_REMOTE}:${R2_BUCKET}/"; then
|
|
log "ERROR: rclone upload failed"
|
|
alert "VYNDR backup upload FAILED at ${DATE}"
|
|
exit 1
|
|
fi
|
|
|
|
log "Upload to ${R2_REMOTE}:${R2_BUCKET} complete"
|
|
|
|
# Prune old local copies. R2 retention is configured on the bucket; this
|
|
# script doesn't try to manage remote retention to avoid accidental deletes.
|
|
find "$BACKUP_DIR" -name 'vyndr-*.sql.gz' -mtime "+${RETENTION_DAYS}" -delete
|
|
|
|
log "Backup ${DATE} complete (${SIZE})"
|