#!/usr/bin/env bash
# ssh-e2e-test.sh — SPIKE de validación (sucio, temporal) del Paso 1: probar el handshake SSH REAL
# contra un hammer booteado en QEMU, con sshd supervisado por arje-zero como genesis card.
#
# Esto NO es plomería de producto: arma un `work/rootfs-ssh-test` desechable = el 4/4 limpio del
# store + openssh + netup (todos PRECOMPILADOS de la caché del lab, no rebuild) + una seed con un
# card `sshd`, y lo bootea como initramfs. La red es QEMU slirp con hostfwd :2222->:22, así que el
# host hace `ssh -p 2222 root@localhost` SIN tocar firewall/bridge (la lección de netup). El chroot
# de privsep —que el bwrap anidado no pudo— acá corre como root REAL del guest.
#
# Quema tres incógnitas que el sandbox de bwrap dejó abiertas:
#   1. el handshake completo (post-chroot, post-setuid sshd-user) ocurre,
#   2. arje maneja el ciclo de vida del daemon (Restart) como cualquier card Native,
#   3. los fds de red de sshd no segfaultean contra el musl del target.
#
# Variables: KERNEL (def store/*-linux/boot/bzImage) MEM (def 2048) KVM (1 si /dev/kvm)
#            PORT (hostfwd, def 2222) DEADLINE (def 150s)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}"
MEM="${MEM:-2048}"
KVM="${KVM:-1}"
PORT="${PORT:-2222}"
DEADLINE="${DEADLINE:-150}"
WORK="$ROOT/work/ssh-e2e"
RFS="$ROOT/work/rootfs-ssh-test"
LOG="$WORK/console.log"

