farm: campana-deuda.sh — el HUB dicta la lista, el worker sólo muele
`saldar-deuda-static.sh` calcula la deuda en vivo con `hammer hash --check` contra el store LOCAL. En el worker ese store es PARCIAL (el del volumen) ⇒ mide 716 en vez de 37. Es la regla de siempre con otra cara: el worker MIDE, el hub CLASIFICA. Acá el hub decide (DRY=1) y el worker ejecuta una lista explícita, con las raíces del stack GUI primero (glib unblocks=14 → harfbuzz/cairo/pango → gtk4 → libadwaita) para que la cascada caiga cuanto antes. Incluye el PATH del `go` del store: `go mod vendor` corre host-side y una sesión SSH no interactiva no carga /etc/profile ni cargo/env. Y HOME por defecto, que systemd-run no hereda (con `set -u` era fatal). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Semantic delta
No definition changed: this change is text, documentation or data.
1 files · +65 −0
diff --git a/scripts/farm/campana-deuda.sh b/scripts/farm/campana-deuda.sh
new file mode 100755
index 00000000..b989c056
--- /dev/null
+++ b/scripts/farm/campana-deuda.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+# campana-deuda.sh — muele en el WORKER una lista EXPLÍCITA de recetas en deuda, medida en el HUB.
+#
+# POR QUÉ NO `saldar-deuda-static.sh` DIRECTO EN EL WORKER (2026-07-21): ese script calcula la deuda
+# en vivo con `hammer hash --check` contra el store LOCAL. En el worker el store es PARCIAL (el del
+# volumen: ~290 artefactos vs 720 del hub) ⇒ mide 716 recetas de deuda en vez de 37 y se pone a
+# reconstruir media distro. Es la regla de [[frente-harkaq-jaula]] con otra cara: **el worker MIDE,
+# el hub CLASIFICA**. Acá el hub decide la lista (`DRY=1 saldar-deuda-static.sh`) y el worker sólo
+# ejecuta. La lista se pasa por argumentos o por DEUDA=, no se recalcula.
+#
+# ORDEN: las RAÍCES del stack GUI primero (glib unblocks=14, luego harfbuzz/cairo/pango → gtk4 →
+# libadwaita). `hammer build` arrastra deps recursivamente, así que el orden no es obligatorio —
+# pero empezar por la raíz hace que la cascada caiga cuanto antes y que un corte temprano deje el
+# máximo destrabado. El stack GUI es lo que el laptop NO puede construir (zig-skew, ver
+# [[etapa-g-gui-chain-boundary]]) ⇒ es exactamente lo que justifica la granja.
+#
+# PATH: `go mod vendor` y demás corren HOST-SIDE (fuera del sandbox) ⇒ el `go` del store tiene que
+# estar en el PATH. Una sesión SSH no interactiva no carga /etc/profile ni cargo/env: sin esto toda
+# receta Go muere con "spawn go mod vendor: No such file or directory".
+#
+# Uso (en el worker): scripts/farm/campana-deuda.sh <receta> [receta…]
+# DEUDA="glib cairo" scripts/farm/campana-deuda.sh
+# Lanzarlo desde el hub para que sobreviva al cierre del SSH:
+# ssh root@<ip> 'systemd-run --unit=campana-deuda --working-directory=/opt/hammer \
+# /opt/hammer/scripts/farm/campana-deuda.sh <recetas…>'
+# ssh root@<ip> 'journalctl -u campana-deuda -f'
+set -u
+HAMMER_DIR="${HAMMER_DIR:-/opt/hammer}"
+cd "$HAMMER_DIR"
+HAMMER="${HAMMER:-$HAMMER_DIR/target/release/hammer}"
+STORE="${STORE:-$HAMMER_DIR/store}"
+LOG="${LOG:-/var/log/campana-deuda.log}"
+
+# go del store (host-side) + cargo, para que las recetas Go/Rust no mueran por PATH
+for g in "$STORE"/*-go/usr/bin; do [ -d "$g" ] && PATH="$g:$PATH"; done
+HOME="${HOME:-/root}"; export HOME # systemd-run no hereda HOME y `set -u` lo convierte en fatal
+[ -r "$HOME/.cargo/env" ] && . "$HOME/.cargo/env"
+export PATH
+
+DEUDA="${DEUDA:-$*}"
+[ -n "$DEUDA" ] || { echo "uso: campana-deuda.sh <receta> [receta…] (o DEUDA=…)" >&2; exit 2; }
+
+ts() { date -u +%FT%TZ; }
+total=$(echo "$DEUDA" | wc -w); i=0; ok=0; fail=0; falladas=""
+echo "$(ts) campaña-deuda arranca: $total recetas | go=$(command -v go || echo AUSENTE)" | tee -a "$LOG"
+
+for r in $DEUDA; do
+ i=$((i + 1))
+ f="recipes/$r.toml"
+ [ -r "$f" ] || f="$(ls recipes/incoming*/"$r".toml 2>/dev/null | head -1)"
+ [ -n "$f" ] && [ -r "$f" ] || { echo "$(ts) [$i/$total] $r ⚠ sin receta" | tee -a "$LOG"; continue; }
+ echo "$(ts) [$i/$total] → $r" | tee -a "$LOG"
+ # heartbeat: el dead-man ya cuenta `hammer build` como trabajo, pero entre receta y receta hay
+ # huecos (fetch/vendor) donde no hay proceso con ese nombre. Tocarlo evita una muerte espuria.
+ touch /run/hammer-heartbeat 2>/dev/null || true
+ if "$HAMMER" --store "$STORE" build "$f" >>"$LOG" 2>&1; then
+ ok=$((ok + 1)); echo "$(ts) [$i/$total] ✓ $r" | tee -a "$LOG"
+ else
+ fail=$((fail + 1)); falladas="$falladas $r"; echo "$(ts) [$i/$total] ✗ $r" | tee -a "$LOG"
+ fi
+done
+
+echo "$(ts) campaña-deuda fin: $ok selladas, $fail fallidas de $total" | tee -a "$LOG"
+[ -n "$falladas" ] && echo "$(ts) fallidas:$falladas" | tee -a "$LOG"
+exit 0