Ctrl K
ComunidadGuías…

backup/backup.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/sh
# PulseBase DB backup: pg_dump (custom format) -> age-encrypt -> retention -> optional offsite.
# Connects to the DB over the internal Docker network using libpq env vars — no docker socket.
#
# Env:
#   PGHOST/PGPORT/PGUSER/PGPASSWORD  libpq connection (PGHOST defaults to "db")
#   PGDATABASE                       database to dump (default: garmin)
#   AGE_RECIPIENT                    age PUBLIC key — REQUIRED, encrypts every backup
#   BACKUP_DIR                       output dir (default: /backups)
#   BACKUP_RETENTION_DAYS            delete local backups older than N days (default: 14)
#   RCLONE_REMOTE                    optional offsite target (e.g. b2:pulsebase-backups)
set -eu

: "${PGHOST:=db}"
: "${PGPORT:=5432}"
: "${PGDATABASE:=garmin}"
: "${BACKUP_DIR:=/backups}"
: "${BACKUP_RETENTION_DAYS:=14}"
RCLONE_REMOTE="${RCLONE_REMOTE:-}"
export PGHOST PGPORT PGDATABASE

log() { printf '{"ts":"%s","svc":"backup","level":"%s","msg":"%s"}\n' "$(date -u +%FT%TZ)" "$1" "$2"; }
die() { log error "$1"; exit 1; }

# --- Preflight -------------------------------------------------------------
command -v pg_dump >/dev/null || die "pg_dump missing"
command -v age >/dev/null     || die "age missing"
[ -n "${AGE_RECIPIENT:-}" ]   || die "AGE_RECIPIENT not set"
[ -d "$BACKUP_DIR" ]          || die "BACKUP_DIR $BACKUP_DIR does not exist"
[ -w "$BACKUP_DIR" ]          || die "BACKUP_DIR $BACKUP_DIR not writable"
pg_isready -q || die "database not reachable at $PGHOST:$PGPORT"

# --- Dump + encrypt (streamed; no plaintext dump ever hits disk) -----------
out="$BACKUP_DIR/pulsebase-$(date -u +%F).dump.age"
tmp="$out.tmp"
log info "backup.start file=$(basename "$out") db=$PGDATABASE"
if pg_dump -Fc "$PGDATABASE" | age -r "$AGE_RECIPIENT" -o "$tmp"; then
  mv "$tmp" "$out"
  log info "backup.ok file=$(basename "$out") bytes=$(wc -c < "$out" | tr -d ' ')"
else
  rm -f "$tmp"
  die "pg_dump|age pipeline failed"
fi

# --- Retention -------------------------------------------------------------
removed=$(find "$BACKUP_DIR" -name 'pulsebase-*.dump.age' -mtime +"$BACKUP_RETENTION_DAYS" -print -delete | wc -l | tr -d ' ')
log info "retention.done removed=$removed keep_days=$BACKUP_RETENTION_DAYS"

# --- Optional offsite copy (3-2-1 rule) ------------------------------------
if [ -n "$RCLONE_REMOTE" ]; then
  command -v rclone >/dev/null || die "RCLONE_REMOTE set but rclone missing in image"
  if rclone copy "$BACKUP_DIR" "$RCLONE_REMOTE" --max-age 24h --include 'pulsebase-*.dump.age'; then
    log info "offsite.ok remote=$RCLONE_REMOTE"
  else
    die "rclone copy failed"
  fi
fi

log info "backup.done"