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 1389Shell 137Markdown 80Rust 802602 Definitions

install-image: UNA imagen que arranca por BIOS **y** por UEFI

«Instalable en Hetzner o en cualquier otro servicio» se rompía en el arranque: Hetzner Cloud arranca
BIOS y otros proveedores sólo ofrecen UEFI. Tener dos imágenes hermanas obliga a elegir por proveedor
y a que diverjan — y ya divergían: la hermana EFI documenta etiquetas `takana-*` que su propio código
no escribe (corregido en este commit; son `hammer-*` y están congeladas a propósito por el ADR 0016,
porque viven en sistemas YA INSTALADOS).

Layout nuevo: `p1` BIOS-boot · **`p2` ESP FAT32** · `p3` `/` · `p4` estado · `p5` store (última, la
que crece). El `core.img` de i386-pc y el `BOOTX64.EFI` de x86_64-efi apuntan **los dos** a
`(hd0,gpt3)/boot/grub`: **un solo `grub.cfg`, una sola línea de comando, un solo sitio donde
editarla.** La ESP se puebla con mtools, sin root y sin loop, como el resto del script.

No se usa EFI-stub directo acá (sí `install-image-efi.sh`, ADR 0010): el stub por la ruta fallback
recibe LoadOptions VACÍO y necesita la cmdline HORNEADA en el kernel; la de `linux-generic` sólo trae
la consola, sin `root=`. Hornearla ataría la línea de comando al ArtifactHash del kernel.

Verificado con LA MISMA imagen en los dos firmwares, hasta entrar por SSH:

    UEFI (OVMF)   → /sys/firmware/efi presente · PID1 arje-zero · raíz sda3 · store sda5
    BIOS (SeaBIOS)→ /sys/firmware/efi ausente  · PID1 arje-zero · raíz sda3 · store sda5

Si falta `grub-mkimage` con x86_64-efi o mtools, la ESP se saltea con un aviso que dice qué se pierde
—no en silencio—: la imagen sigue arrancando por BIOS, pero eso la ata a esos proveedores.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x

808e4a466012130401fb24bd04cc3ea12498bb3b · by Sergio · (1 month ago) · parent 62e6a1481 · browse the tree at this commit

Semantic delta

No definition changed: this change is text, documentation or data.

3 files · +87 −24

diff --git a/docs/29-mudanza.md b/docs/29-mudanza.md
index 130bf4b3..62859faf 100644
--- a/docs/29-mudanza.md
+++ b/docs/29-mudanza.md
@@ -247,9 +247,26 @@ Ejecuta el plan contra la máquina nueva. Requisitos, todos pagados en el SDD 28
 
 Lo que hoy la ata a Hetzner es poco y está medido:
 
-- **Arranque**: la imagen es GPT+BIOS. Hetzner Cloud arranca BIOS; otros proveedores piden UEFI. Los
-  dos caminos existen (`install-image.sh` / `install-image-efi.sh`); falta **una imagen que haga las
-  dos** (ESP + BIOS boot en el mismo disco) para no elegir por proveedor.
+- **Arranque: ✅ RESUELTO (2026-09-11).** La imagen ahora arranca por **BIOS y por UEFI**, y es la
+  misma imagen. Layout: `p1` BIOS-boot · `p2` ESP FAT32 · `p3` `/` · `p4` estado · `p5` store.
+
+  **Un solo `grub.cfg`**: el `core.img` de i386-pc y el `BOOTX64.EFI` de x86_64-efi apuntan los dos a
+  `(hd0,gpt3)/boot/grub`, así que hay una sola línea de comando y un solo sitio donde editarla. Dos
+  imágenes hermanas obligarían a elegir por proveedor y a que divergieran — y ya divergían: la
+  hermana EFI documentaba etiquetas `takana-*` que su propio código no escribe (corregido).
+
+  **No se usa EFI-stub directo acá** (sí lo hace `install-image-efi.sh`, ADR 0010): el stub arrancado
+  por la ruta fallback recibe LoadOptions VACÍO, así que necesita la cmdline HORNEADA en el kernel, y
+  la de `linux-generic` sólo trae la consola — sin `root=`. Hornearla ataría la línea de comando al
+  ArtifactHash del kernel. Con GRUB EFI la cmdline vive en el `grub.cfg`, que es donde se edita.
+
+  **Verificado con la MISMA imagen en los dos firmwares**, hasta SSH:
+
+  | | UEFI (OVMF) | BIOS (SeaBIOS) |
+  |---|---|---|
+  | `/sys/firmware/efi` | **presente** | ausente |
+  | PID 1 | `arje-zero` | `arje-zero` |
+  | raíz · store | `sda3` · `sda5` | `sda3` · `sda5` |
 - **Red**: `netup` hace DHCPv4 y ya cubre el caso difícil (IP `/32` con la puerta fuera del prefijo,
   que es lo de Hetzner). Falta IPv6 estático y los metadatos de otros proveedores.
 - **Instalación**: `rescue → dd → reboot` no es de Hetzner: sirve en cualquier entorno de rescate con
