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…
el saltito sin USB: kexec primero, espacio libre si la máquina no puede, y nunca desde adentro
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 echo " ⚠ ${_hr_rel##*/} ← $(command -v "$_hr_host") DEL HOST, sin hash: por acá la imagen no es reproducible" >&2
79 command -v "$_hr_host"; return 0
80 fi
81 echo " ✗ falta ${_hr_rel##*/}: ni sellado ($_hr_store/$_hr_hash-$_hr_receta/$_hr_rel) ni en el PATH ($_hr_host)" >&2
82 echo " construilo con: flock work/.farm-build.lock ./target/release/takana --store $_hr_store build recipes/$_hr_receta.toml" >&2
83 return 1
84}
85
86# ── sonda_hardlink <dir-origen> <dir-destino> ───────────────────────────────────────────────────
87# El staging de una imagen ENLAZA (hardlink) desde el rootfs, y `linkat()` no cruza un punto de
88# montaje aunque sea el mismo disco ([[hardlink-cruza-mount-exdev]]). Sin sonda, el fallo llega como
89# una PARED de `cp: Invalid cross-device link` fichero por fichero —cientos de líneas— y el diagnóstico
90# real («el STAGE está en otro montaje que el ROOTFS») no aparece en ninguna de ellas. Medido el
91# 2026-09-22 armando una imagen EFI con el rootfs en /store y el stage en /work.
92#
93# La sonda es FUNCIONAL —se intenta un enlace de verdad— y no una comparación de `st_dev`: en este
94# repo `store` y `work/out` son bind-mounts del MISMO dispositivo y `st_dev` coincide, así que
95# comparar números no dispara. Es la misma sonda que `hydrate-profile.py` ya tenía.
96sonda_hardlink() {
97 _sh_src="$1"; _sh_dst="$2"
98 mkdir -p "$_sh_dst" 2>/dev/null || true
99 _sh_a="$_sh_src/.sonda-hardlink.$$"; _sh_b="$_sh_dst/.sonda-hardlink.$$"
100 if ! : > "$_sh_a" 2>/dev/null; then
101 # origen de sólo lectura (un artefacto del store): se prueba con un fichero que YA está
102 _sh_a=$(find "$_sh_src" -maxdepth 2 -type f -print -quit 2>/dev/null)
103 [ -n "$_sh_a" ] || { echo "sonda_hardlink: $_sh_src no tiene ningún fichero con el que probar" >&2; return 1; }
104 _sh_limpiar=no
105 else
106 _sh_limpiar=si
107 fi
108 if ln "$_sh_a" "$_sh_b" 2>/dev/null; then
109 rm -f "$_sh_b"; [ "$_sh_limpiar" = si ] && rm -f "$_sh_a"
110 return 0
111 fi
112 [ "$_sh_limpiar" = si ] && rm -f "$_sh_a"
113 echo " ✗ EXDEV: no se puede ENLAZAR de '$_sh_src' a '$_sh_dst'." >&2
114 echo " El staging usa hardlinks y un enlace no cruza un montaje aunque sea el mismo disco." >&2
115 echo " origen está en: $(findmnt -n -T "$_sh_src" -o TARGET 2>/dev/null || echo '?')" >&2
116 echo " destino está en: $(findmnt -n -T "$_sh_dst" -o TARGET 2>/dev/null || echo '?')" >&2
117 echo " Poné STAGE= (y IMG=) bajo el MISMO montaje que el rootfs." >&2
118 return 1
119}