Vai al contenuto
Q
QuoteNode

Wiki

Backup e ripristino

Come QuoteNode gestisce i backup di database e file: archivi cifrati con age, ripristino guidato da manifest, kit di ripristino e profili di runtime.

Backup e ripristino

QuoteNode include un sistema di backup integrato che protegge i tuoi dati commerciali da guasti hardware, eliminazioni accidentali e incidenti operativi.

Cosa viene incluso nel backup

Ogni backup crea un archivio contenente:

  1. Dump del database — un dump PostgreSQL completo in formato personalizzato (pg_dump --format=custom), comprese tutte le tabelle, le sequenze e i vincoli.
  2. Archiviazione dei file — tutti i file caricati (immagini dei prodotti, loghi aziendali) e i PDF generati, archiviati come files.tar.gz con percorsi relativi.
  3. Manifest del backup — un file backup-manifest.json contenente la versione di origine, la modalità di cifratura, la modalità del payload del database, i checksum e i metadati di compatibilità del ripristino.
  4. Checksum di integrità — un file checksums.sha256 contenente gli hash SHA-256 di tutti i componenti dell’archivio.

Cifratura dell’archivio

I nuovi backup cifrati usano la cifratura per destinatario age (AGE_RECIPIENT). È l’unico modello di cifratura supportato per i nuovi archivi.

  • Il processo di backup usa solo un destinatario age pubblico (age1...) — sul server non è necessaria alcuna chiave privata per creare i backup.
  • Gli archivi cifrati hanno l’estensione .tar.age.
  • Il checksum SHA-256 dell’archivio cifrato viene memorizzato accanto all’archivio per la verifica lato operatore.
  • La decifratura richiede la corrispondente identità age privata, conservata esclusivamente nel kit di ripristino dell’operatore.
  • L’identità age privata grezza non viene mai memorizzata nel database, nei log, nei DTO o negli argomenti di processo dopo la conferma della configurazione.

Backup locali non cifrati

I backup locali non cifrati (.tar) sono consentiti solo quando tutte le condizioni seguenti sono vere:

  • BACKUP_ARCHIVE_ENCRYPTION_MODE=NONE
  • BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=true
  • La destinazione è lo storage locale
  • BACKUP_DATABASE_PAYLOAD_MODE=APP_ENCRYPTED
  • L’amministratore ha riconosciuto esplicitamente il rischio nell’interfaccia

Gli archivi non cifrati sono bloccati per lo storage remoto e per i payload con PII decifrate.

Configurazione (.env). Il destinatario age viene generato dal kit di ripristino (Amministrazione → Sicurezza → Backup) e memorizzato nell’istanza — non inserisci alcuna chiave nel file .env. Imposta solo le due variabili di modalità:

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

Per disattivare la cifratura dell’archivio (solo locale, sconsigliato — le PII restano comunque cifrate nel payload):

BACKUP_ARCHIVE_ENCRYPTION_MODE=NONE
BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=true

Con AGE_RECIPIENT, se non è ancora stato generato alcun destinatario, il backup fallisce in modo esplicito anziché scrivere un archivio non protetto. La combinazione NONE + PII_DECRYPTED viene rifiutata.

Modalità del payload del database

Modalità Descrizione
APP_ENCRYPTED Predefinita. I campi PII rimangono cifrati con la chiave applicativa DB_ENCRYPTION_KEY. Il ripristino richiede la stessa chiave di cifratura o un’impronta di chiave corrispondente.
PII_DECRYPTED I campi PII vengono decifrati in un database temporaneo prima dell’archiviazione. Gli hash delle password utente restano invariati. Richiede la cifratura dell’archivio AGE_RECIPIENT. Utile per la portabilità tra istanze.

Quando si ripristina un backup PII_DECRYPTED in una destinazione con ENCRYPT_PII=true, il processo di ripristino ricifra automaticamente le PII con la DB_ENCRYPTION_KEY della destinazione prima del normale avvio dell’applicazione.

Kit di ripristino

Durante la configurazione, l’amministratore genera (o importa) un destinatario age per i backup cifrati. Il kit di ripristino è un file ZIP contenente:

  • quotenode-age-identity.txt — l’identità age privata (restituita una sola volta)
  • quotenode-age-recipient.txt — il destinatario pubblico e l’impronta
  • quotenode-backup-verify.sh — script di verifica locale (Linux/macOS)
  • quotenode-backup-verify.ps1 — script di verifica locale (Windows)
  • README.md — comandi di verifica e ripristino

