253 lines
14 KiB
Markdown
253 lines
14 KiB
Markdown
# 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**:
|
|
|
|
1. Archiv herunterladen; Größe und SHA-256 gegen das Manifest prüfen.
|
|
2. Archivstruktur und Kompression vollständig validieren.
|
|
3. Platz für die entpackte Komponente abschätzen und diese mit GNU tar entpacken.
|
|
4. Das komprimierte Archiv sofort löschen.
|
|
5. Bei einem DB-Dump eine frische Test-DB starten, importieren und Tabellen prüfen.
|
|
6. 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](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.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```text
|
|
[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:
|
|
|
|
```bash
|
|
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](https://docs.docker.com/reference/cli/docker/container/run),
|
|
[Docker ohne Netzwerk](https://docs.docker.com/engine/network/drivers/none/),
|
|
[MariaDB-Systemtabellen initialisieren](https://mariadb.com/docs/server/clients-and-utilities/deployment-tools/mariadb-install-db),
|
|
[MariaDB-Serveroptionen](https://mariadb.com/docs/server/server-management/starting-and-stopping-mariadb/mariadbd-options).
|
|
|
|
## 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:
|
|
|
|
```bash
|
|
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.
|