Zum Inhalt springen
Q
QuoteNode

Wiki

Backup und Wiederherstellung

Wie QuoteNode Datenbank- und Datei-Backups handhabt — age-verschlüsselte Archive, manifestgesteuerte Wiederherstellung, Recovery-Kits und Runtime-Profile.

Backup und Wiederherstellung

QuoteNode enthält ein integriertes Backup-System, das Ihre Geschäftsdaten vor Hardwareausfällen, versehentlichem Löschen und betrieblichen Vorfällen schützt.

Was gesichert wird

Jedes Backup erstellt ein Archiv mit:

  1. Datenbank-Dump — ein vollständiger PostgreSQL-Dump im Custom-Format (pg_dump --format=custom), einschließlich aller Tabellen, Sequenzen und Constraints.
  2. Dateispeicher — alle hochgeladenen Dateien (Produktbilder, Firmenlogos) und generierten PDFs, archiviert als files.tar.gz mit relativen Pfaden.
  3. Backup-Manifest — eine Datei backup-manifest.json mit Quellversion, Verschlüsselungsmodus, Datenbank-Payload-Modus, Prüfsummen und Metadaten zur Wiederherstellungskompatibilität.
  4. Integritäts-Prüfsummen — eine Datei checksums.sha256 mit SHA-256-Hashes aller Archivkomponenten.

Archivverschlüsselung

Neue verschlüsselte Backups verwenden die age-Empfängerverschlüsselung (AGE_RECIPIENT). Dies ist das einzige unterstützte Verschlüsselungsmodell für neue Archive.

  • Der Backup-Prozess verwendet ausschließlich einen öffentlichen age-Empfänger (age1...) — auf dem Server wird kein privater Schlüssel zum Erstellen von Backups benötigt.
  • Verschlüsselte Archive haben die Endung .tar.age.
  • Die SHA-256-Prüfsumme des verschlüsselten Archivs wird neben dem Archiv abgelegt, damit der Betreiber sie verifizieren kann.
  • Die Entschlüsselung erfordert die passende private age-Identität, die ausschließlich im Recovery-Kit des Betreibers aufbewahrt wird.
  • Die rohe private age-Identität wird nach der Einrichtungsbestätigung niemals in der Datenbank, in Logs, in DTOs oder in Prozessargumenten gespeichert.

Unverschlüsselte lokale Backups

Lokale unverschlüsselte Backups (.tar) sind nur zulässig, wenn alle folgenden Bedingungen erfüllt sind:

  • BACKUP_ARCHIVE_ENCRYPTION_MODE=NONE
  • BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=true
  • Das Ziel ist lokaler Speicher
  • BACKUP_DATABASE_PAYLOAD_MODE=APP_ENCRYPTED
  • Der Administrator hat das Risiko in der Oberfläche ausdrücklich bestätigt

Unverschlüsselte Archive werden für Remote-Speicher und für PII-entschlüsselte Payloads blockiert.

Konfiguration (.env). Der age-Empfänger wird vom Recovery-Kit (Admin → Sicherheit → Backup) generiert und in der Instanz gespeichert — Sie hinterlegen keinen Schlüssel in der .env. Setzen Sie nur die beiden Modus-Variablen:

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

So deaktivieren Sie die Archivverschlüsselung (nur lokal, nicht empfohlen — PII bleibt im Payload weiterhin verschlüsselt):

BACKUP_ARCHIVE_ENCRYPTION_MODE=NONE
BACKUP_ALLOW_UNENCRYPTED_LOCAL_BACKUP=true

Bei AGE_RECIPIENT schlägt das Backup laut fehl, wenn noch kein Empfänger generiert wurde, anstatt ein ungeschütztes Archiv zu schreiben. Die Kombination NONE + PII_DECRYPTED wird abgelehnt.

Datenbank-Payload-Modi

