From 0de4bf08aaeae609481851fec2c3373d92a93ccc Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 10 Aug 2026 18:57:13 +0000 Subject: [PATCH] =?UTF-8?q?lab:=20el=20rootfs=20se=20ANCLA=20en=20una=20im?= =?UTF-8?q?agen=20pineada=20=E2=80=94=20deja=20de=20resolverse=20con=20apk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Al entrar el toolchain en hash_inputs (58d3161) se abrio un agujero operativo: los repos del rootfs apuntan a Alpine edge, que es RODANTE, asi que dos maquinas que corran bootstrap-devfs.sh en fechas distintas resuelven toolchains distintos y NO COMPARTEN NI UN ARTEFACTO. Ni cosecha de granja, ni mirror pull, ni un cache-hit. Cada worker efimero nacia con un lab propio. `apk add` contra edge es irrepetible por diseno (edge sirve solo la ultima version). La salida no es repetir la resolucion, es no repetirla: el rootfs se construye UNA vez y se TRAE pineado por sha256 — el mismo trato que ya reciben alpine-minirootfs y zig en este script. imagen: 294 M comprimida (1,1 G extraida) sha256: db8a32526ad41fbcc76223842dbb4eb65c8ed576570b82152dc7c5691fc2a349 EL TAR ES DETERMINISTA A PROPOSITO (--sort=name --mtime=@1 --owner=0 --numeric-owner): sin eso el sha depende del orden del directorio y del uid de quien empaqueta, dos empaquetados del MISMO rootfs darian imagenes distintas y no habria forma de verificar que una imagen es la que dice ser. Comprobado: dos pasadas, mismo sha. EL REEMPLAZO ES POR ROTACION, no extrayendo encima: a medias quedaria una MEZCLA de dos rootfs, que es justo lo que esto existe para evitar. VERIFICADO de punta a punta: bajada del box, sha intacto tras el viaje, extraida, y la huella de lab que produce es IDENTICA a la del rootfs vivo (zlib -> b3:c7a7338f... en las dos). O sea que una maquina que arranque de esta imagen comparte store. --from-scratch conserva el camino viejo: es como se FABRICA una imagen nueva. Reconstruir el corpus es el precio de ese cambio, y ahora es explicito. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/bootstrap-devfs.sh | 21 +++++++ scripts/lab-image.sh | 119 +++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100755 scripts/lab-image.sh diff --git a/scripts/bootstrap-devfs.sh b/scripts/bootstrap-devfs.sh index ccd80a8f..63b93d49 100755 --- a/scripts/bootstrap-devfs.sh +++ b/scripts/bootstrap-devfs.sh @@ -21,6 +21,7 @@ # ./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 @@ -34,6 +35,7 @@ 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 @@ -43,6 +45,7 @@ for arg in "$@"; do --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 ;; @@ -66,6 +69,24 @@ 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:-db8a32526ad41fbcc76223842dbb4eb65c8ed576570b82152dc7c5691fc2a349}" + +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" diff --git a/scripts/lab-image.sh b/scripts/lab-image.sh new file mode 100755 index 00000000..6035c595 --- /dev/null +++ b/scripts/lab-image.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# lab-image.sh — empaqueta y publica el ROOTFS YA BOOTSTRAPEADO como imagen pineada por sha256. +# +# ── POR QUÉ EXISTE ────────────────────────────────────────────────────────────────────────────── +# Desde el 2026-08-10 el toolchain del lab entra en `hash_inputs` (ver `hammer-core/src/lab.rs`): +# dos máquinas con distinto `rustc` sellan en direcciones distintas. Eso cierra un agujero real +# —antes sellaban bytes distintos en la MISMA dirección— pero abre uno operativo: +# +# Los repos del rootfs apuntan a Alpine **edge**, que es RODANTE. Dos máquinas que corran +# `bootstrap-devfs.sh` en fechas distintas resuelven toolchains distintos ⇒ **no comparten NI UN +# artefacto**. Ni cosecha de granja, ni `mirror pull` entre hubs, ni cache-hit. Cada worker +# efímero nacería con un lab propio y reconstruiría todo desde cero. +# +# El `apk add` contra edge es, por diseño, irrepetible. Así que el rootfs deja de resolverse y pasa +# a **traerse**: una imagen construida UNA vez, pineada por sha256 y compartida por todas las +# máquinas — exactamente el trato que ya reciben `alpine-minirootfs` y `zig` en bootstrap-devfs.sh. +# +# ── EL TAR ES DETERMINISTA A PROPÓSITO ────────────────────────────────────────────────────────── +# `--sort=name` + `--mtime=@1` + `--owner=0 --group=0 --numeric-owner`: sin esto el sha256 depende +# del orden del directorio y del uid de quien empaqueta, así que dos empaquetados del MISMO rootfs +# darían imágenes distintas y no habría forma de verificar que una imagen es la que dice ser. +# Con esto, `--crear` sobre el mismo rootfs reproduce el mismo sha, y eso es comprobable. +# Ojo: el uid 0 del tar es cosmético — quien extrae sin ser root se queda como dueño igual, y el +# sandbox usa `--tmp-overlay /`, así que la propiedad de los ficheros del rootfs no influye. +# +# Uso: +# scripts/lab-image.sh --crear # empaqueta .dev-fs/alpine, imprime el sha256 y lo sube al box +# scripts/lab-image.sh --sha # sólo calcula el sha del rootfs actual (no sube nada) +# scripts/lab-image.sh --traer # baja la imagen pineada y reemplaza .dev-fs/alpine +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" +DEVFS="$ROOT/.dev-fs" +ROOTFS="$DEVFS/alpine" + +# El sha de la imagen VIGENTE. Es el contrato compartido: todas las máquinas deben tener ésta. +# Se actualiza a mano tras un `--crear`, igual que ALPINE_SHA256 y ZIG_SHA256 en bootstrap-devfs.sh. +LAB_IMAGE_SHA256="${LAB_IMAGE_SHA256:-PENDIENTE}" + +SB_USER="${SB_USER:-u647150}" +SB_HOST="${SB_HOST:-u647150.your-storagebox.de}" +SB_PORT="${SB_PORT:-23}" +KEY="${KEY:-$HOME/.ssh/github5}" +SSH_CMD="ssh -4 -p $SB_PORT -i $KEY -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30" +REMOTO="hammer/lab" + +log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m✓\033[0m %s\n' "$*"; } + +empaquetar() { # $1 = destino + [ -d "$ROOTFS" ] || { echo "no existe $ROOTFS — corré bootstrap-devfs.sh primero" >&2; exit 1; } + tar --sort=name --mtime=@1 --owner=0 --group=0 --numeric-owner \ + -C "$DEVFS" -cf - alpine \ + | zstd -19 -T0 -q -o "$1" -f +} + +case "${1:---sha}" in + --sha) + tmp="$(mktemp -p "$DEVFS" lab-image.XXXXXX.tar.zst)" + trap 'rm -f "$tmp"' EXIT + log "empaquetando (determinista) para calcular el sha…" + empaquetar "$tmp" + echo "sha256: $(sha256sum "$tmp" | cut -d' ' -f1)" + echo "tamaño: $(du -h "$tmp" | cut -f1)" + ;; + + --crear) + img="$DEVFS/lab-image.tar.zst" + log "empaquetando $ROOTFS (determinista)…" + empaquetar "$img" + sha="$(sha256sum "$img" | cut -d' ' -f1)" + ok "imagen: $(du -h "$img" | cut -f1) · sha256 $sha" + log "subiendo a $SB_USER@$SB_HOST:$REMOTO/" + $SSH_CMD "$SB_USER@$SB_HOST" "mkdir $REMOTO" >/dev/null 2>&1 || true + # El nombre lleva el sha: las imágenes son inmutables y conviven, así que una máquina que + # todavía use la anterior no se queda sin su imagen cuando se publica una nueva. + rsync -a --partial-dir=.rsync-partial -e "$SSH_CMD" \ + "$img" "$SB_USER@$SB_HOST:$REMOTO/lab-$sha.tar.zst" + ok "publicada como $REMOTO/lab-$sha.tar.zst" + echo + echo "AHORA: poné este sha en bootstrap-devfs.sh (LAB_IMAGE_SHA256) y commiteá." + echo " LAB_IMAGE_SHA256=\"$sha\"" + ;; + + --traer) + [ "$LAB_IMAGE_SHA256" = "PENDIENTE" ] && { + echo "no hay imagen pineada todavía: corré --crear en la máquina de referencia" >&2; exit 1; } + img="$DEVFS/lab-$LAB_IMAGE_SHA256.tar.zst" + if [ -f "$img" ] && [ "$(sha256sum "$img" | cut -d' ' -f1)" = "$LAB_IMAGE_SHA256" ]; then + ok "imagen ya presente y verificada" + else + log "bajando lab-$LAB_IMAGE_SHA256.tar.zst" + rsync -a --partial-dir=.rsync-partial -e "$SSH_CMD" \ + "$SB_USER@$SB_HOST:$REMOTO/lab-$LAB_IMAGE_SHA256.tar.zst" "$img" + real="$(sha256sum "$img" | cut -d' ' -f1)" + [ "$real" = "$LAB_IMAGE_SHA256" ] || { + echo "!! sha NO coincide: esperaba $LAB_IMAGE_SHA256, llegó $real" >&2 + rm -f "$img"; exit 1; } + ok "sha verificado" + fi + # Reemplazo atómico-ish: extraer al lado y rotar. Extraer ENCIMA dejaría una mezcla de los dos + # rootfs si algo falla a medias, y una mezcla es justo lo que este fichero existe para evitar. + log "extrayendo…" + rm -rf "$DEVFS/alpine.nuevo" + mkdir -p "$DEVFS/alpine.nuevo" + zstd -d -q -c "$img" | tar -x -C "$DEVFS/alpine.nuevo" + rm -rf "$DEVFS/alpine.viejo" + [ -d "$ROOTFS" ] && mv "$ROOTFS" "$DEVFS/alpine.viejo" + mv "$DEVFS/alpine.nuevo/alpine" "$ROOTFS" + rmdir "$DEVFS/alpine.nuevo" + rm -rf "$DEVFS/alpine.viejo" + ok "rootfs reemplazado por la imagen pineada" + ;; + + *) + sed -n '2,/^set -euo/p' "$0" | sed 's/^# \?//;$d' + exit 0 ;; +esac