Skip to content

Releases: ArnaudFeld/pabo

v1.3.1

Choose a tag to compare

@ArnaudFeld ArnaudFeld released this 30 Sep 03:40

Was kaputt war

Die Zeile [DB] ✅ Dump OK (512) stand 20 Tage in Folge in deinem Log – und sie war reine Anzeigekosmetik mit fataler Nebenwirkung. du -h zählt belegte Blöcke, nicht die Dateigröße. Auf deinem ZFS-Dataset bedeutet das:

Datei du -h wirkliche Größe
leer 512 0 B
100 000 B 512 98 K
pg_dump 512 73,8 MB

Die erste Zahl einer leeren Datei ist ein Block, nicht ihr Inhalt. Deine Backups waren also die ganze Zeit in Ordnung – ich habe in der Fehlersuche deshalb zu früh geschlossen, der Dump sei leer.

Was jetzt anders ist

Die Größenangabe stimmt. Alle fünf du-Aufrufe gehen über human_size, das --apparent-size nutzt. Wo GNU coreutils fehlt, fällt es auf wc -c zurück.

Ein leerer Dump gilt als Fehler. pg_dump kann mit Rückgabewert 0 zurückkommen, obwohl kaum etwas geschrieben wurde. PABO prüft den Dump jetzt zusätzlich auf Mindestgröße, Zahl der CREATE/COPY-Anweisungen und den Abschlussmarker database dump complete, den ein echter pg_dump immer schreibt. Statt nur eines Exit-Codes nennt die Meldung den konkreten Grund:

❌ Backup fehlgeschlagen
🔴 Fehler: [DB] PostgreSQL-Dump
⚠️ pg_dump meldete Erfolg, der Dump ist aber unbrauchbar: Abschlussmarker fehlt – vermutlich abgebrochen

Nebenbei gefunden

grep -c kann bei einem Fehlschlag mehrzeilig werden und hat damit in der Dump-Prüfung eine Arithmetik-Abstürzung ausgelöst (syntax error in expression). Normalisiert.

Tests: 163 bestanden, 0 fehlgeschlagen.

v1.3.0

Choose a tag to compare

@ArnaudFeld ArnaudFeld released this 29 Sep 21:16

1. Stiller Ausfall wird gemeldet

Bisher hat jeder Timer nach oben Erfolg gemeldet, aber nirgends nachgesehen, wann das letzte Backup war. Ein toter Backup-Timer sieht damit genauso aus wie ein guter Tag ohne Backup – und genau das ist das Problem.

Nach jedem erfolgreichen Lauf schreibt PABO jetzt einen Zeitstempel, und jeder reguläre Lauf prüft dessen Alter:

❌ Kein erfolgreiches Backup seit 3 Tagen
📅 Letztes Backup vor 3 Tagen
⏳ Erwartet: mindestens alle 2 Tage
🔎 Grund: prüfe den Backup-Timer (systemctl list-timers | grep paperless)

Die Prüfung sitzt in allen drei erzeugten Skripten, nicht nur im Backup. Das ist der entscheidende Punkt: Ein toter Timer erreicht sonst keinen Aufruf, und genau deshalb wäre eine Prüfung im Backup selbst nutzlos.

STALE_BACKUP_DAYS steuert die Schwelle, Standard 2 Tage, im Setup einstellbar und in der Config änderbar. Ohne den Schlüssel gilt der Standardwert, bestehende Configs laufen unverändert.

Was das nicht leistet: Fällt der Host komplett aus – kein Strom, keine Platte, kein Netz –, schweigt auch dieser Check. Er ist die Ergänzung zu einer externen Überwachung, nicht deren Ersatz.

2. Logs rotieren sich selbst

Das Backup-Log war auf einem Host bereits 6,2 MB groß und wuchs unbegrenzt weiter. Das Setup legt jetzt eine Regel unter /etc/logrotate.d/paperless-backup an: wöchentlich, acht Generationen, komprimiert. Vorhandene Regeln werden nicht überschrieben, und fehlt logrotate auf dem System, läuft alles unverändert weiter.

3. Der Restore-Test prüft den DB-Dump wirklich

Bisher genügten die ersten fünf Zeilen des Dumps und die Existenz der Datei. Ein mitten im Schreiben abgeschnittener Dump wäre durchgegangen – und du hättest es erst bei einem echten Restore gemerkt.

