Files
hammer/scripts/bootstrap-devfs.sh
T
sergioandClaude Opus 4.8 9a028ba04f gnome: SPIDERMONKEY (mozjs 128) SELLA en el hub — keystone caído
El motor JS de Mozilla, keystone del frente GNOME (gjs→gnome-shell cuelgan de él),
sella b3:c48dbd10: libmozjs-128.so + libjs_static.a + shell js128 + mozjs-128.pc +
550 headers. Compiló el C++ entero a -O3 -j8 sin OOM en el hub (31GB/0 swap, al filo).

Seis capas iteradas desde el borrador:
  1-2. FIX DE INFRA: el rootfs builder tenía clang pero NO el paquete  (Alpine
       separa los binutils LLVM del compilador). Sin llvm-ar/llvm-objdump/llvm-profdata
       configure aborta. bootstrap-devfs.sh: +llvm22 en NEEDED + paso 3a-ter que
       symlinkea la suite llvm-* a /usr/bin (Alpine la deja en /usr/lib/llvm22/bin sin
       exponerla). Aplicado ya al rootfs real (.dev-fs/alpine).
  3. triple: mozjs no traduce vendor ; 4. debe COINCIDIR con el host de rustc
     (); fijado ese exacto.
  5. venv de mach: cada fase es un bwrap con /tmp fresco ⇒ el venv no sobrevivía de
     configure a compile. Anclado a /src/.mozbuild (persiste, es scratch).
No re-hashea artefactos (el rootfs no entra al hash). OJO: el worker golden también
necesita llvm22 (ya cubierto por bootstrap-devfs.sh para la próxima provisión).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 14:17:13 -04:00

