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.
105 lines
6.3 KiB
Bash
Executable File
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
|