No description
  • Python 65.3%
  • HTML 23.2%
  • Shell 7.8%
  • JavaScript 1.8%
  • CSS 1%
  • Other 0.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
nicedevil007 a064fe0540 Der Dialog hatte keine Breite — Tailwind verwarf sie stillschweigend
w-[min(30rem,90vw)] und max-w-[90vw] stehen beide nicht im gebauten CSS.
Tailwind versteht den Wert nicht und lässt die Regel weg, ohne ein Wort
zu sagen. Ohne Breite wuchs der Kasten auf die Länge seines längsten
Satzes und lief über die halbe Seite, links statt in der Mitte.

Jetzt m-auto und max-w-md am dialog, w-full am Formular — alles aus
Tailwinds eigenem Vorrat, alle drei im gebauten CSS nachgezählt.

ui/klassen.test.mjs prüft das ab sofort: jede Klasse mit eckigen
Klammern aus einem schlichten class="…" muss eine Regel im gebauten CSS
haben. Jinja-Ausdrücke und :class-Bindungen bleiben außen vor, die
werden erst beim Rendern zu Klassen. Gegen die kaputte Fassung schlägt
er fehl, gegen die reparierte nicht.

Damit prüfen npm test und make test jetzt drei Dinge, die alle nur im
Browser sichtbar wären: Syntax der Skriptblöcke, erzeugte Klassen, und
die Umrechnung der Zeitpläne.
2026-09-05 07:24:45 +00:00
assets Logo als PNG beilegen und in der README einbinden 2026-08-23 08:38:38 +00:00
config pgbackrest: repo1-checksum-type gibt es nicht 2026-08-23 21:57:50 +00:00
deploy/docker Templates und statische Dateien gehören ins Paket 2026-08-23 20:31:09 +00:00
docs docs: kein erfundenes pki-init-Kommando versprechen 2026-08-23 20:57:22 +00:00
scripts Fortschritt anzeigen — mit pgBackRests eigener Zahl 2026-08-24 19:13:17 +00:00
src Der Dialog hatte keine Breite — Tailwind verwarf sie stillschweigend 2026-09-05 07:24:45 +00:00
systemd Konfiguration schreiben scheiterte am eigenen Sandkasten 2026-08-24 21:17:50 +00:00
tests Rückfrage gestalten statt das Browserfenster nehmen 2026-09-05 07:14:38 +00:00
ui Der Dialog hatte keine Breite — Tailwind verwarf sie stillschweigend 2026-09-05 07:24:45 +00:00
.dockerignore Docker-Fassung des Hubs 2026-08-23 19:24:08 +00:00
.gitignore Backup-Layer: pgBackRest-Konfiguration, Timer und Dump-Skripte 2026-08-23 08:36:41 +00:00
CHANGELOG.md Installer, Restore-Drill und Dokumentation 2026-08-23 08:37:21 +00:00
install.sh Installer, Restore-Drill und Dokumentation 2026-08-23 08:37:21 +00:00
Makefile Der Dialog hatte keine Breite — Tailwind verwarf sie stillschweigend 2026-09-05 07:24:45 +00:00
package-lock.json Hub: API, OIDC gegen authentik und Oberfläche 2026-08-23 08:37:21 +00:00
package.json Der Dialog hatte keine Breite — Tailwind verwarf sie stillschweigend 2026-09-05 07:24:45 +00:00
pyproject.toml Templates und statische Dateien gehören ins Paket 2026-08-23 20:31:09 +00:00
README.md docs: Quellen über HTTPS klonen 2026-08-23 20:07:09 +00:00
tailwind.config.js Hub: API, OIDC gegen authentik und Oberfläche 2026-08-23 08:37:21 +00:00
uninstall.sh Installer, Restore-Drill und Dokumentation 2026-08-23 08:37:21 +00:00
uv.lock Assistent aufgeräumt, Live-Log repariert 2026-08-24 19:52:14 +00:00

walross

walross

pgBackRest-Manager mit WebGUI — Backup, PITR und Einzel-DB-Restore für PostgreSQL

Der Name kommt vom WAL, dem Write-Ahead Log. Es ist das, worauf jede Wiederherstellung aufsetzt: ohne lückenlose WAL-Kette gibt es kein Point-in-Time Recovery, sondern nur einzelne Backup-Zeitpunkte.


