ADR 0017 y 0018: ciclo de vida del kernel y soberanía del arranque

Dos preguntas de diseño convertidas en decisiones con su evidencia.

0017 — el kernel es artefacto de contenido, así que el rollback ya es
gratis y atómico: lo que falta es A/B con vuelta atrás automática.
Medido sobre los 6 .config SELLADOS (no sobre las recetas): KEXEC=y en
los 6 (la primitiva está pagada, gratis), LIVEPATCH en NINGUNO —lo que
hay es HAVE_LIVEPATCH, que es capacidad de arch, no el feature— y
MODULES sólo en linux. Un livepatch ES un .ko ⇒ en los 5 kernels que
arrancan máquinas está cerrado por construcción.

0018 — la NVRAM es estado mutable compartido y Windows es otro agente
escribiendo sin coordinarse: el mismo patrón del índice de git (regla 2)
y del árbol de fuentes (ADR 0012) ⇒ reconciliación, no confianza.
Medido: efibootmgr aparece 0 veces en el repo y takana NUNCA crea una
entrada NVRAM — depende entera de la ruta EFI/BOOT/BOOTX64.EFI, que por
especificación es la de medios REMOVIBLES. Correcto para el USB, la peor
dirección posible para un disco compartido con Windows.

Se atan por KEXEC_FILE: no está en ninguno de los 6, y lockdown (que
trae Secure Boot) prohíbe el kexec_load viejo ⇒ hoy kexec y Secure Boot
no coexisten.

