diff --git a/scripts/respaldo-storagebox.sh b/scripts/respaldo-storagebox.sh new file mode 100755 index 00000000..02c06d6e --- /dev/null +++ b/scripts/respaldo-storagebox.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# Respaldo de hammer 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]]. +# +# ── 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. borg comprimiría 4x (el 79% del store es `.debug_`), pero 126 G en 1 TiB no +# aprieta a nadie: no vale la complejidad todavía. Si algún día aprieta, ahí sí borg. +# +# ── 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] +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}" +RAIZ="${RAIZ:-/home/sergio/hammer}" + +SECO="" +[ "${1:-}" = "--seco" ] && SECO="--dry-run" + +SSH_CMD="ssh -p $SB_PORT -i $KEY -o StrictHostKeyChecking=accept-new" + +if ! $SSH_CMD "$SB_USER@$SB_HOST" true 2>/dev/null; then + echo "!! No hay SSH al Storage Box ($SB_HOST:$SB_PORT)." + echo "!! Si acabás de crearlo, el DNS tarda unos minutos en propagar. Reintentá." + exit 1 +fi + +echo "==> destino: $SB_USER@$SB_HOST:$SB_PORT ${SECO:+(SECO)}" +$SSH_CMD "$SB_USER@$SB_HOST" 'mkdir -p hammer/store hammer/repo hammer/estado' 2>/dev/null || true + +# 1. EL STORE — lo caro e irreemplazable (meses de CPU). Sin --delete, ver cabecera. +echo "==> store" +rsync -a --info=progress2 --partial $SECO -e "$SSH_CMD" \ + "$RAIZ/store/" "$SB_USER@$SB_HOST:hammer/store/" + +# 2. EL REPO — barato, y ya está espejado a GitHub privado; va igual para tener todo en un sitio. +# Se excluye lo regenerable y lo gigante: store (arriba), work, target, .dev-fs. +echo "==> repo" +rsync -az --delete $SECO -e "$SSH_CMD" \ + --exclude /store --exclude /work --exclude /target --exclude /.dev-fs \ + --exclude /dist --exclude /.scratch \ + "$RAIZ/" "$SB_USER@$SB_HOST:hammer/repo/" + +# 3. El grafo de estado, aparte, para poder mirar qué había sin desempacar nada. +echo "==> estado" +rsync -az $SECO -e "$SSH_CMD" "$RAIZ/docs/state/" "$SB_USER@$SB_HOST:hammer/estado/" 2>/dev/null || true + +echo "==> ocupación en el Storage Box:" +$SSH_CMD "$SB_USER@$SB_HOST" 'df -h . 2>/dev/null | tail -1; du -sh hammer/* 2>/dev/null'