#!/bin/sh
# Prove that backups and archives can actually be restored (charter §9:
# "a backup counts only after a successful restore test").
#
#   tools/restore-test [--repo TARGET] [--archive DIR] [--keys DIR] [--log FILE] [--read-data]
#
#   --repo TARGET   restore the latest restic snapshot of TARGET into a temp
#                   dir, then open every compartment from the restored copy
#   --archive DIR   for each compartment, check the newest DIR/cN-*.tar.age:
#                   checksum, decrypt, list (nothing is extracted to disk)
#   --keys DIR      unlock files to use (default: $CONTINUITY_DATA/keys);
#                   point it at a recovery bundle to test a clean-machine path
#   --log FILE      append a one-line result (e.g. mnt/c1/recovery/restore-test-log.md)
#   --read-data     also make restic read and verify every data blob (slow)
#
# Asks for a vault code: give the PAPER code to test path C (ADR-007 §2).
# Restored ciphertext goes to a temp dir; plaintext is only ever seen through
# a FUSE mount there, which is unmounted at the end.
. "$(dirname "$0")/lib.sh"
need age
need_identity
target=; adir=; kdir=$KEYS; log=; readdata=no
while [ $# -gt 0 ]; do
    case $1 in
        --repo) target=$2; shift ;;
        --archive) adir=$2; shift ;;
        --keys) kdir=$2; shift ;;
        --log) log=$2; shift ;;
        --read-data) readdata=yes ;;
        *) die "unknown argument $1" ;;
    esac
    shift
done
[ -n "$target$adir" ] || die "nothing to test: give --repo TARGET and/or --archive DIR"

work=$(mktemp -d "${TMPDIR:-/tmp}/continuity-restore-test.XXXXXX")
cleanup() {
    for m in "$work"/mnt/*; do [ -d "$m" ] && is_mounted "$m" && unmount "$m"; done
    rm -rf "$work"
}
trap cleanup EXIT INT TERM
summary=
fail() { say "FAIL: $*"; result FAIL "$*"; exit 1; }
result() {
    line="$(date '+%Y-%m-%d %H:%M') $1 restore-test repo=${target:--} archive=${adir:+yes}${adir:--} identity=$(identity_label) $2"
    printf '%s\n' "$line"
    if [ -n "$log" ]; then printf -- '- %s\n' "$line" >> "$log"; fi
}

# Check a plaintext tree: COMPARTMENT.md exists, every record has front matter.
# Prints the record count; returns 1 (after saying why) on a problem.
check_tree() {  # DIR LABEL
    [ -f "$1/COMPARTMENT.md" ] || { say "$2: COMPARTMENT.md missing"; return 1; }
    n=0
    for f in $(find "$1/records" -type f -name '*.md' 2>/dev/null); do
        [ "$(head -n1 "$f")" = "---" ] || { say "$2: $f has no front matter"; return 1; }
        n=$((n + 1))
    done
    printf '%s' "$n"
}

if [ -n "$target" ]; then
    need restic gocryptfs
    repo=$(repo_for "$target"); [ -n "$repo" ] || fail "REPO for $target is not set"
    pw=$(unwrap "$kdir/restic-$target.pass.age") || fail "cannot decrypt restic password for $target"
    printf '%s\n' "$pw" | restic -r "$repo" --password-file /dev/stdin restore latest --target "$work/restore" -q \
        || fail "restic restore from $target failed"
    if [ $readdata = yes ]; then
        printf '%s\n' "$pw" | restic -r "$repo" --password-file /dev/stdin check --read-data -q || fail "restic check --read-data failed"
    fi
    unset pw
    for c in $CONTINUITY_COMPARTMENTS; do
        [ -f "$work/restore/cipher/$c/gocryptfs.conf" ] || fail "$c: not in the restored snapshot"
        mkdir -p "$work/mnt/$c"
        unwrap "$kdir/$c.pass.age" \
            | gocryptfs -q -passfile /dev/stdin "$work/restore/cipher/$c" "$work/mnt/$c" \
            || fail "$c: restored copy does not unlock"
        n=$(check_tree "$work/mnt/$c" "$c (restic)") || fail "$c: restored content is incomplete"
        unmount "$work/mnt/$c"
        summary="$summary $c:$n"
        say "$c restored from $target and opened ($n records)"
    done
fi

if [ -n "$adir" ]; then
    need tar sha256sum
    for c in $CONTINUITY_COMPARTMENTS; do
        f=$(ls "$adir"/"$c"-*.tar.age 2>/dev/null | grep -v '\.pq\.tar\.age$' | sort | tail -n1 || true)
        [ -n "$f" ] || fail "$c: no archive in $adir"
        (cd "$(dirname "$f")" && sha256sum -c --quiet "$(basename "$f").sha256") || fail "$f: checksum mismatch"
        listing=$(unwrap "$f" | tar -tf -) || true
        printf '%s\n' "$listing" | grep -qx '\./COMPARTMENT\.md' || fail "$f: does not decrypt, or COMPARTMENT.md missing"
        n=$(printf '%s\n' "$listing" | grep -c '^\./records/.*\.md$' || true)
        summary="$summary $c-archive:$n"
        say "$(basename "$f") decrypts and lists ($n records)"
    done
fi

result OK "records:$summary"
