bootstrap: builder ejecutable — toolchain con bwrap/git/curl + shim del loader

Correr el rebuild EN la VM destapó dos faltantes reales del builder (justo lo que
la verificación de Stage 2 debe cazar):

1. El toolchain (variante a, desde Alpine) traía compiladores (cargo/make/zig)
   pero NO las herramientas de orquestación del lab — bwrap (anida el sandbox),
   git (git archive del mirror) y curl (tarballs). En el host las aporta el
   sistema; en la VM el toolchain es el único userland capaz, así que deben vivir
   ahí. bootstrap-devfs.sh las añade (paquete `bubblewrap`, no `bwrap`).

2. Esos binarios son Alpine *dinámicos* y no corren desde el userland Stage 1
   *estático* (musl --disable-shared ⇒ no hay loader en /lib). rebuild-stage1
   ahora instala un shim del loader musl (cp a /lib) + LD_LIBRARY_PATH/PATH al
   toolchain antes de invocar hammer; el sandbox de build sigue anidando en
   /toolchain. El hammer estático corre por ruta absoluta.

El builder boot end-to-end ya estaba probado; esto lo hace además *capaz de
reconstruirse*. 28 tests verdes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-06-11 15:13:32 +00:00
co-authored by Claude Opus 4.8
parent 4a35dfb398
commit 64894d863c
2 changed files with 21 additions and 4 deletions
+16 -3
View File
@@ -489,22 +489,35 @@ pub fn verify_against(report: &VerifyReport, rebuilt_content: ArtifactHash) -> R
/// `/etc/hammer/rebuild.env` (`SEED_HASH`/`SEED_KIND`/`REF_CONTENT`), apunta el sandbox de build al
/// toolchain de adentro (`/toolchain`) y compara `stage1'` contra la referencia. Si `REF_CONTENT` va
/// vacío, sólo imprime el content-hash de `stage1'` para comparación manual.
///
/// El toolchain es Alpine **dinámico**: sus binarios (bwrap/git/curl/cargo/make) no corren desde el
/// userland Stage 1, que es **estático** (no hay loader en `/lib`). El driver instala un *shim* del
/// loader musl (`cp` a `/lib`) + `LD_LIBRARY_PATH`/`PATH` al toolchain, y a partir de ahí esas
/// herramientas son ejecutables; el `hammer` estático vive en `/usr/bin/hammer`. El sandbox de build
/// (bwrap) anida en `/toolchain`. (El shim sólo se usa para *invocar* el toolchain desde fuera del
/// sandbox: fetch git/tarball; dentro del sandbox el loader está en su sitio.)
const REBUILD_DRIVER: &str = r#"#!/bin/sh
# rebuild-stage1 — rebuild de Stage 1 con el toolchain del propio rootfs (auto-alojamiento, SDD 11 §7).
# Booteado en la VM (builder rootfs), reconstruye stage1' usando SÓLO las herramientas de adentro y
# compara su content-hash con la referencia que `hammer bootstrap stage2` ancló afuera.
set -eu
. /etc/hammer/rebuild.env # SEED_HASH, SEED_KIND, REF_CONTENT (REF_CONTENT puede ir vacío)
# Shim del loader musl: deja correr los binarios *dinámicos* del toolchain (bwrap/git/curl/cargo/make)
# desde el root Stage 1 estático. Usa el busybox estático (rutas absolutas) ANTES de tocar el PATH.
/bin/mkdir -p /lib
/bin/cp -f /toolchain/lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1
export LD_LIBRARY_PATH=/toolchain/lib:/toolchain/usr/lib
export PATH=/toolchain/usr/bin:/toolchain/usr/sbin:/toolchain/bin:/usr/bin:/bin:/sbin
export HAMMER_ROOTFS=/toolchain # el sandbox de build: el toolchain (variante a, desde Alpine)
export HAMMER_WORK=/work
export HAMMER_CACHE=
echo ">> rebuild stage1 (toolchain in-rootfs, semilla $SEED_HASH)"
PRIME=$(hammer --store /store bootstrap stage1 --seed-hash "$SEED_HASH" --seed "$SEED_KIND" --recipes /etc/hammer/recipes)
PRIME=$(/usr/bin/hammer --store /store bootstrap stage1 --seed-hash "$SEED_HASH" --seed "$SEED_KIND" --recipes /etc/hammer/recipes)
echo ">> stage1' = $PRIME"
if [ -n "${REF_CONTENT:-}" ]; then
hammer --store /store bootstrap stage2 --rootfs "$PRIME" --verify "$REF_CONTENT"
/usr/bin/hammer --store /store bootstrap stage2 --rootfs "$PRIME" --verify "$REF_CONTENT"
else
hammer --store /store bootstrap stage2 --rootfs "$PRIME"
/usr/bin/hammer --store /store bootstrap stage2 --rootfs "$PRIME"
fi
"#;
+5 -1
View File
@@ -117,7 +117,11 @@ else
# paquetes la necesitan. zig cc nativo la encuentra en /usr/include. (Lo destapó la
# verificación de reproducibilidad de Stage 2: con el config determinista, los applets de
# console-tools la exigen.)
NEEDED="make autoconf automake m4 patch coreutils libtool pkgconf bash binutils rust cargo linux-headers"
# `bwrap git curl`: el rebuild *dentro* del builder rootfs (Stage 2 pleno, SDD 11 §7) corre
# hammer-build con su propio toolchain. En el host estas tres las aporta el sistema, pero en la
# VM el toolchain ES el único userland capaz, así que deben vivir aquí: `bwrap` anida el sandbox
# de build, `git` rematerializa las fuentes git (git archive del mirror), `curl` baja tarballs.
NEEDED="make autoconf automake m4 patch coreutils libtool pkgconf bash binutils rust cargo linux-headers bubblewrap git curl"
missing=""
for pkg in $NEEDED; do
# apk info -e devuelve el paquete si está instalado, vacío si no.