21 KiB
app-backup – geprüfte Sicherungen für den JR IT Server
21.09.2026 – Speicherblockade: Bei Platznot bleiben mit SPACE_KEEP_LOCAL=1
mindestens eine bestätigte lokale Sicherung und alle unbestätigten Sätze erhalten.
Ältere Kopien werden erst nach lokalen Inhalts- und erneuten Offsite-Prüfungen
entfernt, bis der geplante Platzbedarf gedeckt ist. Die normale Retention bleibt
bei drei Kopien. Ablauf und Grenzen,
Deployment. Die Speicherreparatur und der neue Backup-Lauf
auf Gemini wurden am 19.10.2026 erfolgreich abgenommen; siehe Abschlussprüfung.
Sichert WordPress, Nextcloud, native Gitea-Installationen mit MariaDB, Maildaten und zusätzliche Verzeichnisse. Jede Komponente erhält ein eigenes Archiv. Uploads gehen ausschließlich über ein rclone-Crypt-Remote; anschließend werden Existenz und Größe der Remote-Dateien geprüft. Vollständige Downloads mit Prüfsummen-, Entpack- und DB-Importprüfung erfolgen beim monatlichen Restore-Test.
Die vollständige Analyse, Architektur, Konfiguration, Betriebsanleitung und verbleibenden Grenzen stehen in Dokumentation.md.
Seit 16.09.2026 laden Backup, Platzbereinigung und Update-Abnahme keine Archive zur Prüfung zurück. Nur kleine Metadaten und der Abschlussmarker werden gelesen. Dateien mit falschem Inhalt, aber passender Größe werden erst beim Restore erkannt. Hintergrund und Prüfumfang.
Neu: Monatlicher Offsite-Restore-Test mit vollständiger Prüfung
aller Komponenten, isoliertem MariaDB-Import und Bereinigung auch bei Fehlern.
Für knappen Speicher wird jeweils nur eine Komponente verarbeitet. Das Update
mit --enable-restore-test erkennt die Produktions-MariaDB, bereitet das passende
lokale Docker-Image vor und prüft Initialisierung, Import und Bereinigung. Nach
Backup mit Remote-Dateiprüfung aktiviert es den Monatstimer.
--enable-ai-rem nimmt auf Gemini /srv/ai-rem und /etc/ai-rem samt
Dienststopps wieder auf; siehe Update-Anleitung.
Ein installierter ai-rem-managed-update.timer und sein Dienst werden dabei
ebenfalls vor dem Snapshot angehalten und anschließend fortgesetzt. Die
ai-rem-Betriebsautomatisierung
nutzt zusätzlich die konfigurierte Backup-Sperre, um parallele Updates zu vermeiden.
Speicherbereinigung und Fehlerbehebung beschreibt die automatische
Entfernung erkannter Arbeitsreste und die geschützte lokale Notfallaufbewahrung.
Ohne Aktivierungsoption bleibt ein bisher deaktivierter Restore-Test deaktiviert.
Produktionsstand und verbleibende Abnahme
Stand 19.10.2026: Nach geprüfter Archivverlagerung und Trennung von Staging
und aktivem Archiv bestand appbackup_2026-10-18_15-22-34_fe0edb31 um 03:53:28 MESZ
mit SUCCESS. Der Upload von 32,053 GiB, alle 17 Remote-Dateien samt Größen und
der Abschlussmarker wurden bestätigt. Anwendungen und Timer laufen wieder.
Der vollständige monatliche Offsite-Restore bleibt eine separate offene Abnahme.
Nachweise und Grenzen stehen in SPEICHER.md.
Auf Gemini bestand am 10.09.2026 ein vollständiger Paketlauf mit geschützter Dateikopie, DB-Dumps, OneDrive-Upload, vollständigem Rücklesen und separater Archivprüfung (14 Archive). Dienste und Backup-Timer wurden anschließend gestartet. Das belegt die technische Abnahme für die konfigurierte Umgebung. Eine fachliche Wiederherstellung mit funktionierenden Anwendungen auf einem Testsystem sowie unabhängige Alarmierung und separat verfügbare Crypt-Schlüssel bleiben Voraussetzungen für einen belastbaren Produktionsbetrieb; ihre Abnahme ist noch nicht bestätigt.
Ein OneDrive-HTTP-409 wurde im zweiten Upload-Versuch überwunden. Der Code erlaubt standardmäßig bis zu sechs Kopierversuche und veröffentlicht den Remote-Abschlussmarker erst nach erfolgreicher Remote-Datei-/Größenprüfung. Wiederkehrende Konflikte oder endgültige Fehler müssen untersucht werden; ein aktiver Timer allein belegt kein aktuelles Backup. Details: Fehlerbehandlung und Betriebsgrenzen.
Der aktuelle Stand ergänzt sichtbaren Fortschritt und lehnt nicht unterstützte
exportierte RCLONE_*-Optionen ab, die etwa die Inhaltsprüfung oder Dateiauswahl
verändern könnten. Diese Ergänzungen sind erst nach Installation des aktuellen
Pakets auf dem Server wirksam; der erfolgreiche ältere Paketlauf installiert sie
nicht nachträglich.
Der Abschlussmarker wird außerdem bytegenau bestätigt, einschließlich seiner
Zeilenumbrüche, damit Backup und Restore dieselbe strikte Prüfung verwenden.
Seit 12.09.2026 lässt sich der Hostname in Mail-Betreff und Bericht mit
MAIL_HOSTNAME festlegen, etwa gemini.jr-it-services.de statt
localhost.localdomain. Einstellung und Update-Schritte stehen unter
Mailberichte.
Automatisches Update-Paket (empfohlen)
Das neue Paket übernimmt Konfigurationsabgleich, Rechteprüfung, Installation,
vollständigen Backup-Probelauf und Remote-Datei-/Größenprüfung. Du benötigst auf dem
Server keinen Git-Checkout. Übertrage die gelieferte .run-Datei und ihre Prüfsumme
per SCP und starte im Zielverzeichnis:
sha256sum -c app-backup-update-VERSION.run.sha256
sudo bash app-backup-update-VERSION.run
VERSION durch den tatsächlichen Dateinamen ersetzen. Der längere Lauf arbeitet anschließend als systemd-Auftrag unabhängig von der SSH-Verbindung. Bei unklaren Gitea-/Cron-/rclone-Einstellungen fragt der Starter gezielt nach. Die Bedienung und der kombinierte Rückweg stehen in UPDATE_ANLEITUNG.md.
Der Starter bestätigt nur die Übergabe. Erst SUCCESS in der genannten
result.txt bestätigt Installation, Backup und Remote-Prüfung. Den Verlauf ohne
Terminal-Pager anzeigen:
sudo journalctl -fu app-backup-upgrade.service --no-pager
Ein Startabbruch nach genau 60 Sekunden in den Paketen 8e6c9a058fa9 und
56fbcdad74f4 ist durch die asynchrone Sperrübergabe behoben. Bei diesem Fehler
hatte die Installation noch nicht begonnen; das neue Paket erneut verwenden.
Die kurzlebige Unit kann nach einem Fehler verschwinden, das Journal bleibt
abfragbar. Diagnose und Details stehen in der Update-Anleitung.
Die folgende manuelle Anleitung bleibt als Alternative für individuelle Updates.
Auf Rocky/RHEL starten Backup-Dienst und Update-Auftrag über Bash mit
SELinuxContext=system_u:system_r:unconfined_service_t:s0. SELinux bleibt systemweit
aktiv; diese privilegierten Backup-Prozesse sind bewusst nicht durch SELinux
eingeschränkt. Dadurch können sie Web-/Mail-/Home-Daten samt Sicherheitsattributen
sichern. Der Installer ändert keine globalen rsync-Booleans und keine Quell-Labels.
Details und geprüfte Grenzen stehen in der Update-Anleitung.
Wesentliche Änderungen
- Funktionierender Restore mit Prüflauf, Entpackmodus, Komponentenauswahl und ausdrücklichem
--apply --yes. - Fehler bei Dump, rsync, Kompression und Remote-Dateiprüfung brechen den Lauf ab.
- Wartungsmodus und Dienststopps umfassen Datenbankdump und Dateikopie. Danach endet die Ausfallzeit; Kompression und Upload laufen aus dem Staging.
- Vorübergehende systemd-Startsperren verhindern die erneute Aktivierung konfigurierter Writer während der Kopie. Zustands- und Zeitstempelprüfungen brechen bei erkannten Neustarts vor Veröffentlichung ab. Eigene Sperren werden beim Wiederanlauf entfernt; bestehende Maskierungen bleiben erhalten.
- Laufende Stop-Aufträge sind zusätzlich gegen Verdrängung durch fremde Reload-/Start-Aufträge geschützt. Damit kann ein Healthcheck den geplanten Dienststopp nicht abbrechen; echte Stopfehler brechen das Backup weiterhin ab.
- Upload und Remote-Dateiprüfung melden ihre Phase und standardmäßig jede Minute rclone-Statistiken im Journal (
RCLONE_STATS_INTERVAL). Ein laufender Auftrag ist erst nach erfolgreicher Remote-Prüfung abgenommen. - Vollständige Sicherungssätze mit SHA-256-Manifest, Archivprüfung und zuletzt hochgeladenem
COMPLETE-Marker. - Retention löscht nur vollständige neue Sicherungssätze und behält mindestens
RETENTION_KEEP_LASTLäufe. Historische v1-Sicherungen bleiben erhalten; bei Platzdruck gilt zusätzlich die abgesicherte lokale Bereinigung aus SPEICHER.md. - Private Arbeitsverzeichnisse, gemeinsame Sperre für Backup/Restore/Update und Wiederanlaufhinweise nach Abbrüchen.
- Installer erhält die Serverkonfiguration und den Zeitplan und erstellt ein Rollback-Paket.
Bestehende Installation aktualisieren
Vor dem ersten Lauf: Für ein konsistentes Backup werden nun standardmäßig httpd, php-fpm, postfix und dovecot während der Kopie angehalten, soweit die zugehörigen Anwendungen aktiviert sind und die Dienste laufen. Fehlende Einstellungen in alten Konfigurationen erhalten diese neuen Defaults. Zusätzliche Cronjobs/Worker müssen in QUIESCE_SERVICES erfasst oder extern angehalten werden. Zeitfenster und Datenumfang vorher prüfen.
Die bisherigen echten *.cnf-Dateien wurden aus dem aktuellen Repository entfernt. Vor git pull auch mögliche Zugangsdaten im Server-Checkout sichern. Historische Commits können die alten Werte weiterhin enthalten; betroffene Datenbankpasswörter wechseln. Die Dateien unter /etc/app-backup werden vom Installer nicht überschrieben.
# Auf dem Server, im vorhandenen Checkout:
cd /pfad/zum/app-backup
sudo systemctl stop app-backup.timer
systemctl is-active app-backup.service
# Falls aktiv: den laufenden Job zuerst regulär fertig werden lassen.
# Einmalige geschützte Sicherung, bevor git pull alte Dateien aus dem Checkout entfernt:
sudo install -d -m 700 /var/backups/app-backup-before-v2
sudo cp -a /etc/app-backup /var/backups/app-backup-before-v2/etc-app-backup
sudo cp -a etc/app-backup /var/backups/app-backup-before-v2/checkout-app-backup
git status --short
# Lokale Anpassungen vorher sichern/auflösen; keinen Reset erzwingen.
git pull --ff-only
sudo ./install.sh
Der Installer nennt anschließend einen Ordner wie /var/backups/app-backup-update-XXXXXXXX mit alten Paketdateien und rollback.sh. Weil der Timer zuvor gestoppt wurde, bleibt er beim Update gestoppt. Eine sonst aktive Timer-Instanz wird vom Installer nach erfolgreichem Update wieder aktiviert; deshalb ist der vorherige Stopp für die Migration wichtig.
# Neue Optionen mit der bestehenden Konfiguration abgleichen (Datei enthält keine DB-Passwörter):
sudo diff -u /etc/app-backup/app-backup.conf /etc/app-backup/app-backup.conf.dist
sudoedit /etc/app-backup/app-backup.conf
# Vorhandene Dateinamen aus WP_DB_CNF / NC_DB_CNF / GITEA_DB_CNF verwenden:
sudo chown root:root /etc/app-backup/*.cnf
sudo chmod 600 /etc/app-backup/*.cnf
sudo chmod 600 /etc/app-backup/app-backup.conf
# Den tatsächlich konfigurierten rclone-Dateipfad verwenden:
sudo chown root:root /root/.config/rclone/rclone.conf
sudo chmod 600 /root/.config/rclone/rclone.conf
sudo /usr/local/sbin/app-backup.sh --check
# Im vereinbarten Wartungsfenster den ersten vollständigen Lauf ausführen:
sudo systemctl start app-backup.service
sudo journalctl -u app-backup.service -n 100 --no-pager
sudo cat /var/backups/app-backup/last-backup.json
--check prüft Werkzeuge, Quellpfade, Dateirechte, DB-Verbindungen, das globale
DB-Recht RELOAD für die Dump-Sperre, bei MariaDB den Zugriff auf Routinen-Metadaten,
Speicherreserve und Crypt-Zugriff.
Die Prüfung liest Quellverzeichnisse über rsync und überträgt nur deren
Verzeichnisattribute in einen privaten temporären Prüfbereich im Staging.
Dabei werden keine Datei-Inhalte kopiert. Die DB-Rechteprüfung liest Metadaten;
es werden keine Anwendungen gestoppt, keine globale DB-Sperre genommen und kein
Backup oder Upload ausgeführt. Die Vorprüfung ersetzt keinen vollständigen
Probelauf. Fehlt RELOAD, die tatsächlich verwendeten Backup-Konten ermitteln und
gezielt berechtigen; siehe DB-Rechte korrigieren.
In der Konfiguration insbesondere ergänzen/prüfen:
RCLONE_CONFIG="/root/.config/rclone/rclone.conf"
RCLONE_REMOTE_BASE="OneDriveCrypt:gemini" # vorhandenes Crypt-Ziel beibehalten!
RETENTION_KEEP_LAST=3
SPACE_KEEP_LOCAL=1 # nur bei Platznot, nach erneuter Prüfung
ENABLE_REMOTE_RETENTION="false" # während der Migration zunächst aus
MAIL_BACKUP_MODE="stop"
# Zentrale Mailablage muss existieren; bei ausschließlich in Homes liegenden
# Mailboxen ENABLE_MAIL_STORE=false und MAIL_HOME_DIRS vollständig konfigurieren.
ENABLE_MAIL_STORE="true"
# Tatsächliche Timer-/Service-Namen einsetzen; Timer vor seinem Service nennen:
QUIESCE_SERVICES=()
# Gitea war in der bisherigen mitgelieferten Konfiguration NICHT aktiviert:
# ENABLE_GITEA="true" nach Prüfung aller Gitea-Pfade und DB-Einstellungen setzen.
Den neuen Laufnamen aus Log oder last-backup.json übernehmen:
sudo /usr/local/sbin/app-restore.sh --remote-run appbackup_DATUM_UHRZEIT_ID --verify-only
sudo /usr/local/sbin/app-restore.sh --remote-run appbackup_DATUM_UHRZEIT_ID --extract-only
DATUM_UHRZEIT_ID ist ein Platzhalter, z. B. 2026-09-10_02-30-00_a1b2c3d4. Der Entpackmodus nennt seinen geschützten Ergebnisordner. Nach Prüfung der Dateien und einer erfolgreichen Wiederherstellungsprobe auf einem Testsystem den Zeitplan einschalten:
sudo systemctl enable --now app-backup.timer
systemctl list-timers app-backup.timer
Rückweg bei einem Paketproblem: Timer stoppen, aktiven Job fertig werden lassen und das vom Installer erzeugte Skript ausführen:
sudo /var/backups/app-backup-update-XXXXXXXX/rollback.sh
Das stellt die ersetzten Paketdateien wieder her. Konfigurationsänderungen und bereits zurückgeschriebene Anwendungsdaten werden dadurch nicht rückgängig gemacht. Der Timer bleibt bis zur manuellen Prüfung gestoppt. Der alte Stand enthält die im Review beschriebenen Mängel.
Neuinstallation
Benötigt Linux mit systemd, Bash ≥ 4.4, Python ≥ 3.9, GNU tar/coreutils, rsync mit ACL/xattr-Unterstützung, zstd bzw. gzip, sudo, PHP/occ, MariaDB-Clients und rclone. Details und Rechte siehe Dokumentation.
git clone https://gitea.jr-it-services.de/johannes/app-backup.git
cd app-backup
sudo ./install.sh
sudoedit /etc/app-backup/app-backup.conf
# DB-Dateien aus *.cnf.example erstellen; root:root und chmod 600 setzen.
# rclone Crypt als root konfigurieren, Schlüssel getrennt sicher verwahren.
sudo /usr/local/sbin/app-backup.sh --check
Der Installer aktiviert bei einer Neuinstallation den Timer standardmäßig erst auf ausdrückliche Anforderung. Nach Backup- und Restore-Probe: sudo systemctl enable --now app-backup.timer.
Backup und Restore
sudo /usr/local/sbin/app-backup.sh
# Standard ist ein geprüfter Plan, ohne Änderungen an den Anwendungen:
sudo /usr/local/sbin/app-restore.sh --local-run /var/backups/app-backup/archives/LAUFNAME
# Ausführung überschreibt ausgewählte Dateien und Datenbankobjekte:
sudo /usr/local/sbin/app-restore.sh --config /etc/app-backup/restore.conf \
--local-run /var/backups/app-backup/archives/LAUFNAME --apply --yes
# Nur WordPress-Dateien; Konfiguration definiert die Zielpfade:
sudo /usr/local/sbin/app-restore.sh --local-run /var/backups/app-backup/archives/LAUFNAME \
--components wordpress --no-db --apply --yes
# Alte Split-Archive ohne Manifest: nur kontrolliert entpacken, manuell prüfen/importieren:
sudo /usr/local/sbin/app-restore.sh --local-run /pfad/zu/einem/alten/lauf \
--legacy-unverified --extract-only
Ein echter Restore benötigt passende Zielverzeichnisse, Anwendungsstände und Datenbanken sowie einen DB-Account mit Wiederherstellungsrechten. --yes ist keine automatische Datenrettung bei Fehlern; bei einem Teilfehler bleiben angehaltene Dienste für die manuelle Prüfung stehen.
Mailberichte
Jeder beendete Backup-Lauf kann wieder einen ausführlichen UTF-8-Bericht senden:
Status SUCCESS/FAIL, Host, Laufzeit, letzte Phase, Upload-/Prüfergebnis,
Staging-Größen, Archivgrößen und Protokollauszug. Der Betreff verwendet wieder
das Schema [app-backup] SUCCESS/FAIL HOST LAUF.
Am Ende stehen zusätzlich die gesamte Verarbeitungsdauer, die nach Bereinigung
belegten lokalen Arbeitsbereiche (Archive, Staging, Restore und Restore-Test),
Protokolle sowie der freie Platz auf den zugehörigen Datenträgern. Der monatliche
Restore-Test erhält dieselbe Schlussübersicht.
In /etc/app-backup/app-backup.conf werden vorhandene Einstellungen weiterverwendet:
ENABLE_MAIL_REPORT=true
MAIL_TO="johannes"
MAIL_FROM="" # Standard: app-backup@<FQDN>
MAIL_SUBJECT_PREFIX="[app-backup]"
MAIL_HOSTNAME="" # Leer: System-FQDN; überschreibt nur Betreff/Bericht
MAIL_INCLUDE_LOG_TAIL_LINES=200
MAIL_SEND_TIMEOUT=30
Für Gemini vor dem Start des neuen Update-Pakets die aktive Konfiguration öffnen:
sudoedit /etc/app-backup/app-backup.conf
Diese Zeile ergänzen beziehungsweise einen vorhandenen Wert ersetzen:
MAIL_HOSTNAME="gemini.jr-it-services.de"
Anschließend das aktuelle .run-Paket wie oben beschrieben installieren. Es erhält
die Einstellung; bereits die Mail des Abnahmebackups verwendet dann beispielsweise
[app-backup] SUCCESS gemini.jr-it-services.de appbackup_2026-… und
Host: gemini.jr-it-services.de im Bericht. Auch Fehlermails verwenden den Wert.
Nach Installation dieser Version genügt für spätere Änderungen das Bearbeiten der
Konfiguration; sie wird bei jedem Lauf neu gelesen, ein Dienstneustart ist nicht nötig.
Ein leerer oder fehlender Wert verwendet weiterhin den automatisch über Python
socket.getfqdn() ermittelten Systemnamen. MAIL_HOSTNAME ändert weder den
Systemhostname noch Archivnamen oder Absender. Für den Absender gilt weiterhin
MAIL_FROM beziehungsweise bei leerem Wert app-backup@<System-FQDN>.
MAIL_TO darf wie bisher ein lokaler Alias oder eine Mailadresse sein. Versand
über /usr/sbin/sendmail; die lokale Mailzustellung muss dafür eingerichtet sein.
Der Bericht bleibt zusätzlich unter /var/log/app-backup/report_LAUF.eml mit
Modus 600 erhalten. Versandfehler werden protokolliert und verändern das
Backup-Ergebnis nicht. --check und Restore-Prüfungen senden keine Backup-Mail.
Die Mail bestätigt den Backup-Lauf; beim Update ist weiterhin dessen abschließende
result.txt nach Backup und Remote-Dateiprüfung maßgeblich. Der monatliche
Restore-Test führt seinen eigenen Erfolgsnachweis.
Tests
Verifikation am 21.09.2026: 132 unterschiedliche Tests bestanden, einschließlich Speicherblockade, Schutz der letzten lokalen Sicherung, Metadaten-Retries, Paketinstallation/Rollback und echtem Benutzer-systemd. Fünf optionale zusätzliche Docker-DB-Tests wurden nicht erneut ausgeführt. ShellCheck und Shell-Syntax sind erfolgreich; Details und Grenzen.
Der Restore-Test ist separat unter RESTORE_TEST.md dokumentiert, einschließlich Aktivierung, Platzbedarf, Statusdateien, Fehlern und Bereinigung.
docker build -t app-backup-test -f tests/Dockerfile .
docker run --rm --network none -v "$PWD:/src:ro" app-backup-test
ShellCheck, echte MariaDB-Dumps/Imports, gzip/zstd, rsync, lokale Crypt-Uploads und Remote-Restore sowie Fehlerfälle laufen im Container. systemd, Nextcloud-occ und der Mailversand sind simuliert; Nachrichten werden nur lokal erfasst. Am 12.09.2026 bestand der Standardlauf mit 81 Fällen (80 erfolgreich, ein separater Docker-Test planmäßig übersprungen). Zusätzlich bestanden alle 19 gezielten Restore-Tests einschließlich echter DB-Imports und Abbruchbereinigung sowie der Installer-Test mit aktiviertem Restore-Timer. ShellCheck, Shell-Syntax und systemd-Unit-Prüfung bestanden. Eine echte Rocky-/Nextcloud-/Gitea-/OneDrive-Wiederherstellungsprobe und die tatsächliche Mailzustellung bleiben Teil der Serverabnahme.
Der Konflikt zwischen Stop und fremdem Reload/Start lässt sich separat gegen einen echten lokalen Benutzer-systemd prüfen (ohne sudo). Der Test erzeugt nur drei eindeutig benannte, kurzlebige Testdienste und räumt sie wieder auf:
APP_BACKUP_TEST_USER_SYSTEMD=1 python3 -m unittest discover -s tests -p test_systemd_stop.py -v
Er reproduziert zunächst den abgebrochenen ungeschützten Stop trotz Startbedingung und prüft danach den produktiven Stop-Code gegen konkurrierende Reloads und Starts. Ohne diese ausdrückliche Aktivierung werden die drei Fälle übersprungen.
Verifikation der Stop-Korrektur am 15.09.2026: Der Container-Standardlauf bestand mit 119 Fällen (111 erfolgreich, fünf separate Docker-Integrationstests und die drei Benutzer-systemd-Tests planmäßig übersprungen; 906,3 Sekunden). Die drei Benutzer-systemd-Tests bestanden separat, einschließlich der Prüfung, dass der geschützte Stop den Dienst tatsächlich inaktiv hinterlässt. ShellCheck und Shell-Syntaxprüfung bestanden ebenfalls. Die Abnahme dieser Korrektur auf Gemini steht noch aus.