Make Nextcloud NAS backups independent, verified and retryable

This commit is contained in:
admin committed 2026-10-26 11:39:54 +01:00
1 parent ffe3c354be
commit 7e94915442
15 files changed
+889 -504

No files matched your search

+94 -249
View File
@@ -1,283 +1,128 @@
# Dokumentation: nextcloud-borg-backup
# Betriebsdokumentation
## Ziel
## Architektur und Befund
Diese Anwendung sichert den via rclone gemounteten Ordner `/home/johannes/Nextcloud` regelmäßig auf den via autofs erreichbaren Synology-Pfad `/net/thor/volume1/NetBackup/Nextcloud`.
Das vorhandene Projekt liegt unter `~/localdev/nextcloud-borg-backup`; Gitea: https://gitea.jr-it-services.de/johannes/nextcloud-borg-backup.
Die Sicherung verwendet Borg, weil Borg inkrementelle, deduplizierte und verschlüsselte Archive erstellt. Dadurch bleiben tägliche Backups platzsparend und einzelne Dateien können aus älteren Ständen wiederhergestellt werden.
Am 26.10.2026 wurde das Ausbleiben täglicher Sicherungen untersucht. Der persistente User-Timer startete nach dem Login teilweise vor dem Desktop-rclone-Mount: am 26.10. Abbruch wegen leerer Quelle um 08:48:38, rclone-Start erst 08:48:54. Es gab keine Wiederholung. Zusätzlich waren ein FUSE-Abbruch am 15.10. und ein Verzeichnis-Cache von 72 Stunden konfiguriert. Das NAS war erreichbar; das letzte erfolgreiche Archiv stammte vom 23.10., nicht vom 13.10.
## Architektur
Version 2 entkoppelt die Sicherung vollständig vom Desktop-Mount:
Bestandteile:
- `scripts/nextcloud-borg-backup`: Haupt-CLI für Backup, Restore, Suche, Mount, Check und Retention.
- `etc/config.example`: Beispielkonfiguration mit den produktiven Standardpfaden.
- `systemd/user/nextcloud-borg-backup.service`: systemd User Service für einen Backup-Lauf.
- `systemd/user/nextcloud-borg-backup.timer`: täglicher Timer.
- `scripts/install.sh`: Installation in das Home-Verzeichnis des Users.
- `scripts/uninstall.sh`: Entfernt Service, Timer und Executable, lässt Daten bewusst bestehen.
Die Anwendung läuft als User `johannes`. Das ist für diesen Fall sinnvoll, weil die Quelle ein rclone/FUSE-Mount unterhalb des Home-Verzeichnisses ist. Ein root-Service könnte je nach FUSE-Konfiguration keine stabilen Leserechte auf diesen Mount haben.
## Pfade
Standardkonfiguration:
```bash
SOURCE_DIR="${HOME}/Nextcloud"
REPO_DIR="/net/thor/volume1/NetBackup/Nextcloud/borg-repo"
ARCHIVE_PREFIX="nextcloud"
RETENTION_WITHIN="4w"
```text
Nextcloud WebDAV (rclone Nextcloud:)
-> vollständige Inventur, Plausibilitätsprüfung
-> rclone sync in privaten lokalen Zwischenstand
-> Hash-/Downloadvergleich, unveränderte zweite Cloud-Inventur
-> Borg create auf geprüftem NFS-Share
-> Dateizahl/Bytezahl vergleichen, Archivdaten verifizieren, Repository prüfen
-> atomare Erfolgsmarkierung
-> Aufbewahrung und Kompaktierung
```
Lokale Laufzeitdaten:
Die Cloud ist immer die Quelle. Keine Uploads oder Löschungen in Nextcloud. Nur der eigene lokale Zwischenstand wird mit `rclone sync --delete-after` an Cloud-Löschungen angepasst. Bei Fehlern entsteht kein als erfolgreich markiertes Archiv und es wird keine Aufbewahrungsbereinigung gestartet. Vorhandene Borg-Archive bleiben nutzbar.
## Dateien und Zustände
| Pfad | Inhalt |
| --- | --- |
| `~/.local/bin/nextcloud-borg-backup` | Bash-CLI für Backup und vorhandene Restore-Befehle |
| `~/.local/libexec/nextcloud-borg-runner.py` | Python-Steuerung, keine externen Python-Pakete |
| `~/.config/nextcloud-borg-backup/config` | Vertrauenswürdige Bash-Konfiguration |
| `~/.config/nextcloud-borg-backup/passphrase` | Bestehende Borg-Passphrase, niemals regenerieren |
| `~/.local/share/nextcloud-borg-backup/staging/` | Unverschlüsselter, privater Cloud-Zwischenstand |
| `~/.local/share/nextcloud-borg-backup/staging.owner.json` | Bindung des Zwischenstands an die Cloud-Quelle |
| `~/.local/state/nextcloud-borg-backup/last-success.json` | Zeitpunkt, Archivname, Dateizahl und Bytezahl der bestätigten Sicherung |
| `~/.local/state/nextcloud-borg-backup/last-attempt.json` | Laufstatus und Fehlerphase |
| `~/.local/state/nextcloud-borg-backup/repository.json` | Bindung an die Borg-Repository-ID |
| `~/.local/state/nextcloud-borg-backup/verified-inventory.json` | Zuletzt geprüfte Datei-Inventur mit Größen, Zeiten und Serverhashes |
| `~/.local/state/nextcloud-borg-backup/logs/` | Ablaufprotokolle, neue Protokolle nach 90 Tagen bereinigt |
Vollständige Kindprozessausgaben stehen im systemd-Journal; die zusätzlichen Ablaufdateien enthalten Phasen und Status. Die Marker werden über temporäre Dateien, fsync und atomare Umbenennung geschrieben. Ein laufender Zwischenstand ist kein Wiederherstellungspunkt. Der letzte bestätigte Stand liegt im Borg-Repository.
## Schutzmaßnahmen
- Ausschließlich ein Lauf dank `flock`; auch manuelle Wartungsbefehle verwenden denselben Lock. Borg besitzt zusätzlich seinen eigenen Repository-Lock. Fremde Borg-Prozesse werden nicht durch automatisches `break-lock` übergangen.
- Der NFS-Share muss exakt `EXPECTED_NAS_SOURCE` mit `EXPECTED_NAS_FSTYPE` entsprechen. Für die Workstation: `thor:/volume1/NetBackup`, `nfs4`. Autofs wird vor der Prüfung durch einen begrenzten Zugriff ausgelöst. Backup legt keine Verzeichnisse unter `/net` und kein Repository an.
- Erste Quell-Plausibilisierung gegen das jüngste vorhandene Borg-Archiv; danach gegen den letzten bestätigten Stand. Rückgang von Dateizahl **oder** Bytezahl um mehr als `MAX_SOURCE_DROP_PERCENT=20` führt zum Abbruch vor der Synchronisierung. Leere Quelle wird immer abgewiesen.
- Staging muss lokal sein, darf keine Symlinks/Mounts enthalten und darf sich nicht mit State, Repository oder `~/Nextcloud` überschneiden. Ein bereits gefülltes, fremdes Verzeichnis wird nicht übernommen.
- Hashvergleich, wenn jede Quelldatei MD5/SHA1 anbietet. Dateien ohne Serverhash werden vor dem Sync mit `rclone copy --ignore-times` erneut gelesen und anschließend zusätzlich per `rclone check --download` verglichen. Leere Dateien sind bereits durch Größe null eindeutig. Bei Zeilenumbrüchen in ungehashten Dateinamen erfolgt ein vollständiger Bytevergleich aller Dateien, weil rclones Dateiliste solche Namen nicht darstellen kann. Das kostet zusätzlich Übertragungszeit, vermeidet aber den unsicheren Rückfall auf reine Größenvergleiche.
- Zusätzlich berechnet Python die lokalen SHA1-/MD5-Werte unabhängig von rclone und vergleicht jede verfügbare Cloud-Prüfsumme sowie die Dateigrößen.
- Inventur vor und nach Sync/Check muss exakt gleich sein, einschließlich Dateinamen, Größen, Zeitstempeln und vorhandenen Hashes. Änderungen während des Laufs führen zu einem späteren neuen Versuch.
- Borg-Warnungen zählen als Fehler. Jeder neue Archivstand durchläuft `borg check --archives-only --verify-data --glob-archives <Archiv>` und einen Repository-Check. Ältere Optionen `FULL_CHECK_VERIFY_DATA`, `FULL_CHECK_INTERVAL_DAYS`, `REPOSITORY_CHECK_AFTER_BACKUP`, `SOURCE_DIR`, `ONE_FILE_SYSTEM` und `EXCLUDE_CACHES` beeinflussen den neuen Backup-Pfad nicht mehr: jedes neue Archiv wird geprüft und alle Cloud-Dateien werden erfasst. Aktive alte Exclude-Regeln führen zu einem erklärenden Abbruch, statt unbemerkt Dateien auszulassen.
- Erst nach dieser Prüfung wird der Erfolg gespeichert. Ein anschließender Wartungsfehler lässt das bestätigte Archiv gültig; der nächste Timerlauf versucht die Wartung erneut. `--keep-within 4w --keep-last 7` schützt zusätzlich die letzten sieben Archivstände.
- Einzelne Kindprozesse sind auf sechs Stunden begrenzt; systemd begrenzt den Gesamtlauf auf zwölf Stunden und beendet die gesamte Prozessgruppe. Keine endlose Wiederholung innerhalb eines Backup-Prozesses. Das nächste 15-Minuten-Fenster versucht erneut.
## Zeitplanung und Warnungen
`nextcloud-borg-backup.timer` prüft alle 15 Minuten plus maximal 60 Sekunden Zufallsverzögerung, erstmals zwei Minuten nach Start des User Managers. Eine bestätigte Sicherung ist nach 24 Stunden wieder fällig. `Persistent=true` holt verpasste Kalendertermine nach. Bei einem aktiven Lauf startet systemd keinen zweiten gleichnamigen Service.
`nextcloud-borg-health.timer` prüft unabhängig stündlich sowie nach Start des User Managers das Alter. Nach 36 Stunden ohne bestätigten Stand meldet `health` Exitcode 2 und der Wächter versucht eine kritische Desktop-Benachrichtigung. Fehler beim Backup werden ebenfalls gemeldet; Benachrichtigungen werden sechs Stunden gedrosselt. Ohne Desktop-Benachrichtigungsdienst bleiben Journal und Exitstatus die Überwachung. Es wird keine E-Mail versandt.
Auf der Workstation ist `loginctl show-user johannes -p Linger` bereits `yes`. Der User Manager kann damit ohne Desktop-Anmeldung laufen. Voraussetzung bleiben eingeschaltete Workstation, erreichbare Cloud/NAS und ohne Dialog lesbare rclone-/Borg-Zugangsdaten. Bei ausgeschaltetem Rechner erfolgen weder Sicherung noch Warnung; beim nächsten Start wird nachgeholt.
```bash
~/.config/nextcloud-borg-backup/config
~/.config/nextcloud-borg-backup/passphrase
~/.config/nextcloud-borg-backup/excludes
~/.local/state/nextcloud-borg-backup/logs/
~/Nextcloud-Restore/
nextcloud-borg-backup health
systemctl --user list-timers 'nextcloud-borg-*'
journalctl --user -u nextcloud-borg-backup.service --since today
journalctl --user -u nextcloud-borg-health.service --since today
cat ~/.local/state/nextcloud-borg-backup/last-attempt.json
```
## Installation
## Fehlerbehebung
**NAS nicht erreichbar / falscher Mount:** `findmnt -T /net/thor/volume1/NetBackup/Nextcloud/borg-repo` prüfen, autofs/NAS-Verbindung wiederherstellen. Niemals ein neues Repository an einem Ersatzpfad initialisieren. NFS-Hardmounts können bei Serverausfall im Kernel hängen; auch Prozess-Timeouts können einen ununterbrechbaren Kernelzugriff nicht sofort beenden.
**Cloud ändert sich während des Laufs:** Der nächste Timerlauf wiederholt. Bei dauernder hoher Änderungsrate ist eine ruhigere Zeit oder eine serverseitige Snapshot-Sicherung erforderlich. WebDAV liefert keinen atomaren Snapshot der gesamten Nextcloud. Lokale, noch nicht zur Cloud hochgeladene Desktop-Änderungen sind erst nach dem Upload Teil der Quelle.
**Quellrückgang / Löschgrenze:** Zuerst absichtliche Löschungen oder Umstrukturierungen prüfen und ältere Archive aufbewahren. Nur nach Prüfung `MAX_SOURCE_DROP_PERCENT` bzw. `MAX_STAGE_DELETES` in der privaten Konfiguration gezielt erhöhen, manuell sichern, anschließend Schutzgrenzen zurücksetzen. Diese Grenzen werden nicht automatisch aufgeweicht.
**Staging zu groß / Speicher knapp:** Keine Archive oder Staging-Daten während eines laufenden Backups löschen. Nach Stoppen des Services kann ausschließlich der Zwischenstand zusammen mit seiner Owner-Datei entfernt und später neu aufgebaut werden. Die Borg-Historie und Passphrase bleiben bestehen. Im Zweifel freien Speicher schaffen oder `STAGING_DIR` auf einen privaten lokalen Datenträger verschieben; die konservative Platzprüfung bleibt aktiv.
**Repository-ID geändert:** Erst klären, ob das richtige NAS und Repository vorliegen. `repository.json` nicht automatisch löschen. Bei bewusstem Repository-Wechsel State und Baseline nach Prüfung neu aufsetzen. Ein fehlendes oder beschädigtes State-JSON soll nicht still als Erfolg gelten.
**Abbruch / Stromausfall:** Ein unvollständiger Zwischenstand wird beim nächsten Lauf erneut synchronisiert. Ein Archiv vor der Erfolgsmarkierung gilt nicht als bestätigt und wird nicht als Anlass für Bereinigung verwendet. Borg-Checkpoint-Archive sind keine bestätigten Sicherungen.
## Wiederherstellung und Notfall
```bash
cd /home/johannes/localdev/nextcloud-borg-backup
./scripts/install.sh
```
Der Installer:
1. kopiert die CLI nach `~/.local/bin/nextcloud-borg-backup`,
2. legt die Konfiguration an, falls sie noch nicht existiert,
3. erzeugt eine Borg-Passphrase, falls noch keine existiert,
4. installiert systemd-User-Service und Timer,
5. aktiviert den Timer.
Für automatische Läufe ohne aktive Anmeldung:
```bash
sudo loginctl enable-linger johannes
```
## Erster Backup-Lauf
```bash
systemctl --user start nextcloud-borg-backup.service
```
Logs:
```bash
journalctl --user -u nextcloud-borg-backup.service -f
ls -lah ~/.local/state/nextcloud-borg-backup/logs/
```
Status:
```bash
nextcloud-borg-backup status
nextcloud-borg-backup list
```
## Zeitplanung
Der Timer läuft täglich:
```ini
OnCalendar=*-*-* 03:15:00
Persistent=true
RandomizedDelaySec=30m
```
`Persistent=true` sorgt dafür, dass ein verpasster Lauf nachgeholt wird, sobald der User-systemd-Manager wieder aktiv ist.
## Retention
Die Aufbewahrung ist auf vier Wochen ausgelegt:
```bash
RETENTION_WITHIN="4w"
```
Nach jedem Backup läuft:
```bash
borg prune --keep-within 4w
borg compact
borg check --repository-only
```
Damit bleiben alle Archive der letzten vier Wochen erhalten. Bei einem täglichen Timer sind dadurch mindestens tägliche Wiederherstellungspunkte über rund einen Monat verfügbar. Manuelle Zusatzläufe innerhalb der vier Wochen werden ebenfalls behalten.
## Resilienzmaßnahmen
Die Anwendung bricht bewusst ab, wenn eine Sicherheitsannahme nicht erfüllt ist:
- Quelle ist nicht erreichbar oder nicht lesbar.
- Quelle wirkt leer und `ALLOW_EMPTY_SOURCE=false`.
- Synology-Ziel ist nicht erreichbar oder nicht beschreibbar.
- Freier Platz am Ziel liegt unter `MIN_FREE_REPO_GB`.
- Borg-Passphrase fehlt bei verschlüsseltem Repository.
- Ein anderer Backup-Lauf hält bereits den Lock.
Weitere Maßnahmen:
- Borg-Repo wird bei Bedarf einmalig initialisiert.
- Borg verwendet deduplizierte Archive statt Vollkopien.
- Nach dem Backup läuft Retention und Kompaktierung.
- Nach dem Backup läuft ein Repository-Check.
- Optional kann regelmäßig ein vollständiger Datencheck aktiviert werden:
```bash
FULL_CHECK_VERIFY_DATA="true"
FULL_CHECK_INTERVAL_DAYS="7"
```
Ein vollständiger `borg check --verify-data` kann bei großen Repositories lange dauern.
## Dateien suchen
Über alle Archive suchen:
```bash
nextcloud-borg-backup search "Steuer"
```
Nur in einem bestimmten Archiv suchen:
```bash
nextcloud-borg-backup search "Steuer" nextcloud-2026-07-10_03-15-00
```
Die Ausgabe enthält bei archivübergreifender Suche den Archivnamen und den Pfad.
## Archive mounten
Alle Archive als lesbare Struktur mounten:
```bash
nextcloud-borg-backup restore "" "Dokumente/Beispiel.pdf" ~/Restore-Test
nextcloud-borg-backup restore nextcloud-2026-10-23_08-45-12 "Dokumente" ~/Restore-Test
nextcloud-borg-backup mount
```
Standard-Mountpunkt:
```bash
${XDG_RUNTIME_DIR}/nextcloud-borg-backup-mount
```
Ein einzelnes Archiv mounten:
```bash
nextcloud-borg-backup mount nextcloud-2026-07-10_03-15-00
```
Unmount:
```bash
nextcloud-borg-backup umount
```
Falls `borg mount` nicht verfügbar ist, muss auf Arch Linux das passende FUSE-Paket installiert sein.
Ziel ist standardmäßig `~/Nextcloud-Restore/<Archivname>`, niemals automatisch die produktive Cloud. Wiederhergestellte Dateien prüfen und erst danach gezielt zurückkopieren.
## Restore
Neuestes Archiv komplett wiederherstellen:
Für ein neues System braucht man Borg 1.x, Repository und die originale Passphrase bzw. den exportierten Schlüssel. Sicherer zusätzlicher Export:
```bash
nextcloud-borg-backup restore
BORG_PASSCOMMAND='cat /sicherer/pfad/passphrase' borg key export /net/thor/volume1/NetBackup/Nextcloud/borg-repo /sicherer/pfad/borg-key.txt
```
Ziel:
Passphrase und exportierten Schlüssel unabhängig vom NAS und der Workstation sicher verwahren. Keine Zugangsdaten in Git, Logs oder Chat kopieren.
```bash
~/Nextcloud-Restore/<Archivname>/
```
## Tests und Installation
Bestimmten Pfad aus dem neuesten Archiv wiederherstellen:
`make check` prüft Bash-Syntax, Python, alle vier systemd-Units und automatisierte Ausfallszenarien. Integrationstests verwenden echtes rclone mit lokalem Test-Remote sowie echtes Borg in temporären Verzeichnissen: Archiv erstellen, Daten prüfen, extrahieren und Bytevergleich. Sie verändern weder echte Cloud noch produktives Repository.
```bash
nextcloud-borg-backup restore "" "Dokumente/Projekt"
```
15 Tests prüfen außerdem leere/geschrumpfte Quelle, falsches NAS, fremdes/symlinkendes Staging, Netzfehler in Sync und Check, geänderte Cloud-Inventur, Repository-Austausch, konkurrierende Läufe, Timeout, Zeitrücksprung und Wartungswiederholung. Gleiche Dateigröße und gleicher Zeitstempel bei geändertem Inhalt werden im Integrationstest ausdrücklich abgedeckt.
Bestimmten Pfad aus einem bestimmten Archiv wiederherstellen:
Der Installer aktualisiert Programme und Units, erhält private Konfiguration und Schlüssel und startet die Timer neu. Eine fehlende Passphrase bei vorhandenem Repository führt zum Abbruch. Die Deinstallation stoppt Backup/Timer und entfernt Programme/Units; Daten, Schlüssel, State und Archive bleiben erhalten.
```bash
nextcloud-borg-backup restore nextcloud-2026-07-10_03-15-00 "Dokumente/Projekt" ~/Restore-Test
```
## Grenzen
Wichtig: Die Anwendung extrahiert standardmäßig nicht direkt zurück in den produktiven Nextcloud-Ordner. Das reduziert das Risiko, versehentlich aktuelle Dateien zu überschreiben. Nach Prüfung können die wiederhergestellten Dateien gezielt zurückkopiert werden.
Die Sicherung schützt die durch das konfigurierte Nextcloud-Konto erreichbaren Dateien. Nextcloud-Datenbank, App-Konfiguration, Freigaberechte und serverseitige Versions-/Papierkorb-Metadaten sind kein Bestandteil dieser Datei-Sicherung. Ein vollständiges Nextcloud-Server-Backup bleibt separat erforderlich. Aktuelle Schäden oder Ransomware können als neue Dateiversion übernommen werden; die vierwöchige Historie und Mindestanzahl ersetzen kein unabhängiges unveränderbares Offsite-Backup.
## Disaster Recovery
## Offizielle Referenzen
Für eine Wiederherstellung auf einem neuen System werden benötigt:
- [rclone sync: Zielrichtung und Löschverhalten](https://rclone.org/commands/rclone_sync/)
- [rclone check: Hash- und Downloadvergleich](https://rclone.org/commands/rclone_check/)
- [Borg 1.4 create: Warnungen und Fehler](https://borgbackup.readthedocs.io/en/1.4.5/usage/create.html)
- [Borg check](https://borgbackup.readthedocs.io/en/stable/usage/check.html)
1. das Borg-Repository unter `/net/thor/volume1/NetBackup/Nextcloud/borg-repo`,
2. die Borg-Passphrase aus `~/.config/nextcloud-borg-backup/passphrase`,
3. Borg selbst.
## Einrichtung auf der Workstation am 26.10.2026
Minimaler Restore:
Bestehende Konfiguration und Passphrase wurden beibehalten. Die neuen Einstellungen wurden ausdrücklich ergänzt; die vorherige Konfiguration liegt privat unter `~/.config/nextcloud-borg-backup/config.pre-v2-20261026`. Beide Timer sind aktiviert. Das vorhandene Linger blieb aktiv. Der Desktop-Mount und dessen Autostart wurden nicht umgebaut.
```bash
export BORG_PASSCOMMAND='cat /pfad/zur/passphrase'
borg list /net/thor/volume1/NetBackup/Nextcloud/borg-repo
borg extract /net/thor/volume1/NetBackup/Nextcloud/borg-repo::ARCHIVNAME
```
Die Passphrase sollte separat sicher abgelegt werden. Ohne Passphrase ist ein verschlüsseltes Repository nicht nutzbar.
## Wartung
Manueller Repository-Check:
```bash
nextcloud-borg-backup check
```
Vollständiger Datencheck:
```bash
nextcloud-borg-backup check --verify-data
```
Borg-Lock lösen, falls ein Lauf hart abgebrochen wurde und sicher kein Backup mehr läuft:
```bash
nextcloud-borg-backup break-lock
```
Timer deaktivieren:
```bash
systemctl --user disable --now nextcloud-borg-backup.timer
```
Deinstallation der lokalen Anwendung:
```bash
./scripts/uninstall.sh
```
Die Deinstallation löscht bewusst nicht:
- Borg-Repository,
- Passphrase,
- Konfiguration,
- Logs,
- Restore-Verzeichnis.
## Gitea-Upload
Das Verzeichnis `/home/johannes/localdev/nextcloud-borg-backup` ist als eigenständiges Projekt vorbereitet. Vor dem Push:
```bash
cd /home/johannes/localdev/nextcloud-borg-backup
git init
git add .
git commit -m "Initial nextcloud borg backup application"
git remote add origin <gitea-url>
git push -u origin main
```
Keine lokalen Geheimnisse werden im Projekt abgelegt. Die produktive Passphrase entsteht erst bei der Installation unter `~/.config/nextcloud-borg-backup/passphrase`.
Für die Erstabnahme wurde der private Zwischenstand aus dem vorhandenen Archiv vom 23.10. vorgefüllt. Der anschließende direkte Cloud-Abgleich und sämtliche Prüfungen bleiben dadurch unverändert erforderlich; ein aus dem Altarchiv gelesener Stand gilt nicht automatisch als aktuelle Sicherung. Der beobachtete aktuelle Cloud-Bestand umfasst 8.218 Dateien mit 38.274.641.627 Bytes.