#!/usr/bin/env bash
# Installs Bearer runtime artifacts sent by capy-bearer-distribute.
#
# Runs as the forced command of the distributor's SSH key. It reads a tar
# stream of bin/bearer_fastcgi.linux.bin, bin/capyc, and bin/wasm/core.wasm
# from stdin, checks the application sources with the new compiler, and only
# then swaps the files and restarts the service. A failed health check restores
# the previous files.
set -uo pipefail

CONFIG=/etc/default/capy-bearer-receive
# shellcheck disable=SC1090
[[ -r $CONFIG ]] && . "$CONFIG"

RUNTIME=${RUNTIME:-/opt/capy-bearer}
APPLICATION=${APPLICATION:-}
SERVICE=${SERVICE:-bearer}
HEALTH_URL=${HEALTH_URL:-http://127.0.0.1/}
HEALTH_HOST=${HEALTH_HOST:-}
BACKUP=${BACKUP:-/var/backups/capy-bearer}
FILES=(bin/bearer_fastcgi.linux.bin bin/capyc bin/wasm/core.wasm)

# The distributor's updater log adds the timestamp.
log() { printf '%s\n' "$*" >&2; }
fail() { log "FAILED: $*"; exit 1; }

# The distributor runs `ssh target capy-bearer-receive REVISION`; sshd hands
# that line to this forced command through SSH_ORIGINAL_COMMAND.
read -r _ revision _ <<<"${SSH_ORIGINAL_COMMAND:-receive unknown}"
[[ $revision =~ ^[0-9a-f]{7,40}$ ]] || fail "bad revision '$revision'"

exec 9>/var/lock/capy-bearer-receive.lock
flock -n 9 || fail "another install is running"

incoming=$(mktemp -d /tmp/capy-bearer-receive.XXXXXX) || fail "mktemp"
trap 'rm -rf "$incoming"' EXIT
tar -C "$incoming" -x || fail "tar"
for file in "${FILES[@]}"; do
	[[ -s $incoming/$file ]] || fail "missing $file in stream"
done
chmod 0755 "$incoming/bin/capyc" "$incoming/bin/bearer_fastcgi.linux.bin"

if [[ -n $APPLICATION ]]; then
	# Check the tracked units only. Scratch files under the application tree are
	# not the application.
	if git -C "$APPLICATION" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
		# A tracked file that is gone, for example mid-move in a work tree, is not a unit to check.
		mapfile -t units < <(git -C "$APPLICATION" ls-files -z -- '*.capy' | tr '\0' '\n' | sed "s#^#$APPLICATION/#" | while read -r unit; do [[ -f $unit ]] && printf '%s\n' "$unit"; done)
	else
		mapfile -t units < <(find "$APPLICATION" -name '*.capy' -not -path '*/tmp/*')
	fi
	check=$("$incoming/bin/capyc" --check --source-root "$APPLICATION" "${units[@]}" 2>&1) || fail "application check with $revision:"$'\n'"$check"
	log "application check passed for $revision (${#units[@]} units)"
fi

stamp=$(date +%Y%m%dT%H%M%S)
previous="$BACKUP/$stamp"
mkdir -p "$previous/bin/wasm" || fail "backup directory"
for file in "${FILES[@]}"; do
	cp -a "$RUNTIME/$file" "$previous/$file" || fail "backup $file"
done
cat "$RUNTIME/bin/REVISION" 2>/dev/null >"$previous/revision"

restore() {
	log "restoring previous artifacts from $previous"
	for file in "${FILES[@]}"; do
		cp -a "$previous/$file" "$RUNTIME/$file"
	done
	cp -a "$previous/revision" "$RUNTIME/bin/REVISION" 2>/dev/null
	systemctl restart "$SERVICE"
}

owner=$(stat -c %U:%G "$RUNTIME/bin/capyc")
for file in "${FILES[@]}"; do
	mode=0644
	[[ $file == *.bin || $file == */capyc ]] && mode=0755
	install -m "$mode" -o "${owner%:*}" -g "${owner#*:}" "$incoming/$file" "$RUNTIME/$file" || { restore; fail "install $file"; }
done
printf '%s\n' "$revision" >"$RUNTIME/bin/REVISION"

systemctl restart "$SERVICE" || { restore; fail "restart"; }
code=""
for _ in $(seq 1 30); do
	code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' ${HEALTH_HOST:+-H "Host: $HEALTH_HOST"} "$HEALTH_URL" || true)
	[[ $code == 200 ]] && break
	sleep 3
done
if [[ $code != 200 ]]; then
	restore
	fail "health check returned $code after restart"
fi
log "installed $revision, health 200"
ls -1d "$BACKUP"/*/ 2>/dev/null | head -n -5 | xargs -r rm -rf
