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 1477Shell 179Rust 106Markdown 953595 Sutichaykuna

⎇ e30ffbbf07685fc0c00ad1d6548a92e000d42051 scripts/lib/herramientas-desde-store.sh Shell · 107 siq'ikuna · 6.6 KB Ñawpa willay ch'uya

paso 4: el ensamblado de imagen toma sus herramientas del STORE — y estaba roto en las 3 máquinas e30ffbbf0 · 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    _hr_store="$(_herr_store)"; _hr_tk="$(_herr_takana)"
56    _hr_hash=$("$_hr_tk" --store "$_hr_store" hash "recipes/$_hr_receta.toml" 2>/dev/null \
57               | tail -1 | sed 's/^b3://')
58    if [ -n "$_hr_hash" ]; then
59        _hr_p="$_hr_store/$_hr_hash-$_hr_receta/$_hr_rel"
60        if [ -x "$_hr_p" ]; then
61            echo "   · ${_hr_rel##*/} ← $_hr_receta $(echo "$_hr_hash" | cut -c1-12) (sellado)" >&2
62            echo "$_hr_p"; return 0
63        fi
64    fi
65    if [ -n "$_hr_host" ] && command -v "$_hr_host" >/dev/null 2>&1; then
66        echo "   ⚠ ${_hr_rel##*/} ← $(command -v "$_hr_host") DEL HOST, sin hash: por acá la imagen no es reproducible" >&2
67        command -v "$_hr_host"; return 0
68    fi
69    echo "   ✗ falta ${_hr_rel##*/}: ni sellado ($_hr_store/$_hr_hash-$_hr_receta/$_hr_rel) ni en el PATH ($_hr_host)" >&2
70    echo "     construilo con: flock work/.farm-build.lock ./target/release/takana --store $_hr_store build recipes/$_hr_receta.toml" >&2
71    return 1
72}
73
74# ── sonda_hardlink <dir-origen> <dir-destino> ───────────────────────────────────────────────────
75# El staging de una imagen ENLAZA (hardlink) desde el rootfs, y `linkat()` no cruza un punto de
76# montaje aunque sea el mismo disco ([[hardlink-cruza-mount-exdev]]). Sin sonda, el fallo llega como
77# una PARED de `cp: Invalid cross-device link` fichero por fichero —cientos de líneas— y el diagnóstico
78# real («el STAGE está en otro montaje que el ROOTFS») no aparece en ninguna de ellas. Medido el
79# 2026-09-22 armando una imagen EFI con el rootfs en /store y el stage en /work.
80#
81# La sonda es FUNCIONAL —se intenta un enlace de verdad— y no una comparación de `st_dev`: en este
82# repo `store` y `work/out` son bind-mounts del MISMO dispositivo y `st_dev` coincide, así que
83# comparar números no dispara. Es la misma sonda que `hydrate-profile.py` ya tenía.
84sonda_hardlink() {
85    _sh_src="$1"; _sh_dst="$2"
86    mkdir -p "$_sh_dst" 2>/dev/null || true
87    _sh_a="$_sh_src/.sonda-hardlink.$$"; _sh_b="$_sh_dst/.sonda-hardlink.$$"
88    if ! : > "$_sh_a" 2>/dev/null; then
89        # origen de sólo lectura (un artefacto del store): se prueba con un fichero que YA está
90        _sh_a=$(find "$_sh_src" -maxdepth 2 -type f -print -quit 2>/dev/null)
91        [ -n "$_sh_a" ] || { echo "sonda_hardlink: $_sh_src no tiene ningún fichero con el que probar" >&2; return 1; }
92        _sh_limpiar=no
93    else
94        _sh_limpiar=si
95    fi
96    if ln "$_sh_a" "$_sh_b" 2>/dev/null; then
97        rm -f "$_sh_b"; [ "$_sh_limpiar" = si ] && rm -f "$_sh_a"
98        return 0
99    fi
100    [ "$_sh_limpiar" = si ] && rm -f "$_sh_a"
101    echo "   ✗ EXDEV: no se puede ENLAZAR de '$_sh_src' a '$_sh_dst'." >&2
102    echo "     El staging usa hardlinks y un enlace no cruza un montaje aunque sea el mismo disco." >&2
103    echo "     origen  está en: $(findmnt -n -T "$_sh_src" -o TARGET 2>/dev/null || echo '?')" >&2
104    echo "     destino está en: $(findmnt -n -T "$_sh_dst" -o TARGET 2>/dev/null || echo '?')" >&2
105    echo "     Poné STAGE= (y IMG=) bajo el MISMO montaje que el rootfs." >&2
106    return 1
107}