gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1373Shell 116Rust 74Markdown 641915 Definitions

⎇ b405fc77dfee7904f5f78f838cd6479854419522 scripts/install-image-efi.sh Shell · 243 lines · 14.7 KB History raw

install-image-efi: STAGE configurable por entorno aefdce439 · Sergio ·

1#!/bin/sh
2# install-image-efi.sh — imagen de disco EFI-STUB SOBERANA (ADR 0010 paso 5), validable en OVMF.
3#
4# Hermana EFI de scripts/install-image.sh (que es GPT+BIOS+GRUB). Ésta NO lleva GRUB ni systemd-boot:
5# el propio bzImage es el binario EFI (EFI-STUB) que la firmware lanza como \EFI\BOOT\BOOTX64.EFI (la
6# ruta de arranque FALLBACK de medios removibles, sin necesidad de una entrada NVRAM). Es el loader
7# soberano del ADR 0010: sin bootloader ajeno, sin el GRUB 2.14 que cuelga la rama UEFI en el metal.
8#
9# Cómo pasa el cmdline sin LoadOptions: el kernel `linux-metal` hornea CONFIG_CMDLINE (CMDLINE_BOOL, sin
10# FORCE) = "... initrd=/initramfs.cpio.gz rdinit=/init". Arrancado por la ruta fallback la firmware pasa
11# LoadOptions VACÍO ⇒ el kernel usa ese cmdline horneado ⇒ el EFI-stub carga \initramfs.cpio.gz desde la
12# MISMA ESP y corre /init. (En un boot con bootloader que sí pasa LoadOptions, el horneado se ignora.)
13#
14# El initramfs es MÍNIMO — sólo pivota: findfs LABEL=hammer-root → switch_root a la ext4 real. Ese
15# "initrd chico" es lo que destraba EFI-stub directo (ADR 0010 paso 2: el cuelgue del metal era por un
16# initrd de 100MB). El sistema real vive en las particiones ext4, no en RAM.
17#
18# Layout (GPT, UEFI):
19#   p1  ESP  FAT32  [EFI System Partition]   \EFI\BOOT\BOOTX64.EFI (=bzImage) + \initramfs.cpio.gz
20#   p2  /                ext4  [hammer-root]  el rootfs real del producto (/sbin/init → arje-zero)
21#   p3  /store           ext4  [hammer-store]
22#   p4  /var/lib/hammer  ext4  [hammer-state]
23#
24# Cadena de arranque del disco: firmware UEFI → \EFI\BOOT\BOOTX64.EFI (EFI-stub) → initramfs pivote
25# (findfs LABEL=hammer-root → switch_root) → /sbin/init real → arje-zero PID1.
26#
27# La ESP se puebla con el mtools de hammer (mformat/mcopy, sin root ni loop). Las ext4 con `mke2fs -d`
28# bajo `unshare -r` (ficheros root-owned sin sudo), igual que install-image.sh.
29#
30# Uso:
31#   ./scripts/install-image-efi.sh                       # crea work/hammer-efi-disk.img
32#   BOOT=1 KVM=1 ./scripts/install-image-efi.sh          # además lo arranca en OVMF (sin -kernel)
33#
34# Variables:
35#   ROOTFS      dir rootfs a instalar          (default el product-rootfs más reciente del store)
36#   KERNEL      bzImage EFI-stub a embeber      (default store/*-linux-metal/boot/bzImage más reciente)
37#   BUSYBOX     busybox ESTÁTICO para el initramfs (default el del ROOTFS)
38#   MTOOLS      dir usr/bin de mtools de hammer  (default store/*-mtools/usr/bin)
39#   IMG         imagen de salida                (default work/hammer-efi-disk.img)
40#   ESP_MB      MiB de la ESP FAT32             (default 64)
41#   ROOT_SIZE   MiB de /         (default 2048)
42#   STORE_SIZE  MiB de /store    (default 512)
43#   STATE_SIZE  MiB de /var/lib/hammer (default 512)
44#   BOOT        1 ⇒ arranca en OVMF tras crearla   KVM/MEM passthrough al arranque
45#   OVMF_CODE/OVMF_VARS  firmware OVMF (default Arch/CachyOS)
46set -eu
47ROOT="$(cd "$(dirname "$0")/.." && pwd)"
48cd "$ROOT"
49
50ROOTFS="${ROOTFS:-$(ls -dt store/*-product-rootfs 2>/dev/null | head -1)}"
51KERNEL="${KERNEL:-$(ls -dt store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}"
52IMG="${IMG:-work/hammer-efi-disk.img}"
53ESP_MB="${ESP_MB:-64}"
54ROOT_SIZE="${ROOT_SIZE:-2048}"
55STORE_SIZE="${STORE_SIZE:-512}"
56STATE_SIZE="${STATE_SIZE:-512}"
57MTOOLS="${MTOOLS:-$(ls -dt store/*-mtools/usr/bin 2>/dev/null | head -1)}"
58
59[ -n "${ROOTFS:-}" ] && [ -d "$ROOTFS" ] || { echo "no existe ROOTFS: '${ROOTFS:-}' (set ROOTFS=<dir product-rootfs>)" >&2; exit 1; }
60[ -e "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init (arje-zero)" >&2; exit 1; }
61[ -n "${KERNEL:-}" ] && [ -r "$KERNEL" ] || { echo "no encuentro el kernel EFI-stub (build recipes/linux-metal.toml o set KERNEL=)" >&2; exit 1; }
62BUSYBOX="${BUSYBOX:-$ROOTFS/bin/busybox}"
63[ -r "$BUSYBOX" ] || { echo "no encuentro busybox estático en $BUSYBOX (set BUSYBOX=)" >&2; exit 1; }
64[ -n "${MTOOLS:-}" ] && [ -x "$MTOOLS/mformat" ] || { echo "no encuentro mtools de hammer (build recipes/mtools.toml o set MTOOLS=)" >&2; exit 1; }
65for t in sfdisk mke2fs cpio gzip; do command -v "$t" >/dev/null 2>&1 || { echo "falta $t" >&2; exit 1; }; done
66unshare -r true 2>/dev/null || { echo "se requiere 'unshare -r' (ficheros root-owned sin sudo)" >&2; exit 1; }
67
68STAGE="${STAGE:-work/.install-efi-stage}"
69rm -rf "$STAGE"; mkdir -p "$STAGE"
70ROOT_TREE="$STAGE/root"
71IRD_TREE="$STAGE/initramfs"
72ROOT_IMG="$STAGE/root.img"; STORE_IMG="$STAGE/store.img"; STATE_IMG="$STAGE/state.img"
73ESP_IMG="$STAGE/esp.img"; IRD="$STAGE/initramfs.cpio.gz"
74
75# --- 1) initramfs de pivote: busybox estático + /init (findfs LABEL=hammer-root → switch_root) --------
76echo "==> initramfs de pivote (busybox estático + /init switch_root a hammer-root)"
77mkdir -p "$IRD_TREE/bin" "$IRD_TREE/dev" "$IRD_TREE/proc" "$IRD_TREE/sys" "$IRD_TREE/newroot"
78cp "$BUSYBOX" "$IRD_TREE/bin/busybox"; chmod 0755 "$IRD_TREE/bin/busybox"
79for 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
80cat > "$IRD_TREE/init" <<'INIT'
81#!/bin/busybox sh
82# initramfs de pivote (EFI-stub → aquí). Monta las pseudo-fs, resuelve la ext4 real por LABEL, monta
83# /store y /var/lib/hammer bajo ella y hace switch_root al init real (arje-zero). Sin root= en cmdline:
84# el descubrimiento por LABEL vive acá, así el disco es reubicable (cualquier /dev/…, virtio o nvme).
85export PATH=/bin
86mount -t proc     proc /proc 2>/dev/null || true
87mount -t sysfs    sys  /sys  2>/dev/null || true
88mount -t devtmpfs dev  /dev  2>/dev/null || true
89# ⚠ EL CMDLINE HORNEADO ES "console=tty0 console=ttyS0,115200 …" Y EL KERNEL HACE /dev/console = LA
90# ÚLTIMA `console=` ⇒ **ttyS0**. O sea que el stdout de este script va al SERIAL, no a la pantalla: en
91# un metal sin puerto serie TODO lo que pase acá es INVISIBLE y el arranque parece congelado en el
92# último printk del kernel. Es el mismo bug que costó el 1er viaje físico de KDE, una capa más
93# temprano: allá se arregló para DESPUÉS del switch_root (getty en tty1) y acá seguía sin arreglar.
94# Por eso el log va explícitamente a **/dev/tty0** (la VT en primer plano = la pantalla) además del
95# serial, y nunca se confía en el stdout heredado.
96log() {
97    echo "$*"
98    for _d in /dev/tty0 /dev/ttyS0; do echo "$*" > "$_d" 2>/dev/null || true; done
99}
100# Shell de rescate VISIBLE: si algo falla, un `exec sh` a secas nace en el serial y el usuario ve una
101# pantalla muerta sin manera de averiguar nada. Se abre uno en tty1 (pantalla) y se conserva el serial.
102rescate() {
103    log "HAMMER-EFI-FALLO: $*"
104    log "   bloques visibles: $(ls /dev/sd* /dev/nvme* /dev/vd* /dev/mmcblk* 2>/dev/null | tr '\n' ' ')"
105    log "   abriendo shell de rescate en tty1 — probá: blkid ; dmesg | tail -30"
106    setsid -c sh -c 'exec /bin/sh </dev/tty1 >/dev/tty1 2>&1' >/dev/null 2>&1 &
107    exec sh
108}
109log "HAMMER-EFI: initramfs de pivote — resolviendo hammer-root por LABEL"
110# ⏱ LA ESPERA TIENE QUE SER LARGA, Y ÉSTE ES EL MOTIVO: el rdinit arranca en cuanto se desempaqueta el
111# initramfs, pero el bus USB enumera de forma ASÍNCRONA y tarda SEGUNDOS (reset del hub, settling de 1s
112# por dispositivo, scan SCSI). En QEMU el disco es virtio y está desde el instante cero, así que la
113# carrera NUNCA se ve en validación: sólo aparece al bootear de un USB en metal, y con 5 reintentos de
114# 1s se pierde. Es el equivalente de `rootwait`, que acá no aplica porque no hay root= en el cmdline.
115ROOTDEV=""
116_try=0
117while [ "$_try" -lt 60 ]; do
118    ROOTDEV="$(findfs LABEL=hammer-root 2>/dev/null || true)"
119    [ -n "$ROOTDEV" ] && break
120    _try=$(( _try + 1 ))
121    [ $(( _try % 5 )) -eq 0 ] && log "HAMMER-EFI: esperando hammer-root… ${_try}s (el USB tarda en enumerar)"
122    sleep 1
123done
124[ -n "$ROOTDEV" ] || rescate "no encuentro LABEL=hammer-root tras ${_try}s"
125log "HAMMER-EFI: hammer-root = $ROOTDEV → montando en /newroot"
126mount -t ext4 "$ROOTDEV" /newroot || rescate "no pude montar $ROOTDEV"
127# /store y /var/lib/hammer por LABEL (no bloqueante: el wrapper del root también reintenta).
128STOREDEV="$(findfs LABEL=hammer-store 2>/dev/null || true)"
129STATEDEV="$(findfs LABEL=hammer-state 2>/dev/null || true)"
130[ -n "$STOREDEV" ] && mount -t ext4 "$STOREDEV" /newroot/store          2>/dev/null || true
131[ -n "$STATEDEV" ] && mount -t ext4 "$STATEDEV" /newroot/var/lib/hammer  2>/dev/null || true
132log "HAMMER-EFI-DISK-PIVOT-OK: switch_root → /sbin/init"
133exec switch_root /newroot /sbin/init
134INIT
135chmod +x "$IRD_TREE/init"
136( cd "$IRD_TREE" && find . -print0 | cpio --null -o -H newc --owner=root:root 2>/dev/null | gzip -1 ) > "$IRD"
137echo "    initramfs: $(du -h "$IRD" | cut -f1)"
138
139# --- 2) árbol root: hardlinks del rootfs SIN store/estado (van en sus particiones dedicadas) -----------
140echo "==> staging root (hardlinks del rootfs, sin /store ni /var/lib/hammer)"
141cp -al "$ROOTFS" "$ROOT_TREE"
142rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
143mkdir -p "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
144# CLI `hammer` (static musl) para el menú de arranque por grafo (ADR 0010): el hook de init lo invoca
145# como `hammer boot menu`. El producto trae arje-zero/hammerd pero NO el CLI ⇒ lo inyectamos acá si
146# está compilado (igual patrón que hammer-recover). Opcional: sin él, el wrapper saltea el menú.
147HAMMER_MUSL="${HAMMER_MUSL:-$ROOT/target/x86_64-unknown-linux-musl/release/hammer}"
148if [ -x "$HAMMER_MUSL" ]; then
149    cp "$HAMMER_MUSL" "$ROOT_TREE/usr/bin/hammer"; chmod 0755 "$ROOT_TREE/usr/bin/hammer"
150    echo "    + CLI hammer (static musl) para el menú de arranque por grafo"
151else
152    echo "    ! sin hammer musl ($HAMMER_MUSL) ⇒ el disco no traerá el menú de arranque (cargo build --target x86_64-unknown-linux-musl -p hammer-cli)" >&2
153fi
154# /sbin/init: wrapper que emite un marcador a /dev/ttyS0 (sobrevive `quiet`, que suprime el printk
155# kernel-side) y hace exec de arje-zero. Prueba positiva de que el switch_root llegó al init real.
156rm -f "$ROOT_TREE/sbin/init"
157cat > "$ROOT_TREE/sbin/init" <<'RINIT'
158#!/bin/sh
159# PID1 real tras el switch_root del pivote (store/estado ya montados por LABEL en el initramfs).
160# busybox switch_root NO mueve /dev al nuevo root ⇒ montamos devtmpfs antes del marcador (si no,
161# /dev/ttyS0 no existe todavía y el echo se pierde). arje-zero re-monta lo que necesite, es idempotente.
162/bin/busybox mount -t devtmpfs dev /dev 2>/dev/null || true
163# A la PANTALLA (tty0) además del serial: en metal sin puerto serie éste es el único indicio de que el
164# pivote llegó al init real. Ver el comentario largo del initramfs.
165for _d in /dev/tty0 /dev/ttyS0; do
166    echo "HAMMER-EFI-INIT-OK: arje-zero PID1" > "$_d" 2>/dev/null || true
167done
168# Menú de arranque por grafo (ADR 0010): emite /run/hammer/boot-graph.json y, si hay un compositor
169# (mirada), lo pinta sobre KMS para elegir un nodo. Graceful: sin compositor, emite el grafo y sigue.
170# Salida al serial (no pinta tty0 ⇒ respeta el cero-parpadeo). No bloquea el arranque si falla.
171# El `timeout` no es paranoia decorativa: esto corre ANTES del exec de arje-zero, o sea que si se
172# cuelga no hay PID1, no hay getty y no hay pantalla — un arranque muerto e indistinguible de un kernel
173# colgado. El `|| true` protege del fallo, no del bloqueo.
174if [ -x /usr/bin/hammer ]; then
175    /bin/busybox timeout 15 /usr/bin/hammer boot menu > /dev/ttyS0 2>&1 || true
176fi
177exec /usr/bin/arje-zero
178RINIT
179chmod +x "$ROOT_TREE/sbin/init"
180
181# --- 3) ESP FAT32: BOOTX64.EFI (=bzImage EFI-stub) + initramfs.cpio.gz, poblada con mtools de hammer ---
182echo "==> ESP FAT32 (${ESP_MB}M): \\EFI\\BOOT\\BOOTX64.EFI (=bzImage) + \\initramfs.cpio.gz"
183rm -f "$ESP_IMG"; truncate -s "$(( ESP_MB * 1024 * 1024 ))" "$ESP_IMG"
184"$MTOOLS/mformat" -F -v HAMMER -i "$ESP_IMG" ::
185"$MTOOLS/mmd" -i "$ESP_IMG" ::/EFI ::/EFI/BOOT
186"$MTOOLS/mcopy" -i "$ESP_IMG" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI
187"$MTOOLS/mcopy" -i "$ESP_IMG" "$IRD"    ::/initramfs.cpio.gz
188"$MTOOLS/mdir" -i "$ESP_IMG" ::/EFI/BOOT 2>/dev/null | grep -iE 'BOOTX64|initram' || true
189
190# --- 4) GPT: p1=ESP, p2=hammer-root, p3=hammer-store, p4=hammer-state --------------------------------
191SECT_MIB=2048
192ESP_SECT=$(( ESP_MB    * SECT_MIB ))
193ROOT_SECT=$(( ROOT_SIZE  * SECT_MIB ))
194STORE_SECT=$(( STORE_SIZE * SECT_MIB ))
195STATE_SECT=$(( STATE_SIZE * SECT_MIB ))
196START1=2048
197START2=$(( START1 + ESP_SECT ))
198START3=$(( START2 + ROOT_SECT ))
199START4=$(( START3 + STORE_SECT ))
200TOTAL_SECT=$(( START4 + STATE_SECT + 2048 ))
201ESP_GUID="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"   # GPT "EFI System Partition"
202
203echo "==> imagen: $IMG (GPT UEFI; ESP ${ESP_MB}M + root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)"
204rm -f "$IMG"; truncate -s "$(( TOTAL_SECT * 512 ))" "$IMG"
205sfdisk --quiet "$IMG" >/dev/null <<EOF
206label: gpt
207start=$START1, size=$ESP_SECT,   type=$ESP_GUID, name="EFI System Partition"
208start=$START2, size=$ROOT_SECT,  type=linux,     name="hammer-root"
209start=$START3, size=$STORE_SECT, type=linux,     name="hammer-store"
210start=$START4, size=$STATE_SECT, type=linux,     name="hammer-state"
211EOF
212
213# --- 5) poblar las 3 ext4 (root-owned vía unshare -r) y empalmar ESP + ext4 en sus offsets ----------
214echo "==> mke2fs -d + empalme (ESP con mtools ya lista; ext4 bajo unshare -r)"
215unshare -r sh -eu <<EOF
216mke2fs -q -t ext4 -L hammer-root  -E root_owner=0:0 -d "$ROOT_TREE"             "$ROOT_IMG"  ${ROOT_SIZE}M
217mke2fs -q -t ext4 -L hammer-store -E root_owner=0:0 -d "$ROOTFS/store"          "$STORE_IMG" ${STORE_SIZE}M
218mke2fs -q -t ext4 -L hammer-state -E root_owner=0:0 -d "$ROOTFS/var/lib/hammer" "$STATE_IMG" ${STATE_SIZE}M
219EOF
220dd if="$ESP_IMG"   of="$IMG" bs=512 seek=$START1 conv=sparse,notrunc status=none
221dd if="$ROOT_IMG"  of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
222dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START3 conv=sparse,notrunc status=none
223dd if="$STATE_IMG" of="$IMG" bs=512 seek=$START4 conv=sparse,notrunc status=none
224
225rm -rf "$STAGE"
226echo "==> imagen EFI creada: $(du -h "$IMG" | cut -f1) en disco (sparse), $(( TOTAL_SECT * 512 / 1024 / 1024 ))M virtuales"
227echo "    auto-boot UEFI: qemu-system-x86_64 -drive if=pflash,...OVMF... -drive file=$IMG   (sin -kernel)"
228
229# --- 6) arranque de prueba en OVMF (sin -kernel: la firmware lanza \EFI\BOOT\BOOTX64.EFI) -----------
230if [ "${BOOT:-0}" = 1 ]; then
231    OVMF_CODE="${OVMF_CODE:-/usr/share/edk2/x64/OVMF_CODE.4m.fd}"
232    OVMF_VARS="${OVMF_VARS:-/usr/share/edk2/x64/OVMF_VARS.4m.fd}"
233    [ -r "$OVMF_CODE" ] && [ -r "$OVMF_VARS" ] || { echo "no encuentro OVMF (set OVMF_CODE/OVMF_VARS): $OVMF_CODE" >&2; exit 1; }
234    cp "$OVMF_VARS" work/.efi-vars.fd
235    accel=""
236    [ "${KVM:-0}" = 1 ] && [ -w /dev/kvm ] && accel="-enable-kvm -cpu host"
237    echo "==> arranque de prueba en OVMF (sin -kernel) — deadline ${DEADLINE:-180}s"
238    # shellcheck disable=SC2086
239    exec timeout "${DEADLINE:-180}" qemu-system-x86_64 -m "${MEM:-4096}" -no-reboot -nographic $accel \
240        -drive if=pflash,unit=0,format=raw,readonly=on,file="$OVMF_CODE" \
241        -drive if=pflash,unit=1,format=raw,file="work/.efi-vars.fd" \
242        -drive file="$IMG",format=raw,if=virtio
243fi