Files
takana/scripts/farm/respaldo-promover.sh
T
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

105 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# respaldo-promover.sh — mueve lo que el worker depositó en el BUZÓN al respaldo consolidado.
#
# ── POR QUÉ HAY UN BUZÓN Y NO ESCRITURA DIRECTA ────────────────────────────────────────────────
# El worker es EFÍMERO y se borra solo; darle escritura sobre `takana/store` sería darle permiso de
# borrado sobre el único respaldo que existe. Y los permisos de una subcuenta del Storage Box son
# BINARIOS: `--readonly` sí o no. No hay append-only, no hay write-sin-delete (verificado en la API
# el 2026-08-09, no de memoria).
#
# Así que la contención no se hace con un permiso sino con el ALCANCE: la subcuenta de escritura
# tiene su home en `incoming/` y `takana/store` sencillamente NO EXISTE para ella. Lo máximo que
# puede destruir es lo que ella misma depositó y todavía no se promovió — que sigue estando en el
# volumen de la granja. Un permiso puede estar mal puesto; un directorio que no está en 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 hace viable que el laptop dirija la operación con un uplink de 440 kB/s — manda
# órdenes, no datos.
#
# ── LO QUE LA SHELL DEL BOX NO TIENE (medido) ──────────────────────────────────────────────────
# Es una shell RESTRINGIDA, no un bash. Medido el 2026-08-09:
# · `for … do … done` → «Command not found». No hay bucles: el batch se arma ACÁ.
# · `a; b` → no encadena de fiar (la segunda orden no corrió). Una orden por conexión.
# · `mv a b c destino/`→ SÍ acepta varios orígenes ⇒ así se mueven cientos en una sola conexión.
# · `find` → NO existe, y devuelve 0 EN SILENCIO. Un recuento con `find` acá parece
# «no hay nada respaldado» y es mentira. Comparar con `ls` + `comm`.
#
# ── LA TRAMPA DEL `mv` SOBRE UN DESTINO QUE YA EXISTE ──────────────────────────────────────────
# `mv A destino/` con `destino/A` ya existente NO falla: mete A DENTRO, y queda `destino/A/A`. Como
# el store es CAS, un artefacto que ya está en el respaldo es idéntico al del buzón ⇒ no se mueve,
# se BORRA del buzón. Por eso se parte en dos conjuntos antes de tocar nada.
#
# Uso: scripts/farm/respaldo-promover.sh [--seco]
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
KEY="${KEY:-$HOME/.ssh/github5}"
MAIN="${MAIN:-u647150@u647150.your-storagebox.de}"
BUZON="${BUZON:-incoming/store}"
RESP="${RESP:-hammer/store}"
LOTE="${LOTE:-80}" # orígenes por conexión; el límite real es la longitud de la orden
SECO=""; [ "${1:-}" = "--seco" ] && SECO=1
sb() { ssh -4 -p 23 -i "$KEY" -o StrictHostKeyChecking=no -o ServerAliveInterval=30 "$MAIN" "$@"; }
ART='^[0-9a-f]{64}-' # sólo artefactos: `bootstrap.json` y demás metadatos NO se promueven
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
sb "ls $BUZON" 2>/dev/null | grep -E "$ART" | sort -u > "$TMP/buzon.txt"
sb "ls $RESP" 2>/dev/null | grep -E "$ART" | sort -u > "$TMP/resp.txt"
N_BUZON=$(wc -l < "$TMP/buzon.txt"); N_RESP=$(wc -l < "$TMP/resp.txt")
# Un listado vacío del RESPALDO es un fallo de red disfrazado de respaldo borrado. Si lo tomáramos
# por bueno, todo el buzón parecería «nuevo» y lo moveríamos sobre un destino que quizá sí tiene
# contenido. Ante la duda, no se toca nada.
if [ "$N_RESP" -eq 0 ]; then
echo "⚠ el respaldo devolvió 0 artefactos — no promuevo (¿enlace caído?)" >&2; exit 1
fi
comm -23 "$TMP/buzon.txt" "$TMP/resp.txt" > "$TMP/nuevos.txt"
comm -12 "$TMP/buzon.txt" "$TMP/resp.txt" > "$TMP/duplicados.txt"
N_NUEVOS=$(wc -l < "$TMP/nuevos.txt"); N_DUP=$(wc -l < "$TMP/duplicados.txt")
echo "==> buzón $N_BUZON · respaldo $N_RESP ⇒ nuevos $N_NUEVOS · ya estaban $N_DUP"
[ "$N_BUZON" -eq 0 ] && { echo " buzón vacío, nada que hacer"; exit 0; }
[ -n "$SECO" ] && { echo " (--seco: no toco nada)"; exit 0; }
# Los lotes se arman ACÁ (el box no sabe iterar) y viajan como argumentos de UNA orden por conexión.
# Cada nombre es `<64 hex>-<paquete>`: sin espacios ni metacaracteres por construcción, así que
# concatenarlos es seguro — y aun así se filtra por `$ART` antes de llegar hasta acá.
en_lotes() { # $1 = fichero de nombres, $2 = orden ("mv"|"rm")
lote=""; n=0; fallos=0
while read -r d; do
lote="$lote $BUZON/$d"; n=$((n + 1))
if [ "$n" -ge "$LOTE" ]; then
case "$2" in
mv) sb "mv$lote $RESP/" >/dev/null 2>&1 || fallos=$((fallos + 1)) ;;
rm) sb "rm -rf$lote" >/dev/null 2>&1 || fallos=$((fallos + 1)) ;;
esac
lote=""; n=0; printf '.'
fi
done < "$1"
if [ -n "$lote" ]; then
case "$2" in
mv) sb "mv$lote $RESP/" >/dev/null 2>&1 || fallos=$((fallos + 1)) ;;
rm) sb "rm -rf$lote" >/dev/null 2>&1 || fallos=$((fallos + 1)) ;;
esac
printf '.'
fi
echo " ($2: $fallos lote(s) con error)"
}
[ "$N_NUEVOS" -gt 0 ] && { printf ' promoviendo '; en_lotes "$TMP/nuevos.txt" mv; }
[ "$N_DUP" -gt 0 ] && { printf ' limpiando duplicados '; en_lotes "$TMP/duplicados.txt" rm; }
# ── EL GUARDIÁN: RECONTAR, NO CREER ────────────────────────────────────────────────────────────
# «moví N» no es lo mismo que «hay N más». La misma regla que salvó la poda del store.
sb "ls $RESP" 2>/dev/null | grep -cE "$ART" > "$TMP/n2" || true
sb "ls $BUZON" 2>/dev/null | grep -cE "$ART" > "$TMP/b2" || true
N_RESP2=$(cat "$TMP/n2"); N_BUZON2=$(cat "$TMP/b2")
echo "==> respaldo $N_RESP$N_RESP2 (esperado $((N_RESP + N_NUEVOS))) · buzón $N_BUZON$N_BUZON2 (esperado 0)"
RC=0
[ "$N_RESP2" -eq "$((N_RESP + N_NUEVOS))" ] || { echo "⚠ el respaldo NO cuadra"; RC=1; }
[ "$N_BUZON2" -eq 0 ] || { echo "⚠ el buzón no quedó vacío ($N_BUZON2)"; RC=1; }
[ "$RC" -eq 0 ] && echo "GUARDIÁN: el recuento CASA ✓"
# El manifiesto que hace honesto al grafo de estado (ver la cabecera de build-state.py).
scripts/respaldo-storagebox.sh --listar || true
exit $RC