Geprüft werden jetzt:

  • Mindestgröße
  • Zahl der SQL-Anweisungen
  • der Abschlussmarker database dump complete, den ein echter pg_dump immer schreibt

Läuft der Datenbank-Container, kommt eine Syntaxprüfung per psql hinzu. Dann ist die Meldung nicht mehr „die Datei sieht gut aus", sondern „der Dump ist lesbar".

4. Die Dateisystem-Warnung greift endlich

Der Vergleich lief über df --output=source. Bei einem BIND-Mount meldet das das Datenverzeichnis als anderes Gerät, obwohl der Speicher derselbe ist – der Hinweis „Repo liegt auf derselben Platte" blieb also aus. PABO löst den Mountpunkt jetzt über findmnt auf und fällt auf df zurück.

5. run_setup() aufgeteilt

397 Zeilen Frageblock sind jetzt neun Funktionen mit Namen. run_setup() hat 91 Zeilen und liest sich als Ablauf. Das Verhalten ist unverändert; ein Befehlsvergleich vor und nach dem Umbau ergibt null verlorene Zeilen.

v1.2.1

Choose a tag to compare

@ArnaudFeld ArnaudFeld released this 29 Sep 20:53

Neu: Bezeichnung nachträglich ändern

Die Frage nach der Telegram-Bezeichnung gab es bisher nur im vollständigen Setup. Auf einem Host, der schon konfiguriert ist, kommt man nicht an sie. Jetzt gibt es einen dritten Modus:

sudo pabo.sh  # → 1) setup → 3) Nur Bezeichnung für Telegram ändern
🏷️  Telegram-Bezeichnung ändern

   Das ist die Kennung, die über jeder Telegram-Meldung steht.
   Leer lassen schaltet auf den automatischen Fallback zurück
   (Hostname + IP-Adresse).

   Aktuell: keine (automatischer Name)

   Neue Bezeichnung (leer = entfernen, b = abbrechen): Privat

Es wird ausschließlich diese eine Zeile angefasst. Die Datei wird nicht neu geschrieben, sondern zeilenweise verarbeitet, damit Kommentare und Handänderungen erhalten bleiben. Vorher entsteht eine Sicherung mit Zeitstempel, und falls die Config danach ungültig wäre, rollt PABO sie zurück. Leere Eingabe entfernt den Namen wieder, b bricht ab.

Für die Log-Zeile genügt es, die Scripts einmal neu zu erzeugen (setup → 2). Die Telegram-Meldungen zeigen den neuen Namen ab dem nächsten Backup.

Auf neue Hosts

Unverändert gilt: ein neuer Host wird wie bisher mit einem Durchlauf durch setup eingerichtet, die Frage nach der Bezeichnung kommt direkt nach der Chat-ID. Die bestehenden Hosts brauchen für einen Namen jetzt keinen Handgriff in der Config mehr.

v1.2.0

Choose a tag to compare

@ArnaudFeld ArnaudFeld released this 29 Sep 20:42

Was neu ist

Telegram-Meldungen sagen jetzt, von welchem Host sie kommen. Wer PABO auf mehreren Maschinen betreibt, bekommt sonst alle Meldungen in denselben Chat und kann sie nicht zuordnen. Jede Nachricht beginnt jetzt mit dem Absender:

🏠 Privat
✅ Backup fertig – 1284 Dateien in 3m 20s

Die Zeile ist bewusst separat, damit Statuszeichen und Meldungstext unverändert bleiben. Dieselbe Kennung steht einmal pro Lauf im Log, damit sich Log und Nachricht zuordnen lassen.

Einrichtung

setup fragt nach einer optionalen Bezeichnung. Leer lassen ist ausdrücklich erlaubt, dann ermittelt PABO die Kennung selbst:

🏠 paperless-ngx (10.10.10.108)

Bestandshosts brauchen dafür nichts zu tun – der automatische Fallback greift ohne Eingriff, es ist also auch kein setup-Durchlauf nötig, nur wenn du einen Namen setzen willst.

Regeln für INSTANCE_NAME

Freitext bis 40 Zeichen. Quotes, Backslash, $ und Backtick sind nicht erlaubt, damit der Wert die Bash-Datei nicht aushebeln kann. Nach dem Ändern Scripts und Timer neu erzeugen.

Technisches

