diff --git a/scripts/farm/respaldo-subcuenta.sh b/scripts/farm/respaldo-subcuenta.sh index 9eb6f178..8a7be7a2 100755 --- a/scripts/farm/respaldo-subcuenta.sh +++ b/scripts/farm/respaldo-subcuenta.sh @@ -71,6 +71,20 @@ BUZ_USER="${BUZ_USER:-u647150-sub2}" KEY="${KEY:-$HOME/.ssh/github5}" CRED="$HOME/.config/hammer/storagebox-worker.env" +# ── LA GRANJA REUSA IPs, ASÍ QUE LA HOST KEY CAMBIA CON RAZÓN ────────────────────────────────── +# Los workers son EFÍMEROS y Hetzner recicla las direcciones: una IP que ayer fue `hworker-1` hoy es +# otra máquina distinta con otra llave. Sin esto, `ssh` corta con «Host key verification failed» — +# y como estas funciones mandaban stderr a /dev/null, el fallo salía como «no obtuve la pública del +# worker», que apunta al worker cuando el problema estaba en el known_hosts del laptop. Un mensaje +# que culpa al sitio equivocado cuesta más que no tener mensaje. +# Purgar la entrada vieja es correcto ACÁ y sólo acá: la identidad del worker no es su llave, es el +# label `role=hammer-worker` del proyecto hcloud; el secreto no viaja por este canal. +ssh_worker() { # $1 = ip, resto = orden remota + ip="$1"; shift + ssh-keygen -R "$ip" >/dev/null 2>&1 + ssh -i "$KEY" -o StrictHostKeyChecking=accept-new -o ConnectTimeout=20 "root@$ip" "$@" +} + case "${1:-}" in crear) # La contraseña la exige la API aunque después se use llave. Debe traer mayúscula, minúscula, @@ -85,9 +99,9 @@ crear) ;; autorizar) IP="${2:?falta la IP del worker}" - PUB=$(ssh -i "$KEY" "root@$IP" 'mkdir -p /root/.ssh + PUB=$(ssh_worker "$IP" 'mkdir -p /root/.ssh [ -f /root/.ssh/sb_sub ] || ssh-keygen -q -t ed25519 -N "" -C "hworker→respaldo(ro)" -f /root/.ssh/sb_sub - cat /root/.ssh/sb_sub.pub' 2>/dev/null | tail -1) + cat /root/.ssh/sb_sub.pub' | tail -1) [ -z "$PUB" ] && { echo "!! no obtuve la pública del worker"; exit 1; } T=$(mktemp); printf '%s\n' "$PUB" > "$T" ssh -4 -p 23 -i "$KEY" "$MAIN" 'mkdir -p hammer/.ssh' >/dev/null 2>&1 @@ -97,7 +111,7 @@ autorizar) ;; verificar) IP="${2:?falta la IP del worker}" - ssh -i "$KEY" "root@$IP" "SB=\"ssh -4 -p 23 -i /root/.ssh/sb_sub -o StrictHostKeyChecking=no $SUB_USER@$SUB_HOST\" + ssh_worker "$IP" "SB=\"ssh -4 -p 23 -i /root/.ssh/sb_sub -o StrictHostKeyChecking=no $SUB_USER@$SUB_HOST\" lee=\$(\$SB 'ls' 2>/dev/null | tr '\n' ' ') borra=\$(\$SB 'rm -rf estado' 2>&1 | head -1) echo hola > /tmp/.wtest; esc=\$(scp -4 -P 23 -i /root/.ssh/sb_sub -o StrictHostKeyChecking=no /tmp/.wtest $SUB_USER@$SUB_HOST:.wtest 2>&1 | head -1) @@ -113,9 +127,9 @@ autorizar-buzon) IP="${2:?falta la IP del worker}" # La MISMA llave del worker sirve para las dos subcuentas: son dos autorizaciones distintas del # box, no dos identidades del worker. Se genera allá y nunca viaja una privada desde el laptop. - PUB=$(ssh -i "$KEY" "root@$IP" 'mkdir -p /root/.ssh + PUB=$(ssh_worker "$IP" 'mkdir -p /root/.ssh [ -f /root/.ssh/sb_sub ] || ssh-keygen -q -t ed25519 -N "" -C "hworker→respaldo" -f /root/.ssh/sb_sub - cat /root/.ssh/sb_sub.pub' 2>/dev/null | tail -1) + cat /root/.ssh/sb_sub.pub' | tail -1) [ -z "$PUB" ] && { echo "!! no obtuve la pública del worker"; exit 1; } T=$(mktemp); printf '%s\n' "$PUB" > "$T" ssh -4 -p 23 -i "$KEY" "$MAIN" 'mkdir -p incoming/.ssh' >/dev/null 2>&1 @@ -129,18 +143,29 @@ verificar-buzon) # La verificación tiene DOS mitades y las dos son necesarias: que SÍ escriba (si no, el respaldo # no se alimenta y nos enteramos el día que haga falta) y que NO alcance el respaldo consolidado # (si lo alcanza, el aislamiento es decorativo). Una sola mitad no prueba nada útil. - ssh -i "$KEY" "root@$IP" "SB=\"ssh -4 -p 23 -i /root/.ssh/sb_sub -o StrictHostKeyChecking=no $BUZ_USER@$BUZ_HOST\" - echo hola > /tmp/.btest - esc=\$(scp -4 -P 23 -i /root/.ssh/sb_sub -o StrictHostKeyChecking=no /tmp/.btest $BUZ_USER@$BUZ_HOST:store/.btest 2>&1 | head -1) - ve=\$(\$SB 'ls store' 2>&1 | head -3 | tr '\n' ' ') - fuera=\$(\$SB 'ls ../hammer/store' 2>&1 | head -1) - \$SB 'rm -f store/.btest' >/dev/null 2>&1 - echo \"escribir en buzón : \${esc:-(sin error = OK)}\" - echo \"ve su buzón : \$ve\" + # ⚠ DOS DETALLES QUE HICIERON QUE ESTA PRUEBA MINTIERA LA PRIMERA VEZ (2026-08-09): + # 1. el «Warning: Permanently added …» del host key sale por stderr y se comía el `head -1`, así + # que el resultado de escribir era el aviso y no el resultado. Se filtra explícitamente. + # 2. el testigo se llamaba `.btest` y `ls` NO muestra los ficheros con punto ⇒ el listado salía + # vacío tanto si escribió como si no. Un testigo que no se puede ver no prueba nada. + ssh_worker "$IP" "SB=\"ssh -4 -p 23 -i /root/.ssh/sb_sub -o StrictHostKeyChecking=no $BUZ_USER@$BUZ_HOST\" + limpia() { grep -v 'Warning: Permanently added' | head -1; } + echo testigo > /tmp/testigo-buzon.txt + esc=\$(scp -4 -P 23 -i /root/.ssh/sb_sub -o StrictHostKeyChecking=no /tmp/testigo-buzon.txt $BUZ_USER@$BUZ_HOST:store/testigo-buzon.txt 2>&1 | limpia) + ve=\$(\$SB 'ls store' 2>/dev/null | grep -c testigo-buzon) + fuera=\$(\$SB 'ls ../hammer/store' 2>&1 | limpia) + borra=\$(\$SB 'rm -f store/testigo-buzon.txt' 2>&1 | limpia) + queda=\$(\$SB 'ls store' 2>/dev/null | grep -c testigo-buzon) + echo \"escribir en buzón : \${esc:-(sin error)}\" + echo \"testigo visible : \$ve (1 = escribió de verdad)\" + echo \"limpia lo suyo : \${borra:-(sin error)} → queda \$queda\" echo \"alcanza respaldo : \$fuera\" - case \"\$fuera\" in *[Nn]o\ such*|*denied*|*[Ff]ailure*|'') - echo 'VEREDICTO: buzón aislado ✓' ;; - *) echo 'VEREDICTO: ⚠ ALCANZA EL RESPALDO — no usar'; exit 1 ;; esac" + ok=1 + [ \"\$ve\" = 1 ] || { echo 'VEREDICTO: ⚠ NO ESCRIBE — el respaldo no se alimentaría'; ok=0; } + case \"\$fuera\" in *[Nn]o\ such*|*denied*|*[Ff]ailure*) ;; + *) echo 'VEREDICTO: ⚠ ALCANZA EL RESPALDO — no usar'; ok=0 ;; esac + [ \"\$ok\" = 1 ] && echo 'VEREDICTO: escribe en su buzón ✓ · no alcanza el respaldo ✓' + [ \"\$ok\" = 1 ] || exit 1" ;; *) sed -n '/^# Uso:/,/verificar-buzon/p' "$0"; exit 2 ;; esac diff --git a/scripts/farm/worker-depositar.sh b/scripts/farm/worker-depositar.sh index d67fafcd..52da2688 100755 --- a/scripts/farm/worker-depositar.sh +++ b/scripts/farm/worker-depositar.sh @@ -41,7 +41,12 @@ 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 \ +# `--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 en miles de copias de la misma línea. Un +# depósito de 8 segundos dejó así un log ilegible; en el latido, cada media hora, sería peor. +PROG=""; [ -t 1 ] && PROG="--info=progress2" +# shellcheck disable=SC2086 +rsync -aHr --files-from="$TMP/nuevos.txt" --partial-dir=.rsync-partial $PROG \ -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».