From fd09e842b4a55e02e0c233de4dc7f4988af554f3 Mon Sep 17 00:00:00 2001 From: sergio Date: Sun, 21 Jun 2026 04:39:13 -0400 Subject: [PATCH] =?UTF-8?q?arranque=20UEFI:=20ISO=20h=C3=ADbrido=20BIOS+EF?= =?UTF-8?q?I=20con=20grubx64.efi=20+=20ESP=20de=20mtools=20(refinamiento?= =?UTF-8?q?=20#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iso-image.sh EFI=1 añade un 2º El Torito EFI: ESP FAT (poblada con el mtools de hammer, recipes/mtools.toml) con EFI/BOOT/BOOTX64.EFI = grubx64.efi (grub-mkimage -O x86_64-efi). GRUB EFI lee el mismo grub.cfg y carga el kernel EFI_STUB (ya en el kernel, sin rebuild) + initrd vía protocolo EFI. El mismo ISO sigue booteando por BIOS (El Torito i386-pc) ⇒ híbrido. efi-boot-test.sh valida ambas firmwares E2E (OVMF→grubx64.efi→sshd, y SeaBIOS→sshd). GOTCHA: mformat -F fuerza FAT32 (min ~33MiB); sobre ESP chica deja FAT inválido que la firmware no lee -> sin -F, auto FAT12/16. Co-Authored-By: Claude Opus 4.8 --- docs/13-release-engineering.md | 17 ++++++++- scripts/efi-boot-test.sh | 67 ++++++++++++++++++++++++++++++++++ scripts/iso-image.sh | 37 ++++++++++++++++++- 3 files changed, 117 insertions(+), 4 deletions(-) create mode 100755 scripts/efi-boot-test.sh diff --git a/docs/13-release-engineering.md b/docs/13-release-engineering.md index 9dc98fb1..c33d400a 100644 --- a/docs/13-release-engineering.md +++ b/docs/13-release-engineering.md @@ -101,8 +101,21 @@ kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-m refinamiento). Validado E2E in-VM (`iso-boot-test.sh`, ISO 113M): SeaBIOS → GRUB 2.14 (El Torito) → Linux 6.16.12 hammer-built → `/init` (marcador `HAMMER-ISO-LIVE-OK`) → arje-zero PID1 → hammerd + netup (lease DHCP) → **sshd escuchando**: el producto entero corriendo desde el medio live. **Pendiente - (refinamiento):** squashfs+overlay para no cargar todo el rootfs en RAM; build EFI (el kernel ya trae - EFI_STUB, falta poblar la ESP). + (refinamiento):** squashfs+overlay para no cargar todo el rootfs en RAM (diferido: el kernel no trae + SQUASHFS ni CDROM/SR ⇒ exige rebuild de kernel, valor marginal en el producto lean). + - **Arranque UEFI ✅** (`iso-image.sh EFI=1` + `recipes/mtools.toml` + `scripts/efi-boot-test.sh`). El + mismo ISO se vuelve **híbrido BIOS+UEFI**: además del El Torito i386-pc se añade un 2º El Torito EFI + (`-eltorito-alt-boot -e efi.img -no-emul-boot -isohybrid-gpt-basdat`) que arranca una ESP FAT con + `EFI/BOOT/BOOTX64.EFI` = un `grubx64.efi` (`grub-mkimage -O x86_64-efi`). GRUB EFI lee el mismo + `grub.cfg` y carga kernel (EFI_STUB, ya en el kernel — sin rebuild) + initrd vía el protocolo EFI. + **Herramental:** poblar la ESP FAT sin root exige `mtools` (`mformat`/`mcopy`), ausente en el host ⇒ + `recipes/mtools.toml` lo construye (GNU mtools 4.0.49, estático musl, dogfoodeado por iso-image.sh). + **GOTCHA:** `mformat -F` fuerza **FAT32** (mínimo ~33 MiB); sobre una ESP chica deja un FAT inválido + que la firmware no lee ("failed to load … Not Found") ⇒ sin `-F`, mformat auto-elige FAT12/16. + Validado E2E: el MISMO ISO bootea por UEFI (QEMU+OVMF → grubx64.efi → "Loaded initrd from + LINUX_EFI_INITRD_MEDIA_GUID" → arje-zero → sshd) y por BIOS (SeaBIOS, El Torito). **Refinamiento #3 + CERRADO.** (Falta como pulido: rama EFI del INSTALADOR a disco — GPT+ESP en `hammer-install`, hoy el + instalado es MBR-BIOS.) - **Instalar DESDE el live ✅** (`scripts/iso-image.sh INSTALLER=1` + `scripts/hammer-live-install.sh` → `/usr/bin/hammer-install` + `scripts/iso-install-test.sh`). Cierra el lazo **ISO live → disco instalado → bootea solo**. El ISO instalador bundlea un *payload de disco* en el initramfs bajo diff --git a/scripts/efi-boot-test.sh b/scripts/efi-boot-test.sh new file mode 100755 index 00000000..a1991bb3 --- /dev/null +++ b/scripts/efi-boot-test.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# efi-boot-test.sh — valida la rama UEFI del medio live (refinamiento #3): arma un ISO con +# iso-image.sh EFI=1 y lo arranca por las DOS firmwares, probando que es un HÍBRIDO BIOS+UEFI: +# (A) UEFI — QEMU con OVMF (pflash) → grubx64.efi (de la ESP FAT poblada con mtools de hammer) → +# kernel EFI-stub + initrd vía EFI → producto + sshd. +# (B) BIOS — el MISMO ISO con SeaBIOS (El Torito i386-pc) → producto + sshd. +# La ESP FAT la puebla el mtools construido por hammer (recipes/mtools.toml); el grubx64.efi lo arma +# grub-mkimage -O x86_64-efi. El kernel ya trae EFI_STUB (sin rebuild). +# +# Uso: PRODUCT= ./scripts/efi-boot-test.sh +# Vars: KERNEL MEM (def 4096) KVM (def 1) OVMF_CODE OVMF_VARS UEFI_DEADLINE (def 200) BIOS_DEADLINE (def 120) +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" +KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}" +MEM="${MEM:-4096}"; KVM="${KVM:-1}" +UEFI_DEADLINE="${UEFI_DEADLINE:-200}"; BIOS_DEADLINE="${BIOS_DEADLINE:-120}" +# OVMF (edk2). Default Arch/CachyOS; override con OVMF_CODE/OVMF_VARS si tu distro los pone en otro lado. +OVMF_CODE="${OVMF_CODE:-/usr/share/edk2/x64/OVMF_CODE.4m.fd}" +OVMF_VARS="${OVMF_VARS:-/usr/share/edk2/x64/OVMF_VARS.4m.fd}" +WORK="$ROOT/work/efi-boot-test"; ISO="$WORK/hammer-efi.iso" +mkdir -p "$WORK" + +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"; exit 1; } +[ -r "$OVMF_CODE" ] && [ -r "$OVMF_VARS" ] || { echo "no encuentro OVMF (set OVMF_CODE/OVMF_VARS): $OVMF_CODE"; exit 1; } +echo "==> product-rootfs: $PDIR" +echo "==> OVMF: $OVMF_CODE" + +accel=(); if [ "$KVM" = 1 ] && [ -w /dev/kvm ]; then accel=(-enable-kvm -cpu host); fi + +echo "==> armando ISO híbrido (EFI=1) con iso-image.sh" +ROOTFS="$PDIR" ISO="$ISO" KERNEL="$KERNEL" EFI=1 ./scripts/iso-image.sh >/dev/null +echo " ISO: $(du -h "$ISO" | cut -f1)" + +fail=0 +markers() { # $1=log $2=etiqueta + local log="$1" tag="$2" + if grep -aqiE "Linux version 6\.16" "$log" && grep -aqiE "Server listening on .* port 22" "$log"; then + echo " ✓ $tag: kernel cargado + sshd escuchando" + else + echo " ✗ $tag: no llegó a sshd — ver $log" >&2; fail=1 + fi +} + +echo "==> (A) arranque UEFI (OVMF) — deadline ${UEFI_DEADLINE}s" +cp "$OVMF_VARS" "$WORK/vars.fd" +timeout "$UEFI_DEADLINE" qemu-system-x86_64 -m "$MEM" -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/vars.fd" \ + -cdrom "$ISO" -boot d > "$WORK/uefi.log" 2>&1 || true +grep -aqi "Loaded initrd from LINUX_EFI_INITRD" "$WORK/uefi.log" && echo " ✓ UEFI: GRUB EFI cargó kernel+initrd vía EFI" +markers "$WORK/uefi.log" "UEFI" + +echo "==> (B) arranque BIOS (SeaBIOS) del MISMO ISO — deadline ${BIOS_DEADLINE}s" +timeout "$BIOS_DEADLINE" qemu-system-x86_64 -m "$MEM" -no-reboot -nographic "${accel[@]}" \ + -cdrom "$ISO" -boot d > "$WORK/bios.log" 2>&1 || true +markers "$WORK/bios.log" "BIOS" + +if [ "$fail" = 0 ]; then + echo; echo "✓✓ EFI VERDE: ISO HÍBRIDO bootea por UEFI (OVMF→grubx64.efi de mtools) y por BIOS (El Torito)" +else + echo; echo "✗ efi-boot-test FALLÓ" >&2; exit 1 +fi diff --git a/scripts/iso-image.sh b/scripts/iso-image.sh index 52905d8a..2e7298eb 100755 --- a/scripts/iso-image.sh +++ b/scripts/iso-image.sh @@ -35,10 +35,13 @@ ROOTFS="${ROOTFS:-work/builder-rootfs}" ISO="${ISO:-work/hammer-live.iso}" KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}" GRUB_LIB="${GRUB_LIB:-/usr/lib/grub/i386-pc}" +GRUB_EFI_LIB="${GRUB_EFI_LIB:-/usr/lib/grub/x86_64-efi}" CMDLINE="${CMDLINE:-console=ttyS0 rdinit=/init}" # xorriso: preferimos el construido por hammer (dogfooding de recipes/xorriso.toml); fallback al host. XORRISO="${XORRISO:-$(ls store/*-xorriso/usr/bin/xorriso 2>/dev/null | head -1)}" [ -n "$XORRISO" ] && [ -x "$XORRISO" ] || XORRISO="$(command -v xorriso 2>/dev/null || true)" +# mtools (mformat/mcopy): para poblar la ESP FAT del arranque EFI. Preferimos el de hammer (dogfooding). +MTOOLS_DIR="${MTOOLS_DIR:-$(ls -d store/*-mtools/usr/bin 2>/dev/null | head -1)}" [ -d "$ROOTFS" ] || { echo "no existe ROOTFS: $ROOTFS" >&2; exit 1; } [ -e "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init" >&2; exit 1; } @@ -122,8 +125,37 @@ echo "==> grub-mkimage El Torito (formato i386-pc-eltorito, módulos iso9660)" grub-mkimage -O i386-pc-eltorito -p '/boot/grub' -o "$ISO_ROOT/boot/grub/i386-pc/eltorito.img" \ biosdisk iso9660 part_gpt part_msdos normal configfile linux echo ls boot search -# --- armar el ISO con xorriso (personalidad mkisofs), El Torito + isohybrid MBR --- -echo "==> xorriso: armando $ISO (El Torito BIOS + isohybrid MBR)" +# --- EFI=1: rama UEFI (refinamiento #3). Construye grubx64.efi (mismo grub.cfg, lee kernel+initrd del +# iso9660) y una ESP FAT con EFI/BOOT/BOOTX64.EFI, poblada con mtools (sin root). El kernel ya trae +# EFI_STUB; aquí GRUB EFI es el cargador (espeja la rama BIOS). xorriso añade un 2º El Torito (EFI). --- +EFI_ARGS="" +if [ "${EFI:-0}" = 1 ]; then + [ -d "$GRUB_EFI_LIB" ] || { echo "faltan módulos GRUB x86_64-efi en $GRUB_EFI_LIB" >&2; exit 1; } + MFORMAT="$MTOOLS_DIR/mformat"; MCOPY="$MTOOLS_DIR/mcopy"; MMD="$MTOOLS_DIR/mmd" + [ -x "$MFORMAT" ] && [ -x "$MCOPY" ] || MFORMAT="$(command -v mformat 2>/dev/null || true)" + [ -n "$MFORMAT" ] && [ -x "$MFORMAT" ] || { echo "no encuentro mtools (build recipes/mtools.toml)" >&2; exit 1; } + [ -x "$MCOPY" ] || { MCOPY="$(command -v mcopy)"; MMD="$(command -v mmd)"; } + echo "==> EFI=1: grub-mkimage x86_64-efi + ESP FAT (mtools: $MFORMAT)" + mkdir -p "$STAGE/efi" + grub-mkimage -O x86_64-efi -p '/boot/grub' -o "$STAGE/efi/bootx64.efi" \ + part_gpt part_msdos fat iso9660 normal configfile linux echo ls boot search search_fs_uuid efi_gop efi_uga + # ESP FAT: dimensionada al grubx64.efi (+ holgura). mformat sobre una imagen truncada (FAT sin + # particionar; el El Torito EFI arranca el FS plano). Poblamos EFI/BOOT/BOOTX64.EFI con mcopy. + ESP="$ISO_ROOT/boot/grub/efi.img" + # Tamaño ≥ 1.44 MiB (mínimo razonable); mformat SIN -F auto-elige FAT12/16 por nº de clusters. NO + # forzar FAT32 (-F): exige ~33 MiB mínimos y sobre una imagen chica deja un FAT inválido que la + # firmware no lee ("failed to load ... Not Found"). + espkb=$(( ( $(stat -c%s "$STAGE/efi/bootx64.efi") / 1024 ) + 512 )) + [ "$espkb" -lt 1440 ] && espkb=1440 + rm -f "$ESP"; truncate -s "$(( espkb * 1024 ))" "$ESP" + "$MFORMAT" -i "$ESP" :: + "$MMD" -i "$ESP" ::/EFI ::/EFI/BOOT + "$MCOPY" -i "$ESP" "$STAGE/efi/bootx64.efi" ::/EFI/BOOT/BOOTX64.EFI + EFI_ARGS="-eltorito-alt-boot -e boot/grub/efi.img -no-emul-boot -isohybrid-gpt-basdat" +fi + +# --- armar el ISO con xorriso (personalidad mkisofs), El Torito BIOS (+EFI si EFI=1) + isohybrid MBR --- +echo "==> xorriso: armando $ISO (El Torito BIOS${EFI_ARGS:+ + EFI} + isohybrid MBR)" rm -f "$ISO" MBR_ARGS="" [ -r "$GRUB_LIB/boot_hybrid.img" ] && MBR_ARGS="--grub2-mbr $GRUB_LIB/boot_hybrid.img" @@ -135,6 +167,7 @@ MBR_ARGS="" -b boot/grub/i386-pc/eltorito.img \ -no-emul-boot -boot-load-size 4 -boot-info-table \ --grub2-boot-info \ + $EFI_ARGS \ -o "$ISO" \ "$ISO_ROOT" 2>&1 | grep -viE "^xorriso : NOTE|Drive current|Media current|Media status|Media summary|libisofs:|^xorriso : UPDATE" || true