From 822d0b89497ec48c1e8e971ebf5f1542a8ff7b0f Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 11 Sep 2026 19:08:08 +0000 Subject: [PATCH] =?UTF-8?q?ADR=200017=20y=200018:=20ciclo=20de=20vida=20de?= =?UTF-8?q?l=20kernel=20y=20soberan=C3=ADa=20del=20arranque?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj --- docs/adr/0017-ciclo-de-vida-del-kernel.md | 149 ++++++++++++++++++++++ docs/adr/0018-soberania-del-arranque.md | 140 ++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 docs/adr/0017-ciclo-de-vida-del-kernel.md create mode 100644 docs/adr/0018-soberania-del-arranque.md diff --git a/docs/adr/0017-ciclo-de-vida-del-kernel.md b/docs/adr/0017-ciclo-de-vida-del-kernel.md new file mode 100644 index 00000000..b1a5c56b --- /dev/null +++ b/docs/adr/0017-ciclo-de-vida-del-kernel.md @@ -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 ` | deja el kernel listo; entra en el próximo reboot natural | **cero** | total | escritorio | +| `takana kernel kexec ` | 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. diff --git a/docs/adr/0018-soberania-del-arranque.md b/docs/adr/0018-soberania-del-arranque.md new file mode 100644 index 00000000..3134557b --- /dev/null +++ b/docs/adr/0018-soberania-del-arranque.md @@ -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.