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
This commit is contained in:
+56
-14
@@ -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)"
|
||||
|
||||
Reference in New Issue
Block a user