Critico: l’identità age privata viene restituita una sola volta durante la generazione del kit. Se persa, gli archivi cifrati non potranno essere decifrati. Conserva il kit di ripristino in un luogo offline sicuro.

L’amministratore deve confermare che il kit è stato salvato e testato prima che la procedura guidata di configurazione contrassegni il backup come pronto per la produzione.

Verifica locale dei backup

I backup scaricati possono essere verificati al di fuori dell’applicazione in esecuzione usando gli script del kit di ripristino:

scripts/quotenode-backup-verify.sh \
  --archive /path/backup.tar.age \
  --identity /path/quotenode-age-identity.txt \
  --expected-sha256 <sha256-from-ui-or-sidecar>

Il verificatore:

  1. Controlla il checksum SHA-256 dell’archivio cifrato rispetto al valore atteso o al file affiancato .sha256.
  2. Decifra con age -d in una directory temporanea privata.
  3. Valida le voci tar (rifiuta percorsi assoluti, .., collegamenti simbolici e file di dispositivo).
  4. Verifica che backup-manifest.json esista e abbia una versione supportata.
  5. Esegue sha256sum -c checksums.sha256.
  6. Esegue pg_restore --list db.dump quando pg_restore è disponibile.
  7. Rimuove per impostazione predefinita i dati temporanei decifrati (usa --keep-decrypted per conservarli).

Backup online (predefinito)

I backup online vengono eseguiti mentre l’applicazione serve le richieste. Non c’è alcun tempo di inattività.

  • pg_dump in formato personalizzato crea uno snapshot transazionalmente coerente senza bloccare le tabelle né le query.
  • L’archiviazione dei file avviene in parallelo — i file caricati e i PDF sono immutabili dopo la creazione, quindi l’archivio è sempre coerente.
  • PostgreSQL non deve fermarsi per produrre un dump del database coerente.

Come vengono eseguiti i backup: scegliere una topologia

Esistono due modi per eseguire in modo continuo i backup pianificati. Entrambi producono archivi identici — differiscono solo per dove viene eseguito il job di backup.

Topologia Quando usarla Container
Worker dedicato (predefinito) Produzione. I backup vengono eseguiti in un container backup-worker separato, quindi non competono mai con il traffico web per memoria o CPU. backend web + backup-worker
In-process Laptop / valutazione / piccole installazioni su singolo host. Il backend web esegue anche i backup — un container in meno da gestire. solo backend web

Il job di backup in sé è la stessa pipeline shell (pg_dump → archiviazione → cifratura) in entrambi i casi, eseguita al di fuori dell’heap della JVM, quindi il suo costo di memoria è modesto.

Come selezionare una topologia

Due variabili d’ambiente lavorano insieme:

  • JOBS_MODE decide quale container esegue effettivamente il job di backup pianificato.
  • BACKUP_RUNTIME_PROFILE è un’etichetta registrata nei log e nei manifest dei backup, in modo da poter vedere, a posteriori, come è stato prodotto ogni backup.
Topologia Backend web Backup worker
Worker dedicato (predefinito) JOBS_MODE=web + BACKUP_RUNTIME_PROFILE=WORKER container JOBS_MODE=backup-only
In-process JOBS_MODE=all + BACKUP_RUNTIME_PROFILE=WEB_MAINTENANCE (nessuno)

Il file Compose di produzione incluso usa la topologia worker dedicato. Le guide laptop/valutazione usano in-process (JOBS_MODE=all + BACKUP_RUNTIME_PROFILE=WEB_MAINTENANCE) così da eseguire un solo container backend.

Riferimento dei profili di runtime

BACKUP_RUNTIME_PROFILE accetta quattro valori. I primi due sono per le topologie continue sopra descritte; gli ultimi due per backup occasionali gestiti dall’operatore.

Profilo Descrizione
WORKER Predefinito. Un container backup-worker permanente esegue i backup pianificati tramite cron.
WEB_MAINTENANCE Il backend web esegue direttamente i backup pianificati. Non è necessario alcun container worker aggiuntivo.
ONE_SHOT Un cron esterno o un operatore esegue scripts/backup-one-shot.sh e termina. Nessun worker permanente.
OFFLINE_MAINTENANCE Uno script automatizzato annuncia il tempo di inattività, arresta frontend/backend/backup-worker, mantiene PostgreSQL in esecuzione, esegue il backup, riavvia i servizi e ne verifica lo stato.

