- Python 65.3%
- HTML 23.2%
- Shell 7.8%
- JavaScript 1.8%
- CSS 1%
- Other 0.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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. |
||
| assets | ||
| config | ||
| deploy/docker | ||
| docs | ||
| scripts | ||
| src | ||
| systemd | ||
| tests | ||
| ui | ||
| .dockerignore | ||
| .gitignore | ||
| CHANGELOG.md | ||
| install.sh | ||
| Makefile | ||
| package-lock.json | ||
| package.json | ||
| pyproject.toml | ||
| README.md | ||
| tailwind.config.js | ||
| uninstall.sh | ||
| uv.lock | ||
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,
verifyundcheck. - 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:
- 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.
- Verglichen wird gegen den Ist-Zustand; angelegt wird nur, was fehlt.
- Erst dann
pg_restore -C --clean --if-exists. - 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
argverzeugt, 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 inWALROSS_DUMP_DATABASESstehen. --db-includeist 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-cipherwü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_dumpallundpg_dumpschreiben. 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.