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…
paso 4: el ensamblado de imagen toma sus herramientas del STORE — y estaba roto en las 3 máquinas
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# ⚠ Las etiquetas son `hammer-*` y NO `takana-*`: están en el fstab y el arranque de
20# sistemas YA INSTALADOS, así que el renombre del ADR 0016 las dejó congeladas a propósito.
21# Este comentario decía `takana-*` y el código escribía `hammer-*` — mandaba a buscar una
22# etiqueta que no existe.
23# p1 ESP FAT32 [EFI System Partition] \EFI\takana\takanax64.efi + \EFI\BOOT\BOOTX64.EFI
24# (las DOS = el mismo bzImage, ADR 0018 §3) + \initramfs.cpio.gz
25# p2 / ext4 [hammer-root] el rootfs real del producto (/sbin/init → arje-zero)
26# p3 /store ext4 [hammer-store]
27# p4 /var/lib/hammer ext4 [hammer-state]
28#
29# Cadena de arranque del disco: firmware UEFI → \EFI\takana\takanax64.efi (con entrada NVRAM) o
30# \EFI\BOOT\BOOTX64.EFI (sin ella, y es lo que usa OVMF en las pruebas) → EFI-stub → initramfs pivote
31# (findfs LABEL=hammer-root → switch_root) → /sbin/init real → arje-zero PID1.
32#
33# La ESP se puebla con el mtools de takana (mformat/mcopy, sin root ni loop). Las ext4 con `mke2fs -d`
34# bajo `unshare -r` (ficheros root-owned sin sudo), igual que install-image.sh.
35#
36# Uso:
37# ./scripts/install-image-efi.sh # crea work/hammer-efi-disk.img
38# BOOT=1 KVM=1 ./scripts/install-image-efi.sh # además lo arranca en OVMF (sin -kernel)
39#
40# Variables:
41# ROOTFS dir rootfs a instalar (default el product-rootfs más reciente del store)
42# KERNEL bzImage EFI-stub a embeber (default store/*-linux-metal/boot/bzImage más reciente)
43# BUSYBOX busybox ESTÁTICO para el initramfs (default el del ROOTFS)
44# MTOOLS dir usr/bin de mtools de takana (default store/*-mtools/usr/bin)
45# IMG imagen de salida (default work/hammer-efi-disk.img)
46# ESP_MB MiB de la ESP FAT32 (default 64)
47# ROOT_SIZE MiB de / (default 2048)
48# STORE_SIZE MiB de /store (default 512)
49# STATE_SIZE MiB de /var/lib/hammer (default 512)
50# BOOT 1 ⇒ arranca en OVMF tras crearla KVM/MEM passthrough al arranque
51# OVMF_CODE/OVMF_VARS firmware OVMF (default Arch/CachyOS)
52set -eu
53ROOT="$(cd "$(dirname "$0")/.." && pwd)"
54cd "$ROOT"
55
56ROOTFS="${ROOTFS:-$(ls -dt store/*-product-rootfs 2>/dev/null | head -1)}"
57KERNEL="${KERNEL:-$(ls -dt store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}"
58IMG="${IMG:-work/hammer-efi-disk.img}"
59ESP_MB="${ESP_MB:-64}"
60ROOT_SIZE="${ROOT_SIZE:-2048}"
61STORE_SIZE="${STORE_SIZE:-512}"
62STATE_SIZE="${STATE_SIZE:-512}"
63# ⚠ Las herramientas del ensamblado salen del STORE, por HASH VIGENTE, no del PATH del host ni del
64# artefacto más reciente por fecha (2026-09-22). En este hub, en su anfitrión y en el worker NO existe
65# ninguna de `cpio mformat xorriso grub-mkimage`: el pipeline estaba roto en las tres máquinas y sólo
66# se veía al correrlo. Ver `scripts/lib/herramientas-desde-store.sh`.
67. "$(dirname "$0")/lib/herramientas-desde-store.sh"
68
69[ -n "${ROOTFS:-}" ] && [ -d "$ROOTFS" ] || { echo "no existe ROOTFS: '${ROOTFS:-}' (set ROOTFS=<dir product-rootfs>)" >&2; exit 1; }
70# ⚠ Esto decía `[ -e "$ROOTFS/sbin/init" ]` y era un chequeo ROTO para todo rootfs sano: `/sbin/init`
71# es un symlink ABSOLUTO (→ `/usr/bin/arje-zero`) y `-e` LO SIGUE contra la raíz del HOST, donde no
72# existe. ⇒ el script abortaba con «ROOTFS sin /sbin/init» teniendo el init perfectamente presente
73# adentro. Un chequeo que valida algo distinto de lo que cree validar, y que además falla del lado
74# ruidoso: nunca dejó pasar una imagen mala, pero bloqueaba todas las buenas.
75_init_l="$ROOTFS/sbin/init"
76if [ -L "$_init_l" ]; then
77 _init_d="$(readlink "$_init_l")"
78 case "$_init_d" in
79 /*) _init_l="$ROOTFS$_init_d" ;; # absoluto: es absoluto DENTRO del rootfs
80 *) _init_l="$ROOTFS/sbin/$_init_d" ;; # relativo: contra el directorio que lo contiene
81 esac
82fi
83[ -e "$_init_l" ] || { echo "ROOTFS sin /sbin/init utilizable (arje-zero): $_init_l" >&2; exit 1; }
84[ -n "${KERNEL:-}" ] && [ -r "$KERNEL" ] || { echo "no encuentro el kernel EFI-stub (build recipes/linux-metal.toml o set KERNEL=)" >&2; exit 1; }
85BUSYBOX="${BUSYBOX:-$ROOTFS/bin/busybox}"
86[ -r "$BUSYBOX" ] || { echo "no encuentro busybox estático en $BUSYBOX (set BUSYBOX=)" >&2; exit 1; }
87echo "==> herramientas del ensamblado"
88MFORMAT=$(herramienta mtools usr/bin/mformat mformat) || exit 1
89MMD=$(herramienta mtools usr/bin/mmd mmd) || exit 1
90MCOPY=$(herramienta mtools usr/bin/mcopy mcopy) || exit 1
91MDIR=$(herramienta mtools usr/bin/mdir mdir) || exit 1
92SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1
93MKE2FS=$(herramienta e2fsprogs sbin/mke2fs mke2fs) || exit 1
94CPIO=$(herramienta libarchive usr/bin/bsdcpio cpio) || exit 1
95GZIP=$(herramienta gzip bin/gzip gzip) || exit 1
96# `MTOOLS=` sigue existiendo como override manual (un directorio con los binarios), que es como lo
97# documenta la cabecera; si se pasa, gana sobre el store.
98if [ -n "${MTOOLS:-}" ]; then
99 MFORMAT="${MTOOLS}/mformat"; MMD="${MTOOLS}/mmd"; MCOPY="${MTOOLS}/mcopy"; MDIR="${MTOOLS}/mdir"
100 [ -x "$MFORMAT" ] || { echo "MTOOLS=$MTOOLS no tiene mformat" >&2; exit 1; }
101fi
102unshare -r true 2>/dev/null || { echo "se requiere 'unshare -r' (ficheros root-owned sin sudo)" >&2; exit 1; }
103
104STAGE="${STAGE:-work/.install-efi-stage}"
105rm -rf "$STAGE"; mkdir -p "$STAGE"
106ROOT_TREE="$STAGE/root"
107IRD_TREE="$STAGE/initramfs"
108ROOT_IMG="$STAGE/root.img"; STORE_IMG="$STAGE/store.img"; STATE_IMG="$STAGE/state.img"
109ESP_IMG="$STAGE/esp.img"; IRD="$STAGE/initramfs.cpio.gz"
110
111# --- 1) initramfs de pivote: busybox estático + /init (findfs LABEL=hammer-root → switch_root) --------
112echo "==> initramfs de pivote (busybox estático + /init switch_root a hammer-root)"
113mkdir -p "$IRD_TREE/bin" "$IRD_TREE/dev" "$IRD_TREE/proc" "$IRD_TREE/sys" "$IRD_TREE/newroot"
114cp "$BUSYBOX" "$IRD_TREE/bin/busybox"; chmod 0755 "$IRD_TREE/bin/busybox"
115for 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
116cat > "$IRD_TREE/init" <<'INIT'
117#!/bin/busybox sh
118# initramfs de pivote (EFI-stub → aquí). Monta las pseudo-fs, resuelve la ext4 real por LABEL, monta
119# /store y /var/lib/hammer bajo ella y hace switch_root al init real (arje-zero). Sin root= en cmdline:
120# el descubrimiento por LABEL vive acá, así el disco es reubicable (cualquier /dev/…, virtio o nvme).
121export PATH=/bin
122mount -t proc proc /proc 2>/dev/null || true
123mount -t sysfs sys /sys 2>/dev/null || true
124mount -t devtmpfs dev /dev 2>/dev/null || true
125# ⚠ EL CMDLINE HORNEADO ES "console=tty0 console=ttyS0,115200 …" Y EL KERNEL HACE /dev/console = LA
126# ÚLTIMA `console=` ⇒ **ttyS0**. O sea que el stdout de este script va al SERIAL, no a la pantalla: en
127# un metal sin puerto serie TODO lo que pase acá es INVISIBLE y el arranque parece congelado en el
128# último printk del kernel. Es el mismo bug que costó el 1er viaje físico de KDE, una capa más
129# temprano: allá se arregló para DESPUÉS del switch_root (getty en tty1) y acá seguía sin arreglar.
130# Por eso el log va explícitamente a **/dev/tty0** (la VT en primer plano = la pantalla) además del
131# serial, y nunca se confía en el stdout heredado.
132log() {
133 echo "$*"
134 for _d in /dev/tty0 /dev/ttyS0; do echo "$*" > "$_d" 2>/dev/null || true; done
135}
136# Shell de rescate VISIBLE: si algo falla, un `exec sh` a secas nace en el serial y el usuario ve una
137# pantalla muerta sin manera de averiguar nada. Se abre uno en tty1 (pantalla) y se conserva el serial.
138rescate() {
139 log "HAMMER-EFI-FALLO: $*"
140 log " bloques visibles: $(ls /dev/sd* /dev/nvme* /dev/vd* /dev/mmcblk* 2>/dev/null | tr '\n' ' ')"
141 log " abriendo shell de rescate en tty1 — probá: blkid ; dmesg | tail -30"
142 setsid -c sh -c 'exec /bin/sh </dev/tty1 >/dev/tty1 2>&1' >/dev/null 2>&1 &
143 exec sh
144}
145log "HAMMER-EFI: initramfs de pivote — resolviendo hammer-root por LABEL"
146# ⏱ LA ESPERA TIENE QUE SER LARGA, Y ÉSTE ES EL MOTIVO: el rdinit arranca en cuanto se desempaqueta el
147# initramfs, pero el bus USB enumera de forma ASÍNCRONA y tarda SEGUNDOS (reset del hub, settling de 1s
148# por dispositivo, scan SCSI). En QEMU el disco es virtio y está desde el instante cero, así que la
149# carrera NUNCA se ve en validación: sólo aparece al bootear de un USB en metal, y con 5 reintentos de
150# 1s se pierde. Es el equivalente de `rootwait`, que acá no aplica porque no hay root= en el cmdline.
151ROOTDEV=""
152_try=0
153while [ "$_try" -lt 60 ]; do
154 ROOTDEV="$(findfs LABEL=hammer-root 2>/dev/null || true)"
155 [ -n "$ROOTDEV" ] && break
156 _try=$(( _try + 1 ))
157 [ $(( _try % 5 )) -eq 0 ] && log "HAMMER-EFI: esperando hammer-root… ${_try}s (el USB tarda en enumerar)"
158 sleep 1
159done
160[ -n "$ROOTDEV" ] || rescate "no encuentro LABEL=hammer-root tras ${_try}s"
161log "HAMMER-EFI: hammer-root = $ROOTDEV → montando en /newroot"
162mount -t ext4 "$ROOTDEV" /newroot || rescate "no pude montar $ROOTDEV"
163# /store y /var/lib/hammer por LABEL (no bloqueante: el wrapper del root también reintenta).
164STOREDEV="$(findfs LABEL=hammer-store 2>/dev/null || true)"
165STATEDEV="$(findfs LABEL=hammer-state 2>/dev/null || true)"
166[ -n "$STOREDEV" ] && mount -t ext4 "$STOREDEV" /newroot/store 2>/dev/null || true
167[ -n "$STATEDEV" ] && mount -t ext4 "$STATEDEV" /newroot/var/lib/hammer 2>/dev/null || true
168log "HAMMER-EFI-DISK-PIVOT-OK: switch_root → /sbin/init"
169exec switch_root /newroot /sbin/init
170INIT
171chmod +x "$IRD_TREE/init"
172# `bsdcpio` de libarchive acepta el mismo `--null -o -H newc --owner=root:root` que el GNU cpio
173# (comprobado 2026-09-22 contra el artefacto sellado: mismo `newc`, dueños root).
174( cd "$IRD_TREE" && find . -print0 | "$CPIO" --null -o -H newc --owner=root:root 2>/dev/null | "$GZIP" -1 ) > "$IRD"
175echo " initramfs: $(du -h "$IRD" | cut -f1)"
176
177# --- 2) árbol root: hardlinks del rootfs SIN store/estado (van en sus particiones dedicadas) -----------
178echo "==> staging root (hardlinks del rootfs, sin /store ni /var/lib/hammer)"
179sonda_hardlink "$ROOTFS" "$STAGE" || exit 1
180cp -al "$ROOTFS" "$ROOT_TREE"
181rm -rf "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
182mkdir -p "$ROOT_TREE/store" "$ROOT_TREE/var/lib/hammer"
183# CLI `takana` (static musl) para el menú de arranque por grafo (ADR 0010): el hook de init lo invoca
184# como `takana boot menu`. El producto trae arje-zero/hammerd pero NO el CLI ⇒ lo inyectamos acá si
185# está compilado (igual patrón que hammer-recover). Opcional: sin él, el wrapper saltea el menú.
186HAMMER_MUSL="${TAKANA_MUSL:-${HAMMER_MUSL:-$ROOT/target/x86_64-unknown-linux-musl/release/hammer}}"
187if [ -x "$HAMMER_MUSL" ]; then
188 cp "$HAMMER_MUSL" "$ROOT_TREE/usr/bin/hammer"; chmod 0755 "$ROOT_TREE/usr/bin/hammer"
189 echo " + CLI hammer (static musl) para el menú de arranque por grafo"
190else
191 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
192fi
193# /sbin/init: wrapper que emite un marcador a /dev/ttyS0 (sobrevive `quiet`, que suprime el printk
194# kernel-side) y hace exec de arje-zero. Prueba positiva de que el switch_root llegó al init real.
195rm -f "$ROOT_TREE/sbin/init"
196cat > "$ROOT_TREE/sbin/init" <<'RINIT'
197#!/bin/sh
198# PID1 real tras el switch_root del pivote (store/estado ya montados por LABEL en el initramfs).
199# busybox switch_root NO mueve /dev al nuevo root ⇒ montamos devtmpfs antes del marcador (si no,
200# /dev/ttyS0 no existe todavía y el echo se pierde). arje-zero re-monta lo que necesite, es idempotente.
201/bin/busybox mount -t devtmpfs dev /dev 2>/dev/null || true
202# A la PANTALLA (tty0) además del serial: en metal sin puerto serie éste es el único indicio de que el
203# pivote llegó al init real. Ver el comentario largo del initramfs.
204for _d in /dev/tty0 /dev/ttyS0; do
205 echo "HAMMER-EFI-INIT-OK: arje-zero PID1" > "$_d" 2>/dev/null || true
206done
207# Menú de arranque por grafo (ADR 0010): emite /run/hammer/boot-graph.json y, si hay un compositor
208# (mirada), lo pinta sobre KMS para elegir un nodo. Graceful: sin compositor, emite el grafo y sigue.
209# Salida al serial (no pinta tty0 ⇒ respeta el cero-parpadeo). No bloquea el arranque si falla.
210# El `timeout` no es paranoia decorativa: esto corre ANTES del exec de arje-zero, o sea que si se
211# cuelga no hay PID1, no hay getty y no hay pantalla — un arranque muerto e indistinguible de un kernel
212# colgado. El `|| true` protege del fallo, no del bloqueo.
213if [ -x /usr/bin/hammer ]; then
214 /bin/busybox timeout 15 /usr/bin/hammer boot menu > /dev/ttyS0 2>&1 || true
215fi
216
217# efivarfs: la NVRAM del firmware NO es visible hasta que se monta, y el kernel `linux-metal` ya trae
218# CONFIG_EFIVAR_FS=y (verificado en el .config sellado, no supuesto) — sólo faltaba montarlo. El
219# directorio sólo existe si la máquina arrancó por UEFI, así que el `[ -d ]` es la prueba de firmware
220# y no una precaución: en BIOS no está y no hay nada que montar.
221# ⚠ `busybox switch_root` NO arrastra /sys, igual que no arrastra /dev (por eso existe la línea del
222# devtmpfs de arriba). Sin sysfs, `/sys/firmware/efi/efivars` NO EXISTE y el reconciliador concluye
223# «esta máquina no arrancó por UEFI» — en una máquina que arrancó por UEFI. Medido: el primer intento
224# de este hook dio exactamente ese diagnóstico falso.
225/bin/busybox mount -t sysfs sys /sys 2>/dev/null || true
226if [ -d /sys/firmware/efi/efivars ]; then
227 /bin/busybox mount -t efivarfs efivarfs /sys/firmware/efi/efivars 2>/dev/null || true
228fi
229
230# Reconciliador del arranque (ADR 0018 §2). La NVRAM del firmware es estado compartido: otro sistema
231# operativo instalado al lado la reescribe sin coordinarse —Windows Update poniéndose primero en
232# BootOrder es el caso típico—. Contra eso no sirve confiar, sirve CONVERGER en cada arranque.
233#
234# Va al SERIAL **y a /dev/tty0**, a diferencia del menú de arriba: si takana tuvo que reponer su
235# entrada, el usuario tiene que poder enterarse mirando la pantalla. Un reconciliador que repara en
236# silencio deja al usuario conviviendo con una rareza intermitente que no entiende (§2).
237#
238# Mismo `timeout` y mismo `|| true` que arriba, por la misma razón: esto corre ANTES del exec de
239# arje-zero y colgarse acá es un arranque muerto.
240if [ -x /usr/bin/hammer ]; then
241 /bin/busybox timeout 15 /usr/bin/hammer boot entry reconcile 2>&1 \
242 | /bin/busybox tee /dev/tty0 > /dev/ttyS0 2>/dev/null || true
243fi
244
245# Respaldo del arranque (ADR 0018 §5). Se hace DESPUÉS de reconciliar, así lo que queda guardado es
246# el estado bueno y no el que dejó el vecino. El nombre del fichero sale del CONTENIDO: un arranque
247# que no cambió produce el mismo fichero, así que esto corre en cada arranque sin llenar la
248# partición de estado con copias idénticas.
249#
250# Vive en /var/lib/hammer, que en las imágenes es su propia partición y sobrevive a una reinstalación
251# del sistema. **No va al store**: el store lo barre `store-gc.sh`, que clasifica por nombre de
252# receta — un respaldo ahí sería «huérfano» y se borraría en el primer `--huerfanos`.
253[ -x /usr/bin/hammer ] && /bin/busybox timeout 15 /usr/bin/hammer boot entry backup > /dev/ttyS0 2>&1 || true
254exec /usr/bin/arje-zero
255RINIT
256chmod +x "$ROOT_TREE/sbin/init"
257
258# --- 3) ESP FAT32: DOS rutas al MISMO kernel + initramfs.cpio.gz, poblada con mtools de takana ------
259# ADR 0018 §1 y §3. El kernel se escribe en DOS sitios de la ESP, y no es redundancia decorativa:
260#
261# \EFI\takana\takanax64.efi VENDOR PATH — el nuestro. Nadie más lo reclama. Es el que apunta la
262# entrada NVRAM cuando la hay, y el que sobrevive a que otro sistema
263# operativo repueble la ruta de abajo.
264# \EFI\BOOT\BOOTX64.EFI RUTA FALLBACK de medios REMOVIBLES (spec UEFI). Es tierra de nadie:
265# la usa cualquier USB y varias rutas de recuperación de Windows la
266# reescriben. Se mantiene porque es lo que arranca SIN entrada NVRAM
267# —incluido OVMF en las pruebas— pero ya no es el único camino.
268#
269# Content-addressed ⇒ son el MISMO artefacto dos veces: el coste es bytes en la ESP, no complejidad.
270echo "==> ESP FAT32 (${ESP_MB}M): \\EFI\\takana\\takanax64.efi + \\EFI\\BOOT\\BOOTX64.EFI (=bzImage) + \\initramfs.cpio.gz"
271
272# Guardián de capacidad (ADR 0018 §4): que el contenido ENTRE se comprueba ANTES, con los números a
273# la vista. Sin esto el fallo llega como un «disk full» de mcopy a mitad de poblado — o peor, en una
274# ESP de fábrica compartida (100 MiB, medio llena de Windows) donde el margen real es el que decide
275# si takana se puede instalar. Duplicar el kernel CUESTA, y el costo se dice en voz alta.
276_k_b=$(wc -c < "$KERNEL"); _i_b=$(wc -c < "$IRD")
277_need_b=$(( _k_b * 2 + _i_b ))
278_need_mb=$(( (_need_b + 1048575) / 1048576 ))
279_esp_usable_mb=$(( ESP_MB - 2 )) # ~2 MiB de FAT32: sector de arranque, dos FAT y el raíz
280echo " kernel $(( _k_b / 1048576 ))M x2 (vendor + fallback) + initramfs $(( (_i_b + 1048575) / 1048576 ))M = ${_need_mb}M sobre ${_esp_usable_mb}M utiles"
281if [ "$_need_mb" -gt "$_esp_usable_mb" ]; then
282 echo "install-image-efi: NO ENTRA en la ESP — hacen falta ${_need_mb}M utiles y la ESP de ${ESP_MB}M da ${_esp_usable_mb}M." >&2
283 echo " subí ESP_MB (p.ej. ESP_MB=$(( _need_mb + 8 ))) o achicá el initramfs." >&2
284 exit 1
285fi
286
287rm -f "$ESP_IMG"; truncate -s "$(( ESP_MB * 1024 * 1024 ))" "$ESP_IMG"
288"$MFORMAT" -F -v HAMMER -i "$ESP_IMG" ::
289"$MMD" -i "$ESP_IMG" ::/EFI ::/EFI/BOOT ::/EFI/takana
290"$MCOPY" -i "$ESP_IMG" "$KERNEL" ::/EFI/takana/takanax64.efi
291"$MCOPY" -i "$ESP_IMG" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI
292"$MCOPY" -i "$ESP_IMG" "$IRD" ::/initramfs.cpio.gz
293# Verificar las DOS rutas: que una exista no dice nada de la otra, y el modo de fallo que esto
294# previene es precisamente quedarse con una sola sin enterarse.
295for _p in ::/EFI/takana/takanax64.efi ::/EFI/BOOT/BOOTX64.EFI ::/initramfs.cpio.gz; do
296 "$MDIR" -i "$ESP_IMG" "$_p" >/dev/null 2>&1 || { echo "install-image-efi: falta $_p en la ESP" >&2; exit 1; }
297done
298echo " ESP poblada y verificada: las dos rutas del kernel + el initramfs"
299
300# --- 4) GPT: p1=ESP, p2=hammer-root, p3=hammer-store, p4=hammer-state --------------------------------
301SECT_MIB=2048
302ESP_SECT=$(( ESP_MB * SECT_MIB ))
303ROOT_SECT=$(( ROOT_SIZE * SECT_MIB ))
304STORE_SECT=$(( STORE_SIZE * SECT_MIB ))
305STATE_SECT=$(( STATE_SIZE * SECT_MIB ))
306START1=2048
307START2=$(( START1 + ESP_SECT ))
308START3=$(( START2 + ROOT_SECT ))
309START4=$(( START3 + STORE_SECT ))
310TOTAL_SECT=$(( START4 + STATE_SECT + 2048 ))
311ESP_GUID="C12A7328-F81F-11D2-BA4B-00A0C93EC93B" # GPT "EFI System Partition"
312
313echo "==> imagen: $IMG (GPT UEFI; ESP ${ESP_MB}M + root ${ROOT_SIZE}M + store ${STORE_SIZE}M + estado ${STATE_SIZE}M)"
314rm -f "$IMG"; truncate -s "$(( TOTAL_SECT * 512 ))" "$IMG"
315"$SFDISK" --quiet "$IMG" >/dev/null <<EOF
316label: gpt
317start=$START1, size=$ESP_SECT, type=$ESP_GUID, name="EFI System Partition"
318start=$START2, size=$ROOT_SECT, type=linux, name="hammer-root"
319start=$START3, size=$STORE_SECT, type=linux, name="hammer-store"
320start=$START4, size=$STATE_SECT, type=linux, name="hammer-state"
321EOF
322
323# --- 5) poblar las 3 ext4 (root-owned vía unshare -r) y empalmar ESP + ext4 en sus offsets ----------
324echo "==> mke2fs -d + empalme (ESP con mtools ya lista; ext4 bajo unshare -r)"
325unshare -r sh -eu <<EOF
326"$MKE2FS" -q -t ext4 -L hammer-root -E root_owner=0:0 -d "$ROOT_TREE" "$ROOT_IMG" ${ROOT_SIZE}M
327"$MKE2FS" -q -t ext4 -L hammer-store -E root_owner=0:0 -d "$ROOTFS/store" "$STORE_IMG" ${STORE_SIZE}M
328"$MKE2FS" -q -t ext4 -L hammer-state -E root_owner=0:0 -d "$ROOTFS/var/lib/hammer" "$STATE_IMG" ${STATE_SIZE}M
329EOF
330dd if="$ESP_IMG" of="$IMG" bs=512 seek=$START1 conv=sparse,notrunc status=none
331dd if="$ROOT_IMG" of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
332dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START3 conv=sparse,notrunc status=none
333dd if="$STATE_IMG" of="$IMG" bs=512 seek=$START4 conv=sparse,notrunc status=none
334
335rm -rf "$STAGE"
336echo "==> imagen EFI creada: $(du -h "$IMG" | cut -f1) en disco (sparse), $(( TOTAL_SECT * 512 / 1024 / 1024 ))M virtuales"
337echo " auto-boot UEFI: qemu-system-x86_64 -drive if=pflash,...OVMF... -drive file=$IMG (sin -kernel)"
338
339# --- 6) arranque de prueba en OVMF (sin -kernel: la firmware lanza \EFI\BOOT\BOOTX64.EFI) -----------
340if [ "${BOOT:-0}" = 1 ]; then
341 OVMF_CODE="${OVMF_CODE:-/usr/share/edk2/x64/OVMF_CODE.4m.fd}"
342 OVMF_VARS="${OVMF_VARS:-/usr/share/edk2/x64/OVMF_VARS.4m.fd}"
343 [ -r "$OVMF_CODE" ] && [ -r "$OVMF_VARS" ] || { echo "no encuentro OVMF (set OVMF_CODE/OVMF_VARS): $OVMF_CODE" >&2; exit 1; }
344 cp "$OVMF_VARS" work/.efi-vars.fd
345 accel=""
346 [ "${KVM:-0}" = 1 ] && [ -w /dev/kvm ] && accel="-enable-kvm -cpu host"
347 echo "==> arranque de prueba en OVMF (sin -kernel) — deadline ${DEADLINE:-180}s"
348 # shellcheck disable=SC2086
349 exec timeout "${DEADLINE:-180}" qemu-system-x86_64 -m "${MEM:-4096}" -no-reboot -nographic $accel \
350 -drive if=pflash,unit=0,format=raw,readonly=on,file="$OVMF_CODE" \
351 -drive if=pflash,unit=1,format=raw,file="work/.efi-vars.fd" \
352 -drive file="$IMG",format=raw,if=virtio
353fi