Files
takana/scripts/bootstrap-devfs.sh
T
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

396 lines
23 KiB
Bash
Executable File

#!/usr/bin/env bash
# bootstrap-devfs.sh — Prepara .dev-fs/ con todo lo que takana-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
# ./scripts/bootstrap-devfs.sh --relock # acepta el toolchain actual como lock
# ./scripts/bootstrap-devfs.sh --from-scratch # resuelve el rootfs con apk en vez de traer la imagen
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
FROM_SCRATCH=0
FORCE_ZIG=0
FORCE_ROOTFS=0
RELOCK=0
for arg in "$@"; do
case "$arg" in
--skip-apk) SKIP_APK=1 ;;
--force-zig) FORCE_ZIG=1 ;;
--force-rootfs) FORCE_ROOTFS=1 ;;
--relock) RELOCK=1 ;;
--from-scratch) FROM_SCRATCH=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"
# --- 0. Imagen pineada del lab (el camino por defecto) ------------------------
# Desde que el toolchain entra en `hash_inputs`, el rootfs NO se resuelve: se TRAE. `apk add` contra
# Alpine edge es irrepetible por diseño (edge sólo sirve la última versión), así que dos máquinas
# que lo resuelvan en fechas distintas obtienen toolchains distintos y **no comparten ni un
# artefacto**: ni cosecha de granja, ni `mirror pull`, ni un cache-hit. Con la imagen pineada por
# sha256 todas las máquinas tienen el MISMO lab y vuelven a compartir store.
#
# `--from-scratch` recupera el camino viejo (minirootfs + apk). Es como se FABRICA una imagen nueva:
# ./scripts/bootstrap-devfs.sh --from-scratch && ./scripts/lab-image.sh --crear
# y después se pinea el sha nuevo acá abajo. Reconstruir el corpus es el precio de ese cambio.
LAB_IMAGE_SHA256="${LAB_IMAGE_SHA256:-d1e341d5dd434a11e2290b0340069ce4a83563eac8e4481f379b4951b1162045}"
if [[ $FROM_SCRATCH -eq 0 ]] && [[ ! -f "$ROOTFS/bin/busybox" ]]; then
log "rootfs: trayendo la imagen pineada del lab"
LAB_IMAGE_SHA256="$LAB_IMAGE_SHA256" "$REPO_ROOT/scripts/lab-image.sh" --traer
SKIP_APK=1 # la imagen ya trae el toolchain resuelto; volver a correr apk lo movería
fi
# --- 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 takana-built); esto es el toolchain del
# CATÁLOGO. OJO: edge es RODANTE y no se puede pinear —sólo publica la última versión y Alpine no
# da snapshots datados—, así que el paso 3a-quater registra lo resuelto en un lock y AVISA de la
# deriva. Anclar de verdad exigiría espejar APKINDEX + los .apk, que es un frente aparte.
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
# takana-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
# takana 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 takana.
# 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.
# `lld`: el linker de LLVM (`ld.lld`). Lo pide `compiler = "clang"` — hoy `recipes/firefox.toml`,
# que es la primera receta del corpus que lo usa. Firefox sondea el linker por la cadena que
# imprime (busca «LLD»/«GNU ld»/«GNU gold») y `--enable-lto=cross` necesita un linker con plugin
# LLVM; `llvm22-linker-tools` da el LLVMgold.so para GNU ld, pero el camino soportado por
# upstream —y el que usa el propio APKBUILD de Alpine con `--enable-linker=lld`— es lld.
# ⚠ DEUDA CONOCIDA: `lld` NO casa ningún prefijo de `TOOLCHAIN_PREFIXES` (takana-core/src/lab.rs)
# ⇒ su versión NO entra en la huella del lab. Eso es lo que permite añadirlo HOY sin re-hashear
# los 837 artefactos sellados (medido: la huella no se movió), y a la vez es un agujero: dos labs
# con distinto lld pueden sellar bytes distintos en la misma dirección. Sólo expone a las recetas
# `compiler="clang"`, que hoy es una. Meter "lld" en la lista de prefijos es lo correcto y cuesta
# un re-hasheo del corpus entero: va en la próxima campaña, no de paso.
# `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`.
# `libffi-dev ncurses-dev readline-dev sqlite-dev bzip2-dev xz-dev expat-dev`: los HEADERS que
# CPython necesita para sus módulos opcionales. Sin ellos `configure` los omite EN SILENCIO y
# sella un intérprete mutilado que arranca bien; el fallo aparece días después en otra receta
# (`ModuleNotFoundError: No module named '_ctypes'` en gjs, `_curses` en gdm — 2026-08-11), y
# **310 recetas dependen de python3**.
#
# POR QUÉ EN EL ROOTFS Y NO COMO `[deps]` DE LA RECETA, que sería lo habitual: se intentó y NO
# FUNCIONA. CPython construye sus módulos opcionales como `.so` COMPARTIDOS y las `.a` del corpus
# NO son PIC ⇒ `ld.lld: relocation R_X86_64_PC32 cannot be used against symbol 'stdscr';
# recompile with -fPIC`. Las libs del rootfs son compartidas y sí sirven. Mientras no existan
# recetas PIC de esas libs, el lab es el único sitio donde esto se resuelve.
#
# ⚠ `openssl-dev` NO va, y es deliberado: se de-Alpinizó a `recipes/openssl.toml` porque el
# host-tool del kernel enlaza el openssl del corpus desde la capa overlay. Devolverlo al rootfs
# podría hacer que el kernel linkee el de Alpine y cambiar su artefacto. ⇒ python3 sigue sin
# módulo `ssl`; ninguna receta lo necesita para construir, `pip` sí lo necesitaría.
NEEDED="make autoconf automake m4 patch coreutils libtool pkgconf bash binutils g++ zlib-dev rust cargo clang-dev clang-libs lld llvm22 linux-headers bubblewrap git curl kmod libffi-dev ncurses-dev readline-dev sqlite-dev bzip2-dev xz-dev expat-dev"
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-quater. Lock del toolchain: NOMBRAR la deriva de edge ------------------
# Los repos de arriba apuntan a Alpine **edge**, que es RODANTE. Dos hubs bootstrapeados en fechas
# distintas obtienen compiladores distintos con el mismo script: medido el 2026-08-10, el laptop
# tenía rust 1.96 y gioser resolvió **1.97.0-r0**. Para un proyecto cuyo invariante es reproducir,
# eso es deriva del LAB, y no se ve en `build-state.json`.
#
# POR QUÉ UN LOCK Y NO UN PIN DE VERDAD. Un pin exacto (`apk add rust=1.96.0-r0`) NO funciona: edge
# sólo publica la última versión — `apk policy rust` lista únicamente 1.97.0-r0 —, así que el pin
# rompe en cuanto edge avanza. Un snapshot datado tampoco existe: Alpine no los publica para edge.
# Anclar de verdad exigiría espejar APKINDEX + los .apk nosotros, que es un frente aparte.
# Mientras tanto esto hace lo segundo mejor y lo hace RUIDOSO: registra el toolchain resuelto y
# AVISA cuando el de esta máquina difiere del registrado, con el diff delante.
# Un lock que no se puede imponer sigue valiendo si al menos nombra lo que cambió.
LOCK="$REPO_ROOT/docs/state/lab-toolchain.lock"
if [[ -f "$ROOTFS/lib/apk/db/installed" ]]; then
ACTUAL="$WORK/.lab-toolchain.actual"
bwrap --bind "$ROOTFS" / --proc /proc --dev /dev --unshare-all \
--setenv PATH /usr/bin:/usr/sbin:/bin:/sbin \
/sbin/apk info -v 2>/dev/null | sort > "$ACTUAL"
if [[ $RELOCK -eq 1 ]] || [[ ! -f "$LOCK" ]]; then
mkdir -p "$(dirname "$LOCK")"
{ echo "# Toolchain del lab (.dev-fs/alpine) resuelto desde Alpine edge — RODANTE."
echo "# Regenerar deliberadamente con: scripts/bootstrap-devfs.sh --relock"
echo "# No es un pin: edge sólo sirve la última versión. Esto DETECTA la deriva, no la evita."
cat "$ACTUAL"
} > "$LOCK"
ok "lock del toolchain escrito ($(wc -l < "$ACTUAL") paquetes) → docs/state/lab-toolchain.lock"
else
if diff -q <(grep -v '^#' "$LOCK") "$ACTUAL" >/dev/null 2>&1; then
ok "toolchain del lab idéntico al lock ($(wc -l < "$ACTUAL") paquetes)"
else
printf '\033[1;33m⚠\033[0m el toolchain de esta máquina DIFIERE del lock:\n'
diff <(grep -v '^#' "$LOCK") "$ACTUAL" | grep -E '^[<>]' | sed 's/^/ /'
printf ' ⇒ un artefacto Cargo sellado aquí puede no reproducir el del otro hub.\n'
printf ' Aceptarlo: scripts/bootstrap-devfs.sh --relock (y commitear el lock).\n'
fi
fi
rm -f "$ACTUAL"
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 takana-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