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:
@@ -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 20–30 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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user