Modus Beschreibung
APP_ENCRYPTED Standard. PII-Felder bleiben mit dem Anwendungsschlüssel DB_ENCRYPTION_KEY verschlüsselt. Die Wiederherstellung erfordert denselben Verschlüsselungsschlüssel oder einen passenden Schlüssel-Fingerabdruck.
PII_DECRYPTED PII-Felder werden vor der Archivierung in eine temporäre Datenbank entschlüsselt. Passwort-Hashes der Benutzer bleiben unverändert. Erfordert die Archivverschlüsselung AGE_RECIPIENT. Nützlich für die Portabilität zwischen Instanzen.

Beim Wiederherstellen eines PII_DECRYPTED-Backups in ein Ziel mit ENCRYPT_PII=true verschlüsselt der Wiederherstellungsprozess die PII automatisch mit dem DB_ENCRYPTION_KEY des Ziels neu, bevor die Anwendung normal startet.

Recovery-Kit

Während der Einrichtung generiert (oder importiert) der Administrator einen age-Empfänger für verschlüsselte Backups. Das Recovery-Kit ist eine ZIP-Datei mit:

  • quotenode-age-identity.txt — die private age-Identität (wird nur einmal zurückgegeben)
  • quotenode-age-recipient.txt — der öffentliche Empfänger und der Fingerabdruck
  • quotenode-backup-verify.sh — lokales Verifizierungsskript (Linux/macOS)
  • quotenode-backup-verify.ps1 — lokales Verifizierungsskript (Windows)
  • README.md — Befehle für Verifizierung und Wiederherstellung

Kritisch: Die private age-Identität wird nur einmal bei der Kit-Erstellung zurückgegeben. Geht sie verloren, können verschlüsselte Archive nicht mehr entschlüsselt werden. Bewahren Sie das Recovery-Kit an einem sicheren Offline-Ort auf.

Der Administrator muss bestätigen, dass das Kit gespeichert und getestet wurde, bevor der Einrichtungsassistent das Backup als produktionsbereit markiert.

Lokale Backup-Verifizierung

Heruntergeladene Backups können außerhalb der laufenden Anwendung mit den Skripten des Recovery-Kits verifiziert werden:

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

Das Verifizierungstool:

  1. Prüft die SHA-256-Prüfsumme des verschlüsselten Archivs gegen den erwarteten Wert oder die .sha256-Begleitdatei.
  2. Entschlüsselt mit age -d in ein privates temporäres Verzeichnis.
  3. Validiert die tar-Einträge (lehnt absolute Pfade, .., Symlinks und Gerätedateien ab).
  4. Prüft, ob backup-manifest.json existiert und eine unterstützte Version hat.
  5. Führt sha256sum -c checksums.sha256 aus.
  6. Führt pg_restore --list db.dump aus, sofern pg_restore verfügbar ist.
  7. Entfernt standardmäßig die entschlüsselten temporären Daten (mit --keep-decrypted werden sie behalten).

Online-Backup (Standard)

Online-Backups laufen, während die Anwendung Anfragen bedient. Es gibt keine Ausfallzeit.

  • pg_dump im Custom-Format erstellt einen transaktional konsistenten Snapshot, ohne Tabellen zu sperren oder Abfragen zu blockieren.
  • Der Dateispeicher wird parallel archiviert — hochgeladene Dateien und PDFs sind nach der Erstellung unveränderlich, sodass das Archiv stets konsistent ist.
  • PostgreSQL muss für einen konsistenten Datenbank-Dump nicht angehalten werden.

Wie Backups laufen: Auswahl einer Topologie

Es gibt zwei Möglichkeiten, geplante Backups kontinuierlich auszuführen. Beide erzeugen identische Archive — sie unterscheiden sich nur darin, wo der Backup-Job ausgeführt wird.

Topologie Wann verwenden Container
Dedizierter Worker (Standard) Produktion. Backups laufen in einem separaten backup-worker-Container, sodass sie nie mit dem Web-Traffic um Speicher oder CPU konkurrieren. Web-Backend + backup-worker
In-Process Laptop / Evaluierung / kleine Single-Host-Installationen. Das Web-Backend führt die Backups selbst aus — ein Container weniger im Betrieb. nur Web-Backend

