1# ADR 0018 — Soberanía del arranque: convivir con otro gestor sin depender de su buena fe
2
3- **Estado:** PROPUESTO, con **§1 a §5 IMPLEMENTADOS y verificados** (2026-09-11/12).
4 Queda **una decisión abierta** (§Secure Boot); el §6 es una no-acción deliberada y el §7 va al
5 manual del piloto (SDD 21).
6- **Fecha:** 2026-09-11
7- **Frontera:** `scripts/install-image-efi.sh`, `scripts/install-image.sh`, `scripts/iso-image.sh`,
8 `scripts/metal-usb-sdboot.sh`, `scripts/takana-live-install.sh`, el instalador TUI.
9- **Hermano:** [ADR 0017](0017-ciclo-de-vida-del-kernel.md). **Relacionado:**
10 [ADR 0010](0010-arranque-grafo-mirada.md), [SDD 27](../27-perfiles-instalables-y-el-instalador.md).
11
12## Contexto
13
14El caso es: el usuario tiene Windows y quiere takana al lado. Cada tanto Windows Update o el
15«Startup Repair» reordenan el arranque y takana deja de aparecer. El reflejo es tratarlo como un
16enemigo; es más útil y más productivo verlo por lo que es:
17
18> **La NVRAM del firmware es estado mutable compartido, y Windows es otro agente escribiendo en él
19> sin coordinarse con nadie.**
20
21Ese patrón este repo ya lo pagó dos veces: el índice de git es estado compartido (regla 2 de
22`CLAUDE.md`, commit `ff0b556`) y el árbol de fuentes es estado compartido ([ADR 0012](0012-arbol-de-fuentes-compartido-vs-privado.md)).
23La lección que costó las dos veces es la misma y se aplica igual acá:
24
25> No se resuelve pidiéndole al otro que se porte bien. Se resuelve con **reconciliación**, no con
26> **confianza**.
27
28Conviene además separar los dos modos de fallo, porque se arreglan distinto y se confunden siempre:
29
30- **El común (la enorme mayoría):** Windows **no borra nada**, te *desordena*. Reescribe `BootOrder`
31 poniéndose primero. Tu gestor sigue intacto; simplemente ya no es el que arranca.
32- **El feo:** algo **pisa el fichero**. Y ahí takana está hoy expuesta por diseño propio, no por
33 culpa de Windows.
34
35## Lo medido (2026-09-11)
36
37| qué | resultado |
38|---|---|
39| apariciones de `efibootmgr` en `scripts/` y `crates/` | **0** |
40| receta `efibootmgr`, `efivar` en el catálogo | **no existen** |
41| 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`) |
42| entradas NVRAM que takana crea | **ninguna, nunca** |
43| 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 |
44
45**El hallazgo que ordena todo el ADR:** takana **jamás crea una entrada de arranque**. Depende
46íntegramente de `\EFI\BOOT\BOOTX64.EFI`, que **por especificación UEFI es la ruta de medios
47removibles** — el último recurso que prueba el firmware cuando no encuentra nada más. Es tierra de
48nadie: la usa cualquier USB, la repueblan varias rutas de recuperación de Windows, y no le pertenece
49a ningún sistema instalado.
50
51Para un USB de bringup eso es **correcto**: es literalmente su lugar. Para una instalación en disco
52que comparte firmware con otro sistema operativo es **la peor dirección posible**, y hoy los dos
53casos comparten script.
54
55## Decisión
56
57### 1. Dos casos, dos rutas — y dejan de compartir código
58
59- **Medio removible (USB, ISO):** sigue en `\EFI\BOOT\BOOTX64.EFI`. Es su lugar por especificación.
60- **Instalación en disco:** vendor path propio **`\EFI\takana\takanax64.efi`** + entrada NVRAM
61 creada explícitamente. Deja de haber colisión de nombres con nadie.
62
63### 2. Estado de arranque **declarado** + reconciliador idempotente
64
65Takana declara cómo debe verse su arranque —vendor path, entrada NVRAM, posición en `BootOrder`— y
66**en cada arranque converge la NVRAM hacia esa declaración**. Corre siempre, no sólo cuando hay
67sospecha. Windows te desordena el lunes; el martes takana lo deshace.
68
69Y —esto no es opcional— **el reconciliador reporta que tuvo que actuar**. Si repara en silencio, el
70usuario vive con una rareza intermitente que nunca entiende. Un ausente falla ruidosamente; un
71arreglo mudo se parece demasiado a que no pasó nada (regla 3 de `CLAUDE.md`).
72
73> **ENMIENDA 2026-09-11 (3ª) — el §2 está implementado, y lo caro no fue el reconciliador.**
74>
75> `takana boot entry reconcile` descubre su propia ESP por **tipo de partición** (GUID de ESP en GPT,
76> `0xEF` en MBR), que es el único dato fiable sin montar nada. Si hay **varias** ESP no adivina: las
77> lista y pide `--disk`. Elegir mal significa escribir una entrada que apunta a un disco que puede no
78> estar, y eso es peor que no escribir nada. Corre desde el wrapper de PID1 de las dos imágenes, y
79> cuando actúa **grita a `/dev/tty0` además del serial** — un reconciliador que repara en silencio
80> deja al usuario conviviendo con una rareza que no entiende.
81>
82> **Lo que costó el tiempo fue un diagnóstico falso, y vale más que el código:** el primer arranque
83> con el hook puesto imprimió
84>
85> > `boot entry reconcile: sin firmware EFI en /sys/firmware/efi/efivars — nada que hacer`
86> > `(esta máquina no arrancó por UEFI)`
87>
88> **en una VM que había arrancado por UEFI.** La causa no tenía nada que ver con UEFI:
89> `busybox switch_root` **no arrastra `/sys`** —igual que no arrastra `/dev`, cosa que el script ya
90> contemplaba con su `mount -t devtmpfs`— así que `/sys/firmware/efi/efivars` sencillamente no
91> existía. El mensaje mandaba a investigar el firmware, que estaba perfecto.
92>
93> **Verificado después en la máquina real, dos arranques seguidos de la imagen completa:**
94>
95> | arranque | qué hizo | salida |
96> |---|---|---|
97> | 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,…` |
98> | 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` |
99>
100> O sea: un sistema recién instalado **se da de alta en el firmware en su primer arranque** y a
101> partir del segundo arranca por su propia entrada, sin que nadie corra un comando. Y el segundo
102> arranque no escribe nada.
103>
104> Dos arreglos, y el segundo importa tanto como el primero:
105> 1. El wrapper monta `sysfs` y después `efivarfs`. El kernel ya traía `CONFIG_EFIVAR_FS=y`
106> —verificado en el `.config` **sellado**, no supuesto—, así que no se re-hashea nada.
107> 2. El mensaje distingue ahora **tres** estados que se parecen: el directorio no existe (BIOS, o
108> `/sys` sin montar), existe y está **vacío** (falta el `mount`, y lo dice con el comando exacto),
109> o tiene variables. Decir «no hay UEFI» cuando lo que falta es un `mount` manda a diagnosticar al
110> lugar equivocado — y el que se equivocó primero fue este ADR.
111
112### 3. La segunda pata, porque el reconciliador tiene huevo y gallina
113
114Si Windows deja a takana de último, **takana no arranca, y entonces el reconciliador nunca corre**.
115Hace falta algo que no dependa de haber arrancado:
116
117- **Dos punteros al mismo artefacto.** Vendor path (el de verdad) **y** una copia en la ruta
118 fallback como red. Es content-addressed: **el mismo kernel, dos entradas, coste marginal cero**.
119 Si Windows pisa la fallback, sobrevive la vendor; si desordena la NVRAM, en varios firmwares la
120 fallback todavía salva.
121- **La tecla del menú de arranque del firmware, como parte del contrato del instalador.** Suena
122 tonto. Es lo único que funciona cuando todo lo demás falló, y **ninguna distro te lo dice antes de
123 que lo necesites** — te lo dice un foro, a las dos de la mañana, desde otro dispositivo.
124
125> **ENMIENDA 2026-09-11 — implementado el §3 en las dos imágenes, y la medición corrigió el alcance.**
126>
127> `install-image-efi.sh` y `takana-live-install.sh` escriben ya el kernel en las **dos** rutas, con
128> guardián de capacidad previo. Verificado construyendo una imagen real y arrancándola en OVMF:
129>
130> | prueba | resultado |
131> |---|---|
132> | las dos copias en la ESP vs el bzImage del store | **sha256 idéntico** (`162cd18e4f8f1f5c`), 15 299 584 B cada una |
133> | arranque de la imagen completa | ✅ `HAMMER-EFI-DISK-PIVOT-OK` → `HAMMER-EFI-INIT-OK: arje-zero PID1` |
134> | **control negativo:** pisar `\EFI\BOOT\BOOTX64.EFI` con 4 KiB de basura, sin otra vía | ✅ `BdsDxe: No bootable option or device was found` — **cero** apariciones de `HAMMER-EFI` |
135>
136> El control negativo es el que da valor al resto: confirma que **pisar la ruta fallback mata el
137> arranque por completo** cuando es el único camino. Es el escenario de Windows, reproducido.
138>
139> ⚠ **Y lo que NO se pudo verificar cambia el alcance del §3, así que va acá y no en una nota al
140> pie:** no se logró arrancar POR el vendor path. Sin entrada NVRAM el firmware no lo busca —OVMF
141> cayó en `No bootable option` con el kernel intacto en `\EFI\takana\takanax64.efi`— y no hay
142> forma de crear esa entrada desde este hub (`efibootmgr` existe en el host pero escribe en la NVRAM
143> **del host**, no en un fichero `OVMF_VARS`; no hay `virt-firmware`; este OVMF no cae al UEFI Shell,
144> así que el truco del `startup.nsh` tampoco corre).
145>
146> ⇒ **La copia en el vendor path es un seguro que todavía no se puede cobrar solo.** Protege contra
147> que *pisen el fichero*, pero sólo si además existe la entrada NVRAM del §1. **El §3 es condición
148> necesaria y NO suficiente: sin el §1 la mitad del seguro falta.**
149>
150> **↳ CERRADO el mismo día por la enmienda de abajo.** El bloqueo se levantó, y no con `efibootmgr`.
151
152> **ENMIENDA 2026-09-11 (2ª) — el §1 está implementado y el arranque por vendor path está MEDIDO.**
153>
154> La entrada NVRAM la escribe takana, no `efibootmgr`. Se intentó primero la vía ajena y el muro se
155> midió: `efivar` 38 con musl/zig-cc choca con `secure_getenv` (no existe en musl), `sys/cdefs.h`
156> (tampoco) y `-Wl,--add-needed` (lld lo rechaza) — tres muros sólo para compilar su generador de
157> tablas, más `popt` arrastrado al sistema instalado. Lo que `efibootmgr` hace en el fondo es
158> **escribir dos ficheros** con una estructura de la spec UEFI estable desde la 2.0. Está en
159> `crates/takana-cli/src/efi_boot.rs` y se usa con `takana boot entry {list,add}`.
160>
161> **El experimento, que es lo que vale:** misma imagen de disco con la ruta fallback **pisada con
162> 4 KiB de basura**, dos corridas en OVMF que difieren **sólo** en la NVRAM.
163>
164> | corrida | NVRAM | resultado |
165> |---|---|---|
166> | control | limpia | `No bootable option or device was found` — cero líneas del initramfs |
167> | prueba | con la entrada que escribió takana | ✅ `BdsDxe: starting Boot0004 "takana" from HD(1,GPT,DA80800E-…,0x800,0x30000)/\EFI\takana\takanax64.efi` |
168>
169> El firmware imprime el device path que decodificó: `0x800` = LBA 2048 y `0x30000` = 196 608
170> sectores, **exactamente** los números que takana leyó del GPT. No es que «arrancó»: es que arrancó
171> **por la entrada que escribimos, con la fallback destruida**. Eso es el escenario de Windows con
172> el seguro cobrado.
173>
174> **Y la idempotencia del §2 quedó probada en condiciones reales**, no en un test: la segunda corrida
175> vuelve a ejecutar el reconciliador y dice «la entrada Boot0004 ya dice exactamente esto — no se
176> reescribe» y «BootOrder ya empieza por Boot0004 — no se toca». Importa más de lo que parece: la
177> NVRAM tiene un número finito de escrituras y esto está pensado para correr en cada arranque.
178>
179> De paso, el parser se validó contra **las cuatro entradas reales del firmware** (`BootManagerMenuApp`,
180> `EFI Firmware Setup`, `UEFI Misc Device`, `EFI Internal Shell`), que es un vector que nadie escribió
181> para que pasara la prueba.
182
183### 4. El instalador mira el terreno y lo dice en voz alta
184
185Antes de tocar nada: qué hay en la ESP, cuánto espacio libre queda, cómo está `BootOrder`, si hay un
186Windows Boot Manager. Reportar, no adivinar.
187
188Un fallo silencioso concreto que muerde mucho: **las ESP de fábrica son de 100 MB**, Windows las
189llena, y un bzImage con EFI-stub más su initramfs **no entra**. Eso tiene que fallar **ruidosamente
190y por adelantado**, con el número medido en pantalla — nunca truncar a mitad de instalación.
191
192> **ENMIENDA 2026-09-11 (4ª) — el §4 implementado: `takana boot entry survey`.**
193>
194> Reporta firmware, discos, ESPs, con quién se comparten y si la instalación entra. **No escribe
195> nada** — y para poder prometer eso hubo que leer la ESP **sin montarla**, que es lo que obligó a
196> escribir un lector FAT de sólo lectura (`crates/takana-cli/src/fat_ro.rs`). Montar para averiguarlo
197> tenía tres problemas: pide privilegios, deja un efecto secundario justo cuando prometimos no tocar
198> nada, y falla si el vecino dejó la FAT sucia por su hibernación (el «Fast Startup» del §7).
199>
200> Sobre una ESP de fábrica (100 MiB) con Windows dentro y el kernel duplicado a instalar:
201>
202> ```
203> FAT32 «ESP» — 100.0 MiB totales, 77.1 MiB usados, 22.1 MiB libres
204> vecinos en \EFI: Microsoft, BOOT
205> ⚠ «Microsoft» ⇒ hay Windows en esta ESP. Es el que reordena BootOrder al actualizarse.
206> ✗ NO ENTRA: hacen falta 31.0 MiB y hay 22.1 MiB libres
207> ── veredicto ──
208> ✗ …p1: faltan 8.9 MiB en la ESP
209> ```
210>
211> **Contrastado contra `mtools` como oráculo**, que es lo que hace que el número sea creíble: `mdir`
212> reporta `23 221 248 bytes free` y el lector propio dice 22,1 MiB — el mismo número. Los clusters
213> libres se cuentan recorriendo la FAT y **no** se lee el `FSInfo` de FAT32 a propósito: ese campo es
214> una pista que un sistema operativo que desmontó mal deja desactualizada, y un número optimista de
215> más haría fallar la instalación a mitad, que es exactamente lo que este §4 existe para evitar.
216>
217> **Y en el instalador el §4 resultó ser algo más que «cuánto espacio hay».** La rama UEFI se lleva
218> el disco **entero** (§Lo medido), así que lo que hay que decir en voz alta es **con qué se lo va a
219> llevar puesto**: el survey corre antes de particionar y, si hay un `\EFI\Microsoft` en ese disco,
220> lo nombra. El usuario se entera **antes**, y no después de que su Windows dejó de arrancar.
221>
222> Dos detalles que habrían pasado inadvertidos sin un test:
223> - **El nombre largo tiene que ganarle al 8.3.** Sin juntar los LFN, `Microsoft` se lee `MICROS~1` y
224> la advertencia —la razón de ser de esto— no dispara nunca.
225> - **El tipo de FAT sale del número de clusters, no del texto del BPB**, que es informativo y hay
226> formateadores que mienten en él. (El primer test que escribí para esto estaba mal calculado:
227> 1999 clusters *es* FAT12. El que fallaba era el test, y el síntoma es idéntico a un bug del
228> lector.)
229
230### 5. Respaldo del arranque como artefacto sellado
231
232Volcar NVRAM (`efibootmgr -v`) + el contenido de la ESP al store en cada instalación y cada
233actualización de arranque. «Restaurar el arranque» pasa de adivinar a **reproducir un estado
234conocido**. Barato, y es exactamente la forma de takana.
235
236> **ENMIENDA 2026-09-11 (5ª) — el §5 implementado, y el ADR se equivocaba en DÓNDE guardarlo.**
237>
238> `takana boot entry backup` / `restore`. El respaldo guarda las variables de NVRAM **crudas** —se
239> reescriben tal cual; decodificarlas para volver a codificarlas al restaurar sería una oportunidad
240> de perder algo que no entendemos— y de la ESP un **manifiesto con hashes**, no los bytes: el kernel
241> ya vive en el store y copiarlo otra vez sería churn. El manifiesto es **evidencia de qué había**;
242> para lo que no esté en el store dice qué falta, en vez de prometer que lo puede reponer.
243>
244> **Corrección al §5 tal como estaba escrito:** decía «volcar al store». **No va al store.** Lo barre
245> `store-gc.sh`, que clasifica por nombre de receta: un respaldo ahí sería *huérfano* y se borraría
246> en el primer `--huerfanos`. Vive en `/var/lib/hammer/boot/`, que en las imágenes es **su propia
247> partición** y sobrevive a una reinstalación del sistema. El nombre del fichero sale del
248> **contenido**, así que un arranque que no cambió produce el mismo fichero y esto puede correr en
249> cada arranque sin llenar la partición de estado con copias idénticas.
250>
251> **Lo que encontró la prueba de punta a punta, y que no estaba en el diseño:** restaurar es volver
252> al pasado, **y lo que llegó después no estaba en ese pasado**. Al reponer el `BootOrder` del
253> respaldo, el Windows instalado *más tarde* quedaba **fuera del orden** — semánticamente correcto y
254> exactamente lo que el usuario no espera de algo llamado «restaurar el arranque». Ahora se avisa
255> antes, con nombre y apellido:
256>
257> ```
258> ⚠ ESTO SACA DE BootOrder a 1 entrada(s) que hoy sí están:
259> Boot0001 «Windows Boot Manager»
260> Son las que aparecieron DESPUÉS del respaldo. Si alguna es otro sistema
261> operativo, va a dejar de arrancar desde el menú del firmware hasta que la repongas.
262> ```
263>
264> Y `restore` **no escribe por defecto**: hay que pasar `--apply`. La NVRAM es lo único de la máquina
265> que no se puede rehacer desde el store.
266>
267> **Y la promesa de «un arranque que no cambió produce el mismo fichero» empezó siendo falsa.** Dos
268> arranques reales seguidos, sin tocar nada, dieron `boot-27b952c9….json` y `boot-f999e1e8….json`.
269> La causa: el filtro era «empieza por `Boot`», y eso deja entrar **`BootCurrent`**, que dice por
270> dónde arrancó **esta** vez y cambia en cada arranque — además de ser de **sólo lectura**, así que
271> `restore` habría intentado escribirla. Ahora es lista blanca (`BootOrder` y `Boot####` y nada más),
272> que también deja fuera `BootNext` (de un solo uso: restaurarla dispararía un arranque que nadie
273> pidió) y `BootOptionSupport` (informativa). Comprobado: cambiando `BootCurrent` a mano, el respaldo
274> da el mismo fichero y lo dice — *«idéntico a uno que ya estaba: el arranque no cambió»*.
275>
276> Sólo se veía **arrancando dos veces**. Un test del respaldo contra un `efivarfs` fabricado habría
277> pasado en verde: la variable que rompía la propiedad la pone el firmware, no el código.
278>
279> El lector FAT ganó lectura de ficheros para el manifiesto, con su trampa propia: **hay que truncar
280> al tamaño declarado en el directorio**, no al final del último cluster. Un fichero de 1,5 MB en
281> clusters de 1 KiB termina con relleno, y hashear el relleno daría un hash distinto al del mismo
282> fichero en disco — el síntoma sería «dos respaldos del mismo arranque difieren». Verificado contra
283> `mtools` como oráculo (extrae 1 500 000 y 900 000 bytes exactos, mismo sha256 que los originales) y
284> con un test determinista que fabrica una FAT16 a mano, sin depender de que mtools esté.
285
286### 6. Lo que takana NO hace: `os-prober`
287
288No se escanean discos ajenos para adivinar qué sistemas hay y generar entradas. Es una fuente
289clásica de roturas y de menús que mienten. El firmware **ya tiene** un menú de arranque: takana pone
290su entrada, cuida su entrada, y no toca la del vecino.
291
292### 7. Las tres trampas del vecino, que no son del gestor de arranque
293
294Van en el manual del piloto (SDD 21) porque muerden a todo el mundo y no tienen que ver con el
295loader:
296
297- **BitLocker.** Tocar Secure Boot o el orden de arranque con BitLocker activo hace que Windows pida
298 la clave de recuperación de 48 dígitos en el siguiente arranque. **Suspenderlo antes.**
299- **Fast Startup.** Windows hiberna en vez de apagar: deja la ESP y la NTFS en estado sucio, y
300 montarlas desde Linux puede corromperlas. `powercfg /h off`.
301- **El reloj.** Windows escribe la RTC en hora local y Linux la espera en UTC. Cada cambio de
302 sistema desfasa el reloj — y acá un reloj mal puesto ensucia cualquier medición con marca de
303 tiempo, que es la mitad de la evidencia del proyecto.
304
305## Lo que no decide este ADR
306
307**¿Takana firma para Secure Boot?** ⛔ **ABIERTA.** Y no es cosmética: **ata con el
308[ADR 0017](0017-ciclo-de-vida-del-kernel.md)**, porque *lockdown* prohíbe el `kexec_load` viejo, que
309es el único que los seis kernels sellados soportan hoy.
310
311El caso de takana es **el más fácil que existe**: un único bzImage, sin módulos ⇒ una sola firma,
312cero infraestructura de firma de módulos.
313
314| opción | a favor | en contra |
315|---|---|---|
316| **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 |
317| **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 |
318| **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 |
319
320La tercera fila es la que suele elegirse por omisión y la que hay que mirar de frente: «no firmar» no
321es neutral, es **mover el costo a la persona que instala**.
322
323## Dependencias que este ADR crea
324
325- ~~Recetas nuevas: `efibootmgr` / `efivar`~~ — **resuelto sin dependencias nuevas**: la NVRAM la
326 escribe `takana boot entry add` (ver la 2ª enmienda). Si se decide firmar seguirá haciendo falta
327 `sbsigntool`, y `shim`/`mokutil` en la variante shim.
328- Los 5 scripts de arriba se parten en «medio removible» y «disco».
329- El instalador TUI gana el informe del terreno del §4.
0018-soberania-del-arranque.md
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:
- 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.
- 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-OK → HAMMER-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 found — cero 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 / efivar — resuelto 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.