#!/usr/bin/env bash
umask 077   # this artifact IS secrets — it must be born root-only
# Nightly host CONFIG snapshot (PAPER-08).
#
# WHY: the 2026-07-11 restore drill proved all three data stores restore — and then
# proved that restoring the DATA does not restore the SERVICE. The configuration and
# credentials a service needs in order to START were captured by nothing:
#   · /opt/openobserve/CREDENTIALS  — a restored OO carries its ORIGINAL users, and
#     ZO_ROOT_USER_* only seeds a FRESH volume, so without this you cannot log in at all
#   · /opt/woodpecker/.env          — WOODPECKER_AGENT_SECRET (agents can't connect
#                                     without it), the OAuth client secret, DEVPI_URL
#   · /opt/logship/collector.env    — the ingest token
#   · /opt/watcher/{.codeberg-token,backup-pull-key} — the pager, and the key the
#     off-host mirror uses to pull EVERY backup (the thing protecting everything else
#     was itself protected by nothing)
#
# The artifact lands in /opt/backups/ so the existing off-host mirror carries it with no
# extra plumbing. Blast radius is not materially widened: the Postgres dump the mirror
# already holds contains all 17 org secrets (proven in the drill), so agent2 is already
# trusted with the crown jewels.
#
# NOT a substitute for an OFFLINE copy of the three bootstrap credentials that no restore
# can regenerate (OO root password · WOODPECKER_AGENT_SECRET · the mirror's pull key).
# Keep those in a password manager. A backup that can only be read by restoring the thing
# it is meant to restore is a circular dependency.
set -uo pipefail

TS=$(date +%Y%m%d-%H%M)
LOG=${LOG:-/opt/backups/backup.log}
OUT=${OUT:-/opt/backups/config-$(hostname -s)-$TS.tar.gz}
KEEP=${KEEP:-14}
HEARTBEAT_URL_FILE="${HEARTBEAT_URL_FILE:-/etc/mgf/backup-heartbeat.url}"
HEARTBEAT_TOKEN_FILE="${HEARTBEAT_TOKEN_FILE:-/etc/mgf/backup-heartbeat.token}"

fail() { echo "[$TS] HEARTBEAT config-snapshot FAILED: $1" >> "$LOG"; exit 1; }

post_heartbeat() {
  [ -r "$HEARTBEAT_URL_FILE" ] && [ -r "$HEARTBEAT_TOKEN_FILE" ] || {
    echo "[$TS] WARNING: heartbeat url/token missing — none sent (watcher WILL page)" >> "$LOG"
    return 0
  }
  curl -fsS -m 20 -X POST -H 'Content-Type: application/json' \
    -H "Authorization: $(cat "$HEARTBEAT_TOKEN_FILE")" \
    -d "[{\"job\":\"config-snapshot\",\"status\":\"ok\",\"host\":\"$(hostname)\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}]" \
    "$(cat "$HEARTBEAT_URL_FILE")" >/dev/null 2>&1 \
    || echo "[$TS] WARNING: heartbeat POST failed (watcher WILL page)" >> "$LOG"
}

# Every candidate path across the estate; each host snapshots whatever it actually has.
CANDIDATES=(
  /opt/openobserve/CREDENTIALS
  /opt/openobserve/docker-compose.yml
  /opt/logship/collector.env
  /opt/woodpecker/.env
  /opt/woodpecker/docker-compose.yml
  /opt/woodpecker-agent/.env
  /opt/woodpecker-agent/docker-compose.yml
  /opt/devpi/docker-compose.yml
  /opt/watcher/.codeberg-token
  /opt/watcher/backup-pull-key
  /etc/mgf/backup-heartbeat.url
  /etc/mgf/backup-heartbeat.token
)

PRESENT=()
for f in "${CANDIDATES[@]}"; do [ -e "$f" ] && PRESENT+=("$f"); done
[ ${#PRESENT[@]} -gt 0 ] || fail "no config paths present on $(hostname -s) — nothing to snapshot"

tar -czf "$OUT" --absolute-names "${PRESENT[@]}" 2>/dev/null || fail "tar"
chmod 600 "$OUT"

SZ=$(stat -c%s "$OUT") || fail "stat"
[ "$SZ" -gt 200 ] || fail "artifact suspiciously small ($SZ bytes)"
gunzip -t "$OUT" || fail "gzip integrity"

# Retention: keep the last $KEEP snapshots for THIS host.
ls -1t /opt/backups/config-$(hostname -s)-*.tar.gz 2>/dev/null | tail -n +$(( KEEP + 1 )) | xargs -r rm -f

echo "[$TS] HEARTBEAT config-snapshot OK ${#PRESENT[@]} file(s), $(du -h "$OUT" | cut -f1)" >> "$LOG"
post_heartbeat
