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…
auto-recover al arranque: crate hammer-recover + hook /sbin/init (E4 #4b)
1#!/bin/sh
2# iso-image.sh — Etapa E5 (SDD 13): produce un MEDIO LIVE booteable como ISO 9660 / El Torito.
3#
4# A diferencia de install-image.sh (E1, disco GPT con / persistente en vda2), un medio live arranca
5# ENTERO desde RAM: GRUB (El Torito) carga kernel + initramfs desde el ISO 9660, el kernel desempaqueta
6# el initramfs (el rootfs del producto) a un tmpfs y corre /init — sin tocar disco. Es el medio para
7# correr el instalador (`hammer install /dev/sdX`) en hardware nuevo, o para probar el sistema sin
8# instalarlo.
9#
10# El ISO lo ARMA `xorriso` construido por hammer desde fuente (recipes/xorriso.toml) — el host no lo
11# trae. La cadena de boot BIOS reusa los módulos GRUB i386-pc de E1, pero con el core El Torito
12# (formato i386-pc-eltorito + iso9660) en vez del core de disco (ext2 + biosdisk).
13#
14# Cadena: SeaBIOS → El Torito boot catalog → eltorito.img (cdboot+core GRUB) → lee (cd0)/boot/grub/
15# grub.cfg → `linux /boot/bzImage rdinit=/init` + `initrd /boot/initramfs.cpio.gz` → kernel desempaqueta
16# → /init (marcador + exec del init real del rootfs).
17#
18# Uso:
19# ./scripts/iso-image.sh # crea work/hammer-live.iso
20# BOOT=1 KVM=1 ./scripts/iso-image.sh # además lo arranca desde el CD (auto-boot)
21#
22# Variables:
23# ROOTFS dir rootfs a empaquetar como initramfs (default work/builder-rootfs)
24# KERNEL bzImage a embeber (default store/*-linux/boot/bzImage)
25# ISO ISO de salida (default work/hammer-live.iso)
26# XORRISO binario xorriso (default el del store; si no, host)
27# GRUB_LIB módulos GRUB i386-pc (default /usr/lib/grub/i386-pc)
28# CMDLINE cmdline del kernel (default "console=ttyS0 rdinit=/init")
29# BOOT/KVM/MEM passthrough al arranque de prueba
30set -eu
31ROOT="$(cd "$(dirname "$0")/.." && pwd)"
32cd "$ROOT"
33
34ROOTFS="${ROOTFS:-work/builder-rootfs}"
35ISO="${ISO:-work/hammer-live.iso}"
36KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}"
37GRUB_LIB="${GRUB_LIB:-/usr/lib/grub/i386-pc}"
38GRUB_EFI_LIB="${GRUB_EFI_LIB:-/usr/lib/grub/x86_64-efi}"
39CMDLINE="${CMDLINE:-console=ttyS0 rdinit=/init}"
40# xorriso: preferimos el construido por hammer (dogfooding de recipes/xorriso.toml); fallback al host.
41XORRISO="${XORRISO:-$(ls store/*-xorriso/usr/bin/xorriso 2>/dev/null | head -1)}"
42[ -n "$XORRISO" ] && [ -x "$XORRISO" ] || XORRISO="$(command -v xorriso 2>/dev/null || true)"
43# mtools (mformat/mcopy): para poblar la ESP FAT del arranque EFI. Preferimos el de hammer (dogfooding).
44MTOOLS_DIR="${MTOOLS_DIR:-$(ls -d store/*-mtools/usr/bin 2>/dev/null | head -1)}"
45
46[ -d "$ROOTFS" ] || { echo "no existe ROOTFS: $ROOTFS" >&2; exit 1; }
47[ -e "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init" >&2; exit 1; }
48[ -n "$KERNEL" ] && [ -r "$KERNEL" ] || { echo "no encuentro el kernel (set KERNEL=<bzImage>)" >&2; exit 1; }
49[ -n "$XORRISO" ] || { echo "no encuentro xorriso (build recipes/xorriso.toml)" >&2; exit 1; }
50[ -d "$GRUB_LIB" ] && [ -r "$GRUB_LIB/cdboot.img" ] || { echo "faltan módulos GRUB i386-pc en $GRUB_LIB" >&2; exit 1; }
51command -v grub-mkimage >/dev/null 2>&1 || { echo "falta grub-mkimage" >&2; exit 1; }
52command -v cpio >/dev/null 2>&1 || { echo "falta cpio" >&2; exit 1; }
53
54echo "==> xorriso: $XORRISO ($("$XORRISO" -version 2>&1 | grep -m1 'xorriso version' || echo '?'))"
55
56STAGE="work/.iso-stage"
57rm -rf "$STAGE"
58ISO_ROOT="$STAGE/iso"
59RFS="$STAGE/rfs"
60mkdir -p "$ISO_ROOT/boot/grub/i386-pc" "$RFS"
61
62# --- initramfs: rootfs (hardlinks) + /init marcador que arranca el init real ---
63echo "==> staging initramfs desde $ROOTFS (hardlinks) + /init marcador"
64cp -al "$ROOTFS" "$RFS/root"
65# El cpio se arma desde $RFS/root; inyectamos /init ahí (rdinit=/init lo busca en la raíz del initramfs).
66# /init del medio live: monta lo básico + marcador. Con AUTO_INSTALL=<dev>, instala desatendido a ese
67# disco y apaga (instalación unattended + scaffolding del test); si no, arranca el init real (live).
68{
69 echo '#!/bin/sh'
70 echo '# /init del medio live (Etapa E5).'
71 echo '/bin/busybox mount -t proc proc /proc 2>/dev/null || true'
72 echo '/bin/busybox mount -t sysfs sys /sys 2>/dev/null || true'
73 echo '/bin/busybox mount -t devtmpfs dev /dev 2>/dev/null || true'
74 echo 'echo "HAMMER-ISO-LIVE-OK: medio live arrancado desde El Torito"'
75 if [ -n "${AUTO_INSTALL:-}" ]; then
76 echo "echo 'HAMMER-AUTO-INSTALL: instalando a ${AUTO_INSTALL}'"
77 echo "if /usr/bin/hammer-install '${AUTO_INSTALL}'; then echo 'HAMMER-INSTALL-OK'; else echo 'HAMMER-INSTALL-FAIL'; fi"
78 echo '/bin/busybox sync'
79 echo '/bin/busybox poweroff -f'
80 else
81 echo 'exec /sbin/init'
82 fi
83} > "$RFS/root/init"
84chmod +x "$RFS/root/init"
85
86# --- INSTALLER=1: payload de instalación a disco DESDE el live (refinamiento #1) ---
87# Bundle kernel + GRUB (boot.img + core.img MBR-prefix + módulos) bajo /usr/lib/hammer/install/ y el
88# instalador en /usr/bin/hammer-install. El core.img se arma acá (build-time, el host tiene
89# grub-mkimage); el live NO lo necesita — sólo dd's. core prefix (hd0,msdos1)/boot/grub para boot MBR.
90if [ "${INSTALLER:-0}" = 1 ]; then
91 echo "==> INSTALLER=1: bundling payload de instalación a disco (kernel + GRUB MBR + hammer-install)"
92 IPL="$RFS/root/usr/lib/hammer/install"
93 mkdir -p "$IPL/grub/i386-pc" "$IPL/kernel"
94 cp "$KERNEL" "$IPL/kernel/bzImage"; chmod 0644 "$IPL/kernel/bzImage"
95 cp "$GRUB_LIB/boot.img" "$IPL/grub/boot.img"
96 cp "$GRUB_LIB"/*.mod "$GRUB_LIB"/*.lst "$IPL/grub/i386-pc/" 2>/dev/null || true
97 # core.img para disco MBR: prefix a la 1ª partición msdos, módulos de disco (biosdisk/part_msdos/ext2).
98 grub-mkimage -O i386-pc -p '(hd0,msdos1)/boot/grub' -o "$IPL/grub/core.img" \
99 biosdisk part_msdos ext2 normal configfile linux echo ls boot search
100 install -m 0755 "$ROOT/scripts/hammer-live-install.sh" "$RFS/root/usr/bin/hammer-install"
101 # hammer-recover (static musl): auto-sana un upgrade interrumpido al arranque del sistema instalado.
102 RECOVER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer-recover"
103 if [ ! -x "$RECOVER_BIN" ]; then
104 echo "==> compilando hammer-recover (static musl) para el payload"
105 cargo build --release --target x86_64-unknown-linux-musl -p hammer-recover --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1
106 fi
107 [ -x "$RECOVER_BIN" ] || { echo "no pude construir hammer-recover (target musl?)" >&2; exit 1; }
108 install -m 0755 "$RECOVER_BIN" "$IPL/hammer-recover"
109fi
110
111echo "==> empaquetando initramfs.cpio.gz"
112( cd "$RFS/root" && find . -print0 | cpio --null -o -H newc -R 0:0 2>/dev/null | gzip -1 ) > "$ISO_ROOT/boot/initramfs.cpio.gz"
113
114# --- kernel + módulos GRUB + grub.cfg en el árbol del ISO ---
115cp "$KERNEL" "$ISO_ROOT/boot/bzImage"; chmod 0644 "$ISO_ROOT/boot/bzImage"
116cp "$GRUB_LIB"/*.mod "$GRUB_LIB"/*.lst "$ISO_ROOT/boot/grub/i386-pc/" 2>/dev/null || true
117cat > "$ISO_ROOT/boot/grub/grub.cfg" <<CFG
118set timeout=2
119set default=0
120serial --unit=0 --speed=115200
121terminal_input serial console
122terminal_output serial console
123insmod iso9660
124insmod normal
125menuentry "hammer (live)" {
126 linux /boot/bzImage $CMDLINE
127 initrd /boot/initramfs.cpio.gz
128}
129CFG
130
131# --- core GRUB El Torito: igual que el de disco pero con iso9660+biosdisk en vez de ext2 ---
132echo "==> grub-mkimage El Torito (formato i386-pc-eltorito, módulos iso9660)"
133grub-mkimage -O i386-pc-eltorito -p '/boot/grub' -o "$ISO_ROOT/boot/grub/i386-pc/eltorito.img" \
134 biosdisk iso9660 part_gpt part_msdos normal configfile linux echo ls boot search
135
136# --- EFI=1: rama UEFI (refinamiento #3). Construye grubx64.efi (mismo grub.cfg, lee kernel+initrd del
137# iso9660) y una ESP FAT con EFI/BOOT/BOOTX64.EFI, poblada con mtools (sin root). El kernel ya trae
138# EFI_STUB; aquí GRUB EFI es el cargador (espeja la rama BIOS). xorriso añade un 2º El Torito (EFI). ---
139EFI_ARGS=""
140if [ "${EFI:-0}" = 1 ]; then
141 [ -d "$GRUB_EFI_LIB" ] || { echo "faltan módulos GRUB x86_64-efi en $GRUB_EFI_LIB" >&2; exit 1; }
142 MFORMAT="$MTOOLS_DIR/mformat"; MCOPY="$MTOOLS_DIR/mcopy"; MMD="$MTOOLS_DIR/mmd"
143 [ -x "$MFORMAT" ] && [ -x "$MCOPY" ] || MFORMAT="$(command -v mformat 2>/dev/null || true)"
144 [ -n "$MFORMAT" ] && [ -x "$MFORMAT" ] || { echo "no encuentro mtools (build recipes/mtools.toml)" >&2; exit 1; }
145 [ -x "$MCOPY" ] || { MCOPY="$(command -v mcopy)"; MMD="$(command -v mmd)"; }
146 echo "==> EFI=1: grub-mkimage x86_64-efi + ESP FAT (mtools: $MFORMAT)"
147 mkdir -p "$STAGE/efi"
148 grub-mkimage -O x86_64-efi -p '/boot/grub' -o "$STAGE/efi/bootx64.efi" \
149 part_gpt part_msdos fat iso9660 normal configfile linux echo ls boot search search_fs_uuid efi_gop efi_uga
150 # ESP FAT: dimensionada al grubx64.efi (+ holgura). mformat sobre una imagen truncada (FAT sin
151 # particionar; el El Torito EFI arranca el FS plano). Poblamos EFI/BOOT/BOOTX64.EFI con mcopy.
152 ESP="$ISO_ROOT/boot/grub/efi.img"
153 # Tamaño ≥ 1.44 MiB (mínimo razonable); mformat SIN -F auto-elige FAT12/16 por nº de clusters. NO
154 # forzar FAT32 (-F): exige ~33 MiB mínimos y sobre una imagen chica deja un FAT inválido que la
155 # firmware no lee ("failed to load ... Not Found").
156 espkb=$(( ( $(stat -c%s "$STAGE/efi/bootx64.efi") / 1024 ) + 512 ))
157 [ "$espkb" -lt 1440 ] && espkb=1440
158 rm -f "$ESP"; truncate -s "$(( espkb * 1024 ))" "$ESP"
159 "$MFORMAT" -i "$ESP" ::
160 "$MMD" -i "$ESP" ::/EFI ::/EFI/BOOT
161 "$MCOPY" -i "$ESP" "$STAGE/efi/bootx64.efi" ::/EFI/BOOT/BOOTX64.EFI
162 EFI_ARGS="-eltorito-alt-boot -e boot/grub/efi.img -no-emul-boot -isohybrid-gpt-basdat"
163fi
164
165# --- armar el ISO con xorriso (personalidad mkisofs), El Torito BIOS (+EFI si EFI=1) + isohybrid MBR ---
166echo "==> xorriso: armando $ISO (El Torito BIOS${EFI_ARGS:+ + EFI} + isohybrid MBR)"
167rm -f "$ISO"
168MBR_ARGS=""
169[ -r "$GRUB_LIB/boot_hybrid.img" ] && MBR_ARGS="--grub2-mbr $GRUB_LIB/boot_hybrid.img"
170# shellcheck disable=SC2086
171"$XORRISO" -as mkisofs \
172 -volid HAMMER_LIVE \
173 -graft-points \
174 $MBR_ARGS \
175 -b boot/grub/i386-pc/eltorito.img \
176 -no-emul-boot -boot-load-size 4 -boot-info-table \
177 --grub2-boot-info \
178 $EFI_ARGS \
179 -o "$ISO" \
180 "$ISO_ROOT" 2>&1 | grep -viE "^xorriso : NOTE|Drive current|Media current|Media status|Media summary|libisofs:|^xorriso : UPDATE" || true
181
182[ -s "$ISO" ] || { echo "FALLO: xorriso no produjo el ISO" >&2; exit 1; }
183rm -rf "$STAGE"
184echo "==> ISO live creado: $(du -h "$ISO" | cut -f1) — $ISO"
185echo " auto-boot: qemu-system-x86_64 -cdrom $ISO -boot d -nographic"
186
187if [ "${BOOT:-0}" = 1 ]; then
188 echo "==> arranque de prueba (desde el CD El Torito)"
189 accel=""
190 [ "${KVM:-0}" = 1 ] && [ -w /dev/kvm ] && accel="-enable-kvm -cpu host"
191 # shellcheck disable=SC2086
192 exec qemu-system-x86_64 -m "${MEM:-4096}" -no-reboot -nographic $accel \
193 -cdrom "$ISO" -boot d
194fi