tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1569Shell 203Rust 109Markdown 993728 Definitions

⎇ d23946fa3d45e29f42e0ea707d612d5477ab04f3 docs/13-release-engineering.md Markdown · 86 lines · 6.8 KB History raw

hammer-upgrade: upgrades atómicos con generaciones y rollback (Etapa E4, primer corte) d23946fa3 · sergio ·

1# SDD 13 — Release engineering (Etapa E): imagen auto-booteable, instalador, mirror, upgrades
2
3> Estado: **EN CURSO**. Etapa E del roadmap (`docs/10-roadmap.md`, memoria distro-completion-roadmap).
4> Es trabajo nuevo: las Etapas A–D dejaron un sistema que (A) se orquesta, (B) bootea de disco
5> particionado real, (C) tiene userland Rust-nativo y (D) atesta confianza. Falta convertir eso en algo
6> que un tercero **instale y arranque sin el host de dev**: una imagen que bootea sola (sin
7> `qemu -kernel`), un instalador a disco físico, un mirror del store y un camino de upgrade.
8
9## 1. El gap concreto que abre la Etapa E
10
11Hasta B3 la imagen de disco (`scripts/disk-image.sh`, GPT con `/`, `/store`, `/var/lib/hammer`
12dedicados) **no se auto-bootea**: el `drive-rebuild.py` la arranca con `qemu -kernel <bzImage>
13-append root=/dev/vdaN` — el kernel lo carga QEMU, no la imagen. Eso sirve para verificar el
14auto-alojamiento, pero un disco así no arranca en hardware real ni en una VM "normal": le falta un
15**bootloader** en el propio disco.
16
17## 2. Decisión de bootloader: GRUB BIOS (i386-pc) ahora, UEFI después
18
19- **GRUB BIOS (i386-pc) — ELEGIDO para el primer corte.** QEMU trae SeaBIOS built-in ⇒ un disco con
20  GRUB en el MBR + una *BIOS boot partition* (GPT type `ef02`) bootea con `qemu-system-x86_64 -drive
21  file=img` **sin firmware extra ni `-kernel`**. Se instala **en userspace, sin root ni loop**:
22  `grub-mkimage` arma `core.img` y `grub-bios-setup` escribe `boot.img`→MBR + `core.img`→partición
23  BIOS sobre el *fichero* imagen (es I/O de bloques, no necesita montar). Los módulos de GRUB y el
24  kernel van en `/boot` de la partición root, que `mke2fs -d` puebla desde un directorio.
25- **UEFI (EFI-stub + ESP FAT) — diferido.** El kernel ya trae `CONFIG_EFI_STUB=y`, pero (a) el host de
26  dev no tiene firmware OVMF usable para verificar el arranque, y (b) poblar un ESP FAT sin root exige
27  `mtools` (ausente). Camino futuro cuando haya OVMF + mtools (o se construyan): ESP con
28  `EFI/BOOT/BOOTX64.EFI` = el bzImage (EFI-stub), cmdline por `loader.conf`/UKI.
29
30## 3. Layout de la imagen instalable (GPT, BIOS)
31
32```
33/dev/vda1  BIOS boot (ef02, ~2 MiB, sin fs)   core.img de GRUB
34/dev/vda2  /                ext4  [hammer-root]   + /boot/bzImage + /boot/grub
35/dev/vda3  /store           ext4  [hammer-store]  artefactos CAS (inmutable)
36/dev/vda4  /var/lib/hammer  ext4  [hammer-state]  estado mutable (journal, overlays)
37```
38
39Cadena de arranque: SeaBIOS → MBR (boot.img) → core.img (BIOS boot part) → lee `(hd0,gpt2)/boot/grub/
40grub.cfg` → `linux /boot/bzImage root=/dev/vda2 rdinit=/sbin/init` → wrapper `/sbin/init` monta
41vda3/vda4 → `exec /usr/bin/arje-zero` (PID 1). El wrapper es el mismo puente de B3 (arje todavía no lee
42fstab/cards de montaje por sí mismo).
43
44`scripts/install-image.sh` produce esta imagen. Reusa la técnica de particionado sin-root de B3
45(sfdisk + `mke2fs -d` bajo `unshare -r` + `dd conv=sparse,notrunc`) y añade la BIOS boot partition, el
46kernel en `/boot`, los módulos GRUB en `/boot/grub/i386-pc` y los pasos `grub-mkimage`/`grub-bios-setup`.
47
48## 4. Piezas pendientes de la Etapa E (orden tentativo)
49
50- **E1 — imagen auto-booteable (GRUB BIOS): ✅ primer corte** (`scripts/install-image.sh`). Bootea en
51  QEMU sin `-kernel` hasta la shell de arje-zero. Hoy empaqueta el *builder rootfs* (con toolchain);
52  un *product rootfs* lean es refinamiento.
53- **E2 — instalador a disco físico:** un `hammer install <device>` que particiona un disco real y
54  vuelca las tres particiones + GRUB (la misma lógica, apuntando a `/dev/sdX` en vez de un fichero).
55- **E3 — mirror del store: ✅ primer corte** (crate `hammer-mirror` + CLI `hammer mirror push|pull|status`).
56  Replica `/store` content-addressed (BLAKE3) entre máquinas; el hash ES la dirección, así que un mirror es
57  un CAS replicado + resolución por hash. La integridad se ancla en `of_tree` (content-hash del árbol): el
58  receptor **recomputa** `of_tree` sobre lo recibido y exige que case el del índice **antes** de sellar
59  (`Store::seal` atómico + read-only) — una transferencia corrupta/manipulada se **rechaza**, no se instala.
60  `push`/`pull` son `sync(src,dst)` (idempotente, salta presentes, reporta conflictos = mismo hash con otro
61  contenido). Transporte de esta entrega = **sistema de ficheros** (el remoto es una ruta a otro store, p.ej.
62  un montaje sshfs); el transporte sobre SSH/red que el producto ya expone es un envoltorio posterior.
63  Endurecimiento pendiente: anclar el `of_tree` a una raíz firmada (log de transparencia `bootstrap.json` /
64  atestación de Etapa D) para defenderse de un origen plenamente malicioso (hoy el invariante forzado es
65  "el contenido recibido hashea a lo que el índice anuncia", que ataja corrupción de transporte).
66- **E4 — upgrades: ✅ primer corte** (crate `hammer-upgrade` + CLI `hammer upgrade apply|rollback|status`).
67  Aplica un árbol Stage1/producto **nuevo** (artefacto sellado del store, CAS BLAKE3) sobre el FHS vivo
68  sin reinstalar, dejando una **generación**, y revierte exactamente al árbol anterior con `rollback`.
69  **Modelo de generaciones** (inspirado en NixOS, in-place al FHS porque el boot lee el root directo, no
70  un symlink indirecto): cada generación graba bajo `<state>/generations/<N>/` su `manifest.json`
71  (tree_dir del store, `of_tree`, parent, lista de ficheros, cambios) y un `backup/` con los bytes
72  **previos** de todo path que pisó o retiró. **Atomicidad:** proyección fichero-a-fichero (escritura a
73  temporal + `rename` ⇒ cada fichero conmuta atómicamente), con `current` (id de la generación viva) como
74  commit-point; un corte a mitad deja `current` en la vieja y los backups intactos ⇒ re-apply/rollback
75  recuperan. **Diff vs el árbol previo:** el apply añade/pisa los ficheros del árbol nuevo y **retira** los
76  que la generación viva aportaba y el nuevo no tiene. **Rollback:** deshace en orden inverso (borra lo
77  añadido, restaura desde `backup/` lo pisado/retirado) y retrocede `current` al padre — restauración
78  *exacta*, incluso al estado pre-upgrades. Cada cambio se registra en el [journal](05-journal.md) como
79  `HammerHydrate{artifact=tree_dir}` (replay-able). Verificación opcional de integridad: si el llamador
80  anuncia el `of_tree` esperado (de un índice de **mirror E3** o release firmada), el apply lo exige antes
81  de tocar el root. Maneja ficheros regulares + symlinks; valida E2E en host con `scripts/upgrade-e2e-test.sh`
82  (apply v1→v2→rollback→rollback contra el binario real). **Endurecimiento pendiente:** journal de
83  *intención* + replay para sobrevivir un kernel-panic a media escritura; GC de generaciones viejas
84  (prune); cableado del init de boot para que el rollback sea seleccionable al arranque.
85- **E5 — ISO/medio de arranque:** medio live para correr el instalador (xorriso/grub-mkrescue;
86  herramental a construir).

