La primera corrida completa desde la caja (puerta 7) se cayó 40 veces seguidas con el MISMO fichero: open ".../fonts/dejavu/.rsync-partial/DejaVuSans-Oblique.ttf" failed: No such file… (2) .. store: corte de red (rsync 23). Intento 39; reanudo en 60s. !! store: 40 intentos y sigue cayéndose. Dejo lo subido y paro. No era red. Una transferencia interrumpida el 11-sep dejó ese PARCIAL en el destino con modo -r--r--r-- (el del origen: el store es de sólo lectura por diseño), y ningún intento posterior puede reescribirlo. El fallo es ETERNO, y el bucle reintentaba cada 60 s contra esa pared — exactamente lo que la cabecera de la función de reintento dice que no hay que hacer. Dos arreglos: 1. `--chmod=Fu+w`: los ficheros del respaldo quedan escribibles por su dueño, así que un corte a mitad se RETOMA en vez de trabarse. El precio es no conservar el bit de sólo-lectura en la copia; es bajo: el contenido es lo que se respalda y los permisos los reconstruye el store. 2. Al rsync 23 se le dan TRES intentos, no cuarenta, y al tercero se NOMBRA la causa probable (un parcial de sólo lectura en el destino) en vez de llamarlo corte de red. El 23 es «some files were not transferred», que tanto puede ser un cable como una pared. Comprobado: borrado el parcial envenenado, ese fichero sube a la primera. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
280 lines
20 KiB
Bash
Executable File
280 lines
20 KiB
Bash
Executable File
#!/bin/sh
|
|
# Respaldo de takana al Storage Box de Hetzner (BX11, 1 TiB, hel1).
|
|
#
|
|
# ── POR QUÉ UN STORAGE BOX Y NO EL VOLUMEN ──────────────────────────────────────────────────────
|
|
# El usuario pidió respaldar «en el volumen, aunque sea montándolo aquí». **No se puede**: un Hetzner
|
|
# Cloud Volume es un dispositivo de bloque por RED que sólo se adjunta a servidores de Hetzner Cloud
|
|
# del mismo datacenter — no se monta en un laptop, y encima sólo puede estar en UN servidor a la vez.
|
|
# `harkaq-cosecha` (100 G) es el disco del worker efímero y `vvv` (260 G, en gioser) está al 78%.
|
|
# El Storage Box SÍ es el producto para esto: se monta desde acá (SSHFS/CIFS) y habla rsync/borg/
|
|
# restic por SSH. BX11 = 1 TiB por €3,20/mes, sin coste de alta. Ver [[capacidad-disco-proyeccion]].
|
|
#
|
|
# ── ES INTERRUMPIBLE Y CONTINUABLE. ESE ES EL DISEÑO, NO UN EFECTO SECUNDARIO ───────────────────
|
|
# Medido en la oficina el 2026-08-07: el enlace da **8 Mbps de subida**, idénticos por cable (eth0)
|
|
# y por wifi (wlan0) ⇒ el cuello NO está en el laptop, está en el uplink. Con 128 G de store eso son
|
|
# ~35 h sin comprimir. Por lo tanto el respaldo NO se completa en una sentada, y todo el script está
|
|
# construido alrededor de esa realidad:
|
|
# · rsync incremental: lo ya subido no se vuelve a subir (el store es CAS, los ficheros nunca
|
|
# cambian, así que la comparación por tamaño+fecha es exacta y baratísima).
|
|
# · `--partial-dir`: un fichero cortado a la mitad conserva el trozo y la próxima corrida lo
|
|
# TERMINA en vez de empezarlo de cero. Sin esto, cortar a mitad de un artefacto de 500 MB
|
|
# tiraría los 400 MB ya subidos.
|
|
# · Se puede matar con Ctrl-C o apagar el laptop en cualquier momento. Se relanza y sigue.
|
|
#
|
|
# ── EL ORDEN ES POR VALOR IRREEMPLAZABLE, NO POR TAMAÑO ─────────────────────────────────────────
|
|
# Como no cabe todo en una ventana, lo que sube primero importa. De más a menos crítico:
|
|
# 1. ESTADO Y REPO (~cientos de MB, minutos). Recetas, scripts, SDDs, el grafo. Es el CEREBRO: con
|
|
# esto solo, el store entero se reconstruye — caro en CPU, pero se reconstruye. Sin esto no hay
|
|
# proyecto. Va primero aunque ya esté espejado a GitHub, porque un respaldo que depende de otro
|
|
# respaldo no es un respaldo.
|
|
# 2. HUÉRFANOS del store (~4,5 G). Artefactos que son el ÚNICO ejemplar de su receta: su hash
|
|
# vigente no está sellado, así que perderlos es perder trabajo real, no reconstruible en dos
|
|
# minutos. Es el material genuinamente irreemplazable y es sorprendentemente barato.
|
|
# 3. EL RESTO DEL STORE (~128 G). Meses de CPU, pero todo reconstruible desde (1).
|
|
#
|
|
# ── POR QUÉ rsync PLANO Y NO borg/restic ────────────────────────────────────────────────────────
|
|
# El store es CAS: los ficheros son INMUTABLES y se nombran por hash. Nunca cambian, sólo aparecen o
|
|
# desaparecen. Para eso rsync incremental es exactamente lo correcto y no hay que mantener repos ni
|
|
# claves de cifrado. Sí se usa COMPRESIÓN zstd, que aquí no es un lujo: medido, 53 MB tardan 53 s sin
|
|
# comprimir y 27 s con `--compress-choice=zstd` — **el doble de rendimiento efectivo**, porque el 79%
|
|
# del contenido del store son secciones `.debug_*` y comprimen como texto. Con un enlace de 8 Mbps la
|
|
# CPU sobra y el ancho de banda es el recurso escaso: comprimir siempre gana.
|
|
#
|
|
# ── EL STORE NO SE BORRA EN EL DESTINO, A PROPÓSITO ─────────────────────────────────────────────
|
|
# `--delete` NO va sobre el store. Un respaldo que replica los borrados no protege del borrado por
|
|
# error, y el 2026-08-07 `store-gc.sh` demostró que puede equivocarse EN SILENCIO (reportó 364
|
|
# artefactos borrados sin haber borrado ninguno). El respaldo acumula; podarlo es una decisión
|
|
# deliberada y aparte. El repo sí va con `--delete`, que para eso está git.
|
|
#
|
|
# Uso: scripts/respaldo-storagebox.sh [--seco]
|
|
# scripts/respaldo-storagebox.sh --listar # refresca work/respaldo-sellados.txt y sale
|
|
# Relanzalo cuantas veces quieras: continúa donde quedó.
|
|
#
|
|
# ── `--listar`: el respaldo también es un REGISTRO, no sólo una copia ──────────────────────────
|
|
# Desde que el store se mudó al volumen (2026-08-09), `build-state.py` y `farm-sync.sh` responden
|
|
# «¿esto está sellado?» con la unión del disco local y los manifiestos, y el del respaldo es el más
|
|
# completo de los dos. Sin refrescarlo, recetas que existen y están respaldadas se reportan como
|
|
# deuda y el worker las rehace. Es barato (una lista de nombres) y no toca un solo byte de datos.
|
|
set -eu
|
|
|
|
SB_USER="${SB_USER:-u647150}"
|
|
SB_HOST="${SB_HOST:-u647150.your-storagebox.de}"
|
|
SB_PORT="${SB_PORT:-23}" # 23 = SSH completo (rsync). El 22 sólo da SFTP/SCP restringido.
|
|
KEY="${KEY:-$HOME/.ssh/github5}"
|
|
# La raíz se DERIVA de dónde está el script, como el resto de `scripts/`. Antes el default era
|
|
# `/mnt/vvv/takana` cableado —la ruta de gioser— así que el script no corría en ningún otro hub:
|
|
# 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"
|
|
|
|
if [ "${1:-}" = "--listar" ]; then
|
|
cd "$RAIZ"; mkdir -p work
|
|
# `du -s`, NO `ls`. Un `ls` lista NOMBRES DE DIRECTORIO, y un artefacto VACÍO tiene nombre igual
|
|
# que uno bueno ⇒ el manifiesto lo avala, `build-state.py` lo da por sellado, rsync lo baja vacío
|
|
# y `takana build` hace cache-hit sobre el directorio vacío: sella sin construir y sale 0. Toda la
|
|
# cadena lee el vacío como presencia, y el único síntoma aparece al final, disfrazado de éxito.
|
|
# (2026-08-10: 7 vacíos de 1749, todos del stack gráfico, de una subida que falló callada.)
|
|
# Cuesta 8,6 s sobre 1751 artefactos frente a un `ls` instantáneo — barato para lo que evita.
|
|
# La shell del box no es bash y no tiene `find`, pero SÍ expande globs y SÍ tiene `du`.
|
|
# `du -s takana/store/[0-9a-f]*` y NO `store/*`: el glob amplio incluye `.dmerge`, la caché
|
|
# transitoria de fusión —48 GB el 2026-08-12—, y recorrerla hace que el listado tarde tanto que se
|
|
# corta por timeout. El sufijo del glob además descarta de entrada los `.bootstrap-tmp`/`.mirror-tmp`
|
|
# que nunca son artefactos. Un artefacto SIEMPRE empieza por hex: filtrar en el origen es más barato
|
|
# que traerse la basura y descartarla aquí.
|
|
ssh -4 -p "$SB_PORT" -i "$KEY" -o StrictHostKeyChecking=accept-new \
|
|
"$SB_USER@$SB_HOST" 'du -s hammer/store/[0-9a-f]*' 2>/dev/null > work/.respaldo-du.tmp
|
|
# OJO CON EL ORDEN: recortar la ruta ANTES del awk se come el contador (`sed 's#.*/##'` es codicioso
|
|
# y borra "5347<TAB>takana/store/" entero), y entonces $1 es el NOMBRE, no los bloques: el filtro
|
|
# compara basura y devuelve cualquier cosa. Primero se filtra por número, después se recorta.
|
|
# Campo 1 = bloques. Un directorio sin contenido da 1..4; con contenido, mucho más.
|
|
awk '$1<=4 {sub(/.*\//,"",$2); print $2}' work/.respaldo-du.tmp \
|
|
| grep -E '^[0-9a-f]{64}-' | sort -u > work/respaldo-vacios.txt
|
|
awk '$1>4 {sub(/.*\//,"",$2); print $2}' work/.respaldo-du.tmp \
|
|
| grep -E '^[0-9a-f]{64}-' | sort -u > work/respaldo-sellados.txt.new
|
|
rm -f work/.respaldo-du.tmp
|
|
N=$(wc -l < work/respaldo-sellados.txt.new)
|
|
V=$(wc -l < work/respaldo-vacios.txt)
|
|
# Un listado vacío es un fallo de red disfrazado de respaldo borrado. No pisa nada.
|
|
if [ "$N" -eq 0 ]; then
|
|
echo "⚠ el box devolvió 0 artefactos — no piso el manifiesto (¿enlace caído?)" >&2
|
|
rm -f work/respaldo-sellados.txt.new; exit 1
|
|
fi
|
|
mv work/respaldo-sellados.txt.new work/respaldo-sellados.txt
|
|
echo "==> manifiesto del respaldo: $N artefactos → work/respaldo-sellados.txt"
|
|
# Se DICE siempre, aunque sea 0: un guardián que sólo habla cuando hay desastre no enseña a nadie
|
|
# qué está vigilando, y el día que hable nadie sabrá interpretarlo.
|
|
if [ "$V" -gt 0 ]; then
|
|
echo "⚠ $V artefactos VACÍOS en el respaldo (excluidos del manifiesto) → work/respaldo-vacios.txt" >&2
|
|
sed 's/^/ /' work/respaldo-vacios.txt >&2
|
|
echo " curar = reconstruir la receta y volver a subirla; NO se cuentan como respaldadas" >&2
|
|
else
|
|
echo " (0 artefactos vacíos)"
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# `-4` a propósito: el box tiene AAAA y el laptop tiene IPv6 sólo en wlan0, así que sin esto la ruta
|
|
# por cable nunca se usaría aunque el cable estuviera enchufado.
|
|
SSH_CMD="ssh -4 -p $SB_PORT -i $KEY -o StrictHostKeyChecking=accept-new -o ServerAliveInterval=30"
|
|
|
|
# Opciones comunes. `--partial-dir` es lo que hace el respaldo continuable a mitad de fichero.
|
|
# `--exclude /.dmerge`: son directorios TRANSITORIOS de fusión del store — aparecen y desaparecen
|
|
# mientras takana sella. rsync los empieza a copiar, se esfuman a media transferencia y devuelve 23
|
|
# («some files/attrs were not transferred»). Como 23 está en la lista de reintentables, el bucle los
|
|
# reintentó **40 veces seguidas** y se rindió con el respaldo a medias (79 G de 127 G), sin que el
|
|
# mensaje dijera nunca que la culpa era de un directorio temporal. `cosecha-cron.sh` ya los excluía;
|
|
# este script no, y ésa era toda la diferencia.
|
|
# ⇒ Un error reintentable que se repite 40 veces no es un corte de red: es algo estructural. Vale la
|
|
# pena que el bucle distinga «se cayó una vez» de «falla siempre igual».
|
|
# `--info=progress2` SÓLO con terminal. Sin TTY reescribe la misma línea con `\r` y, como nadie
|
|
# interpreta el retorno de carro, el «progreso» se acumula: una subida dejó un log de **2,6 MB**
|
|
# donde lo único que importaba era la última línea. Con el respaldo corriendo desatendido (que es
|
|
# como se corre), ese log es la única forma de saber qué pasó — y a 2,6 MB no se lee.
|
|
PROG=""; [ -t 1 ] && PROG="--info=progress2"
|
|
# zstd duplica el rendimiento efectivo (medido, ver cabecera) pero NO todo rsync lo trae: el del
|
|
# PROPIO CORPUS se construye con `--disable-zstd` (`recipes/rsync.toml`), así que desde una caja
|
|
# takana el respaldo moría en el primer paso con `unknown compress name: zstd` y un error 4 que el
|
|
# script clasifica como "no de red" y no reintenta. Un respaldo que no corre por un algoritmo de
|
|
# compresión es peor que un respaldo lento: se degrada a zlib y se avisa.
|
|
if rsync --version 2>/dev/null | grep -A1 -i "compress list" | grep -qw zstd; then
|
|
COMPRESION="-z --compress-choice=zstd --compress-level=3"
|
|
else
|
|
COMPRESION="-z"
|
|
echo "⚠ este rsync no trae zstd (compress list sin él) ⇒ comprimo con zlib."
|
|
echo " Es ~la mitad de rendimiento efectivo. Se arregla en recipes/rsync.toml (dep zstd)."
|
|
fi
|
|
# ⚠ `--chmod=Fu+w` NO es cosmético: sin él, un parcial interrumpido queda en el destino con el modo
|
|
# del origen —el store es `-r--r--r--` por diseño— y **ningún intento posterior puede reescribirlo**.
|
|
# Eso envenena esa ruta PARA SIEMPRE (medido el 2026-09-17: 40 reintentos contra el mismo .ttf). Con
|
|
# los ficheros del respaldo escribibles por su dueño, un corte a mitad se retoma en vez de trabarse.
|
|
# El precio es que la copia no conserva el bit de sólo-lectura; es un precio bajo: el contenido es lo
|
|
# que se respalda, y los permisos del store los reconstruye el propio store.
|
|
RS="rsync -a --chmod=Fu+w --partial-dir=.rsync-partial --exclude /.dmerge $PROG $COMPRESION"
|
|
|
|
# ── REINTENTOS: EL ENLACE SE CAE, Y ESO NO ES EXCEPCIONAL ───────────────────────────────────────
|
|
# Primera corrida real (2026-08-07): tras subir el cerebro y 2,3 G de store, murió con
|
|
# `Connection reset by peer` / `Broken pipe` y rsync salió con 255. Con una subida de ~19 h sobre un
|
|
# enlace de oficina, que la conexión se corte no es un accidente: es lo NORMAL. Un respaldo que hay
|
|
# que relanzar a mano cada vez que se cae no se completa nunca, porque nadie está mirando.
|
|
#
|
|
# Como rsync ya es incremental y `--partial-dir` conserva los trozos, reintentar es barato y seguro:
|
|
# cada intento retoma donde quedó. El bucle sólo insiste ante fallos de RED (códigos 10/12/23/30/35/
|
|
# 255); un error de verdad —permisos, disco lleno en destino, ruta inexistente— sale a la primera en
|
|
# vez de repetirse 40 veces contra la misma pared.
|
|
reintentar() {
|
|
etiqueta="$1"; shift
|
|
intento=1
|
|
while :; do
|
|
"$@" && return 0
|
|
rc=$?
|
|
case "$rc" in
|
|
10|12|30|35|255) ;; # red / pipe roto / timeout: insistir
|
|
# ⚠ 23 NO ES NECESARIAMENTE RED, y creerlo costó 40 reintentos inútiles (2026-09-17). El
|
|
# respaldo desde la caja se cayó 40 veces seguidas con el MISMO fichero:
|
|
#
|
|
# open ".../fonts/dejavu/.rsync-partial/DejaVuSans-Oblique.ttf" failed: No such file… (2)
|
|
#
|
|
# La causa: una transferencia interrumpida el 11-sep dejó ese PARCIAL en el destino con modo
|
|
# `-r--r--r--` (el del origen: el store es de sólo lectura por diseño). Ningún intento
|
|
# posterior puede reescribirlo, así que el fallo es ETERNO — y el bucle lo llamaba «corte de
|
|
# red» y volvía a intentar cada 60 s. Es exactamente la pared que la cabecera de esta función
|
|
# dice que no hay que golpear 40 veces.
|
|
#
|
|
# ⇒ al 23 se le dan TRES intentos, no cuarenta, y al tercero se nombra la causa probable.
|
|
23)
|
|
if [ "$intento" -ge 3 ]; then
|
|
echo "!! $etiqueta: rsync 23 tres veces seguidas. NO parece red: mirá si quedó un" >&2
|
|
echo "!! parcial de sólo lectura en el destino (\`.rsync-partial/\` dentro del árbol)." >&2
|
|
echo "!! Se borra a mano en el Storage Box y la corrida siguiente pasa." >&2
|
|
return "$rc"
|
|
fi
|
|
;;
|
|
# 24 = «ficheros de origen desaparecieron durante la transferencia». NO es un fallo: pasa
|
|
# cuando `store-gc.sh` poda artefactos superados mientras el respaldo corre, que es una
|
|
# combinación NORMAL en este proyecto (el respaldo dura ~19 h y la poda es la válvula del
|
|
# disco). Sin esta línea el bucle lo tomaría por «error que no es de red» y PARARÍA el
|
|
# respaldo entero por algo inofensivo — y encima justo cuando el disco aprieta, que es cuando
|
|
# menos conviene quedarse sin copia.
|
|
24) ;;
|
|
*) echo "!! $etiqueta: error $rc que NO es de red — no reintento"; return "$rc" ;;
|
|
esac
|
|
if [ "$intento" -ge 40 ]; then
|
|
echo "!! $etiqueta: 40 intentos y sigue cayéndose. Dejo lo subido y paro."; return "$rc"
|
|
fi
|
|
espera=$(( intento < 6 ? intento * 10 : 60 ))
|
|
echo ".. $etiqueta: corte de red (rsync $rc). Intento $intento; reanudo en ${espera}s."
|
|
intento=$((intento + 1))
|
|
sleep "$espera"
|
|
done
|
|
}
|
|
|
|
# ── LA COMPROBACIÓN INICIAL TAMBIÉN REINTENTA ──────────────────────────────────────────────────
|
|
# Era un `if` de un solo intento, y el 2026-08-07 tiró la corrida al relanzar el respaldo justo al
|
|
# llegar a casa: el wifi todavía no había levantado, el `ssh` falló una vez y el script se rindió con
|
|
# «no hay SSH» — cuando medio minuto después conectaba perfecto. Es el peor momento para rendirse:
|
|
# quien relanza un respaldo interrumpido acaba de cambiar de red, así que el hipo es LO ESPERABLE.
|
|
# Mismo criterio que el bucle de transferencia: insistir es barato, rendirse cuesta una noche.
|
|
intento=1
|
|
until $SSH_CMD "$SB_USER@$SB_HOST" 'df -h .' >/dev/null 2>&1; do
|
|
if [ "$intento" -ge 20 ]; then
|
|
echo "!! No hay SSH al Storage Box ($SB_HOST:$SB_PORT) tras 20 intentos."
|
|
echo "!! Si acabás de crearlo, el DNS tarda unos minutos en propagar."
|
|
exit 1
|
|
fi
|
|
echo ".. sin SSH al box todavía (¿red recién levantada?). Intento $intento; reintento en 15s."
|
|
intento=$((intento + 1))
|
|
sleep 15
|
|
done
|
|
|
|
echo "==> destino: $SB_USER@$SB_HOST:$SB_PORT ${SECO:+(SECO)}"
|
|
$SSH_CMD "$SB_USER@$SB_HOST" 'mkdir hammer hammer/store hammer/repo hammer/estado' >/dev/null 2>&1 || true
|
|
|
|
# ── 1. EL CEREBRO: estado + repo. Minutos, y es lo que no se puede perder. ──────────────────────
|
|
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 \
|
|
--exclude /work --exclude /target --exclude /.dev-fs --exclude /dist --exclude /.scratch \
|
|
"$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=<ruta> (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" "$STORE/" "$SB_USER@$SB_HOST:hammer/store/" || true
|
|
fi
|
|
echo " · resto del 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
|