Was es tut

  • Backup-Layer — pgBackRest mit block-inkrementellen Backups, WAL-Archiving und systemd-Timern für Full, Diff, Incr, verify und check.
  • PITR — Wiederherstellung auf einen beliebigen Zeitpunkt, eine LSN, einen benannten Restore Point oder bis zur Konsistenz.
  • Einzel-DB-Restore mit Rollen — der Teil, den pgBackRest allein nicht löst.
  • WebGUI — Dashboard mit Ampel, Backup-Liste, Aktionen mit Live-Log, Restore-Wizard, Rollen-Diff, Logs, Konfiguration.
  • OIDC gegen authentik — die Anwendung validiert das Token selbst.
  • Restore-Drill — der Probelauf gegen eine Wegwerf-Instanz, mit Owner- und ACL-Vergleich.

Warum der Einzel-DB-Restore ein eigener Aufbau ist

pg_dump einer einzelnen Datenbank enthält kein CREATE ROLE. Rollen liegen in pg_authid, einem cluster-globalen Katalog. Wer nur die Datenbank zurückspielt, bekommt Objekte, deren Owner im Ziel fehlen — pg_restore scheitert dann mitten im Lauf oder legt Objekte mit falschem Owner an.

Der naheliegende Ausweg wäre, den kompletten pg_dumpall --roles-only einzuspielen. Genau den geht walross nicht: mit -c erzeugt er DROP ROLE auf produktiv genutzte Rollen, ohne -c überschreibt er Attribute und Passwörter bestehender Rollen.

Stattdessen:

  1. Aus dem Globals-Dump werden nur die tatsächlich benötigten Rollen ermittelt — Owner der Datenbank, Owner aller enthaltenen Objekte, alle in GRANTs, Default Privileges und RLS-Policies referenzierten Rollen, dazu der transitive Abschluss über Gruppenmitgliedschaften in beide Richtungen.
  2. Verglichen wird gegen den Ist-Zustand; angelegt wird nur, was fehlt.
  3. Erst dann pg_restore -C --clean --if-exists.
  4. Zum Schluss werden Owner und ACLs gegen den Backup-Stand geprüft und Abweichungen als Bericht ausgegeben.

Jede vorgeschlagene Rolle trägt die Begründung, über die sie in den Plan kam. Vor der Ausführung steht immer ein Dry-Run.

Architektur

Browser ──NPMplus──► walross-hub               walross-agent (je PostgreSQL-Host)
                     OIDC/authentik    ──mTLS──►  prüft jede Absicht lokal
                     Rollen, Audit, UI            sudo ► ein Helper, root
                     SQLite (Jobs)                pgbackrest / pg_dump / systemd-run
                     kein DB-Zugriff              /srv/pgbackrest (lokale vDisk)

Der Hub schickt Absichten, keine Kommandozeilen. Er sagt „Diff-Backup der Stanza pg18-crm", nicht pgbackrest --stanza=… --type=diff backup. Das argv baut der Agent aus seiner eigenen Konfiguration. Ein fehlerhafter oder übernommener Hub ist damit kein Weg zu beliebiger Codeausführung auf den Datenbankhosts.

Der Agent hat ein eigenes Veto. read_only: true in /etc/walross/agent.yaml sperrt schreibende Vorgänge auf diesem Host, unabhängig davon, was der Hub oder das Rollenmodell sagt.

Jobs leben beim Agenten, als transiente systemd-Unit. Ein Neustart des Hubs, ein Verbindungsabbruch oder ein Update schneiden kein laufendes Backup und kein laufendes Restore ab.

Quickstart

# Auf einem PostgreSQL-Host
git clone https://git.glaeser-it.de/klueber-it/walross.git /opt/walross-src
cd /opt/walross-src
sudo ./install.sh --agent

# /etc/walross/agent.yaml anpassen, Zertifikate nach /etc/walross/tls/
sudo systemctl enable --now walross-agent
sudo systemctl enable --now walross-backup-full@pg18-main.timer

# Auf dem UI-Host — nativ
sudo ./install.sh --hub
# /etc/walross/hub.yaml anpassen (OIDC, Agentenliste)
sudo systemctl enable --now walross-hub

Auf einem Docker-Host läuft der Hub stattdessen als Compose-Stack; Dockerfile und Referenz-Compose liegen unter deploy/docker/. Der Agent bleibt in jedem Fall nativ — er muss pgbackrest und pg_dump als postgres ausführen, transiente systemd-Units starten und den Cluster stoppen können. Im Container ginge das nur mit Rechten, die das Sicherheitskonzept aufheben.

