granja: automuerte TOTAL + el store VIVE en el volumen (sin ventana de pérdida)
Dos fallos de raíz, no uno: 1. HABÍA DOS CAMINOS de crear workers. harkaq-vol.sh ya tenía volumen Y automuerte desde la 1ª campaña; farm-up.sh no tenía ninguna de las dos. hworker-4 nació por farm-up ⇒ 5 días idle con 37G que nadie salvó. La protección existía y el camino que usé la esquivaba. Ahora farm-up monta el MISMO volumen (harkaq-cosecha) ⇒ collect/close cosechan y clausuran ambos. 2. NI harkaq-vol salvaba los artefactos: su rsync excluye /store y el volumen sólo guardaba verdicts/ y logs/. Los 438 artefactos KDE se habrían perdido igual. FIX: el store del worker ES /mnt/cosecha/store (symlink desde /store). Sellar YA es persistir: cada artefacto está en el volumen en el instante en que se crea. "Guardar lo generado" deja de ser un paso que puede fallar antes de morir y pasa a ser la estructura. ⇒ la automuerte puede ser INCONDICIONAL. Quitada la guarda "no borrar si hay cosecha pendiente": era el bug de hworker-4 con otra cara — el worker se queda VIVO justo cuando hay trabajo que salvar. Un switch que se desarma solo cuando más falta hace no sirve. Antes de morir sólo sync+umount: no es guardar (ya está guardado), es cerrar la puerta al salir. El volumen sobrevive al server a propósito (~€0.044/GB/mes). El baseline €0 lo da harkaq-vol.sh close, que es decisión del usuario, no de un timer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+30
-6
@@ -18,6 +18,21 @@
|
||||
# comprometido puede borrar servers del proyecto. Se acepta porque la alternativa medida fue peor:
|
||||
# 5 días de VPS idle. Mitigación: el worker es efímero y no guarda secretos propios.
|
||||
#
|
||||
# AUTOMUERTE TOTAL, y por qué NO hay guarda de "cosecha pendiente" (2026-07-17): la primera versión
|
||||
# abortaba el borrado si quedaba cosecha sin recoger. Eso es exactamente el bug de hworker-4 con
|
||||
# otra cara: el worker se queda VIVO esperando a que alguien lo rescate. Un switch que se desarma
|
||||
# solo cuando más falta hace no sirve.
|
||||
#
|
||||
# La salida correcta no es "no morir": es que **el store del worker VIVA EN EL VOLUMEN**
|
||||
# (/mnt/cosecha/store, symlinkeado desde el store local por farm-up). Así "guardar lo generado" no
|
||||
# es un paso que pueda fallar antes de morir — es la estructura misma. Cada artefacto ya está en el
|
||||
# volumen persistente en el instante en que se sella. Morir no pierde NADA, y por eso puede ser
|
||||
# incondicional. El volumen sobrevive al server; el hub cosecha cuando esté vivo (harkaq-vol.sh
|
||||
# collect) y clausura con `close`.
|
||||
#
|
||||
# Antes de morir sólo queda una cosa honesta: sync(1) + umount, para que el ext4 del volumen quede
|
||||
# consistente. No es "guardar" (ya está guardado), es cerrar la puerta al salir.
|
||||
#
|
||||
# QUÉ CUENTA COMO TRABAJO: un `hammer build` en vuelo, el loop del worker, o un heartbeat fresco
|
||||
# (`/run/hammer-heartbeat`, que un job largo puede tocar). Si nada de eso aparece durante
|
||||
# IDLE_MAX_TICKS ticks seguidos, se borra.
|
||||
@@ -76,12 +91,21 @@ log "sin trabajo: tick $ticks/$IDLE_MAX_TICKS"
|
||||
self="$(hostname)"
|
||||
log "IDLE $ticks ticks ⇒ borrando $self"
|
||||
|
||||
# GUARDA: nunca borrar con cosecha sin recoger. El worker no sabe qué cosechó el hub, pero sí puede
|
||||
# negarse a morir si alguien dejó una marca explícita. `farm-run`/la campaña la ponen mientras haya
|
||||
# artefactos que nadie bajó; el hub la quita al cosechar.
|
||||
if [ -f /var/lib/hammer-no-borrar ]; then
|
||||
log "ABORTA: /var/lib/hammer-no-borrar presente (cosecha pendiente) ⇒ sigo vivo, avisá al hub"
|
||||
exit 0
|
||||
# CERRAR LA PUERTA AL SALIR. No es "guardar" — lo generado YA está en el volumen (el store vive
|
||||
# ahí). Es dejar el ext4 consistente para que el `collect` del hub lo monte limpio. Sin guarda de
|
||||
# "cosecha pendiente" A PROPÓSITO: esa guarda dejaba al worker vivo justo cuando había trabajo que
|
||||
# salvar, que es el bug original con otra cara. La automuerte es TOTAL.
|
||||
if mountpoint -q /mnt/cosecha 2>/dev/null; then
|
||||
art=$(ls -d /mnt/cosecha/store/*/ 2>/dev/null | wc -l)
|
||||
log "volumen: $art artefactos ya persistidos; sync+umount antes de morir"
|
||||
sync
|
||||
# -l (lazy): si algo aún tiene el FS abierto, no queremos bloquear la muerte. Los datos ya
|
||||
# están en disco por el sync; el volumen se detacha solo al borrarse el server.
|
||||
umount /mnt/cosecha 2>/dev/null || umount -l /mnt/cosecha 2>/dev/null || \
|
||||
log "aviso: umount falló; el sync ya persistió los datos"
|
||||
else
|
||||
log "AVISO: /mnt/cosecha NO montado ⇒ este worker no tenía volumen. Lo que haya en su store"
|
||||
log " LOCAL se pierde al borrarlo. Nació por un camino sin volumen (¿farm-up viejo?)."
|
||||
fi
|
||||
|
||||
if [ -z "${HCLOUD_TOKEN:-}" ]; then
|
||||
|
||||
@@ -25,6 +25,14 @@ LOCATION="${LOCATION:-hel1}"
|
||||
SSHKEY="${SSHKEY:-desarrollo@jlsoltech.com}"
|
||||
SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
|
||||
REMOTE="${REMOTE:-/opt/hammer}"
|
||||
# MISMO volumen que harkaq-vol.sh, a propósito: eran DOS caminos de crear workers y sólo uno tenía
|
||||
# volumen+automuerte. hworker-4 nació por ÉSTE (farm-up) y por eso quedó 5 días idle con 37G que
|
||||
# nadie salvó — la protección existía y este camino la esquivaba. Compartir el volumen hace que
|
||||
# `harkaq-vol.sh collect` y `close` cosechen y clausuren lo de AMBOS caminos.
|
||||
VOL_NAME="${VOL_NAME:-harkaq-cosecha}"
|
||||
VOL_SIZE="${VOL_SIZE:-100}" # GB. ~€0.044/GB/mes ⇒ 100G ≈ €4.4/mes MIENTRAS exista.
|
||||
# El baseline €0 lo da `harkaq-vol.sh close`, que es
|
||||
# tuyo: el volumen es el único que sobrevive a propósito.
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
FLEET="$ROOT/scripts/farm/.fleet"
|
||||
# Workers efímeros RECICLAN IPs de Hetzner ⇒ un known_hosts persistente choca ("HOST KEY CHANGED")
|
||||
@@ -58,6 +66,35 @@ for k in $(seq 1 "$N"); do
|
||||
# el servicio ya arrancó en el boot (baked/enabled); reiniciar fuerza rescan inmediato de la cola
|
||||
$SSH root@"$ip" 'systemctl restart hammer-farm' 2>/dev/null || true
|
||||
|
||||
# VOLUMEN PERSISTENTE + STORE DENTRO. Sin esto la automuerte perdería lo construido: hworker-4
|
||||
# tenía 438 artefactos (37G) SÓLO en su disco local. El store vive en /mnt/cosecha/store ⇒ cada
|
||||
# artefacto está en el volumen persistente EN EL INSTANTE en que se sella. "Guardar lo generado"
|
||||
# deja de ser un paso que puede fallar antes de morir y pasa a ser la estructura. El volumen
|
||||
# sobrevive al server; se cosecha con harkaq-vol.sh collect y se clausura con close.
|
||||
echo " montando volumen persistente $VOL_NAME y anclando el store dentro…"
|
||||
vid=$(hcloud volume list -o noheader -o columns=id,name 2>/dev/null | awk -v v="$VOL_NAME" '$2==v{print $1}')
|
||||
if [ -z "$vid" ]; then
|
||||
hcloud volume create --name "$VOL_NAME" --size "$VOL_SIZE" --location "$LOCATION" --format ext4 >/dev/null 2>&1 || true
|
||||
vid=$(hcloud volume list -o noheader -o columns=id,name 2>/dev/null | awk -v v="$VOL_NAME" '$2==v{print $1}')
|
||||
fi
|
||||
if [ -n "$vid" ]; then
|
||||
hcloud volume attach --server "$name" "$VOL_NAME" >/dev/null 2>&1 || true
|
||||
dev="/dev/disk/by-id/scsi-0HC_Volume_$vid"
|
||||
$SSH root@"$ip" "mkdir -p /mnt/cosecha
|
||||
mount $dev /mnt/cosecha 2>/dev/null || { mkfs.ext4 -q -F $dev && mount $dev /mnt/cosecha; }
|
||||
mkdir -p /mnt/cosecha/store /mnt/cosecha/verdicts /mnt/cosecha/logs
|
||||
# el store del worker ES el del volumen: sellar YA es persistir
|
||||
rm -rf $REMOTE/store 2>/dev/null; ln -sfn /mnt/cosecha/store $REMOTE/store
|
||||
grep -q /mnt/cosecha /etc/fstab || echo '$dev /mnt/cosecha ext4 defaults,nofail 0 0' >> /etc/fstab" >/dev/null 2>&1
|
||||
if $SSH root@"$ip" 'mountpoint -q /mnt/cosecha && test -L '"$REMOTE"'/store' 2>/dev/null; then
|
||||
echo " ✓ store anclado al volumen ($( $SSH root@"$ip" 'ls -d /mnt/cosecha/store/*/ 2>/dev/null | wc -l') artefactos ya presentes)"
|
||||
else
|
||||
echo " ⚠ el store NO quedó en el volumen ⇒ lo que construya se PIERDE al auto-borrarse. Revisar."
|
||||
fi
|
||||
else
|
||||
echo " ⚠ sin volumen ⇒ este worker construiría a disco local y la automuerte perdería todo."
|
||||
fi
|
||||
|
||||
# DEAD-MAN SWITCH: el worker se borra SOLO tras 1h sin trabajo. Va acá, en el nacimiento, porque
|
||||
# el modelo "efímero" NO puede depender de que alguien llame a farm-down: hworker-4 estuvo 5 días
|
||||
# idle (2026-07-17) justo por eso — el agente que lo creó se fue y el server quedó. Un invariante
|
||||
|
||||
Reference in New Issue
Block a user