bootstrap: builder rootfs — ensambla el rebuild in-rootfs (Stage 2 pleno, §7)

El único sub-ítem que le quedaba a Stage 2: el rebuild *dentro* del rootfs. El
Stage 1 que booteamos es runtime (musl+busybox+hammerd+arje-zero), sin compilador
— no puede reconstruirse. El builder rootfs es Stage 1 + el toolchain adentro.

Implementa `hammer_bootstrap::builder_rootfs` (variante a pragmática, SDD 11 §7.2):
sobre el Stage 1 rootfs hidratado monta /toolchain (rootfs Alpine = sandbox de
build), /store con la semilla replicada (el hammer de adentro resuelve zig por
hash), /usr/bin/hammer + /etc/hammer/recipes, y el driver /usr/bin/rebuild-stage1
que apunta HAMMER_ROOTFS=/toolchain, corre `bootstrap stage1` y compara stage1'
contra la referencia con `stage2 --verify` (lee SEED_HASH/SEED_KIND/REF_CONTENT
de /etc/hammer/rebuild.env).

- BuilderSpec/BuilderReport + builder_hash lógico (insumos: stage1+semilla+
  recetas+binario+tag toolchain+driver) — reproducible y auditable sin hashear el
  árbol Alpine; anota línea stage 2 en el manifiesto.
- link_or_copy_tree (hardlink-or-copy, sin chmod: no toca permisos del .dev-fs).
- El builder no se sella (toolchain Alpine no es content-addressed); se ensambla
  en out_dir para empaquetar como initramfs.
- CLI `hammer bootstrap builder --stage1 H --seed-hash H [--hammer-bin] [--toolchain]
  [--ref-content] [--work-cache] [--out]`. +4 tests (assemble, hash determinista,
  sin-ref, stage1 no sellado).

Validado contra el store real: Stage 1 73d7a9be… + semilla 3ce721ec… ⇒ builder
81dad3d9… (1.2 GB con el toolchain). Lo que queda es operacional: bootear en la
VM y correr rebuild-stage1 — runbook §8c documenta la receta (hammer estático
musl, initramfs, qemu -cpu Broadwell, --work-cache para rebuild offline).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-06-11 14:32:14 +00:00
co-authored by Claude Opus 4.8
parent 731d4f6ffc
commit f17a8fa301
4 changed files with 543 additions and 1 deletions
+52
View File
@@ -266,6 +266,58 @@ Con ambos, `musl` y `busybox` reconstruyen **bit-idéntico**. Sumados `hammerd`
queda a Stage 2 es el rebuild *dentro* de un **builder rootfs** (un Stage 1 que incluya el
toolchain) — el auto-alojamiento pleno, un hito propio (el Stage 1 mínimo es runtime, sin compilador).
## 8c. Builder rootfs — el rebuild in-rootfs (auto-alojamiento, SDD 11 §7)
El **ensamblado** del builder ya está implementado (`hammer bootstrap builder`, [SDD 11 §7.4](../11-bootstrap.md);
variante a, toolchain desde Alpine). Produce, sobre el Stage 1 rootfs, una imagen con el toolchain
adentro lista para reconstruirse a sí misma. Receta operacional:
**1. Binario `hammer` estático (musl).** El builder lo bootea como PID-algo en la VM, así que debe
ser autocontenido (como hammerd/arje-zero, crt-static — §7):
```sh
cargo build --release --target x86_64-unknown-linux-musl -p hammer-cli
# (o cargo rustc -- -C target-feature=+crt-static, según el camino de oro del boot)
```
**2. Ensamblar el builder** anclando la referencia `of_tree(stage1)` (la que imprimió `stage2`):
```sh
hammer --store store bootstrap builder \
--stage1 b3:<STAGE1> --seed-hash b3:<SEED> \
--hammer-bin target/x86_64-unknown-linux-musl/release/hammer \
--toolchain .dev-fs/alpine --toolchain-tag alpine-3.23.4 \
--ref-content b3:<OF_TREE_STAGE1> \
--work-cache work \
--out work/builder-rootfs
```
`--work-cache work` embebe los mirrors git / tarballs ya fetcheados en `/work` ⇒ rebuild **offline y
determinista** en la VM (sin él, el rebuild fetchea por red; la seed card trae `networking: full`).
**3. Empaquetar como initramfs y bootear** (igual que §4/§7, `-cpu Broadwell` por AVX):
```sh
( cd work/builder-rootfs && find . | cpio -o -H newc | gzip ) > builder.cpio.gz
qemu-system-x86_64 -m 1024 -no-reboot -nographic -cpu Broadwell \
-kernel <vmlinuz> -initrd builder.cpio.gz -append "console=ttyS0 rdinit=/sbin/init"
```
**4. Dentro de la VM**, desde la shell de la consola, correr el driver embebido:
```sh
rebuild-stage1
# >> rebuild stage1 (toolchain in-rootfs, semilla b3:3ce721ec…)
# >> stage1' = b3:…
# ✓ REPRODUCIBLE: stage1' == stage1 (auto-alojado bit a bit) ← of_tree(stage1')==REF_CONTENT
```
`rebuild-stage1` apunta `HAMMER_ROOTFS=/toolchain`, corre `hammer bootstrap stage1` con la semilla y
las recetas de adentro, y compara con `stage2 --verify $REF_CONTENT`. **✓ REPRODUCIBLE** cierra el
auto-alojamiento end-to-end de la variante (a); **✗ DIVERGENTE** caza un no-determinismo nuevo
([SDD 09 §2](../09-trust-model.md)). La variante (b) — el toolchain construido por hammer desde
fuente — reemplaza `/toolchain` Alpine pieza a pieza, con este mismo lazo verificando cada paso.
## 9. Cross-check opcional — `arje-packager`
arje trae su propio empaquetador (`03_ukupacha/arje/init/arje-packager`):