Renombrado el directorio del worker y la unit, con las tres units del repo (farm, deadman service y timer). Todas las rutas /opt/hammer del hub pasan a /opt/takana: eran defaults REMOTE= y HAMMER_DIR=, mas los systemctl/journalctl que nombraban la unit sin el sufijo .service, que el primer sed no casaba. Orden, para que no quedara partido: el hub siembra por rsync a una ruta fija, asi que se saco el cron ANTES de mover. Si se movia el worker con el hub apuntando a la ruta vieja, la siguiente cosecha recreaba /opt/hammer y quedaban dos arboles. Verificado de punta a punta: - el worker cerro un ciclo de build REAL desde /opt/takana (dunst sellado, 1/1) - una cosecha completa del hub contra la ruta nueva: siembra, manifiesto, los nueve grafos, static-audit (691 estaticos, MIENTEN 0) y estado pusheado - la unit vieja quedo deshabilitada y borrada; /opt tiene una sola entrada NO se tocan dos referencias a /opt/hammer que siguen siendo CORRECTAS: docs/23-plan-rehasheo.md describe rutas EMBEBIDAS en artefactos ya construidos —que literalmente dicen /opt/hammer/work en sus secciones .debug— y el HANDOFF de la noche de KDE es registro. Cambiarlas haria que los documentos mientan.
153 lines
7.8 KiB
Bash
Executable File
153 lines
7.8 KiB
Bash
Executable File
#!/bin/sh
|
|
# harkaq-vol.sh — granja efímera con VOLUMEN persistente y workers auto-terminantes.
|
|
#
|
|
# El modelo que la 1ª campaña pidió a gritos: el worker no depende de que yo esté viva para
|
|
# cosechar, y no se queda idle cobrando. Tres piezas:
|
|
#
|
|
# up N crea/reusa el volumen, levanta N workers con él montado en /mnt/cosecha, cada uno
|
|
# con auto-shutdown: tras IDLE_CICLOS ciclos sin trabajo nuevo, el worker SE
|
|
# AUTO-ELIMINA (poweroff en Hetzner sigue cobrando ⇒ delete de verdad). Las cosechas
|
|
# ya están en el volumen, que sobrevive.
|
|
# collect (yo, cuando esté viva) monta el volumen en un helper barato efímero, rsync las
|
|
# cosechas al hub, corre el harvest, destruye el helper.
|
|
# close detach + delete del volumen. Se paga sólo mientras existe (~€0.44/mes).
|
|
# status qué hay vivo y qué hay en el volumen.
|
|
#
|
|
# Por qué el token va al worker: para auto-eliminarse necesita la API. Riesgo acotado — el worker
|
|
# es efímero, sin servicios entrantes salvo SSH-por-clave, y se destruye. El token vive sólo
|
|
# mientras el worker corre. (Hetzner no da tokens por-recurso; es full-project.)
|
|
set -eu
|
|
|
|
# Sin llaves en el mensaje del `${1:?...}`: una `}` interna cierra la expansión antes de tiempo
|
|
# (CMD salía como "status}"). El uso completo va en el `*)` del case.
|
|
CMD="${1:?uso: harkaq-vol.sh up N | collect | close | status}"
|
|
HUB="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
cd "$HUB"
|
|
VOL="${VOL:-harkaq-cosecha}"
|
|
IMAGE="${IMAGE:-408909310}" # golden con kernel 6.17 (Landlock ABI 7)
|
|
TYPE="${TYPE:-ccx23}"
|
|
HELPER_TYPE="${HELPER_TYPE:-ccx13}" # para collect: el más barato que monte el volumen
|
|
LOCATION="${LOCATION:-hel1}"
|
|
SSHKEY="${SSHKEY:-desarrollo@jlsoltech.com}"
|
|
SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
|
|
REMOTE="${REMOTE:-/opt/takana}"
|
|
IDLE_CICLOS="${IDLE_CICLOS:-3}" # ciclos idle antes de auto-eliminarse
|
|
SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes"
|
|
|
|
token() { grep -oP 'token\s*=\s*"\K[^"]+' "$HOME/.config/hcloud/cli.toml" | head -1; }
|
|
|
|
vol_id() { hcloud volume list -o noheader -o columns=id,name | awk -v v="$VOL" '$2==v{print $1}'; }
|
|
vol_dev() { echo "/dev/disk/by-id/scsi-0HC_Volume_$(vol_id)"; }
|
|
|
|
ensure_vol() {
|
|
if [ -z "$(vol_id)" ]; then
|
|
echo "== creando volumen $VOL (10GB)"
|
|
hcloud volume create --name "$VOL" --size 10 --location "$LOCATION" --format ext4 >/dev/null
|
|
fi
|
|
}
|
|
|
|
wait_ssh() { for _ in $(seq 1 30); do $SSH "root@$1" true 2>/dev/null && return 0; sleep 6; done; return 1; }
|
|
|
|
case "$CMD" in
|
|
up)
|
|
N="${2:-1}"; ensure_vol
|
|
DEV="$(vol_dev)"
|
|
for i in $(seq 1 "$N"); do
|
|
name="hkw-$i"
|
|
echo "== $name: creando con volumen $VOL montado"
|
|
# --automount monta el volumen; el user-data hornea el token, la config de auto-shutdown y
|
|
# arranca el servicio. Así el worker es autónomo desde el boot: mide → escribe al volumen →
|
|
# se auto-elimina al agotar la cola. Nada depende de que yo esté para nada.
|
|
ud=$(mktemp)
|
|
cat > "$ud" <<EOF
|
|
#cloud-config
|
|
write_files:
|
|
- path: /root/.hcloud-token
|
|
permissions: '0600'
|
|
content: "$(token)"
|
|
- path: /etc/harkaq-idle-ciclos
|
|
content: "$IDLE_CICLOS"
|
|
EOF
|
|
hcloud server create --name "$name" --image "$IMAGE" --type "$TYPE" \
|
|
--location "$LOCATION" --ssh-key "$SSHKEY" --volume "$VOL" \
|
|
--user-data-from-file "$ud" --label role=harkaq-vol >/dev/null
|
|
rm -f "$ud"
|
|
ip=$(hcloud server ip "$name")
|
|
wait_ssh "$ip" || { echo " $name no responde"; continue; }
|
|
# Provisión: código + harkaq + montaje del volumen + servicio con auto-shutdown.
|
|
# --exclude /work es CRÍTICO: work/ son 69GB (sources, tandas). Sin él el rsync manda
|
|
# decenas de GB y el up cuelga horas — pasó. farm-up.sh ya lo excluye; se perdió al copiar.
|
|
rsync -az -e "$SSH" --exclude /work --exclude /store --exclude '/store-*' --exclude /target \
|
|
--exclude /dist --exclude /tandas --exclude /.dev-fs --exclude /.git --exclude /.scratch \
|
|
--exclude '*.png' --exclude '/content*' \
|
|
./ "root@$ip:$REMOTE/" >/dev/null
|
|
$SSH "root@$ip" "MNT=/mnt/cosecha; mkdir -p \$MNT; mount $DEV \$MNT 2>/dev/null || (mkfs.ext4 -q -F $DEV && mount $DEV \$MNT); mkdir -p \$MNT/verdicts \$MNT/logs"
|
|
scripts/farm/harkaq-farm-setup.sh "$ip" >/dev/null 2>&1 || echo " (setup con avisos, sigo)"
|
|
$SSH "root@$ip" "export PATH=\$HOME/.cargo/bin:\$PATH; cd $REMOTE && cargo build --release -q -p takana-cli 2>&1 | tail -1"
|
|
# Servicio con auto-shutdown (el script harkaq-campana-vol lo maneja).
|
|
$SSH "root@$ip" "cat > /etc/systemd/system/harkaq-vol.service <<UNIT
|
|
[Unit]
|
|
Description=harkaq campana con volumen + auto-shutdown
|
|
After=network-online.target
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=$REMOTE
|
|
Environment=HARKAQ_BIN=/root/.cache/harkaq
|
|
ExecStart=$REMOTE/scripts/farm/harkaq-campana-vol.sh $name
|
|
Restart=always
|
|
RestartSec=30
|
|
StandardOutput=append:/var/log/harkaq-vol.log
|
|
StandardError=append:/var/log/harkaq-vol.log
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT
|
|
systemctl daemon-reload && systemctl enable --now harkaq-vol >/dev/null 2>&1"
|
|
echo " $name ✓ midiendo → volumen; se auto-elimina tras $IDLE_CICLOS ciclos idle"
|
|
done
|
|
;;
|
|
|
|
collect)
|
|
VID="$(vol_id)"; [ -n "$VID" ] || { echo "no hay volumen $VOL"; exit 1; }
|
|
# ¿ya hay un worker vivo con el volumen? úsalo. Si no, helper efímero barato.
|
|
host=$(hcloud server list -o noheader -o columns=name,labels | awk '/role=harkaq-vol/{print $1; exit}')
|
|
helper=""
|
|
if [ -z "$host" ]; then
|
|
helper="hkcollect"; echo "== sin worker vivo: helper efímero $helper ($HELPER_TYPE)"
|
|
hcloud server create --name "$helper" --image debian-12 --type "$HELPER_TYPE" \
|
|
--location "$LOCATION" --ssh-key "$SSHKEY" --volume "$VOL" >/dev/null
|
|
host="$helper"
|
|
fi
|
|
ip=$(hcloud server ip "$host"); wait_ssh "$ip"
|
|
$SSH "root@$ip" "mkdir -p /mnt/cosecha; mount $(vol_dev) /mnt/cosecha 2>/dev/null || true; ls /mnt/cosecha/verdicts 2>/dev/null | wc -l" \
|
|
| 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
|
|
# 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
|
|
# recetas + commit no debe colgar de un `collect`. Se reporta y se deja al operador/harvest.
|
|
n=$(ls work/harkaq-campana/*.verdicts 2>/dev/null | wc -l)
|
|
echo "== recogidos $n verdicts a work/harkaq-campana/. Clasificar/declarar:"
|
|
echo " scripts/farm/harkaq-suggest.py <v> ~/.cache/harkaq/base.policy \$PWD/store \$PWD/recipes"
|
|
;;
|
|
|
|
close)
|
|
VID="$(vol_id)"; [ -z "$VID" ] && { echo "no hay volumen $VOL"; exit 0; }
|
|
srv=$(hcloud volume describe "$VID" -o 'format={{.Server}}' 2>/dev/null || echo "")
|
|
[ -n "$srv" ] && [ "$srv" != "<nil>" ] && hcloud volume detach "$VID" >/dev/null 2>&1 || true
|
|
hcloud volume delete "$VID" >/dev/null && echo "== volumen $VOL clausurado (deja de cobrar)"
|
|
;;
|
|
|
|
status)
|
|
echo "── volumen: $(vol_id | sed 's/^$/(ninguno)/')"
|
|
echo "── workers harkaq-vol vivos:"; hcloud server list -o noheader -o columns=name,status,age | awk '/hkw-|hkcollect/{print " "$0}' || echo " ninguno"
|
|
;;
|
|
|
|
*) echo "uso: harkaq-vol.sh {up N|collect|close|status}"; exit 2 ;;
|
|
esac
|