Files
hammer/scripts/install-image-efi.sh
T
sergioandClaude Opus 4.8 e80f15b6bc arranque-grafo: harness EFI robustos a quiet + efi-flicker-test (ADR 0010 paso 4)
Con el kernel metal cero-parpadeo (quiet), los marcadores serie kernel-side
(EFI-stub/Linux-version/EXT4) desaparecen. Los /init de pivote + /sbin/init
real ahora tee'an marcadores a /dev/ttyS0 (escritura al puerto directo, sortea
el printk ⇒ sobreviven quiet): HAMMER-EFI-DISK-PIVOT-OK + HAMMER-EFI-INIT-OK.
efi-disk-boot-test y efi-install-test pasan a apoyarse en ésos (kernel-side =
informativos). Compatible con el kernel viejo (verbose) también.

efi-flicker-test.sh: valida cero-parpadeo por captura de framebuffer OVMF-GOP
(media de brillo < umbral ⇒ pantalla negra, sin texto de boot). El seamless
i915 queda para metal Intel real.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 20:13:45 -04:00

194 lines
11 KiB
Bash
Executable File

#!/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=<dir product-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"
# /sbin/init: wrapper que emite un marcador a /dev/ttyS0 (sobrevive `quiet`, que suprime el printk
# kernel-side) y hace exec de arje-zero. Prueba positiva de que el switch_root llegó al init real.
rm -f "$ROOT_TREE/sbin/init"
cat > "$ROOT_TREE/sbin/init" <<'RINIT'
#!/bin/sh
# PID1 real tras el switch_root del pivote (store/estado ya montados por LABEL en el initramfs).
echo "HAMMER-EFI-INIT-OK: arje-zero PID1" > /dev/ttyS0 2>/dev/null || true
exec /usr/bin/arje-zero
RINIT
chmod +x "$ROOT_TREE/sbin/init"
# --- 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 <<EOF
label: gpt
start=$START1, size=$ESP_SECT, type=$ESP_GUID, name="EFI System Partition"
start=$START2, size=$ROOT_SECT, type=linux, name="hammer-root"
start=$START3, size=$STORE_SECT, type=linux, name="hammer-store"
start=$START4, size=$STATE_SECT, type=linux, name="hammer-state"
EOF
# --- 5) poblar las 3 ext4 (root-owned vía unshare -r) y empalmar ESP + ext4 en sus offsets ----------
echo "==> mke2fs -d + empalme (ESP con mtools ya lista; ext4 bajo unshare -r)"
unshare -r sh -eu <<EOF
mke2fs -q -t ext4 -L hammer-root -E root_owner=0:0 -d "$ROOT_TREE" "$ROOT_IMG" ${ROOT_SIZE}M
mke2fs -q -t ext4 -L hammer-store -E root_owner=0:0 -d "$ROOTFS/store" "$STORE_IMG" ${STORE_SIZE}M
mke2fs -q -t ext4 -L hammer-state -E root_owner=0:0 -d "$ROOTFS/var/lib/hammer" "$STATE_IMG" ${STATE_SIZE}M
EOF
dd if="$ESP_IMG" of="$IMG" bs=512 seek=$START1 conv=sparse,notrunc status=none
dd if="$ROOT_IMG" of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START3 conv=sparse,notrunc status=none
dd if="$STATE_IMG" of="$IMG" bs=512 seek=$START4 conv=sparse,notrunc status=none
rm -rf "$STAGE"
echo "==> 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