# 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. ## 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 Backup-Rückleseprü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 vollständiger Restore-Abnahme aus. Erst die neue `result.txt` mit `SUCCESS` bestätigt die gesamte Abnahme. [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 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 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.