#!/usr/bin/env bash
#
# Daily production-database backup for the standalone/Coolify box.
# Installed as a systemd timer by scripts/standalone-deploy.sh.
#
# WHAT IT BACKS UP (corrected 2026-07-14): the REAL production database, which
# is **Neon** — reached via the app container's own DATABASE_URL. The compose
# also runs an unused local `postgres` container; the previous version of this
# script dumped THAT by mistake, so daily backups were capturing empty/stale
# data. Do not reintroduce `$COMPOSE exec postgres pg_dump`.
#
# HOW: Neon runs Postgres 17, so we dump with an ephemeral `postgres:17`
# container (the box's local client is 16 and refuses a newer server). The
# app's DATABASE_URL on this box is malformed (two connstrings mashed on one
# line — a fragile latent bug), so we take the clean Neon URL up to the second
# "DATABASE_URL=" marker. `${URL%%DATABASE_URL=*}` is a no-op once the .env is
# fixed, so this stays correct either way.
#
# Output: custom-format (`-Fc`) dumps — compressed + restorable selectively via
# pg_restore. Compatible with scripts/restore.sh and scripts/backup-restore-drill.sh.
#
# Optional env (set in /opt/gluecron/.env):
#   BACKUP_RCLONE_REMOTE  e.g. r2:gluecron-backups   (needs rclone configured)
#   BACKUP_SCP_TARGET     e.g. user@100.x.y.z:/backups  (self-owned offsite over Tailscale)
#   HEALTHCHECK_PING_URL  e.g. https://hc-ping.com/<uuid>  (dead-man's-switch)
set -euo pipefail

REPO_DIR="/opt/gluecron"
BACKUP_DIR="$REPO_DIR/backups"
RETAIN_DAYS=14
APP_CONTAINER="gluecron-gluecron-1"
PG_IMAGE="postgres:17-alpine"

cd "$REPO_DIR"
mkdir -p "$BACKUP_DIR"
ts=$(date +%Y%m%d-%H%M%S)
out="$BACKUP_DIR/gluecron-db-$ts.dump"

# Resolve the live DATABASE_URL from the running app, then clean it. Reading it
# from the container (not .env) means the backup always matches what the app
# actually connects to.
raw_url="$(docker exec "$APP_CONTAINER" printenv DATABASE_URL)"
clean_url="${raw_url%%DATABASE_URL=*}"
if [ -z "$clean_url" ]; then
  echo "$(date -Is) FATAL: could not resolve DATABASE_URL from $APP_CONTAINER" >&2
  exit 1
fi

# Dump Neon with a matching-major-version client. Custom format, no owner/ACLs
# (portable across restore targets). Password + URL travel only in container
# env; the pg_dump runs via `sh -c` so $NEONURL expands INSIDE the container
# (host-side expansion would be empty and trip `set -u`).
docker run --rm -e NEONURL="$clean_url" -e OUTFILE="/backups/$(basename "$out")" \
  -v "$BACKUP_DIR:/backups" "$PG_IMAGE" \
  sh -c 'pg_dump "$NEONURL" --format=custom --no-owner --no-privileges -f "$OUTFILE"' \
  2>/tmp/backup-err.$$ || {
    echo "$(date -Is) FATAL: pg_dump failed:" >&2
    cat /tmp/backup-err.$$ >&2
    rm -f /tmp/backup-err.$$ "$out"
    exit 1
  }
rm -f /tmp/backup-err.$$

# Sanity: a valid custom-format dump must list objects. Guards against a
# 0-byte/corrupt file being counted as a good backup.
obj_count="$(docker run --rm -e F="/backups/$(basename "$out")" -v "$BACKUP_DIR:/backups" "$PG_IMAGE" \
  sh -c 'pg_restore --list "$F"' 2>/dev/null | grep -cE 'TABLE DATA|TABLE|SEQUENCE' || true)"
if [ "${obj_count:-0}" -lt 20 ]; then
  echo "$(date -Is) FATAL: dump looks empty ($obj_count objects) — refusing to keep it" >&2
  rm -f "$out"
  exit 1
fi

size="$(du -h "$out" | cut -f1)"

# Retention — keep RETAIN_DAYS of daily dumps.
find "$BACKUP_DIR" -name 'gluecron-db-*.dump' -mtime +$RETAIN_DAYS -delete

# Optional self-owned offsite copy over Tailscale (preferred — no third party).
if [ -n "${BACKUP_SCP_TARGET:-}" ]; then
  scp -o BatchMode=yes -o StrictHostKeyChecking=accept-new "$out" "$BACKUP_SCP_TARGET/" \
    && echo "$(date -Is) offsite scp ok -> $BACKUP_SCP_TARGET" \
    || echo "$(date -Is) WARN: offsite scp failed"
fi

# Optional rclone offsite copy (if you do use a bucket).
if [ -n "${BACKUP_RCLONE_REMOTE:-}" ] && command -v rclone >/dev/null 2>&1; then
  rclone copy "$out" "$BACKUP_RCLONE_REMOTE" || echo "$(date -Is) WARN: rclone copy failed"
fi

# Optional dead-man's-switch heartbeat (alerts if a backup is ever missed).
if [ -n "${HEALTHCHECK_PING_URL:-}" ]; then
  curl -fsS -m 10 "$HEALTHCHECK_PING_URL" >/dev/null 2>&1 || true
fi

echo "$(date -Is) backup written: $out ($size, $obj_count objects)"
