#!/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}" 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 # --- 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. # `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.) NEEDED="make autoconf automake m4 patch coreutils libtool pkgconf bash binutils g++ zlib-dev rust cargo 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 # --- 4. Resumen --------------------------------------------------------------- cat <