#!/bin/sh # iso-image.sh — Etapa E5 (SDD 13): produce un MEDIO LIVE booteable como ISO 9660 / El Torito. # # A diferencia de install-image.sh (E1, disco GPT con / persistente en vda2), un medio live arranca # ENTERO desde RAM: GRUB (El Torito) carga kernel + initramfs desde el ISO 9660, el kernel desempaqueta # el initramfs (el rootfs del producto) a un tmpfs y corre /init — sin tocar disco. Es el medio para # correr el instalador (`hammer install /dev/sdX`) en hardware nuevo, o para probar el sistema sin # instalarlo. # # El ISO lo ARMA `xorriso` construido por hammer desde fuente (recipes/xorriso.toml) — el host no lo # trae. La cadena de boot BIOS reusa los módulos GRUB i386-pc de E1, pero con el core El Torito # (formato i386-pc-eltorito + iso9660) en vez del core de disco (ext2 + biosdisk). # # Cadena: SeaBIOS → El Torito boot catalog → eltorito.img (cdboot+core GRUB) → lee (cd0)/boot/grub/ # grub.cfg → `linux /boot/bzImage rdinit=/init` + `initrd /boot/initramfs.cpio.gz` → kernel desempaqueta # → /init (marcador + exec del init real del rootfs). # # Uso: # ./scripts/iso-image.sh # crea work/hammer-live.iso # BOOT=1 KVM=1 ./scripts/iso-image.sh # además lo arranca desde el CD (auto-boot) # # Variables: # ROOTFS dir rootfs a empaquetar como initramfs (default work/builder-rootfs) # KERNEL bzImage a embeber (default store/*-linux/boot/bzImage) # ISO ISO de salida (default work/hammer-live.iso) # XORRISO binario xorriso (default el del store; si no, host) # GRUB_LIB módulos GRUB i386-pc (default /usr/lib/grub/i386-pc) # CMDLINE cmdline del kernel (default "console=ttyS0 rdinit=/init") # BOOT/KVM/MEM passthrough al arranque de prueba set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" ROOTFS="${ROOTFS:-work/builder-rootfs}" ISO="${ISO:-work/hammer-live.iso}" KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}" GRUB_LIB="${GRUB_LIB:-/usr/lib/grub/i386-pc}" GRUB_EFI_LIB="${GRUB_EFI_LIB:-/usr/lib/grub/x86_64-efi}" CMDLINE="${CMDLINE:-console=ttyS0 rdinit=/init}" # xorriso: preferimos el construido por hammer (dogfooding de recipes/xorriso.toml); fallback al host. XORRISO="${XORRISO:-$(ls store/*-xorriso/usr/bin/xorriso 2>/dev/null | head -1)}" [ -n "$XORRISO" ] && [ -x "$XORRISO" ] || XORRISO="$(command -v xorriso 2>/dev/null || true)" # mtools (mformat/mcopy): para poblar la ESP FAT del arranque EFI. Preferimos el de hammer (dogfooding). MTOOLS_DIR="${MTOOLS_DIR:-$(ls -d store/*-mtools/usr/bin 2>/dev/null | head -1)}" [ -d "$ROOTFS" ] || { echo "no existe ROOTFS: $ROOTFS" >&2; exit 1; } [ -e "$ROOTFS/sbin/init" ] || { echo "ROOTFS sin /sbin/init" >&2; exit 1; } [ -n "$KERNEL" ] && [ -r "$KERNEL" ] || { echo "no encuentro el kernel (set KERNEL=)" >&2; exit 1; } [ -n "$XORRISO" ] || { echo "no encuentro xorriso (build recipes/xorriso.toml)" >&2; exit 1; } [ -d "$GRUB_LIB" ] && [ -r "$GRUB_LIB/cdboot.img" ] || { echo "faltan módulos GRUB i386-pc en $GRUB_LIB" >&2; exit 1; } command -v grub-mkimage >/dev/null 2>&1 || { echo "falta grub-mkimage" >&2; exit 1; } command -v cpio >/dev/null 2>&1 || { echo "falta cpio" >&2; exit 1; } echo "==> xorriso: $XORRISO ($("$XORRISO" -version 2>&1 | grep -m1 'xorriso version' || echo '?'))" STAGE="work/.iso-stage" rm -rf "$STAGE" ISO_ROOT="$STAGE/iso" RFS="$STAGE/rfs" mkdir -p "$ISO_ROOT/boot/grub/i386-pc" "$RFS" # --- initramfs: rootfs (hardlinks) + /init marcador que arranca el init real --- echo "==> staging initramfs desde $ROOTFS (hardlinks) + /init marcador" cp -al "$ROOTFS" "$RFS/root" # El cpio se arma desde $RFS/root; inyectamos /init ahí (rdinit=/init lo busca en la raíz del initramfs). # /init del medio live: monta lo básico + marcador. Con AUTO_INSTALL=, instala desatendido a ese # disco y apaga (instalación unattended + scaffolding del test); si no, arranca el init real (live). { echo '#!/bin/sh' echo '# /init del medio live (Etapa E5).' echo '/bin/busybox mount -t proc proc /proc 2>/dev/null || true' echo '/bin/busybox mount -t sysfs sys /sys 2>/dev/null || true' echo '/bin/busybox mount -t devtmpfs dev /dev 2>/dev/null || true' echo 'echo "HAMMER-ISO-LIVE-OK: medio live arrancado desde El Torito"' if [ -n "${AUTO_INSTALL:-}" ]; then echo "echo 'HAMMER-AUTO-INSTALL: instalando a ${AUTO_INSTALL}'" echo "if /usr/bin/hammer-install '${AUTO_INSTALL}'; then echo 'HAMMER-INSTALL-OK'; else echo 'HAMMER-INSTALL-FAIL'; fi" echo '/bin/busybox sync' echo '/bin/busybox poweroff -f' elif [ "${INSTALLER:-0}" = 1 ] && [ "${INSTALLER_INTERACTIVE:-1}" = 1 ]; then # ISO instalador SIN AUTO_INSTALL ⇒ recibir al usuario con la TUI del instalador en la consola. # Tras instalar (o cancelar) ofrece reiniciar o caer al live. HAMMER_INSTALL_INTERACTIVE fuerza el # modo aunque /dev/console no pase el test -t (a esta altura stdin puede no ser un tty). echo '[ -e /dev/console ] && exec 0/dev/console 2>/dev/console' echo 'HAMMER_INSTALL_INTERACTIVE=1 /usr/bin/hammer-install || true' echo 'echo; echo "Instalación finalizada. [r] reiniciar · [Enter] shell en vivo"' echo 'read -r _a || _a=""' echo 'case "$_a" in r|R) /bin/busybox reboot -f ;; *) exec /sbin/init ;; esac' else echo 'exec /sbin/init' fi } > "$RFS/root/init" chmod +x "$RFS/root/init" # --- INSTALLER=1: payload de instalación a disco DESDE el live (refinamiento #1) --- # Bundle kernel + GRUB (boot.img + core.img MBR-prefix + módulos) bajo /usr/lib/hammer/install/ y el # instalador en /usr/bin/hammer-install. El core.img se arma acá (build-time, el host tiene # grub-mkimage); el live NO lo necesita — sólo dd's. core prefix (hd0,msdos1)/boot/grub para boot MBR. if [ "${INSTALLER:-0}" = 1 ]; then echo "==> INSTALLER=1: bundling payload de instalación a disco (kernel + GRUB MBR + hammer-install)" IPL="$RFS/root/usr/lib/hammer/install" mkdir -p "$IPL/grub/i386-pc" "$IPL/kernel" cp "$KERNEL" "$IPL/kernel/bzImage"; chmod 0644 "$IPL/kernel/bzImage" # Rama EFI del instalador (ADR 0010 paso 5): el kernel `linux-metal` hornea CONFIG_CMDLINE con # initrd=/initramfs.cpio.gz rdinit=/init ⇒ arranca por EFI-stub como \EFI\BOOT\BOOTX64.EFI sin # bootloader. Lo bundleamos junto al genérico; hammer-install lo usa si el live corre en UEFI. EFI_KERNEL="${EFI_KERNEL:-$(ls -dt store/*-linux-metal/boot/bzImage 2>/dev/null | head -1)}" if [ -n "${EFI_KERNEL:-}" ] && [ -r "$EFI_KERNEL" ]; then cp "$EFI_KERNEL" "$IPL/kernel/bzImage-efi"; chmod 0644 "$IPL/kernel/bzImage-efi" echo " + kernel EFI-stub (metal) para la rama UEFI del instalador" else echo " ! sin kernel linux-metal en el store ⇒ el instalador sólo tendrá rama BIOS/MBR" >&2 fi cp "$GRUB_LIB/boot.img" "$IPL/grub/boot.img" cp "$GRUB_LIB"/*.mod "$GRUB_LIB"/*.lst "$IPL/grub/i386-pc/" 2>/dev/null || true # core.img para disco MBR: prefix a la 1ª partición msdos, módulos de disco (biosdisk/part_msdos/ext2). grub-mkimage -O i386-pc -p '(hd0,msdos1)/boot/grub' -o "$IPL/grub/core.img" \ biosdisk part_msdos ext2 normal configfile linux echo ls boot search install -m 0755 "$ROOT/scripts/hammer-live-install.sh" "$RFS/root/usr/bin/hammer-install" # hammer-recover (static musl): auto-sana un upgrade interrumpido al arranque del sistema instalado. RECOVER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer-recover" if [ ! -x "$RECOVER_BIN" ]; then echo "==> compilando hammer-recover (static musl) para el payload" cargo build --release --target x86_64-unknown-linux-musl -p hammer-recover --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1 fi [ -x "$RECOVER_BIN" ] || { echo "no pude construir hammer-recover (target musl?)" >&2; exit 1; } install -m 0755 "$RECOVER_BIN" "$IPL/hammer-recover" # CLI `hammer` (static musl) para el menú de arranque por grafo (ADR 0010): el hook de init del # sistema instalado lo invoca como `hammer boot menu`. El producto trae arje-zero/hammerd pero NO el # CLI ⇒ lo bundleamos al payload; hammer-install lo copia a /usr/bin/hammer del disco. HAMMER_BIN="$ROOT/target/x86_64-unknown-linux-musl/release/hammer" if [ ! -x "$HAMMER_BIN" ]; then echo "==> compilando hammer CLI (static musl) para el payload" cargo build --release --target x86_64-unknown-linux-musl -p hammer-cli --manifest-path "$ROOT/Cargo.toml" >/dev/null 2>&1 fi [ -x "$HAMMER_BIN" ] || { echo "no pude construir el CLI hammer (target musl?)" >&2; exit 1; } install -m 0755 "$HAMMER_BIN" "$IPL/hammer" fi echo "==> empaquetando initramfs.cpio.gz" ( cd "$RFS/root" && find . -print0 | cpio --null -o -H newc -R 0:0 2>/dev/null | gzip -1 ) > "$ISO_ROOT/boot/initramfs.cpio.gz" # --- kernel + módulos GRUB + grub.cfg en el árbol del ISO --- cp "$KERNEL" "$ISO_ROOT/boot/bzImage"; chmod 0644 "$ISO_ROOT/boot/bzImage" cp "$GRUB_LIB"/*.mod "$GRUB_LIB"/*.lst "$ISO_ROOT/boot/grub/i386-pc/" 2>/dev/null || true cat > "$ISO_ROOT/boot/grub/grub.cfg" < grub-mkimage El Torito (formato i386-pc-eltorito, módulos iso9660)" grub-mkimage -O i386-pc-eltorito -p '/boot/grub' -o "$ISO_ROOT/boot/grub/i386-pc/eltorito.img" \ biosdisk iso9660 part_gpt part_msdos normal configfile linux echo ls boot search # --- EFI=1: rama UEFI (refinamiento #3). Construye grubx64.efi (mismo grub.cfg, lee kernel+initrd del # iso9660) y una ESP FAT con EFI/BOOT/BOOTX64.EFI, poblada con mtools (sin root). El kernel ya trae # EFI_STUB; aquí GRUB EFI es el cargador (espeja la rama BIOS). xorriso añade un 2º El Torito (EFI). --- EFI_ARGS="" if [ "${EFI:-0}" = 1 ]; then MFORMAT="$MTOOLS_DIR/mformat"; MCOPY="$MTOOLS_DIR/mcopy"; MMD="$MTOOLS_DIR/mmd" [ -x "$MFORMAT" ] && [ -x "$MCOPY" ] || MFORMAT="$(command -v mformat 2>/dev/null || true)" [ -n "$MFORMAT" ] && [ -x "$MFORMAT" ] || { echo "no encuentro mtools (build recipes/mtools.toml)" >&2; exit 1; } [ -x "$MCOPY" ] || { MCOPY="$(command -v mcopy)"; MMD="$(command -v mmd)"; } ESP="$ISO_ROOT/boot/grub/efi.img" if [ "${EFI_STUB:-0}" = 1 ]; then # --- EFI_STUB=1: arranque por EFI-STUB DIRECTO (sin GRUB-EFI). El bzImage ES el BOOTX64.EFI; la # firmware lo lanza por el path de medio removible \EFI\BOOT\BOOTX64.EFI. El cmdline va # HORNEADO en el kernel (CONFIG_CMDLINE, recipes/linux-metal.toml) con initrd=/initramfs.cpio.gz # ⇒ el EFI-stub carga el initrd de la PROPIA ESP (file.c convierte '/'→'\'). Esquiva el bug de # GRUB 2.14 que cuelga la rama UEFI con cualquier kernel. --- INITRAMFS="$ISO_ROOT/boot/initramfs.cpio.gz" [ -r "$INITRAMFS" ] || { echo "no encuentro $INITRAMFS para la ESP EFI-stub" >&2; exit 1; } echo "==> EFI=1 EFI_STUB=1: kernel→BOOTX64.EFI + initramfs en la ESP (FAT32, sin GRUB-EFI)" # ESP grande (kernel + initramfs + holgura) ⇒ FAT32 (-F), válido y recomendado para ESP en metal. espkb=$(( ( $(stat -c%s "$KERNEL") + $(stat -c%s "$INITRAMFS") ) / 1024 + 8192 )) rm -f "$ESP"; truncate -s "$(( espkb * 1024 ))" "$ESP" "$MFORMAT" -F -i "$ESP" :: "$MMD" -i "$ESP" ::/EFI ::/EFI/BOOT "$MCOPY" -i "$ESP" "$KERNEL" ::/EFI/BOOT/BOOTX64.EFI "$MCOPY" -i "$ESP" "$INITRAMFS" ::/initramfs.cpio.gz else # --- EFI=1 (default): GRUB EFI como cargador (espeja la rama BIOS, lee kernel+initrd del iso9660). --- [ -d "$GRUB_EFI_LIB" ] || { echo "faltan módulos GRUB x86_64-efi en $GRUB_EFI_LIB" >&2; exit 1; } echo "==> EFI=1: grub-mkimage x86_64-efi + ESP FAT (mtools: $MFORMAT)" mkdir -p "$STAGE/efi" grub-mkimage -O x86_64-efi -p '/boot/grub' -o "$STAGE/efi/bootx64.efi" \ part_gpt part_msdos fat iso9660 normal configfile linux echo ls boot search search_fs_uuid efi_gop efi_uga # Tamaño ≥ 1.44 MiB; mformat SIN -F auto-elige FAT12/16 por nº de clusters (FAT32 exige ~33 MiB). espkb=$(( ( $(stat -c%s "$STAGE/efi/bootx64.efi") / 1024 ) + 512 )) [ "$espkb" -lt 1440 ] && espkb=1440 rm -f "$ESP"; truncate -s "$(( espkb * 1024 ))" "$ESP" "$MFORMAT" -i "$ESP" :: "$MMD" -i "$ESP" ::/EFI ::/EFI/BOOT "$MCOPY" -i "$ESP" "$STAGE/efi/bootx64.efi" ::/EFI/BOOT/BOOTX64.EFI fi EFI_ARGS="-eltorito-alt-boot -e boot/grub/efi.img -no-emul-boot -isohybrid-gpt-basdat" fi # --- armar el ISO con xorriso (personalidad mkisofs), El Torito BIOS (+EFI si EFI=1) + isohybrid MBR --- echo "==> xorriso: armando $ISO (El Torito BIOS${EFI_ARGS:+ + EFI} + isohybrid MBR)" rm -f "$ISO" MBR_ARGS="" [ -r "$GRUB_LIB/boot_hybrid.img" ] && MBR_ARGS="--grub2-mbr $GRUB_LIB/boot_hybrid.img" # shellcheck disable=SC2086 "$XORRISO" -as mkisofs \ -volid HAMMER_LIVE \ -graft-points \ $MBR_ARGS \ -b boot/grub/i386-pc/eltorito.img \ -no-emul-boot -boot-load-size 4 -boot-info-table \ --grub2-boot-info \ $EFI_ARGS \ -o "$ISO" \ "$ISO_ROOT" 2>&1 | grep -viE "^xorriso : NOTE|Drive current|Media current|Media status|Media summary|libisofs:|^xorriso : UPDATE" || true [ -s "$ISO" ] || { echo "FALLO: xorriso no produjo el ISO" >&2; exit 1; } rm -rf "$STAGE" echo "==> ISO live creado: $(du -h "$ISO" | cut -f1) — $ISO" echo " auto-boot: qemu-system-x86_64 -cdrom $ISO -boot d -nographic" if [ "${BOOT:-0}" = 1 ]; then echo "==> arranque de prueba (desde el CD El Torito)" accel="" [ "${KVM:-0}" = 1 ] && [ -w /dev/kvm ] && accel="-enable-kvm -cpu host" # shellcheck disable=SC2086 exec qemu-system-x86_64 -m "${MEM:-4096}" -no-reboot -nographic $accel \ -cdrom "$ISO" -boot d fi