Files
takana/scripts/install-image-efi.sh
T
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

244 lines
15 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=takana-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 [takana-root] el rootfs real del producto (/sbin/init → arje-zero)
# p3 /store ext4 [takana-store]
# p4 /var/lib/hammer ext4 [takana-state]
#
# Cadena de arranque del disco: firmware UEFI → \EFI\BOOT\BOOTX64.EFI (EFI-stub) → initramfs pivote
# (findfs LABEL=takana-root → switch_root) → /sbin/init real → arje-zero PID1.
#
# La ESP se puebla con el mtools de takana (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 takana (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="${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=takana-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 ls cat setsid dmesg blkid; 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 ES "console=tty0 console=ttyS0,115200 …" Y EL KERNEL HACE /dev/console = LA
# ÚLTIMA `console=` ⇒ **ttyS0**. O sea que el stdout de este script va al SERIAL, no a la pantalla: en
# un metal sin puerto serie TODO lo que pase acá es INVISIBLE y el arranque parece congelado en el
# último printk del kernel. Es el mismo bug que costó el 1er viaje físico de KDE, una capa más
# temprano: allá se arregló para DESPUÉS del switch_root (getty en tty1) y acá seguía sin arreglar.
# Por eso el log va explícitamente a **/dev/tty0** (la VT en primer plano = la pantalla) además del
# serial, y nunca se confía en el stdout heredado.
log() {
echo "$*"
for _d in /dev/tty0 /dev/ttyS0; do echo "$*" > "$_d" 2>/dev/null || true; done
}
# Shell de rescate VISIBLE: si algo falla, un `exec sh` a secas nace en el serial y el usuario ve una
# pantalla muerta sin manera de averiguar nada. Se abre uno en tty1 (pantalla) y se conserva el serial.
rescate() {
log "HAMMER-EFI-FALLO: $*"
log " bloques visibles: $(ls /dev/sd* /dev/nvme* /dev/vd* /dev/mmcblk* 2>/dev/null | tr '\n' ' ')"
log " abriendo shell de rescate en tty1 — probá: blkid ; dmesg | tail -30"
setsid -c sh -c 'exec /bin/sh </dev/tty1 >/dev/tty1 2>&1' >/dev/null 2>&1 &
exec sh
}
log "HAMMER-EFI: initramfs de pivote — resolviendo hammer-root por LABEL"
# ⏱ LA ESPERA TIENE QUE SER LARGA, Y ÉSTE ES EL MOTIVO: el rdinit arranca en cuanto se desempaqueta el
# initramfs, pero el bus USB enumera de forma ASÍNCRONA y tarda SEGUNDOS (reset del hub, settling de 1s
# por dispositivo, scan SCSI). En QEMU el disco es virtio y está desde el instante cero, así que la
# carrera NUNCA se ve en validación: sólo aparece al bootear de un USB en metal, y con 5 reintentos de
# 1s se pierde. Es el equivalente de `rootwait`, que acá no aplica porque no hay root= en el cmdline.
ROOTDEV=""
_try=0
while [ "$_try" -lt 60 ]; do
ROOTDEV="$(findfs LABEL=hammer-root 2>/dev/null || true)"
[ -n "$ROOTDEV" ] && break
_try=$(( _try + 1 ))
[ $(( _try % 5 )) -eq 0 ] && log "HAMMER-EFI: esperando hammer-root… ${_try}s (el USB tarda en enumerar)"
sleep 1
done
[ -n "$ROOTDEV" ] || rescate "no encuentro LABEL=hammer-root tras ${_try}s"
log "HAMMER-EFI: hammer-root = $ROOTDEV → montando en /newroot"
mount -t ext4 "$ROOTDEV" /newroot || rescate "no pude montar $ROOTDEV"
# /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"
# CLI `takana` (static musl) para el menú de arranque por grafo (ADR 0010): el hook de init lo invoca
# como `takana boot menu`. El producto trae arje-zero/hammerd pero NO el CLI ⇒ lo inyectamos acá si
# está compilado (igual patrón que hammer-recover). Opcional: sin él, el wrapper saltea el menú.
HAMMER_MUSL="${TAKANA_MUSL:-${HAMMER_MUSL:-$ROOT/target/x86_64-unknown-linux-musl/release/hammer}}"
if [ -x "$HAMMER_MUSL" ]; then
cp "$HAMMER_MUSL" "$ROOT_TREE/usr/bin/hammer"; chmod 0755 "$ROOT_TREE/usr/bin/hammer"
echo " + CLI hammer (static musl) para el menú de arranque por grafo"
else
echo " ! sin hammer musl ($HAMMER_MUSL) ⇒ el disco no traerá el menú de arranque (cargo build --target x86_64-unknown-linux-musl -p takana-cli)" >&2
fi
# /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).
# busybox switch_root NO mueve /dev al nuevo root ⇒ montamos devtmpfs antes del marcador (si no,
# /dev/ttyS0 no existe todavía y el echo se pierde). arje-zero re-monta lo que necesite, es idempotente.
/bin/busybox mount -t devtmpfs dev /dev 2>/dev/null || true
# A la PANTALLA (tty0) además del serial: en metal sin puerto serie éste es el único indicio de que el
# pivote llegó al init real. Ver el comentario largo del initramfs.
for _d in /dev/tty0 /dev/ttyS0; do
echo "HAMMER-EFI-INIT-OK: arje-zero PID1" > "$_d" 2>/dev/null || true
done
# Menú de arranque por grafo (ADR 0010): emite /run/hammer/boot-graph.json y, si hay un compositor
# (mirada), lo pinta sobre KMS para elegir un nodo. Graceful: sin compositor, emite el grafo y sigue.
# Salida al serial (no pinta tty0 ⇒ respeta el cero-parpadeo). No bloquea el arranque si falla.
# El `timeout` no es paranoia decorativa: esto corre ANTES del exec de arje-zero, o sea que si se
# cuelga no hay PID1, no hay getty y no hay pantalla — un arranque muerto e indistinguible de un kernel
# colgado. El `|| true` protege del fallo, no del bloqueo.
if [ -x /usr/bin/hammer ]; then
/bin/busybox timeout 15 /usr/bin/hammer boot menu > /dev/ttyS0 2>&1 || true
fi
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 takana ---
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=takana-root, p3=takana-store, p4=takana-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