Files
takana/scripts/farm/worker-depositar.sh
T
sergioandClaude Opus 5 400f7171e0 respaldo: escribir sin poder borrar no es un permiso, es un alcance
Los permisos de subcuenta del Storage Box son BINARIOS: --readonly sí o no.
No hay append-only ni write-sin-delete (verificado en la API, no recordado).

Así que la contención se hace por alcance: el worker escribe en un BUZÓN
cuyo home es incoming/, y hammer/store sencillamente no existe para él. Lo
máximo que puede destruir es lo que él mismo depositó y aún no se promovió,
que sigue estando en el volumen. Un permiso puede estar mal puesto; un
directorio fuera de tu home, no.

La promoción es un mv DENTRO del mismo filesystem: un rename, instantáneo,
cero bytes por la red. Eso es lo que la hace viable con un uplink de
440 kB/s — el laptop manda órdenes, los datos van worker→box por dentro de
Hetzner (122 MB/s, 280×).

Medido de la shell del box, que NO es un bash:
  · no hay `for`   ⇒ los lotes se arman en el hub
  · `a; b` no encadena de fiar ⇒ una orden por conexión
  · `mv a b c dest/` sí acepta varios orígenes ⇒ cientos por conexión
  · `find` no existe y devuelve 0 EN SILENCIO ⇒ parece «no hay respaldo»

Y la trampa que motivó partir el trabajo en dos conjuntos: `mv A dest/` con
dest/A ya existente NO falla, mete A DENTRO y deja dest/A/A. Como el store
es CAS, un artefacto ya respaldado es idéntico ⇒ no se mueve, se borra del
buzón. Los dos caminos probados de punta a punta con un artefacto falso,
recuento verificado y sin anidar.

Defensa en profundidad aparte: plan de snapshots diario (03:17 UTC, retiene
7 de 10) y la carpeta ZFS visible para recuperar ficheros sueltos sin
rollback. La subcuenta no las alcanza: viven en la cuenta, con el token que
el worker no tiene.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 21:39:30 -04:00

52 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# worker-depositar.sh — CORRE EN EL WORKER. Sube al buzón del Storage Box lo que selló y todavía no
# está respaldado. El laptop no participa: la ruta es worker→box, por dentro de Hetzner.
#
# ── POR QUÉ NO LO HACE EL LAPTOP ───────────────────────────────────────────────────────────────
# Medido el 2026-08-09: el uplink de la oficina da 440 kB/s y el tramo interno de Hetzner 122 MB/s
# — 280 veces más. Cualquier diseño que haga pasar los bytes por el laptop no se completa nunca.
# El laptop manda ÓRDENES (la promoción es un `mv`, cero bytes); los DATOS van por acá.
#
# ── LAS DOS CREDENCIALES, Y POR QUÉ HACEN FALTA LAS DOS ────────────────────────────────────────
# `sub1` (sólo lectura, home `hammer`) sirve para PREGUNTAR qué hay ya respaldado. Sin eso el worker
# resubiría el corpus entero en cada ciclo.
# `sub2` (escritura, home `incoming`) es el BUZÓN donde deposita. No alcanza a `hammer/store`: el
# respaldo consolidado no está dentro de su home. Ver `respaldo-subcuenta.sh`.
#
# ── UN SOLO rsync CON --files-from, NO UNO POR ARTEFACTO ───────────────────────────────────────
# El store comparte ficheros entre artefactos por ENLACE DURO. `-H` sólo preserva los enlaces que ve
# DENTRO de una misma corrida: con un rsync por directorio, cada artefacto viaja como si fuera único
# y el buzón llega inflado. Una sola invocación con la lista completa los ve todos.
set -uo pipefail
STORE="${STORE:-/opt/hammer/store}"
KEY="${KEY:-/root/.ssh/sb_sub}"
RO_HOST="${RO_HOST:-u647150-sub1.your-storagebox.de}"; RO_USER="${RO_USER:-u647150-sub1}"
BZ_HOST="${BZ_HOST:-u647150-sub2.your-storagebox.de}"; BZ_USER="${BZ_USER:-u647150-sub2}"
SSH_SB="ssh -4 -p 23 -i $KEY -o StrictHostKeyChecking=no -o ServerAliveInterval=30"
ART='^[0-9a-f]{64}-'
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
ls "$STORE" 2>/dev/null | grep -E "$ART" | sort -u > "$TMP/local.txt"
$SSH_SB "$RO_USER@$RO_HOST" 'ls store' 2>/dev/null | grep -E "$ART" | sort -u > "$TMP/resp.txt"
N_L=$(wc -l < "$TMP/local.txt"); N_R=$(wc -l < "$TMP/resp.txt")
# Un listado vacío del respaldo es un fallo de red disfrazado de «no hay nada respaldado». Tomarlo
# por bueno significaría resubir el corpus entero por gusto.
if [ "$N_R" -eq 0 ]; then
echo "⚠ el respaldo devolvió 0 artefactos — no deposito (¿enlace caído?)" >&2; exit 1
fi
comm -23 "$TMP/local.txt" "$TMP/resp.txt" > "$TMP/nuevos.txt"
N_N=$(wc -l < "$TMP/nuevos.txt")
echo "==> store $N_L · respaldo $N_R ⇒ a depositar $N_N"
[ "$N_N" -eq 0 ] && { echo " nada nuevo"; exit 0; }
# `--files-from` con los nombres a secas (son directorios del store) + `-r` para bajar en cada uno.
rsync -aHr --files-from="$TMP/nuevos.txt" --partial-dir=.rsync-partial --info=progress2 \
-e "$SSH_SB" "$STORE/" "$BZ_USER@$BZ_HOST:store/" || { echo "⚠ el depósito falló"; exit 1; }
# Guardián: recontar el buzón. «rsync salió 0» no es «llegaron N».
EN_BUZON=$($SSH_SB "$BZ_USER@$BZ_HOST" 'ls store' 2>/dev/null | grep -cE "$ART")
echo "==> buzón: $EN_BUZON artefactos (depositados esperados: $N_N)"
[ "$EN_BUZON" -ge "$N_N" ] || { echo "⚠ el buzón tiene MENOS de lo depositado"; exit 1; }
echo " ahora el hub promueve con scripts/farm/respaldo-promover.sh"