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 Definitions

granja: reaper HUB-SIDE — la garantía "vps idle inadmisible" deja de depender del worker

INCIDENTE (2026-07-23): un worker quedó 3.5h idle quemando € mientras el usuario
dormía. El dead-man del worker estaba "activo" (timer disparando cada 10 min)
pero FALLABA con exit 1 en cada tick: llegaba a matarse pero su
/etc/hammer-deadman.env no tenía token válido ⇒ salía 1 "sin HCLOUD_TOKEN" y
nunca se borraba. Verificaron que el timer DISPARA, no que puede BORRAR —
firing ≠ can-delete.

Causa de fondo: la garantía dependía de que CADA worker se auto-provisione bien
un token, y eso falla EN SILENCIO. Un invariante que depende de una provisión
frágil no es un invariante.

FIX en dos capas (defensa en profundidad):
 1. REAPER HUB-SIDE (cosecha-cron.sh): el hub tiene un token que funciona y el
    latido corre cada 30 min aunque no haya sesión (setsid). Un worker sin trabajo
    activo REAPER_MAX=2 ciclos (~1h) se BORRA desde el hub, con el MISMO blindaje
    de label role=hammer-worker (gioser jamás pasa). Cubre "dead-man del worker
    roto". El dead-man del worker sigue cubriendo "hub caído".
 2. farm-up verifica CAN-DELETE, no sólo firing: que el token del worker vea su
    propio id en la API (lo mismo que hace al morir). Si no puede, LO GRITA al
    provisionar en vez de descubrirlo 3.5h tarde.

Acción inmediata tomada: worker borrado a mano (label verificado), €0 baseline
restaurado — sólo queda gioser (protegido, fijo). Todo estaba cosechado.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

1203f86cda553df7d1f31f362251ab5bd2bd28a0 · by sergio · (3 months ago) · parent ada3767e0 · browse the tree at this commit

Semantic delta

No definition changed: this change is text, documentation or data.

2 files · +48 −0

diff --git a/scripts/farm/cosecha-cron.sh b/scripts/farm/cosecha-cron.sh
index a1312c6a..16a1a95f 100755
--- a/scripts/farm/cosecha-cron.sh
+++ b/scripts/farm/cosecha-cron.sh
@@ -76,6 +76,43 @@ else
   done < "$FLEET"
 fi
 
+# 2.5 REAPER HUB-SIDE (2026-07-23). El dead-man DEL WORKER probó FRÁGIL: un worker quedó 3.5h idle
+# quemando € porque su `/etc/hammer-deadman.env` no tenía token válido ⇒ el dead-man llegaba a
+# matarse pero salía 1 "sin HCLOUD_TOKEN" cada tick. La garantía "un vps idle es inadmisible" NO
+# puede depender de que cada worker se auto-provisione bien un token (falla en silencio). El HUB sí
+# tiene un token que funciona, y este latido corre cada 30 min aunque no haya sesión (setsid) ⇒ el
+# hub es la AUTORIDAD de vida del worker. Es defensa en profundidad: el dead-man del worker cubre
+# "hub caído"; esto cubre "dead-man del worker roto". Un worker sin trabajo activo REAPER_MAX ciclos
+# seguidos se BORRA desde acá, con el MISMO blindaje que el dead-man (label role=hammer-worker;
+# gioser no tiene label ⇒ jamás pasa, ni por accidente).
+REAPER_MAX="${REAPER_MAX:-2}"   # 2 ciclos × 30 min ≈ 1 h idle, igual que el dead-man del worker
+if [ -s "$FLEET" ] && command -v hcloud >/dev/null 2>&1; then
+  mkdir -p "$ROOT/work/.reaper"; sobreviven=""
+  while read -r name ip; do
+    [ -n "${name:-}" ] || continue
+    strike_f="$ROOT/work/.reaper/$name"
+    if $SSH root@"$ip" 'pgrep -f "hammer build|campana-deuda|farm-worker-loop" >/dev/null 2>&1'; then
+      echo 0 > "$strike_f"; sobreviven="${sobreviven}${name} ${ip}
+"; continue
+    fi
+    strikes=$(( $(cat "$strike_f" 2>/dev/null || echo 0) + 1 )); echo "$strikes" > "$strike_f"
+    echo "==> reaper: $name SIN trabajo activo — strike $strikes/$REAPER_MAX"
+    if [ "$strikes" -lt "$REAPER_MAX" ]; then sobreviven="${sobreviven}${name} ${ip}
+"; continue; fi
+    # BLINDAJE (igual que el dead-man): sólo se borra un server con label role=hammer-worker.
+    if hcloud server describe "$name" -o format='{{.Labels}}' 2>/dev/null | grep -q hammer-worker; then
+      echo "   ☠ IDLE $strikes ciclos ⇒ el HUB borra $name (label verificado; gioser protegido)"
+      if hcloud server delete "$name" >/dev/null 2>&1; then rm -f "$strike_f"
+      else echo "   ⚠ delete falló — sobrevive, reintenta próximo ciclo"; sobreviven="${sobreviven}${name} ${ip}
+"; fi
+    else
+      echo "   ⛔ $name SIN label hammer-worker ⇒ NO se borra (protegido)"; sobreviven="${sobreviven}${name} ${ip}
+"
+    fi
+  done < "$FLEET"
+  printf '%s' "$sobreviven" | grep -v '^[[:space:]]*$' > "$FLEET.tmp" 2>/dev/null; mv "$FLEET.tmp" "$FLEET" 2>/dev/null || true
+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"
diff --git a/scripts/farm/farm-up.sh b/scripts/farm/farm-up.sh
index 72099e95..85938f54 100755
--- a/scripts/farm/farm-up.sh
+++ b/scripts/farm/farm-up.sh
@@ -141,6 +141,17 @@ for k in $(seq 1 "$N"); do
     # estaba "validado" y nunca disparó porque el PID 1 no tenía crond).
     if $SSH root@"$ip" 'systemctl is-active hammer-deadman.timer' 2>/dev/null | grep -q active; then
       echo "   ✓ dead-man armado: $($SSH root@"$ip" 'systemctl list-timers hammer-deadman.timer --no-pager 2>/dev/null | sed -n 2p' | awk '{print $1, $2, $3}')"
+      # FIRING ≠ CAN-DELETE (2026-07-23): un worker quedó idle 3.5h quemando € porque el token en
+      # /etc/hammer-deadman.env estaba vacío ⇒ el dead-man DISPARABA pero salía 1 "sin HCLOUD_TOKEN"
+      # y nunca se borraba. Que el timer esté activo NO prueba que pueda matarse. Se verifica que su
+      # token VE su propio id en la API — lo MISMO que hace al morir.
+      if $SSH root@"$ip" '. /etc/hammer-deadman.env 2>/dev/null; [ -n "${HCLOUD_TOKEN:-}" ] && curl -sS -H "Authorization: Bearer $HCLOUD_TOKEN" "https://api.hetzner.cloud/v1/servers?name=$(hostname)" 2>/dev/null | grep -q "\"id\""' 2>/dev/null; then
+        echo "   ✓ dead-man PUEDE borrarse (token válido, se ve en la API)"
+      else
+        echo "   ⛔ dead-man NO PUEDE borrarse: token vacío/inválido en /etc/hammer-deadman.env."
+        echo "      Solo, este worker quedaría idle PARA SIEMPRE. Lo cubre el reaper del hub"
+        echo "      (cosecha-cron), pero sólo si el latido corre. Arreglar la provisión del token."
+      fi
     else
       echo "   ⚠ el timer NO quedó activo ⇒ este worker puede quedarse idle para siempre. Revisar."
     fi