Wiki
Copia de seguridad y recuperación
Cómo QuoteNode gestiona las copias de seguridad de base de datos y archivos: archivos cifrados con age, restauración guiada por manifiesto, kits de recuperación y perfiles de ejecución.
Copia de seguridad y recuperación
QuoteNode incluye un sistema de copias de seguridad integrado que protege tus datos comerciales frente a fallos de hardware, eliminaciones accidentales e incidentes operativos.
Qué se incluye en la copia de seguridad
Cada copia de seguridad crea un archivo que contiene:
- Volcado de la base de datos — un volcado completo de PostgreSQL en formato personalizado (
pg_dump --format=custom), que incluye todas las tablas, secuencias y restricciones. - Almacenamiento de archivos — todos los archivos subidos (imágenes de productos, logotipos de empresa) y los PDF generados, archivados como
files.tar.gzcon rutas relativas. - Manifiesto de la copia de seguridad — un archivo
backup-manifest.jsonque contiene la versión de origen, el modo de cifrado, el modo de carga útil de la base de datos, las sumas de comprobación y los metadatos de compatibilidad de restauración. - Sumas de comprobación de integridad — un archivo
checksums.sha256que contiene los hashes SHA-256 de todos los componentes del archivo.
Cifrado del archivo
Las nuevas copias de seguridad cifradas usan el cifrado por destinatario age (AGE_RECIPIENT). Es el único modelo de cifrado admitido para los archivos nuevos.
- El proceso de copia de seguridad usa únicamente un destinatario age público (
age1...) — no se necesita ninguna clave privada en el servidor para crear copias de seguridad. - Los archivos cifrados tienen la extensión
.tar.age. - La suma de comprobación SHA-256 del archivo cifrado se almacena junto al archivo para su verificación por parte del operador.
- El descifrado requiere la identidad age privada correspondiente, que se conserva exclusivamente en el kit de recuperación del operador.
- La identidad age privada en bruto nunca se almacena en la base de datos, los registros, los DTO ni los argumentos del proceso tras la confirmación de la configuración.
Copias de seguridad locales sin cifrar
Las copias de seguridad locales sin cifrar (.tar) solo se permiten cuando se cumplen todas las condiciones siguientes:
BACKUP_ARCHIVE_ENCRYPTION_MODE=NONEBACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=true- El destino es el almacenamiento local
BACKUP_DATABASE_PAYLOAD_MODE=APP_ENCRYPTED- El administrador ha reconocido explícitamente el riesgo en la interfaz
Los archivos sin cifrar se bloquean para el almacenamiento remoto y para las cargas útiles con PII descifradas.
Configuración (.env). El destinatario age lo genera el kit de recuperación (Administración → Seguridad → Copia de seguridad) y se almacena en la instancia; no colocas ninguna clave en el .env. Define únicamente las dos variables de modo:
BACKUP_ENABLED=true
BACKUP_ARCHIVE_ENCRYPTION_MODE=AGE_RECIPIENT
BACKUP_DATABASE_PAYLOAD_MODE=APP_ENCRYPTED
# Alternative — encrypted archive + decrypted PII (cross-instance portability):
# BACKUP_DATABASE_PAYLOAD_MODE=PII_DECRYPTED
BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=false
Para desactivar el cifrado del archivo (solo local, no recomendado — los PII siguen cifrados en la carga útil):
BACKUP_ARCHIVE_ENCRYPTION_MODE=NONE
BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=true
Con
AGE_RECIPIENT, si aún no se ha generado ningún destinatario, la copia de seguridad falla de forma explícita en lugar de escribir un archivo sin proteger. La combinaciónNONE+PII_DECRYPTEDse rechaza.
Modos de carga útil de la base de datos
| Modo | Descripción |
|---|---|
APP_ENCRYPTED |
Predeterminado. Los campos PII permanecen cifrados con la clave de la aplicación DB_ENCRYPTION_KEY. La restauración requiere la misma clave de cifrado o una huella de clave coincidente. |
PII_DECRYPTED |
Los campos PII se descifran en una base de datos temporal antes del archivado. Los hashes de las contraseñas de usuario permanecen sin cambios. Requiere el cifrado de archivo AGE_RECIPIENT. Útil para la portabilidad entre instancias. |
Al restaurar una copia de seguridad PII_DECRYPTED en un destino con ENCRYPT_PII=true, el proceso de restauración vuelve a cifrar automáticamente los PII con la DB_ENCRYPTION_KEY del destino antes del arranque normal de la aplicación.
Kit de recuperación
Durante la configuración, el administrador genera (o importa) un destinatario age para las copias de seguridad cifradas. El kit de recuperación es un archivo ZIP que contiene:
quotenode-age-identity.txt— la identidad age privada (se devuelve solo una vez)quotenode-age-recipient.txt— el destinatario público y la huellaquotenode-backup-verify.sh— script de verificación local (Linux/macOS)quotenode-backup-verify.ps1— script de verificación local (Windows)README.md— comandos de verificación y restauración
Crítico: La identidad age privada se devuelve solo una vez durante la generación del kit. Si se pierde, los archivos cifrados no podrán descifrarse. Guarda el kit de recuperación en una ubicación sin conexión segura.
El administrador debe confirmar que el kit se ha guardado y probado antes de que el asistente de configuración marque la copia de seguridad como lista para producción.
Verificación local de copias de seguridad
Las copias de seguridad descargadas pueden verificarse fuera de la aplicación en ejecución mediante los scripts del kit de recuperación:
scripts/quotenode-backup-verify.sh \
--archive /path/backup.tar.age \
--identity /path/quotenode-age-identity.txt \
--expected-sha256 <sha256-from-ui-or-sidecar>
El verificador:
- Comprueba la suma de comprobación SHA-256 del archivo cifrado frente al valor esperado o al archivo adjunto
.sha256. - Descifra con
age -den un directorio temporal privado. - Valida las entradas tar (rechaza rutas absolutas,
.., enlaces simbólicos y archivos de dispositivo). - Comprueba que
backup-manifest.jsonexiste y tiene una versión admitida. - Ejecuta
sha256sum -c checksums.sha256. - Ejecuta
pg_restore --list db.dumpcuandopg_restoreestá disponible. - Elimina por defecto los datos temporales descifrados (usa
--keep-decryptedpara conservarlos).
Copia de seguridad en línea (predeterminada)
Las copias de seguridad en línea se ejecutan mientras la aplicación atiende solicitudes. No hay tiempo de inactividad.
pg_dumpen formato personalizado crea una instantánea coherente a nivel transaccional sin bloquear las tablas ni las consultas.- El almacenamiento de archivos se archiva en paralelo — los archivos subidos y los PDF son inmutables tras su creación, por lo que el archivo siempre es coherente.
- PostgreSQL no necesita detenerse para obtener un volcado de base de datos coherente.
Cómo se ejecutan las copias de seguridad: elegir una topología
Hay dos formas de ejecutar de forma continua las copias de seguridad programadas. Ambas producen archivos idénticos — solo se diferencian en dónde se ejecuta la tarea de copia de seguridad.
| Topología | Cuándo usarla | Contenedores |
|---|---|---|
| Worker dedicado (predeterminado) | Producción. Las copias de seguridad se ejecutan en un contenedor backup-worker independiente, por lo que nunca compiten con el tráfico web por memoria o CPU. |
backend web + backup-worker |
| En proceso | Portátil / evaluación / instalaciones pequeñas de un solo host. El backend web ejecuta también las copias de seguridad — un contenedor menos que operar. | solo backend web |
La tarea de copia de seguridad en sí es el mismo pipeline de shell (pg_dump → archivado → cifrado) en ambos casos, ejecutado fuera del heap de la JVM, por lo que su coste de memoria es modesto.
Cómo seleccionar una topología
Dos variables de entorno funcionan en conjunto:
JOBS_MODEdecide qué contenedor ejecuta realmente la tarea de copia de seguridad programada.BACKUP_RUNTIME_PROFILEes una etiqueta que se registra en los registros y manifiestos de las copias de seguridad para que puedas ver, a posteriori, cómo se produjo cada copia.
| Topología | Backend web | Worker de copia de seguridad |
|---|---|---|
| Worker dedicado (predeterminado) | JOBS_MODE=web + BACKUP_RUNTIME_PROFILE=WORKER |
contenedor JOBS_MODE=backup-only |
| En proceso | JOBS_MODE=all + BACKUP_RUNTIME_PROFILE=WEB_MAINTENANCE |
(ninguno) |
El archivo Compose de producción incluido usa la topología de worker dedicado. Las guías de portátil/evaluación usan en proceso (
JOBS_MODE=all+BACKUP_RUNTIME_PROFILE=WEB_MAINTENANCE) para que solo ejecutes un contenedor de backend.
Referencia de perfiles de ejecución
BACKUP_RUNTIME_PROFILE acepta cuatro valores. Los dos primeros son para las topologías continuas anteriores; los dos últimos, para copias de seguridad puntuales gestionadas por el operador.
| Perfil | Descripción |
|---|---|
WORKER |
Predeterminado. Un contenedor backup-worker permanente ejecuta las copias de seguridad programadas mediante cron. |
WEB_MAINTENANCE |
El backend web ejecuta directamente las copias de seguridad programadas. No se necesita ningún contenedor worker adicional. |
ONE_SHOT |
Un cron externo o un operador ejecuta scripts/backup-one-shot.sh y finaliza. Sin worker permanente. |
OFFLINE_MAINTENANCE |
Un script automatizado anuncia la inactividad, detiene frontend/backend/backup-worker, mantiene PostgreSQL en ejecución, ejecuta la copia de seguridad, reinicia los servicios y verifica su estado. |
Copia de seguridad puntual (one-shot)
COMPOSE_PROJECT_NAME=quotenode COMPOSE_ENV_FILE=infra/.env.prod \
BACKUP_ARCHIVE_ENCRYPTION_MODE=AGE_RECIPIENT \
BACKUP_AGE_RECIPIENT=age1... \
bash scripts/backup-one-shot.sh
Mantenimiento sin conexión
COMPOSE_PROJECT_NAME=quotenode COMPOSE_ENV_FILE=infra/.env.prod \
BACKUP_OFFLINE_NOTICE_SECONDS=300 \
bash scripts/backup-offline-maintenance.sh
El script de mantenimiento sin conexión:
- Anuncia un mantenimiento programado (período de gracia configurable).
- Opcionalmente habilita una página de mantenimiento estática mientras los servicios están detenidos.
- Detiene el frontend, el backend y el backup-worker (mantiene PostgreSQL en ejecución).
- Ejecuta una copia de seguridad puntual.
- Reinicia todos los servicios.
- Verifica el estado del backend.
- Escribe un diario de mantenimiento JSONL a nivel de host.
Si el script falla en cualquier punto, una trampa de seguridad reinicia los servicios automáticamente.
Usa --dry-run para previsualizar el plan sin ejecutarlo:
bash scripts/backup-offline-maintenance.sh --dry-run
Programación
- Programación predeterminada: todos los días a las 2:00 (
0 0 2 * * *, configurable medianteBACKUP_CRON) - Activar/desactivar:
BACKUP_ENABLED=true|false - Activación manual: los administradores pueden activar una copia de seguridad inmediata desde el panel de administración o mediante la API (
POST /api/v1/admin/backup/trigger). El activador responde de inmediato (HTTP 202) mientras la copia de seguridad se ejecuta de forma asíncrona. - Protección frente a concurrencia: un bloqueo respaldado por la base de datos garantiza que solo se ejecute una copia de seguridad a la vez en todos los workers y activaciones manuales.
Para los perfiles ONE_SHOT y OFFLINE_MAINTENANCE, el planificador interno está desactivado — las copias de seguridad se gestionan externamente.
Opciones de almacenamiento
Almacenamiento local (predeterminado)
Las copias de seguridad se almacenan en BACKUP_LOCAL_DIR (predeterminado: /app/data/backups), mapeado a un volumen de Docker.
En producción, las copias de seguridad locales deben complementarse con copias remotas.
Almacenamiento remoto mediante rclone
Si BACKUP_RCLONE_REMOTE está configurado, las copias de seguridad se suben automáticamente a un destino remoto tras su creación local. rclone admite más de 70 proveedores de almacenamiento en la nube, incluidos los compatibles con S3, Google Cloud Storage, Azure Blob, SFTP y WebDAV.
Descarga de copias de seguridad
La descarga de archivos de copia de seguridad requiere autorización reforzada (step-up):
- Ve a Configuración > Copia de seguridad.
- Haz clic en Descargar en la entrada de copia de seguridad.
- Introduce tu contraseña actual (y el código TOTP si la MFA está activada).
- El sistema emite una concesión de descarga de un solo uso y de corta duración (predeterminado: 120 segundos).
- El archivo se descarga automáticamente.
El token de concesión se almacena únicamente como hash SHA-256. La creación de la concesión y la descarga se auditan y se notifica a los demás administradores.
Nota: una descarga directa sin concesión devuelve 403. Las copias de seguridad en almacenamiento remoto no se pueden descargar a través del panel de administración.
Metadatos y registro de auditoría
Cada copia de seguridad se registra en la tabla backup_logs con:
- Estado (RUNNING, SUCCESS, FAILED)
- Iniciada por (SCHEDULER o ADMIN)
- Hora de inicio/finalización
- Tamaño (bytes) y suma de comprobación SHA-256
- Destino (ruta local o URL remota)
- Versión del manifiesto y versión de la app de origen
- Modo de cifrado del archivo y huella
- Modo de carga útil de la base de datos
- Perfil de ejecución
- Estado de verificación (NOT_RUN, PASSED, FAILED, SKIPPED_TIMEOUT)
- Estado de la prueba de restauración y estado de compatibilidad de restauración
Restauración
Uso de scripts/restore.sh
El script de restauración canónico gestiona el ciclo de vida completo:
COMPOSE_PROJECT_NAME=quotenode \
COMPOSE_ENV_FILE=infra/.env.prod \
RESTORE_AGE_IDENTITY_FILE=/path/to/quotenode-age-identity.txt \
[email protected] \
RESTORE_ADMIN_PASSWORD_FILE=/path/to/password \
bash scripts/restore.sh /path/to/backup.tar.age
El script:
- Extrae y valida el archivo (manifiesto, sumas de comprobación, seguridad de las entradas tar).
- Ejecuta comprobaciones previas (capacidad de disco, versión de PostgreSQL, conectividad con la base de datos).
- Crea una copia de seguridad de protección del destino antes de cualquier operación destructiva.
- Escribe un diario de restauración a nivel de host (JSONL, sobrevive al reemplazo de la base de datos).
- Restaura la base de datos.
- Prepara los archivos en un directorio temporal y los intercambia en su lugar.
- Conserva la identidad del destino (ID de instancia, metadatos de cifrado de copias de seguridad, estado de la licencia).
- Borra las sesiones, los enlaces públicos de ofertas y los tokens de notificación.
- Restablece o fuerza el cambio del acceso de administrador.
- Vuelve a cifrar los PII si la copia de seguridad es
PII_DECRYPTEDy el destino tieneENCRYPT_PII=true.
Restauración entre instancias
Al restaurar una copia de seguridad de una instalación diferente, el modo predeterminado es PRESERVE_TARGET_IDENTITY:
- El ID de instancia de destino, el destinatario age de las copias de seguridad, los metadatos del kit de recuperación y el estado de la licencia se conservan.
- Las sesiones, los enlaces públicos de ofertas y los tokens de notificación se borran.
- La contraseña de administrador se restablece/se fuerza su cambio.
- Los datos de negocio se reemplazan por completo (sin fusión).
Compatibilidad de restauración
La comprobación previa de restauración valida:
- Copia de seguridad más antigua en una app más nueva: permitido (compatible).
- Copia de seguridad más nueva en una app más antigua: bloqueado.
- Manifiesto ausente: bloqueado.
- Huella de clave de cifrado incorrecta para una carga útil
APP_ENCRYPTED: bloqueado antes del trabajo destructivo. PII_DECRYPTEDsin cifrado de archivo: bloqueado.
Garantía de acceso de administrador
La restauración garantiza el acceso de administrador mediante una de estas opciones:
RESTORE_ADMIN_PASSWORD_FILE— contraseña proporcionada por el operador (definida conforcePasswordChange=true)RESTORE_ADMIN_PASSWORD— solo para uso local/de pruebas- Token de restablecimiento de un solo uso generado automáticamente — impreso en la consola y en el diario a nivel de host
Ejecución de prueba (dry run)
RESTORE_DRY_RUN=true bash scripts/restore.sh /path/to/backup.tar.age
Valida el archivo, comprueba las sumas de comprobación y el manifiesto, e informa del plan de restauración sin modificar nada.
Recuperación ante desastres — reconstrucción desde cero
Tu servidor ha desaparecido. Tienes un archivo de copia de seguridad (.tar.age o .tar) y un servidor nuevo con Docker. Así es como se restaura.
Paso 1 — Preparar el nuevo servidor
mkdir quotenode && cd quotenode
Configura docker-compose.yml y .env como se describe en la Guía de instalación.
Crítico para las copias APP_ENCRYPTED: usa la misma
DB_ENCRYPTION_KEYque en tu instalación original. Sin ella, los PII cifrados quedarán permanentemente ilegibles.
Crítico para los archivos cifrados: necesitas el archivo de identidad privada del kit de recuperación (
quotenode-age-identity.txt) para descifrar el archivo.
Paso 2 — Copiar la copia de seguridad y el script de restauración
scp backup.tar.age user@new-server:~/quotenode/
scp scripts/restore.sh user@new-server:~/quotenode/
Paso 3 — Ejecutar la restauración
RESTORE_AGE_IDENTITY_FILE=./quotenode-age-identity.txt \
[email protected] \
RESTORE_ADMIN_PASSWORD_FILE=./admin-password \
./restore.sh --fresh-install backup.tar.age
Paso 4 — Verificar
Abre tu dominio e inicia sesión. Todos los clientes, ofertas, productos y configuraciones deberían estar exactamente como estaban en el momento de la copia de seguridad.
Retención de copias de seguridad
- Local: se conservan las
BACKUP_RETENTION_DAILY(predeterminado: 7) copias de seguridad correctas más recientes. - Remoto: configura políticas de ciclo de vida en tu proveedor de almacenamiento.
- Las copias de seguridad con PII descifradas deberían tener una retención más estricta debido a su naturaleza sensible.
Tiempo de espera de la copia de seguridad
Cada copia de seguridad tiene un tiempo de espera configurable (predeterminado: 30 minutos mediante BACKUP_TIMEOUT_MINUTES). Si se supera, la copia de seguridad se termina y se registra como FAILED.
Estrategia recomendada
- Activa las copias de seguridad automáticas diarias con cifrado
AGE_RECIPIENTy almacenamiento remoto. - Genera un kit de recuperación durante la configuración y guárdalo en una ubicación sin conexión segura.
- Verifica tu kit de recuperación frente a una copia de seguridad real usando los scripts de verificación incluidos.
- Prueba periódicamente los procedimientos de restauración — una copia de seguridad que nunca se ha probado no es una copia de seguridad.
- Descarga una copia de seguridad al menos una vez al mes a una ubicación sin conexión (requiere autorización reforzada).
- Supervisa el estado de las copias de seguridad en el panel de administración — aparecen avisos para las copias obsoletas, fallidas o sin cifrar.
- Conserva al menos 7 días de historial de copias de seguridad (el valor predeterminado).