#!/usr/bin/env bash # farm-up.sh [N] — levanta N workers EFÍMEROS desde el snapshot golden y los alinea con la cola # actual del laptop. Modelo hub-and-spoke: el worker NO tiene secretos (ni firma ni gitea); es # compute puro y descartable. La imagen 'hammer-golden' trae el toolchain + el store sellado # horneados ⇒ el worker arranca con TODO el catálogo cacheado (cache-hit instantáneo), sólo # construye lo NUEVO que el hub le empuje. # # Flujo: scripts/farm/farm-up.sh 3 # crea hworker-1..3, los alimenta con la cola actual # ... (muelen) ... # scripts/farm/farm-down.sh # cosecha su store al laptop y los DESTRUYE # # La flota viva se registra en scripts/farm/.fleet (" " por línea; gitignored). # Idempotente al escalar: re-correr con más N agrega workers sin pisar los vivos. # # Env: IMAGE (snapshot, def 405120842=hammer-golden-2026-07-05), TYPE (def ccx23), LOCATION # (def hel1), SSHKEY (llave del proyecto Hetzner, def desarrollo@jlsoltech.com), SSH_KEY # (privada local p/ SSH, def ~/.ssh/github5), REMOTE (def /opt/hammer). set -euo pipefail N="${1:-1}" IMAGE="${IMAGE:-405120842}" TYPE="${TYPE:-ccx23}" LOCATION="${LOCATION:-hel1}" SSHKEY="${SSHKEY:-desarrollo@jlsoltech.com}" SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}" REMOTE="${REMOTE:-/opt/hammer}" 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") # y accept-new RECHAZA la clave cambiada, colgando el `until ssh true` para siempre. Como el modelo # es hub-and-spoke (el worker NO tiene secretos: compute puro descartable), saltamos la verificación # de host y no ensuciamos known_hosts (/dev/null). No hay superficie MITM que proteger acá. SSH="ssh -i $SSH_KEY -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR -o ConnectTimeout=10" cd "$ROOT" touch "$FLEET" # índice de arranque: el mayor hworker-N vivo + 1 (para escalar sin colisión de nombres) base=$(sed -n 's/^hworker-\([0-9]*\) .*/\1/p' "$FLEET" | sort -n | tail -1) base="${base:-0}" for k in $(seq 1 "$N"); do i=$((base + k)); name="hworker-$i" echo "==> creando $name ($TYPE @ $LOCATION desde image $IMAGE)" hcloud server create --name "$name" --image "$IMAGE" --type "$TYPE" \ --location "$LOCATION" --ssh-key "$SSHKEY" --label role=hammer-worker >/dev/null ip=$(hcloud server ip "$name") echo "$name $ip" >> "$FLEET" echo " ip=$ip; esperando SSH…" until $SSH root@"$ip" true 2>/dev/null; do sleep 3; done echo " alineando cola con el laptop (rsync código + recipes)…" rsync -az --delete -e "$SSH" \ --exclude /work --exclude /store --exclude '/store-*' --exclude /target \ --exclude /dist --exclude /.dev-fs --exclude /.git --exclude /.scratch \ --exclude '*.png' --exclude '/content*' \ ./ "root@$ip:$REMOTE/" # 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 echo " ✓ $name muele la cola actual" done sort -u -o "$FLEET" "$FLEET" echo "==> flota viva:"; cat "$FLEET" echo "cosechar+destruir con: scripts/farm/farm-down.sh"