Backup occasionale (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

Manutenzione offline

COMPOSE_PROJECT_NAME=quotenode COMPOSE_ENV_FILE=infra/.env.prod \
  BACKUP_OFFLINE_NOTICE_SECONDS=300 \
  bash scripts/backup-offline-maintenance.sh

Lo script di manutenzione offline:

  1. Annuncia una manutenzione pianificata (periodo di tolleranza configurabile).
  2. Abilita facoltativamente una pagina di manutenzione statica mentre i servizi sono inattivi.
  3. Arresta frontend, backend e backup-worker (mantiene PostgreSQL in esecuzione).
  4. Esegue un backup occasionale.
  5. Riavvia tutti i servizi.
  6. Verifica lo stato del backend.
  7. Scrive un journal di manutenzione JSONL a livello di host.

Se lo script fallisce in qualsiasi momento, un trap di sicurezza riavvia automaticamente i servizi.

Usa --dry-run per visualizzare l’anteprima del piano senza eseguirlo:

bash scripts/backup-offline-maintenance.sh --dry-run

Pianificazione

  • Pianificazione predefinita: ogni giorno alle 2:00 (0 0 2 * * *, configurabile tramite BACKUP_CRON)
  • Attiva/disattiva: BACKUP_ENABLED=true|false
  • Attivazione manuale: gli amministratori possono attivare un backup immediato dal pannello di amministrazione o tramite API (POST /api/v1/admin/backup/trigger). L’attivazione risponde immediatamente (HTTP 202) mentre il backup viene eseguito in modo asincrono.
  • Protezione dalla concorrenza: un lock basato sul database garantisce che venga eseguito un solo backup alla volta su tutti i worker e le attivazioni manuali.

Per i profili ONE_SHOT e OFFLINE_MAINTENANCE, lo scheduler interno è disattivato — i backup sono gestiti esternamente.

Opzioni di archiviazione

Storage locale (predefinito)

I backup vengono memorizzati in BACKUP_LOCAL_DIR (predefinito: /app/data/backups), mappato su un volume Docker.

In produzione, i backup locali dovrebbero essere integrati con copie remote.

Storage remoto tramite rclone

Se BACKUP_RCLONE_REMOTE è configurato, i backup vengono caricati automaticamente su una destinazione remota dopo la creazione locale. rclone supporta oltre 70 provider di cloud storage, inclusi quelli compatibili con S3, Google Cloud Storage, Azure Blob, SFTP e WebDAV.

Download dei backup

Il download degli archivi di backup richiede un’autorizzazione rafforzata (step-up):

  1. Vai su Impostazioni > Backup.
  2. Fai clic su Scarica sulla voce del backup.
  3. Inserisci la tua password attuale (e il codice TOTP se l’MFA è attiva).
  4. Il sistema rilascia una concessione di download monouso e di breve durata (predefinito: 120 secondi).
  5. L’archivio viene scaricato automaticamente.

Il token di concessione viene memorizzato solo come hash SHA-256. La creazione della concessione e il download vengono registrati a fini di audit e gli altri amministratori vengono notificati.

Nota: un download diretto senza concessione restituisce 403. I backup su storage remoto non possono essere scaricati tramite il pannello di amministrazione.

Metadati e traccia di audit

Ogni backup viene tracciato nella tabella backup_logs con:

  • Stato (RUNNING, SUCCESS, FAILED)
  • Avviato da (SCHEDULER o ADMIN)
  • Ora di inizio/completamento
  • Dimensione (byte) e checksum SHA-256
  • Destinazione (percorso locale o URL remoto)
  • Versione del manifest e versione dell’app di origine
  • Modalità di cifratura dell’archivio e impronta
  • Modalità del payload del database
  • Profilo di runtime
  • Stato di verifica (NOT_RUN, PASSED, FAILED, SKIPPED_TIMEOUT)
  • Stato del test di ripristino e stato di compatibilità del ripristino

Ripristino

Uso di scripts/restore.sh

Lo script di ripristino canonico gestisce l’intero ciclo di vita:

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

Lo script:

  1. Estrae e valida l’archivio (manifest, checksum, sicurezza delle voci tar).
  2. Esegue controlli preliminari (capacità del disco, versione di PostgreSQL, connettività al database).
  3. Crea un backup di sicurezza della destinazione prima di qualsiasi operazione distruttiva.
  4. Scrive un journal di ripristino a livello di host (JSONL, sopravvive alla sostituzione del database).
  5. Ripristina il database.
  6. Prepara i file in una directory temporanea e li scambia al loro posto.
  7. Preserva l’identità della destinazione (ID istanza, metadati di cifratura dei backup, stato della licenza).
  8. Cancella sessioni, link pubblici delle offerte e token di notifica.
  9. Reimposta o forza la modifica dell’accesso amministratore.
  10. Ricifra le PII se il backup è PII_DECRYPTED e la destinazione ha ENCRYPT_PII=true.

Ripristino tra istanze

Quando si ripristina un backup da un’installazione diversa, la modalità predefinita è PRESERVE_TARGET_IDENTITY:

  • L’ID istanza di destinazione, il destinatario age dei backup, i metadati del kit di ripristino e lo stato della licenza vengono preservati.
  • Le sessioni, i link pubblici delle offerte e i token di notifica vengono cancellati.
  • La password amministratore viene reimpostata/ne viene forzata la modifica.
  • I dati aziendali vengono completamente sostituiti (nessuna fusione).

Compatibilità del ripristino

Il controllo preliminare del ripristino valida:

  • Backup più vecchio in un’app più recente: consentito (compatibile).
  • Backup più recente in un’app più vecchia: bloccato.
  • Manifest mancante: bloccato.
  • Impronta della chiave di cifratura errata per un payload APP_ENCRYPTED: bloccato prima del lavoro distruttivo.
  • PII_DECRYPTED senza cifratura dell’archivio: bloccato.

Garanzia di accesso amministratore

Il ripristino garantisce l’accesso amministratore tramite una delle seguenti opzioni:

  • RESTORE_ADMIN_PASSWORD_FILE — password fornita dall’operatore (impostata con forcePasswordChange=true)
  • RESTORE_ADMIN_PASSWORD — solo per uso locale/di test
  • Token di reimpostazione monouso generato automaticamente — stampato sulla console e nel journal a livello di host

Esecuzione di prova (dry run)

RESTORE_DRY_RUN=true bash scripts/restore.sh /path/to/backup.tar.age

Valida l’archivio, controlla checksum e manifest e segnala il piano di ripristino senza modificare nulla.

Disaster recovery — ricostruzione da zero

Il tuo server non c’è più. Hai un archivio di backup (.tar.age o .tar) e un nuovo server con Docker. Ecco come effettuare il ripristino.

Passo 1 — Preparare il nuovo server

mkdir quotenode && cd quotenode

Configura docker-compose.yml e .env come descritto nella Guida all’installazione.

Critico per i backup APP_ENCRYPTED: usa la stessa DB_ENCRYPTION_KEY dell’installazione originale. Senza di essa, le PII cifrate saranno permanentemente illeggibili.

Critico per gli archivi cifrati: ti serve il file di identità privata del kit di ripristino (quotenode-age-identity.txt) per decifrare l’archivio.

Passo 2 — Copiare il backup e lo script di ripristino

scp backup.tar.age user@new-server:~/quotenode/
scp scripts/restore.sh user@new-server:~/quotenode/

Passo 3 — Eseguire il ripristino

RESTORE_AGE_IDENTITY_FILE=./quotenode-age-identity.txt \
  [email protected] \
  RESTORE_ADMIN_PASSWORD_FILE=./admin-password \
  ./restore.sh --fresh-install backup.tar.age

Passo 4 — Verificare

Apri il tuo dominio ed effettua l’accesso. Tutti i clienti, le offerte, i prodotti e le impostazioni dovrebbero essere esattamente come erano al momento del backup.

Conservazione dei backup

  • Locale: vengono conservati i BACKUP_RETENTION_DAILY (predefinito: 7) backup riusciti più recenti.
  • Remoto: configura le policy di ciclo di vita presso il tuo provider di storage.
  • I backup con PII decifrate dovrebbero avere una conservazione più rigorosa per via della loro natura sensibile.

Timeout dei backup

Ogni backup ha un timeout configurabile (predefinito: 30 minuti tramite BACKUP_TIMEOUT_MINUTES). Se viene superato, il backup viene terminato e registrato come FAILED.

Strategia consigliata

  1. Attiva i backup automatici giornalieri con cifratura AGE_RECIPIENT e storage remoto.
  2. Genera un kit di ripristino durante la configurazione e conservalo in un luogo offline sicuro.
  3. Verifica il tuo kit di ripristino rispetto a un backup reale usando gli script di verifica inclusi.
  4. Testa periodicamente le procedure di ripristino — un backup mai testato non è un backup.
  5. Scarica un backup almeno una volta al mese in una posizione offline (richiede autorizzazione rafforzata).
  6. Monitora lo stato dei backup nel pannello di amministrazione — compaiono avvisi per i backup obsoleti, falliti o non cifrati.
  7. Conserva almeno 7 giorni di cronologia dei backup (il valore predefinito).

Last reviewed: Recently