#!/usr/bin/env bash
# luks-header-backup.sh — back up the LUKS2 header off-host.
#
# Why this matters: if the on-disk LUKS header gets corrupted (bad sectors,
# accidental overwrite), the data is unrecoverable WITHOUT a header backup —
# even with the correct passphrase. luksHeaderBackup writes a copy that can
# be restored later via luksHeaderRestore.
#
# Run from cron once daily. Idempotent — only writes if the header SHA has
# actually changed since the last successful run (so daily runs after a
# stable provision are no-ops).
#
# Local copies: /var/lib/whp/luks-headers/YYYY-MM-DDTHH-MM-SS.backup
#               (last 30 retained; older pruned)
# Also keeps /etc/whp/luks-header.backup current.
# Off-host: if rclone remote `whp-system-backup:` is configured, also uploads.

set -uo pipefail

SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
# shellcheck disable=SC1091
. "$SCRIPT_DIR/lib-luks-state.sh"

if ! luks_is_enabled; then
    # Non-LUKS server — nothing to back up. Exit cleanly so cron doesn't email.
    exit 0
fi

LOCAL_DIR=/var/lib/whp/luks-headers
SHA_CACHE=/var/lib/whp/luks-header-last-sha
# -u: generate a unique name but DON'T create the file. cryptsetup
# luksHeaderBackup refuses to overwrite an existing target.
TMP_FILE=$(mktemp -u -t luks-header-XXXXXX.bin)
trap 'rm -f "$TMP_FILE"' EXIT

mkdir -p "$LOCAL_DIR"
chmod 0700 "$LOCAL_DIR"

# Generate the header backup. cryptsetup writes the LUKS2 header (~16 MB)
# to the target file. Doesn't require the passphrase.
if ! cryptsetup luksHeaderBackup "$LUKS_BACKING_DEVICE" \
        --header-backup-file "$TMP_FILE" 2>&1; then
    echo "ERROR: luksHeaderBackup failed for $LUKS_BACKING_DEVICE" >&2
    exit 1
fi
chmod 0600 "$TMP_FILE"

NEW_SHA=$(sha256sum "$TMP_FILE" | awk '{print $1}')
LAST_SHA=$(cat "$SHA_CACHE" 2>/dev/null || true)

if [ "$NEW_SHA" = "$LAST_SHA" ]; then
    # Unchanged — nothing to do.
    exit 0
fi

# Header has changed (or first run): persist locally + push off-host.
TS=$(date -u +%Y-%m-%dT%H-%M-%SZ)
LOCAL_FILE="$LOCAL_DIR/$TS.backup"
cp "$TMP_FILE" "$LOCAL_FILE"
chmod 0600 "$LOCAL_FILE"

# Keep /etc/whp/luks-header.backup current too (some recovery procedures
# look there first).
cp "$TMP_FILE" /etc/whp/luks-header.backup
chmod 0600 /etc/whp/luks-header.backup

# Update SHA cache for next run's comparison.
echo "$NEW_SHA" > "$SHA_CACHE"
chmod 0600 "$SHA_CACHE"

# Prune to last 30 backups.
ls -1t "$LOCAL_DIR"/*.backup 2>/dev/null | tail -n +31 | xargs -r rm -f

echo "Wrote $LOCAL_FILE (sha=$NEW_SHA, was=${LAST_SHA:-none})"

# Off-host upload via rclone. Skip silently if the remote isn't configured —
# local backups still ran above. Document the remote name in the runbook.
REMOTE_NAME="whp-system-backup"
if command -v rclone >/dev/null && rclone listremotes 2>/dev/null | grep -qx "${REMOTE_NAME}:"; then
    REMOTE_PATH="${REMOTE_NAME}:luks-headers/$(hostname)/$TS.backup"
    if rclone copyto "$LOCAL_FILE" "$REMOTE_PATH" 2>&1; then
        echo "Uploaded to $REMOTE_PATH"
    else
        echo "WARN: rclone upload to $REMOTE_PATH failed" >&2
        exit 1
    fi
else
    echo "rclone remote ${REMOTE_NAME}: not configured — local-only backup. Configure rclone for off-host redundancy."
fi

exit 0
