granja: farm-run.sh — el ciclo efímero desatendido (up→esperar-ciclo→cosecha+destruye)
Encadena farm-up + farm-down con detección de fin-de-ciclo por el marcador 'ciclo terminado' del worker-loop (cuenta ocurrencias en el journal, robusto a skew de reloj y builds largos). 'Subí la cola, corré esto, se apaga solo al terminar.' Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
1 archivos · +67 −0
- scripts/farm/farm-run.sh +67 −0
diff --git a/scripts/farm/farm-run.sh b/scripts/farm/farm-run.sh
new file mode 100755
index 00000000..ca9d1dfb
--- /dev/null
+++ b/scripts/farm/farm-run.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+# farm-run.sh [N] — el ciclo EFÍMERO de una tanda, de punta a punta y DESATENDIDO: levanta N
+# workers desde el snapshot golden, los alinea con la cola actual del laptop, espera a que MUELAN
+# la cola entera (un ciclo completo del worker-loop), y en cuanto terminan COSECHA su store y los
+# DESTRUYE. "Subí la cola, corré esto, andá a hacer otra cosa; se apaga solo cuando termina."
+#
+# Es el pegamento up→esperar→down: farm-up.sh + farm-down.sh existen sueltos (control manual/parcial);
+# farm-run.sh los encadena con la detección de "ya no queda nada nuevo por construir".
+#
+# Detección de "terminó": el worker-loop (farm-worker-loop.sh) imprime "ciclo terminado" al journal
+# tras construir TODAS las colas una vez. Contamos esas líneas ANTES de arrancar (baseline, persisten
+# en el journal) y esperamos a que el contador suba: +1 ⇒ el worker intentó la cola entera una vez
+# (lo que se pudo sellar, sellado; lo que falla, falla y se reintentaría en vano). Robusto a skew de
+# reloj (contamos ocurrencias, no comparamos timestamps) y a builds largos (no hay falso-positivo por
+# "store estable": sólo el marcador de fin-de-ciclo cuenta). Con varios workers, espera a TODOS.
+#
+# Uso: scripts/farm/farm-run.sh # 1 worker, cola actual, cosecha+destruye+firma al terminar
+# scripts/farm/farm-run.sh 3 # 3 workers en paralelo sobre la misma cola
+# Env: POLL (def 60s entre sondeos), MAXWAIT (def 21600s=6h tope duro), + los de farm-up/down
+# (IMAGE/TYPE/LOCATION/SSHKEY/SSH_KEY/REMOTE/NO_PROMOTE).
+set -euo pipefail
+
+N="${1:-1}"
+POLL="${POLL:-60}"
+MAXWAIT="${MAXWAIT:-21600}"
+SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+FLEET="$ROOT/scripts/farm/.fleet"
+SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10"
+cd "$ROOT"
+
+cycles() { $SSH "root@$1" 'journalctl -u hammer-farm -o cat --no-pager 2>/dev/null | grep -c "ciclo terminado"' 2>/dev/null || echo 0; }
+
+echo "==> [farm-run] levantando $N worker(s) y alineando la cola…"
+scripts/farm/farm-up.sh "$N"
+
+# Baseline por worker de la flota recién levantada (los que YA existían también entran: esperamos
+# a que cada uno complete un ciclo nuevo desde el restart que hizo farm-up).
+declare -A base
+while read -r name ip; do
+ [ -n "${name:-}" ] || continue
+ base["$name|$ip"]=$(cycles "$ip")
+done < "$FLEET"
+
+echo "==> [farm-run] esperando a que la flota muela la cola (POLL=${POLL}s, tope ${MAXWAIT}s)…"
+waited=0
+while :; do
+ pending=0
+ while read -r name ip; do
+ [ -n "${name:-}" ] || continue
+ b=${base["$name|$ip"]:-0}
+ c=$(cycles "$ip")
+ if [ "$c" -le "$b" ]; then
+ pending=$((pending + 1))
+ fi
+ done < "$FLEET"
+ [ "$pending" -eq 0 ] && { echo "==> [farm-run] la flota completó un ciclo — cola molida."; break; }
+ if [ "$waited" -ge "$MAXWAIT" ]; then
+ echo " ⚠ [farm-run] tope de espera (${MAXWAIT}s) alcanzado con $pending worker(s) sin cerrar ciclo — cosecho igual lo que haya."
+ break
+ fi
+ sleep "$POLL"; waited=$((waited + POLL))
+done
+
+echo "==> [farm-run] cosechando + destruyendo + firmando…"
+scripts/farm/farm-down.sh
+echo "==> [farm-run] listo. Baseline €0 (sin cajas vivas de la tanda)."