303 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
# bootstrap-devfs.sh — Prepara .dev-fs/ con todo lo que hammer-build necesita:
# 1. Rootfs Alpine minirootfs como base hermética del sandbox.
# 2. Compilador zig oficial bajo .dev-fs/tools/zig (symlink versionado).
# 3. Build tools (make, autoconf, automake, m4, patch, coreutils, libtool,
# pkgconf, bash) instaladas dentro del rootfs con apk.
# 4. Directorios de caché y work para builds posteriores.
#
# Idempotente: cada paso se salta si ya hizo. Reinvocar tras un cambio de
# versión actualiza in-place.
#
# Variables (override por entorno):
# ALPINE_BRANCH rama de Alpine (default v3.23)
# ALPINE_VER versión exacta del tarball (default 3.23.4)
# ALPINE_SHA256 sha256 del tarball (verificado obligatorio)
# ZIG_VER versión de zig (default 0.16.0)
# ZIG_SHA256 sha256 del tarball zig
#
# Uso:
# ./scripts/bootstrap-devfs.sh # bootstrap completo
# ./scripts/bootstrap-devfs.sh --skip-apk # sólo rootfs + zig
# ./scripts/bootstrap-devfs.sh --force-zig # re-descarga zig
set -euo pipefail
ALPINE_BRANCH="${ALPINE_BRANCH:-v3.23}"
ALPINE_VER="${ALPINE_VER:-3.23.4}"
ALPINE_SHA256="${ALPINE_SHA256:-85498865362aa7ebececa0d725a2f2e4db7ac4e4b2850b8df21645afa0d03ee3}"
ZIG_VER="${ZIG_VER:-0.16.0}"
ZIG_SHA256="${ZIG_SHA256:-70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00}"
# Toolchain Go del HOST (para `go mod vendor` en el fetch; cf. recipes/go.toml para el sandbox).
GO_VER="${GO_VER:-1.26.4}"
GO_SHA256="${GO_SHA256:-1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f}"
SKIP_APK=0
FORCE_ZIG=0
FORCE_ROOTFS=0
for arg in "$@"; do
case "$arg" in
--skip-apk) SKIP_APK=1 ;;
--force-zig) FORCE_ZIG=1 ;;
--force-rootfs) FORCE_ROOTFS=1 ;;
-h|--help)
sed -n '2,/^$/p' "$0" | sed 's/^# \?//'
exit 0 ;;
*)
echo "argumento desconocido: $arg" >&2
exit 1 ;;
esac
done
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DEVFS="$REPO_ROOT/.dev-fs"
ROOTFS="$DEVFS/alpine"
TOOLS="$DEVFS/tools"
ZIG_DIR="$TOOLS/zig-x86_64-linux-$ZIG_VER"
ZIG_LINK="$TOOLS/zig"
CACHE="$DEVFS/cache"
WORK="$REPO_ROOT/work"
log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m✓\033[0m %s\n' "$*"; }
mkdir -p "$DEVFS" "$TOOLS" "$CACHE" "$WORK"
# --- 1. Rootfs Alpine ---------------------------------------------------------
if [[ $FORCE_ROOTFS -eq 1 ]] && [[ -d "$ROOTFS" ]]; then
log "rootfs: --force-rootfs ⇒ borro $ROOTFS"
rm -rf "$ROOTFS"
fi
if [[ -f "$ROOTFS/bin/busybox" ]]; then
ok "rootfs Alpine ya presente en $ROOTFS"
else
log "rootfs: descargando alpine-minirootfs $ALPINE_VER"
tar="$DEVFS/alpine-minirootfs-$ALPINE_VER.tar.gz"
url="https://dl-cdn.alpinelinux.org/alpine/$ALPINE_BRANCH/releases/x86_64/alpine-minirootfs-$ALPINE_VER-x86_64.tar.gz"
curl -fsSL "$url" -o "$tar"
echo "$ALPINE_SHA256 $tar" | sha256sum -c -
mkdir -p "$ROOTFS"
tar -xzf "$tar" -C "$ROOTFS"
rm -f "$tar"
ok "rootfs Alpine $ALPINE_VER extraído"
fi
# --- 2. Compilador zig --------------------------------------------------------
if [[ $FORCE_ZIG -eq 1 ]] && [[ -e "$ZIG_DIR" ]]; then
log "zig: --force-zig ⇒ borro $ZIG_DIR"
rm -rf "$ZIG_DIR"
fi
if [[ -x "$ZIG_LINK/zig" ]]; then
ok "zig $ZIG_VER ya presente en $ZIG_LINK"
else
log "zig: descargando $ZIG_VER"
tar="$TOOLS/zig.tar.xz"
url="https://ziglang.org/download/$ZIG_VER/zig-x86_64-linux-$ZIG_VER.tar.xz"
curl -fsSL "$url" -o "$tar"
echo "$ZIG_SHA256 $tar" | sha256sum -c -
tar -xJf "$tar" -C "$TOOLS"
rm -f "$tar"
ln -sfn "zig-x86_64-linux-$ZIG_VER" "$ZIG_LINK"
ok "zig $ZIG_VER instalado, symlink $ZIG_LINK"
fi
# --- 2b. Toolchain Go del host (frente Go, Etapa G) ----------------------------
# El vendoring de módulos Go (`go mod vendor`) corre en el HOST durante el fetch (red
# permitida), igual que `cargo vendor`. El binario `go` oficial es estático ⇒ corre en
# cualquier host. Lo dejamos en .dev-fs/tools/go (fuente de verdad versionada) y lo
# enlazamos a ~/.cargo/bin (que el worker pone en PATH vía `. ~/.cargo/env`).
GO_DIR="$TOOLS/go-$GO_VER"
GO_LINK="$TOOLS/go"
if [[ -x "$GO_LINK/bin/go" ]] && "$GO_LINK/bin/go" version 2>/dev/null | grep -q "go$GO_VER"; then
ok "go $GO_VER ya presente en $GO_LINK"
else
log "go: descargando $GO_VER"
tar="$TOOLS/go.tar.gz"
url="https://go.dev/dl/go$GO_VER.linux-amd64.tar.gz"
curl -fsSL "$url" -o "$tar"
echo "$GO_SHA256 $tar" | sha256sum -c -
rm -rf "$GO_DIR"; mkdir -p "$GO_DIR"
tar -xzf "$tar" -C "$GO_DIR" --strip-components=1
rm -f "$tar"
ln -sfn "go-$GO_VER" "$GO_LINK"
ok "go $GO_VER instalado en $GO_LINK"
fi
# Symlink en un dir del PATH del host (junto a cargo) para que el fetch encuentre `go`.
if [[ -d "$HOME/.cargo/bin" ]]; then
ln -sfn "$GO_LINK/bin/go" "$HOME/.cargo/bin/go"
ln -sfn "$GO_LINK/bin/gofmt" "$HOME/.cargo/bin/gofmt"
ok "go enlazado en ~/.cargo/bin (PATH del host/worker)"
else
log "go: ~/.cargo/bin no existe; agregá $GO_LINK/bin al PATH del host a mano"
fi
# --- 3. Build tools en el rootfs (apk add) ------------------------------------
if [[ $SKIP_APK -eq 1 ]]; then
log "apk: --skip-apk ⇒ saltando build tools"
else
# `binutils` aporta ld/ar/ranlib/nm/strip: configure de autotools los inspecciona
# incluso cuando el compilador real es zig cc.
#
# `rust cargo`: las recetas Cargo (hammerd, arje-zero) corren `cargo build` DENTRO del
# sandbox. CAVEAT (primera corrida 2026-06-11): el rust de Alpine tiene host triple
# `x86_64-alpine-linux-musl`, pero `BuildSys::Cargo` pide `--target x86_64-unknown-linux-musl`
# y Alpine no trae ese std (no hay rustup). Falta resolver el toolchain Rust del lab (plan C.2)
# — ver docs/runbooks/stage1-vm-boot.md §8. Hasta entonces, los componentes C (musl, busybox)
# sí construyen end-to-end.
# BUMP DELIBERADO de toolchain (2026-06-22, [[cargo-recipe-msrv-ceiling]]): Alpine v3.23 trae
# rust 1.91.1 ⇒ techo MSRV que bloquea recetas modernas (ouch/uv 1.93, oxlint/ruff 1.94,
# zellij 1.92). edge trae rust 1.96.0. Apuntamos los repos del rootfs a EDGE para que `apk add`
# resuelva rust/cargo/clang 1.96 (+ arrastra gcc 15.2/musl 1.2.6/llvm22 como deps). El of_tree
# del 4/4 NO depende de esto (usa SWAP_RUST con el rust hammer-built); esto es el toolchain del
# CATÁLOGO. Para anclar una versión exacta en vez de un edge rodante, pinear un snapshot de edge.
if [[ -f "$ROOTFS/etc/apk/repositories" ]]; then
printf '%s\n%s\n' \
'https://dl-cdn.alpinelinux.org/alpine/edge/main' \
'https://dl-cdn.alpinelinux.org/alpine/edge/community' \
> "$ROOTFS/etc/apk/repositories"
fi
# `linux-headers`: UAPI del kernel (linux/kd.h, etc.) que zig cc NO bundlea; busybox y otros
# 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.)
# `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.
# `kmod` aporta insmod/modprobe: en la VM el sandbox de build usa overlay (módulo del kernel,
# CONFIG_OVERLAY_FS=m) y el initramfs arranca sin módulos cargados, así que el builder debe poder
# cargarlo. El `overlay.ko` en sí es específico del kernel destino (no de Alpine): se inyecta
# aparte en el builder (ver docs/runbooks/stage1-vm-boot.md §8c).
# `g++ zlib-dev`: bootstrap del frente rust variante (b) — mrustc es C++14 y DEBE compilarse con
# g++ (clang/zig rompen sus macros `tagged_union`, ver mrustc issue #388) y enlaza `-lz`. El
# toolchain traía libstdc++ runtime pero no cc1plus/headers ni zlib-dev. Son compiladores/libs de
# arranque (mismo status que make/zig/gcc); el veto purista es sobre un *rustc* prebuilt, no sobre
# un compilador C++. (zlib-dev quedará reemplazado por recipes/zlib.toml cuando el frente avance.)
# NOTA: `flex bison` YA NO van aquí — de-Alpinizados a recipes/{flex,bison}.toml (deps.build del
# kernel). Sólo el kernel los usa (kconfig); el overlay de deps los provee. (m4, que bison invoca en
# runtime, sigue en el base como tool autotools general.)
# NOTA: `elfutils-dev` YA NO va aquí — de-Alpinizado a recipes/elfutils.toml (sólo libelf, con shims
# musl argp/error/libintl/fts/obstack/rawmemchr), deps.build del kernel. objtool enlaza el libelf
# hammer desde la capa overlay. (El runtime libelf.so.1 de Alpine, si algún otro tool lo necesita,
# es la misma versión 0.194 ABI-compatible.)
# NOTA: `openssl-dev` YA NO va aquí — de-Alpinizado a recipes/openssl.toml (libcrypto estática,
# deps.build del kernel). El host-tool certs/extract-cert lo enlaza desde la capa overlay hammer.
# El runtime libcrypto3/libssl3 (que curl/git necesitan) lo trae Alpine aparte, no es openssl-dev.
# `clang-dev clang-libs`: aportan libclang.so — `bindgen` (que usan varios *-sys: libbzip3-sys,
# etc.) lo carga en build para generar bindings de los headers C. No lo trae el base.
# `llvm22`: la SUITE binutils de LLVM (llvm-ar/llvm-objdump/llvm-profdata/llvm-nm/llvm-readobj).
# `clang`/`clang-libs` NO la arrastran (sólo libLLVM.so + los binarios clang), y mozjs/spidermonkey
# con clang exige llvm-ar y llvm-objdump ⇒ sin esto configure aborta "Cannot find ar/llvm-objdump".
# Alpine deja los binarios en /usr/lib/llvm22/bin SIN symlinkear a /usr/bin (multi-versión); el paso
# 3a-ter de abajo los expone en el PATH estándar, igual que Alpine ya hace con `clang`.
NEEDED="make autoconf automake m4 patch coreutils libtool pkgconf bash binutils g++ zlib-dev rust cargo clang-dev clang-libs llvm22 linux-headers bubblewrap git curl kmod"
missing=""
for pkg in $NEEDED; do
# apk info -e devuelve el paquete si está instalado, vacío si no.
if ! bwrap --bind "$ROOTFS" / --proc /proc --dev /dev --unshare-all \
--setenv PATH /usr/bin:/usr/sbin:/bin:/sbin \
/sbin/apk info -e "$pkg" >/dev/null 2>&1; then
missing="$missing $pkg"
fi
done
if [[ -n "$missing" ]]; then
log "apk: instalando paquetes faltantes:$missing"
bwrap --bind "$ROOTFS" / --proc /proc --dev /dev \
--ro-bind /etc/resolv.conf /etc/resolv.conf --share-net \
/sbin/apk update >/dev/null
bwrap --bind "$ROOTFS" / --proc /proc --dev /dev \
--ro-bind /etc/resolv.conf /etc/resolv.conf --share-net \
/sbin/apk add $missing
fi
ok "build tools presentes en el rootfs ($NEEDED)"
fi
# --- 3a-ter. Exponer la suite llvm-* en /usr/bin -----------------------------
# Alpine instala los binutils de LLVM en /usr/lib/llvm22/bin (versionado) sin symlink a /usr/bin,
# para permitir varias versiones. El PATH del sandbox es fijo (/opt/zig:/usr/local/bin:/usr/bin:...)
# y NO incluye ese dir, así que sin symlinks `llvm-ar`/`llvm-objdump` son invisibles y mozjs no
# configura. Los symlinkeamos relativos (idéntico a como el propio Alpine expone `clang`). Idempotente.
if [[ -d "$ROOTFS/usr/lib/llvm22/bin" ]]; then
for f in "$ROOTFS"/usr/lib/llvm22/bin/llvm-*; do
t="$(basename "$f")"
[[ -e "$ROOTFS/usr/bin/$t" ]] || ln -s "../lib/llvm22/bin/$t" "$ROOTFS/usr/bin/$t"
done
ok "suite llvm-* expuesta en /usr/bin"
fi
# --- 3a-bis. Neutralizar scudo-malloc (frente edge rust) ----------------------
# edge empaqueta rust/cargo con `scudo-malloc` como DEPENDENCIA DURA: los shims rustc/cargo lo
# linkean como PRIMER NEEDED ⇒ el allocator endurecido scudo interpone malloc en todo el proceso
# (incluido librustc_driver). scudo CRASHEA a rustc en ciertos proc-macros (corrupted-chunk-header /
# SIGSEGV: lo cazaron jnv/presenterm/trippy) — choque entre scudo y la carga/descarga de dylibs de
# proc-macro. No se puede `apk del scudo-malloc` (rust/cargo dependen). Mitigación: aliasar
# libscudo.so → la libc musl ⇒ el NEEDED resuelve pero malloc cae a musl (sin scudo). Los shims no
# referencian símbolos scudo-específicos (sólo querían el override de malloc), así que es seguro.
# Reversible: el backup .orig-scudo. Destrabó jnv/presenterm/trippy.
SCUDO="$ROOTFS/usr/lib/libscudo.so"
if [[ $SKIP_APK -eq 0 && -f "$SCUDO" && ! -L "$SCUDO" ]]; then
log "scudo: neutralizando (alias libscudo.so → musl libc; evita el crash de rustc en proc-macros)"
cp -a "$SCUDO" "$SCUDO.orig-scudo"
ln -sf /lib/libc.musl-x86_64.so.1 "$SCUDO"
ok "scudo neutralizado"
fi
# --- 3b. musl del toolchain con PTHREAD_KEYS_MAX=256 (frente rust variante b) --
# El rustc booteado por mrustc (frente rust) agota los pthread keys de musl al correr:
# `fatal runtime error: out of TLS keys, aborting` (musl default PTHREAD_KEYS_MAX=128; es el
# gotcha #4 de mrustc issue #388). Es el musl del TOOLCHAIN — el loader que toda tool del rootfs
# linkea dinámicamente como intérprete (/lib/ld-musl-x86_64.so.1) — NO el musl target del 4/4
# (recipes/musl.toml, estático, intacto). Reconstruimos musl 1.2.5 con 256 keys y reemplazamos el
# loader. ABI-compat (mismo musl 1.2.5): gcc/make/etc. siguen corriendo. Seguro para of_tree: el
# loader runtime de las tools no afecta los bytes que emiten; el 4/4 es estático contra hammer-musl.
# Idempotente: si ya existe el backup .orig128, no rehace nada.
# CRÍTICO: el loader reconstruido DEBE ser de la MISMA versión de musl que el rootfs (Alpine edge
# es rodante: pasó de 1.2.5 a 1.2.6). Si difieren, los coreutils del rootfs (linkeados contra la
# versión de edge) no hallan símbolos nuevos en el loader viejo (p.ej. `renameat2: symbol not
# found` con loader 1.2.5 vs coreutils 1.2.6) ⇒ chmod/cat/rm rotos dentro del sandbox y los builds
# fallan al ejecutar el wrapper zig-cc (EACCES). Por eso DETECTAMOS la versión del rootfs.
MUSL_LOADER="$ROOTFS/lib/ld-musl-x86_64.so.1"
if [[ $SKIP_APK -eq 0 && ! -f "$MUSL_LOADER.orig128" ]]; then
# versión de musl instalada en el rootfs (apk db): "1.2.6-r2" → "1.2.6"
MUSL_VER="$(awk '/^P:musl$/{f=1} f&&/^V:/{sub(/-.*/,"",$0);print substr($0,3);exit}' \
"$ROOTFS/lib/apk/db/installed" 2>/dev/null)"
MUSL_VER="${MUSL_VER:-1.2.6}"
case "$MUSL_VER" in
1.2.5) MUSL_SHA=a9a118bbe84d8764da0ea0d28b3ab3fae8477fc7e4085d90102b8596fc7c75e4 ;;
1.2.6) MUSL_SHA=d585fd3b613c66151fc3249e8ed44f77020cb5e6c1e635a616d3f9f82460512a ;;
*) die "musl $MUSL_VER del rootfs sin sha256 conocido — agregalo al case (bootstrap-devfs.sh §3b)" ;;
esac
log "musl: reconstruyendo loader del toolchain $MUSL_VER con PTHREAD_KEYS_MAX=256 (frente rust)"
MWORK="$WORK/musl256"; rm -rf "$MWORK"; mkdir -p "$MWORK"
curl -fsSL -o "$MWORK/musl.tar.gz" "https://musl.libc.org/releases/musl-$MUSL_VER.tar.gz"
echo "$MUSL_SHA $MWORK/musl.tar.gz" | sha256sum -c - >/dev/null || die "musl tarball sha256 mismatch"
tar -C "$MWORK" -xzf "$MWORK/musl.tar.gz"
sed -i 's/#define PTHREAD_KEYS_MAX 128/#define PTHREAD_KEYS_MAX 256/' "$MWORK/musl-$MUSL_VER/include/limits.h"
# Build de lib/libc.so (= el loader) dentro del rootfs con gcc (como lo compila Alpine).
bwrap --bind "$ROOTFS" / --bind "$MWORK" /musl256 --proc /proc --dev /dev --tmpfs /tmp \
--setenv PATH /usr/bin:/bin --setenv HOME /tmp --chdir "/musl256/musl-$MUSL_VER" \
sh -c 'CC=gcc ./configure --prefix=/usr >/dev/null && make -j"$(nproc)" lib/libc.so >/dev/null' \
|| die "build de musl-256 falló"
cp -a "$MUSL_LOADER" "$MUSL_LOADER.orig128" # backup del loader Alpine original
cp "$MWORK/musl-$MUSL_VER/lib/libc.so" "$MUSL_LOADER"
ok "musl del toolchain con 256 TLS keys (loader reemplazado; backup en .orig128)"
fi
# --- 4. Resumen ---------------------------------------------------------------
cat <<EOF
bootstrap-devfs.sh — OK
rootfs: $ROOTFS
zig: $ZIG_LINK ($("$ZIG_LINK/zig" version))
cache: $CACHE
work: $WORK
Variables que la CLI ya recoge por defecto (override con env vars):
HAMMER_ROOTFS = $ROOTFS
HAMMER_ZIG = $ZIG_LINK
HAMMER_CACHE = $CACHE # vacío ("") desactiva la caché
HAMMER_WORK = $WORK
EOF