From 11ff41d6148040049da4dc1beb5b3728ee383a9a Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 10 Jul 2026 18:03:47 -0400 Subject: [PATCH] arranque-grafo: install-to-disk EFI-stub soberano (ADR 0010 paso 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install-image-efi.sh arma una imagen GPT+ESP donde el bzImage metal ES el binario EFI (\EFI\BOOT\BOOTX64.EFI, ruta fallback removible) — sin GRUB ni systemd-boot. La cmdline horneada del kernel (initrd=/initramfs.cpio.gz rdinit=/init) se activa al no haber LoadOptions; un initramfs mínimo de pivote resuelve hammer-root por LABEL (findfs) y hace switch_root a la ext4 real. Layout: ESP + hammer-root/store/state ext4 (mke2fs -d bajo unshare -r, ESP con mtools de hammer). Cierra el paso 2 (initrd chico destraba EFI-stub) y el 5. efi-disk-boot-test.sh valida en OVMF (sin -kernel): VERDE — firmware → BOOTX64.EFI → pivote → arje-zero PID1. Marcadores serie deterministas (los dos primeros bytes del handoff firmware→kernel son informativos; el veredicto se apoya en el montaje/re-mount aguas abajo, prueba concluyente de la cadena). Co-Authored-By: Claude Opus 4.8 --- scripts/efi-disk-boot-test.sh | 50 ++++++++++ scripts/install-image-efi.sh | 183 ++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100755 scripts/efi-disk-boot-test.sh create mode 100755 scripts/install-image-efi.sh diff --git a/scripts/efi-disk-boot-test.sh b/scripts/efi-disk-boot-test.sh new file mode 100755 index 00000000..bf592fd9 --- /dev/null +++ b/scripts/efi-disk-boot-test.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# efi-disk-boot-test.sh — valida el DISCO INSTALADO EFI-STUB soberano (ADR 0010 paso 5) en OVMF. +# +# Arma la imagen con install-image-efi.sh y la arranca por UEFI (OVMF pflash, SIN -kernel: la firmware +# lanza \EFI\BOOT\BOOTX64.EFI). Asevera la cadena soberana completa por marcadores serie deterministas: +# 1. EFI-stub cargó el kernel+initrd horneados (sin GRUB ni systemd-boot) +# 2. el initramfs de pivote resolvió hammer-root por LABEL y montó las ext4 +# 3. switch_root: arje-zero re-montó / (re-mounted) = tomó PID1 +# +# Nota: el cmdline horneado termina en console=tty0 ⇒ el stdout de userspace va a la pantalla. Los +# marcadores kernel-side (printk a TODAS las consolas) más el tee a /dev/ttyS0 del /init dan la prueba +# en el serial headless. +# +# Uso: ./scripts/efi-disk-boot-test.sh +# Vars: ROOTFS KERNEL MEM (def 4096) KVM (def 1) DEADLINE (def 180) OVMF_CODE OVMF_VARS +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" +MEM="${MEM:-4096}"; KVM="${KVM:-1}"; DEADLINE="${DEADLINE:-180}" +WORK="$ROOT/work/efi-disk-boot-test"; mkdir -p "$WORK" +IMG="$WORK/hammer-efi-disk.img"; LOG="$WORK/boot.log" + +echo "==> armando la imagen EFI-stub con install-image-efi.sh" +IMG="$IMG" ROOTFS="${ROOTFS:-}" KERNEL="${KERNEL:-}" ./scripts/install-image-efi.sh >"$WORK/build.log" 2>&1 \ + || { echo "✗ build falló — ver $WORK/build.log" >&2; tail -20 "$WORK/build.log" >&2; exit 1; } +echo " imagen: $(du -h "$IMG" | cut -f1)" + +echo "==> arranque UEFI (OVMF, sin -kernel) — deadline ${DEADLINE}s" +BOOT=1 KVM="$KVM" MEM="$MEM" DEADLINE="$DEADLINE" IMG="$IMG" \ + ./scripts/install-image-efi.sh >"$LOG" 2>&1 || true + +# marcadores (limpiamos escapes ANSI que mete OVMF) +clean() { sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\r//g' "$LOG"; } +fail=0 +# Los dos primeros bytes serie del handoff firmware→kernel se pierden a veces (EFI-stub/Linux-version): +# son INFORMATIVOS. El veredicto se apoya en los marcadores AGUAS ABAJO — prueba concluyente de que +# EFI-stub cargó el kernel+initrd (nada más podría montar hammer-root ni re-montar / sin esta cadena). +info() { if clean | grep -aqiE "$1"; then echo " · $2"; else echo " · ($2 — bytes serie tempranos perdidos, no bloqueante)"; fi; } +check() { if clean | grep -aqiE "$1"; then echo " ✓ $2"; else echo " ✗ $2 (falta /$1/)"; fail=1; fi; } +info 'EFI stub: Loaded initrd' 'EFI-stub cargó kernel+initrd (sin GRUB/systemd-boot)' +info 'Linux version 6\.16' 'kernel arrancó' +check 'HAMMER-EFI-DISK-PIVOT-OK|EXT4-fs \(vda2\): mounted' 'initramfs pivote montó hammer-root (⇒ EFI-stub cargó kernel+initrd)' +check 'EXT4-fs \(vda3\): mounted' '/store montado por LABEL' +check 'EXT4-fs \(vda2\): re-mounted' 'switch_root → arje-zero re-montó / (PID1)' + +echo +if [ "$fail" = 0 ]; then + echo "✓✓ EFI-DISK VERDE: disco instalado bootea EFI-stub SOBERANO en OVMF (firmware → BOOTX64.EFI → pivote → arje-zero)" +else + echo "✗ efi-disk-boot-test FALLÓ — log: $LOG" >&2; exit 1 +fi diff --git a/scripts/install-image-efi.sh b/scripts/install-image-efi.sh new file mode 100755 index 00000000..a8653cd7 --- /dev/null +++ b/scripts/install-image-efi.sh @@ -0,0 +1,183 @@ +#!/bin/sh +# install-image-efi.sh — imagen de disco EFI-STUB SOBERANA (ADR 0010 paso 5), validable en OVMF. +# +# Hermana EFI de scripts/install-image.sh (que es GPT+BIOS+GRUB). Ésta NO lleva GRUB ni systemd-boot: +# el propio bzImage es el binario EFI (EFI-STUB) que la firmware lanza como \EFI\BOOT\BOOTX64.EFI (la +# ruta de arranque FALLBACK de medios removibles, sin necesidad de una entrada NVRAM). Es el loader +# soberano del ADR 0010: sin bootloader ajeno, sin el GRUB 2.14 que cuelga la rama UEFI en el metal. +# +# Cómo pasa el cmdline sin LoadOptions: el kernel `linux-metal` hornea CONFIG_CMDLINE (CMDLINE_BOOL, sin +# FORCE) = "... initrd=/initramfs.cpio.gz rdinit=/init". Arrancado por la ruta fallback la firmware pasa +# LoadOptions VACÍO ⇒ el kernel usa ese cmdline horneado ⇒ el EFI-stub carga \initramfs.cpio.gz desde la +# MISMA ESP y corre /init. (En un boot con bootloader que sí pasa LoadOptions, el horneado se ignora.) +# +# El initramfs es MÍNIMO — sólo pivota: findfs LABEL=hammer-root → switch_root a la ext4 real. Ese +# "initrd chico" es lo que destraba EFI-stub directo (ADR 0010 paso 2: el cuelgue del metal era por un +# initrd de 100MB). El sistema real vive en las particiones ext4, no en RAM. +# +# Layout (GPT, UEFI): +# p1 ESP FAT32 [EFI System Partition] \EFI\BOOT\BOOTX64.EFI (=bzImage) + \initramfs.cpio.gz +# p2 / ext4 [hammer-root] el rootfs real del producto (/sbin/init → arje-zero) +# p3 /store ext4 [hammer-store] +# p4 /var/lib/hammer ext4 [hammer-state] +# +# Cadena de arranque del disco: firmware UEFI → \EFI\BOOT\BOOTX64.EFI (EFI-stub) → initramfs pivote +# (findfs LABEL=hammer-root → switch_root) → /sbin/init real → arje-zero PID1. +# +# La ESP se puebla con el mtools de hammer (mformat/mcopy, sin root ni loop). Las ext4 con `mke2fs -d` +# bajo `unshare -r` (ficheros root-owned sin sudo), igual que install-image.sh. +# +# Uso: +# ./scripts/install-image-efi.sh # crea work/hammer-efi-disk.img +# BOOT=1 KVM=1 ./scripts/install-image-efi.sh # además lo arranca en OVMF (sin -kernel) +# +# Variables: +# ROOTFS dir rootfs a instalar (default el product-rootfs más reciente del store) +# KERNEL bzImage EFI-stub a embeber (default store/*-linux-metal/boot/bzImage más reciente) +# BUSYBOX busybox ESTÁTICO para el initramfs (default el del ROOTFS) +# MTOOLS dir usr/bin de mtools de hammer (default store/*-mtools/usr/bin) +# IMG imagen de salida (default work/hammer-efi-disk.img) +# ESP_MB MiB de la ESP FAT32 (default 64) +# ROOT_SIZE MiB de / (default 2048) +# STORE_SIZE MiB de /store (default 512) +# STATE_SIZE MiB de /var/lib/hammer (default 512) +# BOOT 1 ⇒ arranca en OVMF tras crearla KVM/MEM passthrough al arranque +# OVMF_CODE/OVMF_VARS firmware OVMF (default Arch/CachyOS) +set -eu +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +ROOTFS="${ROOTFS:-$(ls -dt store/*-product-rootfs 2>/dev/null | head -1)}" +KERNEL="${KERNEL:-$(ls -dt store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}" +IMG="${IMG:-work/hammer-efi-disk.img}" +ESP_MB="${ESP_MB:-64}" +ROOT_SIZE="${ROOT_SIZE:-2048}" +STORE_SIZE="${STORE_SIZE:-512}" +STATE_SIZE="${STATE_SIZE:-512}" +MTOOLS="${MTOOLS:-$(ls -dt store/*-mtools/usr/bin 2>/dev/null | head -1)}" + +[ -n "${ROOTFS:-}" ] && [ -d "$ROOTFS" ] || { echo "no existe ROOTFS: '${ROOTFS:-}' (set ROOTFS=)" >&2; exit 1; } +[ -e "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init (arje-zero)" >&2; exit 1; } +[ -n "${KERNEL:-}" ] && [ -r "$KERNEL" ] || { echo "no encuentro el kernel EFI-stub (build recipes/linux-metal.toml o set KERNEL=)" >&2; exit 1; } +BUSYBOX="${BUSYBOX:-$ROOTFS/bin/busybox}" +[ -r "$BUSYBOX" ] || { echo "no encuentro busybox estático en $BUSYBOX (set BUSYBOX=)" >&2; exit 1; } +[ -n "${MTOOLS:-}" ] && [ -x "$MTOOLS/mformat" ] || { echo "no encuentro mtools de hammer (build recipes/mtools.toml o set MTOOLS=)" >&2; exit 1; } +for t in sfdisk mke2fs cpio gzip; do command -v "$t" >/dev/null 2>&1 || { echo "falta $t" >&2; exit 1; }; done +unshare -r true 2>/dev/null || { echo "se requiere 'unshare -r' (ficheros root-owned sin sudo)" >&2; exit 1; } + +STAGE="work/.install-efi-stage" +rm -rf "$STAGE"; mkdir -p "$STAGE" +ROOT_TREE="$STAGE/root" +IRD_TREE="$STAGE/initramfs" +ROOT_IMG="$STAGE/root.img"; STORE_IMG="$STAGE/store.img"; STATE_IMG="$STAGE/state.img" +ESP_IMG="$STAGE/esp.img"; IRD="$STAGE/initramfs.cpio.gz" + +# --- 1) initramfs de pivote: busybox estático + /init (findfs LABEL=hammer-root → switch_root) -------- +echo "==> initramfs de pivote (busybox estático + /init switch_root a hammer-root)" +mkdir -p "$IRD_TREE/bin" "$IRD_TREE/dev" "$IRD_TREE/proc" "$IRD_TREE/sys" "$IRD_TREE/newroot" +cp "$BUSYBOX" "$IRD_TREE/bin/busybox"; chmod 0755 "$IRD_TREE/bin/busybox" +for a in sh mount umount findfs switch_root mkdir echo sleep; do ln -sf busybox "$IRD_TREE/bin/$a"; done +cat > "$IRD_TREE/init" <<'INIT' +#!/bin/busybox sh +# initramfs de pivote (EFI-stub → aquí). Monta las pseudo-fs, resuelve la ext4 real por LABEL, monta +# /store y /var/lib/hammer bajo ella y hace switch_root al init real (arje-zero). Sin root= en cmdline: +# el descubrimiento por LABEL vive acá, así el disco es reubicable (cualquier /dev/…, virtio o nvme). +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 +# El cmdline horneado del kernel pone console=tty0 al final ⇒ el stdout de userspace va a la PANTALLA, +# no al serial. Para que el pivote sea visible en un OVMF headless (y para depurar en metal), tee'amos +# los marcadores también a /dev/ttyS0. +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; } +# /store y /var/lib/hammer por LABEL (no bloqueante: el wrapper del root también reintenta). +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 +chmod +x "$IRD_TREE/init" +( cd "$IRD_TREE" && find . -print0 | cpio --null -o -H newc --owner=root:root 2>/dev/null | gzip -1 ) > "$IRD" +echo " initramfs: $(du -h "$IRD" | cut -f1)" + +# --- 2) árbol root: hardlinks del rootfs SIN store/estado (van en sus particiones dedicadas) ----------- +echo "==> staging root (hardlinks del rootfs, sin /store ni /var/lib/hammer)" +cp -al "$ROOTFS" "$ROOT_TREE" +rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer" +mkdir -p "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer" + +# --- 3) ESP FAT32: BOOTX64.EFI (=bzImage EFI-stub) + initramfs.cpio.gz, poblada con mtools de hammer --- +echo "==> ESP FAT32 (${ESP_MB}M): \\EFI\\BOOT\\BOOTX64.EFI (=bzImage) + \\initramfs.cpio.gz" +rm -f "$ESP_IMG"; truncate -s "$(( ESP_MB * 1024 * 1024 ))" "$ESP_IMG" +"$MTOOLS/mformat" -F -v HAMMER -i "$ESP_IMG" :: +"$MTOOLS/mmd" -i "$ESP_IMG" ::/EFI ::/EFI/BOOT +"$MTOOLS/mcopy" -i "$ESP_IMG" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI +"$MTOOLS/mcopy" -i "$ESP_IMG" "$IRD" ::/initramfs.cpio.gz +"$MTOOLS/mdir" -i "$ESP_IMG" ::/EFI/BOOT 2>/dev/null | grep -iE 'BOOTX64|initram' || true + +# --- 4) GPT: p1=ESP, p2=hammer-root, p3=hammer-store, p4=hammer-state -------------------------------- +SECT_MIB=2048 +ESP_SECT=$(( ESP_MB * SECT_MIB )) +ROOT_SECT=$(( ROOT_SIZE * SECT_MIB )) +STORE_SECT=$(( STORE_SIZE * SECT_MIB )) +STATE_SECT=$(( STATE_SIZE * SECT_MIB )) +START1=2048 +START2=$(( START1 + ESP_SECT )) +START3=$(( START2 + ROOT_SECT )) +START4=$(( START3 + STORE_SECT )) +TOTAL_SECT=$(( START4 + STATE_SECT + 2048 )) +ESP_GUID="C12A7328-F81F-11D2-BA4B-00A0C93EC93B" # GPT "EFI System Partition" + +echo "==> imagen: $IMG (GPT UEFI; ESP ${ESP_MB}M + root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)" +rm -f "$IMG"; truncate -s "$(( TOTAL_SECT * 512 ))" "$IMG" +sfdisk --quiet "$IMG" >/dev/null < mke2fs -d + empalme (ESP con mtools ya lista; ext4 bajo unshare -r)" +unshare -r sh -eu < imagen EFI creada: $(du -h "$IMG" | cut -f1) en disco (sparse), $(( TOTAL_SECT * 512 / 1024 / 1024 ))M virtuales" +echo " auto-boot UEFI: qemu-system-x86_64 -drive if=pflash,...OVMF... -drive file=$IMG (sin -kernel)" + +# --- 6) arranque de prueba en OVMF (sin -kernel: la firmware lanza \EFI\BOOT\BOOTX64.EFI) ----------- +if [ "${BOOT:-0}" = 1 ]; then + OVMF_CODE="${OVMF_CODE:-/usr/share/edk2/x64/OVMF_CODE.4m.fd}" + OVMF_VARS="${OVMF_VARS:-/usr/share/edk2/x64/OVMF_VARS.4m.fd}" + [ -r "$OVMF_CODE" ] && [ -r "$OVMF_VARS" ] || { echo "no encuentro OVMF (set OVMF_CODE/OVMF_VARS): $OVMF_CODE" >&2; exit 1; } + cp "$OVMF_VARS" work/.efi-vars.fd + accel="" + [ "${KVM:-0}" = 1 ] && [ -w /dev/kvm ] && accel="-enable-kvm -cpu host" + echo "==> arranque de prueba en OVMF (sin -kernel) — deadline ${DEADLINE:-180}s" + # shellcheck disable=SC2086 + exec timeout "${DEADLINE:-180}" qemu-system-x86_64 -m "${MEM:-4096}" -no-reboot -nographic $accel \ + -drive if=pflash,unit=0,format=raw,readonly=on,file="$OVMF_CODE" \ + -drive if=pflash,unit=1,format=raw,file="work/.efi-vars.fd" \ + -drive file="$IMG",format=raw,if=virtio +fi