Files
takana/docs/adr/0018-soberania-del-arranque.md
T
SergioandClaude Opus 5 2b87ecb50f respaldo del arranque: lista blanca, porque BootCurrent rompía la idempotencia
Dos arranques reales seguidos, sin tocar nada, daban dos respaldos con
nombre distinto: boot-27b952c9….json y boot-f999e1e8….json. La promesa
de "un arranque que no cambió produce el mismo fichero" era falsa.

La causa: el filtro era «empieza por Boot», y eso deja entrar
BootCurrent, que dice por dónde arrancó ESTA vez y cambia en cada
arranque. Y encima es de SÓLO LECTURA, así que restore habría intentado
escribirla.

Ahora es lista blanca —BootOrder y Boot#### y nada más—, que también
deja fuera BootNext (de un solo uso: restaurarla dispararía un arranque
que nadie pidió) y BootOptionSupport (informativa). Ninguna de las tres
describe cómo debe arrancar la máquina.

Comprobado: cambiando BootCurrent a mano, el respaldo da el mismo
fichero y lo dice — "idéntico a uno que ya estaba: el arranque no
cambió".

Esto sólo se veía ARRANCANDO DOS VECES. Un test del respaldo contra un
efivarfs fabricado habría pasado en verde: la variable que rompía la
propiedad la pone el firmware, no el código.

80/80 del CLI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
2026-09-12 00:37:13 +00:00

21 KiB

ADR 0018 — Soberanía del arranque: convivir con otro gestor sin depender de su buena fe

  • Estado: PROPUESTO, con §1 a §5 IMPLEMENTADOS y verificados (2026-09-11/12). Queda una decisión abierta (§Secure Boot); el §6 es una no-acción deliberada y el §7 va al manual del piloto (SDD 21).
  • Fecha: 2026-09-11
  • Frontera: scripts/install-image-efi.sh, scripts/install-image.sh, scripts/iso-image.sh, scripts/metal-usb-sdboot.sh, scripts/takana-live-install.sh, el instalador TUI.
  • Hermano: ADR 0017. Relacionado: ADR 0010, SDD 27.

Contexto

El caso es: el usuario tiene Windows y quiere takana al lado. Cada tanto Windows Update o el «Startup Repair» reordenan el arranque y takana deja de aparecer. El reflejo es tratarlo como un enemigo; es más útil y más productivo verlo por lo que es:

La NVRAM del firmware es estado mutable compartido, y Windows es otro agente escribiendo en él sin coordinarse con nadie.

Ese patrón este repo ya lo pagó dos veces: el índice de git es estado compartido (regla 2 de CLAUDE.md, commit ff0b556) y el árbol de fuentes es estado compartido (ADR 0012). La lección que costó las dos veces es la misma y se aplica igual acá:

No se resuelve pidiéndole al otro que se porte bien. Se resuelve con reconciliación, no con confianza.

Conviene además separar los dos modos de fallo, porque se arreglan distinto y se confunden siempre:

  • El común (la enorme mayoría): Windows no borra nada, te desordena. Reescribe BootOrder poniéndose primero. Tu gestor sigue intacto; simplemente ya no es el que arranca.
  • El feo: algo pisa el fichero. Y ahí takana está hoy expuesta por diseño propio, no por culpa de Windows.

Lo medido (2026-09-11)

qué resultado
apariciones de efibootmgr en scripts/ y crates/ 0
receta efibootmgr, efivar en el catálogo no existen
scripts que escriben \EFI\BOOT\BOOTX64.EFI 5 (install-image-efi.sh:190, install-image.sh, iso-image.sh, metal-usb-sdboot.sh:64, takana-live-install.sh)
entradas NVRAM que takana crea ninguna, nunca
el instalador live en UEFI, ¿respeta particiones existentes? no: particiona el disco ENTERO en MBR (takana-live-install.sh:286-292) ⇒ hoy no existe el caso «instalar al lado de Windows», no es que esté frágil

