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 1411Shell 141Markdown 84Rust 832898 Definitions

granja: el dead-man del worker se mata SOLO (sin hub) — 3 bugs + gioser doble-blindado

El worker DEBE matarse solo, sin depender de la laptop (es la razón de ser del
dead-man: vive en el worker; el volumen hace que morir no pierda nada). El reaper
del hub queda sólo como último recurso. El dead-man estaba TRIPLEMENTE roto:

 1. TOKEN EN EL FICHERO EQUIVOCADO: hay 2 caminos de creación con el token en
    lugares distintos (farm-up → /etc/hammer-deadman.env; harkaq-vol → /root/
    .hcloud-token). La service lee sólo el primero ⇒ un worker del otro camino
    quedaba sin token, disparaba pero salía 1 "sin HCLOUD_TOKEN". Fix: deadman.sh
    busca el token EN CADENA (volumen primero, que es lo más persistente).
 2. REGEX DEL ID ROTO: la API devuelve JSON con espacio ("id": 126, no "id":126)
    y el dead-man usaba '"id":[0-9]+' ⇒ NUNCA resolvía su id, aun con token
    válido. Fix: '"id":[[:space:]]*[0-9]+'. (Doblemente roto: por eso NUNCA murió.)
 3. FIRING ≠ CAN-DELETE: farm-up sólo verificaba que el timer dispara. Ahora
    corre `deadman.sh --puede-borrar` (token en cadena + ve su id en la API) y si
    NO puede matarse, DESTRUYE el worker ahí mismo. Un worker que no se autodestruye
    es inadmisible ⇒ jamás debe existir.

gioser DOBLE-BLINDADO en TODOS los sitios de borrado (lista negra por nombre +
label role=hammer-worker), igual que farm-down ya tenía: reaper, farm-up-destroy,
deadman, y el helper de harkaq-vol. "Ahí está nuestra vida." Verificado vivo (104d).

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

3de75a5229cdede8738cec486c25bf3df36e303f · by sergio · (3 months ago) · parent be0631433 · browse the tree at this commit

Semantic delta

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

4 files · +60 −15

diff --git a/scripts/farm/cosecha-cron.sh b/scripts/farm/cosecha-cron.sh
index f84f61f1..3bf8ff49 100755
--- a/scripts/farm/cosecha-cron.sh
+++ b/scripts/farm/cosecha-cron.sh
@@ -90,6 +90,11 @@ 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
+    # LISTA NEGRA (capa 1 de 2, igual que el dead-man): gioser JAMÁS se toca, pase lo que pase.
+    # Segunda capa = el chequeo de label abajo. gioser no tiene label Y no matchea worker de la
+    # granja ⇒ imposible que muera por acá. "Ahí está nuestra vida" (2026-07-23).
+    case "$name" in *gioser*) echo "==> reaper: $name en LISTA NEGRA ⇒ intocable"; sobreviven="${sobreviven}${name} ${ip}
+"; continue;; esac
     strike_f="$ROOT/work/.reaper/$name"
     # ¿el server siquiera existe? Si ya no está en hcloud (borrado a mano o por un ciclo previo),
     # NO cuesta € y no debe quedar en .fleet para siempre. Se dropea (no entra a `sobreviven`).
diff --git a/scripts/farm/deadman.sh b/scripts/farm/deadman.sh
index c55f1952..2fccaf7b 100644
--- a/scripts/farm/deadman.sh
+++ b/scripts/farm/deadman.sh
@@ -54,6 +54,34 @@ LOG="/var/log/hammer-deadman.log"
 
 log() { echo "$(date -u +%FT%TZ) deadman: $*" >> "$LOG"; }
 
+# TOKEN ROBUSTO A CADA CAMINO DE CREACIÓN (incidente 2026-07-23). Hay DOS caminos que crean workers y
+# ponen el token en ficheros DISTINTOS: `farm-up` → /etc/hammer-deadman.env (EnvironmentFile de la
+# service); `harkaq-vol.sh` → /root/.hcloud-token (cloud-init). La service del snapshot lee sólo el
+# primero ⇒ un worker nacido por el OTRO camino quedaba SIN token en su env, disparaba pero salía 1
+# "sin HCLOUD_TOKEN" y NUNCA se borraba (3.5h idle quemando €). El dead-man NO puede depender del hub
+# para matarse (ésa es su razón de ser: vive en el worker). Así que busca el token en CADENA — el
+# volumen primero, porque es lo más persistente (sobrevive al server, la idea del volumen del usuario).
+: "${HCLOUD_TOKEN:=}"
+if [ -z "$HCLOUD_TOKEN" ]; then
+    for f in /mnt/cosecha/.hcloud-token /root/.hcloud-token /etc/hcloud-token; do
+        [ -r "$f" ] || continue
+        HCLOUD_TOKEN=$(tr -d ' \t\n\r' < "$f" 2>/dev/null || true)
+        [ -n "$HCLOUD_TOKEN" ] && { log "token recuperado de $f (env vacío)"; break; }
+    done
+fi
+export HCLOUD_TOKEN
+
+# ¿PUEDE este worker borrarse? (token que VE su propio id en la API). firing ≠ can-delete: `farm-up`
+# usa esto para no dejar vivo un worker que no puede matarse, en vez de descubrirlo idle 3.5h tarde.
+if [ "${1:-}" = "--puede-borrar" ]; then
+    if [ -z "$HCLOUD_TOKEN" ]; then echo "NO: sin token en ninguna ubicación conocida"; exit 1; fi
+    id=$(curl -sS -H "Authorization: Bearer $HCLOUD_TOKEN" \
+         "https://api.hetzner.cloud/v1/servers?name=$(hostname)" 2>/dev/null \
+         | grep -oE '"id":[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+')
+    if [ -n "$id" ]; then echo "SÍ: puedo borrar mi id=$id"; exit 0
+    else echo "NO: el token no resuelve mi propio id (¿nombre '$(hostname)' no está en el proyecto?)"; exit 1; fi
+fi
+
 if [ "${1:-}" = "--verificar" ]; then
     echo "══ ¿el dead-man switch está REALMENTE armado?"
     echo "── timer:"; systemctl is-active hammer-deadman.timer 2>/dev/null || echo "   INACTIVO ⚠"
