diff --git a/scripts/respaldo-storagebox.sh b/scripts/respaldo-storagebox.sh index 94975adc..66c186bf 100755 --- a/scripts/respaldo-storagebox.sh +++ b/scripts/respaldo-storagebox.sh @@ -66,6 +66,13 @@ KEY="${KEY:-$HOME/.ssh/github5}" # desde la caja de producción moría con `cd: /mnt/vvv/takana: No such file or directory` (SDD 28 # §6.9). El override por env se conserva. RAIZ="${RAIZ:-$(cd "$(dirname "$0")/.." && pwd)}" +# El store NO siempre vive dentro del repo. En gioser `store/` es un bind-mount del volumen y queda +# bajo la raíz; en una caja takana INSTALADA es una partición propia montada en `/store`, y el script +# moría con `change_dir "/opt/takana/store" failed`. Peor: rsync devuelve 23, que está en la lista de +# reintentables, así que el bucle lo reintentaba una y otra vez — el mismo cuadro que esta cabecera ya +# documenta («un error reintentable que se repite 40 veces no es un corte de red: es algo +# estructural»). Ahora es env, y si no existe se ABORTA antes de entrar al bucle. +STORE="${STORE:-$RAIZ/store}" SECO="" [ "${1:-}" = "--seco" ] && SECO="--dry-run" @@ -210,6 +217,17 @@ $SSH_CMD "$SB_USER@$SB_HOST" 'mkdir hammer hammer/store hammer/repo hammer/estad echo "==> [1/3] estado (el grafo: qué había construido y con qué hash)" reintentar estado $RS $SECO -e "$SSH_CMD" "$RAIZ/docs/state/" "$SB_USER@$SB_HOST:hammer/estado/" || true +# ⚠ GUARDA: el repo va con `--delete`, así que respaldar desde un árbol INCOMPLETO no sube menos +# cosas: BORRA las que faltan del respaldo. El caso real (2026-09-11, SDD 28 §6.10): la caja de +# producción tiene el repo por `rsync --exclude=.git` —no es un clon—, y una corrida real desde ahí +# habría borrado `.git` del Storage Box, o sea el historial entero. Falla ruidosamente en vez de +# podar en silencio. `REPO_INCOMPLETO=1` es la escotilla para el caso deliberado. +if [ ! -d "$RAIZ/.git" ] && [ "${REPO_INCOMPLETO:-0}" != 1 ]; then + echo "!! '$RAIZ' no tiene .git — parece una copia, no un clon." >&2 + echo " El paso [2/3] va con --delete: subir desde acá BORRARÍA del respaldo lo que falte," >&2 + echo " empezando por el historial de git. Respaldá desde un hub completo, o REPO_INCOMPLETO=1." >&2 + exit 1 +fi echo "==> [2/3] repo (recetas, scripts, SDDs — el cerebro; con esto solo se reconstruye el store)" reintentar repo $RS --delete $SECO -e "$SSH_CMD" \ --exclude /store --exclude /store-rust --exclude /store-kern \ @@ -217,16 +235,19 @@ reintentar repo $RS --delete $SECO -e "$SSH_CMD" \ "$RAIZ/" "$SB_USER@$SB_HOST:hammer/repo/" # ── 2 y 3. EL STORE, huérfanos primero. ──────────────────────────────────────────────────────── +# Guarda ANTES del bucle: un store ausente da rsync 23, que es reintentable, y se convierte en 40 +# intentos y un respaldo a medias sin que nada diga «esa ruta no existe». +[ -d "$STORE" ] || { echo "!! el store no está en '$STORE' — pasá STORE= (en una caja instalada suele ser /store)" >&2; exit 1; } # La lista de huérfanos la produce `store-gc.sh` como subproducto; si no hay una reciente, se sube # el store entero en un solo paso y santas pascuas (rsync es incremental: no se pierde nada). echo "==> [3/3] store (128 G a 8 Mbps ⇒ NO cabe en una sentada; esto se corta y se continúa)" HUER="$(ls -t "$RAIZ"/work/store-gc-huerfanos-*.txt 2>/dev/null | head -1)" if [ -n "$HUER" ] && [ -s "$HUER" ]; then echo " · huérfanos primero ($(wc -l < "$HUER") artefactos, únicos ejemplares) — desde $HUER" - reintentar huerfanos $RS $SECO -e "$SSH_CMD" --files-from="$HUER" "$RAIZ/store/" "$SB_USER@$SB_HOST:hammer/store/" || true + reintentar huerfanos $RS $SECO -e "$SSH_CMD" --files-from="$HUER" "$STORE/" "$SB_USER@$SB_HOST:hammer/store/" || true fi echo " · resto del store" -reintentar store $RS $SECO -e "$SSH_CMD" "$RAIZ/store/" "$SB_USER@$SB_HOST:hammer/store/" +reintentar store $RS $SECO -e "$SSH_CMD" "$STORE/" "$SB_USER@$SB_HOST:hammer/store/" echo "==> ocupación en el Storage Box:" $SSH_CMD "$SB_USER@$SB_HOST" 'df -h .' 2>/dev/null | tail -2