El hallazgo que ordena todo el ADR: takana jamás crea una entrada de arranque. Depende íntegramente de \EFI\BOOT\BOOTX64.EFI, que por especificación UEFI es la ruta de medios removibles — el último recurso que prueba el firmware cuando no encuentra nada más. Es tierra de nadie: la usa cualquier USB, la repueblan varias rutas de recuperación de Windows, y no le pertenece a ningún sistema instalado.

Para un USB de bringup eso es correcto: es literalmente su lugar. Para una instalación en disco que comparte firmware con otro sistema operativo es la peor dirección posible, y hoy los dos casos comparten script.

Decisión

1. Dos casos, dos rutas — y dejan de compartir código

  • Medio removible (USB, ISO): sigue en \EFI\BOOT\BOOTX64.EFI. Es su lugar por especificación.
  • Instalación en disco: vendor path propio \EFI\takana\takanax64.efi + entrada NVRAM creada explícitamente. Deja de haber colisión de nombres con nadie.

2. Estado de arranque declarado + reconciliador idempotente

Takana declara cómo debe verse su arranque —vendor path, entrada NVRAM, posición en BootOrder— y en cada arranque converge la NVRAM hacia esa declaración. Corre siempre, no sólo cuando hay sospecha. Windows te desordena el lunes; el martes takana lo deshace.

Y —esto no es opcional— el reconciliador reporta que tuvo que actuar. Si repara en silencio, el 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. Hace falta algo que no dependa de haber arrancado:

  • Dos punteros al mismo artefacto. Vendor path (el de verdad) y una copia en la ruta fallback como red. Es content-addressed: el mismo kernel, dos entradas, coste marginal cero. Si Windows pisa la fallback, sobrevive la vendor; si desordena la NVRAM, en varios firmwares la fallback todavía salva.
  • La tecla del menú de arranque del firmware, como parte del contrato del instalador. Suena tonto. Es lo único que funciona cuando todo lo demás falló, y ninguna distro te lo dice antes de que lo necesites — te lo dice un foro, a las dos de la mañana, desde otro dispositivo.

ENMIENDA 2026-09-11 — implementado el §3 en las dos imágenes, y la medición corrigió el alcance.

install-image-efi.sh y takana-live-install.sh escriben ya el kernel en las dos rutas, con guardián de capacidad previo. Verificado construyendo una imagen real y arrancándola en OVMF:

prueba resultado
las dos copias en la ESP vs el bzImage del store sha256 idéntico (162cd18e4f8f1f5c), 15 299 584 B cada una
arranque de la imagen completa HAMMER-EFI-DISK-PIVOT-OKHAMMER-EFI-INIT-OK: arje-zero PID1
control negativo: pisar \EFI\BOOT\BOOTX64.EFI con 4 KiB de basura, sin otra vía BdsDxe: No bootable option or device was foundcero apariciones de HAMMER-EFI

El control negativo es el que da valor al resto: confirma que pisar la ruta fallback mata el arranque por completo cuando es el único camino. Es el escenario de Windows, reproducido.

Y lo que NO se pudo verificar cambia el alcance del §3, así que va acá y no en una nota al pie: no se logró arrancar POR el vendor path. Sin entrada NVRAM el firmware no lo busca —OVMF cayó en No bootable option con el kernel intacto en \EFI\takana\takanax64.efi— y no hay forma de crear esa entrada desde este hub (efibootmgr existe en el host pero escribe en la NVRAM del host, no en un fichero OVMF_VARS; no hay virt-firmware; este OVMF no cae al UEFI Shell, así que el truco del startup.nsh tampoco corre).

La copia en el vendor path es un seguro que todavía no se puede cobrar solo. Protege contra que pisen el fichero, pero sólo si además existe la entrada NVRAM del §1. El §3 es condición necesaria y NO suficiente: sin el §1 la mitad del seguro falta.

