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 926Rust 57Shell 37Markdown 331282 Definitions

⎇ 254c5a9cc77e81424057df7c6b12e7abd80423ea scripts/farm/harvest-go.sh Shell · 150 lines · 8.0 KB History raw

Etapa G: ronda 2 pre-siembra finde (+18 Go) + alias gate argo-rollouts/step-cli 254c5a9cc · sergio ·

1#!/bin/sh
2# harvest-go.sh — COSECHA-ONLY automática del frente Go (modo finde desatendido). Determinista, sin
3# IA: pensado para un cron en el laptop (el hub). NO elige categorías ni diagnostica fallos; sólo
4# convierte en release firmado lo que el worker YA selló y que PASA un smoke-test del binario.
5#
6# Ciclo (idempotente, seguro de re-correr):
7#   1. git pull --rebase (sincroniza con el otro agente que pushea al mismo main)
8#   2. baja el store sellado del worker (rsync)
9#   3. por cada receta en recipes/incoming-go/: ¿está sellada? (hammer build con `timeout` ⇒ cache-hit
10#      instantáneo = sí; si se pasa del timeout sería un build local ⇒ se SALTA, no compilamos en el hub)
11#   4. SMOKE-TEST del binario instalado: existe + ELF estático + corre version/--help sin panic/segfault.
12#      - PASA  ⇒ promueve (mv recipes/, pack --build) — se firma+commitea al final.
13#      - FALLA ⇒ mueve a tandas/needs-review-weekend/ con el motivo (no reintenta cada cosecha).
14#   5. si promovió algo: firma el repo 1 vez + git add EXPLÍCITO (nunca -A: respeta al otro agente) +
15#      commit + push (con pull --rebase de reintento).
16#   Todo se loguea a work/harvest-go.log para revisar el lunes.
17#
18# Uso (cron):  */DohubVPS  cd /home/sergio/hammer && scripts/farm/harvest-go.sh root@1.2.3.4 >>work/harvest-go.log 2>&1
19# Env: SSH_KEY (def ~/.ssh/github5), REMOTE (def /opt/hammer), STORE (def ./store), REPO (def dist/repo),
20#      KEY (def dist/keys/release.ed25519), DISTRO (def dev), HAMMER (def target/release/hammer),
21#      SEAL_TIMEOUT (def 60s: techo del cache-hit; más = sería build local ⇒ skip).
22set -u
23
24VPS="${1:?uso: harvest-go.sh user@host}"
25ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
26SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
27REMOTE="${REMOTE:-/opt/hammer}"
28STORE="${STORE:-$ROOT/store}"
29REPO="${REPO:-$ROOT/dist/repo}"
30KEY="${KEY:-$ROOT/dist/keys/release.ed25519}"
31DISTRO="${DISTRO:-dev}"
32HAMMER="${HAMMER:-$ROOT/target/release/hammer}"
33SEAL_TIMEOUT="${SEAL_TIMEOUT:-60}"
34QUEUE="recipes/incoming-go"
35REVIEW="tandas/needs-review-weekend"
36SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o ConnectTimeout=20"
37# git push/pull a gitea (origin) usa la MISMA clave que el farm (github5 = clave "tawasuyu"). Fijarla
38# explícita para que el cron funcione sin ssh-agent.
39export GIT_SSH_COMMAND="ssh -i $SSH_KEY -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"
40STAMP() { date -u +%FT%TZ; }
41
42# Lock: una sola cosecha a la vez (el cron no se solapa).
43LOCK="$ROOT/work/.harvest-go.lock"
44mkdir -p "$ROOT/work"
45if ! mkdir "$LOCK" 2>/dev/null; then
46  echo "$(STAMP) harvest-go: ya hay una cosecha en curso ($LOCK) ⇒ salgo"; exit 0
47fi
48trap 'rmdir "$LOCK" 2>/dev/null' EXIT INT TERM
49
50echo "=================================================================="
51echo "$(STAMP) harvest-go: arranco (VPS=$VPS)"
52
53# 1. sincronizar con el otro agente (mismo main). --autostash por si quedó algo sin commitear.
54git pull --rebase --autostash origin main 2>&1 | tail -2 || echo "$(STAMP) WARN: git pull falló (sigo)"
55
56# 2. bajar el store sellado del worker.
57echo "$(STAMP) bajando store del worker…"
58rsync -az -e "$SSH" "$VPS:$REMOTE/store/" "$STORE/" 2>&1 | tail -1 || { echo "$(STAMP) ERROR: rsync store falló"; exit 1; }
59
60mkdir -p "$REVIEW"
61promoted=""; rejected=""; skipped=""
62
63# 3+4. por cada receta de la cola aislada Go.
64for f in "$QUEUE"/*.toml; do
65  [ -e "$f" ] || { echo "$(STAMP) cola vacía"; break; }
66  n=$(basename "$f" .toml)
67
68  # Pre-check BARATO: ¿el worker selló algo para este nombre? (tras el rsync el store local lo tiene).
69  # Sin esto, `hammer build` de una receta NO sellada ARRANCARÍA un build local (vendor+compile) y lo
70  # mataría el timeout ⇒ CPU del hub desperdiciada cada cosecha. Si no hay seal `*-<n>`, salto ya.
71  ls "$STORE"/*-"$n" >/dev/null 2>&1 || { skipped="$skipped $n"; continue; }
72
73  # ¿sellada con el hash ACTUAL? cache-hit instantáneo imprime el hash; un build real (hash distinto,
74  # raro) se pasa del timeout ⇒ skip (no compilamos en el hub).
75  H=$(timeout "$SEAL_TIMEOUT" "$HAMMER" --store "$STORE" build "$f" 2>/dev/null | tail -1)
76  case "$H" in
77    b3:*) : ;;
78    *) skipped="$skipped $n"; continue ;;   # no sellada aún (o tardaría en compilar) ⇒ el worker sigue
79  esac
80
81  # NOMBRE ESPERADO del binario: el de la receta, o un alias conocido (pname nixpkgs ≠ binario).
82  case "$n" in
83    opentofu) want=tofu ;; minio-client) want=mc ;; natscli) want=nats ;; delve) want=dlv ;;
84    fluxcd) want=flux ;; chart-testing) want=ct ;; sealed-secrets|kubeseal) want=kubeseal ;;
85    prometheus-node-exporter|node_exporter) want=node_exporter ;;
86    prometheus-blackbox-exporter|blackbox_exporter) want=blackbox_exporter ;;
87    argo-rollouts) want=kubectl-argo-rollouts ;; step-cli) want=step ;;
88    *) want="$n" ;;
89  esac
90
91  # SMOKE-TEST: el seal debe instalar un binario cuyo nombre == $want. Si instaló otro (un helper:
92  # functional-test, protoc-gen-buf-lint, gosecutil…), detect_go_main eligió mal el main ⇒ NO es la
93  # herramienta correcta aunque "corra" → a REVIEW (no firmamos un binario equivocado). Lección eksctl.
94  d="$STORE/${H#b3:}-$n"
95  allbins=$(find "$d" -type f -path '*/bin/*' -perm -100 2>/dev/null)
96  bin=$(printf '%s\n' "$allbins" | while read -r b; do [ "$(basename "$b")" = "$want" ] && { echo "$b"; break; }; done)
97  if [ -z "$bin" ]; then
98    got=$(printf '%s\n' "$allbins" | sed 's#.*/##' | tr '\n' ',' | sed 's/,$//')
99    { echo "# REVIEW $(STAMP): binario instalado='$got' ≠ esperado='$want' (detect_go_main eligió otro main); fijar flags=[\"./cmd/$want\"]"; cat "$f"; } > "$REVIEW/$n.toml"
100    rm -f "$f"; rejected="$rejected $n(bin=$got)"; continue
101  fi
102  # ELF estático (los del lab son musl estáticos; uno dinámico es sospechoso pero no lo rechazo solo por eso).
103  isstatic=$(file "$bin" 2>/dev/null | grep -c 'statically linked')
104  # corre sin panic/segfault: probamos varios flags benignos bajo timeout.
105  ran_ok=0; out=""
106  for sub in version --version -v --help -h; do
107    out=$(timeout 10 "$bin" $sub 2>&1); rc=$?
108    # panic de Go / segfault ⇒ malo; rc 124 = timeout (se colgó, p.ej. abrió server) ⇒ probamos otro flag.
109    case "$out" in *panic:*|*"runtime error"*|*SIGSEGV*|*"signal SIGABRT"*) continue ;; esac
110    [ "$rc" = 124 ] && continue
111    ran_ok=1; break
112  done
113  if [ "$ran_ok" = 1 ]; then
114    bn=$(basename "$bin")
115    echo "$(STAMP) ✓ smoke $n (bin=$bn static=$isstatic): ${out%%
116*}"
117    mv "$f" "recipes/$n.toml"
118    if "$HAMMER" --store "$STORE" pack "recipes/$n.toml" --repo "$REPO" --distro-version "$DISTRO" --build >/dev/null 2>&1; then
119      promoted="$promoted $n"
120    else
121      echo "$(STAMP) ✗ pack falló para $n ⇒ a review"
122      mv "recipes/$n.toml" "$REVIEW/$n.toml"; rejected="$rejected $n(pack)"
123    fi
124  else
125    { echo "# REVIEW $(STAMP): smoke-test falló (panic/segfault/cuelga). bin=$(basename "$bin") última salida:"; echo "$out" | sed 's/^/#   /' | head -5; cat "$f"; } > "$REVIEW/$n.toml"
126    rm -f "$f"; rejected="$rejected $n(smoke)"
127  fi
128done
129
130echo "$(STAMP) promovidos:$promoted"
131echo "$(STAMP) a-review:$rejected"
132echo "$(STAMP) aún-no-sellados (worker sigue):$skipped"
133
134# 5. firmar + commit + push sólo si hubo promociones o movimientos a review.
135if [ -n "$promoted" ] || [ -n "$rejected" ]; then
136  [ -n "$promoted" ] && "$HAMMER" repo sign --repo "$REPO" --key "$KEY" >/dev/null 2>&1 && echo "$(STAMP) repo re-firmado"
137  # add EXPLÍCITO: jamás `git add -A` (otro agente comparte el working-tree).
138  git add "$QUEUE" "$REVIEW" 2>/dev/null
139  for n in $promoted; do git add "recipes/$n.toml" 2>/dev/null; done
140  if ! git diff --cached --quiet; then
141    git commit -q -m "Etapa G: cosecha-go finde (auto) — promovidos:$promoted | review:$rejected"
142    # push con reintento ante el push concurrente del otro agente.
143    for try in 1 2 3; do
144      git pull --rebase --autostash origin main >/dev/null 2>&1
145      if git push origin main >/dev/null 2>&1; then echo "$(STAMP) ✓ pusheado (intento $try)"; break; fi
146      echo "$(STAMP) push rechazado, reintento $try"; sleep 5
147    done
148  fi
149fi
150echo "$(STAMP) harvest-go: fin"