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:
- Dump del database — un dump PostgreSQL completo in formato personalizzato (
pg_dump --format=custom), comprese tutte le tabelle, le sequenze e i vincoli. - Archiviazione dei file — tutti i file caricati (immagini dei prodotti, loghi aziendali) e i PDF generati, archiviati come
files.tar.gzcon percorsi relativi. - Manifest del backup — un file
backup-manifest.jsoncontenente la versione di origine, la modalità di cifratura, la modalità del payload del database, i checksum e i metadati di compatibilità del ripristino. - Checksum di integrità — un file
checksums.sha256contenente 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=NONEBACKUP_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 combinazioneNONE+PII_DECRYPTEDviene 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’improntaquotenode-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:
- Controlla il checksum SHA-256 dell’archivio cifrato rispetto al valore atteso o al file affiancato
.sha256. - Decifra con
age -din una directory temporanea privata. - Valida le voci tar (rifiuta percorsi assoluti,
.., collegamenti simbolici e file di dispositivo). - Verifica che
backup-manifest.jsonesista e abbia una versione supportata. - Esegue
sha256sum -c checksums.sha256. - Esegue
pg_restore --list db.dumpquandopg_restoreè disponibile. - Rimuove per impostazione predefinita i dati temporanei decifrati (usa
--keep-decryptedper conservarli).
Backup online (predefinito)
I backup online vengono eseguiti mentre l’applicazione serve le richieste. Non c’è alcun tempo di inattività.
pg_dumpin 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_MODEdecide 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:
- Annuncia una manutenzione pianificata (periodo di tolleranza configurabile).
- Abilita facoltativamente una pagina di manutenzione statica mentre i servizi sono inattivi.
- Arresta frontend, backend e backup-worker (mantiene PostgreSQL in esecuzione).
- Esegue un backup occasionale.
- Riavvia tutti i servizi.
- Verifica lo stato del backend.
- 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 tramiteBACKUP_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):
- Vai su Impostazioni > Backup.
- Fai clic su Scarica sulla voce del backup.
- Inserisci la tua password attuale (e il codice TOTP se l’MFA è attiva).
- Il sistema rilascia una concessione di download monouso e di breve durata (predefinito: 120 secondi).
- 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:
- Estrae e valida l’archivio (manifest, checksum, sicurezza delle voci tar).
- Esegue controlli preliminari (capacità del disco, versione di PostgreSQL, connettività al database).
- Crea un backup di sicurezza della destinazione prima di qualsiasi operazione distruttiva.
- Scrive un journal di ripristino a livello di host (JSONL, sopravvive alla sostituzione del database).
- Ripristina il database.
- Prepara i file in una directory temporanea e li scambia al loro posto.
- Preserva l’identità della destinazione (ID istanza, metadati di cifratura dei backup, stato della licenza).
- Cancella sessioni, link pubblici delle offerte e token di notifica.
- Reimposta o forza la modifica dell’accesso amministratore.
- Ricifra le PII se il backup è
PII_DECRYPTEDe la destinazione haENCRYPT_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_DECRYPTEDsenza 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 conforcePasswordChange=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_KEYdell’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
- Attiva i backup automatici giornalieri con cifratura
AGE_RECIPIENTe storage remoto. - Genera un kit di ripristino durante la configurazione e conservalo in un luogo offline sicuro.
- Verifica il tuo kit di ripristino rispetto a un backup reale usando gli script di verifica inclusi.
- Testa periodicamente le procedure di ripristino — un backup mai testato non è un backup.
- Scarica un backup almeno una volta al mese in una posizione offline (richiede autorizzazione rafforzata).
- Monitora lo stato dei backup nel pannello di amministrazione — compaiono avvisi per i backup obsoleti, falliti o non cifrati.
- Conserva almeno 7 giorni di cronologia dei backup (il valore predefinito).