↳ CERRADO el mismo día por la enmienda de abajo. El bloqueo se levantó, y no con efibootmgr.

ENMIENDA 2026-09-11 (2ª) — el §1 está implementado y el arranque por vendor path está MEDIDO.

La entrada NVRAM la escribe takana, no efibootmgr. Se intentó primero la vía ajena y el muro se midió: efivar 38 con musl/zig-cc choca con secure_getenv (no existe en musl), sys/cdefs.h (tampoco) y -Wl,--add-needed (lld lo rechaza) — tres muros sólo para compilar su generador de tablas, más popt arrastrado al sistema instalado. Lo que efibootmgr hace en el fondo es escribir dos ficheros con una estructura de la spec UEFI estable desde la 2.0. Está en crates/takana-cli/src/efi_boot.rs y se usa con takana boot entry {list,add}.

El experimento, que es lo que vale: misma imagen de disco con la ruta fallback pisada con 4 KiB de basura, dos corridas en OVMF que difieren sólo en la NVRAM.

corrida NVRAM resultado
control limpia No bootable option or device was found — cero líneas del initramfs
prueba con la entrada que escribió takana BdsDxe: starting Boot0004 "takana" from HD(1,GPT,DA80800E-…,0x800,0x30000)/\EFI\takana\takanax64.efi

El firmware imprime el device path que decodificó: 0x800 = LBA 2048 y 0x30000 = 196 608 sectores, exactamente los números que takana leyó del GPT. No es que «arrancó»: es que arrancó por la entrada que escribimos, con la fallback destruida. Eso es el escenario de Windows con el seguro cobrado.

Y la idempotencia del §2 quedó probada en condiciones reales, no en un test: la segunda corrida vuelve a ejecutar el reconciliador y dice «la entrada Boot0004 ya dice exactamente esto — no se reescribe» y «BootOrder ya empieza por Boot0004 — no se toca». Importa más de lo que parece: la NVRAM tiene un número finito de escrituras y esto está pensado para correr en cada arranque.

De paso, el parser se validó contra las cuatro entradas reales del firmware (BootManagerMenuApp, EFI Firmware Setup, UEFI Misc Device, EFI Internal Shell), que es un vector que nadie escribió para que pasara la prueba.

4. El instalador mira el terreno y lo dice en voz alta

Antes de tocar nada: qué hay en la ESP, cuánto espacio libre queda, cómo está BootOrder, si hay un Windows Boot Manager. Reportar, no adivinar.

Un fallo silencioso concreto que muerde mucho: las ESP de fábrica son de 100 MB, Windows las llena, y un bzImage con EFI-stub más su initramfs no entra. Eso tiene que fallar ruidosamente y por adelantado, con el número medido en pantalla — nunca truncar a mitad de instalación.

ENMIENDA 2026-09-11 (4ª) — el §4 implementado: takana boot entry survey.

Reporta firmware, discos, ESPs, con quién se comparten y si la instalación entra. No escribe nada — y para poder prometer eso hubo que leer la ESP sin montarla, que es lo que obligó a escribir un lector FAT de sólo lectura (crates/takana-cli/src/fat_ro.rs). Montar para averiguarlo tenía tres problemas: pide privilegios, deja un efecto secundario justo cuando prometimos no tocar nada, y falla si el vecino dejó la FAT sucia por su hibernación (el «Fast Startup» del §7).

Sobre una ESP de fábrica (100 MiB) con Windows dentro y el kernel duplicado a instalar:

  FAT32 «ESP» — 100.0 MiB totales, 77.1 MiB usados, 22.1 MiB libres
  vecinos en \EFI: Microsoft, BOOT
    ⚠ «Microsoft» ⇒ hay Windows en esta ESP. Es el que reordena BootOrder al actualizarse.
  ✗ NO ENTRA: hacen falta 31.0 MiB y hay 22.1 MiB libres
  ── veredicto ──
  ✗ …p1: faltan 8.9 MiB en la ESP

