#!/bin/bash
# vlda-01 side of the alpha -> vlda-01 Gitea backup strategy
# (see SYNC-PLAN.md "Ongoing backup" section). Pulls whatever nightly
# gitea-dump archives exist on alpha that aren't already here yet - so a
# run after several days offline just catches up on all of them, not
# just the latest. Verifies each dump's sha256 checksum after pulling
# it, and only then deletes the alpha-side copy - a failed/incomplete
# verification leaves the remote copy alone for a retry next run.
#
# Uses SFTP (not real rsync) because the alpha-side account is
# restricted to `ForceCommand internal-sftp` - that blocks a real
# `rsync --server` invocation, so plain SFTP get/rm is the compatible
# transport here.
#
# Stored on the WSD2L840 Unassigned Device (not the array), which isn't
# guaranteed to already be mounted - this script mounts it itself first
# and refuses to proceed if that didn't actually result in a real
# mountpoint, rather than risk silently writing backups onto the array.

set -u
KEY=/root/.ssh/gitea-backup/id_ed25519
KNOWN_HOSTS=/root/.ssh/gitea-backup/known_hosts
REMOTE=giteabackup@alpha.jayfield.org
REMOTE_PORT=10022
UD_LABEL=WSD2L840
UD_MOUNTPOINT=/mnt/disks/WSD2L840
LOCAL_DIR="$UD_MOUNTPOINT/git"
KEEP=30

# WSD2L840 is an Unassigned Device, not part of the array - not
# guaranteed to already be mounted when this runs. Resolve by label
# (not a hardcoded /dev/sdX - USB/UD device paths aren't stable across
# reboots) and mount it via the Unassigned Devices plugin's own tool,
# which is safe to call even if it's already mounted.
UD_DEV=$(findfs LABEL="$UD_LABEL" 2>/dev/null)
if [ -z "$UD_DEV" ]; then
	echo "$(date -Is) ERROR: no device labeled $UD_LABEL found - is it plugged in?" >&2
	exit 1
fi

/usr/local/sbin/rc.unassigned mount "$UD_DEV" >/dev/null 2>&1

if ! mountpoint -q "$UD_MOUNTPOINT"; then
	echo "$(date -Is) ERROR: $UD_MOUNTPOINT did not come up as a real mountpoint after mount attempt - refusing to write backups onto the array by mistake" >&2
	exit 1
fi

mkdir -p "$LOCAL_DIR"

SFTP="sftp -P $REMOTE_PORT -i $KEY -o UserKnownHostsFile=$KNOWN_HOSTS -o BatchMode=yes"

REMOTE_LISTING=$($SFTP "$REMOTE" <<< 'ls -1 backups/' 2>/dev/null)
if [ -z "$REMOTE_LISTING" ]; then
	echo "$(date -Is) ERROR: could not list remote backups (host down or auth failed)" >&2
	exit 1
fi
REMOTE_ZIPS=$(echo "$REMOTE_LISTING" | grep '\.zip$')

GET_BATCH=$(mktemp)
DEL_BATCH=$(mktemp)
trap 'rm -f "$GET_BATCH" "$DEL_BATCH"' EXIT

TO_FETCH=0
while read -r fname; do
	[ -z "$fname" ] && continue
	base=$(basename "$fname")
	if [ ! -f "$LOCAL_DIR/$base" ]; then
		echo "get $fname $LOCAL_DIR/" >> "$GET_BATCH"
		TO_FETCH=$((TO_FETCH + 1))
		# grab the checksum alongside it if alpha actually has one
		# (older dumps from before checksums existed won't)
		if echo "$REMOTE_LISTING" | grep -qxF "${fname}.sha256"; then
			echo "get ${fname}.sha256 $LOCAL_DIR/" >> "$GET_BATCH"
		fi
	fi
done <<< "$REMOTE_ZIPS"

if [ "$TO_FETCH" -eq 0 ]; then
	echo "$(date -Is) up to date, nothing new to pull"
	exit 0
fi

if ! $SFTP -b "$GET_BATCH" "$REMOTE" 2>&1; then
	echo "$(date -Is) ERROR: sftp batch fetch failed partway through" >&2
	exit 1
fi

# verify each freshly-fetched dump before trusting it enough to delete
# the alpha-side copy
VERIFIED=0
UNVERIFIED=0
while read -r fname; do
	[ -z "$fname" ] && continue
	base=$(basename "$fname")
	local_zip="$LOCAL_DIR/$base"
	local_sum="$LOCAL_DIR/$base.sha256"
	[ -f "$local_zip" ] || continue   # wasn't newly fetched this run

	if [ ! -f "$local_sum" ]; then
		echo "$(date -Is) WARNING: no checksum available for $base (older dump, predates checksums?) - keeping local copy, leaving alpha's copy alone" >&2
		UNVERIFIED=$((UNVERIFIED + 1))
		continue
	fi

	if (cd "$LOCAL_DIR" && sha256sum -c "$(basename "$local_sum")") >/dev/null 2>&1; then
		echo "rm $fname" >> "$DEL_BATCH"
		echo "rm ${fname}.sha256" >> "$DEL_BATCH"
		VERIFIED=$((VERIFIED + 1))
	else
		echo "$(date -Is) ERROR: checksum mismatch for $base - deleting the bad local copy, leaving alpha's copy for a retry" >&2
		rm -f "$local_zip" "$local_sum"
		UNVERIFIED=$((UNVERIFIED + 1))
	fi
done <<< "$REMOTE_ZIPS"

if [ -s "$DEL_BATCH" ]; then
	if ! $SFTP -b "$DEL_BATCH" "$REMOTE" 2>&1; then
		echo "$(date -Is) WARNING: some verified files could not be deleted on alpha - harmless, alpha's own retention prune will clean them up eventually" >&2
	fi
fi

# prune to the last $KEEP archives (vlda-01 has far more headroom than
# alpha, so this is the real long-term history now that alpha deletes
# its own copies right after a verified pull)
cd "$LOCAL_DIR" || exit 1
ls -1t gitea-dump-*.zip 2>/dev/null | tail -n +$((KEEP + 1)) | while read -r old; do
	rm -f "$old" "${old}.sha256"
done

echo "$(date -Is) pulled $TO_FETCH new archive(s), $VERIFIED verified+deleted from alpha, $UNVERIFIED kept unverified, $(ls -1 gitea-dump-*.zip 2>/dev/null | wc -l) retained locally"
