gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1181Shell 82Rust 62Markdown 521515 Sutichaykuna

⎇ main scripts/lib/herramientas-desde-store.sh Shell · 123 siq'ikuna · 7.9 KB Ñawpa willay ch'uya

instalador: el censo sale en JSON, mirar deja de costar lo que escribir, y una candidata que no entra deja de ofrecerse 251405e72 · Sergio ·

1# herramientas-desde-store.sh — las herramientas que ENSAMBLAN una imagen salen del STORE, no del host.
2#
3# ══ POR QUÉ EXISTE (medido el 2026-09-22) ══════════════════════════════════════════════════════
4# Armar una imagen necesita `sfdisk mke2fs cpio gzip mformat mcopy xorriso` y, en la rama BIOS,
5# `grub-mkimage`/`grub-bios-setup`. Los scripts los tomaban del `PATH` del host — y **en este hub, en
6# su anfitrión y en el worker no existe NINGUNO de ellos**:
7#
8#   grub-mkimage AUSENTE · grub-bios-setup AUSENTE · cpio AUSENTE · mformat AUSENTE · xorriso AUSENTE
9#
10# O sea que el pipeline de imagen estaba roto en las tres máquinas que tenemos, en silencio, hasta que
11# alguien lo corriera. Y lo absurdo es que **seis de las siete son nuestras**: `util-linux` (sfdisk),
12# `e2fsprogs` (mke2fs), `libarchive` (bsdcpio), `gzip`, `mtools`, `xorriso` y `dosfstools` están
13# SELLADAS en el store, son estáticas musl y corren tal cual en el host. Lo único que no tenemos es
14# el gestor de arranque de la rama BIOS (ver `vigia-ensamblado.sh`).
15#
16# ── EL ARTEFACTO SE ELIGE POR HASH, NO POR FECHA ─────────────────────────────────────────────────
17# `iso-image.sh` resolvía `xorriso` con `ls store/*-xorriso/...|head -1` y `install-image-efi.sh`
18# mtools con `ls -dt ...|head -1`. Con dos artefactos del mismo paquete conviviendo —lo normal tras
19# cualquier re-hash— el más reciente puede ser el viejo: **la fecha miente; el hash no**. Es la misma
20# lección que `hydrate-profile.py` y `libs-compartidas-desde-store.sh` ya habían pagado.
21#
22# ── EL HOST ES CAÍDA, NO FUENTE ─────────────────────────────────────────────────────────────────
23# Si no está en el store se cae al host, pero **avisando**: un binario del host no tiene hash, así que
24# la imagen deja de ser reproducible por ese lado y eso tiene que verse en el log de la corrida, no
25# descubrirse después. Si no está en ninguno de los dos, se ABORTA con las dos rutas nombradas.
26#
27# Uso:  . scripts/lib/herramientas-desde-store.sh
28#       SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1
29#       CPIO=$(herramienta libarchive usr/bin/bsdcpio cpio)  || exit 1
30# Vars: STORE_ART  (dónde buscar; por defecto se detecta) · TAKANA (binario; por defecto el del árbol)
31
32_herr_store() {
33    # Se detecta UNA vez y se dice cuál se eligió: en el hub el `./store` del clon está vacío y el
34    # store de verdad es `/store`, así que un default mudo manda a diagnosticar al lugar equivocado.
35    [ -n "${STORE_ART:-}" ] && { echo "$STORE_ART"; return; }
36    [ -n "${HAMMER_STORE:-}" ] && { echo "$HAMMER_STORE"; return; }
37    for c in store /store; do
38        if [ -d "$c" ] && [ -n "$(find "$c" -maxdepth 1 -name '*-*' -print -quit 2>/dev/null)" ]; then
39            echo "$c"; return
40        fi
41    done
42    echo store
43}
44
45_herr_takana() {
46    for c in "${TAKANA:-}" ./target/release/takana /usr/bin/takana; do
47        [ -n "$c" ] && [ -x "$c" ] && { echo "$c"; return; }
48    done
49    command -v takana 2>/dev/null || echo ./target/release/takana
50}
51
52# herramienta <receta> <ruta-relativa-en-el-artefacto> [nombre-en-el-host]
53herramienta() {
54    _hr_receta="$1"; _hr_rel="$2"; _hr_host="${3:-}"
55    # ── Override explícito: `TOOL_<binario>=/ruta` ────────────────────────────────────────────────
56    # Existe para los entornos donde NO HAY STORE y no puede haberlo: dentro del initramfs de un
57    # instalador, por ejemplo, que lleva los binarios adentro porque el disco todavía no está
58    # formateado. Ahí `herramienta` no puede calcular un hash ni leer el store, y caer al host es
59    # caer a un host que tampoco existe. El override es explícito y se anuncia igual que el resto:
60    # quien lo pone sabe lo que hace, y el log lo dice para que no se confunda con un sellado.
61    _hr_bin="${_hr_rel##*/}"
62    eval "_hr_ovr=\${TOOL_$(printf '%s' "$_hr_bin" | tr -c 'A-Za-z0-9_' '_'):-}"
63    if [ -n "${_hr_ovr:-}" ] && [ -x "$_hr_ovr" ]; then
64        echo "   · $_hr_bin ← $_hr_ovr (TOOL_ explícito, sin store)" >&2
65        echo "$_hr_ovr"; return 0
66    fi
67    _hr_store="$(_herr_store)"; _hr_tk="$(_herr_takana)"
68    _hr_hash=$("$_hr_tk" --store "$_hr_store" hash "recipes/$_hr_receta.toml" 2>/dev/null \
69               | tail -1 | sed 's/^b3://')
70    if [ -n "$_hr_hash" ]; then
71        _hr_p="$_hr_store/$_hr_hash-$_hr_receta/$_hr_rel"
72        if [ -x "$_hr_p" ]; then
73            echo "   · ${_hr_rel##*/} ← $_hr_receta $(echo "$_hr_hash" | cut -c1-12) (sellado)" >&2
74            echo "$_hr_p"; return 0
75        fi
76    fi
77    if [ -n "$_hr_host" ] && command -v "$_hr_host" >/dev/null 2>&1; then
78        # El aviso se lee también donde no se está armando ninguna imagen —`takana metal survey`
79        # en una máquina ajena cae acá siempre, porque ahí NO HAY store y no puede haberlo—, así
80        # que dice qué pasó y no qué significa: «sin hash» vale en los dos casos; «la imagen no es
81        # reproducible» sonaba a error para el que sólo está mirando el disco.
82        echo "   ⚠ ${_hr_rel##*/} ← $(command -v "$_hr_host") del HOST, sin hash (si estás armando una imagen, por acá deja de ser reproducible)" >&2
83        command -v "$_hr_host"; return 0
84    fi
85    echo "   ✗ falta ${_hr_rel##*/}: ni sellado ($_hr_store/$_hr_hash-$_hr_receta/$_hr_rel) ni en el PATH ($_hr_host)" >&2
86    echo "     construilo con: flock work/.farm-build.lock ./target/release/takana --store $_hr_store build recipes/$_hr_receta.toml" >&2
87    return 1
88}
89
90# ── sonda_hardlink <dir-origen> <dir-destino> ───────────────────────────────────────────────────
91# El staging de una imagen ENLAZA (hardlink) desde el rootfs, y `linkat()` no cruza un punto de
92# montaje aunque sea el mismo disco ([[hardlink-cruza-mount-exdev]]). Sin sonda, el fallo llega como
93# una PARED de `cp: Invalid cross-device link` fichero por fichero —cientos de líneas— y el diagnóstico
94# real («el STAGE está en otro montaje que el ROOTFS») no aparece en ninguna de ellas. Medido el
95# 2026-09-22 armando una imagen EFI con el rootfs en /store y el stage en /work.
96#
97# La sonda es FUNCIONAL —se intenta un enlace de verdad— y no una comparación de `st_dev`: en este
98# repo `store` y `work/out` son bind-mounts del MISMO dispositivo y `st_dev` coincide, así que
99# comparar números no dispara. Es la misma sonda que `hydrate-profile.py` ya tenía.
100sonda_hardlink() {
101    _sh_src="$1"; _sh_dst="$2"
102    mkdir -p "$_sh_dst" 2>/dev/null || true
103    _sh_a="$_sh_src/.sonda-hardlink.$$"; _sh_b="$_sh_dst/.sonda-hardlink.$$"
104    if ! : > "$_sh_a" 2>/dev/null; then
105        # origen de sólo lectura (un artefacto del store): se prueba con un fichero que YA está
106        _sh_a=$(find "$_sh_src" -maxdepth 2 -type f -print -quit 2>/dev/null)
107        [ -n "$_sh_a" ] || { echo "sonda_hardlink: $_sh_src no tiene ningún fichero con el que probar" >&2; return 1; }
108        _sh_limpiar=no
109    else
110        _sh_limpiar=si
111    fi
112    if ln "$_sh_a" "$_sh_b" 2>/dev/null; then
113        rm -f "$_sh_b"; [ "$_sh_limpiar" = si ] && rm -f "$_sh_a"
114        return 0
115    fi
116    [ "$_sh_limpiar" = si ] && rm -f "$_sh_a"
117    echo "   ✗ EXDEV: no se puede ENLAZAR de '$_sh_src' a '$_sh_dst'." >&2
118    echo "     El staging usa hardlinks y un enlace no cruza un montaje aunque sea el mismo disco." >&2
119    echo "     origen  está en: $(findmnt -n -T "$_sh_src" -o TARGET 2>/dev/null || echo '?')" >&2
120    echo "     destino está en: $(findmnt -n -T "$_sh_dst" -o TARGET 2>/dev/null || echo '?')" >&2
121    echo "     Poné STAGE= (y IMG=) bajo el MISMO montaje que el rootfs." >&2
122    return 1
123}