Die IP wird bevorzugt aus der Route des ausgehenden Verkehrs gelesen (ip route get). hostname -I dient nur als Rückfall und wird dabei um Loopback- und Linklocal-Adressen bereinigt – sonst würde bei mehreren Maschinen die 127.0.0.1 der ersten im Weg stehen. Läuft PABO hinter mehreren Netzwerkkarten, ist die aktive Route diejenige, die auch der Bot und rclone benutzen.

v1.1.1

Choose a tag to compare

@ArnaudFeld ArnaudFeld released this 29 Sep 20:23

Korrektur für das Update von 1.0.5. Das Setup bis 1.0.5 schrieb Array-Einträge der Konfiguration mit printf %q, wodurch Glob-Metazeichen maskiert in der Datei landeten: *.tmp stand dort als \*.tmp. Der Parser aus 1.1.0 lehnt Backslashes ab, weshalb jedes Update mit BORG_EXCLUDES enthält ungültigen Eintrag abbrach – betroffen war jede Installation, die die Standard-Excludes verwendet.

Array-Werte mit Backslash werden jetzt beim Einlesen aufgelöst und danach wie gewohnt validiert. Eine neu geschriebene Konfiguration enthält sie ohne Backslash. Schädliche Inhalte bleiben abgelehnt.

cd /opt/pabo && git pull
sudo bash pabo.sh    # → 1) setup → 2) Scripts und Timer neu generieren
sudo pabo.sh        # → 5) config-check

v1.1.0

Choose a tag to compare

@ArnaudFeld ArnaudFeld released this 29 Sep 20:16

Backup-Skript für Paperless-ngx, Version 1.1.0. Vollständige Liste aller Änderungen in CHANGELOG.md.

Was sich geändert hat

Backup-Modell. Vorher hat jedes Cloud-Ziel ein eigenes Archiv erzeugt. Borg zählt Archive, nicht Tage, 14 tägliche Archive bedeuteten bei drei Zielen also rund fünf Tage statt zwei Wochen. Jetzt entsteht ein Archiv pro Lauf, das danach in alle Ziele hochgeladen wird.

Upload-Schutz. rclone sync löscht im Ziel alles, was lokal fehlt. Ein nicht eingehängtes Repository hätte so die Cloud leergeräumt. Vor jedem Sync prüft jetzt ein Preflight, ob config, Archive und Datensegmente vorhanden sind, zusätzlich begrenzt RCLONE_MAX_DELETE (Standard 500) die Löschungen.

Konfiguration wird nicht mehr ausgeführt. Die Datei /etc/paperless-backup.conf wurde per source als Shellcode mit root-Rechten geladen. Sie wird jetzt zeilenweise als Text gelesen, jeder Schlüssel muss auf einer Whitelist stehen, jeder Wert wird vor der Zuweisung geprüft.

Prüfungen vor dem Backup. Freier Platz (BACKUP_MIN_FREE_MB, Standard 4096 MB), laufende Container, Passphrase-Datei (kein Symlink, root, Mode 600) und die Borg-Version werden vor dem eigentlichen Backup geprüft.

systemd. Die Units hatten kein TimeoutStartSec, damit galt der Standard von 90 Sekunden und ein laufendes Backup wurde mitten im borg create beendet. Jetzt TimeoutStartSec=infinity, ergänzt um UMask=0077, ProtectSystem=full und PrivateTmp. Locks liegen in /run/pabo (0700) statt im für alle schreibbaren /var/lock.

Restore. Download in ein eigenes Verzeichnis, borg check vor dem Extrahieren, Archivname wird gegen die Liste geprüft, Paperless-Container wird gestoppt, psql läuft mit -v ON_ERROR_STOP=1 – ein teilweise eingespielter Dump wird nicht mehr als Erfolg gemeldet.

Update von 1.0.5

cd /opt/pabo && git pull
sudo bash pabo.sh    # → 1) setup → 2) Scripts und Timer neu generieren
sudo pabo.sh        # → 5) config-check

Zwei neue Konfigurationsschlüssel bekommen automatisch Standardwerte: RCLONE_MAX_DELETE=500 und BACKUP_MIN_FREE_MB=4096.

Zu beachten: Die Konfigurationsprüfung ist strenger als früher. Pfade mit Leerzeichen, .. oder doppelten Schrägstrichen sowie ein Bot-Token ohne <id>:<token>-Form brechen den Lauf ab. Von Hand ergänzte Schlüssel, die nicht auf der Whitelist stehen, müssen entfernt werden – config-check benennt sie.