Architektur
Zielgruppe: Technische Administration
Überblick
oPBWeb läuft als ein Docker-Compose-Projekt mit dem Namen opbweb. Die Datei docker-compose.yml beschreibt den Produktivbetrieb, docker-compose.dev.yml ergänzt sie für die Entwicklung. Dieses Kapitel zeigt, welche Dienste es gibt, wie sie miteinander verbunden sind und wo welche Daten liegen.
Dienste
| Dienst | Container | Image | Aufgabe |
|---|---|---|---|
| frontend | opbweb-frontend | opbweb-frontend:latest (nginx:stable-alpine) | liefert die Weboberfläche und das Handbuch aus, leitet /api an die API weiter; einziger Dienst mit veröffentlichten Ports |
| api | opbweb-api | opbweb-backend:latest (python:3.13-slim) | REST-API und WebSocket (FastAPI/uvicorn auf Port 8000), Anmeldung, Datenbankmigrationen beim Start, erstes Administrator-Konto |
| worker | opbweb-worker | opbweb-backend:latest | führt alle Jobs aus (arq auf Redis); einziger Dienst, der mit den opsi-Configservern spricht und deren Passwörter entschlüsselt |
| db | opbweb-db | mariadb:11.4 | alle Anwendungsdaten (Pakete, Versionen, Benutzer, Jobs, Protokoll, Einstellungen) |
| redis | opbweb-redis | redis:7-alpine | Job-Warteschlange, Heartbeat des Workers, Status der opsi-Verbindung, Sperrliste abgemeldeter Tokens, Depot-Sperren |
| garage | opbweb-garage | dxflrs/garage:v2.2.0 | S3-kompatibler Objektspeicher (ein Knoten, Replikationsfaktor 1) für Dateiinhalte und gebaute Pakete |
| garage-init | opbweb-garage-init | opbweb-garage-init:1.0.0 (alpine:3.21) | richtet bei jedem Start Layout, Zugangsschlüssel opbweb und Bucket packages ein und beendet sich dann |
API und Worker verwenden dasselbe Image; der Worker startet nur mit einem anderen Befehl (./start-worker.sh). Das Image von garage-init trägt auch im Produktivbetrieb das Tag :dev.
Alle Dienste außer garage-init laufen mit restart: unless-stopped und starten nach einem Neustart des Hosts automatisch.
Startreihenfolge
- db, redis und garage starten und melden sich über ihre Healthchecks als bereit.
- garage-init wartet auf die Admin-API von Garage, legt fehlende Ressourcen an und schreibt die S3-Zugangsdaten in das Volume
garage-credentials. - api startet, sobald db und redis gesund sind und garage-init erfolgreich beendet ist. Sie wartet bis zu 60 Sekunden auf die Datenbank, führt
alembic upgrade headaus und legt das erste Administrator-Konto an, solange die Benutzertabelle leer ist. - worker startet nach der API, schreibt einen ersten Heartbeat und prüft die Verbindung zu allen Configservern.
- frontend startet nach der API und wählt anhand der Zertifikate HTTPS oder HTTP.
Netze
| Netz | Eigenschaft | Teilnehmer |
|---|---|---|
frontend-net | normal | frontend, api |
backend-net | internal: true (kein Zugang nach außen) | api, worker, db, redis, garage |
opsi-net | normal, ausgehend | worker |
Von außen erreichbar sind nur die beiden Ports des Frontends (FRONTEND_HTTP_PORT, FRONTEND_HTTPS_PORT). Datenbank, Redis und Garage haben im Produktivbetrieb keine veröffentlichten Ports. Die API hat keinen Weg nach außen; nur der Worker erreicht über opsi-net die opsi-Configserver und ihre Depots (HTTPS, Port 4447). Die tatsächlichen Docker-Netze heißen mit Projektpräfix, zum Beispiel opbweb_opsi-net.
Datenfluss
Browser
│ HTTPS 443 (oder HTTP 80)
▼
┌──────────────────────── frontend (nginx) ────────────────────────┐
│ / Weboberfläche (Vue-SPA) │
│ /manual/ Handbuch Deutsch, /manual/en/ Englisch │
│ /health statische Antwort „healthy“ │
│ /api Proxy inkl. WebSocket ──────────────┐ │
└──────────────────────────────────────────────────┼───────────────┘
frontend-net ▼
┌──── api :8000 ────┐
│ Anmeldung, REST, │
│ Job-Events (WS) │
└───┬──────┬─────┬──┘
backend-net (intern) │ │ │ Job einreihen
┌─────────────────────────┘ │ ▼
▼ ▼ redis ◄──────┐
db (MariaDB) garage (S3, Bucket packages) │
▲ ▲ │
└──────────────┬─────────────────┘ │
│ │
┌── worker ──┐ ─────────────────────────┘
│ arq-Jobs, │ holt Jobs, Heartbeat
│ Cron │
└─────┬──────┘
│ opsi-net, HTTPS :4447
▼
opsi-Configserver und Depots (opsiconfd)
JSON-RPC sowie WebDAV /repository, /workbench, /depot
Typische Abläufe:
- Bearbeiten in der Oberfläche: Browser → nginx → API → Datenbank und Objektspeicher. Der Worker ist nicht beteiligt.
- Lange Vorgänge (Import, Bauen, Installieren, Depot-Abgleich, Workbench, Dateimanager-Kopien): Die API legt einen Job in der Datenbank an und reiht ihn in Redis ein. Der Worker führt ihn aus und schreibt Fortschritt und Protokoll. Der Browser verfolgt den Job live per WebSocket über
/api/jobs/{id}/events. - Dateimanager in den Depots: Einzelne Dateioperationen (Auflisten, Lesen, Speichern) reicht die API direkt an den Worker weiter und wartet bis zu 60 Sekunden auf die Antwort. Antwortet der Worker nicht, erscheint „Worker nicht erreichbar.“ bzw. „Der Worker antwortet nicht.“
- Uploads: nginx nimmt Uploads bis 20 GB an und reicht sie ungepuffert an die API weiter. Die API legt sie im Objektspeicher unter
uploads/ab, ein Job verarbeitet sie. - Downloads: Gebaute Pakete liefert die API aus dem Objektspeicher über kurzlebige Download-Links (2 Minuten gültig). Dateien von einem Depot kopiert zuerst ein Job nach
downloads/.
Datenhaltung
Datenbank
MariaDB (Datenbank opbweb, Zeichensatz utf8mb4) enthält alle Metadaten: Benutzer, Protokoll, Produkte und Versionen mit control-Daten, Properties, Abhängigkeiten, die Dateiliste je Version mit SHA-256-Prüfsumme, Build-Ergebnisse, Jobs mit Protokollen, Snippets, Anwendungseinstellungen, die Configserver mit ihren Verbindungsdaten und Stufen sowie die Depots, von denen jedes genau einem Configserver zugeordnet ist. Das Passwort des technischen opsi-Benutzers liegt je Configserver verschlüsselt dort (RSA-OAEP): Die API verschlüsselt es mit secrets/opsi_key_public.pem, entschlüsseln kann nur der Worker mit secrets/opsi_key_private.pem.
Objektspeicher
Garage speichert alle Dateiinhalte im Bucket packages. Die Objekte sind nach Präfix gegliedert:
| Präfix | Inhalt | Lebensdauer |
|---|---|---|
blobs/<2 Zeichen>/<sha256> | Inhalte der Paketdateien, nach SHA-256 adressiert | dauerhaft; identische Dateien mehrerer Versionen oder Pakete liegen nur einmal vor (Deduplizierung) |
artifacts/<productId>/<Dateiname> | gebaute Pakete: .opsi, .md5, .zsync | dauerhaft; ein neuer Build ersetzt das vorherige Ergebnis der Version |
uploads/<id>/<Dateiname> | hochgeladene Dateien bis zur Verarbeitung | temporär, nach 24 Stunden gelöscht |
downloads/<Job-ID>/<Dateiname> | vom Depot geholte Dateien zum Herunterladen | temporär, nach 24 Stunden gelöscht |
Wegen der Deduplizierung bleiben nach dem Löschen von Paketen oder Versionen Blobs zurück, die keine Paketdatei mehr verwendet. Sie entfernt ein Administrator gezielt über Bereinigen … im Systemstatus .
Physisch liegen die Daten in zwei Docker-Volumes: garage-meta (Metadaten in LMDB, Schlüssel, Bucket) und garage-data (Datenblöcke). Die Präfixe oben sind Objektschlüssel im Bucket, keine Verzeichnisse im Volume. Die Layout-Kapazität von 100 GB, die garage-init setzt, ist eine Gewichtung für Garage und keine Quota; maßgeblich ist der freie Platz auf dem Host.
Volumes
| Volume | Dienst | Inhalt |
|---|---|---|
mariadb-data | db | Datenbankdateien |
redis-data | redis | Warteschlange und Status (AOF-Persistenz) |
garage-meta | garage | Metadaten des Objektspeichers |
garage-data | garage | Datenblöcke des Objektspeichers |
garage-credentials | garage-init, api, worker | S3-Zugangsdaten, von garage-init geschrieben |
Docker legt die Volumes mit dem Präfix opbweb_ an, zum Beispiel opbweb_mariadb-data. Was davon zu sichern ist, steht unter Sicherung und Wiederherstellung
.
Worker und Jobs
Der Worker arbeitet die Jobs aus Redis ab:
- bis zu 10 Jobs gleichzeitig, damit kurze Dateimanager-Aufrufe nicht hinter langen Installationen warten,
- Zeitlimit für Jobs: 8 Stunden (
JOB_TIMEOUT_SECONDS), für die übrigen Worker-Funktionen 3600 Sekunden, - Jobs für dasselbe Depot laufen nacheinander. Eine Depot-Sperre in Redis sorgt dafür; ein Job wartet bis zu 6 Stunden darauf, sonst bricht er mit „Depot ist zu lange durch einen anderen Job belegt.“ ab.
Zusätzlich laufen im Worker zeitgesteuerte Aufgaben:
| Aufgabe | Zeitpunkt | Wirkung |
|---|---|---|
heartbeat | alle 30 Sekunden | schreibt ein Lebenszeichen nach Redis; älter als 90 Sekunden gilt der Worker im Systemstatus als gestört |
check_opsi | alle 5 Minuten (einstellbar) | prüft jeden eingerichteten Configserver (Anmeldung, Admin-Rechte, opsi-Version, Depots), gleicht die Depots jedes erreichbaren Servers ab, vergleicht das CA-Zertifikat und warnt 60 Tage vor Ablauf |
cleanup_storage | täglich 03:17 Uhr (einstellbar) | löscht Objekte unter uploads/ und downloads/, die älter als 24 Stunden sind |
Die Uhrzeit richtet sich nach TZ (Standard Europe/Berlin). Blobs und Build-Ergebnisse löscht die automatische Bereinigung nicht.
Handbuch
Dieses Handbuch ist Teil des Frontend-Images. Beim Bauen erzeugt eine eigene Hugo-Stufe die statische Website, nginx liefert sie aus:
- Deutsch unter
/manual/, - Englisch unter
/manual/en/.
Das Buch-Symbol in der Kopfleiste öffnet die Sprache der Oberfläche in einem neuen Tab. Das Handbuch ist statisch und ohne Anmeldung erreichbar. Änderungen am Handbuch erscheinen erst mit einem neu gebauten Frontend-Image, siehe Build und Auslieferung .