hammer-{core,build,bootstrap,overlay,journal,mirror,upgrade,agent,recover,cli}
→ takana-*, con sus deps de workspace, sus identificadores en el fuente y las
referencias -p de los scripts.
VERIFICADO que no mueve nada del corpus: `takana hash recipes/zlib.toml`
devuelve b3:dc363f26… , idéntico a antes del renombre. Los nombres de crate no
entran en hash_inputs, pero eso se comprueba, no se supone. 600 tests en verde.
DOS BINARIOS SE CONGELAN, y no por prolijidad:
- `hammerd` — paquete Y binario. Es componente de Stage 1 de la distro (musl,
busybox, hammerd, arje-zero), lo supervisa arje-zero en el sistema arrancado,
`PRESEED=hammerd` lo nombra en selfhost-verify y sus bytes anclan el baseline
of_tree. El nombre del crate va en los símbolos ⇒ renombrarlo mueve los bytes.
- `hammer-recover` — el PAQUETE se renombra a takana-recover, el BINARIO no.
hammer-live-install.sh lo copia a /usr/sbin/hammer-recover en sistemas ya
instalados y hornea un hook de arranque que lo invoca por ese nombre:
renombrarlo rompe máquinas instaladas, no el repo.
Consecuencia que hay que anotar igual: al renombrar hammer-core, los bytes de
hammerd cambian de todos modos porque linkea contra un crate con otro nombre.
El baseline of_tree del selfhost hay que rehacerlo — es efecto de la etapa 4,
no de un cambio de hammerd.
Las referencias en comentarios de recetas y docs (rutas hammer-core/src/…)
quedan para la etapa 5: son texto, no mueven hash.
142 lines
7.6 KiB
Bash
Executable File
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 `takana-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
|