Files
takana/scripts/lab-image.sh
T
Sergio 442a319499 lab re-pineado con lld (d1e341d5) — y el sha «determinista» lo ensuciaba un log
Al ir a re-pinear la imagen para propagar el `lld` a la granja, hub y worker empaquetaron a shas
DISTINTOS. La imagen se creó para que el sha VERIFIQUE, así que un desacuerdo ahí no se pinea: se
mide.

Los dos rootfs son idénticos: 9550 entradas con la misma lista de ficheros, los mismos tamaños, y
CERO diferencias de metadato (modo, nlink, destino de symlink) en las 8778 entradas de fichero y
symlink. La única diferencia de contenido en todo el rootfs era `var/log/apk.log`, donde apk anota
la FECHA de cada operación — o sea que dos labs equivalentes empaquetaban distinto sólo porque las
instalaciones ocurrieron en momentos distintos. Un sha que no se puede reproducir desde un rootfs
equivalente no verifica nada, que es justo lo que este fichero existe para dar.

Se excluye ese log del tar (no todo `var/log`: cambio mínimo; apk lo recrea solo). Con la exclusión,
hub y worker dan el MISMO tar: 179d04f054f28d8dafe7c626d6b8c686a33a00a96715ee544a9bcf6f62cf586d.

⇒ LA GRANJA YA ESTABA SINCRONIZADA Y AHORA SE PUEDE DEMOSTRAR, sin correr `--traer` en un worker que
está construyendo firefox — reemplazarle el rootfs a mitad de build habría sido la forma cara de
descubrir lo mismo.

Nueva imagen publicada: hammer/lab/lab-d1e341d5….tar.zst (310 M), pin actualizado en
bootstrap-devfs.sh. Queda en el comentario cómo comparar dos máquinas: se compara el TAR y no el
`.tar.zst`, porque el sha comprimido depende de la versión de zstd de cada máquina y dos labs
idénticos con zstd distintos darían un falso desacuerdo.
2026-09-05 03:49:56 +00:00

142 lines
7.6 KiB
Bash
Executable File

#!/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' "$*"; }
# ── POR QUÉ SE EXCLUYE `var/log/apk.log` (2026-09-05) ───────────────────────────────────────────
# El tar es determinista a propósito (ver la cabecera), pero `apk` escribe en ese log una línea con
# la FECHA de cada operación. O sea: dos rootfs con exactamente los mismos paquetes, los mismos
# ficheros, los mismos modos y los mismos hardlinks empaquetan a shas DISTINTOS, sólo porque las
# instalaciones ocurrieron en momentos distintos. Eso rompe justo lo que este fichero existe para
# dar: si el sha no se puede reproducir desde un rootfs equivalente, no verifica nada.
#
# Medido el día que se añadió `lld` al lab: hub y worker quedaron con listas de ficheros IDÉNTICAS
# (9550 entradas), metadatos idénticos (modo, nlink, destino de symlink: 0 diferencias) y **una sola
# diferencia de contenido en todo el rootfs**: `var/log/apk.log`. Con esa exclusión los dos labs
# empaquetan igual y se puede DEMOSTRAR que la granja está sincronizada sin reemplazarle el rootfs a
# un worker que está construyendo.
#
# Se excluye el log y no todo `var/log` a propósito: cambio mínimo dirigido. `apk` lo recrea solo en
# la siguiente operación.
#
# COMPARAR DOS MÁQUINAS SIN REEMPLAZARLE EL ROOTFS A NINGUNA — corré esto en las dos y compará:
# tar --sort=name --mtime=@1 --owner=0 --group=0 --numeric-owner \
# --exclude alpine/var/log/apk.log -C .dev-fs -cf - alpine | sha256sum
# Se compara el TAR y no el `.tar.zst`: el sha comprimido depende de la versión de zstd de cada
# máquina, así que dos labs idénticos con zstd distintos darían un falso desacuerdo.
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 \
--exclude alpine/var/log/apk.log \
-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