@@ -143,7 +171,7 @@ fi
 
 id=$(curl -sS -H "Authorization: Bearer $HCLOUD_TOKEN" \
      "https://api.hetzner.cloud/v1/servers?name=$self" 2>/dev/null \
-     | grep -oE '"id":[0-9]+' | head -1 | cut -d: -f2)
+     | grep -oE '"id":[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+')
 if [ -z "$id" ]; then
     log "ERROR: no pude resolver mi propio id de server (¿nombre '$self' no está en el proyecto?)"
     exit 1
diff --git a/scripts/farm/farm-up.sh b/scripts/farm/farm-up.sh
index 85938f54..6a3f2bc0 100755
--- a/scripts/farm/farm-up.sh
+++ b/scripts/farm/farm-up.sh
@@ -139,21 +139,29 @@ for k in $(seq 1 "$N"); do
       systemctl daemon-reload && systemctl enable --now hammer-deadman.timer" >/dev/null 2>&1
     # EVIDENCIA, no fe: que el timer esté ARMADO se comprueba, no se asume (el cron de aquella vez
     # 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)"
+    # FIRING ≠ CAN-DELETE (2026-07-23): un worker quedó idle 3.5h quemando € porque su token no
+    # estaba donde la service lo lee ⇒ 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. El worker DEBE poder matarse
+    # SOLO, sin el hub — así que si NO PUEDE, no lo dejamos vivo: lo DESTRUIMOS acá mismo. Un worker
+    # que no se puede autodestruir es exactamente lo que el usuario prohíbe. `deadman.sh --puede-borrar`
+    # busca el token en cadena (env, volumen, /root) y prueba que ve su id en la API.
+    timer_ok=$($SSH root@"$ip" 'systemctl is-active hammer-deadman.timer' 2>/dev/null | grep -q active && echo 1)
+    puede=$($SSH root@"$ip" '/usr/local/bin/deadman.sh --puede-borrar' 2>/dev/null)
+    if [ -n "$timer_ok" ] && printf '%s' "$puede" | grep -q '^SÍ'; then
+      echo "   ✓ dead-man ARMADO y PUEDE borrarse solo ($puede) — no depende del hub"
+    else
+      echo "   ⛔ $name NO puede autodestruirse (timer=${timer_ok:-no} · $puede)."
+      echo "      Un worker que no se mata solo es INADMISIBLE ⇒ lo DESTRUYO ahora para no dejarlo idle."
+      # LISTA NEGRA (capa 1 de 2): gioser JAMÁS se toca. farm-up sólo crea hworker-N, pero defensa
+      # en profundidad — "ahí está nuestra vida". Segunda capa = el label check de abajo.
+      case "$name" in *gioser*) echo "      ⛔ $name en LISTA NEGRA ⇒ NO lo toco. Revisá a mano."; continue;; esac
+      if hcloud server describe "$name" -o format='{{.Labels}}' 2>/dev/null | grep -q hammer-worker; then
+        hcloud server delete "$name" >/dev/null 2>&1 && echo "      ☠ $name destruido (label verificado)."
       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."
+        echo "      ⛔ $name sin label hammer-worker ⇒ NO lo toco (protegido); borralo a mano."
       fi
-    else
-      echo "   ⚠ el timer NO quedó activo ⇒ este worker puede quedarse idle para siempre. Revisar."
+      grep -v "^$name " "$FLEET" > "$FLEET.tmp" 2>/dev/null && mv "$FLEET.tmp" "$FLEET"
+      continue
     fi
   fi
   echo "   ✓ $name muele la cola actual"
diff --git a/scripts/farm/harkaq-vol.sh b/scripts/farm/harkaq-vol.sh
index a7a28f09..665e1b57 100755
--- a/scripts/farm/harkaq-vol.sh
+++ b/scripts/farm/harkaq-vol.sh
@@ -122,7 +122,11 @@ collect)
         | sed 's/^/   verdicts en el volumen: /'
     mkdir -p work/harkaq-campana
     rsync -az -e "$SSH" "root@$ip:/mnt/cosecha/verdicts/" work/harkaq-campana/ 2>/dev/null || true
-    [ -n "$helper" ] && { echo "== destruyo el helper"; hcloud server delete "$helper" >/dev/null; }
+    # El helper lo nombra este script, nunca es gioser — pero guarda igual ("ahí está nuestra vida").
+    case "$helper" in
+      *gioser*) echo "== ⛔ helper matchea gioser?! ABORTO el delete" ;;
+      ?*) echo "== destruyo el helper"; hcloud server delete "$helper" >/dev/null ;;
+    esac
     # NO se corre el harvest acá: harvest-harkaq.sh rsync-ea de un WORKER, y en este punto los
     # verdicts ya están locales (los trajo el rsync del volumen). Clasificarlos es un paso aparte
     # —lo hace el hub con el store COMPLETO (el worker mide, el hub clasifica)— y su edición de