Contrastado contra mtools como oráculo, que es lo que hace que el número sea creíble: mdir reporta 23 221 248 bytes free y el lector propio dice 22,1 MiB — el mismo número. Los clusters libres se cuentan recorriendo la FAT y no se lee el FSInfo de FAT32 a propósito: ese campo es una pista que un sistema operativo que desmontó mal deja desactualizada, y un número optimista de más haría fallar la instalación a mitad, que es exactamente lo que este §4 existe para evitar.

Y en el instalador el §4 resultó ser algo más que «cuánto espacio hay». La rama UEFI se lleva el disco entero (§Lo medido), así que lo que hay que decir en voz alta es con qué se lo va a llevar puesto: el survey corre antes de particionar y, si hay un \EFI\Microsoft en ese disco, lo nombra. El usuario se entera antes, y no después de que su Windows dejó de arrancar.

Dos detalles que habrían pasado inadvertidos sin un test:

  • El nombre largo tiene que ganarle al 8.3. Sin juntar los LFN, Microsoft se lee MICROS~1 y la advertencia —la razón de ser de esto— no dispara nunca.
  • El tipo de FAT sale del número de clusters, no del texto del BPB, que es informativo y hay formateadores que mienten en él. (El primer test que escribí para esto estaba mal calculado: 1999 clusters es FAT12. El que fallaba era el test, y el síntoma es idéntico a un bug del lector.)

5. Respaldo del arranque como artefacto sellado

Volcar NVRAM (efibootmgr -v) + el contenido de la ESP al store en cada instalación y cada actualización de arranque. «Restaurar el arranque» pasa de adivinar a reproducir un estado conocido. Barato, y es exactamente la forma de takana.

ENMIENDA 2026-09-11 (5ª) — el §5 implementado, y el ADR se equivocaba en DÓNDE guardarlo.

takana boot entry backup / restore. El respaldo guarda las variables de NVRAM crudas —se reescriben tal cual; decodificarlas para volver a codificarlas al restaurar sería una oportunidad de perder algo que no entendemos— y de la ESP un manifiesto con hashes, no los bytes: el kernel ya vive en el store y copiarlo otra vez sería churn. El manifiesto es evidencia de qué había; para lo que no esté en el store dice qué falta, en vez de prometer que lo puede reponer.

Corrección al §5 tal como estaba escrito: decía «volcar al store». No va al store. Lo barre store-gc.sh, que clasifica por nombre de receta: un respaldo ahí sería huérfano y se borraría en el primer --huerfanos. Vive en /var/lib/hammer/boot/, que en las imágenes es su propia partición y sobrevive a una reinstalación del sistema. El nombre del fichero sale del contenido, así que un arranque que no cambió produce el mismo fichero y esto puede correr en cada arranque sin llenar la partición de estado con copias idénticas.

Lo que encontró la prueba de punta a punta, y que no estaba en el diseño: restaurar es volver al pasado, y lo que llegó después no estaba en ese pasado. Al reponer el BootOrder del respaldo, el Windows instalado más tarde quedaba fuera del orden — semánticamente correcto y exactamente lo que el usuario no espera de algo llamado «restaurar el arranque». Ahora se avisa antes, con nombre y apellido:

  ⚠ ESTO SACA DE BootOrder a 1 entrada(s) que hoy sí están:
      Boot0001 «Windows Boot Manager»
    Son las que aparecieron DESPUÉS del respaldo. Si alguna es otro sistema
    operativo, va a dejar de arrancar desde el menú del firmware hasta que la repongas.

Y restore no escribe por defecto: hay que pasar --apply. La NVRAM es lo único de la máquina que no se puede rehacer desde el store.

