# Dokumentation app-backup v2 Stand des Reviews: 10. September 2026. Untersucht wurde der Gitea-Stand `099688e` einschließlich Backup, Restore, Installer, Konfiguration, systemd und bisheriger README. Die Erweiterung wird als zusammengehöriges Paket installiert; einzelne Skripte dürfen nicht ohne die gemeinsamen Bibliotheken aktualisiert werden. ## Ergebnis der Codeanalyse | Befund im Ausgangsstand | Auswirkung | Umsetzung | | --- | --- | --- | | **Kritisch:** `app-restore.sh` enthielt nur Initialisierung/Hilfsfunktionen und griff am Ende auf das undefinierte `RUN_DIR` zu. | Die dokumentierten Restore-Aufrufe waren nicht implementiert. | Vollständige Prüfung, Entpacken, Restore-Plan und explizite Ausführung. | | **Kritisch:** Dumps/Kopien wurden teilweise in `&& … || true` bzw. Warnungszweigen ausgeführt. | Ein Lauf konnte trotz fehlender DB oder Dateien erfolgreich erscheinen; Bash-Fehlerbehandlung wurde in solchen Kontexten unterdrückt. | Direkte Aufrufe, `set -Eeuo pipefail`, `inherit_errexit`; Fehlercodes werden weitergegeben. | | **Kritisch:** Nextcloud-Wartungsmodus wurde nach dem DB-Dump vor der Dateikopie ausgeschaltet. WordPress blieb live. | Datenbank und Dateien konnten unterschiedliche Zustände abbilden. | Gemeinsames Ruhefenster über sämtliche Dumps und Dateikopien. | | **Hoch:** Maildienste hatten keine zuverlässige Wiederanlaufbehandlung; Fehler und rsync-Teilergebnisse wurden ignoriert. | Unvollständige Mailsicherung oder unbeabsichtigte Dienstausfälle. | Zustandsabhängige Stopps, geprüfter Wiederanlauf, Exit-Code und Wiederanlaufdatei. | | **Hoch:** Crypt-Prüfung ließ u. a. `no_data_encryption=true` und reine Namensverschleierung zu. Restore verlangte dagegen explizite Optionen, obwohl sichere Defaults gültig sind. | Inhalt konnte trotz Remote-Typ `crypt` unverschlüsselt bleiben; gültige Remotes konnten abgelehnt werden. | Gemeinsame Prüfung von Typ, Inhalt, Dateinamen und Verzeichnisnamen; Defaults werden berücksichtigt. | | **Hoch:** `--checksum` und Remote-Listing ersetzten keine Prüfung des entschlüsselten Inhalts. | Transfererfolg war kein belastbarer Rücklesetest. | Vollständiger `rclone check --download` vor Veröffentlichung des Abschlussmarkers. | | **Hoch:** Retention lief vor dem neuen Backup und löschte remote einzelne alte Objekte rekursiv. | Alte Wiederherstellungspunkte konnten verloren gehen, bevor ein neuer verfügbar war; Sätze konnten unvollständig werden. | Retention ausschließlich nach einem neuen erfolgreichen Lauf, nur vollständige v2-Ordner, Mindestanzahl. | | **Hoch:** Drei nicht als Vorlage gekennzeichnete DB-Dateien enthielten mögliche echte Zugangsdaten. | Geheimnisse im aktuellen Baum und in der Git-Historie. | Dateien entfernt, ausschließlich Vorlagen, `.gitignore`; Passwortrotation bleibt Betriebsaufgabe. Keine Historienumschreibung. | | **Hoch:** Gitea war im ausgelieferten Config-Beispiel nicht eingeschaltet; `APP_DATA_PATH` allein deckt externe Repositories/custom nicht ab. | Erwartete Gitea-Daten konnten fehlen. | Sichtbarer Aktivierungsschalter und zusätzliche Pfade; erforderliche Verzeichnisse fehlen nicht stillschweigend. | | **Mittel:** Konfigurierbare WordPress-Excludes wurden nicht vollständig angewendet; Nextcloud `data/` war fest angenommen. | Falscher Umfang bei abweichender Verzeichnisstruktur. | Ableitung relativer Excludes aus den konfigurierten Pfaden, auch beim Restore. | | **Mittel:** Unkomprimierte Zwischenarchive, zstd `-19 -T0`, flache Ablage und fehlendes Manifest. | Hohe IO-/CPU-/Speicherlast; keine sichere Zuordnung vollständiger Läufe. | Streaming-Kompression, begrenzte Threads, eigene Laufordner und strukturierte Metadaten. | | **Mittel:** Installer aktivierte den Timer sofort und überschrieb Unit-Dateien ohne Rückweg. | Ungeprüfte Erstläufe; individuelle Zeitpläne konnten verloren gehen. | Config-/Timer-Erhalt, `.dist`-Vorlagen, gemeinsame Sperre, Rollback-Paket, frische Timer zunächst aus. | | **Mittel:** Eigene Logdatei je Lauf plus `logrotate`/`notifempty`. | Rotierte Dateien konnten wegen leerer Nachfolgedateien dauerhaft liegen bleiben. | Altersbasierte Bereinigung neuer Laufprotokolle nach erfolgreichen Backups. | Die vollständige Rückleseprüfung erhöht den Datenverkehr um ungefähr die hochgeladene komprimierte Datenmenge. Dieser Aufwand ist bewusst Teil des Sicherheitskonzepts. ## Sicherheitsziel und Grenzen Das Paket soll Fehler sichtbar machen, vollständige Sicherungssätze erhalten und eine nachprüfbare Wiederherstellung ermöglichen. Es kann keine absolute Datensicherheit garantieren. Folgende Ebenen sind getrennt zu betrachten: 1. **Vertraulichkeit:** Das Crypt-Remote verschlüsselt Inhalt und Namen vor dem Upload. Lokales Staging, lokale Archive und entpackte Restores sind Klartext und benötigen ein verschlüsseltes Servervolume, sichere Dateirechte und Zugriffsschutz. 2. **Integrität:** SHA-256, Kompressions-/Tar-Prüfung, Manifest und Rückleseprüfung erkennen fehlende bzw. veränderte Bestandteile. Das Manifest ist nicht digital signiert. Wer Archive und Manifest gemeinsam kontrolliert, kann neue passende Prüfsummen erzeugen. 3. **Konsistenz:** Alle relevanten Writer müssen während der Dumps/Kopien ruhen. Nicht erfasste Cronjobs, externe DB-Schreiber, systemd-Sockets, Container, S3-Speicher oder Remote-Mounts werden nicht automatisch eingefroren. Ein erfolgreicher Dateivergleich beweist keine Anwendungskonsistenz. 4. **Verfügbarkeit:** OneDrive mit demselben löschberechtigten Konto ist keine unabhängige, unveränderliche Kopie. Für Schutz vor kompromittiertem Server/Konto, Ransomware oder versehentlicher Löschung eine weitere getrennte bzw. offline/immutable Sicherung vorsehen. 5. **Wiederherstellbarkeit:** Ein getesteter Restore mit Anwendungskontrollen ist erforderlich. Passende Anwendungsversionen, Systempakete, UID/GID, Datenbankrechte und außerhalb des Umfangs liegende Schlüssel/Konfigurationen müssen verfügbar sein. Crypt verschlüsselt Daten beim Zugriff über das Crypt-Remote; der darunterliegende OneDrive-Zugriff umgeht diese Schicht. Rclone speichert Crypt-Passwörter standardmäßig nur verschleiert in seiner Konfiguration. Passwort **und gegebenenfalls Salt/zweites Passwort** müssen unabhängig vom gesicherten Server verfügbar sein. Nicht allein innerhalb desselben verschlüsselten Backups aufbewahren. Dateigrößen und Zeitinformationen sind nicht vollständig verborgen. Quelle: [rclone Crypt](https://rclone.org/crypt/). ## Architektur und Ablauf ```mermaid flowchart TD A[Config und gemeinsame Sperre] --> B[Quellen / Rechte / DB-Verbindungen / Crypt / Speicher prüfen] B --> C[Wartungsmodus und aktive Writer stoppen] C --> D[Datenbanken dumpen und Dateien ins private Staging kopieren] D --> E[Ursprünglichen Dienstzustand wiederherstellen] E --> F[Je Komponente komprimieren und Tar-Inhalt prüfen] F --> G[Manifest und lokaler COMPLETE-Marker] G --> H[Archive und Manifest über Crypt hochladen] H --> I[Alles entschlüsselt zurücklesen und vergleichen] I --> J[COMPLETE zuletzt hochladen und zurücklesen] J --> K[Retention vollständiger Sätze / Status / Bericht] ``` `ENABLE_UPLOAD=false` beendet den Datenpfad nach dem vollständigen lokalen Satz und führt nur lokale Retention aus. Dies ist kein Offsite-Backup. Dateikopien erhalten mit `rsync -aHAX --numeric-ids` Eigentümer, Modus, Symlinks, Hardlinks innerhalb einer Komponente, ACLs und Extended Attributes. Tar erhält diese Metadaten einschließlich SELinux. Plattform-/Dateisystemunterstützung ist Voraussetzung. Externe Symlink-Ziele werden nicht automatisch gesichert; absolute Symlinks als einzelne Einträge bleiben erhalten. Geräte, FIFOs und andere Spezialdateien werden von der Archivprüfung abgelehnt. Zusätzliche Pfade sind deshalb Datenverzeichnisse, kein Ersatz für ein vollständiges Systemimage. Die Kompression schreibt direkt aus tar in `.partial`-Dateien. Erst erfolgreiche Pipelines werden umbenannt. zstd nutzt standardmäßig Stufe 6 und zwei Threads. `sync -f` vor Fortsetzung bzw. Retention fordert die Persistierung des lokalen Satzes an; Hardware-/Storagefehler bleiben eine eigene Ebene. ## Format und Komponenten Ein neuer Lauf heißt beispielsweise `appbackup_2026-09-10_02-30-00_a1b2c3d4`. Zeitangaben im Namen sind UTC; der systemd-Zeitplan nutzt die Serverzeitzone. Der Zufallssuffix verhindert Namenskollisionen. ```text /var/backups/app-backup/archives/appbackup_.../ meta.tar.zst db-wordpress.tar.zst db-nextcloud.tar.zst wordpress.tar.zst nextcloud.tar.zst nextcloud-data.tar.zst ... manifest.json COMPLETE UPLOADED # nur lokal, nach erfolgreicher Remote-Rückleseprüfung ``` | Komponente | Inhalt / Ziel aus lokaler Config | | --- | --- | | `meta` | Laufkennung, Format und Liste der Quellen; keine vollständige Config-Datei | | `db-wordpress`, `db-nextcloud`, `db-gitea` | Ein SQL-Dump je aktivierter Anwendung | | `wordpress` | `WP_DIR`, ohne konfigurierte untergeordnete Nextcloud-Pfade und `WP_EXCLUDES` | | `nextcloud` | `NC_DIR`, ohne konfiguriertes untergeordnetes `NC_DATA_DIR` | | `nextcloud-data` | `NC_DATA_DIR`, sofern aktiviert | | `gitea`, `gitea-etc` | `GITEA_DATA_DIR`, `GITEA_ETC_DIR` | | `gitea-repos`, `gitea-custom` | Optionale explizite externe Repository-/Custom-Pfade | | `mail`, `postfix`, `dovecot` | Mailstore und beide Konfigurationsverzeichnisse | | `mail-home-0`, `mail-home-1`, … | Reihenfolge von `MAIL_HOME_DIRS`, mit Excludes und Dateisystemgrenze | | `extra-0`, `extra-1`, … | Reihenfolge von `EXTRA_PATHS` | Das Manifest enthält Formatversion, Laufkennung, Host, Erstellungszeit und je Archiv Label, Quellbezeichnung, erlaubten Tar-Wurzelpfad, Dateiname, Größe und SHA-256. Der `COMPLETE`-Marker bindet sich an den SHA-256-Wert dieses Manifests. Ein lokaler vollständiger Satz kann existieren, obwohl der Upload danach fehlgeschlagen ist; erst `UPLOADED` belegt den erfolgreichen Remote-Abschluss dieses lokalen Laufs. Indexbasierte Pfade müssen ihre Reihenfolge behalten. Bei `extra-*` und `mail-home-*` lehnt der automatische Restore eine vom Manifest abweichende Pfadzuordnung ab. Für bewusst geänderte Ziele zuerst entpacken und die Zuordnung manuell prüfen. ## Voraussetzungen und Konfiguration Zielumgebung: Rocky Linux 10 mit systemd und nativen Anwendungen. Die automatische Datenbankbehandlung ist auf MariaDB/MySQL ausgerichtet; PostgreSQL, SQLite-Livekopien und Container-Volume-Snapshots benötigen ein eigenes Verfahren. Erforderliche Werkzeuge: Bash ≥ 4.4 (`inherit_errexit`), Python ≥ 3.9, GNU coreutils/findutils/tar, `flock`, `rsync`, `gzip`/`zstd`, `systemctl`; bei Nextcloud `sudo` und CLI-PHP; für Datenbanken `mariadb-dump`/`mariadb` oder kompatible MySQL-Clients. Ein konfiguriertes Crypt-Remote benötigt rclone mit den verwendeten Crypt-Flags und `check --download`; die Integrationstests laufen auch mit rclone 1.60.1. Für den tatsächlichen Server einen aktuell gepflegten rclone-Stand einsetzen und `--check` ausführen. Ein Ausgangspunkt auf Rocky, abhängig von aktivierten Paketquellen und vorhandener PHP-Version: ```bash sudo dnf install bash python3 tar rsync zstd gzip util-linux coreutils findutils sudo mariadb ``` Rclone und das zur vorhandenen Nextcloud-Installation passende CLI-PHP aus euren gepflegten Paketquellen bereitstellen. Kein automatischer Paket-/PHP-/Anwendungsupgrade durch den Installer. `/etc/app-backup/app-backup.conf` ist ausführbare Bash-Konfiguration: ausschließlich root darf sie verändern. DB-CNF und rclone-Konfiguration müssen reguläre, root-eigene Dateien ohne Gruppen-/Fremdrechte sein (`600`); die Hauptkonfiguration darf nicht gruppen-/weltbeschreibbar sein, empfohlen ebenfalls `600`. Arbeitsverzeichnisse gehören root und werden auf `700` gesetzt. Direkte Symlinks als Arbeitsverzeichnisse und überschneidende Quellen/Arbeitsbereiche werden abgelehnt. Übergeordnete Verzeichnisse müssen ebenfalls administrativ geschützt sein. Die CLI-Option `--config FILE` erlaubt getrennte Backup- und Restore-Einstellungen. Die Config hat Vorrang vor geerbten Umgebungswerten. Auf `DRY_RUN=true` oder alte `RESTORE_DB`-/`RESTORE_FILES`-Umgebungsvariablen nicht mehr verlassen: Die dokumentierten CLI-Schalter sind maßgeblich. ### Crypt und Schlüssel Beispiel der relevanten Remote-Optionen, ohne Zugangsdaten: ```ini [OneDriveCrypt] type = crypt remote = OneDrive:GESCHUETZTER_BACKUP_ORDNER filename_encryption = standard directory_name_encryption = true no_data_encryption = false ``` Vorhandene Passwörter/Salt und Zuordnung **nicht neu generieren**, wenn bestehende Backups weiter lesbar bleiben sollen. Sichere Defaults dürfen im Config-Block fehlen. `off`, `obfuscate` und deaktivierte Inhaltsverschlüsselung werden abgelehnt. Die Befehle setzen zusätzlich sichere Crypt-/TLS-Flags. Remote-Definitionen über `RCLONE_CONFIG_*`-Umgebungsvariablen sind nicht unterstützt; `RCLONE_CONFIG_PASS` für eine verschlüsselte Konfigurationsdatei ist ausgenommen. Die Config-Ausgabe wird direkt intern geparst und nicht protokolliert. `RCLONE_REMOTE_BASE` muss auf ein benanntes Crypt-Remote mit eigenem nichtleerem Unterpfad zeigen, beispielsweise `OneDriveCrypt:gemini`. Dessen verschlüsselter Basisordner muss bereits erreichbar sein. Kein Fallback auf das unverschlüsselte OneDrive-Remote. Den Zugriff immer mit derselben root-Konfiguration prüfen: ```bash sudo rclone --config /root/.config/rclone/rclone.conf lsd OneDriveCrypt: ``` `rclone check --download` vergleicht die heruntergeladenen Inhalte und benötigt keinen gemeinsamen Backend-Hash. Es verändert die geprüften Dateien nicht. Das Paket verwendet es vor dem abschließenden Marker. Quelle: [rclone check](https://rclone.org/commands/rclone_check/). ### Datenbanken und Writer Es wird je aktivierter Anwendung ein Dump benötigt, sofern `ENABLE_DB_DUMPS=true`. Fehlende DB-Namen oder CNF-Dateien brechen ab. Der Dump enthält Routinen, Events, Trigger und Binärdaten. Leere Ausgaben sowie Fehler beim Schreiben werden abgelehnt. Für gemischte Storage Engines nutzt das Paket `--lock-all-tables --quick`. **Die globale Lesesperre kann während jedes Dumps Schreibzugriffe auf alle Datenbanken desselben Servers blockieren.** Eine reine `--single-transaction`-Sicherung schützt insbesondere nicht alle nichttransaktionalen Tabellen. Zusätzlich müssen Anwendungsschreiber über das gesamte Ruhefenster angehalten sein, damit Dateikopien zu den Dumps passen. Quelle: [MariaDB mariadb-dump](https://mariadb.com/docs/server/clients-and-utilities/backup-restore-and-import-clients/mariadb-dump). Die konkreten Rechte hängen von Serverversion, Views, Routinen und Sperrmethode ab. Der Backup-Account benötigt Leserechte sowie Rechte für Views, Trigger, Events, Routinen und die globale Sperre; ein früherer einfacher `SELECT`-Account reicht möglicherweise nicht. Die Rechte gezielt auf eure Version zuschneiden. `--check` prüft Anmeldung und DB-Zugriff, **nicht sämtliche später benötigten Dump-Privilegien**. Die Integrationstests verwenden einen isolierten administrativen Testaccount. Für einen Restore einen separaten Account mit DDL-/DML-Rechten auf die Ziel-Datenbanken verwenden. Importe sind nicht als Ganzes transaktional. Eine leere vorbereitete Zieldatenbank vermeidet, dass Tabellen oder andere Objekte zurückbleiben, die im Backup nicht vorkommen. Das Skript löscht keine vollständige Datenbank und legt keine Datenbanken/Accounts automatisch an. Nextcloud: Daten, Config, Apps/Themes und Datenbank gehören zusammen. Das konfigurierte Datenverzeichnis auf dem Server ermitteln: ```bash sudo -u apache php /var/www/html/nextcloud/occ config:system:get datadirectory ``` Nextcloud empfiehlt den Wartungsmodus während der Sicherung. Das Paket hält ihn über Dump und Kopie hinweg und erhält einen schon vorher aktiven Wartungsmodus. Zusätzlich tatsächliche Cron-/Worker-Dienste anhalten; `maintenance:mode` ist keine Sperre aller bereits laufenden OS-Prozesse. Quelle: [Nextcloud Backup](https://docs.nextcloud.com/server/latest/admin_manual/maintenance/backup.html). Gitea: `APP_DATA_PATH`, `[repository] ROOT`, Custom-Verzeichnis und `app.ini` prüfen; externe Objektablagen, externes SSH-Home und weitere individuell konfigurierte Pfade zusätzlich erfassen. Gitea verlangt für konsistente Sicherungen einen Stillstand, weil Datenbank, Dateien und Repositories zusammengehören. Quelle: [Gitea Backup and Restore](https://docs.gitea.com/administration/backup-and-restore/). `QUIESCE_SERVICES=(nextcloud-cron.timer nextcloud-cron.service ...)` enthält reale zusätzliche Writer. Timer zuerst, zugehörigen Service danach nennen. Gestartet wird in umgekehrter Reihenfolge. Bereits inaktive Dienste bleiben inaktiv; ein nicht zuordenbarer Übergangszustand führt zum Abbruch. Cronjobs aus crontab/externen Schedulern und socket-aktivierte Writer müssen separat berücksichtigt werden. Dienste können während einer Dateikopie länger ausfallen, insbesondere bei großen Mail-/Nextcloud-Beständen. `MAIL_BACKUP_MODE=live` und abgeschaltete Stopps sind bewusste Ausnahmen mit Konsistenzrisiko. Das Log weist darauf hin. Home-Verzeichnisse werden mit `--one-file-system` kopiert: Daten anderer Mounts fehlen dann absichtlich. Mailqueues, zusätzliche Spamfilter-/DKIM-Verzeichnisse oder Maildatenbanken sind nur gesichert, wenn sie explizit im Umfang stehen. ### Speicher, Retention und Protokolle `MIN_FREE_GB` ist eine Reserveprüfung auf den Staging- und Archivdateisystemen, keine genaue Prognose. Die Kopie braucht Platz für unkomprimierte Quelldaten und Dumps; Archive benötigen zusätzlich komprimierten Platz. Beim Restore kommen die private Kopie des Sicherungssatzes und entpackte Daten hinzu. Eine Platte kann zwischen Prüfungen volllaufen; der Lauf muss dann scheitern. Ausreichend Reserve und ein externes Kapazitätsmonitoring einplanen. Neue lokale Sätze liegen zunächst unter `.partial-LAUFNAME`. Bei Fehlern bleiben Arbeitsdaten zur Analyse liegen. Sie sind **keine abgeschlossenen Backups** und werden nicht durch die Retention entfernt. Nach Ursachenklärung gezielt löschen; keine pauschale automatische Löschung gescheiterter Arbeitsstände. Retention: - Läuft erst nach Abschluss des neuen Backups und gegebenenfalls der kompletten Remote-Prüfung. - Erkennt nur v2-Laufnamen und einen zum Manifest passenden Abschlussmarker. - Löscht ganze Sätze, keine einzelnen Archivinhalte. - Berücksichtigt das UTC-Datum im Laufnamen und behält die jüngsten `RETENTION_KEEP_LAST` geeigneten Sätze; mindestens zwei sind vorgeschrieben, Default drei. - Lokale Sätze ohne `UPLOADED` werden bei aktiviertem Upload nicht gelöscht. Bei bewusst deaktiviertem Upload sind vollständige lokale Sätze löschbar. - Alte v1-Läufe, unbekannte Verzeichnisse, unvollständige Uploads und `.partial`-Verzeichnisse bleiben erhalten. - Remote-Retention ist in neuen Konfigurationen aus. Ein vorhandenes `ENABLE_REMOTE_RETENTION=true` bleibt beim Update erhalten und muss bei Bedarf bewusst geändert werden. Retention prüft die Metadaten alter Sätze, nicht erneut deren gesamte Nutzdaten. Ein `COMPLETE`-Marker beweist den erfolgreichen Abschluss zum Erstellungszeitpunkt, keine ewige Verfügbarkeit. Regelmäßige unabhängige Rücklese-/Restore-Proben bleiben erforderlich. Remote-Metadaten, die sich nicht lesen/validieren lassen, werden konservativ ausgelassen; fehlgeschlagene eigentliche Löschungen führen zu einem fehlgeschlagenen Lauf. Neue Backup-/Restore-Logs liegen unter `/var/log/app-backup` und werden nach `LOG_RETENTION_DAYS` (Default 30) bei einem erfolgreichen Backup entfernt. Historische v1-Protokolle bleiben zur manuellen Prüfung. Das systemd-Journal unterliegt seiner eigenen Aufbewahrung. Das Mail-Reporting ist zusätzlich; eine fehlende Mailzustellung wird protokolliert, sie ersetzt kein unabhängiges Monitoring. ## Betrieb und Monitoring ```bash sudo /usr/local/sbin/app-backup.sh --check sudo systemctl start app-backup.service sudo systemctl status app-backup.service --no-pager sudo journalctl -u app-backup.service --since today --no-pager sudo cat /var/backups/app-backup/last-backup.json sudo cat /var/backups/app-backup/last-successful-backup.json systemctl list-timers app-backup.timer ``` Exit `0` bedeutet, dass die ausgeführten erforderlichen Schritte erfolgreich waren. Jeder Fehlercode ist ein Fehler; `130`/`143` kennzeichnen INT/TERM. `last-backup.json` zeigt den letzten gestarteten operativen Lauf, `last-successful-backup.json` wird ausschließlich nach einem vollständig erfolgreichen Backup aktualisiert. `--check` aktualisiert diese Erfolgsmarkierung nicht. Sehr frühe Fehler vor der Laufinitialisierung/Sperrübernahme können nur auf stderr bzw. im Journal erscheinen; deshalb sowohl Servicefehler als auch Alter des letzten Erfolgs überwachen. Für den täglichen Zeitplan z. B. bei mehr als 36 Stunden ohne erfolgreichen Lauf alarmieren, zusätzlich freien Speicher, offene Recovery-Dateien und wachsende Partial-Verzeichnisse prüfen. Diese Überwachung läuft idealerweise außerhalb des gesicherten Servers. `TimeoutStartSec=12h` und `TimeoutStopSec=5min` sind Ausgangswerte und müssen zum ersten gemessenen Vollbackup plus Rückleseprüfung passen. Ein geplanter Backupabbruch erfolgt über `systemctl stop app-backup.service`. Normale INT-/TERM-Behandlung versucht die ursprünglichen Dienste wiederherzustellen. SIGKILL, Kernelabsturz oder Stromverlust können keine Shell-Traps ausführen. Dafür liegt `recovery-LAUFNAME.json` mit vorher aufgezeichnetem Wartungs-/Dienstzustand und Arbeitsordner im Workdir. Diese Datei ist eine manuelle Wiederanlaufhilfe, kein automatischer Neustartauftrag. Das System startet nicht blind Anwendungen über einen teilweise restaurierten Datenbestand. ## Wiederherstellung ### Prüfphase Ein Restore arbeitet zuerst auf einer privaten lokalen Kopie. Bei einer Remote-Quelle wird über dasselbe geprüfte Crypt-Remote heruntergeladen. Auch der Prüflauf benötigt daher lokalen Speicher und verursacht Download-Traffic. Vor Anwendung werden alle Archivgrößen und SHA-256-Werte mit dem Manifest verglichen, Kompressionsströme geprüft und sämtliche Tar-Einträge untersucht. Absolute Memberpfade, `..`, doppelte Einträge, unerwartete Komponentenwurzeln, Spezialdateien sowie Schreibpfade durch Symlinks/Hardlinks werden abgewiesen. Jede Komponente wird danach getrennt entpackt. Die Zielpfade stammen ausschließlich aus der lokalen vertrauenswürdigen Konfiguration, nicht aus dem Backup. - Ohne Ausführungsoption oder mit `--dry-run`: prüft und zeigt den Plan; keine Dienste, Dateien in Anwendungszielen oder Datenbanken werden geändert. - `--verify-only`: prüft den gesamten v2-Satz ohne Zuordnung zu Anwendungen. - `--extract-only`: prüft und entpackt, behält den angegebenen privaten Ergebnisordner. - `--apply --yes`: schreibt ausgewählte Inhalte in bestehende Zielverzeichnisse und Datenbanken. - `--components wordpress,db-wordpress`: explizite Komponentenauswahl, nur für in der Config aktivierte Komponenten. - `--no-db`, `--no-files`: schließen die jeweilige Kategorie aus. Auch bei einer Auswahl muss der vollständige Satz prüfbar sein. Fehlt eine laut Config erforderliche ausgewählte Komponente, wird vor Eingriffen abgebrochen. ### Ausführung und Nachkontrolle Einen echten Restore zunächst auf einem isolierten Testsystem mit eigener `restore.conf`, passender App-/DB-Version und vorbereiteten Zielverzeichnissen durchführen. Einen bekannten aktuellen Datenbestand separat sichern, bevor bestehende Anwendungen überschrieben werden. Für einen echten Restore mit aktivierter Nextcloud ist verwalteter Wartungsmodus vorgeschrieben. Alle in der Config aktivierten Anwendungen und zusätzlichen Writer werden für die Konsistenz angehalten, auch bei einer kleineren Komponentenauswahl. Dateien werden mit Metadaten zurückkopiert. Standardmäßig bleiben zusätzliche Zieldateien erhalten. `RESTORE_STRICT_DELETE=true` entfernt zusätzliche Dateien innerhalb ausgewählter Zielkomponenten; konfigurierte Excludes bleiben geschützt. Keine atomare Rücksetzung der gesamten Anwendung: Ein Fehler nach Beginn des Zurückschreibens kann einen Teilzustand hinterlassen. Nach Nextcloud-Code-/DB-Restore setzt das Skript den Wartungsmodus erneut und führt `occ maintenance:data-fingerprint` aus. Einen vor dem Restore bereits aktiven Wartungsmodus lässt es an. `maintenance:repair` oder ein vollständiger `files:scan` werden nicht pauschal ausgeführt; sie müssen zum konkreten Wiederherstellungsfall passen. Der Fingerprint informiert Clients über den wiederhergestellten Datenzustand. Quelle: [Nextcloud Restore](https://docs.nextcloud.com/server/latest/admin_manual/maintenance/restore.html). Nach einem erfolgreichen Restore prüfen: - WordPress: Startseite, Admin-Anmeldung, Uploads und Stichproben in Beiträgen. - Nextcloud: `occ status`, Anmeldung, Dateiinhalt/Download, Freigaben, Apps und einen kontrollierten Client-Test. - Gitea: Anmeldung, Repository-Clone, LFS/Attachments, kontrollierter Push; bei geänderten Installationspfaden ggf. Git-Hooks nach Gitea-Anleitung neu erzeugen. - Mail: Zustellung, IMAP-Zugriff, Queue, Rechte und Konfigurationstest von Postfix/Dovecot. - Datenbanken: relevante Tabellenzahlen, definierte fachliche Stichproben, Events/Routinen und Zeichensatz. - SELinux: Labels prüfen; `restorecon` nur passend zu den gewünschten Zielpfadregeln anwenden. Ein Umzug auf andere Pfade kann andere Labels erfordern. Bei einem Teilfehler: Log und `recovery-*.json` lesen, Arbeitsdaten erhalten, Fehlerursache beheben und Restore kontrolliert wiederholen oder auf einen separaten Ausgangssnapshot zurückgehen. Angehaltene Anwendungen bleiben für diese Prüfung gestoppt. Bereits gestartete Dienste bei einem Fehler im Wiederanlauf müssen gesondert kontrolliert werden. Es gibt keinen automatischen Datenbank-/Datei-Rollback. ### Alte Sicherungen v1-Split-Archive haben weder manifestgebundene Prüfsummen noch einen verlässlichen Abschlussmarker. Sie werden nur mit `--legacy-unverified --extract-only` geöffnet. Der Parser akzeptiert die bisherigen datierten Dateinamen und Komponentenwurzeln; jeder Archivinhalt wird geprüft und separat entpackt. **Vollständigkeit und ursprünglicher Inhalt lassen sich damit nicht nachträglich beweisen.** Nur die Dateien **eines** alten Laufs in einen eigenen Ordner kopieren. Alte DB-Dumps liegen gemeinsam unter `db/`, Gitea unter `files/gitea-data`, Mail-Homes ggf. gesammelt unter `files/mail-home`. Nach Prüfung manuell in vorbereitete Testziele kopieren und DB-Dumps mit einem passenden Client importieren. Alte monolithische Formate vor den Split-Archiven unterstützt dieser Entpackmodus nicht. Alte Sicherungen erst nach einer erfolgreichen neuen Offsite-Sicherung und echten Wiederherstellungsprobe freigeben. ## Installation, Update und Rückweg Die ausführbare Schrittfolge steht in [README: bestehende Installation aktualisieren](README.md#bestehende-installation-aktualisieren). Der Installer prüft Shell-/Python-Syntax, wartet nicht auf laufende Sicherungen, sondern lehnt ein Update bei gehaltener Sperre oder aktivem Backup-Service ab. Vor Änderungen werden ersetzte Dateien samt Inventar unter `/var/backups/app-backup-update-XXXXXXXX` gesichert. Neue Dateien werden über temporäre Geschwisterdateien installiert und atomar umbenannt. Das Gesamtpaket ist keine atomare Dateisystemtransaktion; während des Austauschs wird die gemeinsame Sperre gehalten und der Timer angehalten. Bei einem Teilfehler bleibt er gestoppt; das Rollback-Skript stellt das vorherige Inventar wieder her. | Pfad | Updateverhalten | | --- | --- | | `/usr/local/sbin/app-backup.sh`, `app-restore.sh` | Ersetzen, alte Version im Rollback-Paket | | `/usr/local/lib/app-backup/` | Gemeinsame Bibliothek und Python-Prüfer installieren | | `/etc/app-backup/app-backup.conf` | Behalten; neue Vorlage als `.conf.dist` | | `/etc/app-backup/*.cnf` | Bestehende Zugangsdaten behalten; ausschließlich `.example` installieren | | `/etc/systemd/system/app-backup.service` | Ersetzen; vorhandene systemd-Drop-ins bleiben bestehen und müssen geprüft werden | | `/etc/systemd/system/app-backup.timer` | Bestehenden Zeitplan behalten; Vorlage als `.timer.dist` | | `/etc/logrotate.d/app-backup` | Alte Rotationsregel durch Hinweis auf Laufprotokollbereinigung ersetzen | | `/usr/local/share/doc/app-backup/` | README und diese Dokumentation installieren | Der Installer setzt den Timer bei einer Neuinstallation nicht automatisch in Betrieb. `--enable-timer` ist optional für bereits vorbereitete Installationen. Ein zuvor aktiver Timer wird nach einem erfolgreichen gewöhnlichen Update wieder gestartet; beim v1→v2-Wechsel vorher ausdrücklich stoppen, Optionen abgleichen und neu testen. Durch `Persistent=true` kann ein verpasster Termin beim Wiedereinschalten unmittelbar nachgeholt werden. Das Rollback-Paket enthält eine geschützte Kopie der Hauptkonfiguration als Referenz. `rollback.sh` verändert die aktuell erhaltene Hauptkonfiguration nicht automatisch. Es setzt ersetzte Paketdateien zurück und entfernt Dateien, die vor dem Update nicht vorhanden waren. Selbst vorgenommene Config-Änderungen und Backup-/Restore-Daten bleiben separate Entscheidungen. Mit einem alten defekten Restore ist der Rückweg zur alten Software keine Verbesserung der Datenrettung. ### Zugangsdaten aus alten Commits Die Entfernung der drei DB-Dateien aus dem aktuellen Baum löscht deren Historie nicht. Betroffene DB-Accounts und Passwörter am Server identifizieren, Passwörter wechseln, die aktiven CNF-Dateien und gegebenenfalls weitere berechtigte Anwendungen anpassen und einen vollständigen Dump testen. Keine Werte in Issues, Chat, Logs oder neue Commits kopieren. Den Gitea-Repositoryzugriff und vorhandene Klone prüfen. Eine koordinierte Historienbereinigung wäre ein eigener Eingriff mit Auswirkungen auf alle Klone; sie ersetzt keine Passwortrotation und ist nicht Teil dieses Updates. ## Prüfung und Abnahme Reproduzierbar mit `tests/Dockerfile` und `tests/run.sh`. Der Container bekommt das Repository nur lesbar und benötigt während der Tests kein Netzwerk. Die MariaDB läuft ohne TCP-Port in einem temporären Verzeichnis. systemd/occ werden simuliert; alle eigentlichen Archive, Dateikopien, Dumps/Imports und Crypt-Operationen sind real. Ergebnis des lokalen Abschlusslaufs am 10.09.2026: **22 Tests erfolgreich**, Shell-Syntax und ShellCheck ohne Befund. Testumgebung: Debian 13.6, MariaDB 11.8.6, rclone 1.60.1-DEV (Distributionspaket), ShellCheck 0.10.0. Diese Versionsangaben beschreiben die Testumgebung, keine Empfehlung für die Produktivversion. Geprüfte Fälle umfassen: - Shell-Syntax und ShellCheck der Paketdateien. - MyISAM-Dump und tatsächlicher SQL-Import samt Dateiwiederherstellung. - gzip/zstd, korrekte Excludes, vorhandener Wartungsmodus und inaktive Dienste. - Lokaler Crypt-Upload mit vollständigem Rücklesen und Restore aus diesem Remote. - Fehler bei Dump, rsync, Kompression, Diensten und Remote-Rückleseprüfung. - Fehlende/beschädigte Archive, Pfadtraversal, Link-Pivots, unzulässige Tar-Dateitypen. - Gemeinsame Sperre, TERM-Abbruch, Prüflauf ohne falsche Erfolgsmeldung. - Selektiver Restore mit Löschung und Schutz ausgeschlossener Nextcloud-Daten. - Lokale/Remote-Retention mit Mindestanzahl und Erhalt alter/unvollständiger Läufe. - Paketupdate mit Erhalt von Config/Zeitplan und ausführbarem Rollback. **Serverabnahme steht separat aus:** echte Rocky-Linux-10-Umgebung, SELinux-/ACL-Verhalten auf den verwendeten Dateisystemen, installierte Nextcloud-/Gitea-Versionen, vollständige Writer-/Pfadliste, Berechtigungen des echten DB-Backupaccounts, tatsächlicher OneDrive-Upload großer Archive, Laufzeit/Speicherbedarf und fachliche Restore-Probe. Es wurde kein Produktivbackup ausgeführt und keine Serverinstallation verändert.