Der Backup-Job selbst ist in beiden Fällen dieselbe Shell-Pipeline (pg_dump → Archivierung → Verschlüsselung), die außerhalb des JVM-Heaps läuft, sodass ihr Speicherbedarf gering ist.

So wählen Sie eine Topologie

Zwei Umgebungsvariablen arbeiten zusammen:

  • JOBS_MODE entscheidet, welcher Container den geplanten Backup-Job tatsächlich ausführt.
  • BACKUP_RUNTIME_PROFILE ist ein Label, das in Backup-Logs und -Manifesten festgehalten wird, damit Sie im Nachhinein nachvollziehen können, wie jedes Backup erstellt wurde.
Topologie Web-Backend Backup-Worker
Dedizierter Worker (Standard) JOBS_MODE=web + BACKUP_RUNTIME_PROFILE=WORKER Container mit JOBS_MODE=backup-only
In-Process JOBS_MODE=all + BACKUP_RUNTIME_PROFILE=WEB_MAINTENANCE (keiner)

Die mitgelieferte Produktions-Compose-Datei verwendet die Topologie dedizierter Worker. Die Laptop-/Evaluierungsanleitungen verwenden In-Process (JOBS_MODE=all + BACKUP_RUNTIME_PROFILE=WEB_MAINTENANCE), sodass Sie nur einen Backend-Container betreiben.

Referenz der Runtime-Profile

BACKUP_RUNTIME_PROFILE akzeptiert vier Werte. Die ersten beiden gelten für die obigen kontinuierlichen Topologien; die letzten beiden für einmalige, vom Betreiber gesteuerte Backups.

Profil Beschreibung
WORKER Standard. Ein permanenter backup-worker-Container führt geplante Backups per Cron aus.
WEB_MAINTENANCE Das Web-Backend führt geplante Backups direkt aus. Es ist kein zusätzlicher Worker-Container erforderlich.
ONE_SHOT Ein externer Cron oder Betreiber führt scripts/backup-one-shot.sh aus und beendet sich. Kein permanenter Worker.
OFFLINE_MAINTENANCE Ein automatisiertes Skript kündigt die Ausfallzeit an, stoppt Frontend/Backend/backup-worker, lässt PostgreSQL laufen, führt das Backup aus, startet die Dienste neu und prüft den Zustand.

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

Offline-Wartung

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

Das Offline-Wartungsskript:

  1. Kündigt eine geplante Wartung an (konfigurierbare Karenzzeit).
  2. Aktiviert optional eine statische Wartungsseite, während die Dienste ausfallen.
  3. Stoppt Frontend, Backend und backup-worker (lässt PostgreSQL laufen).
  4. Führt ein einmaliges Backup aus.
  5. Startet alle Dienste neu.
  6. Prüft den Zustand des Backends.
  7. Schreibt ein JSONL-Wartungsjournal auf Host-Ebene.

Schlägt das Skript an irgendeiner Stelle fehl, startet ein Sicherheits-Trap die Dienste automatisch neu.

Verwenden Sie --dry-run, um den Plan ohne Ausführung anzuzeigen:

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

Zeitplanung

  • Standardzeitplan: täglich um 2:00 Uhr (0 0 2 * * *, konfigurierbar über BACKUP_CRON)
  • Aktivieren/Deaktivieren: BACKUP_ENABLED=true|false
  • Manuelle Auslösung: Administratoren können ein sofortiges Backup über das Admin-Panel oder die API auslösen (POST /api/v1/admin/backup/trigger). Der Auslöser antwortet sofort (HTTP 202), während das Backup asynchron läuft.
  • Parallelitätsschutz: Ein datenbankgestützter Lock stellt sicher, dass über alle Worker und manuellen Auslösungen hinweg immer nur ein Backup gleichzeitig läuft.

Für die Profile ONE_SHOT und OFFLINE_MAINTENANCE ist der interne Scheduler deaktiviert — Backups werden extern verwaltet.

Speicheroptionen

Lokaler Speicher (Standard)

Backups werden in BACKUP_LOCAL_DIR gespeichert (Standard: /app/data/backups), das auf ein Docker-Volume gemappt ist.

