Files
app-backup/RESTORE_TEST.md

366 lines
21 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.
Seit 16.09.2026 werden die Archive beim täglichen Backup und bei der
Update-Abnahme nur auf Remote-Dateiexistenz und Größe geprüft. Der hier
beschriebene Monatslauf übernimmt die vollständige Inhalts- und Restore-Prüfung.
Auch ai-rem-Zusatzarchive (`extra-N`) werden heruntergeladen und entpackt;
ein Start der ai-rem-Anwendung oder ein Kuzu-Datenbanktest gehört nicht dazu.
## Downloadfehler bei der Abnahme am 15.09.2026
Das Update `4f220dbc6447` erzeugte den Satz
`appbackup_2026-09-15_09-03-06_20b0f7a6`. Sein Upload und der vollständige
Bytevergleich aller 15 Dateien bestanden, auch für die rund 5 GiB große
`nextcloud-data.tar.zst`. Der Abschlussmarker wurde bestätigt. Anschließend
bestand der separate Restore-Test Metadaten, drei Datenbanken, WordPress und
Nextcloud; beim erneuten Abruf der Nextcloud-Daten folgte `Command failed: bash`.
Der Test meldete `FAIL; cleanup OK`, das gesamte Update deshalb `FAILED`.
Der Fehler ist dem Shell-Aufruf für den Komponentendownload einschließlich
Remote-Vorprüfung zuzuordnen: Tar-Prüfung und Entpacken würden einen anderen
Programmnamen melden; Fehler der separaten Platzbeschaffung werden bereits
mit einer eigenen Meldung versehen. Der Supervisor verwarf jedoch stderr
und nannte weder Exit-Code noch genaue Phase. Ob diesmal Crypt-Header, Timeout,
HTTP-Fehler oder eine andere Bridge-Ursache vorlag, ist damit **nicht mehr aus
diesem Log rekonstruierbar**. Ein erneuter `bad magic string` ist nicht belegt.
Die erste Korrektur erfasste `rclone check`, aber nicht diesen eigenen
`rclone cat`-Pfad. Auch `cat` wiederholt den gesamten Befehl in rclone 1.74.3
nicht automatisch über `--retries`.
Quelle: [rclone-1.74.3-cat-Aufruf](https://github.com/rclone/rclone/blob/v1.74.3/cmd/cat/cat.go).
### Korrigierter Ablauf
Der Restore-Test verwendet für seine lesenden Remote-Aufrufe jetzt dieselben
Konfigurationswerte wie die Remote-Metadatenprüfung:
- `RCLONE_CHECK_ATTEMPTS=3`: höchstens drei Versuche je Metadatenabruf/Download.
- `RCLONE_CHECK_DEADLINE=3600`: maximal eine Stunde je Versuch einschließlich
Remote-Vorprüfung. Der Python-Supervisor beendet die eigene Prozessgruppe bei
Überschreitung mit TERM, nötigenfalls nach fünf Sekunden mit KILL.
- `RCLONE_CHECK_TIMEOUT=300` und `RCLONE_CHECK_LOW_LEVEL_RETRIES=3`: kürzere
rclone-Leerlauffrist und begrenzte interne Versuche, auch bei der Vorprüfung.
- `RCLONE_CHECK_RETRY_DELAY=30`: Pause zwischen fehlgeschlagenen Versuchen.
Die Gesamtfrist `RESTORE_TEST_TIMEOUT` und die Speicherüberwachung bleiben
während Downloads und Wartepausen wirksam. Die Fristen addieren sich nicht
zu einer unbegrenzten Verlängerung des gesamten Restore-Tests.
Bei einer Wiederholung wird die angefangene Archivdatei auf null gekürzt und
vollständig neu geladen; es wird niemals an Teilbytes angehängt. Metadaten
werden je Versuch in einem neuen begrenzten Puffer gelesen. Erst ein erfolgreich
beendeter Download mit passender Größe und SHA-256 darf zur Archivprüfung und
zum Entpacken gelangen. Größenüberschreitungen, Prüfsummenfehler, Speichergrenzen,
Gesamtfrist und Signalabbrüche lösen keine Download-Wiederholung aus.
Die Platzbereinigung wird nicht als Remote-Leseoperation wiederholt.
Jede Komponente meldet und speichert nun ihre konkrete Phase: Download,
Prüfsumme, Archivprüfung, Platzabschätzung, Entpacken, DB-Import/-Prüfung und
Bereinigung. Während eines Remote-Aufrufs erscheint etwa jede Minute
ein Fortschrittsbericht mit gelesenen Bytes und verstrichener Zeit, auch bei
Stillstand. Nicht erfolgreiche Prozesse nennen Exit-Code oder Fristablauf.
Remote-stderr wird parallel zum Archivstrom gelesen und ausschließlich in feste
Fehlerklassen übersetzt, etwa `crypt-header`, `io-timeout`, `tls`, `dns`,
`connection`, `http-429` oder `http-503`. Die Auswertung hat einen begrenzten
Textpuffer; große stderr-Ausgaben können die Download-Pipe nicht blockieren.
Es werden keine rohen URLs, Tokens, Header, Dateiinhalte oder Providertexte
übernommen. Unbekannte Meldungen erscheinen als `unclassified remote error`.
Diese Klassifizierung ist ein Diagnosehinweis, kein Beweis der Providerursache.
DB- und sonstige Tool-stderr-Ausgaben bleiben vollständig unterdrückt, da sie
SQL-Inhalte enthalten können. Phase und Exit-Code bleiben trotzdem sichtbar.
Ein bleibender Fehler erhält den bereits verifizierten Backup-Satz und den
vorherigen Restore-Erfolgsnachweis, meldet die betroffene Komponente als `FAIL`
und bereinigt die Testdaten. Nach fehlgeschlagener Update-Abnahme bleiben
Backup- und Restore-Test-Timer gestoppt. Das neue Paket erneut im Wartungsfenster
installieren; es führt ein neues Backup samt Remote-Dateiprüfung aus.
Die neue `result.txt` mit `SUCCESS` bestätigt die Update-Abnahme;
der vollständige Restore-Test erfolgt separat beim Monatstermin.
[Installationsschritte](UPDATE_ANLEITUNG.md).
Der vorhandene erfolgreiche Backup-Satz darf alternativ nach Installation des
Fixes mit `app-restore-test.sh --remote-run` erneut geprüft werden. Dabei bleibt
die Altersgrenze aktiv. Ein solcher Einzeltest setzt nicht nachträglich das
alte Update-Ergebnis auf SUCCESS und aktiviert keine Update-Timer.
## Speicherbedarf und Bereinigung
Der Mailbericht endet mit Laufzeit, belegtem lokalen Platz für Backups,
Arbeitsbereiche, Restore-Test und Protokolle sowie freiem Platz auf den
zugehörigen Datenträgern. Die Werte werden nach der Bereinigung gemessen.
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 lokaler Inhaltsprüfung und erneuter Remote-Datei-/Größenprüfung sowie bytegenauem
Vergleich von Manifest/Abschlussmarker entfernen. Die Mindestanzahl bei Platznot
ist `SPACE_KEEP_LOCAL` (Default eins); Details stehen in [SPEICHER.md](SPEICHER.md).
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
Beim abschließenden Prüflauf der Writer-Stop-Korrektur (`3730ab4676a5`) am
15.09.2026 bestanden 111 Tests der Gesamtsuite. Von 119 erkannten Fällen wurden
fünf optionale Docker-Integrationstests und drei Benutzer-systemd-Fälle
übersprungen; die drei systemd-Fälle bestanden separat. Die fünf Docker-Fälle
wurden bei diesem Lauf nicht erneut ausgeführt. Der folgende Nachweis mit echten
DB-Containern stammt aus der vorherigen Prüfung der Restore-Download-Korrektur.
Die erneute Serverabnahme auf Gemini ist weiterhin offen.
Der Folgefix vom 15.09.2026 bestand alle **33 Restore-Test-Fälle** mit der von
Gemini kopierten rclone-1.74.3-Binärdatei und dem echten lokalen
`mariadb:10.11`-Image. Geprüft wurden auch Teil-Downloads/Metadaten mit erneutem
Erfolg, dauerhaft fehlgeschlagene Downloads, Fristablauf, Signalabbruch,
Gesamtfrist während einer Retry-Pause und zu große Antworten. Große stderr-
Ausgaben blockierten nicht; synthetische Tokens und SQL-Inhalte erschienen
weder im Log noch im Status oder Bericht. Die Testcontainer und Testdaten waren
anschließend entfernt. Diese lokalen Tests ersetzen nicht die erneute
OneDrive-/Rocky-Abnahme auf Gemini.
Die vollständige Basissuite `tests/run.sh` bestand ebenfalls: Bash-Syntax,
ShellCheck und 111 erfolgreiche Tests; fünf optionale Docker-Fälle waren in
diesem netzlosen Basistest ausgelassen und wurden im oben genannten echten
MariaDB-Lauf bestanden. Insgesamt sind damit alle **116 unterschiedlichen
Tests** erfolgreich abgedeckt, einschließlich Paketinstallation und Rollback.
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.