#!/usr/bin/env bash # estado-granja.sh — foto RÁPIDA de la granja, on-demand (no depende del cron). Corre cuando quieras # ver dónde va sin esperar al latido: salud del worker, qué muele, avance KDE, y si el cron corrió. # scripts/farm/estado-granja.sh set -uo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT" SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}" FLEET="$ROOT/scripts/farm/.fleet" SSH="ssh -i $SSH_KEY -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o BatchMode=yes -o LogLevel=ERROR -o ConnectTimeout=12" echo "══ WORKERS ($(date '+%H:%M:%S')) ══" if [ -s "$FLEET" ]; then while read -r name ip; do [ -n "${name:-}" ] || continue echo "▶ $name ($ip)" $SSH "root@$ip" ' echo " $(uptime | sed "s/.*load average/load/")" n=$(ls /opt/takana/store 2>/dev/null | wc -l); echo " store: $n artefactos" # ⚠ LOS DOS NOMBRES. El renombre hammer→takana (ADR 0016) dejó esta sonda ciega: el worker # invoca `./target/release/takana` y acá se buscaba `release/hammer`, así que «moliendo» # decía «(nada — idle)» CON EL WORKER CONSTRUYENDO. Comprobado el 2026-09-14: el informe # decía idle mientras allá corría `takana --store ./store build recipes/centrifugo.toml`. # Un informe que miente sobre si hay trabajo es peor que no tenerlo — leyendo ese «idle» se # concluye que la granja está seca. El binario `hammer` sigue existiendo (cargo lo compila # como alias), así que se aceptan los dos. b=$(pgrep -af "release/(takana|hammer)" | grep -E "release/(takana|hammer) (build|--store|hash)" | grep -oE "[^ /]+\.toml" | sort -u | tr "\n" " ") echo " moliendo: ${b:-(nada — idle o entre colas)}" ' 2>/dev/null || echo " ⚠ sin respuesta (¿dead-man lo mató? ¿aún arrancando?)" done < "$FLEET" else echo " flota vacía (scripts/farm/.fleet) — no hay worker vivo" fi echo; echo "══ AVANCE KDE (grafo local) ══" if [ -f docs/state/build-state-kde.json ]; then python3 - <<'PY' import json,os,time f="docs/state/build-state-kde.json" d=json.load(open(f)); q=d["by_queue"].get("incoming-kde",{}) tot=sum(q.values()); s=q.get("sealed",0) age=int(time.time()-os.path.getmtime(f)) bar=int(30*s/tot) if tot else 0 print(f" [{'#'*bar}{'.'*(30-bar)}] {s}/{tot} sellado " f"(deuda {q.get('debt',0)}, nunca {q.get('never',0)})") print(f" grafo regenerado hace {age//60}m {age%60}s (lo actualiza el cron o `scripts/build-state.py --kde`)") PY else echo " (sin grafo — corré scripts/build-state.py --kde)" fi echo; echo "══ LATIDO DE COSECHA ══" # Se comprueba el EFECTO, no el mecanismo. Antes esto miraba `crontab -l` y decía «⚠ NO instalado» # — cierto y RUIDOSO a la vez: desde el 2026-09-18 el latido lo sostiene el cron de root del # ANFITRIÓN, y un agente enjaulado no ve ese crontab ni lo verá nunca. Un aviso que salta siempre es # un aviso que nadie lee. Lo que sí prueba que el latido vive son sus commits, que están acá. ult=$(git log -1 --format=%ct --grep="cosecha granja" 2>/dev/null) if [ -n "$ult" ]; then edad=$(( $(date +%s) - ult )) printf ' último ciclo: hace %dm %ds — %s\n' $((edad/60)) $((edad%60)) \ "$(git log -1 --format=%s --grep='cosecha granja' 2>/dev/null)" # dispara en :00 y :30 ⇒ más de 40 min sin commit es un latido perdido, no una pausa if [ "$edad" -gt 2400 ]; then echo " ⚠ hace más de 40m que no commitea — el latido dispara en :00 y :30" else echo " ✓ al día" fi else echo " ⚠ no hay ningún commit de cosecha en el historial" fi if crontab -l 2>/dev/null | grep -q '[c]osecha-cron'; then echo " (y además está en el crontab de este usuario)" fi if [ -f work/cosecha-cron.log ]; then echo " último ciclo (tail):" grep -E 'cosecha-cron (arranca|fin)|siembra|cosecha ✓|estado commit' work/cosecha-cron.log | tail -6 | sed 's/^/ /' fi