Las 7 recetas del stack gráfico (libdrm/mesa/meson/samurai/seatd/wayland/wayland-protocols) + 6 patches eran imports crudos de Alpine, redundantes: las 7 ya tienen receta CANÓNICA en recipes/ (mesa pineada a 24.0.9 iris-only A PROPÓSITO, no la 26.1.1 cruda con FIXME-sha256). Su trabajo aterrizó por la vía canónica (7131cd4) hace 3 semanas; la cola quedó de cruft rompiendo cada ciclo.fa45978las sacó de QUEUES creyéndolas de 'otro agente' (5126a8b). Confirmado que NO hay otro agente ⇒ borradas. recipes/incoming/ vuelve a ser cola de staging general y REGRESA a QUEUES; el guard ls-vacío la salta si no hay nada. recipes/incoming/.deferred/ (24 recetas aparcadas con diagnóstico, git con muro en libgit.a) NO se toca: el glob de QUEUES es top-level, no la muele. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
5.9 KiB
Bash
Executable File
94 lines
5.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# farm-worker-loop.sh — loop AUTÓNOMO del worker. Construye la cola recipes/incoming/ en
|
|
# bucle (PROMOTE=0: sólo sella al store local, NO promueve, NO firma, NO toca gitea), con
|
|
# watchdog de disco integrado. El hub (laptop) le mete recetas nuevas vía farm-sync.sh y le
|
|
# baja el store sellado; este loop sólo muele lo que haya, 24/7, sin esperar a nadie.
|
|
#
|
|
# Idempotente: cada ciclo re-escanea incoming/; lo ya-sellado sale por cache-hit (instantáneo),
|
|
# sólo construye lo nuevo. Cuando la cola está toda construida, duerme IDLE_SLEEP y reintenta.
|
|
#
|
|
# Lo lanza systemd (hammer-farm.service). Manual: JOBS=2 ./scripts/farm/farm-worker-loop.sh
|
|
set -uo pipefail
|
|
HAMMER_DIR="${HAMMER_DIR:-/opt/hammer}"
|
|
cd "$HAMMER_DIR"
|
|
. "$HOME/.cargo/env" 2>/dev/null || true
|
|
JOBS="${JOBS:-$(nproc)}"
|
|
IDLE_SLEEP="${IDLE_SLEEP:-300}"
|
|
|
|
# --- watchdog de disco: cada 3 min borra work/sources/* que ningún bwrap activo bind-monta
|
|
# (seguro: sellada=store CAS, en-cola=se re-extrae sola). Evita que el vendoring llene el disco.
|
|
# Además, si el disco supera DISK_HIGH%, purga los CACHES Go (GOMODCACHE ~/go/pkg/mod + gocache):
|
|
# el `go mod vendor` de las recetas Go acumula ahí decenas de GB sin tope (el vendor/ local de cada
|
|
# receta ya tiene lo que el build necesita; el modcache sólo se usa DURANTE el vendor). Si la purga
|
|
# pisa un vendor en curso, esa receta reintenta el próximo ciclo (cache-hit el resto). Sin esto, una
|
|
# tanda Go grande llena el disco de 80G y el I/O-wait dispara el load (cuello real, no CPU/RAM).
|
|
DISK_HIGH="${DISK_HIGH:-82}"
|
|
(
|
|
while :; do
|
|
sleep 180
|
|
# Protección 1: árboles que un bwrap activo bind-monta (fase compile dentro del sandbox).
|
|
active=$(pgrep -af bwrap 2>/dev/null | grep -oE 'work/sources/[^ ]+' | sort -u)
|
|
# Protección 2: árboles cuya receta tiene un `hammer build` EN VUELO. Durante el fetch/`go mod
|
|
# vendor`/resolve_phases (host-side, ANTES de que arranque el bwrap) el árbol no está bind-montado
|
|
# ⇒ la protección 1 no lo cubre. Una tanda Go con árbol de deps enorme (dnscontrol/lazysql) tarda
|
|
# minutos vendoreando; sin esto, el watchdog le borra el go.mod a mitad → "no build system".
|
|
building=$(pgrep -af 'release/hammer' 2>/dev/null | grep -oE '[^ ]+\.toml' | sed 's#.*/##;s#\.toml$##' | sort -u)
|
|
for d in work/sources/*/; do
|
|
[ -d "$d" ] || continue; dd=${d%/}
|
|
printf '%s\n' "$active" | grep -qxF "$dd" && continue
|
|
nm=$(basename "$dd"); nm=${nm%-*} # work/sources/<name>-<sha40> → <name> (el sha no trae '-')
|
|
printf '%s\n' "$building" | grep -qxF "$nm" && continue
|
|
# Protección 3: árbol CALIENTE (modificado hace <2 min) ⇒ se está extrayendo AHORA. Una dep
|
|
# transitiva (p.ej. cairo bajo pango/gtk4) se extrae host-side ANTES del bwrap y con un nombre
|
|
# que NO coincide con la receta en vuelo ⇒ protecciones 1 y 2 no la cubren. Sin esto, el rm -rf
|
|
# compite con el tar y deja el árbol a medias ("Cannot mkdir" / luego "Directory not empty" para
|
|
# siempre). Un árbol en frío (extracción ya fallida/terminada) queda purgable ⇒ se re-extrae limpio.
|
|
[ -n "$(find "$dd" -mmin -2 -print -quit 2>/dev/null)" ] && continue
|
|
rm -rf "$dd"
|
|
done
|
|
use=$(df --output=pcent "$HAMMER_DIR" 2>/dev/null | tr -dc '0-9')
|
|
if [ -n "$use" ] && [ "$use" -ge "$DISK_HIGH" ]; then
|
|
# OJO: `go clean -modcache`/`-cache` borran ~/go/pkg/mod y ~/.cache/go-build ENTEROS. Ambos los
|
|
# usa `go mod vendor` (modcache p/ módulos, go-build p/ verificar el grafo). Si la purga corre
|
|
# mientras un vendor está activo —o en la VENTANA justo antes de que arranque (otra receta de la
|
|
# cola en serie)— le arranca el cache bajo los pies (`.partial`/`go-build/...: no such file`) y
|
|
# ESE vendor falla. La purga de work/sources de arriba ya liberó lo grueso (segura: saltea las
|
|
# protegidas). Por eso NO purgo las caches Go si hay CUALQUIER `hammer build` en vuelo (no solo
|
|
# un `go mod vendor` visible en este instante): difiero al próximo tick / al gap idle entre
|
|
# ciclos. El sandbox `go install` usa GOCACHE=/src + -mod=vendor ⇒ no toca estas caches del host.
|
|
if pgrep -f 'go mod vendor' >/dev/null 2>&1 || pgrep -f 'release/hammer.* build ' >/dev/null 2>&1; then
|
|
echo "$(date -u +%FT%TZ) watchdog: disco ${use}% ≥ ${DISK_HIGH}% pero hay build en vuelo ⇒ difiero purga caches Go"
|
|
else
|
|
echo "$(date -u +%FT%TZ) watchdog: disco ${use}% ≥ ${DISK_HIGH}% ⇒ purgo caches Go"
|
|
go clean -modcache 2>/dev/null; go clean -cache 2>/dev/null
|
|
fi
|
|
fi
|
|
done
|
|
) &
|
|
|
|
# Colas a construir (sólo SELLA, PROMOTE=0; el hub promueve+firma). `recipes/incoming` es la cola de
|
|
# staging general; `recipes/incoming-go` es la AISLADA del frente Go (pre-sembrada para moler 24/7).
|
|
# build-farm hace `rm -rf work/farm` al inicio ⇒ construirlas EN SERIE (una por vuelta) es seguro,
|
|
# sin colisión. El glob es TOP-LEVEL (`$Q/*.toml`), así que `recipes/incoming/.deferred/` (24 recetas
|
|
# aparcadas con diagnóstico, p.ej. git con muro en libgit.a) NO se muele — es un subdir a propósito.
|
|
# incoming-clib: libs C (toolkit/foundational) que el worker SELLA pero el cron de cosecha-go IGNORA
|
|
# (son build-deps, se usan vía catálogo; no tienen binario que el gate Go pueda smoke-testear).
|
|
QUEUES="${QUEUES:-recipes/incoming recipes/incoming-go recipes/incoming-clib recipes/incoming-kde}"
|
|
echo "$(date -u +%FT%TZ) worker-loop arrancado: JOBS=$JOBS IDLE_SLEEP=$IDLE_SLEEP QUEUES='$QUEUES'"
|
|
while :; do
|
|
total=0
|
|
for Q in $QUEUES; do
|
|
n=$(ls "$Q"/*.toml 2>/dev/null | wc -l)
|
|
[ "$n" -gt 0 ] || continue
|
|
total=$((total + n))
|
|
echo "$(date -u +%FT%TZ) ciclo: $n recetas en $Q"
|
|
JOBS="$JOBS" PROMOTE=0 QUEUE="$Q" scripts/build-farm.sh 2>&1 | tail -50
|
|
done
|
|
if [ "$total" -gt 0 ]; then
|
|
echo "$(date -u +%FT%TZ) ciclo terminado; store sellado disponible para el hub"
|
|
else
|
|
echo "$(date -u +%FT%TZ) colas vacías; durmiendo ${IDLE_SLEEP}s"
|
|
fi
|
|
sleep "$IDLE_SLEEP"
|
|
done
|