In der Produktion sollten lokale Backups durch Remote-Kopien ergänzt werden.

Remote-Speicher über rclone

Wenn BACKUP_RCLONE_REMOTE konfiguriert ist, werden Backups nach der lokalen Erstellung automatisch an ein Remote-Ziel hochgeladen. rclone unterstützt über 70 Cloud-Speicheranbieter, darunter S3-kompatible, Google Cloud Storage, Azure Blob, SFTP und WebDAV.

Backups herunterladen

Das Herunterladen von Backup-Archiven erfordert eine Step-up-Autorisierung:

  1. Navigieren Sie zu Einstellungen > Backup.
  2. Klicken Sie beim Backup-Eintrag auf Herunterladen.
  3. Geben Sie Ihr aktuelles Passwort ein (und den TOTP-Code, falls MFA aktiviert ist).
  4. Das System stellt eine kurzlebige, einmalige Download-Berechtigung aus (Standard: 120 Sekunden).
  5. Das Archiv wird automatisch heruntergeladen.

Das Berechtigungstoken wird nur als SHA-256-Hash gespeichert. Die Erstellung der Berechtigung und der Download werden auditiert und andere Administratoren werden benachrichtigt.

Hinweis: Ein direkter Download ohne Berechtigung gibt 403 zurück. Backups auf Remote-Speicher können nicht über das Admin-Panel heruntergeladen werden.

Metadaten & Audit-Trail

Jedes Backup wird in der Tabelle backup_logs erfasst, mit:

  • Status (RUNNING, SUCCESS, FAILED)
  • Initiiert von (SCHEDULER oder ADMIN)
  • Start-/Abschlusszeit
  • Größe (Bytes) und SHA-256-Prüfsumme
  • Ziel (lokaler Pfad oder Remote-URL)
  • Manifest-Version und Quell-App-Version
  • Archivverschlüsselungsmodus und Fingerabdruck
  • Datenbank-Payload-Modus
  • Runtime-Profil
  • Verifizierungsstatus (NOT_RUN, PASSED, FAILED, SKIPPED_TIMEOUT)
  • Wiederherstellungstest-Status und Wiederherstellungskompatibilitäts-Status

Wiederherstellung

Verwendung von scripts/restore.sh

Das kanonische Wiederherstellungsskript deckt den gesamten Lebenszyklus ab:

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

Das Skript:

  1. Extrahiert und validiert das Archiv (Manifest, Prüfsummen, Sicherheit der tar-Einträge).
  2. Führt Preflight-Prüfungen durch (Festplattenkapazität, PostgreSQL-Version, DB-Konnektivität).
  3. Erstellt ein Sicherheitsbackup des Ziels vor jeder destruktiven Operation.
  4. Schreibt ein Wiederherstellungsjournal auf Host-Ebene (JSONL, übersteht den DB-Austausch).
  5. Stellt die Datenbank wieder her.
  6. Bereitet Dateien in einem temporären Verzeichnis auf und tauscht sie an Ort und Stelle aus.
  7. Bewahrt die Zielidentität (Instanz-ID, Backup-Verschlüsselungsmetadaten, Lizenzstatus).
  8. Löscht Sessions, öffentliche Angebotslinks und Benachrichtigungstoken.
  9. Setzt den Admin-Zugang zurück oder erzwingt seine Änderung.
  10. Verschlüsselt PII neu, wenn das Backup PII_DECRYPTED ist und das Ziel ENCRYPT_PII=true hat.

Instanzübergreifende Wiederherstellung

Beim Wiederherstellen eines Backups aus einer anderen Installation ist der Standardmodus PRESERVE_TARGET_IDENTITY:

  • Ziel-Instanz-ID, age-Backup-Empfänger, Recovery-Kit-Metadaten und Lizenzstatus bleiben erhalten.
  • Sessions, öffentliche Angebotslinks und Benachrichtigungstoken werden gelöscht.
  • Das Admin-Passwort wird zurückgesetzt/dessen Änderung erzwungen.
  • Geschäftsdaten werden vollständig ersetzt (kein Merge).

Wiederherstellungskompatibilität

