diff --git a/crates/takana-cli/src/main.rs b/crates/takana-cli/src/main.rs index 6697884b..d6f6378e 100644 --- a/crates/takana-cli/src/main.rs +++ b/crates/takana-cli/src/main.rs @@ -1574,9 +1574,18 @@ fn main() -> anyhow::Result<()> { EntryCmd::Reconcile { loader, label, efivars, sys_block, disk, partition, dry_run } => { // 1) ¿Hay firmware EFI? Sin NVRAM no hay nada que reconciliar, y eso NO es // un error: puede ser una máquina que arranca por BIOS. Salir limpio. + // Tres estados que se parecen y NO son lo mismo. Decir «no arrancó por UEFI» + // cuando lo que falta es un `mount` manda a diagnosticar al lugar equivocado + // — y eso pasó de verdad en el primer arranque de este hook. if !efi_boot::hay_firmware_efi(&efivars) { - println!("boot entry reconcile: sin firmware EFI en {} — nada que hacer", efivars.display()); - println!(" (esta máquina no arrancó por UEFI; el arranque sigue normal)"); + if !efivars.exists() { + println!("boot entry reconcile: no existe {} — nada que hacer", efivars.display()); + println!(" (o esta máquina arrancó por BIOS, o /sys no está montado)"); + } else { + println!("boot entry reconcile: {} existe pero está VACÍO", efivars.display()); + println!(" ⇒ falta montarlo: mount -t efivarfs efivarfs {}", efivars.display()); + println!(" (NO es que no haya UEFI: el directorio existe porque el kernel trae CONFIG_EFI)"); + } return Ok(()); } diff --git a/docs/adr/0018-soberania-del-arranque.md b/docs/adr/0018-soberania-del-arranque.md index cbb30c9b..380ee683 100644 --- a/docs/adr/0018-soberania-del-arranque.md +++ b/docs/adr/0018-soberania-del-arranque.md @@ -69,6 +69,45 @@ Y —esto no es opcional— **el reconciliador reporta que tuvo que actuar**. Si usuario vive con una rareza intermitente que nunca entiende. Un ausente falla ruidosamente; un arreglo mudo se parece demasiado a que no pasó nada (regla 3 de `CLAUDE.md`). +> **ENMIENDA 2026-09-11 (3ª) — el §2 está implementado, y lo caro no fue el reconciliador.** +> +> `takana boot entry reconcile` descubre su propia ESP por **tipo de partición** (GUID de ESP en GPT, +> `0xEF` en MBR), que es el único dato fiable sin montar nada. Si hay **varias** ESP no adivina: las +> lista y pide `--disk`. Elegir mal significa escribir una entrada que apunta a un disco que puede no +> estar, y eso es peor que no escribir nada. Corre desde el wrapper de PID1 de las dos imágenes, y +> cuando actúa **grita a `/dev/tty0` además del serial** — un reconciliador que repara en silencio +> deja al usuario conviviendo con una rareza que no entiende. +> +> **Lo que costó el tiempo fue un diagnóstico falso, y vale más que el código:** el primer arranque +> con el hook puesto imprimió +> +> > `boot entry reconcile: sin firmware EFI en /sys/firmware/efi/efivars — nada que hacer` +> > `(esta máquina no arrancó por UEFI)` +> +> **en una VM que había arrancado por UEFI.** La causa no tenía nada que ver con UEFI: +> `busybox switch_root` **no arrastra `/sys`** —igual que no arrastra `/dev`, cosa que el script ya +> contemplaba con su `mount -t devtmpfs`— así que `/sys/firmware/efi/efivars` sencillamente no +> existía. El mensaje mandaba a investigar el firmware, que estaba perfecto. +> +> **Verificado después en la máquina real, dos arranques seguidos de la imagen completa:** +> +> | arranque | qué hizo | salida | +> |---|---|---| +> | 1º (por la ruta fallback) | detectó su ESP **solo** y vio que faltaba la entrada | `ESP detectada en /dev/vda p1` · `⚠ EL ARRANQUE ESTABA CAMBIADO — takana lo repuso` · `faltaba la entrada «takana» ⇒ escrita en Boot0004` · `BootOrder: 0000,0001,0002,0003 → 0004,0000,…` | +> | 2º (sin tocar nada) | **el firmware arrancó por la entrada que se escribió sola** | `BdsDxe: starting Boot0004 "takana" from HD(1,GPT,923A070F-…)/\EFI\takana\takanax64.efi` · `✓ arranque en orden: Boot0004 «takana» ya es la primera` | +> +> O sea: un sistema recién instalado **se da de alta en el firmware en su primer arranque** y a +> partir del segundo arranca por su propia entrada, sin que nadie corra un comando. Y el segundo +> arranque no escribe nada. +> +> Dos arreglos, y el segundo importa tanto como el primero: +> 1. El wrapper monta `sysfs` y después `efivarfs`. El kernel ya traía `CONFIG_EFIVAR_FS=y` +> —verificado en el `.config` **sellado**, no supuesto—, así que no se re-hashea nada. +> 2. El mensaje distingue ahora **tres** estados que se parecen: el directorio no existe (BIOS, o +> `/sys` sin montar), existe y está **vacío** (falta el `mount`, y lo dice con el comando exacto), +> o tiene variables. Decir «no hay UEFI» cuando lo que falta es un `mount` manda a diagnosticar al +> lugar equivocado — y el que se equivocó primero fue este ADR. + ### 3. La segunda pata, porque el reconciliador tiene huevo y gallina Si Windows deja a takana de último, **takana no arranca, y entonces el reconciliador nunca corre**. diff --git a/scripts/install-image-efi.sh b/scripts/install-image-efi.sh index 7d8a0ec1..6d0cdb64 100755 --- a/scripts/install-image-efi.sh +++ b/scripts/install-image-efi.sh @@ -194,6 +194,19 @@ if [ -x /usr/bin/hammer ]; then /bin/busybox timeout 15 /usr/bin/hammer boot menu > /dev/ttyS0 2>&1 || true fi +# efivarfs: la NVRAM del firmware NO es visible hasta que se monta, y el kernel `linux-metal` ya trae +# CONFIG_EFIVAR_FS=y (verificado en el .config sellado, no supuesto) — sólo faltaba montarlo. El +# directorio sólo existe si la máquina arrancó por UEFI, así que el `[ -d ]` es la prueba de firmware +# y no una precaución: en BIOS no está y no hay nada que montar. +# ⚠ `busybox switch_root` NO arrastra /sys, igual que no arrastra /dev (por eso existe la línea del +# devtmpfs de arriba). Sin sysfs, `/sys/firmware/efi/efivars` NO EXISTE y el reconciliador concluye +# «esta máquina no arrancó por UEFI» — en una máquina que arrancó por UEFI. Medido: el primer intento +# de este hook dio exactamente ese diagnóstico falso. +/bin/busybox mount -t sysfs sys /sys 2>/dev/null || true +if [ -d /sys/firmware/efi/efivars ]; then + /bin/busybox mount -t efivarfs efivarfs /sys/firmware/efi/efivars 2>/dev/null || true +fi + # Reconciliador del arranque (ADR 0018 §2). La NVRAM del firmware es estado compartido: otro sistema # operativo instalado al lado la reescribe sin coordinarse —Windows Update poniéndose primero en # BootOrder es el caso típico—. Contra eso no sirve confiar, sirve CONVERGER en cada arranque. diff --git a/scripts/takana-live-install.sh b/scripts/takana-live-install.sh index 2be0b90a..8acbf929 100755 --- a/scripts/takana-live-install.sh +++ b/scripts/takana-live-install.sh @@ -452,6 +452,12 @@ echo "HAMMER-EFI-INIT-OK: arje-zero PID1" > /dev/ttyS0 2>/dev/null || true # Menú de arranque por grafo (ADR 0010): emite /run/hammer/boot-graph.json y, si hay compositor (mirada), # lo pinta sobre KMS. Graceful (sin compositor sigue) y al serial (no pinta tty0 ⇒ cero-parpadeo). [ -x /usr/bin/hammer ] && /usr/bin/hammer boot menu > /dev/ttyS0 2>&1 || true +# efivarfs: sin montarlo la NVRAM del firmware no se ve. El directorio sólo existe si arrancamos por +# UEFI, así que el `[ -d ]` distingue UEFI de BIOS sin preguntarle a nadie. +# switch_root no arrastra /sys (ni /dev): sin esto el directorio de efivars no existe y el +# reconciliador diagnostica «no arrancó por UEFI» en una máquina que sí lo hizo. +/bin/busybox mount -t sysfs sys /sys 2>/dev/null || true +[ -d /sys/firmware/efi/efivars ] && /bin/busybox mount -t efivarfs efivarfs /sys/firmware/efi/efivars 2>/dev/null || true # Reconciliador del arranque (ADR 0018 §2): la NVRAM es estado compartido y el vecino la reescribe # sin avisar. Converger en cada arranque, y DECIRLO en pantalla cuando hubo que actuar — si repara # en silencio, el usuario nunca se entera de que algo le está pisando el arranque.