Commit Graph
6 Commits
Author SHA1 Message Date
SergioandClaude Opus 5 bd1fa8bf8a boot entry backup/restore: el arranque como estado reproducible (ADR 0018 §5)
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 reponerlo.

El ADR se equivocaba en DÓNDE: decía "volcar al store". No va al store,
porque 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. El
nombre sale del CONTENIDO, así que un arranque que no cambió produce el
mismo fichero: corre en cada arranque sin llenar la partición de copias.

Lo que encontró la prueba de punta a punta y 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 — correcto, y justo lo que el usuario no
espera de algo llamado "restaurar el arranque". Ahora se avisa antes,
con nombre y apellido, y restore NO escribe por defecto: la NVRAM es lo
único de la máquina que no se rehace desde el store.

El lector FAT ganó lectura de ficheros, 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 (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é.

79/79 del CLI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
2026-09-12 00:34:21 +00:00
SergioandClaude Opus 5 2201fac08e boot entry survey: el informe del terreno, leyendo la ESP SIN montarla (ADR 0018 §4)
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ó al lector FAT de sólo
lectura (fat_ro.rs). Montar para averiguarlo pedía privilegios, dejaba
un efecto secundario justo cuando prometimos no tocar nada, y falla si
el vecino dejó la FAT sucia por su hibernación.

Sobre una ESP de fábrica (100 MiB) con Windows dentro:

  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

Contrastado contra mtools como oráculo, que es lo que hace creíble el
número: mdir dice "23 221 248 bytes free" y el lector propio dice
22.1 MiB — el mismo. Los clusters libres se cuentan recorriendo la FAT y
NO se lee el FSInfo de FAT32 a propósito: ese campo lo deja
desactualizado un SO que desmontó mal, y un número optimista de más
haría fallar la instalación a mitad — justo lo que el §4 evita.

En el instalador el §4 resultó ser algo más que "cuánto espacio hay": la
rama UEFI se lleva el disco ENTERO, 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 nombra el \EFI\Microsoft si está. El usuario se entera
ANTES, y no después de que su Windows dejó de arrancar.

Dos cosas que habrían pasado inadvertidas sin test: el nombre largo
tiene que ganarle al 8.3 (sin juntar los LFN, "Microsoft" se lee
"MICROS~1" y la advertencia no dispara nunca), y el tipo de FAT sale del
número de clusters y no del texto del BPB, que es informativo y hay
formateadores que mienten. El primer test del tipo lo escribí mal —1999
clusters ES FAT12— y el síntoma es idéntico a un bug del lector.

78/78 del CLI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
2026-09-11 22:55:46 +00:00
SergioandClaude Opus 5 66eaf8738f el reconciliador ARRANCA solo: montar sysfs, y un diagnóstico que no mentía a medias
Cierra el §2 del ADR 0018, verificado con dos arranques de la imagen
completa en OVMF:

  1º (por la fallback) → "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,0001,0002,0003"
  2º → BdsDxe: starting Boot0004 "takana" from HD(1,GPT,923A070F-…)
       /\EFI\takana\takanax64.efi
       "✓ arranque en orden: Boot0004 «takana» ya es la primera"

Un sistema recién instalado se da de alta en el firmware en su PRIMER
arranque y desde el segundo arranca por su propia entrada, sin que nadie
corra un comando. Y el segundo no escribe nada.

Lo caro no fue el reconciliador sino un diagnóstico FALSO: el primer
arranque con el hook dijo "sin firmware EFI — esta máquina no arrancó
por UEFI" en una VM que SÍ arrancó 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— así que el directorio
de efivars no existía. El mensaje mandaba a investigar el firmware, que
estaba perfecto.

Dos arreglos:
- el wrapper monta sysfs y después efivarfs. CONFIG_EFIVAR_FS=y ya
  estaba en el .config SELLADO (verificado, no supuesto) ⇒ no se
  re-hashea ningún kernel.
- el mensaje distingue TRES estados que se parecen: 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
  falta un mount manda a diagnosticar al lugar equivocado.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
2026-09-11 22:25:12 +00:00
SergioandClaude Opus 5 236abe48f3 boot entry reconcile: converger con la NVRAM en CADA arranque (ADR 0018 §2)
La NVRAM es estado compartido y el vecino la reescribe sin coordinarse.
Contra eso no sirve confiar: sirve converger. `reconcile` descubre su
propia ESP, repone la entrada y el BootOrder, y es idempotente.

Descubre la ESP por TIPO de partición (GUID de ESP en GPT, 0xEF en MBR),
que es el único dato fiable sin montar nada — un reconciliador que monta
sistemas de ficheros en el arranque es un efecto secundario que no
queremos. 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.

No rompe el arranque por nada: sin firmware EFI (máquina por BIOS) lo
dice y sale 0.

Y cuando actúa, GRITA — va a /dev/tty0 además del serial, a diferencia
del menú de arranque. Un reconciliador que repara en silencio deja al
usuario conviviendo con una rareza intermitente que no entiende; el
mensaje dice explícitamente que si se repite en cada arranque es que
otro sistema operativo le está reescribiendo la NVRAM.

Enganchado al wrapper de PID1 de las dos imágenes, con el mismo timeout
y el mismo || true que el menú: corre ANTES del exec de arje-zero y
colgarse ahí es un arranque muerto e indistinguible de un kernel colgado.

El test que vale es el escenario completo: takana se instala, llega el
vecino y se pone primero, y el siguiente arranque lo repone — SIN borrar
la entrada del vecino (takana se pone primera, no lo echa) — y el
arranque siguiente ya no escribe nada. Más el GUID de ESP, que va en
orden DE DISCO y no en el legible: escribirlo "como se lee" es el error
clásico y no casaría con ninguna ESP real.

15 tests en el módulo, 74/74 del CLI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
2026-09-11 22:18:16 +00:00
SergioandClaude Opus 5 294959c1da arranque y GC: las dos rutas EFI, guardián de ESP y el kernel vivo como raíz
Primera tanda de los ADR 0017/0018, con las mediciones que la corrigieron.

ADR 0018 §3 — install-image-efi.sh y takana-live-install.sh escriben el
kernel en \EFI\takana\takanax64.efi ADEMAS de la ruta fallback, con un
guardián de capacidad que comprueba ANTES y con los números a la vista
(duplicar el kernel cuesta, y el costo se dice). En el live-install la
verificación es por TAMAÑO, no por presencia: un cp truncado en FAT32
deja el fichero ahí y test -e diría que todo salió bien.

Verificado con imagen real en OVMF: las dos copias dan el mismo sha256
que el bzImage del store, y la imagen arranca hasta arje-zero PID1.
CONTROL NEGATIVO: pisando la fallback con 4K de basura el firmware dice
"No bootable option or device was found" y no aparece HAMMER-EFI ni una
vez ⇒ el escenario de Windows, reproducido.

Y lo que NO se pudo verificar cambia el alcance, así que va en el ADR:
sin entrada NVRAM el firmware NO busca el vendor path. La copia vendor
es hoy un seguro que no se cobra solo — el §3 es necesario y NO
suficiente sin el §1. Eso asciende la receta efibootmgr a bloqueante.

ADR 0017 §4 — store-gc.sh suma como raíz el kernel EN EJECUCIÓN,
identificado por .config byte a byte. Sin esto el artefacto del kernel
vivo cae en "superados" cuando la receta se movió, y se borra: el
sistema sigue andando perfecto hasta el día que hace falta volver atrás.
Probado en los tres sentidos, incluido el control que TIENE que seguir
condenado.

Además, dos bugs preexistentes que aparecieron al ir a medir:

- install-image-efi.sh abortaba con "ROOTFS sin /sbin/init" en TODO
  rootfs sano: /sbin/init es un symlink ABSOLUTO (→/usr/bin/arje-zero) y
  [ -e ] lo sigue contra la raíz del HOST. Un chequeo que validaba algo
  distinto de lo que creía validar. Arreglado resolviendo el destino
  dentro del rootfs.
- (no arreglado, es del entorno) el cp -al del staging da EXDEV si
  ROOTFS y STAGE no están en el MISMO MOUNT — y el bind-mount del store
  cuenta como otro mount aunque sea el mismo /dev/sdb.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHAKWoBqof9XvsYCvDicEj
2026-09-11 20:40:34 +00:00
SergioandClaude Opus 5 008dd3925e renombre: los instaladores pasan a takana-* — y el peligro no era el fichero, era el PROTOCOLO
ADR 0016 los listaba entre los CONGELADOS; el usuario pidió descongelarlos al abrir el SDD 28. La
enmienda queda escrita en el propio ADR, que si no la etiqueta deja de describir el hecho.

`hammer-install.sh` → `takana-install.sh`, `hammer-live-install.sh` → `takana-live-install.sh`,
`hammer-banner.txt` → `takana-banner.txt`, `BRIEFING-hammer.md` → `BRIEFING-takana.md`.

**Lo que hacía caro esto no es el nombre del fichero.** El instalador se inyecta en el ISO como
`/usr/bin/hammer-install` y su éxito se detecta con un `grep` de `HAMMER-INSTALL-OK` desde TRES
scripts de prueba. Renombrar un solo lado los deja casando NADA — sin fallar —, que es literalmente
el modo en que `atribuir-fallos.py` quedó mudo cuando el renombre movió el target de `tracing`.
Se renombraron las dos puntas en el mismo commit (`/usr/bin/takana-install`,
`TAKANA-INSTALL-OK/FAIL`, `TAKANA_INSTALL_*`, `work/takana-install.img`, `/run/takana-install`),
se comprobó por `grep` que no quede ningún token viejo fuera del ADR, y —lo que decide— se CORRIÓ
`install-tui-test.sh`: 4/4 casos verdes.

Las tres `TAKANA_INSTALL_*` caen al nombre viejo (`${TAKANA_INSTALL_X:-${HAMMER_INSTALL_X:-}}`):
el llamador puede ser un ISO anterior al renombre. Misma convención que `TAKANA_ROOT_PW` unas
líneas más arriba en ese mismo script.

NO se tocó `/usr/sbin/hammer-recover` ni su hook de arranque —renombrarlo rompe máquinas YA
INSTALADAS, no el repo—: sobrevive intacto dentro del script renombrado, verificado por conteo
antes y después (8 ocurrencias). Tampoco `hammerd`, `hammer-edit` (su `name` está en la ruta del
store), `/var/lib/hammer`, `HAMMER_LIVE` ni los siete literales de hash.

De paso: `scripts/.hammer-banner.txt.kate-swp` era un swap de editor commiteado por error. Fuera.

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