#!/usr/bin/env bash # farm-sync.sh — orquesta el worker VPS desde el HUB (laptop). Una sola pasada: # 1. sube código + recipes/incoming/ al worker (rsync, el worker no toca gitea) # 2. baja el store/ sellado que el worker construyó # 3. promueve+firma lo cosechado (cache-hit instantáneo en los artefactos bajados) + commit/push # # El acceso es UNO solo: laptop->VPS por SSH (la clave de firma y gitea nunca salen del laptop). # Idempotente y seguro de re-correr. Pensado para correr cada vez que el laptop está online. # # Uso: scripts/farm/farm-sync.sh root@1.2.3.4 # scripts/farm/farm-sync.sh root@1.2.3.4 --no-promote # sólo sincroniza, no firma # Env: SSH_KEY (def ~/.ssh/github5), REMOTE (def /opt/hammer) set -euo pipefail VPS="${1:?uso: farm-sync.sh user@host [--no-promote]}" SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}" REMOTE="${REMOTE:-/opt/hammer}" ROOT="$(cd "$(dirname "$0")/../.." && pwd)" cd "$ROOT" SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=accept-new" echo "==> 1. subiendo código + cola al worker ($VPS:$REMOTE)" 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*' \ ./ "$VPS:$REMOTE/" echo "==> 2. bajando artefactos sellados del worker" # ── ⚠ NO BAJAR LO QUE YA PODAMOS: era un BUCLE DE CHURN DE 24 G ──────────────────────────────── # Medido el 2026-08-07: `store-gc.sh` borró 362 artefactos superados (24 G) por la mañana y por la # tarde volvió a borrar **los mismos 362** — solapamiento del 100%. No era casualidad: este rsync # bajaba el store del worker ENTERO, y el worker nunca se poda, así que conservaba los superados y # nos los devolvía en cada cosecha. Podar → cosechar → vuelven → podar. El disco pagaba 24 G por # vuelta y la métrica de espacio libre mentía sobre el progreso. # # El registro `work/store-gc-superados.txt` es la unión de todos los manifiestos de poda: nombres # `-` de artefactos que YA decidimos que sobran. Como el store es CAS —los nombres # son inmutables y un artefacto superado no vuelve a ser vigente salvo que una receta retroceda— # excluirlos es seguro; y si una receta retrocediera, su artefacto se reconstruye, que es barato. LEDGER="work/store-gc-superados.txt" if [ -s "$LEDGER" ]; then echo " (excluyendo $(wc -l < "$LEDGER") artefactos ya podados — ver la nota del bucle de churn)" rsync -az -e "$SSH" --exclude-from="$LEDGER" "$VPS:$REMOTE/store/" ./store/ else rsync -az -e "$SSH" "$VPS:$REMOTE/store/" ./store/ fi if [ "${2:-}" = "--no-promote" ]; then echo "✓ store sincronizado (sin promote)"; exit 0 fi echo "==> 3. promote + firma (cache-hit en lo que el worker selló)" scripts/build-farm.sh echo "==> 4. commit + push de la cosecha" git add recipes/ tandas/ 2>/dev/null || true if ! git diff --cached --quiet; then git commit -q -m "Etapa G: cosecha del worker VPS — promote + firma" git push -q origin main && echo "✓ pusheado" else echo "(nada nuevo que commitear)" fi