14 KiB
Monatlicher Offsite-Restore-Test
Der Restore-Test prüft den neuesten vollständigen Sicherungssatz direkt aus dem
konfigurierten rclone-Crypt-Remote. Alle Komponenten werden heruntergeladen,
entschlüsselt, validiert und in ein privates Testverzeichnis entpackt. SQL-Dumps
werden zusätzlich in eine isolierte MariaDB importiert und mit mariadb-check
geprüft. Produktive Anwendungen werden dabei nicht angehalten oder überschrieben.
Speicherbedarf und Bereinigung
Wegen des knappen Speichers auf Gemini läuft immer nur eine Komponente:
- Archiv herunterladen; Größe und SHA-256 gegen das Manifest prüfen.
- Archivstruktur und Kompression vollständig validieren.
- Platz für die entpackte Komponente abschätzen und diese mit GNU tar entpacken.
- Das komprimierte Archiv sofort löschen.
- Bei einem DB-Dump eine frische Test-DB starten, importieren und Tabellen prüfen.
- Den eigenen DB-Container samt eventuellen anonymen Volumes entfernen, danach alle temporären Dateien dieser Komponente löschen; erst dann die nächste laden.
Es liegt somit nie der gesamte Sicherungssatz gleichzeitig auf der Platte. Der Spitzenbedarf ist ungefähr das Maximum aus:
- komprimiertem Archiv plus entpackter größter Komponente und Dateisystem-Overhead;
- SQL-Dump plus zugehöriger Testdatenbank einschließlich MariaDB-Systemtabellen, Indexen und temporären DB-Dateien.
Standard: adaptives Arbeitsbudget (RESTORE_TEST_MAX_WORK_GB=0) mit mindestens
2 GiB freier Reserve. Ein positiver Wert setzt zusätzlich eine feste Grenze.
Die größte Komponente muss vollständig passen. Die frühere Vorgabe von 4 GiB
reicht für Geminis rund 8,8 GB Nextcloud-Daten nicht; die Update-Option
--enable-restore-test setzt deshalb ausdrücklich das adaptive Budget.
Bei Platzdruck versucht der Test zwischen Arbeitsschritten die sichere lokale
Bereinigung aus SPEICHER.md. Schlägt sie fehl, wird der Test mit
Fehlerbericht beendet und sein Scratch bereinigt. Alle Komponenten werden geprüft;
ein fehlender Platz wird nicht durch eine Stichprobe kaschiert.
Vor Download und Entpacken wird der Bedarf geprüft. Während externer Prozesse wird freier Speicher laufend kontrolliert, die tatsächliche Belegung regelmäßig nachgemessen und bei Grenzverletzung abgebrochen. Dies ist keine harte Dateisystemquote: schnelle DB-Schreibvorgänge und fremde Prozesse können zwischen Messungen weiteren Platz belegen. Daher die Reserve nicht knapp auf null setzen. Für den DB-Start werden zusätzlich mindestens 512 MiB eingeplant; das ist eine Mindestreserve, keine obere Grenze für die Größe der importierten Datenbank.
Normale Fehler, Speicher-/Zeitlimits und SIGTERM/SIGINT führen zur Bereinigung.
Nach einem harten Abbruch führt systemd ExecStopPost aus. Bei einem Fehler wird
außerdem der Bereinigungsdienst gestartet; er versucht eine fehlgeschlagene
Bereinigung alle 15 Minuten erneut. Ein aktivierter Boot-Bereinigungsdienst und
der nächste Teststart räumen unterbrochene Tests ebenfalls auf. Beide verwenden
dieselbe Sperre wie Backup und Update.
Kann beispielsweise der Docker-Daemon nicht erreicht und der Testcontainer nicht sicher entfernt werden, lautet das Ergebnis FAIL / Bereinigung FAILED. Die Dateien werden dann nicht unter einer möglicherweise noch laufenden DB gelöscht. Die Bereinigung wird wiederholt; ein solcher Zustand darf nicht als Erfolg gelten. Bei einem ausgeschalteten Server erfolgt sie naturgemäß erst nach dem Neustart.
Nur der eindeutig zu diesem Test gehörende Container und das markierte
$WORKDIR/restore-test/scratch werden bereinigt. Reguläre manuelle Restore-Verzeichnisse und fremde Docker-Ressourcen bleiben
erhalten. Die getrennte Platzbereinigung darf ältere lokale Sicherungskopien nur
nach erneuter vollständiger Offsite-Prüfung und unter Wahrung der Mindestanzahl entfernen.
Es gibt kein allgemeines docker prune. Das einmal vorbereitete DB-Image bleibt
als wiederverwendbare Abhängigkeit erhalten; sein einmaliger Platzbedarf kommt
zum Arbeitsbudget hinzu. Images werden im Test weder heruntergeladen noch gebaut.
Automatische Vorbereitung auf Gemini
Docker Engine und CLI müssen lokal vorhanden sein. Gemini verwendet laut
Bestandsaufnahme vom 12.09.2026 MariaDB 10.11.18 und besitzt Docker bereits.
Der Installer liest SELECT VERSION() mit den vorhandenen Backup-Zugangsdaten.
Alle zu prüfenden Datenbanken müssen dieselbe MariaDB-Major-/Minor-Serie verwenden.
Ohne gesetztes Image wählt er beispielsweise mariadb:10.11. Ein vorhandenes
Image wird wiederverwendet, ein fehlendes einmalig mit Zeitlimit geladen. Ein
explizites Image wird erhalten, muss aber zur Produktionsserie passen.
sha256sum -c app-backup-update-VERSION.run.sha256
sudo bash app-backup-update-VERSION.run --enable-restore-test
Die Option aktiviert den Test und das adaptive Arbeitsbudget in der gesicherten
Kandidatenkonfiguration. Die freie Docker-Kapazität wird vor einem Download geprüft.
Der Installer startet bei Bedarf den vorhandenen Docker-Dienst. Er initialisiert
eine isolierte Test-MariaDB, importiert eine kleine Prüftabelle, führt
mariadb-check aus und entfernt Container und Daten wieder. Erst nach Erfolg
wird ein automatisch ermitteltes Image in die Konfiguration übernommen. Der
Probeimport verändert keine produktive Datenbank und setzt keinen erfolgreichen
monatlichen Sicherungsnachweis.
Anschließend installiert der Update-Worker das Paket, erstellt ein Backup und prüft diesen konkreten Offsite-Satz vollständig inklusive DB-Import. Erst nach erfolgreicher Prüfung und Bereinigung aktiviert er Backup-Timer, Monatstimer und Boot-Bereinigung. Das Update-Ergebnis und der separate Restore-Bericht dokumentieren die Abnahme. Die zeitaufwendige Worker-Phase läuft unabhängig von SSH; die vorgelagerte Vorbereitung sollte bis zur Übergabemeldung verbunden bleiben.
Ohne --enable-restore-test erhält das Update den bisherigen Aktivierungszustand
und ein ausdrücklich eingestelltes Arbeitsbudget. Ist der Test bereits aktiviert,
erfolgen Image-Vorbereitung und vollständige Abnahme ebenfalls automatisch.
Bei einem MariaDB-Serienwechsel ein bisher ausdrücklich eingetragenes Image
anpassen oder leeren; ein inkompatibles Image wird vor Verwendung abgelehnt.
Eine direkte Installation mit install.sh bereitet eine bereits aktivierte
Testkonfiguration ebenfalls vor. Für eine spätere Aktivierung ohne Paketupdate:
sudoedit /etc/app-backup/app-backup.conf
# ENABLE_RESTORE_TEST=true; RESTORE_TEST_DB_IMAGE=""; RESTORE_TEST_MAX_WORK_GB=0
sudo bash /usr/local/sbin/app-restore-test.sh --cleanup
sudo bash /usr/local/lib/app-backup/prepare-restore-test.sh /etc/app-backup/app-backup.conf
sudo bash /usr/local/sbin/app-restore-test.sh --check
sudo systemctl start app-restore-test.service
sudo systemctl enable app-restore-test-cleanup.service
sudo systemctl enable --now app-restore-test.timer
--check prüft Konfiguration, lokale Werkzeuge, Image-Metadaten und freie Reserve.
Es lädt kein Backup herunter und führt keinen DB-Import aus. Den ersten echten
Test nach Installation anstoßen und sein Ergebnis kontrollieren:
sudo systemctl start --no-block app-restore-test.service
sudo journalctl -fu app-restore-test.service --no-pager
sudo cat /var/backups/app-backup/restore-test/state.json
sudo systemctl list-timers app-restore-test.timer --no-pager
Bei angepasstem WORKDIR die Pfade entsprechend ersetzen. Einen bestimmten Satz
kann man manuell prüfen; Altersgrenze und alle übrigen Prüfungen bleiben aktiv:
sudo bash /usr/local/sbin/app-restore-test.sh \
--remote-run appbackup_DATUM_UHRZEIT_ID
Zeitplan, Auswahl und Ergebnisse
Der Standardtermin ist der 1. jedes Monats um 06:00 Uhr Serverzeit, mit bis zu
15 Minuten zufälliger Verzögerung. Persistent=true holt verpasste Termine nach.
Ein belegter Backup-/Restore-/Update-Lock liefert Exit-Code 75; systemd versucht
es eine Stunde später erneut. Ein echter Prüffehler löst eine Fehlermail aus und
wird nicht durch automatische Neuversuche als Erfolg verdeckt.
Unvollständige neuere Sätze ohne COMPLETE werden mit Protokolleintrag übersprungen.
Für den neuesten abgeschlossenen Satz müssen Marker und Manifest zusammenpassen,
die Lauf-ID muss dem Remote-Verzeichnis entsprechen, alle konfigurierten
Komponenten müssen enthalten sein und die Remote-Dateiliste muss vollständig sein.
Ein beschädigter abgeschlossener Satz wird nicht durch einen älteren ersetzt.
Standardmäßig sind maximal 72 Stunden alte Backups zulässig; ein ungültiger
Zeitstempel oder ein Zeitstempel mehr als fünf Minuten in der Zukunft scheitert.
Der Bericht verwendet die bisherigen Mail-Einstellungen (ENABLE_MAIL_REPORT,
MAIL_TO, MAIL_FROM, MAIL_HOSTNAME, MAIL_SENDMAIL_BIN, MAIL_SEND_TIMEOUT).
Der Betreff lautet beispielsweise:
[app-restore-test] SUCCESS gemini.jr-it-services.de appbackup_2026-…
Enthalten sind Alter und ID des Sicherungssatzes, Laufzeit, Status je Komponente, Archivgröße, Anzahl der Archiveinträge, Anzahl der importierten Tabellen sowie Bereinigung: OK/FAILED. Es werden weder SQL-Inhalte noch Dateiinhalte in die Mail oder das Testprotokoll übernommen. Der Status je Komponente wird erst nach seiner Bereinigung als erfolgreich gespeichert.
Unter $WORKDIR/restore-test/ bleiben nur kleine Betriebsnachweise erhalten:
| Datei | Bedeutung |
|---|---|
state.json |
Letzter/aktueller Lauf; bei Start sofort RUNNING, abschließend SUCCESS oder FAIL, Bereinigungs- und Mailstatus |
last-success.json |
Letzter vollständig geprüfter und bereinigter Satz; bleibt bei einem späteren Fehler erhalten |
report.eml |
Jeweils letzter Mailbericht, auch bei Zustellungsfehler |
restore-test.log und .1 |
Rotierendes Testprotokoll, jeweils ungefähr 512 KiB |
.owner |
Kennzeichnung des Testverzeichnisses für sichere Bereinigung |
Temporäre Prozess-/Container-Marker verschwinden nach erfolgreicher Bereinigung.
Die Nachweise sind root-privat. Für die unabhängige Überwachung sollte ein
RUNNING, cleanup != OK, mail == FAILED oder ein mehr als etwa 40 Tage alter
last-success.json auffallen. Ein erfolgreicher Restore bei fehlgeschlagener
Mail bleibt inhaltlich SUCCESS, liefert aber Exit-Code 1 und mail: FAILED.
Der SMTP-Versand ist wie bisher eine Übergabe an den lokalen MTA, kein Nachweis
der tatsächlichen Zustellung.
Manuelle Wiederholung der Bereinigung, auch bei deaktivierter Testfunktion:
sudo bash /usr/local/sbin/app-restore-test.sh --cleanup
sudo journalctl -u app-restore-test-cleanup.service -n 50 --no-pager
Zum Aussetzen des Zeitplans app-restore-test.timer deaktivieren. Den
Boot-Bereinigungsdienst aktiviert lassen, bis eventuell unterbrochene Testdaten
nachweislich entfernt wurden.
Isolation und Aussagekraft
Jeder DB-Dump erhält eine neue Datenbankinstanz. Der Container hat ausschließlich ein Bind-Mount auf sein privates DB-Testverzeichnis, kein Netzwerk, keine veröffentlichten Ports, keine zusätzlichen Linux-Capabilities, ein schreibgeschütztes Root-Dateisystem und keinen persistenten Docker-Logtreiber. Er läuft mit maximal 512 MiB RAM, 128 Prozessen und einer CPU. MariaDB liest keine Hostkonfiguration, verwendet nur ihren eigenen Unix-Socket und hat Event-Scheduler und Binlog aus. Der SQL-Client verwendet Batch-/Binary-Modus, damit Dumps keine lokalen Client-Kommandos ausführen. Docker-API-Ausgaben und SQL-Fehlertexte gelangen nicht ungefiltert in Logs. Hintergrunddatenbanken können keine produktiven DB-Sockets oder Konfigurationen erreichen.
Der Test belegt Download, Entschlüsselung, Integrität, tatsächliches Entpacken und DB-Import mit Tabellenprüfung. Er belegt weder eine vollständige Datenabdeckung, die schon im Backup fehlte, noch fachliche Konsistenz zwischen Anwendungen. Ein Start-/Funktionstest von Nextcloud, WordPress und Gitea auf einer getrennten VM bleibt eine zusätzliche Abnahme. Dateibesitz/ACLs werden beim Entpacken angefordert; die korrekte Anwendung aller Sicherheitskontexte auf einem neuen Produktivsystem bedarf dort einer eigenen Prüfung.
Technische Referenzen: Docker run, Docker ohne Netzwerk, MariaDB-Systemtabellen initialisieren, MariaDB-Serveroptionen.
Entwicklung und lokale Verifikation
Am 12.09.2026 bestanden 100 unterschiedliche Tests: 71 Backup-/Restore-/ Installer-Regressionstests, 23 Restore-Test-Fälle mit dem offiziellen lokalen MariaDB-10.11-Image und 6 gezielte Scratch-/Recovery-Schutzprüfungen. ShellCheck und Python-Syntaxprüfung bestanden ebenfalls. Das Testverzeichnis war danach leer, und es blieben keine zugehörigen Testcontainer zurück.
Die automatischen Tests decken echte Crypt-Downloads, Entpacken, DB-Import und
Tabellenprüfung ab, außerdem ungültiges SQL, SIGTERM/SIGKILL, Platzmangel,
beschädigte Statusdateien und einen zeitweise nicht erreichbaren Docker-Daemon.
Die Installer-Vorbereitung wird zusätzlich mit dem offiziellen lokalen
mariadb:10.11 geprüft, einschließlich automatischer Versionswahl und Ablehnung
einer falschen Produktionsserie. Scratch-, Container- und Konfigurationserhalt
werden nachgeprüft; Testmails bleiben lokal in der Testumgebung.
Auf einem separaten Entwicklungs-/Testrechner mit den Abhängigkeiten aus
tests/Dockerfile, Docker-CLI und lokal vorbereitetem Image lässt sich die
gezielte Prüfung aus dem Quell-Checkout starten:
sudo env RESTORE_TEST_REAL_DOCKER_IMAGE=mariadb:10.11 \
python3 -m unittest discover -s tests -p test_restore_test.py -v
Diese Tests verwenden ausschließlich selbst angelegte temporäre Konfigurationen,
Daten und Container. Die vollständige Basissuite enthält zusätzlich simulierte
systemd-/Installertests und gehört in den bestehenden isolierten Testcontainer.
Die neuen Units wurden mit systemd-analyze verify geprüft. Ihre tatsächliche
Ausführung unter Rocky/SELinux und der produktive OneDrive-/MTA-Pfad sind nach
Installation auf Gemini noch abzunehmen.