Ausführlich: docs/deploy.md. Im Homelab läuft das Ausrollen über die Ansible-Rollen walross_agent und walross_hub sowie playbooks/deploy_walross.yml.

Rollen

Rolle darf
viewer Dashboard, Backup-Liste, Logs
operator zusätzlich Backup, Verify, Expire, Dumps anstoßen
admin zusätzlich Restore, Konfiguration, Audit-Log

Zuordnung über den Gruppen-Claim aus authentik, gemappt in der Konfiguration. Kein passender Claim heißt: kein Zugang — kein stilles Zurückfallen auf viewer. Durchgesetzt wird das serverseitig pro Endpoint; das Ausblenden von Knöpfen in der Oberfläche ist Kosmetik.

Maschinenzugriff läuft über einen eigenen Client-Credentials-Provider mit Scopes (walross:read, walross:operate). /api/v1/status?format=prometheus liefert Metriken. Statische API-Keys gibt es nicht.

Tests

pip install -e '.[dev]'
pytest

Schwerpunkt liegt dort, wo Fehler still bleiben:

  • Rollen-Extraktion — Passwort-Hashes mit Semikolon, quotierte Namen, GRANTED BY, PG16-Optionen, Zyklen, Systemrollen.
  • Autorisierung — jeder schreibende Endpoint wird mit viewer-Rolle aufgerufen und muss 403 liefern. Ein Test findet Endpoints, die neu dazukommen und in keiner Prüfliste stehen.
  • Kommandoerzeugung — dass keine Eingabe ein argv erzeugt, das nicht aus der Konfiguration stammt.

Was diese Lösung nicht kann

Ehrlich, weil es beim Restore zu spät ist, das herauszufinden:

  • Kein Restore einzelner Tabellen oder Zeilen aus dem physischen Backup. pgBackRest arbeitet auf Dateiebene. Für „nur diese eine Tabelle" braucht es eine Wegwerf-Instanz, aus der man dann exportiert.
  • Der Einzel-DB-Restore kennt nur Dump-Zeitpunkte. Er kommt aus pg_dump, nicht aus dem WAL. Ein Punkt zwischen zwei Dumps ist nur über den ganzen Cluster erreichbar. Und er geht nur für Datenbanken, die in WALROSS_DUMP_DATABASES stehen.
  • --db-include ist keine Alternative. Es stellt selektiv wieder her, macht die übrigen Datenbanken im Zielcluster aber unbrauchbar.
  • Bestehende Rollen werden nie geändert. Weicht eine im Ziel vom Backup ab, wird das gemeldet und nicht korrigiert. Owner-Reparaturen an bestehenden Objekten macht walross nicht.
  • Keine Logical-Replication-Slots im Restore. Die sind nach einem physischen Restore weg und müssen neu aufgesetzt werden.
  • Kein Schutz gegen ein zerstörtes Repo. walross prüft mit verify, ob das Repo unversehrt ist — für Geo-Redundanz sorgt der PBS, nicht dieses Werkzeug.
  • Keine Verschlüsselung des Repos. Bewusst: die vDisk liegt im eigenen Haus, und repo-cipher würde die Deduplizierung im PBS zerstören.
  • Kein Cloud-Storage, kein S3. Nicht eingebaut, weil die Hosts kein Internet haben.
  • Keine eigene Benutzerverwaltung. Ohne erreichbares authentik kommt niemand in die Oberfläche. Die Konsolenwege (pgbackrest, walross restore-database) funktionieren dann weiterhin — das ist der Notausgang.
  • Der Rollen-Extraktor ist kein SQL-Parser. Er versteht, was pg_dumpall und pg_dump schreiben. Handgeschriebene Exoten in einem Dump können ihm entgehen; nicht gelesene Statements werden aber gezählt und gemeldet.

Dokumentation

Datei Inhalt
docs/pitr.md Das PITR-Fenster, die vier Zielarten, die Fallstricke
docs/deploy.md Einrichtung auf einem PostgreSQL-Host, Schritt für Schritt
docs/authentik.md Provider anlegen, Gruppen, Client Credentials fürs Monitoring
docs/protocol.md Hub ↔ Agent: Absichten, mTLS, Vertrauensgrenzen
docs/restore-runbook.md Der Ernstfall, ohne Oberfläche
docs/pbs-fenster.md Überlappung mit dem Proxmox-Backup vermeiden

Lizenz

MIT.