Abiertas a propósito: si el perfil servidor quiere livepatch (variante
con MODULES, nunca un flag en el kernel general), y si takana firma.
"No firmar" no es neutral: le traslada al usuario la clave de
recuperación de BitLocker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
This commit is contained in:
Sergio
2026-09-11 19:08:08 +00:00
co-authored by Claude Opus 5
parent 2a836e5224
commit 822d0b8949
2 changed files with 289 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
# ADR 0017 — Ciclo de vida del kernel: cambiarlo sin pagar el reboot
- **Estado:** PROPUESTO — **dos decisiones abiertas** (ver §Lo que no decide este ADR). No
implementar los verbos antes de cerrar la primera.
- **Fecha:** 2026-09-11
- **Frontera:** `recipes/linux-*.toml`, `crates/takana-cli/src/kernel_cmd.rs`, el GC del store.
- **Hermano:** [ADR 0018](0018-soberania-del-arranque.md) — arrancar el kernel nuevo es otro
problema y tiene su propio fichero. **Relacionado:** [SDD 22](../22-configurador-kernel.md)
(el armador), [SDD 25](../25-tasas-del-kernel.md).
## Contexto
El problema de actualizar kernels en una distro normal no es técnico: es que **el kernel es un
nombre mutable**. `/boot/vmlinuz-6.16.12` es un fichero que se sobreescribe, y conservar los N
anteriores es una convención que se rompe sola cuando `/boot` se llena. De ahí sale todo lo demás —
el rollback deja de ser una operación y pasa a ser un procedimiento con pasos que hay que recordar
bajo presión.
En takana el kernel es un artefacto de contenido: el viejo y el nuevo coexisten sin disputarse un
nombre. **La mitad de este problema está resuelta por construcción y sin haberlo buscado.** Esto
importa porque cambia dónde conviene invertir:
> Una distro no se hace fuerte logrando que la actualización no falle.
> Se hace fuerte logrando que **fallar no cueste nada**.
## Lo medido (2026-09-11)
Sobre los `.config` **sellados en `./store`**, no sobre las recetas — el `.config` producido es la
identidad del artefacto (SDD 22 §1) y es lo único que dice qué kernel hay de verdad.
| kernel (store) | `KEXEC` | `KEXEC_FILE` | `LIVEPATCH` | `MODULES` | `EFI_STUB` | `CMDLINE_BOOL` |
|---|---|---|---|---|---|---|
| `linux` `d2a03c29` | ✅ | — | — | **✅** | ✅ | — |
| `linux-generic` `23f1cc43` | ✅ | — | — | — | ✅ | ✅ |
| `linux-metal` `2ed8f54a` | ✅ | — | — | — | ✅ | ✅ |
| `linux-metal-dual` `714f46d6` | ✅ | — | — | — | ✅ | ✅ |
| `linux-gioser` `8ffd5710`, `b026fe14` | ✅ | — | — | — | ✅ | ✅ |
Cuatro consecuencias, y ninguna es opinión:
1. **`CONFIG_KEXEC=y` en los 6.** La primitiva para cambiar de kernel sin pasar por el firmware
**ya está pagada en todo el corpus**, gratis, heredada del `defconfig` de x86. No hay que
re-hashear nada para empezar a usarla.
2. **`CONFIG_LIVEPATCH` en ninguno.** Lo que sí aparece es `CONFIG_HAVE_LIVEPATCH=y`, que es
*capacidad de la arquitectura*, no el feature — confundirlos es exactamente el modo de error que
este repo ya tiene nombrado: la etiqueta no es el hecho.
3. **`CONFIG_MODULES` sólo en `linux`.** Los cinco kernels que arrancan máquinas son monolíticos
(`recipes/linux-metal.toml:108` lo apaga a propósito). **Un livepatch *es* un `.ko`** ⇒ en esos
cinco kernels el livepatch está cerrado por construcción, no por falta de trabajo.
4. **`CONFIG_KEXEC_FILE` en ninguno.** Sólo existe el syscall viejo `kexec_load`. Con *lockdown*
activo (lo que trae Secure Boot) el kernel **rechaza `kexec_load`** y sólo acepta
`kexec_file_load` con imagen firmada. ⇒ **kexec y Secure Boot no coexisten hoy**, y eso ata este
ADR con el 0018 §Secure Boot.
Y el estado de la herramienta: `takana kernel` tiene hoy nueve subcomandos —`closure`, `probe`,
`bundles`, `plan`, `diff-back`, `gate`, `hw`, `contract`, `stats`— y **ninguno escribe nada**. SDD 22
construyó deliberadamente un armador que sólo analiza. Los verbos que propone este ADR serían los
primeros del namespace que actúan sobre la máquina, y esa frontera merece cruzarse a propósito.
No existen en el catálogo `kexec-tools`, `efibootmgr`, `sbsigntool`, `shim`, `mokutil` ni `efivar`.
## Decisión
### 1. Tres verbos, nombrados por lo que cuestan
Casi toda la industria vende «live kernel update» y entrega una de tres cosas distintas. Takana
nombra las tres por separado y **no llama a ninguna lo que no es**:
| verbo | qué hace | downtime | alcance | perfil |
|---|---|---|---|---|
| `takana kernel stage <hash>` | deja el kernel listo; entra en el próximo reboot natural | **cero** | total | escritorio |
| `takana kernel kexec <hash>` | reboot **suave**: salta firmware y gestor de arranque | ~2 s, **mata el userspace** | total | servidor |
| *(livepatch)* | parchea funciones del kernel vivo | cero real | **una función** | CVE en producción |
`kexec` **no es «sin rebootear»** y la ayuda del comando tiene que decirlo con esas palabras. Lo que
ahorra son los 2030 s de POST/UEFI, que en un servidor remoto es la mitad del downtime y en un
portátil no es nada. Ser la distro que **no confunde las tres** ya es una posición: la confusión es
la norma del mercado.
### 2. A/B con vuelta atrás automática — esto es lo que vale
`kexec` solo no hace fuerte a nadie: hace *rápido*. Lo que hace fuerte es que **si el kernel nuevo
no llega a “sistema sano” en T segundos, la máquina vuelve sola al anterior.**
- systemd-boot aproxima esto con contadores en el *nombre del fichero* (`+3-0`). Takana puede
hacerlo mejor porque el estado al que volver es **un hash**, no un fichero que puede no estar.
- Qué es «sano» lo declara el perfil, no el kernel: para `perfil.servidor` es que arje-zero levantó
sus servicios supervisados; para un escritorio, que el compositor pintó.
- **Sin esto, actualizar el kernel de una máquina remota da miedo, y con razón.** Es la única
entrada de esta lista que yo pondría primero.
### 3. El kernel corriendo se identifica por **hash**, no por `uname -r`
`uname -r` dice `6.16.12` y no dice qué `.config`, qué parches ni qué toolchain. Eso es el bug
clásico de los livepatches y los módulos fuera de árbol: se construyen contra el kernel equivocado y
el síntoma aparece meses después. Con el hash del artefacto viajando en el kernel vivo —el cmdline
embebido ya lo permite en 5 de los 6— la pregunta «¿esto corresponde a este kernel?» pasa de
heurística a **comparación exacta**.
Es un cierre de invariante, no una comodidad.
### 4. El kernel EN EJECUCIÓN es raíz del GC
No el de la entrada de arranque: **el que está corriendo**. Si el GC poda el artefacto del kernel
vivo, se pierden el rollback y la capacidad de diagnosticar, **y se pierden en silencio** — el
sistema sigue andando perfecto hasta el día que lo necesitás. Es el mismo patrón que ya costó caro
en el CAS: lo que no es alcanzable desde una raíz desaparece sin avisar.
`scripts/store-gc.sh` y cualquier GC nuevo leen el hash del kernel vivo y lo tratan como raíz.
Guardián con **rotura a propósito y control que tiene que pasar**, no sólo con el caso feliz.
### 5. Si alguna vez hace falta livepatch, es una **variante**, no un flag
La salida correcta **no** es encender `MODULES` en el kernel general —eso revierte una decisión de
reproducibilidad y superficie de ataque para todo el mundo—. Es una variante
`linux-servidor-livepatch` con su propio hash, para el perfil que la pida. En takana las variantes
son baratas; lo caro es descubrir dentro de dos años que la puerta se cerró sin que nadie supiera
que la estaba cerrando.
### 6. El límite que este ADR no cruza, dicho en voz alta
Para «actualizar **sin interrumpir el trabajo**» la respuesta no está en el kernel: es CRIU o drenar
la carga a otra máquina. **Ninguna distro resuelve eso**, y prometerlo es donde empiezan las
mentiras de la categoría. `stage` + `kexec` + rollback automático es el techo honesto.
## Lo que no decide este ADR
1. **¿El perfil servidor quiere livepatch?** ⛔ **ABIERTA.**
Si **sí**: hay que prever la variante con `MODULES` ahora y aceptar su costo (superficie de
módulos cargables, `vermagic`, firma de módulos si además hay Secure Boot).
Si **no**: hay que escribirlo como decisión explícita, para que dentro de un año nadie lo
«arregle» encendiendo módulos en el kernel general creyendo que fue un olvido.
2. **¿`kexec` es compatible con el destino de Secure Boot?** ⛔ **ABIERTA, y depende del 0018.**
Hoy no lo es: falta `KEXEC_FILE` y sobra el `kexec_load` viejo. Si el 0018 decide firmar, este
ADR necesita `CONFIG_KEXEC_FILE=y` + `KEXEC_SIG`, que **re-hashea los seis kernels**. Si el 0018
decide no firmar, `kexec` funciona como está y esto se cierra sin costo.
## Riesgos conocidos
- **El cmdline está embebido** (`CONFIG_CMDLINE_BOOL`, `linux-metal.toml:150`) porque el arranque
normal es por EFI-STUB. `kexec` **no usa el stub**: carga por el protocolo de boot de Linux, así
que initrd y cmdline hay que pasárselos explícitos. El traspaso de la EFI memmap por `setup_data`
con `efi=novamap` puesto **es el punto a probar en QEMU antes de tocar metal** — no está
verificado y no debe darse por bueno.
- **El monolítico es una ventaja acá**: sin módulos no hay `vermagic` ni `/lib/modules` que se
desincronicen. El modo de fallo clásico de «actualicé el kernel y no rebooteé» —enchufás un USB y
no pasa nada porque el módulo ya no está en disco— **no existe en takana**. Vale anotarlo del lado
del haber cuando se discuta el punto 1.
+140
View File
@@ -0,0 +1,140 @@
# ADR 0018 — Soberanía del arranque: convivir con otro gestor sin depender de su buena fe
- **Estado:** PROPUESTO — **una decisión abierta** (§Secure Boot). El resto es implementable ya.
- **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](0017-ciclo-de-vida-del-kernel.md). **Relacionado:**
[ADR 0010](0010-arranque-grafo-mirada.md), [SDD 27](../27-perfiles-instalables-y-el-instalador.md).
## 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](0012-arbol-de-fuentes-compartido-vs-privado.md)).
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 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`).
### 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.
### 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.
### 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.
### 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](0017-ciclo-de-vida-del-kernel.md)**, 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` (o `efivar` y hablarle a la NVRAM desde el binario de takana).
Si se decide firmar: `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.