diff --git a/scripts/install-image-efi.sh b/scripts/install-image-efi.sh
index 4093b0d7..11d74ad6 100755
--- a/scripts/install-image-efi.sh
+++ b/scripts/install-image-efi.sh
@@ -11,18 +11,22 @@
 # 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
+# El initramfs es MÍNIMO — sólo pivota: findfs LABEL=hammer-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):
+# ⚠ Las etiquetas son `hammer-*` y NO `takana-*`: están en el fstab y el arranque de
+# sistemas YA INSTALADOS, así que el renombre del ADR 0016 las dejó congeladas a propósito.
+# Este comentario decía `takana-*` y el código escribía `hammer-*` — mandaba a buscar una
+# etiqueta que no existe.
 #   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]
+#   p2  /                ext4  [hammer-root]  el rootfs real del producto (/sbin/init → arje-zero)
+#   p3  /store           ext4  [hammer-store]
+#   p4  /var/lib/hammer  ext4  [hammer-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.
+# (findfs LABEL=hammer-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.
@@ -72,7 +76,7 @@ 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) --------
+# --- 1) initramfs de pivote: busybox estático + /init (findfs LABEL=hammer-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"
@@ -187,7 +191,7 @@ rm -f "$ESP_IMG"; truncate -s "$(( ESP_MB * 1024 * 1024 ))" "$ESP_IMG"
 "$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 --------------------------------
+# --- 4) GPT: p1=ESP, p2=hammer-root, p3=hammer-store, p4=hammer-state --------------------------------
 SECT_MIB=2048
 ESP_SECT=$(( ESP_MB    * SECT_MIB ))
 ROOT_SECT=$(( ROOT_SIZE  * SECT_MIB ))
diff --git a/scripts/install-image.sh b/scripts/install-image.sh
index 3917e52e..7bb2307f 100755
--- a/scripts/install-image.sh
+++ b/scripts/install-image.sh
@@ -155,7 +155,23 @@ exec /usr/bin/arje-zero
 INIT
 chmod +x "$ROOT_TREE/sbin/init"
 
-# --- GPT: p1=BIOS boot (ef02), p2=/, p3=/var/lib/hammer, p4=/store ---
+# --- GPT: p1=BIOS boot (ef02), p2=ESP (FAT32), p3=/, p4=/var/lib/hammer, p5=/store ---
+#
+# ── LA IMAGEN ARRANCA EN LOS DOS MODOS, Y NO ES UN LUJO ────────────────────────────────────────
+# «Instalable en Hetzner o en cualquier otro servicio» se rompe en el arranque: Hetzner Cloud arranca
+# por BIOS y otros proveedores sólo ofrecen UEFI. Tener dos imágenes hermanas —una BIOS y otra EFI—
+# obliga a elegir por proveedor, y a que las dos diverjan (ya divergían: la hermana EFI documenta
+# etiquetas `takana-*` que su propio código no escribe).
+#
+# Ésta lleva LAS DOS: el MBR + `core.img` en la partición BIOS-boot, y un `BOOTX64.EFI` en la ESP.
+# **Los dos GRUB leen el MISMO `/boot/grub/grub.cfg`** de la raíz, así que hay una sola línea de
+# comando y un solo sitio donde cambiarla. Un firmware toma uno, el otro toma el otro, y el sistema
+# que arranca es idéntico.
+#
+# No se usa EFI-stub directo acá (sí lo hace `install-image-efi.sh`, ADR 0010): el stub arrancado por
+# la ruta fallback recibe LoadOptions VACÍO, así que necesita la cmdline HORNEADA en el kernel — y la
+# de `linux-generic` sólo trae la consola, sin `root=`. Hornearla ataría la línea de comando al
+# ArtifactHash del kernel. Con GRUB EFI la cmdline vive en el `grub.cfg`, que es donde se edita.
 #
 # EL STORE VA ÚLTIMO, Y ES A PROPÓSITO. Una imagen se escribe con `dd` sobre un disco que casi
 # siempre es MÁS GRANDE que ella (la del perfil servidor son 7 G sobre los 76 G de una caja hcloud),
