#!/usr/bin/env bash
# Runs ON debian1, against the disposable danger-lab VM — never against a real box.
# Reproduces the change that took debian2 and debian3 off the network and proves the
# LAN recovery door is still there afterwards.
#
#   modules/buildbox/test/rescue-door-lab-proof.sh /path/to/modules/buildbox/host-config
set -uo pipefail

LAB=/home/user/dangerlab
HC="${1:?usage: rescue-door-lab-proof.sh /path/to/host-config}"
for f in bin/buildbox-rescue-door rescue-door/sshd_config \
         systemd-system/buildbox-rescue-sshd.socket systemd-system/buildbox-rescue-sshd@.service; do
  [ -r "$HC/$f" ] || { echo "missing $HC/$f" >&2; exit 2; }
done
# shellcheck source=/dev/null
source "$LAB/dangerlab-lib.sh"

pass=0; fail=0
ok()  { pass=$((pass+1)); printf 'ok   %s\n' "$1"; }
bad() { fail=$((fail+1)); printf 'FAIL %s\n' "$1"; }
g()      { $SSH lab@"$IP" "$@"; }
rescue() { $SSH -p 2223 lab@"$IP" "$@"; }

push() { scp -q -i "$KEY" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
           -o LogLevel=ERROR "$1" lab@"$IP":"$2"; }

fresh_vm() {
  IP=""
  note "rolling back to pristine base"
  rollback || die "PRE-RUN ROLLBACK FAILED"
  vm_start
  wait_ssh 180 || die "VM did not boot"
  g 'sudo -n true' || die "guest has no passwordless sudo"
  # The guest's stock sshd stands in for the converged 2222 listener: same daemon, same
  # config tree, same failure. The port number is not what this proof is about.
  g 'sudo -n mkdir -p /etc/ssh/sshd_config.d && echo "ListenAddress 0.0.0.0" | sudo -n tee /etc/ssh/sshd_config.d/60-lab.conf >/dev/null'
}

install_door() {
  push "$HC/bin/buildbox-rescue-door" /tmp/buildbox-rescue-door
  push "$HC/rescue-door/sshd_config" /tmp/rescue-sshd_config
  push "$HC/systemd-system/buildbox-rescue-sshd.socket" /tmp/buildbox-rescue-sshd.socket
  push "$HC/systemd-system/buildbox-rescue-sshd@.service" /tmp/buildbox-rescue-sshd@.service
  g 'sudo -n install -m 0755 /tmp/buildbox-rescue-door /usr/local/sbin/buildbox-rescue-door
     sudo -n install -m 0644 /tmp/buildbox-rescue-sshd.socket /etc/systemd/system/buildbox-rescue-sshd.socket
     sudo -n install -m 0644 "/tmp/buildbox-rescue-sshd@.service" "/etc/systemd/system/buildbox-rescue-sshd@.service"
     sudo -n systemctl daemon-reload
     sudo -n /usr/local/sbin/buildbox-rescue-door /tmp/rescue-sshd_config' \
    || { g 'sudo -n journalctl -u buildbox-rescue-sshd.socket --no-pager -n 30'; return 1; }
}

# The exact shape of the change that took debian2 off the network: a ListenAddress that
# does not exist at cold boot, and the removal of the only all-interfaces door. Not
# reloaded — on debian2 this was harmless while sshd kept running, which is why nothing
# caught it until the box was powered off hours later.
apply_bricking_change() {
  g 'echo "ListenAddress 100.64.99.99" | sudo -n tee /etc/ssh/sshd_config.d/10-tailscale.conf >/dev/null
     sudo -n rm -f /etc/ssh/sshd_config.d/60-lab.conf' >/dev/null 2>&1
}

reboot_guest() { # prints nothing; returns after the boot id changed or 300s
  local before after deadline
  before="$(g 'cat /proc/sys/kernel/random/boot_id' 2>/dev/null | tr -d '\r\n')"
  g 'sudo -n systemctl reboot' >/dev/null 2>&1
  deadline=$(( $(date +%s) + 300 ))
  while [ "$(date +%s)" -lt "$deadline" ]; do
    sleep 5
    after="$(rescue 'cat /proc/sys/kernel/random/boot_id' 2>/dev/null | tr -d '\r\n')"
    [ -n "$after" ] && [ "$after" != "$before" ] && { echo "$after"; return 0; }
  done
  echo ""
}

echo "=== scenario A: the door survives ssh.service being stopped ==="
fresh_vm
install_door || bad "install"
rescue true 2>/dev/null && ok "rescue door answers on 2223 while the primary sshd is up" \
  || bad "rescue door did not answer on 2223"
# /run/sshd is ssh.service's RuntimeDirectory=, so systemd deletes it here. A door that
# needed that directory to survive would stop accepting sessions at exactly this moment.
g 'sudo -n systemctl stop ssh' >/dev/null 2>&1
g true 2>/dev/null && bad "primary sshd still answering after stop — scenario is not testing anything" \
  || ok "primary sshd is down"
rescue 'echo alive' 2>/dev/null | grep -qx alive \
  && ok "rescue door still serves new sessions with ssh.service stopped and /run/sshd removed" \
  || bad "rescue door died with ssh.service — it shares the privilege separation directory"

echo
echo "=== scenario B: the debian2 change, then a cold boot ==="
fresh_vm
install_door || bad "install"
apply_bricking_change
g 'test -f /etc/ssh/sshd_config.d/10-tailscale.conf' && ok "bricking change is in place" \
  || bad "change not applied"

BID="$(reboot_guest)"
if [ -z "$BID" ]; then
  bad "guest never came back on 2223 after the bricking reboot — the recovery door did not survive"
else
  ok "guest answered on the rescue door after a reboot it could not survive on the primary door"
fi

if [ -n "$BID" ]; then
  if $SSH lab@"$IP" true 2>/dev/null; then
    bad "primary sshd still answers — the reproduction did not brick the primary door"
  else
    ok "primary sshd is dead, exactly as debian2 and debian3 are"
  fi
  rescue 'sudo -n systemctl is-active ssh' 2>/dev/null | grep -qx active \
    && bad "ssh.service is active — the primary door was not actually broken" \
    || ok "ssh.service is not active, and the box is reachable anyway"
  # The point of the door: from it, the break is repairable without a console.
  rescue 'sudo -n rm -f /etc/ssh/sshd_config.d/10-tailscale.conf && sudo -n systemctl reset-failed ssh && sudo -n systemctl restart ssh' >/dev/null 2>&1
  $SSH lab@"$IP" true 2>/dev/null && ok "primary door repaired through the rescue door, no console needed" \
    || bad "could not repair the primary door through the rescue door"
fi

echo
note "rolling back"
rollback || die "POST-RUN ROLLBACK FAILED — VM is dirty, fix before next run"
printf '\nPASS=%d FAIL=%d\n' "$pass" "$fail"
[ "$fail" -eq 0 ]