Y la promesa de «un arranque que no cambió produce el mismo fichero» empezó siendo falsa. Dos arranques reales seguidos, sin tocar nada, dieron boot-27b952c9….json y boot-f999e1e8….json. La causa: el filtro era «empieza por Boot», y eso deja entrar BootCurrent, que dice por dónde arrancó esta vez y cambia en cada arranque — además de ser de sólo lectura, así que restore habría intentado escribirla. Ahora es lista blanca (BootOrder y Boot#### y nada más), que también deja fuera BootNext (de un solo uso: restaurarla dispararía un arranque que nadie pidió) y BootOptionSupport (informativa). Comprobado: cambiando BootCurrent a mano, el respaldo da el mismo fichero y lo dice — «idéntico a uno que ya estaba: el arranque no cambió».

Sólo se veía arrancando dos veces. Un test del respaldo contra un efivarfs fabricado habría pasado en verde: la variable que rompía la propiedad la pone el firmware, no el código.

El lector FAT ganó lectura de ficheros para el manifiesto, con su trampa propia: hay que truncar al tamaño declarado en el directorio, no al final del último cluster. Un fichero de 1,5 MB en clusters de 1 KiB termina con relleno, y hashear el relleno daría un hash distinto al del mismo fichero en disco — el síntoma sería «dos respaldos del mismo arranque difieren». Verificado contra mtools como oráculo (extrae 1 500 000 y 900 000 bytes exactos, mismo sha256 que los originales) y con un test determinista que fabrica una FAT16 a mano, sin depender de que mtools esté.

6. Lo que takana NO hace: os-prober

No se escanean discos ajenos para adivinar qué sistemas hay y generar entradas. Es una fuente clásica de roturas y de menús que mienten. El firmware ya tiene un menú de arranque: takana pone su entrada, cuida su entrada, y no toca la del vecino.

7. Las tres trampas del vecino, que no son del gestor de arranque

Van en el manual del piloto (SDD 21) porque muerden a todo el mundo y no tienen que ver con el loader:

  • BitLocker. Tocar Secure Boot o el orden de arranque con BitLocker activo hace que Windows pida la clave de recuperación de 48 dígitos en el siguiente arranque. Suspenderlo antes.
  • Fast Startup. Windows hiberna en vez de apagar: deja la ESP y la NTFS en estado sucio, y montarlas desde Linux puede corromperlas. powercfg /h off.
  • El reloj. Windows escribe la RTC en hora local y Linux la espera en UTC. Cada cambio de sistema desfasa el reloj — y acá un reloj mal puesto ensucia cualquier medición con marca de tiempo, que es la mitad de la evidencia del proyecto.

Lo que no decide este ADR

¿Takana firma para Secure Boot? ABIERTA. Y no es cosmética: ata con el ADR 0017, porque lockdown prohíbe el kexec_load viejo, que es el único que los seis kernels sellados soportan hoy.

El caso de takana es el más fácil que existe: un único bzImage, sin módulos ⇒ una sola firma, cero infraestructura de firma de módulos.

opción a favor en contra
shim + MOK cero fricción: el usuario no entra al firmware dependencia de la cadena de confianza de Microsoft, en un proyecto cuyo eje es la soberanía
Claves propias soberano de punta a punta, coherente con el ADN del proyecto el usuario entra al firmware a enrolar; hay que dejar enroladas las de Microsoft o Windows deja de arrancar
No firmar cero trabajo hoy le trasladás el costo al usuario: desactivar Secure Boot con BitLocker activo = clave de recuperación de 48 dígitos. Y cierra kexec bajo lockdown

La tercera fila es la que suele elegirse por omisión y la que hay que mirar de frente: «no firmar» no es neutral, es mover el costo a la persona que instala.

Dependencias que este ADR crea

  • Recetas nuevas: efibootmgr / efivarresuelto sin dependencias nuevas: la NVRAM la escribe takana boot entry add (ver la 2ª enmienda). Si se decide firmar seguirá haciendo falta sbsigntool, y shim/mokutil en la variante shim.
  • Los 5 scripts de arriba se parten en «medio removible» y «disco».
  • El instalador TUI gana el informe del terreno del §4.