granja: cron infinito de cosecha+siembra (recoger/sembrar cada 30min) — latido sin tokens
El worker efímero muele KDE 24/7; este cron en el laptop recoge su store sellado (worker->laptop, CAS merge seguro), siembra recetas nuevas (laptop->worker) y regenera el grafo de estado, commiteando SÓLO docs/state/*.json (el avance que el humano sigue). NO promueve incoming-kde->canónico (decisión de clasificación humana). Robusto a worker-ausente (dead-man ya lo mató por idle). KDE: 66/206 sellado. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+46
-210
File diff suppressed because it is too large
Load Diff
+860
-67
File diff suppressed because it is too large
Load Diff
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
# cosecha-cron.sh — el LATIDO de la granja: recoger + sembrar, en bucle, sin agente ni tokens.
|
||||
#
|
||||
# QUÉ HACE cada ciclo, por cada worker vivo en scripts/farm/.fleet:
|
||||
# 1. SIEMBRA (sube): rsync de código + recetas laptop→worker (el laptop es canónico; si autoré
|
||||
# recetas nuevas —p.ej. openrc— el worker las empieza a moler sin que yo intervenga).
|
||||
# 2. RECOGE (baja): rsync del store sellado worker→laptop (CAS ⇒ merge seguro, sin conflicto).
|
||||
# 3. Regenera el GRAFO DE ESTADO (build-state.json + build-state-kde.json) desde el store ya
|
||||
# cosechado, y commitea SÓLO esos ficheros firmes. El `git log` de docs/state/ ES el avance
|
||||
# que el humano ve y sigue (huecos, fallos, qué se selló entre dos ciclos).
|
||||
#
|
||||
# LO QUE **NO** HACE (a propósito): NO promueve incoming-kde→recipes/ canónico (esa es decisión de
|
||||
# clasificación humana — "el hub clasifica", ver granja-promote-colisiones) ni firma el índice. Sólo
|
||||
# captura al store del laptop y deja el veredicto para la mañana. Idempotente y re-corrible.
|
||||
#
|
||||
# INFINITO: vive en el crontab hasta que lo saques (`crontab -e`, borrar la línea cosecha-cron).
|
||||
# Robusto a worker-ausente: si el dead-man ya mató al worker (cola seca ⇒ €0), el paso 1/2 falla
|
||||
# suave, se loguea y igual regenera+commitea el estado local. NO relanza workers (eso gasta €;
|
||||
# es decisión explícita con farm-up).
|
||||
#
|
||||
# Uso: scripts/farm/cosecha-cron.sh # un ciclo (lo que dispara el cron)
|
||||
# */30 * * * * cd /home/sergio/hammer && scripts/farm/cosecha-cron.sh >>work/cosecha-cron.log 2>&1
|
||||
# Env: SSH_KEY (def ~/.ssh/github5), REMOTE (def /opt/hammer), NO_COMMIT=1 (regenera pero no commitea)
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
|
||||
REMOTE="${REMOTE:-/opt/hammer}"
|
||||
FLEET="$ROOT/scripts/farm/.fleet"
|
||||
HAMMER="${HAMMER:-$ROOT/target/release/hammer}"
|
||||
# Workers efímeros reciclan IPs ⇒ no fijar known_hosts (hub-and-spoke, worker sin secretos).
|
||||
SSH="ssh -i $SSH_KEY -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o BatchMode=yes -o LogLevel=ERROR -o ConnectTimeout=15"
|
||||
|
||||
ts() { date -u +%FT%TZ; }
|
||||
echo "── $(ts) cosecha-cron arranca"
|
||||
|
||||
if [ ! -s "$FLEET" ]; then
|
||||
echo " flota vacía (scripts/farm/.fleet) — nada que cosechar este ciclo"
|
||||
else
|
||||
while read -r name ip; do
|
||||
[ -n "${name:-}" ] || continue
|
||||
echo "==> $name ($ip)"
|
||||
# 1. SIEMBRA: código + recetas laptop→worker (--delete: el laptop manda; nunca toca work/store/target).
|
||||
if rsync -az --delete -e "$SSH" \
|
||||
--exclude /work --exclude /store --exclude '/store-*' --exclude /target \
|
||||
--exclude /dist --exclude /.dev-fs --exclude /.git --exclude /.scratch \
|
||||
--exclude '*.png' --exclude '/content*' \
|
||||
./ "root@$ip:$REMOTE/" 2>&1 | tail -2; then
|
||||
echo " siembra ✓"
|
||||
else
|
||||
echo " ⚠ siembra falló (worker ausente/idle-muerto?) — sigo con el siguiente"
|
||||
continue
|
||||
fi
|
||||
# 2. RECOGE: store sellado worker→laptop (CAS, merge seguro).
|
||||
if rsync -az -e "$SSH" "root@$ip:$REMOTE/store/" "$ROOT/store/" 2>&1 | tail -2; then
|
||||
echo " cosecha ✓"
|
||||
else
|
||||
echo " ⚠ cosecha falló — sigo"
|
||||
fi
|
||||
done < "$FLEET"
|
||||
fi
|
||||
|
||||
# 3. Regenerar el grafo de estado desde el store ya cosechado (usa el hammer del laptop, que tiene
|
||||
# el subcomando `hash`). Barato (~14s cada uno). El corpus canónico y el frente KDE, por separado.
|
||||
echo "==> regenerando grafo de estado"
|
||||
if [ -x "$HAMMER" ]; then
|
||||
scripts/build-state.py >/dev/null 2>&1 && echo " build-state.json ✓" || echo " ⚠ build-state.py falló"
|
||||
scripts/build-state.py --kde >/dev/null 2>&1 && echo " build-state-kde.json ✓" || echo " ⚠ build-state.py --kde falló"
|
||||
else
|
||||
echo " ⚠ sin binario hammer en $HAMMER — no regenero estado"
|
||||
fi
|
||||
|
||||
# 4. Commitear SÓLO el estado firme (el avance que el humano sigue). Recetas nuevas que YO autoré se
|
||||
# commitean aparte, a mano, para no meter autoría a medias en un commit de cron.
|
||||
if [ "${NO_COMMIT:-}" != "1" ]; then
|
||||
git add docs/state/build-state.json docs/state/build-state-kde.json 2>/dev/null || true
|
||||
if ! git diff --cached --quiet 2>/dev/null; then
|
||||
git commit -q -m "estado: cosecha granja $(ts) — avance del árbol KDE" 2>/dev/null \
|
||||
&& { git push -q origin main 2>/dev/null && echo "==> estado commiteado+pusheado" \
|
||||
|| echo "==> estado commiteado (push falló, reintenta próximo ciclo)"; }
|
||||
else
|
||||
echo "==> sin cambios de estado este ciclo"
|
||||
fi
|
||||
fi
|
||||
echo "── $(ts) cosecha-cron fin"
|
||||
Reference in New Issue
Block a user