@@ -164,14 +180,17 @@ chmod +x "$ROOT_TREE/sbin/init"
 # su tamaño de fábrica. El wrapper de /sbin/init la extiende en el primer arranque.
 SECT_MIB=2048
 BIOS_SECT=4096                                   # 2 MiB para core.img
+ESP_SIZE="${ESP_SIZE:-64}"                       # MiB de la ESP (FAT32 pide ≥ 33 MiB útiles)
+ESP_SECT=$(( ESP_SIZE * SECT_MIB ))
 ROOT_SECT=$(( ROOT_SIZE  * SECT_MIB ))
 STATE_SECT=$(( STATE_SIZE * SECT_MIB ))
 STORE_SECT=$(( STORE_SIZE * SECT_MIB ))
 START1=2048
-START2=$(( START1 + BIOS_SECT ))
-START3=$(( START2 + ROOT_SECT ))
-START4=$(( START3 + STATE_SECT ))
-TOTAL_SECT=$(( START4 + STORE_SECT + 2048 ))
+START2=$(( START1 + BIOS_SECT ))                 # ESP
+START3=$(( START2 + ESP_SECT ))                  # /
+START4=$(( START3 + ROOT_SECT ))                 # /var/lib/hammer
+START5=$(( START4 + STATE_SECT ))                # /store  (ÚLTIMA: es la que crece)
+TOTAL_SECT=$(( START5 + STORE_SECT + 2048 ))
 BIOS_GUID="21686148-6449-6E6F-744E-656564454649" # GPT "BIOS boot partition"
 
 # Target = block device (instalación in-place, Etapa E2 `takana install /dev/sdX`) o fichero imagen.
@@ -190,9 +209,10 @@ fi
 sfdisk --quiet "$IMG" >/dev/null <<EOF
 label: gpt
 start=$START1, size=$BIOS_SECT,  type=$BIOS_GUID, name="bios-boot"
-start=$START2, size=$ROOT_SECT,  type=linux,      name="hammer-root"
-start=$START3, size=$STATE_SECT, type=linux,      name="hammer-state"
-start=$START4, size=$STORE_SECT, type=linux,      name="hammer-store"
+start=$START2, size=$ESP_SECT,   type=uefi,       name="hammer-esp"
+start=$START3, size=$ROOT_SECT,  type=linux,      name="hammer-root"
+start=$START4, size=$STATE_SECT, type=linux,      name="hammer-state"
+start=$START5, size=$STORE_SECT, type=linux,      name="hammer-store"
 EOF
 
 # --- poblar las 3 particiones ext4 (root-owned vía unshare -r) y empalmar en su offset ---
@@ -201,9 +221,9 @@ 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
-dd if="$ROOT_IMG"  of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
-dd if="$STATE_IMG" of="$IMG" bs=512 seek=$START3 conv=sparse,notrunc status=none
-dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START4 conv=sparse,notrunc status=none
+dd if="$ROOT_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
+dd if="$STORE_IMG" of="$IMG" bs=512 seek=$START5 conv=sparse,notrunc status=none
 EOF
 
 # --- instalar GRUB en userspace SIN root/loop: grub-mkimage arma core.img, y un patch binario
