verificar-arranque: el test de arranque, en un comando y desde el repo
A pedido del usuario: «lo que necesitas de kvm guardalo en el repo, por si en la laptop donde no tengo granja pero actualizo el repo, lo pruebo». `scripts/verificar-arranque.sh` es el punto de entrada: FIJA el artefacto que hay que verificar (b3:9d4d28c7, el product-rootfs con simi en lugar de busybox), comprueba las cinco piezas que hacen falta y, por cada una que falte, dice CÓMO conseguirla — rsync desde el hub/worker, o los dos comandos de bootstrap para reconstruirla. Si todo está, corre el test. Fijar el hash importa: `product-boot-test.sh` sin `PRODUCT` coge «el más reciente del store», que en una máquina ajena puede ser cualquier cosa. Y avisa de la aceleración: con /dev/kvm tarda segundos, sin él va por TCG y tarda MINUTOS — por eso sube el DEADLINE a 600 s. Un test que parece colgado se cancela, y entonces no verifica nada. ── Dos arreglos a product-boot-test.sh que lo hacían no-portable ── 1. EL STORE SE RESUELVE, NO SE SUPONE. Usaba `store/` RELATIVO en cuatro sitios, así que en el hub decía «no encuentro product-rootfs» con 1400 artefactos al lado: ahí el store es /store y el ./store del clon está vacío. Misma enfermedad que tenía poda-fuentes.sh. Ahora honra HAMMER_STORE/TAKANA_STORE/STORE, cae a /store si tiene contenido, y a ./store como último recurso — que es el caso del portátil recién clonado. 2. TESTKEY, porque ssh-keygen NO SIEMPRE PUEDE generar la clave. Medido en la jaula: corre como uid 1001 y su /etc/passwd no tiene ese uid, así que muere con «No user exists for uid 1001». Probados -C, LOGNAME, USER y HOME: los cuatro fallan igual. Con TESTKEY=<ruta> se usa una clave ya hecha. ── Por qué no se verificó donde se hizo el cambio ── Dos razones medidas, ninguna del cambio: no hay qemu-system-x86_64 en la jaula, y tampoco /dev/kvm (una VM de Hetzner no expone virtualización anidada). El guion las nombra las dos en vez de fallar de forma opaca.
Semantic delta
No definition changed: this change is text, documentation or data.
2 files · +175 −4
- scripts/product-boot-test.sh +36 −4
- scripts/verificar-arranque.sh +139 −0
diff --git a/scripts/product-boot-test.sh b/scripts/product-boot-test.sh
index 8e6e01e1..f22a764d 100755
--- a/scripts/product-boot-test.sh
+++ b/scripts/product-boot-test.sh
@@ -13,15 +13,29 @@
# 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)}"
+# ── ⚠ EL STORE SE RESUELVE, NO SE SUPONE (2026-10-22) ──────────────────────────────────────────
+# Esto usaba `store/` RELATIVO en cuatro sitios, y por eso no corría en el hub: ahí el store vive
+# en `/store` y el `./store` del clon está VACÍO, así que el script decía «no encuentro
+# product-rootfs» con 1400 artefactos al lado. Misma enfermedad que tenía `poda-fuentes.sh`.
+#
+# Ahora: `$HAMMER_STORE` / `$TAKANA_STORE` / `$STORE` si están, `/store` si existe, y `./store` como
+# último recurso — que es el caso del portátil con el repo recién clonado.
+STORE_DIR="${HAMMER_STORE:-${TAKANA_STORE:-${STORE:-}}}"
+if [ -z "$STORE_DIR" ]; then
+ if [ -d /store ] && [ -n "$(ls -d /store/*-* 2>/dev/null | head -1)" ]; then STORE_DIR=/store
+ else STORE_DIR="$ROOT/store"; fi
+fi
+[ -d "$STORE_DIR" ] || { echo "no existe el store '$STORE_DIR' (poné \$STORE)"; exit 1; }
+echo "==> store: $STORE_DIR"
+KERNEL="${KERNEL:-$(ls "$STORE_DIR"/*-linux/boot/bzImage "$STORE_DIR"/*-linux-metal/boot/bzImage "$STORE_DIR"/*-linux-generic/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)
+ PDIR=$(ls -d "$STORE_DIR"/"${PRODUCT#b3:}"*-product-rootfs 2>/dev/null | head -1)
else
- PDIR=$(ls -dt store/*-product-rootfs 2>/dev/null | head -1)
+ PDIR=$(ls -dt "$STORE_DIR"/*-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; }
@@ -35,7 +49,25 @@ 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
+# ⚠ **`TESTKEY` existe porque `ssh-keygen` NO SIEMPRE PUEDE generar la clave** (2026-10-22).
+# Medido en la jaula de desarrollo: corre como uid 1001 y su `/etc/passwd` no tiene ese uid, así que
+# ssh-keygen muere con «No user exists for uid 1001» — llama a `getpwuid` para el comentario y no
+# hay forma de sortearlo (probados `-C`, `LOGNAME`, `USER`, `HOME`: los cuatro fallan igual).
+# Con `TESTKEY=<ruta>` se usa una clave ya existente (su `.pub` al lado) y el test corre igual.
+if [ -n "${TESTKEY:-}" ]; then
+ [ -f "$TESTKEY" ] && [ -f "$TESTKEY.pub" ] || { echo "TESTKEY='$TESTKEY': falta la clave o su .pub"; exit 1; }
+ cp "$TESTKEY" "$WORK/clientkey"; cp "$TESTKEY.pub" "$WORK/clientkey.pub"
+ chmod 600 "$WORK/clientkey"
+ echo "==> clave: provista por TESTKEY"
+else
+ KEYGEN="$(ls "$STORE_DIR"/*-openssh/usr/bin/ssh-keygen | head -1)"
+ "$KEYGEN" -t ed25519 -N '' -f "$WORK/clientkey" -q || {
+ echo "!! ssh-keygen no pudo generar la clave de prueba."
+ echo " Si dice «No user exists for uid N», tu entorno no tiene ese uid en /etc/passwd."
+ echo " Salida: pasá una clave ya hecha ⇒ TESTKEY=~/.ssh/id_ed25519 $0"
+ exit 1
+ }
+fi
install -d -m 0700 "$RFS/root/.ssh"
cat "$WORK/clientkey.pub" > "$RFS/root/.ssh/authorized_keys"
chmod 0600 "$RFS/root/.ssh/authorized_keys"
diff --git a/scripts/verificar-arranque.sh b/scripts/verificar-arranque.sh
new file mode 100755
index 00000000..12aa989b
--- /dev/null
+++ b/scripts/verificar-arranque.sh
@@ -0,0 +1,139 @@
+#!/usr/bin/env bash
+# =============================================================================
+# verificar-arranque.sh — «¿ARRANCA la imagen?», en un comando.
+#
+# Uso: ./scripts/verificar-arranque.sh
+# STORE=/ruta/al/store ./scripts/verificar-arranque.sh
+# TESTKEY=~/.ssh/id_ed25519 ./scripts/verificar-arranque.sh
+#
+# ── POR QUÉ EXISTE (2026-10-22) ─────────────────────────────────────────────
+#
+# El 2026-10-22 salió busybox de la raíz de confianza del arranque: el núcleo
+# pasó de `musl+busybox+util-linux+hammerd+arje-zero` a llevar **`simi`** en
+# lugar de busybox, `/bin/sh` pasó a ser un enlace a `/usr/bin/simi`, y
+# `ATTEST_PATHS` cambió `bin/busybox` por `usr/bin/simi`.
+#
+# Eso está verificado hasta donde se puede sin encender una máquina: 42 tests
+# del crate pasan, el stage1 se reconstruye, el árbol sellado tiene lo que debe
+# y busybox ya no está. **Lo que NO está verificado es que ARRANQUE.**
+#
+# Y no se pudo verificar donde se hizo, por dos razones medidas en la jaula de
+# desarrollo —ninguna de ellas es un defecto del cambio—:
+#
+# 1. **no hay `qemu-system-x86_64`** (y tampoco `/dev/kvm`: una VM de Hetzner
+# no expone virtualización anidada);
+# 2. **`ssh-keygen` no puede generar la clave de prueba**: la jaula corre como
+# uid 1001 y su `/etc/passwd` no tiene ese uid, así que muere con
+# «No user exists for uid 1001». Probados `-C`, `LOGNAME`, `USER` y `HOME`:
+# los cuatro fallan igual. Mitigado con `TESTKEY`.
+#
+# ⇒ Este guion existe para que **cualquiera con una máquina normal** —un
+# portátil con qemu— pueda cerrar esa verificación sin reconstruir el contexto
+# de la sesión en que se hizo el cambio. Dice qué le falta y cómo conseguirlo.
+#
+# ── QUÉ VERIFICA, EXACTAMENTE ───────────────────────────────────────────────
+#
+# Que el `product-rootfs` sellado bootea bajo QEMU con `arje-zero` de PID 1 y
+# sirve SSH. Es el axioma de la consolidación: el MISMO árbol que produce el
+# pipeline arranca el daemon sin tocar el núcleo verificado.
+#
+# Si eso pasa con `simi` como `/bin/sh`, el cambio está cerrado: significa que
+# el init arrancó, que la seed card se leyó, que el `exec` de la card de sshd
+# —que es un `sh -c`— lo ejecutó simi, y que la red subió.
+# =============================================================================
+set -uo pipefail
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
+
+# El artefacto que hay que verificar, FIJADO. Sin esto el test coge «el más
+# reciente del store», que en una máquina ajena puede ser cualquier cosa.
+ESPERADO="9d4d28c7fd3f310c15fc6ab4cc52c3da86ccc58b064c5b2dff4612c9ad041dd6"
+STAGE1="f9b550e016881d8541da84a67379ed121c964bd6e6e86f800464df28996de1a0"
+
+echo "══ verificación de arranque · product-rootfs b3:${ESPERADO:0:12}… ══"
+echo
+
+# ── el store ────────────────────────────────────────────────────────────────
+STORE_DIR="${HAMMER_STORE:-${TAKANA_STORE:-${STORE:-}}}"
+if [ -z "$STORE_DIR" ]; then
+ if [ -d /store ] && [ -n "$(ls -d /store/*-* 2>/dev/null | head -1)" ]; then STORE_DIR=/store
+ else STORE_DIR="$ROOT/store"; fi
+fi
+falta=0
+
+if [ ! -d "$STORE_DIR" ] || [ -z "$(ls -d "$STORE_DIR"/*-* 2>/dev/null | head -1)" ]; then
+ echo "✗ no hay store en '$STORE_DIR'"
+ echo " poné \$STORE, o traelo: rsync -aHz --info=progress2 \\"
+ echo " root@<hub-o-worker>:/store/${ESPERADO}-product-rootfs \\"
+ echo " root@<hub-o-worker>:/store/*-linux-metal \$STORE/"
+ falta=1
+else
+ echo "✓ store: $STORE_DIR"
+fi
+
+# ── el artefacto exacto ─────────────────────────────────────────────────────
+PDIR="$STORE_DIR/${ESPERADO}-product-rootfs"
+if [ -d "$PDIR" ]; then
+ echo "✓ product-rootfs sellado y presente"
+else
+ echo "✗ falta el product-rootfs b3:${ESPERADO:0:12}…"
+ echo " o se trae con rsync (ver arriba), o se reconstruye:"
+ echo " takana --store \$STORE bootstrap stage1 --seed-hash <el de seed-zig>"
+ echo " takana --store \$STORE bootstrap product --rootfs $STAGE1"
+ falta=1
+fi
+
+# ── el kernel ───────────────────────────────────────────────────────────────
+KERNEL="${KERNEL:-$(ls "$STORE_DIR"/*-linux/boot/bzImage "$STORE_DIR"/*-linux-metal/boot/bzImage "$STORE_DIR"/*-linux-generic/boot/bzImage 2>/dev/null | head -1)}"
+if [ -n "$KERNEL" ] && [ -e "$KERNEL" ]; then
+ echo "✓ kernel: ${KERNEL#$STORE_DIR/}"
+else
+ echo "✗ no hay bzImage en el store (hace falta un artefacto linux*/boot/bzImage)"
+ falta=1
+fi
+
+# ── qemu ────────────────────────────────────────────────────────────────────
+if command -v qemu-system-x86_64 >/dev/null 2>&1; then
+ echo "✓ qemu-system-x86_64"
+else
+ echo "✗ falta qemu-system-x86_64 — instalalo con el gestor de tu distro"
+ falta=1
+fi
+
+# ── la clave de prueba ──────────────────────────────────────────────────────
+# El test necesita UNA authorized_keys para entrar por ssh y comprobar que el
+# daemon contesta. Es lo único que el producto NO trae horneado, a propósito.
+if [ -n "${TESTKEY:-}" ]; then
+ if [ -f "$TESTKEY" ] && [ -f "$TESTKEY.pub" ]; then
+ echo "✓ clave: TESTKEY=$TESTKEY"
+ else
+ echo "✗ TESTKEY='$TESTKEY': falta la clave o su .pub"; falta=1
+ fi
+elif command -v ssh-keygen >/dev/null 2>&1 && ssh-keygen -t ed25519 -N '' -f /tmp/.vatest-$$ -q 2>/dev/null; then
+ rm -f /tmp/.vatest-$$ /tmp/.vatest-$$.pub
+ echo "✓ ssh-keygen puede generar la clave de prueba"
+else
+ rm -f /tmp/.vatest-$$ /tmp/.vatest-$$.pub 2>/dev/null
+ echo "✗ ssh-keygen no puede generar la clave acá (¿uid sin entrada en /etc/passwd?)"
+ echo " salida: TESTKEY=~/.ssh/id_ed25519 $0"
+ falta=1
+fi
+
+# ── aceleración ─────────────────────────────────────────────────────────────
+echo
+if [ -w /dev/kvm ]; then
+ echo "▶ KVM disponible: el arranque tarda ~segundos."
+else
+ echo "▶ SIN /dev/kvm: va por emulación TCG. Funciona, pero tarda MINUTOS —"
+ echo " por eso el DEADLINE sube a 600 s. No es un cuelgue."
+ export DEADLINE="${DEADLINE:-600}"
+fi
+
+if [ "$falta" != 0 ]; then
+ echo
+ echo "══ FALTAN PIEZAS (arriba) — no corro el test ══"
+ exit 2
+fi
+
+echo
+echo "══ arrancando ══"
+PRODUCT="$ESPERADO" KERNEL="$KERNEL" exec bash "$ROOT/scripts/product-boot-test.sh"