tawasuyu

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 1572Shell 203Rust 109Markdown 993728 Definiciones

⎇ main scripts/farm/sembrar-fuente.sh Shell · 101 líneas · 5.5 KB Historia crudo

farm: sembrar-fuente.sh — el worker construye lo de tawasuyu SIN tener la clave 79a9ac5bd · Sergio ·

1#!/usr/bin/env bash
2# sembrar-fuente.sh — lleva al worker el ÁRBOL DE FUENTES de una receta que él no puede clonar.
3#
4# ── POR QUÉ EXISTE (medido 2026-09-14) ──────────────────────────────────────────────────────────
5# El worker no tiene `~/.ssh/tawasuyu`, así que cualquier receta cuyo `[source]` apunte al gitea de
6# tawasuyu muere allá con `Please make sure you have the correct access rights`. Y **no debe
7# tenerla**: esa clave es el SSH de todo; el worker sólo necesita LEER un repo. Lo que viaja acá es
8# el árbol ya clonado, no la credencial.
9#
10# ⚠ Y NO ALCANZA CON COPIAR EL MIRROR: el fetch de takana clona con `--filter=blob:none`, así que un
11# mirror copiado tal cual **no tiene los blobs** y los va a buscar al remoto — que allá no responde.
12# El síntoma no nombra la causa:
13#
14#     tar: This does not look like a tar archive
15#     Error: git archive | tar -x falló (tar exit Some(2))
16#
17# Por eso este guión HIDRATA el mirror (`fetch --refetch --no-filter`) antes de mandarlo, y verifica
18# del otro lado con la prueba del CONSUMIDOR —`git archive` de verdad—, no con `cat-file -e`, que
19# pasa igual sobre un mirror parcial.
20#
21# Uso:   scripts/farm/sembrar-fuente.sh <receta.toml> [--host root@host] [--remote /opt/takana]
22set -euo pipefail
23ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
24
25RECETA="${1:?uso: sembrar-fuente.sh <receta.toml> [--host user@host] [--remote /opt/takana]}"; shift || true
26HOST=""; REMOTE="${REMOTE:-/opt/takana}"; SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
27while [ $# -gt 0 ]; do
28  case "$1" in
29    --host)   HOST="$2"; shift;;
30    --remote) REMOTE="$2"; shift;;
31    *) echo "opción desconocida: $1" >&2; exit 2;;
32  esac; shift
33done
34# Sin `--host`: el primero de la flota. `.fleet` es `<nombre> <ip>` por línea y está GITIGNORED —
35# un hub recién clonado la tiene vacía y la granja queda desconectada sin que nada falle.
36if [ -z "$HOST" ]; then
37  ip=$(awk 'NF{print $2; exit}' scripts/farm/.fleet 2>/dev/null || true)
38  [ -n "$ip" ] || { echo "!! no hay --host y scripts/farm/.fleet está vacía o no existe" >&2; exit 2; }
39  HOST="root@$ip"
40fi
41SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o BatchMode=yes"
42
43[ -r "$RECETA" ] || { echo "!! no puedo leer $RECETA" >&2; exit 2; }
44eval "$(python3 - "$RECETA" <<'PY'
45import sys, tomllib
46d = tomllib.load(open(sys.argv[1], "rb"))
47src = d.get("source", {})
48print(f'NOMBRE={d.get("name", "")!r}'.replace("'", '"'))
49print(f'REPO={src.get("repo", "")!r}'.replace("'", '"'))
50print(f'COMMIT={src.get("commit", "")!r}'.replace("'", '"'))
51PY
52)"
53[ -n "$REPO" ] && [ -n "$COMMIT" ] || { echo "!! $RECETA no es una receta git (necesita source.repo + source.commit)" >&2; exit 2; }
54
55MIRROR="work/repos/$NOMBRE.git"
56echo "==> receta : $NOMBRE"
57echo "==> repo   : $REPO"
58echo "==> commit : ${COMMIT:0:12}"
59
60# 1. el mirror en el HUB, que es quien tiene la credencial
61if [ ! -d "$MIRROR" ]; then
62  echo "==> clonando en el hub (mirror completo, con blobs)"
63  git clone --mirror "$REPO" "$MIRROR"
64else
65  echo "==> mirror ya existe en el hub"
66fi
67# Hidratar: si viene de un fetch de takana es PARCIAL. Es idempotente y barato cuando ya está lleno.
68if git -C "$MIRROR" config --get remote.origin.partialclonefilter >/dev/null 2>&1; then
69  echo "==> el mirror es PARCIAL (blob:none) ⇒ hidratando los blobs"
70  git -C "$MIRROR" fetch --refetch --no-filter
71  git -C "$MIRROR" config --unset remote.origin.partialclonefilter || true
72  git -C "$MIRROR" config --unset remote.origin.promisor || true
73fi
74git -C "$MIRROR" cat-file -e "$COMMIT" 2>/dev/null || {
75  echo "==> el commit no está ⇒ fetch"; git -C "$MIRROR" fetch --all --tags
76  git -C "$MIRROR" cat-file -e "$COMMIT" || { echo "!! el commit $COMMIT no existe en $REPO" >&2; exit 1; }
77}
78# La prueba del consumidor, EN EL HUB, antes de gastar la red: `git archive` es lo que corre el fetch
79# de takana, y es lo único que distingue un mirror completo de uno parcial.
80# (`wc -c`, no `grep -q .`: la salida es un tar BINARIO y puede no traer un salto de línea en los
81# primeros bytes — `grep` decía «vacío» sobre un archive perfectamente bueno.)
82# `set +o pipefail` acá y no fuera: `head -c` cierra el pipe y `git archive` muere con SIGPIPE, que
83# con pipefail hace fallar el pipeline ENTERO — el guión se cortaba en silencio justo donde dice
84# que verifica. Un verificador que aborta sin decir nada es peor que no verificar.
85bytes=$(set +o pipefail; git -C "$MIRROR" archive "$COMMIT" 2>/dev/null | head -c 512 | wc -c)
86[ "$bytes" -gt 0 ] || { echo "!! git archive vacío en el hub — el mirror no sirve" >&2; exit 1; }
87echo "==> mirror listo: $(du -sh "$MIRROR" | cut -f1)"
88
89# 2. al worker
90echo "==> enviando a $HOST:$REMOTE/$MIRROR"
91rsync -a --delete -e "$SSH" "$MIRROR/" "$HOST:$REMOTE/$MIRROR/"
92# `chown`: el rsync deja el dueño del hub y git rechaza el repo con «dubious ownership» cuando el
93# builder corre como root. Falla al construir, no al copiar.
94$SSH "$HOST" "chown -R root:root '$REMOTE/$MIRROR'"
95
96# 3. verificar ALLÁ con la prueba del consumidor, no con `cat-file`
97echo "==> verificando en el worker (git archive de verdad)"
98entradas=$(set +o pipefail; $SSH "$HOST" "cd '$REMOTE' && git -C '$MIRROR' archive '$COMMIT' 2>/dev/null | tar -t 2>/dev/null | head -3" || true)
99[ -n "$entradas" ] || { echo "!! el árbol NO se puede extraer en el worker" >&2; exit 1; }
100echo "$entradas" | sed 's/^/    /'
101echo "✓ $NOMBRE: el worker ya puede construirla sin credenciales"