Saltar a contenido

Backup del Homelab

Backup automático consistente cada 2 días a las 03:00. El script detiene temporalmente los contenedores que estaban activos, copia configuración, bind mounts y volúmenes Docker con nombre, crea un archivo Zstandard y lo sube cifrado a Backblaze B2.

Rutas y estado

Qué Dónde
Script /opt/scripts/backup.sh (root, modo 700)
Staging y logs locales /opt/backups/ (root:root, modo 700)
Log /opt/backups/backup.log
Destino remoto b2crypt:/ mediante rclone crypt
Cron root 0 3 */2 * * /opt/scripts/backup.sh

Los archivos se llaman homelab_AAAA-MM-DD_HH-MM.tar.zst y tienen un sidecar .sha256. Se conservan las últimas 7 copias cifradas en B2. El archivo local es solo staging: se elimina después de subir archivo y checksum y comprobar que el tamaño remoto coincide. Si la subida o verificación falla, queda local para reintentar o recuperar.

Cobertura

  • /opt/docker, configuraciones de WireGuard, crontabs y scripts del host.
  • Bind mounts descubiertos desde los contenedores, salvo sockets, /etc/localtime, /etc/timezone, cachés y rutas excluidas por política.
  • Todos los Docker named volumes que estén conectados a un contenedor; se guarda también su inspect.json.
  • Secretos necesarios para recuperación (.env, WireGuard y ntfy). Por eso el staging local es exclusivamente de root y el remoto está cifrado.

Los datos bajo /mnt/hdd_1tb y /mnt/hdd_2tb no se incluyen cuando los discos no están montados. El script lo registra explícitamente. No asumir que un directorio vacío en /mnt representa al disco.

Protecciones operativas

  • flock impide ejecuciones concurrentes.
  • Preflight exige al menos 25 GiB libres y verifica inodos.
  • La salida se escribe como .partial y se renombra atómicamente al finalizar.
  • Los errores de bind mounts se acumulan y producen estado no-cero.
  • Cada contenedor se reinicia individualmente; un fallo de arranque se informa como estado degradado sin descartar una copia ya consistente.
  • Se conserva y verifica el estado previo: solo vuelven a arrancar los contenedores que estaban activos.

Verificación y restore

# Ver el proceso y el log
sudo tail -f /opt/backups/backup.log

# Verificar integridad del último archivo
cd /opt/backups
sudo sha256sum -c homelab_AAAA-MM-DD_HH-MM.tar.zst.sha256
sudo zstd -t homelab_AAAA-MM-DD_HH-MM.tar.zst

# Listar sin extraer
sudo tar --use-compress-program=unzstd -tf homelab_AAAA-MM-DD_HH-MM.tar.zst | less

# Consultar las copias cifradas remotas
sudo rclone lsl b2crypt:/

Para restaurar, extraer primero en un directorio aislado. Restaurar configuración y volúmenes con Docker detenido; no sobrescribir /var/lib/docker completo. Para bases importantes, validar además que cada servicio abra sus datos luego del restore.

Pendientes

  • Hacer y registrar un restore drill trimestral por servicio.
  • Configurar retención GFS y Object Lock/immutability en B2 con credenciales de carga separadas de las de borrado.
  • Incorporar dumps nativos de PostgreSQL/MongoDB/SurrealDB además de las copias en frío.
  • Rehabilitar backup_full.sh solo cuando /mnt/backup_512 exista, esté montado por UUID y el job quede programado.

Incidentes conocidos

  • 15-08-2026: el backup abortó al intentar reiniciar un contenedor obsoleto de odyssey-dev. El último recuperable anterior era del 13-08.
  • 17-08-2026: se endureció el script y se validó una copia local/remota completa de 7,8 GiB, con 16 named volumes y SHA-256 correcto.
  • 17-08-2026: se migró a retención B2-only. Se eliminaron seis duplicados locales ya verificados contra B2 y un .tar.gz heredado; /opt/backups conserva logs y staging solo ante fallo. La raíz pasó de 76% a 46%.