pick() { ls -d store/*-"$1" 2>/dev/null | head -1; }
MUSL=$(pick musl); BB=$(pick busybox); ARJE="store/69e2cab8ac00c33cde7680eb393952e2c7f9ae9af24c180c6593d7a86482efb9-arje-zero"
NETUP=$(pick netup); OSSH=$(pick openssh)
for v in KERNEL MUSL BB ARJE NETUP OSSH; do
  p="${!v}"; [ -n "$p" ] && [ -e "$p" ] || { echo "falta $v ($p)"; exit 1; }
done
echo "==> kernel $KERNEL"
echo "==> componentes: musl busybox arje-zero netup openssh (precompilados del store)"

# ── 1) ensamblar el rootfs desechable ───────────────────────────────────────────────────────────
rm -rf "$RFS" "$WORK"; mkdir -p "$RFS" "$WORK"
for src in "$MUSL" "$BB" "$ARJE" "$NETUP" "$OSSH"; do
  cp -a "$src"/. "$RFS"/ 2>/dev/null || true
done
chmod -R u+w "$RFS"            # el store está sellado read-only; el staging debe ser escribible
rm -rf "$RFS/.hammer"
mkdir -p "$RFS"/{proc,sys,dev,dev/pts,run,tmp,etc,etc/ssh,root,root/.ssh,var,var/empty,ente,sbin,bin,usr/bin}
chmod 0711 "$RFS/var/empty"
ln -sf /usr/bin/arje-zero "$RFS/sbin/init"
# busybox a /bin/sh + applets que la getty/seed necesitan (por si el cp no dejó symlinks en /bin)
[ -e "$RFS/bin/busybox" ] || cp -a "$BB"/bin/busybox "$RFS/bin/busybox"
for a in sh getty; do ln -sf busybox "$RFS/bin/$a"; done

# usuarios: root (login por llave) + sshd (privsep). shells reales para que sshd ejecute el comando.
cat > "$RFS/etc/passwd" <<'EOF'
root:x:0:0:root:/root:/bin/sh
sshd:x:74:74:sshd privsep:/var/empty:/bin/false
EOF
cat > "$RFS/etc/group" <<'EOF'
root:x:0:
sshd:x:74:
EOF
cat > "$RFS/etc/ssh/sshd_config" <<'EOF'
Port 22
ListenAddress 0.0.0.0
PermitRootLogin yes
PubkeyAuthentication yes
PasswordAuthentication no
AuthorizedKeysFile /root/.ssh/authorized_keys
StrictModes no
Subsystem sftp /usr/libexec/sftp-server
EOF

# ── 2) llave de cliente desechable (host) → authorized_keys del guest ────────────────────────────
"$OSSH/usr/bin/ssh-keygen" -t ed25519 -N '' -f "$WORK/clientkey" -q
cat "$WORK/clientkey.pub" > "$RFS/root/.ssh/authorized_keys"
chmod 0600 "$RFS/root/.ssh/authorized_keys"

# ── 3) seed card: console-getty + un card `sshd` (Native, Restart) ───────────────────────────────
# El card sshd arranca la red (netup, DHCP slirp), genera host keys (ssh-keygen -A) y exec sshd -D.
# Mismo esquema validado de STAGE1_SEED_CARD (hammer-bootstrap); ULIDs nuevos A10/A11/A12.
SSHD_CMD='/usr/bin/netup; /usr/bin/ssh-keygen -A; exec /usr/sbin/sshd -D -e'
cat > "$RFS/ente/seed.card.json" <<EOF
{
  "schema_version": 1,
  "id": "01HQAR53D4M2NBV8KZTYXFQA10",
  "lineage": null,
  "label": "hammer-ssh-spike",
  "provides": ["Spawn", "Journal"],
  "requires": [],
  "permissions": { "networking": "full", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
  "soma": {
    "namespaces": { "mount": false, "pid": false, "net": false, "uts": false, "ipc": false, "user": false, "cgroup": false },
    "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
    "cgroup": { "path": "arje.slice/hammer", "cpu_weight": null, "io_weight": null },
    "cpu_affinity": null
  },
  "payload": "Virtual",
  "supervision": "OneShot",
  "lifecycle": "daemon",
  "priority": "normal",
  "flow": { "input": [], "output": [] },
  "genesis": [
    {
      "schema_version": 1,
      "id": "01HQAR53D4M2NBV8KZTYXFQA11",
      "lineage": null,
      "label": "console-getty",
      "provides": [],
      "requires": [],
      "permissions": { "networking": "none", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
      "soma": {
        "namespaces": { "mount": false, "pid": false, "net": false, "uts": false, "ipc": false, "user": false, "cgroup": false },
        "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
        "cgroup": { "path": "arje.slice/getty", "cpu_weight": null, "io_weight": null },
        "cpu_affinity": null
      },
      "payload": { "Native": { "exec": "/bin/busybox", "argv": ["getty", "-n", "-l", "/bin/sh", "115200", "console"], "envp": [] }},
      "supervision": { "Restart": { "initial": 200, "max": 10000 } },
      "lifecycle": "daemon",
      "priority": "normal",
      "flow": { "input": [], "output": [] },
      "genesis": []
    },
    {
      "schema_version": 1,
      "id": "01HQAR53D4M2NBV8KZTYXFQA12",
      "lineage": null,
      "label": "sshd",
      "provides": [],
      "requires": [],
      "permissions": { "networking": "full", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
      "soma": {
        "namespaces": { "mount": false, "pid": false, "net": false, "uts": false, "ipc": false, "user": false, "cgroup": false },
        "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
        "cgroup": { "path": "arje.slice/sshd", "cpu_weight": null, "io_weight": null },
        "cpu_affinity": null
      },
      "payload": { "Native": { "exec": "/bin/busybox", "argv": ["sh", "-c", "${SSHD_CMD}"], "envp": [["HOME", "/root"]] }},
      "supervision": { "Restart": { "initial": 500, "max": 20000 } },
      "lifecycle": "daemon",
      "priority": "normal",
      "flow": { "input": [], "output": [] },
      "genesis": []
    }
  ]
}
EOF

# ── 4) empaquetar initramfs ──────────────────────────────────────────────────────────────────────
# -R 0:0: el cpio corre como uid no-root; sin esto el rootfs queda con nuestro uid y sshd rechaza
# /var/empty ("must be owned by root"). Reasigna todo a root:root dentro del archivo.
( cd "$RFS" && find . -print0 | cpio --null -o -H newc -R 0:0 2>/dev/null | gzip -1 ) > "$WORK/initramfs.cpio.gz"
echo "==> initramfs $(du -h "$WORK/initramfs.cpio.gz" | cut -f1)"

# ── 5) boot QEMU (slirp + hostfwd) en background ─────────────────────────────────────────────────
accel=(-cpu Broadwell)
if [ "$KVM" = 1 ] && [ -w /dev/kvm ]; then accel=(-enable-kvm -cpu host); fi
echo "==> boot QEMU (mem ${MEM}, kvm $([ "${accel[0]}" = -enable-kvm ] && echo sí || echo no)), hostfwd :$PORT->:22"
qemu-system-x86_64 -m "$MEM" -no-reboot -nographic "${accel[@]}" \
  -kernel "$KERNEL" -initrd "$WORK/initramfs.cpio.gz" \
  -append "console=ttyS0 rdinit=/sbin/init" \
  -netdev "user,id=n0,hostfwd=tcp::$PORT-:22" -device e1000,netdev=n0 \
  > "$LOG" 2>&1 &
QPID=$!
trap 'kill $QPID 2>/dev/null || true' EXIT

# ── 6) esperar a sshd y hacer el handshake real ──────────────────────────────────────────────────
SSHOPTS=(-p "$PORT" -i "$WORK/clientkey" -o StrictHostKeyChecking=no
         -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null
         -o PasswordAuthentication=no -o ConnectTimeout=4 -o LogLevel=ERROR)
echo "==> esperando handshake (deadline ${DEADLINE}s)…"
start=$(date +%s); ok=0
while [ $(( $(date +%s) - start )) -lt "$DEADLINE" ]; do
  if ! kill -0 $QPID 2>/dev/null; then echo "!! QEMU murió"; break; fi
  if out=$(ssh "${SSHOPTS[@]}" root@localhost \
        'echo HANDSHAKE_OK uid=$(id -u) user=$(id -un); uname -srm; cat /etc/os-release 2>/dev/null | head -1' 2>/dev/null); then
    echo "================ RESPUESTA DEL GUEST ================"
    echo "$out"
    echo "===================================================="
    echo "$out" | grep -q HANDSHAKE_OK && ok=1
    break
  fi
  sleep 3
done

# ── 7) veredicto + diagnóstico ───────────────────────────────────────────────────────────────────
echo "---- consola (cola) ----"; tail -25 "$LOG" 2>/dev/null
if [ "$ok" = 1 ]; then
  echo; echo "*** SSH E2E OK — handshake real contra hammer+arje, sshd como card ***"; exit 0
else
  echo; echo "*** SSH E2E FALLÓ — ver $LOG ***"; exit 1
fi