@@ -212,9 +232,9 @@ EOF
 #      "adivinar el root device" del dir -d, y falla sin root; el patch manual evita ese sondeo.)
 echo "==> instalar GRUB (grub-mkimage + patch binario boot/core, sin root/loop)"
 mkdir -p "$GRUB_STAGE"
-# core.img: prefix a (hd0,gpt2)/boot/grub (root = 2ª partición GPT); embebemos lo justo para leer
-# GPT+ext2, cargar grub.cfg (normal/configfile) y el kernel (linux).
-grub-mkimage -O i386-pc -p '(hd0,gpt2)/boot/grub' -o "$GRUB_STAGE/core.img" \
+# core.img: prefix a (hd0,gpt3)/boot/grub — la raíz es la 3ª partición desde que hay ESP. Se
+# embebe lo justo para leer GPT+ext2, cargar grub.cfg (normal/configfile) y el kernel (linux).
+grub-mkimage -O i386-pc -p '(hd0,gpt3)/boot/grub' -o "$GRUB_STAGE/core.img" \
     biosdisk part_gpt ext2 normal configfile linux echo ls boot
 # Punteros (ABI estable de GRUB i386-pc):
 #   boot.img  off 0x5c  kernel_sector (u64 LE): LBA del 1er sector de core.img      → START1
@@ -237,6 +257,28 @@ with open(img, "r+b") as f:
 print(f"   boot.img→MBR (kernel_sector={s1}), core.img→sector {s1} ({len(core)} B), blocklist.start={s1+1}")
 PY
 
+# --- ESP: el MISMO GRUB, compilado para UEFI, leyendo el MISMO grub.cfg -------------------------
+# `BOOTX64.EFI` en \EFI\BOOT es la ruta FALLBACK: la firmware la arranca sin necesitar una entrada
+# NVRAM, que es lo que hace que la imagen sirva en un proveedor cualquiera sin tocarle el firmware.
+# La ESP se puebla con mtools (mformat/mcopy): sin root, sin loop, igual que el resto del script.
+if command -v grub-mkimage >/dev/null 2>&1 && [ -d /usr/lib/grub/x86_64-efi ] \
+   && command -v mformat >/dev/null 2>&1; then
+    echo "==> ESP: BOOTX64.EFI (GRUB x86_64-efi, mismo grub.cfg que el BIOS)"
+    grub-mkimage -O x86_64-efi -p '(hd0,gpt3)/boot/grub' -o "$GRUB_STAGE/bootx64.efi" \
+        part_gpt ext2 normal configfile linux echo ls boot efi_gop efi_uga fat
+    ESP_IMG="$STAGE/esp.img"
+    truncate -s "${ESP_SIZE}M" "$ESP_IMG"
+    mformat -i "$ESP_IMG" -F -v HAMMERESP ::
+    mmd -i "$ESP_IMG" ::/EFI ::/EFI/BOOT
+    mcopy -i "$ESP_IMG" "$GRUB_STAGE/bootx64.efi" ::/EFI/BOOT/BOOTX64.EFI
+    dd if="$ESP_IMG" of="$IMG" bs=512 seek=$START2 conv=sparse,notrunc status=none
+    echo "   ESP de ${ESP_SIZE}M poblada — la imagen arranca por BIOS **y** por UEFI"
+else
+    echo "   ⚠ SIN ESP: falta grub-mkimage con /usr/lib/grub/x86_64-efi, o mtools."
+    echo "     La imagen arranca por BIOS pero NO por UEFI — y eso la ata a los proveedores que"
+    echo "     ofrezcan arranque BIOS. No es un detalle: es la mitad del «cualquier servicio»."
+fi
+
 rm -rf "$STAGE"
 echo "==> imagen instalable creada: $(du -h "$IMG" | cut -f1) en disco (sparse), $(( TOTAL_SECT * 512 / 1024 / 1024 ))M virtuales"
 echo "    auto-boot: qemu-system-x86_64 -drive file=$IMG,format=raw -nographic   (sin -kernel)"