Der Wiederherstellungs-Preflight validiert:

  • Älteres Backup in neuere App: erlaubt (kompatibel).
  • Neueres Backup in ältere App: blockiert.
  • Fehlendes Manifest: blockiert.
  • Falscher Verschlüsselungsschlüssel-Fingerabdruck für APP_ENCRYPTED-Payload: blockiert vor destruktiven Arbeiten.
  • PII_DECRYPTED ohne Archivverschlüsselung: blockiert.

Garantie für Admin-Zugang

Die Wiederherstellung garantiert den Admin-Zugang über eine der folgenden Optionen:

  • RESTORE_ADMIN_PASSWORD_FILE — vom Betreiber bereitgestelltes Passwort (gesetzt mit forcePasswordChange=true)
  • RESTORE_ADMIN_PASSWORD — nur für lokale Verwendung/Tests
  • Automatisch generiertes Einmal-Reset-Token — wird auf der Konsole und im Journal auf Host-Ebene ausgegeben

Probelauf (Dry Run)

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

Validiert das Archiv, prüft Prüfsummen und Manifest und meldet den Wiederherstellungsplan, ohne etwas zu verändern.

Disaster Recovery — Wiederaufbau von Grund auf

Ihr Server ist weg. Sie haben ein Backup-Archiv (.tar.age oder .tar) und einen neuen Server mit Docker. So stellen Sie wieder her.

Schritt 1 — Den neuen Server vorbereiten

mkdir quotenode && cd quotenode

Richten Sie docker-compose.yml und .env wie im Installationshandbuch beschrieben ein.

Kritisch für APP_ENCRYPTED-Backups: Verwenden Sie denselben DB_ENCRYPTION_KEY wie bei Ihrer ursprünglichen Installation. Ohne ihn bleiben verschlüsselte PII dauerhaft unlesbar.

Kritisch für verschlüsselte Archive: Sie benötigen die private Identitätsdatei des Recovery-Kits (quotenode-age-identity.txt), um das Archiv zu entschlüsseln.

Schritt 2 — Backup und Wiederherstellungsskript kopieren

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

Schritt 3 — Die Wiederherstellung ausführen

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

Schritt 4 — Verifizieren

Öffnen Sie Ihre Domain und melden Sie sich an. Alle Kunden, Angebote, Produkte und Einstellungen sollten genau so sein, wie sie zum Backup-Zeitpunkt waren.

Backup-Aufbewahrung

  • Lokal: Die BACKUP_RETENTION_DAILY (Standard: 7) neuesten erfolgreichen Backups werden aufbewahrt.
  • Remote: Konfigurieren Sie Lifecycle-Richtlinien bei Ihrem Speicheranbieter.
  • PII-entschlüsselte Backups sollten aufgrund ihrer sensiblen Natur eine strengere Aufbewahrung haben.

Backup-Timeout

Jedes Backup hat ein konfigurierbares Timeout (Standard: 30 Minuten über BACKUP_TIMEOUT_MINUTES). Wird es überschritten, wird das Backup beendet und als FAILED erfasst.

Empfohlene Strategie

  1. Aktivieren Sie tägliche automatische Backups mit AGE_RECIPIENT-Verschlüsselung und Remote-Speicher.
  2. Generieren Sie ein Recovery-Kit während der Einrichtung und bewahren Sie es an einem sicheren Offline-Ort auf.
  3. Verifizieren Sie Ihr Recovery-Kit gegen ein echtes Backup mit den mitgelieferten Verifizierungsskripten.
  4. Testen Sie die Wiederherstellungsverfahren regelmäßig — ein Backup, das nie getestet wurde, ist kein Backup.
  5. Laden Sie mindestens monatlich ein Backup an einen Offline-Ort herunter (erfordert Step-up-Autorisierung).
  6. Überwachen Sie den Backup-Status im Admin-Panel — Warnungen erscheinen für veraltete, fehlgeschlagene oder unverschlüsselte Backups.
  7. Bewahren Sie mindestens 7 Tage Backup-Historie auf (der Standard).

Last reviewed: Recently