13-release-engineering.md

SDD 13 — Release engineering (Etapa E): imagen auto-booteable, instalador, mirror, upgrades

Estado: EN CURSO. Etapa E del roadmap (docs/10-roadmap.md, memoria distro-completion-roadmap). Es trabajo nuevo: las Etapas A–D dejaron un sistema que (A) se orquesta, (B) bootea de disco particionado real, (C) tiene userland Rust-nativo y (D) atesta confianza. Falta convertir eso en algo que un tercero instale y arranque sin el host de dev: una imagen que bootea sola (sin qemu -kernel), un instalador a disco físico, un mirror del store y un camino de upgrade.

1. El gap concreto que abre la Etapa E

Hasta B3 la imagen de disco (scripts/disk-image.sh, GPT con /, /store, /var/lib/hammer dedicados) no se auto-bootea: el drive-rebuild.py la arranca con qemu -kernel <bzImage> -append root=/dev/vdaN — el kernel lo carga QEMU, no la imagen. Eso sirve para verificar el auto-alojamiento, pero un disco así no arranca en hardware real ni en una VM "normal": le falta un bootloader en el propio disco.

2. Decisión de bootloader: GRUB BIOS (i386-pc) ahora, UEFI después

3. Layout de la imagen instalable (GPT, BIOS)

/dev/vda1  BIOS boot (ef02, ~2 MiB, sin fs)   core.img de GRUB
/dev/vda2  /                ext4  [hammer-root]   + /boot/bzImage + /boot/grub
/dev/vda3  /store           ext4  [hammer-store]  artefactos CAS (inmutable)
/dev/vda4  /var/lib/hammer  ext4  [hammer-state]  estado mutable (journal, overlays)

Cadena de arranque: SeaBIOS → MBR (boot.img) → core.img (BIOS boot part) → lee (hd0,gpt2)/boot/grub/ grub.cfg → linux /boot/bzImage root=/dev/vda2 rdinit=/sbin/init → wrapper /sbin/init monta vda3/vda4 → exec /usr/bin/arje-zero (PID 1). El wrapper es el mismo puente de B3 (arje todavía no lee fstab/cards de montaje por sí mismo).

scripts/install-image.sh produce esta imagen. Reusa la técnica de particionado sin-root de B3 (sfdisk + mke2fs -d bajo unshare -r + dd conv=sparse,notrunc) y añade la BIOS boot partition, el kernel en /boot, los módulos GRUB en /boot/grub/i386-pc y los pasos grub-mkimage/grub-bios-setup.

4. Piezas pendientes de la Etapa E (orden tentativo)