diff --git a/docs/adr/0010-arranque-grafo-mirada.md b/docs/adr/0010-arranque-grafo-mirada.md index 78c3cc34..3ddc4286 100644 --- a/docs/adr/0010-arranque-grafo-mirada.md +++ b/docs/adr/0010-arranque-grafo-mirada.md @@ -1,7 +1,8 @@ # ADR 0010 — Arranque por grafo: el menú de boot como navegación del grafo de estados (mirada + hammer) -**Estado:** aceptado (plan), sin implementar. -**Fecha:** 2026-07-06. +**Estado:** aceptado; lado hammer implementado (pasos 1–3, 5 ✅; paso 2 ✅ vía pivote a ext4; paso 4 +—cero-parpadeo— pendiente). Falta validación 2-stage del instalador live + metal, y el lado mirada. +**Fecha:** 2026-07-06 (impl. EFI-stub 2026-07-10). **Contexto cruzado:** hammer (arranque/kernel/generaciones) + tawasuyu/mirada (render). El espejo de este ADR para el lado mirada vive en `tawasuyu/HANDOFF-arranque-grafo.md`. @@ -94,8 +95,11 @@ muerto antes del takeover). ## Plan (lado hammer) 1. **Este ADR + el handoff a tawasuyu** con el contrato (hecho en este commit). -2. **squashfs+overlay** en el medio/instalador → initrd chico → **reprobar EFI-stub directo** en - OVMF, dejarlo listo para el metal. (Refinamiento #2 de [post-etapa-e].) +2. **initrd chico → EFI-stub directo reprobado en OVMF** ✅ (2026-07-10): el disco instalado NO usa un + rootfs en RAM sino un **initramfs mínimo de pivote** (~770K) que resuelve `hammer-root` por LABEL y + hace `switch_root` a la ext4 real. Ese initrd chico es lo que destraba EFI-stub directo (el cuelgue + del metal era por un initrd de 100MB). Validado en OVMF por `scripts/efi-disk-boot-test.sh`. (La vía + squashfs+overlay del [post-etapa-e] queda como optimización futura; el pivote a ext4 ya cumple.) 3. **Contrato de generaciones-grafo** ✅ (módulo `hammer_upgrade::boot_graph` + subcomando `hammer boot`): el modelo in-place de generaciones se lee como un **grafo de estados** navegable (id = `of_tree`, DAG por `parent`), `hammer boot graph` emite `/run/hammer/boot-graph.json` con el @@ -104,9 +108,18 @@ muerto antes del takeover). ante un "redo" hacia una generación huérfana). Validado e2e por el CLI. **mirada ya puede maquetar contra el grafo real.** 4. **Afinar kernel/cmdline** para el handoff sin costura (punto cero-parpadeo). -5. **install-to-disk EFI** (`hammer-live-install` rama EFI): GPT+ESP con el bzImage como - `\EFI\BOOT\BOOTX64.EFI` (EFI-stub), root/store/var en particiones; validar el disco instalado en - OVMF hasta que mirada tome el control. +5. **install-to-disk EFI** ✅ (2026-07-10): el bzImage `linux-metal` ES el binario EFI + (`\EFI\BOOT\BOOTX64.EFI`, ruta fallback removible, sin GRUB ni systemd-boot). Como no hay LoadOptions + en esa ruta, el `CONFIG_CMDLINE` horneado (`… initrd=/initramfs.cpio.gz rdinit=/init`) activa el + pivote. Dos caminos, mismos artefactos: + - **host-side** `scripts/install-image-efi.sh` — GPT+ESP+root/store/state, ESP con mtools de hammer, + ext4 con `mke2fs -d` bajo `unshare -r`; **validado VERDE en OVMF** (`scripts/efi-disk-boot-test.sh`: + firmware → BOOTX64.EFI → pivote → arje-zero PID1). + - **live-side** `scripts/hammer-live-install.sh` rama EFI — detecta `/sys/firmware/efi` y hace + **MBR con ESP tipo 0xEF** (UEFI arranca una ESP MBR igual que GPT ⇒ alcanza busybox + fdisk/mkfs.vfat/mount, sin GPT-tool ni mtools en el live). El kernel EFI-stub viaja en el payload + (`iso-image.sh INSTALLER=1` bundlea `bzImage-efi`). El arranque MBR+ESP-EF quedó **spot-checked en + OVMF**. Falta sólo la validación 2-stage (live UEFI → instalar → rebootear el disco) y el metal. ## Consecuencias diff --git a/scripts/hammer-live-install.sh b/scripts/hammer-live-install.sh index f6fbbde0..f616859d 100755 --- a/scripts/hammer-live-install.sh +++ b/scripts/hammer-live-install.sh @@ -29,6 +29,150 @@ BB=/bin/busybox [ -b "$DEV" ] || { echo "hammer-install: $DEV no es un block device" >&2; exit 1; } [ -d "$PAYLOAD" ] || { echo "hammer-install: falta el payload $PAYLOAD (¿ISO sin INSTALLER=1?)" >&2; exit 1; } + +# --- Detección de firmware: UEFI ⇒ rama EFI-stub SOBERANA (ADR 0010 paso 5); BIOS ⇒ MBR+GRUB (abajo) --- +# La rama EFI usa una ESP tipo 0xEF en tabla MBR (UEFI arranca \EFI\BOOT\BOOTX64.EFI de una ESP MBR igual +# que de GPT) ⇒ alcanza con busybox fdisk/mkfs.vfat/mount, sin GPT-tool ni mtools. El bzImage metal ES el +# binario EFI (EFI-stub, cmdline horneada carga el initrd); un initramfs mínimo pivota por LABEL. Mismos +# artefactos que scripts/install-image-efi.sh (validado en OVMF), armados desde dentro del live. +if [ -d /sys/firmware/efi ]; then + [ -r "$PAYLOAD/kernel/bzImage-efi" ] || { echo "hammer-install: live en UEFI pero falta $PAYLOAD/kernel/bzImage-efi (ISO sin kernel metal)" >&2; exit 1; } + ESP_MB="${ESP_MB:-64}" + echo "==> hammer-install (UEFI): EFI-stub soberano en $DEV (ESP ${ESP_MB}M, root ${ROOT_MB}M, store ${STORE_MB}M, estado=resto)" + echo " ¡esto BORRA $DEV!" + + # MBR, 4 primarias: p1=ESP(0xEF) p2=root p3=store p4=estado. Sectores explícitos (como la rama BIOS). + SPM=2048 + P1S=2048; P1E=$(( P1S + ESP_MB * SPM - 1 )) + P2S=$(( P1E + 1 )); P2E=$(( P2S + ROOT_MB * SPM - 1 )) + P3S=$(( P2E + 1 )); P3E=$(( P3S + STORE_MB* SPM - 1 )) + P4S=$(( P3E + 1 )) # estado = resto del disco + echo "==> particionando $DEV (MBR: ESP ${P1S}-${P1E} tipo ef, root ${P2S}-${P2E}, store ${P3S}-${P3E}, estado ${P4S}-fin)" + "$BB" fdisk "$DEV" >/dev/null 2>&1 </dev/null || true + "$BB" sync; sleep 1 + P1="${DEV}1"; P2="${DEV}2"; P3="${DEV}3"; P4="${DEV}4" + for p in "$P1" "$P2" "$P3" "$P4"; do + [ -b "$p" ] || { echo "hammer-install: no apareció la partición $p tras particionar" >&2; exit 1; } + done + + echo "==> formateando (ESP FAT32 + 3 ext2 etiquetadas para el pivote por LABEL)" + "$BB" mkfs.vfat -F 32 -n HAMMER "$P1" >/dev/null 2>&1 + "$BB" mke2fs -F -L hammer-root "$P2" >/dev/null 2>&1 + "$BB" mke2fs -F -L hammer-store "$P3" >/dev/null 2>&1 + "$BB" mke2fs -F -L hammer-state "$P4" >/dev/null 2>&1 + + # initramfs de pivote (busybox estático del live + /init: findfs LABEL=hammer-root → switch_root). + echo "==> armando el initramfs de pivote (busybox + /init switch_root a hammer-root)" + IRD_TREE=/run/hammer-efi-ird + "$BB" rm -rf "$IRD_TREE" + "$BB" mkdir -p "$IRD_TREE/bin" "$IRD_TREE/dev" "$IRD_TREE/proc" "$IRD_TREE/sys" "$IRD_TREE/newroot" + cp "$BB" "$IRD_TREE/bin/busybox"; "$BB" chmod 0755 "$IRD_TREE/bin/busybox" + for a in sh mount umount findfs switch_root mkdir echo sleep; do "$BB" ln -sf busybox "$IRD_TREE/bin/$a"; done + cat > "$IRD_TREE/init" <<'INIT' +#!/bin/busybox sh +export PATH=/bin +mount -t proc proc /proc 2>/dev/null || true +mount -t sysfs sys /sys 2>/dev/null || true +mount -t devtmpfs dev /dev 2>/dev/null || true +log() { echo "$*"; echo "$*" > /dev/ttyS0 2>/dev/null || true; } +log "HAMMER-EFI: initramfs de pivote — resolviendo hammer-root por LABEL" +ROOTDEV="" +for _try in 1 2 3 4 5; do + ROOTDEV="$(findfs LABEL=hammer-root 2>/dev/null || true)" + [ -n "$ROOTDEV" ] && break + log "HAMMER-EFI: hammer-root aún no visible, reintento $_try…"; sleep 1 +done +[ -n "$ROOTDEV" ] || { log "HAMMER-EFI-FALLO: no encuentro LABEL=hammer-root"; exec sh; } +log "HAMMER-EFI: hammer-root = $ROOTDEV → montando en /newroot" +mount -t ext4 "$ROOTDEV" /newroot || { log "HAMMER-EFI-FALLO: no pude montar $ROOTDEV"; exec sh; } +STOREDEV="$(findfs LABEL=hammer-store 2>/dev/null || true)" +STATEDEV="$(findfs LABEL=hammer-state 2>/dev/null || true)" +[ -n "$STOREDEV" ] && mount -t ext4 "$STOREDEV" /newroot/store 2>/dev/null || true +[ -n "$STATEDEV" ] && mount -t ext4 "$STATEDEV" /newroot/var/lib/hammer 2>/dev/null || true +log "HAMMER-EFI-DISK-PIVOT-OK: switch_root → /sbin/init" +exec switch_root /newroot /sbin/init +INIT + "$BB" chmod +x "$IRD_TREE/init" + ( cd "$IRD_TREE" && "$BB" find . | "$BB" cpio -o -H newc 2>/dev/null | "$BB" gzip -1 ) > /run/hammer-efi-ird.cpio.gz + + # Poblar la ESP (FAT32, montable por el live): BOOTX64.EFI = bzImage metal + el initramfs de pivote. + echo "==> poblando la ESP (\\EFI\\BOOT\\BOOTX64.EFI = kernel metal + \\initramfs.cpio.gz)" + ESPMNT=/run/hammer-esp + "$BB" mkdir -p "$ESPMNT" + "$BB" mount -t vfat "$P1" "$ESPMNT" + "$BB" mkdir -p "$ESPMNT/EFI/BOOT" + cp "$PAYLOAD/kernel/bzImage-efi" "$ESPMNT/EFI/BOOT/BOOTX64.EFI" + cp /run/hammer-efi-ird.cpio.gz "$ESPMNT/initramfs.cpio.gz" + "$BB" sync; "$BB" umount "$ESPMNT" + + # Poblar la root (copia del live) — sin wrapper que monte particiones (el pivote ya montó store/estado). + echo "==> copiando la raíz del live → $P2 (excluye virtuales/efímeros, /store, /var/lib/hammer, payload)" + MNT=/run/hammer-install + "$BB" mkdir -p "$MNT" + "$BB" mount -t ext4 "$P2" "$MNT" + for entry in /* /.[!.]*; do + [ -e "$entry" ] || continue + name="${entry##*/}" + case "$name" in + proc|sys|dev|run|tmp|store|init) continue ;; + *) cp -a "$entry" "$MNT/" 2>/dev/null || true ;; + esac + done + "$BB" rm -rf "$MNT/var/lib/hammer" 2>/dev/null || true + "$BB" rm -rf "$MNT/usr/lib/hammer/install" 2>/dev/null || true + "$BB" mkdir -p "$MNT/store" "$MNT/var/lib/hammer" "$MNT/proc" "$MNT/sys" "$MNT/dev" "$MNT/run" "$MNT/tmp" + # /sbin/init: wrapper que auto-sana un upgrade interrumpido y arranca arje-zero (el pivote ya montó + # store/estado por LABEL, así que acá NO hace falta montar nada — sólo hammer-recover + exec). + if [ -x "$PAYLOAD/hammer-recover" ]; then + "$BB" mkdir -p "$MNT/usr/sbin" + cp "$PAYLOAD/hammer-recover" "$MNT/usr/sbin/hammer-recover"; "$BB" chmod 0755 "$MNT/usr/sbin/hammer-recover" + fi + "$BB" rm -f "$MNT/sbin/init" + cat > "$MNT/sbin/init" <<'INIT' +#!/bin/sh +# Wrapper PID1 (rama UEFI): el initramfs de pivote ya montó / /store /var/lib/hammer por LABEL antes del +# switch_root; sólo auto-sanamos un upgrade interrumpido y arrancamos el init real. +[ -x /usr/sbin/hammer-recover ] && /usr/sbin/hammer-recover || true +exec /usr/bin/arje-zero +INIT + "$BB" chmod +x "$MNT/sbin/init" + "$BB" sync + "$BB" umount "$MNT" + + echo "✓ hammer-install (UEFI): $DEV instalado con EFI-stub SOBERANO (sin GRUB ni systemd-boot). Reiniciá sin el medio live." + exit 0 +fi + +# ===================== rama BIOS (MBR + GRUB) — el firmware NO es UEFI ============================== [ -r "$PAYLOAD/grub/boot.img" ] && [ -r "$PAYLOAD/grub/core.img" ] || { echo "hammer-install: payload GRUB incompleto" >&2; exit 1; } [ -r "$PAYLOAD/kernel/bzImage" ] || { echo "hammer-install: falta el kernel en el payload" >&2; exit 1; } diff --git a/scripts/iso-image.sh b/scripts/iso-image.sh index ab5c8a81..3b0daf7c 100755 --- a/scripts/iso-image.sh +++ b/scripts/iso-image.sh @@ -92,6 +92,16 @@ if [ "${INSTALLER:-0}" = 1 ]; then IPL="$RFS/root/usr/lib/hammer/install" mkdir -p "$IPL/grub/i386-pc" "$IPL/kernel" cp "$KERNEL" "$IPL/kernel/bzImage"; chmod 0644 "$IPL/kernel/bzImage" + # Rama EFI del instalador (ADR 0010 paso 5): el kernel `linux-metal` hornea CONFIG_CMDLINE con + # initrd=/initramfs.cpio.gz rdinit=/init ⇒ arranca por EFI-stub como \EFI\BOOT\BOOTX64.EFI sin + # bootloader. Lo bundleamos junto al genérico; hammer-install lo usa si el live corre en UEFI. + EFI_KERNEL="${EFI_KERNEL:-$(ls -dt store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}" + if [ -n "${EFI_KERNEL:-}" ] && [ -r "$EFI_KERNEL" ]; then + cp "$EFI_KERNEL" "$IPL/kernel/bzImage-efi"; chmod 0644 "$IPL/kernel/bzImage-efi" + echo " + kernel EFI-stub (metal) para la rama UEFI del instalador" + else + echo " ! sin kernel linux-metal en el store ⇒ el instalador sólo tendrá rama BIOS/MBR" >&2 + fi cp "$GRUB_LIB/boot.img" "$IPL/grub/boot.img" cp "$GRUB_LIB"/*.mod "$GRUB_LIB"/*.lst "$IPL/grub/i386-pc/" 2>/dev/null || true # core.img para disco MBR: prefix a la 1ª partición msdos, módulos de disco (biosdisk/part_msdos/ext2).