Saltar a contenido

ADR-033 · DR · backup automation + offsite · RTO 1h / RPO 24h

Fecha: 2026-05-03 Source: /srv/projects/cis/cis-plan/DECISIONS.md (do not edit here — re-split desde la fuente)


Contexto

Hoy los backups CIS son manuales y esporádicos: 5 archivos .sql.gz en /srv/projects/cis/backups/ (cis_admin · cis_platform · cis_inbox · cis_claudia, mezcla de ts del 2026-05-03). Falta:

  1. Automatización (un humano olvida un backup y queda hueco indefinido).
  2. Offsite (todo vive en el mismo disco que protege — pérdida de host = pérdida total).
  3. RTO/RPO declarados (sin objetivos no hay forma de medir si el procedimiento es aceptable).
  4. Drill (nunca se ejecutó un restore desde backup; la utilidad real de los .sql.gz está sin verificar).
  5. Cobertura más allá de DBs (vault, Caddy, systemd units son recursos críticos sin backup).

Decisión

  1. RTO target 1h — desde "host vps-cis caído" hasta servicios CIS (cis-core, cis-admin, Caddy, Postgres) sirviendo tráfico. Lograble si: host nuevo pre-provisionado, offsite activo, runbook §5.2 ejecutado.
  2. RPO target 24h — backup nocturno único. Reducción a 6h o 1h queda para V2 vía WAL archiving (postpuesto post-cesión cochid ADR-030).
  3. Backup orchestrator en cis-core/app/services/dr_backup.py con 4 targets:
  4. backup_all_databases() · pg_dump por DB canónica (ADR-031: cis_admin · cis_platform · cis_claudia · cis_inbox · cis_pagos · cis_monitoreo) · gzip · ts UTC · retención 30 d.
  5. backup_vault() · tar.gz de vault.key + projects/*.json Fernet-encriptados + sha256 manifest.
  6. backup_caddy_config() · tar.gz Caddyfile + sites.d/ + snippets/.
  7. backup_systemd_units() · tar.gz cis-*.{service,timer}.
  8. Cron systemd: cis-backup.timer (OnCalendar *-*-* 03:00:00 UTC, RandomizedDelaySec=300, Persistent=true) → cis-backup.service (oneshot, user=illanes00, group=infra).
  9. Restore drill scripts/dr-restore-drill.sh que crea DB temp <src>_drill, restaura último backup, valida row count ≥ 90% del fuente, drop. Cadencia semestral (mayo + noviembre).
  10. Offsite — decisión humana pendiente entre Backblaze B2 (recomendación, ~USD 5/mes), Hetzner Storage Box (€3.20/mes), Wasabi S3. NO subir antes de aprobación humana + cuenta + TOS firmado. Volumen estimado 50 MB/noche hoy → 300 MB/noche end of 2026.
  11. Logging: structlog JSON (ADR-019) con events dr_backup.run_start/db_ok/db_failed/vault_ok/caddy_ok/systemd_ok/run_complete.

Alternativas descartadas

  • borg con repo offsite directo — más sofisticado (dedup, encrypted), pero overkill para 50 MB/noche y agrega dependencia que el equipo no maneja todavía. Reconsiderar en V2.
  • Backup vía pgBackRest — solo cubre Postgres, no vault/Caddy/systemd. Útil si se adopta WAL archiving en V2 pero no resuelve el alcance completo hoy.
  • Snapshot del VPS al nivel hypervisor (HostingBy ofrece snapshot diario) — entrega RPO 24h sin esfuerzo, pero es opaco (no podés restaurar una sola DB), depende del proveedor, y no cumple "offsite real" porque vive en el mismo datacenter.
  • rsnapshot a otro VPS propio — implica mantener un segundo host sin valor adicional vs B2.

Consecuencias positivas

  • Cualquier pérdida diaria se acota a 24h (RPO declarado, antes era ∞).
  • Restauración determinista vía runbook DR-PLAN.md §5.
  • Cobertura completa: DBs + vault (secrets) + Caddy (TLS / routes) + systemd (service definitions). Permite reconstruir el host casi entero desde tarballs + GitHub mirror del código.
  • Drill semestral asegura que los backups son restaurables (problema típico: backups que silenciosamente fallan).
  • Logs estructurados ADR-019 → cualquier fallo de backup se ve en journalctl -u cis-backup con event names parseables.

Consecuencias negativas / gotchas

  • El vault.key se incluye en el tar — el tar offsite es un secreto top-tier. Cifrado server-side obligatorio antes de subir a B2/Wasabi (lifecycle: enable encryption-at-rest del bucket).
  • Authentik (sqlite) NO está cubierto todavía. Pérdida total = re-seed de sesiones + reauth de todos los usuarios. Migración a Postgres es deuda ADR-024.
  • WAL archiving ausente: si una DB se corrompe a las 22:00, perdemos las últimas 19h de transacciones. Aceptable hoy (volumen bajo); revisitar cuando cis-pagos / cis-inbox crezcan.
  • cis_monitoreo aún no existe como DB — el dump fallará con database does not exist. Tratado como warning (no aborta el run); cuando se cree, se incluirá automáticamente.
  • Sin offsite hoy: si vps-cis pierde su disco, los backups locales se pierden con él. El target RTO 1h NO se cumple hasta que offsite esté activo.

Implementación

  • /srv/projects/cis/cis-core/app/services/dr_backup.py — orchestrator (creado · agent-LH-dr).
  • /etc/systemd/system/cis-backup.{service,timer} — cron systemd (instalado + enabled · agent-LH-dr).
  • /srv/projects/cis/scripts/dr-restore-drill.sh — drill (creado, ejecutable, NO ejecutado contra prod aún).
  • /srv/projects/cis/DR-PLAN.md — RTO/RPO + runbook + checklist (creado · agent-LH-dr).
  • Pendiente humano: elegir proveedor offsite (recomendación B2), abrir cuenta, configurar rclone post-cis-backup.service.
  • Pendiente humano: ejecutar primer drill (sudo /srv/projects/cis/scripts/dr-restore-drill.sh cis_admin) en ventana baja.

Verificación

  • systemctl list-timers cis-backup.timer muestra NEXT en próxima 03:00 UTC.
  • journalctl -u cis-backup.service --since "2 days ago" muestra dr_backup.run_complete ok=true cada noche.
  • ls -lh /srv/projects/cis/backups/{db,vault,caddy,systemd}/ | tail muestra archivos del día actual.
  • find /srv/projects/cis/backups -mtime +30 retorna vacío (retención efectiva).
  • Drill semestral exit 0 con log DRILL OK · cis_admin restore verified.

Relaciona

  • ADR-019 (logging spec, events dr_backup.* siguen el contrato).
  • ADR-024 (Authentik separation — define cuándo Authentik DB entra al backup orchestrator).
  • ADR-026 (operations permission model — restore manual contra prod = restricted op, requiere FES).
  • ADR-030 (cesión cochid — limita V2 WAL archiving timing).
  • ADR-031 (DB universal access — pg_dump usa :5432 directo, único cliente legítimo en session-mode).
  • CONSTITUTION §2 infra canon · /srv/projects/a manual.