hammer-bootstrap: capa de servicios (sshd como producto) — Etapa C Paso 2
Consolidación arquitectónica de sshd-como-servicio (Separación Mecanismo/ Política, decisión del usuario). El núcleo NO se toca: STAGE1_COMPONENTS + STAGE1_SEED_CARD siguen siendo el mecanismo base atómico que el selfhost-verify reconstruye bit a bit (of_tree 9adefb82/7fa6cb4e blindado). Nuevo en hammer-bootstrap: - SERVICE_COMPONENTS = [netup, openssh] (política de producto). - SSHD_SERVICE_CARD: card genesis Native/Restart (netup + ssh-keygen -A + exec sshd -D), validado E2E en QEMU. - product_seed_card(): compone la seed de producto = seed base + cards de servicio apendados al genesis vía serde_json (hammer sigue autocontenido, sin dep de card-core). El núcleo (hammerd+getty) se preserva. - assemble_product_rootfs() + product(): HIDRATACIÓN TARDÍA — hidrata el stage1-rootfs ya sellado (4/4 verificado) + inyecta openssh/netup encima + escribe configs (passwd con sshd, sshd_config con PidFile /run, /var/empty 0711, /root/.ssh) y sella un `product-rootfs` APARTE. of_tree del núcleo intacto. Idempotente, reproducible. GOTCHA: los ficheros hidratados son hardlinks read-only al store ⇒ romper el hardlink (remove+write) en vez de chmod (mutaría el inodo del store). - CLI: `hammer bootstrap product --rootfs <base> --recipes recipes`. - 4 tests nuevos (seed compone, hash determinista, assemble inyecta, recetas de servicio parsean). 36/36 verde. scripts/product-boot-test.sh: valida que el product-rootfs de la RUTA REAL bootea en QEMU y sirve SSH (sólo provisiona authorized_keys de prueba, no ensambla nada). VERDE: arje levanta hammerd+getty+sshd, handshake real "Accepted publickey for root", guest responde (seed=hammer-product con 3 cards, Linux 6.16.12). Cierra el agujero de verificación con la arquitectura final, no con el spike sucio. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# product-boot-test.sh — valida que el `product-rootfs` ENSAMBLADO POR LA RUTA REAL
|
||||
# (`hammer bootstrap product`) bootea en QEMU y sirve SSH. A diferencia de ssh-e2e-test.sh (spike
|
||||
# que arma un rootfs sucio a mano), acá NO se ensambla nada: se toma el artefacto sellado tal cual
|
||||
# (seed de producto, passwd, sshd_config, /var/empty, sshd como card — todo ya inyectado por la capa
|
||||
# de servicios) y sólo se provisiona una authorized_keys de prueba (lo que en deploy haría el admin).
|
||||
#
|
||||
# Prueba el axioma de la consolidación: el mismo árbol que produce el pipeline arranca el daemon SSH
|
||||
# bajo arje sin tocar el núcleo verificado.
|
||||
#
|
||||
# Uso: PRODUCT=<hash-o-prefijo> ./scripts/product-boot-test.sh
|
||||
# (PRODUCT = el que imprimió `hammer bootstrap product`; default: el más reciente del store)
|
||||
# Vars: KERNEL MEM (def 2048) KVM (def 1) PORT (def 2223) DEADLINE (def 150)
|
||||
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:-2223}"; DEADLINE="${DEADLINE:-150}"
|
||||
WORK="$ROOT/work/product-boot"; RFS="$WORK/rootfs"; LOG="$WORK/console.log"
|
||||
|
||||
# localizar el product-rootfs sellado (por prefijo, o el más reciente)
|
||||
if [ -n "${PRODUCT:-}" ]; then
|
||||
PDIR=$(ls -d store/"${PRODUCT#b3:}"*-product-rootfs 2>/dev/null | head -1)
|
||||
else
|
||||
PDIR=$(ls -dt store/*-product-rootfs 2>/dev/null | head -1)
|
||||
fi
|
||||
[ -n "$PDIR" ] && [ -d "$PDIR" ] || { echo "no encuentro product-rootfs (corré 'hammer bootstrap product')"; exit 1; }
|
||||
[ -n "$KERNEL" ] && [ -e "$KERNEL" ] || { echo "no encuentro kernel"; exit 1; }
|
||||
echo "==> product-rootfs: $PDIR"
|
||||
echo "==> kernel: $KERNEL"
|
||||
|
||||
# copia escribible del artefacto sellado (read-only); NO modificamos el store
|
||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||
cp -a "$PDIR"/. "$RFS"/
|
||||
chmod -R u+w "$RFS"
|
||||
rm -rf "$RFS/.hammer"
|
||||
|
||||
# provisión de admin: una authorized_keys de prueba (única cosa que el producto NO trae horneada)
|
||||
"$(ls store/*-openssh/usr/bin/ssh-keygen | head -1)" -t ed25519 -N '' -f "$WORK/clientkey" -q
|
||||
install -d -m 0700 "$RFS/root/.ssh"
|
||||
cat "$WORK/clientkey.pub" > "$RFS/root/.ssh/authorized_keys"
|
||||
chmod 0600 "$RFS/root/.ssh/authorized_keys"
|
||||
|
||||
# sanity: el árbol de producto trae lo que esperamos (sin ensamblarlo acá)
|
||||
echo "==> chequeo de contenido del artefacto:"
|
||||
for f in usr/sbin/sshd usr/bin/netup etc/ssh/sshd_config etc/passwd ente/seed.card.json; do
|
||||
[ -e "$RFS/$f" ] && echo " ok $f" || { echo " FALTA $f"; exit 1; }
|
||||
done
|
||||
grep -q '"label": "sshd"' "$RFS/ente/seed.card.json" && echo " ok card sshd en la seed" || { echo " FALTA card sshd"; exit 1; }
|
||||
|
||||
# empaquetar (root-owned) y bootear
|
||||
( cd "$RFS" && find . -print0 | cpio --null -o -H newc -R 0:0 2>/dev/null | gzip -1 ) > "$WORK/initramfs.cpio.gz"
|
||||
accel=(-cpu Broadwell); { [ "$KVM" = 1 ] && [ -w /dev/kvm ]; } && accel=(-enable-kvm -cpu host)
|
||||
echo "==> boot QEMU (mem ${MEM}), 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
|
||||
|
||||
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
|
||||
kill -0 $QPID 2>/dev/null || { echo "!! QEMU murió"; break; }
|
||||
if out=$(ssh "${SSHOPTS[@]}" root@localhost \
|
||||
'echo PRODUCT_SSH_OK uid=$(id -u); cat /ente/seed.card.json | grep -o "\"label\": \"[a-z-]*\"" | tr "\n" " "; echo; uname -sr' 2>/dev/null); then
|
||||
echo "============== RESPUESTA DEL GUEST =============="; echo "$out"; echo "================================================"
|
||||
echo "$out" | grep -q PRODUCT_SSH_OK && ok=1; break
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
echo "---- consola (cola) ----"; tail -18 "$LOG" 2>/dev/null
|
||||
[ "$ok" = 1 ] && { echo; echo "*** PRODUCT SSH OK — el product-rootfs de la ruta real bootea y sirve SSH ***"; exit 0; }
|
||||
echo; echo "*** PRODUCT SSH FALLÓ — ver $LOG ***"; exit 1
|
||||
Reference in New Issue
Block a user