El rebuild in-VM daba ✗ DIVERGENTE (of_tree host 984e002f ≠ VM 94b93585) pese a que los 4 componentes sellaban bajo el mismo key of_inputs. Bisección por componente en el host: musl, busybox y hammerd reconstruyen byte-idéntico, y la ensambladura del rootfs (of_tree) es determinista e independiente del entorno. El culpable era arje-zero: dos builds del MISMO host daban binarios distintos (Δ ~9.6 KB por .text/.rodata/.eh_frame/ .gcc_except_table) — firma del codegen paralelo de rustc. Raíz: el workspace de hammer pinea [profile.release] codegen-units=1 (por eso hammerd reproducía), pero el monorepo tawasuyu no declara perfil → cargo default codegen-units=16, cuyo reparto del crate en N objetos varía build-a-build. Fix: el sandbox impone codegen-units=1 para TODAS las crates Cargo (el var de cargo gana sobre el profile del repo fuente). El lab elimina el no-determinismo en vez de confiar en upstream (SDD 09 §2). Verificado: dos builds cu=1 de arje-zero → byte-idénticos. Nueva referencia reproducible 4/4: of_tree(stage1)=b3:0039b2b9… (reemplaza 198f209f… de cu=16). Actualizados EXPECT_REF (selfhost-verify.sh) y runbook §8b/§8c. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
139 lines
7.8 KiB
Bash
Executable File
139 lines
7.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# selfhost-verify.sh — Verifica el auto-alojamiento bit a bit de Stage 1 de punta a punta
|
|
# (SDD 11 §7; runbook docs/runbooks/stage1-vm-boot.md §8c).
|
|
#
|
|
# Hace TODO el pipeline en una corrida, pensado para un host con KVM + RAM holgada (p. ej. una
|
|
# laptop): construye el store baseline, ensambla el builder, lo empaqueta como initramfs y lo bootea
|
|
# en QEMU, donde `rebuild-stage1` reconstruye stage1' con el toolchain de adentro y compara su
|
|
# content-hash (`of_tree`) con la referencia anclada afuera → ✓ REPRODUCIBLE / ✗ DIVERGENTE.
|
|
#
|
|
# Prerequisitos:
|
|
# - Lab de dev: ./scripts/bootstrap-devfs.sh (deja .dev-fs/alpine + .dev-fs/tools/zig)
|
|
# - qemu-system-x86_64, cpio, gzip; un kernel x86_64 en $KERNEL.
|
|
# - Red (el fetch de Rust hace `cargo vendor` desde crates.io).
|
|
#
|
|
# Variables (override por entorno):
|
|
# STORE store content-addressed (default ./store)
|
|
# KERNEL kernel a bootear en la VM (default /boot/vmlinuz-linux)
|
|
# MEM RAM de la VM en MiB (default 6144; con KVM subí a 8192+)
|
|
# KVM 1 ⇒ -enable-kvm -cpu host (default auto: 1 si hay /dev/kvm)
|
|
# PRESEED "all" rebuild 4/4 in-VM | "hammerd" preseed C+arje, sólo hammerd in-VM
|
|
# (default all; "hammerd" es el camino barato/rápido si la RAM va justa)
|
|
# EXPECT_REF content-hash esperado (cross-check de reproducibilidad entre máquinas; opcional)
|
|
# STAGE0_URL / STAGE0_SHA256 / SEED_VERSION semilla zig (defaults: zig 0.16.0)
|
|
#
|
|
# Uso típico en la laptop (con KVM):
|
|
# KVM=1 MEM=10240 ./scripts/selfhost-verify.sh
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$REPO_ROOT"
|
|
|
|
STORE="${STORE:-store}"
|
|
KERNEL="${KERNEL:-/boot/vmlinuz-linux}"
|
|
MEM="${MEM:-6144}"
|
|
PRESEED="${PRESEED:-all}"
|
|
SEED_VERSION="${SEED_VERSION:-0.16.0}"
|
|
STAGE0_URL="${STAGE0_URL:-https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz}"
|
|
STAGE0_SHA256="${STAGE0_SHA256:-70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00}"
|
|
TOOLCHAIN="${TOOLCHAIN:-.dev-fs/alpine}"
|
|
TOOLCHAIN_TAG="${TOOLCHAIN_TAG:-alpine-3.23.4-builder}"
|
|
# Referencia conocida-buena (store baseline en el host de dev, runbook §8c). Si tu corrida produce
|
|
# otra, o bien cambió algo del pin (toolchain/recipes/seed) o hay un no-determinismo nuevo.
|
|
# Baseline con codegen-units=1 en el sandbox (reproducibilidad de arje-zero, ver §8c y SDD 09 §2).
|
|
EXPECT_REF="${EXPECT_REF:-b3:0039b2b90fddf62221ad5b804a34df93ef57dc533cb210512cf53752876afdff}"
|
|
: "${KVM:=$([[ -w /dev/kvm ]] && echo 1 || echo 0)}"
|
|
|
|
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
|
|
die() { printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; exit 1; }
|
|
|
|
[[ -x "$TOOLCHAIN/usr/bin/rustc" ]] || die "falta el lab: corré ./scripts/bootstrap-devfs.sh (no veo $TOOLCHAIN)"
|
|
[[ -r "$KERNEL" ]] || die "no puedo leer el kernel $KERNEL (set KERNEL=…)"
|
|
command -v qemu-system-x86_64 >/dev/null || die "falta qemu-system-x86_64"
|
|
|
|
# 0) Binario hammer estático (musl, crt-static) — el builder lo bootea adentro.
|
|
HAMMER_BIN="target/x86_64-unknown-linux-musl/release/hammer"
|
|
say "build hammer estático ($HAMMER_BIN)"
|
|
cargo build --release --target x86_64-unknown-linux-musl -p hammer-cli
|
|
HAMMER="./$HAMMER_BIN"
|
|
|
|
say "stage0 — ingerir la semilla zig (idempotente)"
|
|
"$HAMMER" --store "$STORE" bootstrap stage0 \
|
|
--url "$STAGE0_URL" --sha256 "$STAGE0_SHA256" --version "$SEED_VERSION"
|
|
# El seed_hash sale del nombre del artefacto sellado (robusto: no parsea stdout, donde el sha256 del
|
|
# tarball también es 64-hex y podría confundirse).
|
|
SEED_HASH="$(ls -d "$STORE"/*-seed-zig 2>/dev/null | head -1 | sed -E 's#.*/([0-9a-f]{64})-seed-zig#b3:\1#')"
|
|
[[ "$SEED_HASH" == b3:* && ${#SEED_HASH} -eq 67 ]] || die "no encuentro la semilla sellada en $STORE"
|
|
say "semilla: $SEED_HASH"
|
|
|
|
# 1) Store baseline: stage1 (idempotente; rebuildea lo que falte con -mcpu=baseline ya en fuente).
|
|
say "stage1 — construir y sellar el rootfs baseline"
|
|
"$HAMMER" --store "$STORE" bootstrap stage1 --seed-hash "$SEED_HASH" --recipes recipes
|
|
ROOTFS_HASH="$(ls -d "$STORE"/*-stage1-rootfs | head -1 | sed -E 's#.*/([0-9a-f]{64})-stage1-rootfs#b3:\1#')"
|
|
[[ "$ROOTFS_HASH" == b3:* ]] || die "no encuentro el stage1-rootfs en $STORE"
|
|
say "stage1 rootfs: $ROOTFS_HASH"
|
|
|
|
# 2) Referencia of_tree(stage1) que la VM debe reproducir.
|
|
REF="$("$HAMMER" --store "$STORE" bootstrap stage2 --rootfs "$ROOTFS_HASH" 2>&1 \
|
|
| grep -oE 'content-hash: b3:[0-9a-f]{64}|content=b3:[0-9a-f]{64}' | grep -oE 'b3:[0-9a-f]{64}' | tail -1)"
|
|
[[ "$REF" == b3:* ]] || die "no obtuve la referencia de stage2"
|
|
say "referencia of_tree(stage1) = $REF"
|
|
if [[ -n "$EXPECT_REF" && "$REF" != "$EXPECT_REF" ]]; then
|
|
printf '\033[1;33mAVISO: la referencia difiere de la conocida-buena del host de dev:\n esta %s\n espera %s\nReproducibilidad entre máquinas rota O cambió un pin (toolchain/recipes/seed). Seguimos igual.\033[0m\n' "$REF" "$EXPECT_REF"
|
|
fi
|
|
|
|
# 3) Inyectar los módulos del kernel DE ESTA máquina al toolchain (el builder los carga: overlay para
|
|
# el sandbox bwrap, e1000 para la red del vendoring). Deben matchear el kernel que bootea la VM.
|
|
# La versión de los módulos DEBE matchear el kernel que bootea la VM ($KERNEL), no el del host
|
|
# corriendo el script ($(uname -r)) — en máquinas con varios kernels instalados difieren y el
|
|
# insmod falla con "version magic mismatch" → sin overlayfs, bwrap revienta. Sacamos la versión
|
|
# del propio bzImage; si no se puede, caemos a uname -r.
|
|
KVER="$(file -bL "$KERNEL" 2>/dev/null | grep -oE 'version [0-9][^ ]*' | awk '{print $2}')"
|
|
[[ -n "$KVER" && -d "/lib/modules/$KVER" ]] || KVER="$(uname -r)"
|
|
say "inyectar overlay.ko + e1000.ko (kernel $KVER) al toolchain"
|
|
moddir="/lib/modules/$KVER"
|
|
for m in overlay e1000; do
|
|
src="$(find "$moddir" -name "$m.ko*" 2>/dev/null | head -1)"
|
|
if [[ -n "$src" ]]; then
|
|
# Descomprimir si viene .ko.zst/.ko.gz/.ko.xz; el builder hace insmod del .ko crudo.
|
|
case "$src" in
|
|
*.zst) zstd -dqf "$src" -o "$TOOLCHAIN/lib/$m.ko" 2>/dev/null || cp "$src" "$TOOLCHAIN/lib/$(basename "$src")" ;;
|
|
*.gz) gzip -dc "$src" > "$TOOLCHAIN/lib/$m.ko" ;;
|
|
*.xz) xz -dc "$src" > "$TOOLCHAIN/lib/$m.ko" ;;
|
|
*) cp "$src" "$TOOLCHAIN/lib/$m.ko" ;;
|
|
esac
|
|
echo " $m: $src"
|
|
else
|
|
echo " $m: NO encontrado en $moddir (si la VM lo necesita, fallará: bwrap overlay / red)"
|
|
fi
|
|
done
|
|
|
|
# 4) Ensamblar el builder con la referencia embebida.
|
|
say "ensamblar builder (toolchain in-rootfs + hammer baseline + ref)"
|
|
"$HAMMER" --store "$STORE" bootstrap builder \
|
|
--stage1 "$ROOTFS_HASH" --seed-hash "$SEED_HASH" \
|
|
--hammer-bin "$HAMMER_BIN" \
|
|
--toolchain "$TOOLCHAIN" --toolchain-tag "$TOOLCHAIN_TAG" \
|
|
--ref-content "$REF" \
|
|
--work-cache work --out work/builder-rootfs
|
|
|
|
# 4b) Preseed opcional para abaratar el rebuild in-VM (sólo hammerd se reconstruye).
|
|
if [[ "$PRESEED" == "hammerd" ]]; then
|
|
say "preseed musl+busybox+arje-zero (sólo hammerd se reconstruye in-VM)"
|
|
for n in musl busybox arje-zero; do
|
|
d="$(ls -d "$STORE"/*-"$n" 2>/dev/null | head -1)"
|
|
[[ -n "$d" ]] && cp -a "$d" work/builder-rootfs/store/ && echo " preseed: $(basename "$d")"
|
|
done
|
|
fi
|
|
|
|
# 5) Empaquetar como initramfs. --owner=root:root OBLIGATORIO: si los ficheros viajan con el uid del
|
|
# host, el userns de bwrap (mapea 0→0) no lo mapea y el copy-up de overlay falla con EACCES.
|
|
say "empaquetar initramfs (cpio newc, --owner=root:root)"
|
|
( cd work/builder-rootfs && find . -print0 | cpio --null -o -H newc --owner=root:root 2>/dev/null | gzip -1 ) > work/builder.cpio.gz
|
|
echo " work/builder.cpio.gz: $(du -h work/builder.cpio.gz | cut -f1)"
|
|
|
|
# 6) Bootear + driver no-interactivo → veredicto.
|
|
say "bootear la VM y correr rebuild-stage1 (KVM=$KVM MEM=$MEM)"
|
|
KVM="$KVM" MEM="$MEM" KERNEL="$KERNEL" BUILDER_CPIO="work/builder.cpio.gz" \
|
|
python3 scripts/drive-rebuild.py
|