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 1430Shell 145Markdown 87Rust 852973 Sutichaykuna

⎇ 534014c3ec69daea789a1d0523e1d00a69c6ed65 scripts/farm/traer-artefactos.sh Shell · 98 siq'ikuna · 5.8 KB Ñawpa willay ch'uya

fix(granja): el límite de fds vivía en UN llamador, y la campaña manual no lo tenía 24fa7ec83 · Sergio ·

1#!/bin/sh
2# traer-artefactos.sh — trae del worker los artefactos VIGENTES que al hub le faltan, por receta.
3#
4# ══ POR QUÉ EXISTE ═════════════════════════════════════════════════════════════════════════════
5# La cosecha **ya no baja el store** (es lo que evita que el disco del hub se llene), así que lo que
6# el worker sella se queda allá hasta que alguien lo pida. Y el hub SÍ lo necesita: es donde se
7# arman las imágenes y donde `cierre-perfiles.py` mide la clausura.
8#
9# Lo que había era todo o nada: `farm-sync.sh` baja el store ENTERO y `takana mirror pull` quiere una
10# RUTA local (no sabe de ssh). Traer tres artefactos era copiar a mano un `rsync` con cuatro
11# precauciones que se olvidan — y las olvidé una vez cada una antes de escribir esto.
12#
13# ══ LAS CUATRO PRECAUCIONES, TODAS APRENDIDAS A GOLPES ═════════════════════════════════════════
14#   1. **`-H`**, que `-a` NO incluye. El store es content-addressed y `.dmerge` hardlinkea: sin
15#      `-H` cada enlace viaja y se escribe como copia entera, y el tamaño del destino no se parece
16#      al que informa `du` en el origen (medido: 25 G de origen llenaron 95 G de destino).
17#   2. **Excluir `.dmerge`**: es caché pura que takana reconstruye sola. Copiarla no aporta un
18#      artefacto y es la parte más pesada del árbol.
19#   3. **Aterrizar por `.incoming-*` y después `mv`.** Un directorio de artefacto a medias es
20#      EXACTAMENTE un cache-hit envenenado (regla 3 del CLAUDE.md): `build` mira presencia, así que
21#      un árbol vacío o truncado se da por bueno para siempre. El `mv` dentro del mismo filesystem
22#      es atómico.
23#   4. **No pisar lo que ya está.** `mv A B` con B existente lo ANIDA (B/A), y entonces el
24#      artefacto queda con un nivel de más: presente, con contenido, y mintiendo.
25#
26# ══ QUÉ CUENTA COMO VIGENTE ════════════════════════════════════════════════════════════════════
27# El hash lo calcula `takana hash` sobre la receta DEL HUB, que es la fuente de verdad del
28# contrato. No se lee el nombre del directorio del worker ni su manifiesto: si el worker selló otra
29# cosa, lo que queremos saber es justamente que no coincide, no copiarla.
30#
31# Uso:  scripts/farm/traer-artefactos.sh <receta|ruta.toml>...
32#       scripts/farm/traer-artefactos.sh --lista fichero-con-una-receta-por-linea
33# Env:  WORKER (def dev.gioser.net) · SSH_KEY (def ~/.ssh/id_ed25519) · STORE (def /store)
34#       REMOTE_STORE (def /opt/takana/store) · TAKANA (def ./target/release/takana)
35set -u
36ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
37WORKER="${WORKER:-dev.gioser.net}"
38SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519}"
39STORE="${STORE:-/store}"
40REMOTE_STORE="${REMOTE_STORE:-/opt/takana/store}"
41TAKANA="${TAKANA:-$ROOT/target/release/takana}"
42SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=20"
43
44[ -x "$TAKANA" ] || { echo "no encuentro el binario en $TAKANA" >&2; exit 1; }
45
46recetas=""
47if [ "${1:-}" = "--lista" ]; then
48  [ -r "${2:-}" ] || { echo "no puedo leer la lista ${2:-}" >&2; exit 2; }
49  recetas=$(grep -vE '^\s*(#|$)' "$2")
50else
51  [ $# -gt 0 ] || { sed -n '/^# Uso:/,/^# Env:/p' "$0" | sed 's/^# \{0,1\}//' >&2; exit 2; }
52  recetas="$*"
53fi
54
55# Resolver nombre → ruta de receta. Una receta puede vivir en el corpus o en una cola, y acá eso
56# importa: pedir `recipes/<nombre>.toml` para algo que está en `incoming-kde` sale
57# `io: No such file or directory`, que no se lee como «está en otro sitio». Lo aprendí perdiendo
58# seis builds de una tanda de 22.
59ruta_de() {
60  case "$1" in *.toml) [ -f "$1" ] && echo "$1"; return ;; esac
61  [ -f "recipes/$1.toml" ] && { echo "recipes/$1.toml"; return; }
62  for d in recipes/incoming-*; do
63    [ -f "$d/$1.toml" ] && { echo "$d/$1.toml"; return; }
64  done
65  return 1
66}
67
68ya=0; traidos=0; faltan=0; vacios=0
69for r in $recetas; do
70  f=$(ruta_de "$r") || { printf '%-26s ⚠ no encuentro la receta (ni en el corpus ni en las colas)\n' "$r"; faltan=$((faltan+1)); continue; }
71  n=$(sed -n 's/^name[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' "$f" | head -1)
72  [ -n "$n" ] || n=$(basename "$f" .toml)
73  h=$("$TAKANA" --store "$STORE" hash "$f" 2>/dev/null | tail -1 | sed 's/^b3://')
74  case "$h" in ''|*[!0-9a-f]*) printf '%-26s ⚠ no pude calcular el hash\n' "$r"; faltan=$((faltan+1)); continue ;; esac
75  par="$h-$n"
76  if [ -d "$STORE/$par" ]; then printf '%-26s ya está en el hub\n' "$n"; ya=$((ya+1)); continue; fi
77
78  tmp="$STORE/.incoming-$par"
79  rm -rf "$tmp"
80  if ! rsync -azH --exclude '/.dmerge' -e "$SSH" "root@$WORKER:$REMOTE_STORE/$par/" "$tmp/" 2>/dev/null; then
81    printf '%-26s ⚠ no está en el worker (o el rsync falló): %s\n' "$n" "$h"
82    rm -rf "$tmp"; faltan=$((faltan+1)); continue
83  fi
84  c=$(find "$tmp" ! -type d 2>/dev/null | wc -l)
85  if [ "$c" -eq 0 ]; then
86    printf '%-26s ✗ llegó VACÍO — no lo sello (sería un cache-hit envenenado)\n' "$n"
87    rm -rf "$tmp"; vacios=$((vacios+1)); continue
88  fi
89  mv "$tmp" "$STORE/$par"
90  printf '%-26s ✓ %s entradas · %s\n' "$n" "$c" "$(du -sh "$STORE/$par" 2>/dev/null | cut -f1)"
91  traidos=$((traidos+1))
92done
93
94echo "== ya estaban $ya · traídos $traidos · sin traer $faltan · vacíos $vacios"
95# Un vacío NO es lo mismo que un ausente y por eso se cuentan aparte: el ausente falla ruidosamente
96# la próxima vez que alguien lo pida; el vacío, si se hubiera sellado, se daría por bueno para
97# siempre. Sale ≠0 si algo quedó sin traer, para que un llamador en un guion se entere.
98[ "$faltan" -eq 0 ] && [ "$vacios" -eq 0 ]