#!/usr/bin/env bash
# dangerlab-run — run a dangerous test in a single-use clone of the frozen base image.
source /home/user/dangerlab/dangerlab-lib.sh

usage() {
  cat <<'EOF'
dangerlab-run [--payload DIR] [--timeout SECS] -- <command...>

  /home/user/dangerlab/dangerlab-run --payload ./modules -- bash tests/foo.test.sh

Verifies the frozen base image against its sha256 pin, waits for a free clone slot,
builds a fresh qcow2 overlay clone of that base, boots it, copies --payload (a dir or
file) to /home/lab/payload in the clone, runs <command> there under a host-enforced
timeout, prints the guest's stdout/stderr, and exits with the guest's real exit code.
The clone is destroyed and its overlay discarded when the run ends, however it ends.

Nobody works on the template: it is never booted and never written. A base whose
checksum does not match the pin ERRORS instead of producing clones.

Concurrency is capped by the number of slots; a caller beyond the cap WAITS for a slot
rather than overcommitting the host. A clone whose owner dies is collected by
dangerlab-reaper.timer, not by the owner.

On timeout the clone is force-destroyed and the host-side serial console log is dumped
— it is the only output that survives a guest that stopped responding to ssh.

  --payload DIR   host path copied INTO /home/lab/payload, keeping its basename
                  (--payload ./claude => /home/lab/payload/claude); cwd for <command>
                  is /home/lab/payload
  --timeout SECS  wall-clock cap on the in-guest command (default: 300)
  --keep          skip clone destruction so the wreckage can be inspected; the reaper
                  collects it anyway once the pin expires
  --slots         print slot occupancy and exit

exit code: the guest's exit code; 124 on timeout; 255 when the guest died without
returning one (printed as unrecovered(ssh=NNN)).
EOF
}

PAYLOAD=""; TMO=300; KEEP=0
while [[ $# -gt 0 ]]; do
  case "$1" in
    --payload) PAYLOAD="$2"; shift 2 ;;
    --timeout) TMO="$2"; shift 2 ;;
    --keep) KEEP=1; shift ;;
    --slots) exec /home/user/dangerlab/dangerlab slots ;;
    -h|--help) usage; exit 0 ;;
    --) shift; break ;;
    *) usage >&2; exit 2 ;;
  esac
done
[[ $# -gt 0 ]] || { usage >&2; exit 2; }
[[ -z "$PAYLOAD" || -e "$PAYLOAD" ]] || die "payload not found: $PAYLOAD"
[[ "$TMO" =~ ^[0-9]+$ ]] || die "--timeout must be seconds, got: $TMO"

LAB_KEEP=$KEEP
lab_acquire "$TMO"
SLOT="$LAB_SLOT"; IP="$LAB_IP"

lab_ssh 'rm -rf /home/lab/payload && mkdir -p /home/lab/payload' \
  || die "slot$SLOT: could not prepare payload dir"
if [[ -n "$PAYLOAD" ]]; then
  lab_scp "$PAYLOAD" /home/lab/payload/ || die "slot$SLOT: payload copy failed"
fi

CMD="$*"
note "slot$SLOT ($IP): running (timeout ${TMO}s): $CMD"
OUT=$(mktemp); ERR=$(mktemp)
timeout --foreground "$TMO" $SSH -o ServerAliveInterval=10 -o ServerAliveCountMax=3 \
  lab@"$IP" "cd /home/lab/payload && { $CMD; } >/tmp/dl.out 2>/tmp/dl.err; echo \$? >/tmp/dl.rc; \
             cat /tmp/dl.out; echo '---DANGERLAB-STDERR---' >&2; cat /tmp/dl.err >&2; \
             echo \"---DANGERLAB-RC---\$(cat /tmp/dl.rc)\"" >"$OUT" 2>"$ERR"
SSH_RC=$?

RC=$(sed -n 's/^---DANGERLAB-RC---//p' "$OUT" | tail -1)
if [[ $SSH_RC -ne 124 ]]; then
  sed '/^---DANGERLAB-RC---/d' "$OUT"
  printf '=== stderr ===\n'; sed '/^---DANGERLAB-STDERR---$/d' "$ERR"
fi
# The guest can die without exceeding the wall clock (panic, wedged sshd, reset).
# Any run whose exit code did not come back is a forensics case, not just a timeout.
EXIT_RC=0
if [[ $SSH_RC -eq 124 || -z "$RC" ]]; then
  domain_kill "$(slot_dom "$SLOT")"
  if [[ $SSH_RC -eq 124 ]]; then
    printf '=== TIMEOUT after %ss — clone force-destroyed ===\n' "$TMO"
    RC=timeout; EXIT_RC=124
  else
    printf '=== guest exit code never returned (ssh=%s) — clone force-destroyed ===\n' "$SSH_RC"
    RC="unrecovered(ssh=$SSH_RC)"; EXIT_RC=255
  fi
  printf '=== host serial console tail ===\n'
  sudo -n tail -60 "$(slot_console "$SLOT")" 2>/dev/null
else
  EXIT_RC="$RC"
fi
printf '=== guest exit code: %s ===\n' "$RC"
rm -f "$OUT" "$ERR"

exit "$EXIT_RC"
