Technische Administration
Betrieb, Konfiguration, Sicherung, Aktualisierung und Fehlersuche
Zielgruppe: Technische Administration
Überblick
Dieser Teil richtet sich an alle, die den Docker-Stack von oPBWeb betreiben: Host, Container, Konfigurationsdateien, Secrets, Sicherung und Aktualisierung. Für die Arbeit in der Oberfläche brauchst du hier keine Rolle in oPBWeb, wohl aber Zugriff auf den Docker-Host und das Projektverzeichnis.
Alle Befehle führst du im Projektverzeichnis auf dem Docker-Host aus. Die Einstellungen, die ein Administrator in der Oberfläche vornimmt (Configserver, Depot-Stufen, Benutzer, Bereinigung des Objektspeichers), beschreibt die Fachliche Administration
. Die Erstinstallation steht unter Installation
.
Kapitel
- Architektur
– Dienste, Netze, Datenfluss, Aufbau des Objektspeichers, Worker und Jobs, Auslieferung des Handbuchs.
- Konfiguration
– Variablen in der
.env, Secrets, TLS und COOKIE_SECURE, Ports, Zeitlimits, Image-Overrides und Einstellungen ohne .env-Variable. - LDAP-Anmeldung
– Anmeldung über LDAP oder Active Directory, Zuordnung von Gruppen zu Rollen, CA-Zertifikat, Fehlersuche.
- Betrieb und Aktualisierung
–
start.sh und stop.sh, Container, Logs, Health-Endpunkte, Update, Versionierung, Entwicklungs-Stack, Cron-Jobs. - Sicherung und Wiederherstellung
– was du sicherst, Befehle, Reihenfolge und Konsistenz bei der Wiederherstellung.
- Fehlersuche
– typische Symptome mit Ursache und Abhilfe, Tabelle wichtiger Fehlermeldungen.
- Build und Auslieferung
– Images, GitLab-CI-Pipeline, Handbuch lokal bauen und als PDF erzeugen.
Architektur
Dienste, Netze, Datenfluss, Objektspeicher und Hintergrundjobs des oPBWeb-Stacks
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 head aus 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
.
Konfiguration
Variablen in der .env, Secrets, TLS, Ports, Zeitlimits und Image-Overrides
Zielgruppe: Technische Administration
Überblick
oPBWeb trennt die Konfiguration in drei Ebenen:
.env im Projektverzeichnis: nicht geheime Einstellungen wie Ports, LDAP-Server, Zeitlimits. Docker Compose liest die Datei und setzt die Werte in docker-compose.yml ein.secrets/: alle Passwörter und Schlüssel als einzelne Dateien. Sie erreichen die Container als Docker-Secrets und werden nie in der .env abgelegt.- Oberfläche: die Verbindungen zu den opsi-Configservern (URL, technischer Benutzer, Passwort, Stufe, CA-Zertifikat; in der Ansicht Depots) und die Vorgaben für neue Pakete. Diese Einstellungen liegen in der Datenbank, siehe Configserver
und Einstellungen für neue Pakete
.
Die .env und die fehlenden Secrets erzeugt ./setup-opbweb.sh, siehe Installation
. Du kannst die .env danach direkt bearbeiten oder das Skript erneut aufrufen; die vorhandenen Werte sind dann die Vorgaben. Änderungen an .env werden mit dem nächsten ./start.sh wirksam; Docker Compose erzeugt dabei die betroffenen Container neu.
Variablen in der .env
Die Vorlage .env.example enthält alle Variablen der folgenden Tabellen; auskommentierte Zeilen zeigen den Standardwert. Fehlt eine Variable in der .env, gilt der Standardwert. setup-opbweb.sh schreibt die Variablen für Datenbank, Redis, Objektspeicher und CORS_ORIGINS nur, wenn sie schon in der bisherigen .env standen; Variablen, die das Skript nicht kennt, behält es am Ende der Datei.
Allgemein und Anmeldung
| Variable | Standard | Dienst | Bedeutung |
|---|
TZ | Europe/Berlin | alle | Zeitzone der Container, auch für die Uhrzeit des täglichen Aufräumens (siehe Einstellungen → Worker
) |
LOG_LEVEL | INFO | api, worker | Detailgrad der Logs: DEBUG, INFO, WARNING oder ERROR. Ab INFO protokolliert die API den Start von uvicorn, jede HTTP-Anfrage und Probleme bei der LDAP-Anmeldung, siehe Logs |
DEBUG | false | api, worker | true startet uvicorn mit --reload (Hot Reload der API) und den Worker mit Dateiüberwachung und schaltet zusätzlich die Debug-Ausgaben von httpx und python-opsi ein; nur für die Entwicklung. setup-opbweb.sh warnt außerhalb von --dev, wenn der Wert true ist |
OPBWEB_LANG | Locale des Systems | Skripte | Sprache von setup-opbweb.sh, start.sh und scripts/init-secrets.sh: de oder en. Setzt setup-opbweb.sh (Option --lang); fehlt der Wert, entscheidet die Locale (de… → Deutsch, sonst Englisch). Die Oberfläche von oPBWeb betrifft das nicht |
ADMIN_USERNAME | admin | api | Name des ersten Administrator-Kontos; wirkt nur, solange die Benutzertabelle leer ist |
COOKIE_SECURE | false | api, worker | setup-opbweb.sh setzt true, weil oPBWeb immer per HTTPS läuft (siehe TLS und COOKIE_SECURE
); false nur im Entwicklungs-Stack |
CORS_ORIGINS | ["http://localhost:3100"] | api | erlaubte Ursprünge als JSON-Liste; nur für den Vite-Dev-Server nötig, im Produktivbetrieb kommt alles über denselben Ursprung |
ACCESS_TOKEN_MINUTES | 15 | api | Gültigkeit des Zugriffs-Tokens in Minuten (wird im Browser automatisch erneuert) |
REFRESH_TOKEN_DAYS | 7 | api | Gültigkeit des Refresh-Cookies in Tagen, also wie lange eine Anmeldung ohne erneute Passworteingabe hält |
ACCOUNT_MAX_FAILED_ATTEMPTS | 5 | api | Fehlversuche, nach denen ein lokales Konto für die Adresse gesperrt wird, von der sie kamen. Von einer Adresse sind insgesamt viermal so viele Fehlversuche erlaubt (alle Konten, auch LDAP), danach antwortet die API mit 429. Gesperrt für alle Adressen wird ein Konto erst nach zehnmal so vielen Fehlversuchen. |
ACCOUNT_LOCKOUT_MINUTES | 15 | api | Dauer der Sperren und Zeitraum, in dem Fehlversuche zählen |
LDAP_* | – | api | Anmeldung über LDAP/AD, siehe LDAP-Anmeldung |
opsi und Pakete
| Variable | Standard | Dienst | Bedeutung |
|---|
OPSI_TIMEOUT_SECONDS | 60 | worker | Zeitlimit in Sekunden für die übrigen opsi-Aufrufe (JSON-RPC, Auflisten per WebDAV) |
OPSI_INSTALL_TIMEOUT_SECONDS | 3600 | worker | Zeitlimit in Sekunden für den Upload ins Repository und für depot_installPackage bzw. depot_uninstallPackage |
JOB_TIMEOUT_SECONDS | 28800 | worker | Obergrenze in Sekunden für einen ganzen Job, z. B. eine Installation auf mehreren Depots einschließlich Wartezeit auf belegte Depots |
BUILD_COMPRESSION | zstd | worker | Kompression gebauter .opsi-Dateien: zstd (Standard von opsi 4.3) oder gzip |
BUILD_COMPRESSION wirkt nur auf neue Builds. Bereits gebaute Pakete behalten ihre Kompression bis zum nächsten Bauen.
Frontend und Images
| Variable | Standard | Bedeutung |
|---|
FRONTEND_HTTP_PORT | 80 | Host-Port für HTTP |
FRONTEND_HTTPS_PORT | 443 | Host-Port für HTTPS |
BACKEND_IMAGE | opbweb-backend:latest (Dev: :dev) | Image für api und worker |
FRONTEND_IMAGE | opbweb-frontend:latest | Image für frontend |
GARAGE_INIT_IMAGE | opbweb-garage-init:1.0.0 | Image für garage-init |
OPBWEB_REGISTRY | Registry der GitLab-CI (registry.gitlab.open-mind.space/pages/opsipackagebuilder-web) | nur für setup-opbweb.sh: Präfix, aus dem das Skript die Image-Namen bildet (<Präfix>/backend:<Version> usw.); auch als Umgebungsvariable beim Aufruf des Skripts möglich |
Bleiben die drei Image-Variablen leer, gelten die lokal gebauten Images, siehe Image-Overrides und Registry-Mirror
.
Datenbank, Redis, Objektspeicher
Diese Variablen musst du normalerweise nicht setzen. Sie sind für Sonderfälle da, etwa externe Dienste statt der mitgelieferten Container; in .env.example stehen sie auskommentiert.
| Variable | Standard | Bedeutung |
|---|
DB_NAME | opbweb | Name der Datenbank, die MariaDB beim ersten Start anlegt |
DB_USER | opbweb | Datenbankbenutzer |
DATABASE_URL | mysql+asyncmy://opbweb@db:3306/opbweb | Verbindung der Anwendung (ohne Passwort; das Passwort fügt die Anwendung aus dem Secret ein) |
DATABASE_URL_SYNC | mysql+pymysql://opbweb@db:3306/opbweb | Verbindung für die Migrationen |
REDIS_URL | redis://redis:6379/0 | Redis (ohne Passwort) |
S3_ENDPOINT | garage:3900 | S3-Endpunkt von Garage |
S3_BUCKET | packages | Bucket; garage-init legt ihn unter diesem Namen an |
S3_SECURE | false | TLS zum S3-Endpunkt |
Achtung
Ändere DB_NAME und DB_USER nicht nach dem ersten Start. MariaDB legt Datenbank und Benutzer nur bei der ersten Initialisierung des Volumes an. Außerdem enthalten die Standard-URLs den Datenbanknamen opbweb fest; wer DB_NAME ändert, muss DATABASE_URL und DATABASE_URL_SYNC passend setzen.
Secrets
scripts/init-secrets.sh legt fehlende Secrets im Verzeichnis secrets/ an. setup-opbweb.sh und start.sh rufen das Skript auf, start.sh bei jedem Start. Vorhandene Dateien überschreibt es nie. Gib Inhalte von Secrets nicht in Tickets oder Logs weiter.
Das Verzeichnis secrets/ hat die Rechte 750, die Dateien 444; das Skript setzt sie bei jedem Lauf neu. Docker reicht die Secret-Dateien mit den Rechten des Hosts in die Container durch, deren Prozesse unter anderen Benutzern laufen (MariaDB, Redis, die Anwendung als appuser mit UID 1000). Mit 600 könnten sie die Dateien auf einem Linux-Host nicht lesen. Andere Benutzer des Hosts hält das Verzeichnis mit 750 fern.
| Datei | Erzeugt | Verwendet von | Zweck |
|---|
db_password.txt | Zufallswert (hex) | db, api, worker | Passwort des MariaDB-Benutzers opbweb |
db_root_password.txt | Zufallswert (hex) | db | Root-Passwort von MariaDB, auch für Dumps |
redis_password.txt | Zufallswert (hex) | redis, api, worker | Passwort für Redis |
redis.acl | aus redis_password.txt | redis | ACL-Datei von Redis |
jwt_secret_key.txt | Zufallswert (hex, 64 Zeichen) | api | Signatur der Anmelde-Tokens |
opsi_key_public.pem | RSA-Schlüssel (3072 Bit), öffentlicher Teil | api, worker | verschlüsselt das gespeicherte opsi-Passwort |
opsi_key_private.pem | RSA-Schlüssel (3072 Bit), privater Teil | worker | entschlüsselt das opsi-Passwort; nur der Worker spricht mit opsi |
settings_encryption_key.txt | Zufallswert (hex, 64 Zeichen) | worker | AES-256-Schlüssel der Versionen vor 0.18.6; der Worker liest damit alte Einträge und verschlüsselt sie beim Start neu |
garage_rpc_secret.txt | Zufallswert (hex, 64 Zeichen) | garage | RPC-Secret von Garage |
garage_admin_token.txt | Zufallswert (hex, 64 Zeichen) | garage, garage-init | Token der Garage-Admin-API |
admin_password.txt | 20 Zeichen | api | Passwort des ersten Administrators; wird beim Anlegen einmal angezeigt |
ldap_bind_password.txt | leer; setup-opbweb.sh schreibt das eingegebene Passwort hinein | api | Passwort des LDAP-Dienstkontos |
Die S3-Zugangsdaten stehen nicht in secrets/. garage-init erzeugt den Schlüssel in Garage und schreibt ihn in das Volume garage-credentials.
Secrets nachträglich ändern
Nicht jedes Secret lässt sich nach dem ersten Start einfach austauschen:
| Datei | Folge einer Änderung |
|---|
db_password.txt, db_root_password.txt | MariaDB übernimmt die Passwörter nur bei der ersten Initialisierung. Eine geänderte Datei passt danach nicht mehr zur Datenbank; das Passwort müsstest du zusätzlich in MariaDB selbst ändern. |
redis_password.txt | start.sh erzeugt redis.acl beim nächsten Start aus dem neuen Passwort und startet Redis neu. |
jwt_secret_key.txt | Alle Sitzungen werden ungültig, alle müssen sich neu anmelden. |
opsi_key_private.pem, opsi_key_public.pem | Nur als Paar austauschen (beide löschen, init-secrets.sh erzeugt ein neues Paar). Die gespeicherten opsi-Passwörter lassen sich danach nicht mehr entschlüsseln; trage sie unter Depots → Configserver für jeden Configserver neu ein. |
settings_encryption_key.txt | Wird nur für Einträge aus Versionen vor 0.18.6 gebraucht; nicht ändern, solange solche Einträge umgestellt werden müssen. |
garage_rpc_secret.txt, garage_admin_token.txt | Nicht ändern; sie gehören zum vorhandenen Objektspeicher. |
admin_password.txt | Wirkt nur, solange kein Benutzer existiert. Später ändert der Administrator sein Passwort in der Oberfläche. |
ldap_bind_password.txt | Unkritisch; nach der Änderung den Stack neu starten. |
Die Dateien sind schreibgeschützt (444). Zum Ändern hebst du den Schutz kurz auf und setzt ihn danach wieder, zum Beispiel:
chmod u+w secrets/ldap_bind_password.txt
# Datei bearbeiten
chmod 444 secrets/ldap_bind_password.txt
Das LDAP-Passwort kannst du auch mit ./setup-opbweb.sh neu eingeben. Die Dienste lesen die Secrets beim Start. Starte nach einer Änderung den Stack neu (./stop.sh, dann ./start.sh).
TLS und COOKIE_SECURE
Der Frontend-Container entscheidet bei jedem Start anhand der Dateien im Verzeichnis certs/:
| Vorhanden | nginx liefert aus |
|---|
certs/fullchain.pem und certs/key.pem | HTTPS auf Container-Port 443 (Host: FRONTEND_HTTPS_PORT), HTTP/2, TLS 1.2 und 1.3; Container-Port 80 (Host: FRONTEND_HTTP_PORT) leitet mit 301 auf https://<Host>:<FRONTEND_HTTPS_PORT> weiter |
| eine oder beide Dateien fehlen | nur HTTP auf Container-Port 80 (nur im Entwicklungs-Stack oder bei einer von Hand angelegten .env) |
setup-opbweb.sh sorgt außerhalb des Entwicklungs-Stacks immer für ein Zertifikat: mitgebracht (fullchain.pem und key.pem neben dem Skript), vorhanden oder self-signed, siehe Installation
. Das Log des Containers zeigt die Wahl: „TLS-Zertifikat gefunden – HTTPS auf Port 443“ bzw. „Kein TLS-Zertifikat – HTTP auf Port 80“. fullchain.pem enthält das Serverzertifikat mit allen Zwischenzertifikaten. Den Zielport der Weiterleitung bekommt der Container als HTTPS_PORT (= FRONTEND_HTTPS_PORT).
Mit HTTPS sendet nginx den Header Strict-Transport-Security (ein Jahr). Browser, die oPBWeb einmal per HTTPS geöffnet haben, verweigern danach HTTP für diesen Hostnamen.
Nach dem Austausch eines Zertifikats (neue Dateien neben das Skript legen und ./setup-opbweb.sh --updateonly, oder direkt in certs/ ersetzen) genügt ein Neustart des Frontends:
docker restart opbweb-frontend
Zusätzlich lässt nginx pro Adresse höchstens 10 Anmeldeversuche pro Minute durch (kurzzeitig 10 mehr). Beide Grenzen setzen voraus, dass oPBWeb die echte Adresse der Clients sieht; steht ein weiterer Proxy davor oder verdeckt Docker die Adressen, teilen sich alle Benutzer eine Adresse.
COOKIE_SECURE steuert das Secure-Flag des Refresh-Cookies opbweb_refresh (7 Tage gültig, httpOnly, SameSite=Strict). Kommt die Anmeldung über HTTPS, setzt die API das Flag immer, auch bei false. Für die Einstellung gilt:
| Betrieb | COOKIE_SECURE |
|---|
| HTTPS | true |
| nur HTTP | false; bei true speichert der Browser das Cookie nicht, und die Sitzung lässt sich nicht erneuern |
Ports
| Port im Container | Host-Port | Dienst |
|---|
| 80 | FRONTEND_HTTP_PORT (80) | nginx, HTTP |
| 443 | FRONTEND_HTTPS_PORT (443) | nginx, HTTPS |
Beide Ports werden immer veröffentlicht, aber nur einer antwortet. Alle anderen Dienste sind im Produktivbetrieb nicht von außen erreichbar. Die zusätzlichen Ports des Entwicklungs-Stacks stehen unter Betrieb und Aktualisierung
.
Ausgehend braucht der Worker HTTPS auf Port 4447 zum opsi-Configserver und zu den Depots.
Zeitlimits und Größen
| Grenze | Wert | Einstellbar |
|---|
| Upload und Installation/Deinstallation auf dem Depot | 3600 s | OPSI_INSTALL_TIMEOUT_SECONDS |
| Übrige opsi-Aufrufe | 60 s | OPSI_TIMEOUT_SECONDS |
| Laufzeit eines Jobs | 8 h (28800 s) | JOB_TIMEOUT_SECONDS |
| Parallele Jobs im Worker | 10 | nein |
| Dateimanager-Aufrufe API → Worker | 60 s | nein |
| Warten auf ein belegtes Depot | bis 6 h | nein |
| Uploadgröße über nginx | 20 GB | nein (client_max_body_size in frontend/nginx/common.inc) |
| nginx-Timeout zur API | 660 s ohne Datenfluss | nein |
| Download-Links | 2 min | nein |
| Textdateien im Editor | 5 MB (Paketdateien), 2 MB (Dateimanager) | nein |
Hinweis
OPSI_INSTALL_TIMEOUT_SECONDS begrenzt einen einzelnen opsi-Aufruf, JOB_TIMEOUT_SECONDS den ganzen Job – bei einer Installation also alle Depots nacheinander einschließlich der Wartezeit auf belegte Depots (bis 6 h). Setze JOB_TIMEOUT_SECONDS daher deutlich höher als die Wartezeit. Überschreitet ein Job das Limit oder wird der Worker beendet, bricht der Job mit „Abgebrochen: Zeitlimit des Jobs überschritten oder Worker beendet.“ ab und gilt als fehlgeschlagen.
Image-Overrides und Registry-Mirror
Ohne Overrides baut ./start.sh --build die eigenen Images lokal und nennt sie opbweb-backend:latest, opbweb-frontend:latest und opbweb-garage-init:1.0.0. Mit BACKEND_IMAGE, FRONTEND_IMAGE und GARAGE_INIT_IMAGE verwendest du stattdessen fertige Images, zum Beispiel aus der GitLab-Registry (siehe Build und Auslieferung
). setup-opbweb.sh setzt die drei Variablen, wenn du bei der Frage nach den Images r wählst; das Präfix kommt aus OPBWEB_REGISTRY. Von Hand sieht das so aus (Beispiel, an deine Registry anzupassen):
BACKEND_IMAGE=registry.example.com/gruppe/opsipackagebuilder-web/backend:<version>
FRONTEND_IMAGE=registry.example.com/gruppe/opsipackagebuilder-web/frontend:<version>
GARAGE_INIT_IMAGE=registry.example.com/gruppe/opsipackagebuilder-web/garage-init:latest
Danach holst du die Images und startest ohne --build:
docker login registry.example.com
docker compose pull
./start.sh
LDAP-Anmeldung
Anmeldung über LDAP oder Active Directory einrichten und Gruppen auf Rollen abbilden
Zielgruppe: Technische Administration
Überblick
Neben lokalen Konten kann oPBWeb Benutzer gegen ein LDAP-Verzeichnis oder Active Directory anmelden. Die Rolle in oPBWeb ergibt sich dann aus den Gruppenmitgliedschaften im Verzeichnis. Du konfigurierst die Anbindung über LDAP_*-Variablen in der .env und das Passwort des Dienstkontos in secrets/ldap_bind_password.txt. In der Oberfläche gibt es dafür keine Einstellungen.
So läuft die Anmeldung ab
- Gibt es in oPBWeb ein lokales Konto mit dem eingegebenen Benutzernamen, meldet oPBWeb ausschließlich lokal an. Das Verzeichnis wird nicht gefragt, auch wenn das Passwort falsch ist.
- Sonst, bei
LDAP_ENABLED=true, verbindet sich oPBWeb mit dem Dienstkonto (LDAP_BIND_DN) oder anonym, wenn kein Bind-DN gesetzt ist. - oPBWeb sucht mit
LDAP_USER_FILTER unterhalb von LDAP_USER_SEARCH_BASE (leer: LDAP_BASE_DN). Die Suche muss genau einen Eintrag liefern. - Mit dem gefundenen DN und dem eingegebenen Passwort meldet sich oPBWeb als Benutzer an und prüft so das Passwort.
- Aus dem Attribut
memberOf bestimmt oPBWeb die Rolle (siehe unten). Ohne passende Gruppe gibt es keinen Zugang. - Beim ersten erfolgreichen Login legt oPBWeb das Konto mit der Herkunft „Verzeichnis (LDAP)“ an. Anzeigename (
displayName), E-Mail (mail) und Rolle werden bei jeder Anmeldung neu aus dem Verzeichnis übernommen.
Lokale Konten bleiben also immer lokal, auch das erste Administrator-Konto. So kommst du auch bei einem Ausfall des Verzeichnisses noch an oPBWeb heran.
Variablen
| Variable | Standard | Bedeutung |
|---|
LDAP_ENABLED | false | LDAP-Anmeldung einschalten |
LDAP_HOST | – | Hostname des Domänencontrollers bzw. LDAP-Servers |
LDAP_PORT | 636 | Port |
LDAP_USE_SSL | true | LDAPS (TLS ab Verbindungsaufbau); StartTLS wird nicht unterstützt |
LDAP_TLS_VERIFY | true | Serverzertifikat prüfen |
LDAP_CA_CERTS_FILE | – | CA-Datei im Container, z. B. /run/certs/ldap/ca.pem |
LDAP_BASE_DN | – | Basis-DN des Verzeichnisses |
LDAP_USER_SEARCH_BASE | – | Suchbasis für Benutzer; leer = LDAP_BASE_DN |
LDAP_USER_FILTER | (&(objectClass=user)(sAMAccountName={username})) | Suchfilter; {username} wird durch den eingegebenen Namen ersetzt (maskiert) |
LDAP_BIND_DN | – | DN des Dienstkontos für die Suche; leer = anonyme Suche |
LDAP_ADMIN_GROUPS | – | Gruppen-DNs für die Rolle Administrator |
LDAP_RELEASER_GROUPS | – | Gruppen-DNs für die Rolle Releaser |
LDAP_PACKAGER_GROUPS | – | Gruppen-DNs für die Rolle Packager |
LDAP_VIEWER_GROUPS | – | Gruppen-DNs für die Rolle Leser |
Das Passwort des Dienstkontos steht nicht in der .env, sondern in secrets/ldap_bind_password.txt (von init-secrets.sh leer angelegt). Die Verbindung hat ein Zeitlimit von 10 Sekunden für den Aufbau und 15 Sekunden für Antworten.
Achtung
Mit LDAP_USE_SSL=false gehen Benutzerpasswörter unverschlüsselt über das Netz. Verwende im Betrieb LDAPS. LDAP_TLS_VERIFY=false schaltet die Zertifikatsprüfung ab und ist nur für einen kurzen Test gedacht.
Beispiel Active Directory
Ein Beispiel für eine Domäne example.local. Ersetze alle Namen durch deine Werte:
LDAP_ENABLED=true
LDAP_HOST=dc01.example.local
LDAP_PORT=636
LDAP_USE_SSL=true
LDAP_TLS_VERIFY=true
LDAP_CA_CERTS_FILE=/run/certs/ldap/ca.pem
LDAP_BASE_DN=dc=example,dc=local
LDAP_USER_SEARCH_BASE=ou=Benutzer,dc=example,dc=local
LDAP_USER_FILTER=(&(objectClass=user)(sAMAccountName={username}))
LDAP_BIND_DN=cn=svc-opbweb,ou=Dienstkonten,dc=example,dc=local
LDAP_ADMIN_GROUPS=cn=opbweb-admins,ou=Gruppen,dc=example,dc=local
LDAP_RELEASER_GROUPS=cn=opbweb-releaser,ou=Gruppen,dc=example,dc=local
LDAP_PACKAGER_GROUPS=cn=opbweb-packager,ou=Gruppen,dc=example,dc=local;cn=client-team,ou=Gruppen,dc=example,dc=local
LDAP_VIEWER_GROUPS=cn=opbweb-leser,ou=Gruppen,dc=example,dc=local
Weitere Filter, an dein Verzeichnis anzupassen:
| Zweck | Filter |
|---|
| Anmeldung mit UPN statt Kontoname | (&(objectClass=user)(userPrincipalName={username})) |
| deaktivierte AD-Konten ausschließen | (&(objectClass=user)(sAMAccountName={username})(!(userAccountControl:1.2.840.113556.1.4.803:=2))) |
OpenLDAP mit uid | (&(objectClass=inetOrgPerson)(uid={username})) – setzt das memberOf-Overlay voraus |
Einrichten
Trage die Variablen wie oben in die .env ein.
Schreibe das Passwort des Dienstkontos in secrets/ldap_bind_password.txt (ohne Zeilenumbruch am Ende ist am sichersten; führende und abschließende Leerzeichen entfernt oPBWeb).
Lege bei einer eigenen Unternehmens-CA das CA-Zertifikat im PEM-Format vor dem Setup als ldap-ca.pem neben setup-opbweb.sh (das Setup verschiebt es nach certs/ldap/ldap-ca.pem und schlägt es als CA-Datei vor) oder direkt unter certs/ldap/ ab. Das Verzeichnis certs/ldap/ ist im Container als /run/certs/ldap/ schreibgeschützt eingebunden; LDAP_CA_CERTS_FILE gibt den Pfad im Container an. Ohne CA-Datei prüft oPBWeb gegen die Standard-CAs des Containers.
Starte den Stack neu, damit die API die neuen Werte und das Secret liest:
Melde dich mit einem Verzeichniskonto an. Unter Benutzer und Rollen erscheint das Konto mit der Herkunft „Verzeichnis (LDAP)“ und der zugeordneten Rolle.
Gruppen und Rollen
- In jeder
LDAP_*_GROUPS-Variablen stehen ein oder mehrere vollständige Gruppen-DNs, getrennt durch ;. - oPBWeb vergleicht die DNs ohne Rücksicht auf Groß- und Kleinschreibung, aber sonst exakt mit den Werten aus
memberOf. - Die höchste Rolle gewinnt: Administrator vor Releaser vor Packager vor Leser.
- Keine passende Gruppe, kein Zugang: Die Anmeldung scheitert mit „Keine Berechtigung für oPBWeb (keine passende Gruppe).“ Ist keine der vier Variablen gesetzt, kann sich niemand über LDAP anmelden.
- Verschachtelte Gruppen löst oPBWeb nicht auf; maßgeblich ist nur, was
memberOf direkt liefert. Die primäre Gruppe eines AD-Kontos (meist „Domänen-Benutzer“) steht nicht in memberOf.
Was die Rollen im Einzelnen dürfen, beschreibt Benutzer und Rollen
.
Verhalten von Verzeichniskonten
| Thema | Verhalten |
|---|
| Rolle | kommt aus den Gruppen und wird bei jeder Anmeldung neu gesetzt; in der Oberfläche nicht änderbar |
| Rollenänderung im Verzeichnis | wirkt erst bei der nächsten Anmeldung mit Passwort; eine laufende Sitzung erneuert sich bis zu 7 Tage lang ohne Rückfrage beim Verzeichnis |
| Passwort | wird im Verzeichnis geändert; Passwort ändern und Passwort setzen gibt es für diese Konten nicht |
| Sperre nach Fehlversuchen | die Kontosperre gilt nur für lokale Konten; für Verzeichniskonten greift die Sperrrichtlinie des Verzeichnisses. Die Grenze für Fehlversuche pro Adresse gilt auch für LDAP-Anmeldungen. |
| Deaktivieren in oPBWeb | möglich; das Konto kann sich dann trotz gültigem Verzeichnis-Passwort nicht mehr anmelden |
| Zugang sofort entziehen | Konto unter Benutzer und Rollen deaktivieren; das beendet auch laufende Sitzungen |
Gleichnamige Konten: Existiert ein lokales Konto mit demselben Namen wie ein Verzeichniskonto, gilt immer das lokale Konto.
Fehlersuche
| Meldung beim Anmelden | Ursache | Abhilfe |
|---|
| „Verzeichnisdienst nicht erreichbar.“ | Verbindung, TLS oder Suche mit dem Dienstkonto fehlgeschlagen | Log der API prüfen (siehe unten): Host, Port, CA-Datei, Bind-DN und Passwort |
| „Benutzername oder Passwort falsch.“ | falsches Passwort, Benutzer nicht gefunden oder Filter liefert mehr als einen Eintrag | Filter und Suchbasis prüfen; Anmeldung mit demselben Konto direkt am Verzeichnis testen |
| „Keine Berechtigung für oPBWeb (keine passende Gruppe).“ | Anmeldung gelungen, aber keine Gruppe passt | Gruppen-DNs exakt mit memberOf des Benutzers vergleichen; direkte Mitgliedschaft prüfen |
| „Konto ist deaktiviert.“ | Konto in oPBWeb deaktiviert | unter Benutzer und Rollen wieder aktivieren |
Fehler bei Verbindung und Suche schreibt die API ins Log:
docker logs opbweb-api 2>&1 | grep -i "LDAP search failed"
Typische Ursachen sind ein Hostname, der nicht zum Zertifikat passt, eine fehlende oder falsche CA-Datei (Pfad im Container, nicht auf dem Host), ein leeres secrets/ldap_bind_password.txt oder ein Domänencontroller, der vom Docker-Host aus nicht erreichbar ist.
Zum Gegenprüfen der Suche eignet sich ldapsearch auf einem Rechner mit den OpenLDAP-Werkzeugen. Beispiel, an deine Werte anzupassen:
ldapsearch -H ldaps://dc01.example.local:636 \
-D "cn=svc-opbweb,ou=Dienstkonten,dc=example,dc=local" -W \
-b "ou=Benutzer,dc=example,dc=local" \
"(&(objectClass=user)(sAMAccountName=mmuster))" memberOf displayName mail
Die ausgegebenen memberOf-Werte kannst du direkt in die LDAP_*_GROUPS-Variablen übernehmen.
Betrieb und Aktualisierung
Stack starten und stoppen, Logs, Health-Endpunkte, Updates, Versionierung und Entwicklungs-Stack
Zielgruppe: Technische Administration
Überblick
Für den täglichen Betrieb brauchst du zwei Skripte im Projektverzeichnis: start.sh und stop.sh. Dieses Kapitel beschreibt außerdem, wo du Logs und Zustand der Dienste findest, wie du oPBWeb aktualisierst und was du auf der opsi-Seite beachten musst. Alle Befehle führst du im Projektverzeichnis aus.
Stack starten und stoppen
start.sh
./start.sh [--build] [--logs] [--dev | --test] [--lang de|en]
| Option | Wirkung |
|---|
| (keine) | startet den Produktiv-Stack (docker-compose.yml) mit den vorhandenen Images |
--build | baut die eigenen Images vor dem Start neu |
--logs | folgt nach dem Start den Logs aller Container (logs -f --tail=50); Strg+C beendet nur die Anzeige |
--dev | nimmt docker-compose.dev.yml hinzu (Entwicklungs-Stack, siehe unten); nur im Klon mit Quellen |
--test | nimmt docker-compose.test.yml hinzu: zwei opsi-Mocks als Configserver Test und Produktion, siehe Testumgebung |
--lang de bzw. --lang en | Sprache der Ausgaben für diesen Aufruf (sonst OPBWEB_LANG aus der .env) |
-h, --help | zeigt die Hilfe |
Ablauf: Das Skript bricht ab, wenn .env fehlt („.env fehlt – bitte ./setup-opbweb.sh ausführen (im Entwickler-Checkout: ./setup-opbweb.sh –dev).“), ruft scripts/init-secrets.sh auf, führt docker compose up -d aus und zeigt mit docker compose ps den Status. Laufende Container, deren Konfiguration sich nicht geändert hat, bleiben dabei unberührt. start.sh holt keine neuen Fremd-Images; dafür siehe Aktualisieren
.
stop.sh
Das Skript hat keine Optionen. Es führt docker compose down mit docker-compose.yml und den vorhandenen Overlays (docker-compose.dev.yml, docker-compose.test.yml) aus und stoppt damit Produktiv-, Dev- und Testdienste gleichermaßen. Container und Netze werden entfernt, die Volumes mit Datenbank und Objektspeicher bleiben erhalten.
Einzelne Dienste neu starten
docker restart opbweb-worker
docker restart opbweb-frontend # z. B. nach einem Zertifikatswechsel
Ein Neustart des Workers unterbricht laufende Jobs. Prüfe vorher unter Jobs, ob gerade Installationen laufen.
Container
| Container | Dienst | Hinweis |
|---|
opbweb-frontend | nginx | Weboberfläche, Handbuch, Proxy |
opbweb-api | API | führt beim Start die Migrationen aus |
opbweb-worker | Worker | Jobs, Cron, opsi-Verbindung |
opbweb-db | MariaDB | |
opbweb-redis | Redis | |
opbweb-garage | Garage | Objektspeicher |
opbweb-garage-init | Einrichtung Garage | läuft bei jedem Start kurz und endet mit Exit-Code 0 |
opbweb-opsi-mock | nur Dev | nachgebauter opsiconfd (erstes opsi-System) |
opbweb-opsi-mock2 | nur Dev | nachgebauter opsiconfd (zweites opsi-System) |
opbweb-frontend-dev | nur Dev | Vite-Dev-Server |
opbweb-manual-dev | nur Dev | Hugo-Vorschau des Handbuchs |
opbweb-launcher-build | nur Dev | baut die Launcher-ZIPs beim Start und endet mit Exit-Code 0 |
opbweb-launcher-dev | nur Dev | liefert die Launcher-ZIPs aus |
Den Zustand zeigt docker compose ps (Produktiv-Stack) bzw. docker compose ps -a einschließlich beendeter Container wie garage-init.
Logs
docker logs -f --tail=100 opbweb-api
docker logs -f --tail=100 opbweb-worker
docker compose logs -f --tail=50 # alle Dienste
| Container | Was du dort findest |
|---|
opbweb-api | Migrationen und Start von uvicorn, Anlegen des ersten Admins, HTTP-Zugriffe (eine Zeile je Anfrage), Probleme bei der LDAP-Anmeldung |
opbweb-worker | Job-Ausführung, opsi-Prüfung (Standard alle 5 Minuten), Fehler bei Verbindungen zu opsiconfd |
opbweb-frontend | Wahl von HTTP oder HTTPS, Zugriffe und nginx-Fehler |
opbweb-garage-init | Einrichtung von Layout, Schlüssel und Bucket |
Den Detailgrad von api und worker steuert LOG_LEVEL in der .env (DEBUG, INFO, WARNING, ERROR; Standard INFO). Eine Änderung wird mit dem nächsten ./start.sh wirksam.
Bei der LDAP-Anmeldung protokolliert die API, woran eine Anmeldung scheitert: Benutzer nicht gefunden (null Treffer für den Suchfilter) und falsches Passwort auf Stufe INFO, keine Gruppe mit einer Rolle auf Stufe WARNING. Mit LOG_LEVEL=WARNING oder ERROR siehst du die ersten beiden Fälle also nicht. Zum Beispiel:
docker logs opbweb-api 2>&1 | grep "LDAP login"
python-opsi und der HTTP-Client httpx geben nur Warnungen und Fehler aus. Ihre Debug-Ausgaben schaltet erst DEBUG=true ein; das startet die API aber auch mit Hot Reload und ist nur für die Entwicklung gedacht, siehe Konfiguration
.
Das vollständige Protokoll jedes Jobs findest du zusätzlich in der Oberfläche unter Jobs, siehe Jobs
.
Zustand prüfen
| Endpunkt | Anmeldung | Antwort |
|---|
/health | nein | statisch healthy von nginx; prüft nur, ob nginx antwortet |
/api/health | nein | {"status":"ok","version":"…"}; die API ist über nginx erreichbar |
/api/health/details | ja (Bearer-Token) | Prüfungen database, redis, storage, worker, opsi mit ok, Details und Dauer |
/api/system/database | ja, Admin | Zeilen und Größe je Datenbanktabelle |
/api/system/storage | ja, Admin | Belegung des Objektspeichers je Bereich, Deduplizierung, nicht verwendete und fehlende Blobs |
/api/system/storage/objects | ja, Admin | Objekte eines Bereichs mit den Paketversionen, die sie verwenden (Grundlage des Bucket-Browsers) |
| Seite Systemstatus | ja, alle Rollen | dieselben Prüfungen als Tabelle, alle 10 Sekunden aktualisiert |
Für ein einfaches Monitoring eignet sich /api/health:
curl -fsS https://opbweb.example.local/api/health
Die Detailprüfung bewertet so:
| Prüfung | Fehler, wenn … |
|---|
| Datenbank | keine Verbindung; die Details nennen sonst MariaDB-Version und Schema-Revision |
| Redis | keine Verbindung |
| Objektspeicher | keine Verbindung oder Bucket fehlt („Bucket ‘…’ fehlt“) |
| Worker | kein Heartbeat („kein Lebenszeichen“) oder letzter Heartbeat älter als 90 Sekunden |
| opsi-Server | noch nicht geprüft, kein Configserver eingerichtet oder bei mindestens einem Configserver: Verbindung fehlgeschlagen, keine Admin-Rechte, neues CA-Zertifikat noch nicht übernommen oder Warnung vor dem Ablauf |
Administratoren sehen im Systemstatus zusätzlich Tabellengrößen der Datenbank, die Belegung des Objektspeichers und den Bucket-Browser, siehe Systemstatus
.
Zeitgesteuerte Aufgaben
Der Worker führt diese Aufgaben selbst aus; einen Cron-Eintrag auf dem Host brauchst du nicht.
| Aufgabe | Zeitpunkt | Wirkung |
|---|
| Heartbeat | alle 30 Sekunden | Lebenszeichen für den Systemstatus |
| opsi-Prüfung | alle 5 Minuten (Standard) | Verbindung, Depot-Abgleich, CA-Vergleich, Ablaufwarnung 60 Tage vorher |
| Aufräumen temporärer Dateien | täglich 03:17 Uhr (Standard) | löscht uploads/ und downloads/ älter als 24 Stunden |
Die Zeiten der opsi-Prüfung und des Aufräumens stellst du unter Einstellungen → Worker
ein; sie gelten ohne Neustart.
Nicht mehr verwendete Paketdateien (Blobs) und Build-Ergebnisse entfernt die automatische Aufgabe nicht. Das macht ein Administrator bei Bedarf mit Bereinigen … im Systemstatus
: erst ein Probelauf, dann das bestätigte Löschen.
Aktualisieren
Sichere Datenbank und Objektspeicher, siehe Sicherung und Wiederherstellung
. Datenbankmigrationen lassen sich nicht ohne Weiteres rückgängig machen.
Prüfe unter Jobs, dass keine Installationen laufen.
Hole den neuen Stand, zum Beispiel mit git pull oder mit einem neuen Stack-ZIP
, das du über die Installation entpackst. .env, secrets/ und certs/ enthält das ZIP nicht, sie bleiben erhalten.
Bringe die .env auf den neuen Stand:
./setup-opbweb.sh --updateonly
Das Skript übernimmt alle vorhandenen Werte, ergänzt neue Variablen mit ihrem Standardwert und legt neue Secrets an; vorhandene Secrets bleiben unverändert. Willst du dabei Werte ändern, rufe ./setup-opbweb.sh ohne Option auf.
Aktualisiere die Images und starte den Stack:
Lokal gebaute Images:
--build nutzt den Build-Cache von Docker. Sicherheitsupdates der Basis-Images und der Systempakete kommen so nicht unbedingt an. Bau deshalb bei einem Sicherheitsupdate ohne Cache und mit frischen Basis-Images, danach ./start.sh:
docker compose build --pull --no-cache
Images aus einer Registry: Trage die neuen Image-Versionen ein, entweder interaktiv mit ./setup-opbweb.sh (Fragen nach den Versionen der Images; die Vorgabe ist die bisherige Version) oder direkt in BACKEND_IMAGE, FRONTEND_IMAGE und GARAGE_INIT_IMAGE in der .env. --updateonly behält die bisherigen Versionen. Liegt eine versions.env daneben (Stack-ZIP), sind ihre Versionen die Vorgaben, auch für --updateonly; das Entpacken eines neuen ZIPs und --updateonly stellen die Images also auf dessen Versionen um. Danach:
docker compose pull
./start.sh
Verwendest du die Tags latest, genügen docker compose pull und ./start.sh.
Die API führt beim Start automatisch alembic upgrade head aus. Prüfe danach das Log:
docker logs opbweb-api 2>&1 | head -50
Kontrolliere in der Oberfläche die Versionen in der Statusleiste („Frontend …“, „Backend …“) und den Systemstatus.
Neue Versionen der Fremd-Images (MariaDB 11.4, Redis 7, Garage v2.2.0) innerhalb ihrer Tags holt start.sh nicht automatisch. Beispiel, bei Bedarf auszuführen:
docker compose pull db redis garage
./start.sh
Um auch die Basis-Images der eigenen Images (node, nginx, python) zu aktualisieren, baue mit docker compose build --pull und starte danach mit ./start.sh. Das betrifft nur lokal gebaute Images.
Versionierung
Backend und Frontend haben getrennte Versionsnummern nach dem Schema MAJOR.MINOR.PATCH (Meilenstein, Phase, Fehlerbehebung). Die Oberfläche zeigt beide in der Statusleiste, /api/health liefert die Backend-Version.
Gesetzt werden die Versionen mit set-version.sh; welche Dateien betroffen sind, steht in version.yaml (Backend: backend/src/core/version.py und backend/pyproject.toml, Frontend: frontend/package.json über npm version).
| Aufruf | Wirkung |
|---|
./set-version.sh | zeigt die aktuellen Versionen |
./set-version.sh 0.14.0 | setzt alle Komponenten (mit Rückfrage; --force ohne) |
./set-version.sh --backend 0.13.1 | setzt nur das Backend |
./set-version.sh --frontend 0.13.1 | setzt nur das Frontend |
./set-version.sh --component <name> <version> | setzt eine Komponente aus version.yaml |
./set-version.sh --config <datei> | verwendet eine andere Konfigurationsdatei |
Eine geänderte Versionsdatei auf dem Standard-Branch löst in der GitLab-CI den Build des passenden Images aus, siehe Build und Auslieferung
.
opsiconfd: admin-networks
Der Worker ruft opsiconfd mit Admin-Rechten auf. Ist in der Konfiguration von opsiconfd die Option admin-networks gesetzt, muss die Absenderadresse des Workers darin liegen, sonst antwortet opsiconfd mit HTTP 403.
Welche Adresse opsiconfd sieht, hängt vom Standort ab (abgeleitet aus dem Docker-Netzverhalten, bitte im opsiconfd-Log gegenprüfen):
oPBWeb auf dem Configserver selbst: die Adresse des Workers im Docker-Netz opbweb_opsi-net. Das Subnetz zeigt:
docker network inspect opbweb_opsi-net --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
oPBWeb auf einem anderen Host: in der Regel die Adresse des Docker-Hosts, weil Docker ausgehenden Verkehr maskiert.
./stop.sh entfernt die Docker-Netze; beim nächsten Start kann opbweb_opsi-net ein anderes Subnetz bekommen. Trage daher entweder den gesamten Adressbereich ein, aus dem Docker seine Netze vergibt, oder prüfe das Subnetz nach jedem Neustart. Die Syntax der Option beschreibt das opsi-Handbuch; nach der Änderung muss opsiconfd neu starten.
Entwicklungs-Stack
Der Entwicklungs-Stack ist für die Weiterentwicklung gedacht, nicht für den Betrieb. In einem frischen Entwickler-Checkout legst du zuerst die .env an (aus .env.example, ohne Rückfragen) und startest dann mit lokal gebauten Images:
./setup-opbweb.sh --dev
./start.sh --dev --build
Er ergänzt den Produktiv-Stack um Hot Reload (Quellen als Bind-Mounts, DEBUG=true), zwei nachgebaute opsiconfd, eine Vorschau des Handbuchs und die Launcher-ZIPs. API und Worker laufen mit dem Image opbweb-backend:dev.
| Was | Adresse |
|---|
| Vite-Dev-Server (Oberfläche mit Hot Reload) | http://localhost:3100 |
Handbuch-Vorschau (manual-dev, über den Vite-Proxy) | http://localhost:3100/manual/ |
Launcher-ZIPs (launcher-dev, über den Vite-Proxy) | http://localhost:3100/downloads/ |
| API mit OpenAPI-Oberfläche | http://localhost:8100/api/docs |
| nginx-Frontend | Port aus FRONTEND_HTTP_PORT bzw. FRONTEND_HTTPS_PORT |
| MariaDB | Port 3307 |
| Redis | Port 6380 |
| Garage S3 | Port 3910 |
opsiconfd-Mock (opsi-mock) | opsi-config.mock und depot1.mock, nur im Docker-Netz |
zweiter opsiconfd-Mock (opsi-mock2) | opsi-config2.mock und depot2.mock, nur im Docker-Netz |
Jeder Mock bildet einen Configserver und ein Depot nach, einschließlich Installieren, Deinstallieren und WebDAV. opbweb-opsi-mock ist das erste opsi-System: Solange kein Configserver eingerichtet ist, trägt die API ihn beim Start als „opsiconfd-Mock“ mit der Stufe Test ein (URL http://opsi-config.mock:4447, Benutzer opsiadm, Passwort mock, ohne TLS-Prüfung). opbweb-opsi-mock2 ist ein zweites, unabhängiges opsi-System für Tests mit mehreren Configservern; er wird nicht automatisch eingetragen. Wer ihn in der Oberfläche nutzen will, legt ihn unter Depots mit + an (URL http://opsi-config2.mock:4447, Benutzer opsiadm, Passwort mock, TLS-Prüfung aus).
Die End-to-End-Tests tragen beide Mocks selbst als Configserver ein (als „t-Mock 1“ und „t-Mock 2“, falls noch nicht vorhanden), entfernen sie danach wieder und arbeiten nur auf deren Depots. Sie setzen nicht voraus, dass der Mock der einzige oder voreingestellte Configserver ist, und laufen auch dann, wenn zusätzlich ein echter opsi-Server eingerichtet ist. Die Handbuch-Vorschau aktualisiert sich nicht von selbst; lade die Seite nach einer Änderung neu.
Achtung
Dev- und Produktiv-Stack verwenden dieselben Containernamen und dieselben Volumes. Sie können nicht gleichzeitig laufen und teilen sich Datenbank und Objektspeicher. Starte den Entwicklungs-Stack daher nie auf einem produktiven Host. Zum Wechseln stoppst du mit ./stop.sh und startest mit bzw. ohne --dev neu.
Sicherung und Wiederherstellung
Was du sichern musst, wie du sicherst und in welcher Reihenfolge du wiederherstellst
Zielgruppe: Technische Administration
Überblick
Die Daten von oPBWeb liegen an zwei Stellen, die zusammengehören: In der Datenbank stehen alle Metadaten, im Objektspeicher die Dateiinhalte. Die Datenbank verweist über SHA-256-Prüfsummen und Schlüssel auf Objekte im Speicher (blobs/…, artifacts/…). Eine Sicherung ist nur brauchbar, wenn beide Teile aus demselben Zeitpunkt stammen und die passenden Secrets dabei sind.
Hinweis
Die Befehle in diesem Kapitel sind Beispiele. Die Datenbank-Sicherung mit mariadb-dump und das Sichern der Garage-Volumes bei gestopptem Stack sind der vorgesehene Weg; die konkreten Befehle für Volumes und Wiederherstellung sind daraus abgeleitet. Passe Pfade und Namen an deine Umgebung an und teste die Wiederherstellung, bevor du dich darauf verlässt.
Was du sicherst
| Was | Wo | Sichern | Hinweis |
|---|
| Datenbank | Volume opbweb_mariadb-data | ja, als Dump | Pakete, Versionen, Benutzer, Jobs, Protokoll, Snippets, Configserver und Depots, Einstellungen, verschlüsselte opsi-Passwörter |
| Objektspeicher-Metadaten | Volume opbweb_garage-meta | ja, bei gestopptem Stack | Bucket, Schlüssel, Objektverzeichnis |
| Objektspeicher-Daten | Volume opbweb_garage-data | ja, bei gestopptem Stack | Inhalte der Paketdateien und gebaute Pakete |
| Secrets | secrets/ | ja | ohne opsi_key_private.pem ist das opsi-Passwort verloren, ohne die übrigen Secrets passen Datenbank und Objektspeicher nicht mehr zusammen |
| Konfiguration | .env | ja | |
| Zertifikate | certs/ (inkl. certs/ldap/) | ja | TLS-Zertifikat und Schlüssel, LDAP-CA |
| Redis | Volume opbweb_redis-data | nein | nur Warteschlange und kurzlebiger Status |
| S3-Zugangsdaten | Volume opbweb_garage-credentials | nein | garage-init schreibt sie neu |
Merke dir außerdem den Versionsstand von oPBWeb (Git-Commit oder Versionen aus der Statusleiste). Eine Datenbank lässt sich nur mit derselben oder einer neueren oPBWeb-Version weiterverwenden.
Achtung
Sicherungen enthalten Passwort-Hashes, das verschlüsselte opsi-Passwort und mit secrets/ auch den Schlüssel dazu. Lege sie nur an einem geschützten Ort ab und bewahre secrets/ nicht unverschlüsselt neben den Daten auf, wenn andere Personen Zugriff auf das Sicherungsziel haben.
Sicherung erstellen
Damit Datenbank und Objektspeicher denselben Stand haben, stoppst du zuerst die Dienste, die Daten verändern, sicherst dann die Datenbank und anschließend bei gestopptem Stack den Objektspeicher.
Prüfe unter Jobs, dass keine Jobs laufen oder warten.
Lege ein geschütztes Sicherungsverzeichnis an:
Stoppe Frontend, Worker und API. Datenbank und Objektspeicher laufen weiter:
docker stop opbweb-frontend opbweb-worker opbweb-api
Sichere die Datenbank:
docker exec opbweb-db sh -c 'mariadb-dump -uroot -p"$(cat /run/secrets/db_root_password)" --single-transaction opbweb' > backup/opbweb.sql
Das Root-Passwort liest der Befehl im Container aus dem Docker-Secret; es erscheint nicht auf der Kommandozeile des Hosts.
Stoppe den Stack:
Sichere die beiden Garage-Volumes (Beispiel mit einem Alpine-Hilfscontainer):
docker run --rm -v opbweb_garage-meta:/v:ro -v "$PWD/backup":/b alpine tar czf /b/garage-meta.tgz -C /v .
docker run --rm -v opbweb_garage-data:/v:ro -v "$PWD/backup":/b alpine tar czf /b/garage-data.tgz -C /v .
Sichere Secrets, Konfiguration und Zertifikate:
tar czf backup/config.tgz secrets .env certs
Starte den Stack wieder:
Übertrage den Inhalt von backup/ anschließend auf dein Sicherungsziel. Die Ausfallzeit hängt vor allem von der Größe des Objektspeichers ab.
Wenn eine kurze Unterbrechung des ganzen Stacks ohnehin möglich ist, kannst du statt des Dumps auch das Volume opbweb_mariadb-data bei gestopptem Stack genauso wie die Garage-Volumes sichern. Dann gehören zur Datenbank zwingend die Passwörter aus secrets/db_password.txt und secrets/db_root_password.txt, die im Volume hinterlegt sind.
Wiederherstellen
Die Reihenfolge ist wichtig: erst Secrets und Konfiguration, dann der Objektspeicher, dann die Datenbank, zuletzt der Start.
Stack stoppen.
Programmstand bereitstellen. Checke dieselbe oPBWeb-Version aus, die zur Sicherung gehört (oder eine neuere). Verwendest du Images aus einer Registry, stehen deren Versionen in der gesicherten .env (BACKEND_IMAGE usw.).
Secrets, .env und Zertifikate zurückspielen. Verwende die originalen Dateien aus der Sicherung, nicht neu erzeugte:
tar xzf backup/config.tgz
chmod 750 secrets && chmod 444 secrets/*
Die Dateien brauchen die Rechte 444, damit die Prozesse in den Containern sie lesen können; start.sh setzt die Rechte über init-secrets.sh ohnehin bei jedem Start.
Objektspeicher zurückspielen. Entferne die vorhandenen Garage-Volumes einschließlich der S3-Zugangsdaten und spiele die Sicherung ein:
docker volume rm opbweb_garage-meta opbweb_garage-data opbweb_garage-credentials
docker volume create opbweb_garage-meta
docker volume create opbweb_garage-data
docker run --rm -v opbweb_garage-meta:/v -v "$PWD/backup":/b alpine tar xzf /b/garage-meta.tgz -C /v
docker run --rm -v opbweb_garage-data:/v -v "$PWD/backup":/b alpine tar xzf /b/garage-data.tgz -C /v
Das Volume garage-credentials musst du entfernen, damit garage-init die Zugangsdaten aus dem zurückgespielten Schlüssel neu schreibt. Bleibt ein altes Volume liegen, passen die Zugangsdaten nicht zum Objektspeicher. Im Log von garage-init erscheint beim nächsten Start „Credential files missing, writing from existing key…“.
Datenbank zurückspielen. Entferne das Datenbank-Volume, starte nur die Datenbank und spiele den Dump ein:
docker volume rm opbweb_mariadb-data
docker compose up -d db
docker compose ps db # warten, bis der Status "healthy" lautet
docker exec -i opbweb-db sh -c 'mariadb -uroot -p"$(cat /run/secrets/db_root_password)" opbweb' < backup/opbweb.sql
MariaDB legt beim ersten Start des leeren Volumes die Datenbank opbweb und den Anwendungsbenutzer mit den Passwörtern aus secrets/ an. Hast du das Volume statt eines Dumps gesichert, spielst du es wie die Garage-Volumes zurück.
Stack starten.
Die API führt fehlende Migrationen aus, falls der Programmstand neuer ist als die Sicherung. Ein erstes Administrator-Konto legt sie nicht an, weil die Benutzertabelle aus der Sicherung gefüllt ist.
Prüfen.
- Systemstatus: alle Dienste OK. Als Administrator prüfst du unter Objektspeicher, dass keine fehlenden Blobs gemeldet werden; fehlende Blobs deuten auf Datenbank und Objektspeicher aus unterschiedlichen Zeitpunkten hin.
- Depots → Configserver: für jeden Configserver Verbindung testen. Schlägt die Anmeldung fehl, passt
opsi_key_private.pem nicht zur Datenbank; trage dann das Passwort des technischen Benutzers neu ein. - Öffne ein Paket und lade eine Datei bzw. ein gebautes Paket herunter.
Jobs, die zum Zeitpunkt der Sicherung warteten oder liefen, setzt oPBWeb nach der Wiederherstellung nicht fort. Starte sie bei Bedarf neu.
Konsistenz
| Situation | Folge |
|---|
| Datenbank älter als Objektspeicher | überzählige Objekte; harmlos, Bereinigen … im Systemstatus entfernt sie |
| Datenbank neuer als Objektspeicher | fehlende Blobs oder Build-Ergebnisse; betroffene Dateien lassen sich nicht öffnen, betroffene Versionen müssen neu gebaut werden |
neues Schlüsselpaar opsi_key_*.pem | opsi-Passwort nicht lesbar; neu eintragen |
neue db_password.txt bzw. db_root_password.txt zu einem gesicherten Datenbank-Volume | API und Worker erreichen die Datenbank nicht |
altes Volume garage-credentials zu zurückgespieltem Objektspeicher | Zugriff auf den Objektspeicher scheitert; Volume entfernen und neu starten |
Bei regelmäßigen Sicherungen hilft eine feste Reihenfolge in einem Skript, das alle Schritte des Abschnitts Sicherung erstellen
nacheinander ausführt.
Fehlersuche
Typische Störungen mit Ursache und Abhilfe sowie eine Übersicht wichtiger Fehlermeldungen
Zielgruppe: Technische Administration
Überblick
Dieses Kapitel ordnet typische Störungen nach Symptom. Zu jedem Symptom findest du die wahrscheinliche Ursache und die Abhilfe. Am Ende steht eine Tabelle mit wichtigen Fehlermeldungen. Die Meldungen des Servers erscheinen in der Sprache der Oberfläche; dieses Kapitel zitiert sie in der deutschen Fassung.
Erste Anlaufstellen
| Quelle | Inhalt |
|---|
| Seite Systemstatus | Zustand von Datenbank, Redis, Objektspeicher, Worker und opsi-Server, siehe Systemstatus |
| Seite Jobs | vollständiges Protokoll jedes Jobs mit Fehlermeldung, siehe Jobs |
docker compose ps -a | welche Container laufen, welche beendet sind (mit Exit-Code) |
docker logs opbweb-api | Start, Migrationen, Anmeldung, LDAP |
docker logs opbweb-worker | Jobs, opsi-Verbindung |
docker logs opbweb-garage-init | Einrichtung des Objektspeichers |
| Statusleiste der Oberfläche | „Backend nicht verbunden“, wenn die API nicht antwortet; orangefarbener Hinweis bei Problemen mit dem opsi-Server |
Stack startet nicht
| Symptom | Ursache | Abhilfe |
|---|
start.sh meldet „.env fehlt – bitte ./setup-opbweb.sh ausführen (im Entwickler-Checkout: ./setup-opbweb.sh –dev).“ | keine .env | ./setup-opbweb.sh (oder cp .env.example .env) |
| API startet nicht, Log: „Secret file not found: …“ | Secret-Datei fehlt, etwa weil der Stack mit docker compose statt mit ./start.sh gestartet wurde | ./start.sh verwenden oder ./scripts/init-secrets.sh ausführen |
| API beendet sich nach etwa 60 Sekunden, Log: „Database not reachable: …“ | MariaDB nicht bereit oder Passwort passt nicht zum Volume | docker logs opbweb-db prüfen; bei geändertem db_password.txt siehe Konfiguration |
| api, worker und frontend starten nicht, garage-init mit Exit-Code ungleich 0 | Einrichtung des Objektspeichers fehlgeschlagen | docker logs opbweb-garage-init; „ERROR: No admin token found“ bedeutet fehlendes secrets/garage_admin_token.txt |
| garage-init meldet dauerhaft „Not ready yet, retrying in 2s…“ | Garage startet nicht oder das Admin-Token passt nicht | docker logs opbweb-garage; Token in secrets/ nicht nachträglich ändern |
| Log der API: „No users and no ADMIN_PASSWORD configured - no initial admin created“ | secrets/admin_password.txt leer oder fehlt bei leerer Benutzertabelle | Datei füllen bzw. von init-secrets.sh anlegen lassen, Stack neu starten |
Anmeldung
„Benutzername oder Passwort falsch.“ Bei lokalen Konten zählt jeder Fehlversuch. Nach 5 Fehlversuchen von derselben Adresse ist das Konto für diese Adresse 15 Minuten gesperrt: „Konto gesperrt bis HH:MM Uhr.“ Von anderen Adressen kann sich der Benutzer weiter anmelden; für alle gesperrt wird das Konto erst nach 50 Fehlversuchen. Nach 20 Fehlversuchen von einer Adresse (über alle Konten) antwortet oPBWeb mit „Zu viele fehlgeschlagene Anmeldungen von dieser Adresse.“ Ein Administrator hebt die Sperre unter Benutzer und Rollen mit Sperre aufheben auf; auch Passwort setzen entsperrt das Konto. Siehe Benutzer und Rollen
.
Passwort des einzigen Administrators vergessen. Ein Werkzeug zum Zurücksetzen ohne Anmeldung gibt es nicht. Ein anderer Administrator (lokal oder über eine LDAP-Gruppe) kann das Passwort setzen. Lege deshalb früh ein zweites Administrator-Konto an. secrets/admin_password.txt wirkt nur beim allerersten Start.
„Konto ist deaktiviert.“ Das Konto ist in oPBWeb deaktiviert. Ein Administrator aktiviert es unter Benutzer und Rollen wieder.
LDAP-Meldungen („Verzeichnisdienst nicht erreichbar.“, „Keine Berechtigung für oPBWeb (keine passende Gruppe).“): siehe LDAP-Anmeldung
.
Nach dem Neuladen der Seite oder nach etwa 15 Minuten wieder abgemeldet. Die Sitzung wird über das Cookie opbweb_refresh erneuert. Steht COOKIE_SECURE=true, während oPBWeb nur per HTTP erreichbar ist, speichert der Browser das Cookie nicht. Setze COOKIE_SECURE passend zum Protokoll und starte mit ./start.sh neu.
„Sitzung abgelaufen.“ Das Token ist ungültig oder wurde widerrufen, zum Beispiel nach einer Abmeldung, nach 7 Tagen ohne Erneuerung oder nach einem Wechsel von secrets/jwt_secret_key.txt. Neu anmelden.
Verbindung zum opsi-Server
Den Zustand zeigt die Zeile opsi-Server im Systemstatus, mit einem Eintrag je Configserver; dazu kommt der farbige Punkt vor jedem Configserver in der Ansicht Depots. Der Worker prüft alle Configserver alle 5 Minuten; nach einer Änderung kannst du unter Depots → Configserver mit Verbindung testen sofort prüfen. Die Einrichtung beschreibt Configserver
.
| Symptom | Ursache | Abhilfe |
|---|
| „noch nicht geprüft“ | Worker gerade gestartet | bis zum nächsten Prüflauf warten (Standard 5 Minuten) oder Verbindung testen |
| „Kein Configserver eingerichtet (Depots → Configserver hinzufügen).“ | kein Configserver hinterlegt | unter Depots mit + einen Configserver hinzufügen |
| „Für den opsi-Server ist kein CA-Zertifikat hinterlegt (Depots → Configserver).“ | TLS-Prüfung ohne CA | Vom Server holen, Fingerabdruck vergleichen, Vertrauen, Speichern |
| TLS- oder Zertifikatsfehler trotz CA | URL mit IP-Adresse statt Hostname; das Zertifikat von opsiconfd lautet auf den FQDN | URL mit FQDN eintragen, z. B. https://opsi.example.local:4447 |
| „Der Server liefert ein anderes CA-Zertifikat als hinterlegt (Rotation?)“, im Systemstatus „Server liefert ein neues CA-Zertifikat, bitte unter Depots übernehmen“ | opsi hat seine CA erneuert | unter Depots beim betroffenen Configserver Neues Zertifikat übernehmen; bis dahin schlagen Verbindungen fehl, sobald der Server die neue CA verwendet |
| „CA-Zertifikat läuft in N Tagen ab“ | Warnung 60 Tage vor Ablauf | CA auf dem opsi-Server erneuern lassen und anschließend übernehmen |
| „… hat keine opsi-Admin-Rechte“, im Systemstatus „Anmeldung ohne Admin-Rechte“ | technischer Benutzer ist kein opsi-Admin | Benutzer in die Admin-Gruppe von opsi aufnehmen |
| HTTP 403 bei Aufrufen trotz richtigem Passwort | admin-networks in opsiconfd schließt die Adresse des Workers aus | siehe admin-networks |
| „Configserver nicht erreichbar: …“ | Netz, DNS oder Firewall zwischen Worker und Port 4447 | Erreichbarkeit aus dem Worker prüfen (siehe unten) |
| „Ein Configserver mit dieser URL ist bereits eingerichtet: …“ | dieselbe URL soll ein zweites Mal angelegt werden | vorhandenen Configserver in der Liste auswählen |
| im Job-Protokoll „Depot … gehört bereits zum Configserver … und wird übersprungen“ | derselbe opsi-Server ist unter zwei URLs eingerichtet | doppelten Configserver entfernen |
| Anmeldung am opsi-Server schlägt nach einer Wiederherstellung fehl | opsi_key_private.pem passt nicht zur Datenbank | Passwort des technischen Benutzers neu eintragen |
Erreichbarkeit aus dem Worker-Container prüfen (Beispiel; -k nur für diesen Test, weil die opsi-CA im Container nicht bekannt ist):
docker exec opbweb-worker curl -sk -o /dev/null -w '%{http_code}\n' https://opsi.example.local:4447/
Eine HTTP-Statusnummer zeigt, dass Namensauflösung und Verbindung funktionieren. Ein Fehler wie „Could not resolve host“ oder ein Timeout weist auf DNS oder Firewall hin.
Bauen
Fehler beim Bauen stehen im Protokoll des Jobs „Bauen“ unter Jobs.
| Meldung | Abhilfe |
|---|
| „Script Datei (Aktion) fehlt in CLIENT_DATA.“ | Das in den Produktdaten eingetragene Script fehlt im Paket. Datei hochladen oder Eintrag korrigieren. |
| „Für diese Version läuft bereits ein Build.“ | Ende des laufenden Builds abwarten. |
| „Die Version wurde während des Builds geändert. Bitte erneut bauen.“ | Während des Builds wurde gespeichert. Erneut Bauen. |
| „Version X ist freigegeben; zum Neubauen „Zurück auf Entwurf“.“ | Freigegebene Versionen sind schreibgeschützt. Zurück auf Entwurf oder Neue Version. |
| „Archiv konnte nicht erstellt werden: …“ | Details in der Meldung prüfen; häufig fehlt Speicherplatz auf dem Host oder der Objektspeicher ist nicht erreichbar. |
| „Das Paket kann so nicht gebaut werden.“ | Die Prüfung vor dem Bauen hat Fehler gefunden; jeder Fehler steht einzeln im Job-Protokoll. Korrigieren und erneut Bauen. |
Installieren und Deinstallieren
| Symptom oder Meldung | Ursache | Abhilfe |
|---|
| Installieren … ist ausgegraut bzw. „Die Version wurde noch nicht gebaut oder seit dem letzten Bauen geändert. Bitte zuerst bauen.“ | kein aktueller Build | erneut Bauen |
| „Für das Depot Depot (Stufe) ist die Rolle Releaser nötig.“ | produktives Depot, Rolle Packager | Releaser beauftragen oder Depot-Stufe prüfen, siehe Depot-Verwaltung |
| „Depot Depot ist deaktiviert oder nicht mehr vorhanden.“ | Depot in oPBWeb deaktiviert oder vom Configserver nicht mehr gemeldet | Abgleichen unter Depots, ggf. Depot aktivieren |
| „Installation fehlgeschlagen auf: …“ | Fehler von opsiconfd beim Upload oder bei depot_installPackage | Job-Protokoll und Log von opsiconfd prüfen |
| Produkt im Depot als „gesperrt“ markiert | eine abgebrochene Installation hat die Sperre hinterlassen | unter Depots → Installierte Produkte markieren und Entsperren (n), oder im Installationsdialog „Installation erzwingen (force), auch wenn das Produkt gesperrt ist“ |
| „[Depot] Depot ist zu lange durch einen anderen Job belegt.“ | ein anderer Job hält das Depot länger als 6 Stunden | unter Jobs den laufenden Job prüfen; bei hängendem Worker docker restart opbweb-worker |
| Upload großer Pakete bricht ab | Zeitlimit für Upload und Installation erreicht | OPSI_INSTALL_TIMEOUT_SECONDS erhöhen; bei vielen Depots auch JOB_TIMEOUT_SECONDS |
| Job endet mit „Abgebrochen: Zeitlimit des Jobs überschritten oder Worker beendet.“ | Job länger als JOB_TIMEOUT_SECONDS oder Worker neu gestartet | Limit erhöhen bzw. Job neu starten; eine dabei freigegebene, nirgends installierte Version ist wieder ein Entwurf |
| Version ist freigegeben, obwohl die Installation fehlschlug | oPBWeb gibt die Version beim Start des Installations-Jobs frei | gewollt; für Änderungen Zurück auf Entwurf oder Neue Version |
Worker nicht erreichbar
Symptome: Im Systemstatus meldet die Zeile Worker „kein Lebenszeichen“ oder „letztes Lebenszeichen vor N s“; Jobs bleiben im Zustand wartend; der Dateimanager unter Depots meldet „Worker nicht erreichbar.“ oder „Der Worker antwortet nicht.“
Ursachen und Abhilfe:
- Prüfe, ob der Container läuft:
docker compose ps worker. - Lies das Log:
docker logs --tail=200 opbweb-worker. Häufige Ursachen sind fehlende Secrets, keine Verbindung zu Redis oder ein Fehler beim Start. - Starte den Worker neu:
docker restart opbweb-worker. - „Der Worker antwortet nicht.“ kann auch bedeuten, dass der Worker ausgelastet ist. Er führt höchstens 10 Jobs gleichzeitig aus; Dateimanager-Aufrufe warten höchstens 60 Sekunden. Warte laufende Jobs ab und versuche es erneut.
Objektspeicher
| Symptom | Ursache | Abhilfe |
|---|
| Systemstatus Objektspeicher: „Bucket ‘packages’ fehlt“ | garage-init lief nicht oder nicht erfolgreich | docker logs opbweb-garage-init, dann ./start.sh |
| Zugriff auf den Objektspeicher verweigert, etwa nach einer Wiederherstellung | S3-Zugangsdaten im Volume garage-credentials passen nicht zum Schlüssel in Garage | ./stop.sh, docker volume rm opbweb_garage-credentials, ./start.sh |
| Dateien lassen sich nicht öffnen, Systemstatus meldet fehlende Blobs | Datenbank und Objektspeicher stammen aus unterschiedlichen Zeitpunkten | konsistente Sicherung zurückspielen, siehe Sicherung und Wiederherstellung
; betroffene Versionen neu bauen |
| Speicherplatz auf dem Host wird knapp | alte Blobs und Build-Ergebnisse gelöschter Pakete | Bereinigen … im Systemstatus
; Belegung der Volumes mit docker system df -v prüfen |
Uploads und Zeitlimits
| Symptom | Ursache | Abhilfe |
|---|
| Upload bricht mit HTTP 413 ab | Datei größer als 20 GB (client_max_body_size) | Datei verkleinern oder auf anderem Weg ins Paket bringen, etwa über die Workbench |
| „Netzwerkfehler beim Hochladen“ | Verbindung zwischen Browser und Server abgebrochen, Proxy dazwischen mit eigenem Limit | vorgeschaltete Proxys und Firewalls auf Größen- und Zeitlimits prüfen |
| lange Anfragen brechen nach etwa 11 Minuten ab | nginx-Timeout von 660 Sekunden ohne Datenfluss zur API | lange Vorgänge laufen als Jobs; bei eigenen Proxys davor ähnliche Timeouts setzen |
Hochgeladene Dateien, die nicht verarbeitet wurden, löscht der Worker nach 24 Stunden.
HTTPS und Zertifikate
| Symptom | Ursache | Abhilfe |
|---|
| HTTPS antwortet nicht, nur HTTP | certs/fullchain.pem oder certs/key.pem fehlt oder ist falsch benannt | Dateinamen prüfen; Log von opbweb-frontend zeigt „Kein TLS-Zertifikat – HTTP auf Port 80“ |
| Frontend startet nicht nach Zertifikatswechsel | Zertifikat und Schlüssel passen nicht zusammen oder sind beschädigt | docker logs opbweb-frontend; Paar prüfen |
| neues Zertifikat wird nicht ausgeliefert | nginx liest Zertifikate nur beim Start | docker restart opbweb-frontend |
| HTTP geht nach Umstellung von HTTPS zurück im Browser nicht mehr | HSTS-Header (ein Jahr) im Browser gespeichert | bei HTTPS bleiben oder HSTS-Eintrag im Browser löschen |
| Abmeldung nach Neuladen | COOKIE_SECURE passt nicht zum Protokoll | siehe Anmeldung |
Wichtige Fehlermeldungen
| Meldung | Bedeutung | Abhilfe |
|---|
| Nicht angemeldet. | kein Token mitgeschickt | neu anmelden |
| Sitzung abgelaufen. | Token ungültig oder widerrufen | neu anmelden |
| Konto nicht verfügbar. | Konto gelöscht oder deaktiviert | Administrator fragen |
| Keine Berechtigung für diese Aktion. | Rolle reicht nicht (HTTP 403) | Rolle prüfen |
| Benutzername oder Passwort falsch. | falsche Anmeldedaten oder unbekannter Benutzer | Eingabe prüfen; Sperre nach 5 Fehlversuchen von derselben Adresse |
| Zu viele fehlgeschlagene Anmeldungen von dieser Adresse. | 20 Fehlversuche von einer Adresse innerhalb von 15 Minuten | warten |
| Konto gesperrt bis HH:MM Uhr. | lokale Sperre nach Fehlversuchen | warten oder Sperre aufheben |
| Konto ist deaktiviert. | Konto in oPBWeb deaktiviert | Konto aktivieren |
| Verzeichnisdienst nicht erreichbar. | LDAP-Verbindung oder -Suche fehlgeschlagen | LDAP-Anmeldung |
| Keine Berechtigung für oPBWeb (keine passende Gruppe). | LDAP-Anmeldung ohne passende Gruppe | Gruppenzuordnung prüfen |
| Der letzte aktive Administrator kann nicht entfernt (bzw. gelöscht) werden. | Schutz des letzten Administrators | zuerst einen weiteren Administrator anlegen |
| Version X ist freigegeben (installiert) und schreibgeschützt. … | Bearbeiten einer freigegebenen Version | Zurück auf Entwurf oder Neue Version |
| Script Datei (Aktion) fehlt in CLIENT_DATA. | Script fehlt beim Bauen | Datei ergänzen |
| Die Version wurde noch nicht gebaut oder seit dem letzten Bauen geändert. Bitte zuerst bauen. | Installieren ohne aktuellen Build | Bauen |
| Für das Depot Depot (Stufe) ist die Rolle Releaser/Packager nötig. | Stufen-Regel der Depots | Rolle oder Depot-Stufe prüfen |
| [Depot] Depot ist zu lange durch einen anderen Job belegt. | Depot-Sperre länger als 6 h | hängenden Job bzw. Worker prüfen |
| Installation fehlgeschlagen auf: … | Fehler von opsiconfd | Job-Protokoll, opsiconfd-Log |
| Configserver nicht erreichbar: … | Netzwerk zum opsi-Server | Erreichbarkeit aus dem Worker prüfen |
| Kein Configserver eingerichtet (Depots → Configserver hinzufügen). | kein Configserver konfiguriert | Configserver unter Depots hinzufügen |
| Für den opsi-Server ist kein CA-Zertifikat hinterlegt (Depots → Configserver). | CA fehlt | Vom Server holen |
| Ein Configserver mit dieser URL ist bereits eingerichtet: Name | URL doppelt | vorhandenen Configserver verwenden |
| Der Server liefert ein anderes CA-Zertifikat als hinterlegt (Rotation?) | CA-Rotation | Neues Zertifikat übernehmen |
| Benutzer hat keine opsi-Admin-Rechte | technischer Benutzer ohne Admin-Rechte | Rechte in opsi vergeben |
| {Methode}: HTTP {Code} … | opsiconfd lehnt ab, bei 403 meist admin-networks | admin-networks |
| Worker nicht erreichbar. / Der Worker antwortet nicht. | Worker gestoppt oder ausgelastet | Worker nicht erreichbar |
| Download-Link ungültig oder abgelaufen. | Link älter als 2 Minuten | Download erneut anstoßen |
| Zum Löschen ist ein Probelauf nötig. / Der Probelauf fehlt oder ist älter als eine Stunde. Bitte erneut prüfen. | Bereinigung ohne gültigen Probelauf | neuen Probelauf starten |
| Es laufen gerade Importe oder Builds. Bitte nach deren Ende erneut starten. | Bereinigung blockiert | später erneut starten |
| Unerwarteter Fehler: … | nicht abgefangener Fehler im Job | Job-Protokoll und Log des Workers prüfen |
| Secret file not found: Pfad | Secret-Datei fehlt beim Start | ./start.sh bzw. init-secrets.sh |
| Database not reachable: … | Datenbank nach 60 s nicht erreichbar | Log von opbweb-db |
Build und Auslieferung
Images bauen, GitLab-CI-Pipeline, Container-Scanning und Handbuch als Website und PDF erzeugen
Zielgruppe: Technische Administration
Überblick
oPBWeb besteht aus drei eigenen Images: Backend (für api und worker), Frontend (nginx mit Oberfläche und Handbuch) und garage-init. Du baust sie entweder lokal mit ./start.sh --build oder lässt sie von der GitLab-CI bauen und in die Projekt-Registry schieben. Die CI erzeugt außerdem das Handbuch als PDF. Dieses Kapitel beschreibt die Images, die Pipeline und wie du das Handbuch lokal baust.
Images
Alle Dockerfiles verwenden das Projektverzeichnis als Build-Kontext. .dockerignore hält unter anderem secrets/, certs/, .env, node_modules/ sowie die Hugo-Ausgaben und manual/.doc-state/ aus dem Kontext heraus.
Backend
backend/Dockerfile, ein Image für api und worker:
| Stufe | Inhalt |
|---|
base | python:3.13-slim, Systempakete (u. a. msitools für MSI-Informationen), Python-Abhängigkeiten aus requirements.txt, dazu python-opsi direkt vom GitHub-Tag 4.3.32.1 (Build-Argument PYTHON_OPSI_REF), weil es nicht auf PyPI liegt |
development | zusätzlich Entwicklungswerkzeuge; Quellen kommen im Dev-Stack als Bind-Mount |
production | ohne Compiler und Git, Anwendung unter /app, läuft als Benutzer appuser (UID 1000), Port 8000 |
Lokal entsteht opbweb-backend:latest (Produktion) bzw. opbweb-backend:dev (Dev-Stack).
Frontend
frontend/Dockerfile:
| Stufe | Inhalt |
|---|
development | node:22-alpine mit Vite-Dev-Server (nur Dev-Stack) |
builder | node:22-alpine; npm ci und npm run build erzeugen die Vue-Oberfläche |
manual | hugomods/hugo:exts-0.154.5; baut dieses Handbuch aus manual/ mit hugo --minify --baseURL /manual/ |
launcher | golang:1.24-alpine; baut den Launcher für den opsi-setup-detector (tools/opb-launcher/build.sh), eine ZIP-Datei je Plattform |
production | nginx:stable-alpine (mit apk upgrade), nginx-Konfiguration aus frontend/nginx/, Oberfläche unter /usr/share/nginx/html, Handbuch unter /usr/share/nginx/html/manual, Launcher-ZIPs unter /usr/share/nginx/html/downloads (Adresse /downloads/) |
Das Handbuch ist damit fester Bestandteil des Frontend-Images. Eine Änderung am Handbuch erscheint in der Weboberfläche erst mit einem neu gebauten Frontend-Image.
garage-init
garage/Dockerfile.garage-init: alpine:3.21 mit bash, jq und curl sowie dem Skript garage/init-garage.sh. Lokal heißt das Image opbweb-garage-init:1.0.0. Die Version ist fest und ändert sich nur, wenn sich Dockerfile oder Skript ändern.
Netzzugang beim Bauen
Für einen Build braucht der Host Zugriff auf Docker Hub (Basis-Images), GitHub (python-opsi, Hugo-Module), PyPI, die npm-Registry und den Go-Modul-Proxy proxy.golang.org (Docsy). Der Launcher kommt ohne fremde Go-Module aus.
GitLab-CI-Pipeline
Die Pipeline steht in .gitlab-ci.yml. Die Jobs laufen auf Runnern mit dem Tag dind und bauen mit Docker-in-Docker.
Stufen und Jobs
| Stufe | Job | Ergebnis |
|---|
build | build-backend | backend:<Backend-Version> und backend:latest |
build | build-frontend | frontend:<Frontend-Version> und frontend:latest (mit Handbuch) |
build | build-garage-init | garage-init:1.0.0, garage-init:latest und garage-init:<Commit-Kurz-SHA> |
scan | container_scanning | Schwachstellenbericht für backend:<Version> |
scan | container_scanning-frontend | Schwachstellenbericht für frontend:<Version> |
release | build-manuals | PDFs des Handbuchs und Release „manuals-latest“ |
release | build-launcher | Launcher-ZIPs als generisches Paket opb-launcher/<Launcher-Version> (vorher go vet und go test) |
release | stack-release | Stack-ZIP
oPBWeb-Stack-<Pipeline-Nr>.zip als generisches Paket stack/<Pipeline-Nr> und Release „stack-latest“ |
Die Versionen liest die Pipeline aus backend/src/core/version.py und frontend/package.json. Die Images landen in der Registry des Projekts unter <Registry>/<Projektpfad>/backend, …/frontend und …/garage-init (Pfad in Kleinbuchstaben). Die Registry-Adresse steht als CI_REGISTRY in .gitlab-ci.yml.
Stack-ZIP
Der Job stack-release packt alles, was ein Docker-Host zum Einrichten und Starten mit den Registry-Images braucht, in oPBWeb-Stack-<Pipeline-Nr>.zip, ohne Quellen:
| Inhalt | Zweck |
|---|
setup-opbweb.sh, start.sh, stop.sh, scripts/init-secrets.sh | Einrichtung, Start und Stopp |
docker-compose.yml, garage/garage.toml | der Stack |
versions.env | Registry-Präfix und Image-Versionen dieser Pipeline; setup-opbweb.sh nimmt sie als Vorgaben |
.env.example | Übersicht aller Variablen (das Setup braucht sie nicht) |
docker-compose.test.yml, tools/opsi-mock/, START-TEST-ENVIRONMENT_de.md/_en.md | Testumgebung
mit zwei opsi-Mocks |
QUICKSTART_de.md/_en.md, README.md | Kurzanleitung zur Inbetriebnahme |
docker-compose.dev.yml fehlt bewusst: Der Entwicklungs-Stack bindet die Quellen ein. Ohne Quellen bietet setup-opbweb.sh keinen lokalen Build an, und start.sh --dev bricht mit einem Hinweis ab. Das ZIP liegt im generischen Paket stack/<Pipeline-Nr> der Projekt-Registry und ist im Release oPBWeb Stack (Tag stack-latest) verlinkt, das jeder Lauf neu anlegt.
Images aus der Registry verwenden
Auf dem Zielhost musst du die Images dann nicht bauen. Wähle in ./setup-opbweb.sh bei der Frage nach den Images r, gib das Registry-Präfix (Vorgabe: die Registry dieser CI, abweichend über OPBWEB_REGISTRY) und die gewünschten Versionen an. Das Skript trägt BACKEND_IMAGE, FRONTEND_IMAGE und GARAGE_INIT_IMAGE in die .env ein. Danach holst du die Images (verlangt die Registry eine Anmeldung, vorher docker login) mit docker compose pull und startest mit ./start.sh ohne --build. Details stehen unter Installation
und Konfiguration
.
Wann welcher Job läuft
Auf dem Standard-Branch des Projekts (in GitLab unter Einstellungen → Repository festgelegt, z. B. master oder main) starten die Jobs automatisch, wenn sich ihre Auslöser ändern:
| Job | Auslöser auf dem Standard-Branch |
|---|
build-backend, container_scanning | backend/src/core/version.py |
build-frontend, container_scanning-frontend | frontend/package.json |
build-garage-init | garage/Dockerfile.garage-init, garage/init-garage.sh |
build-manuals | manual/content/, manual/hugo.toml, manual/layouts/, manual/assets/, manual/static/, manual/scripts/, manual/build-pdf.sh |
build-launcher | tools/opb-launcher/version.json |
stack-release | Backend- oder Frontend-Version, docker-compose.yml, docker-compose.test.yml, setup-opbweb.sh, start.sh, stop.sh, scripts/init-secrets.sh, garage/garage.toml, tools/opsi-mock/opsi_mock.py, START-TEST-ENVIRONMENT_*.md, QUICKSTART_*.md |
Ein neues Backend- oder Frontend-Image entsteht also typischerweise durch das Setzen einer neuen Version mit set-version.sh, siehe Betrieb und Aktualisierung
. Eine reine Handbuchänderung erzeugt neue PDFs, aber kein neues Frontend-Image; dafür brauchst du eine neue Frontend-Version oder einen manuellen Lauf von build-frontend.
Pipeline manuell starten
Beim manuellen Start einer Pipeline in GitLab wählst du über die Eingabe run_job, was laufen soll:
run_job | Wirkung |
|---|
all (Standard) | jeder Job folgt seinen Regeln für den Standard-Branch (siehe oben); auf anderen Branches bleibt die Pipeline leer |
build-core | alle drei Image-Builds |
security-scan | beide Container-Scans |
build-backend | nur das Backend-Image |
build-frontend | nur das Frontend-Image |
build-garage-init | nur das garage-init-Image |
build-manuals | nur die Handbuch-PDFs |
build-launcher | nur die Launcher-ZIPs |
stack-release | nur das Stack-ZIP |
Die Scans prüfen das Image mit der aktuellen Versionsnummer aus der Registry. Es muss also vorher gebaut und hochgeladen worden sein.
Container-Scanning
Die Scan-Jobs nutzen die GitLab-Vorlage Jobs/Container-Scanning.gitlab-ci.yml mit der Schwelle HIGH. Die Berichte stehen als Job-Artefakte bereit (gl-container-scanning-report-backend.json, gl-container-scanning-report-frontend.json) und erscheinen in den Sicherheitsberichten von GitLab.
Handbuch-PDFs
Der Job build-manuals läuft im Image python:3, installiert WeasyPrint, Hugo extended in der Version 0.154.5 (Variable HUGO_VERSION), Go, Node.js und das GitLab-release-cli und ruft dann manual/build-pdf.sh output auf. Er erzeugt eine PDF-Datei pro Teil und Sprache:
| Teil | Deutsch | Englisch |
|---|
| Installation | oPBWeb-Installation-<Version>.pdf | oPBWeb-Installation-Guide-<Version>.pdf |
| Benutzerhandbuch | oPBWeb-Benutzerhandbuch-<Version>.pdf | oPBWeb-User-Guide-<Version>.pdf |
| Fachliche Administration | oPBWeb-Fachl-Administration-<Version>.pdf | oPBWeb-Functional-Administration-<Version>.pdf |
| Technische Administration | oPBWeb-Techn-Administration-<Version>.pdf | oPBWeb-Technical-Administration-<Version>.pdf |
<Version> ist die Frontend-Version. Der Job lädt die PDFs in das generische Paket manuals/latest der Projekt-Registry; gleichnamige Dateien löscht er vorher. Danach legt er das Release manuals-latest („oPBWeb Handbücher“) neu an, mit je einem Link pro PDF („DE: Installation“, „EN: User Guide“ usw.) sowie Build-Datum, Pipeline-Nummer und Frontend-Version in der Beschreibung. Zusätzlich bleiben die PDFs 30 Tage als Job-Artefakte erhalten.
Handbuch lokal bauen
Die Quellen liegen unter manual/ (Hugo mit dem Theme Docsy als Hugo-Modul). Deutsche Texte stehen in manual/content/de/, englische in manual/content/en/, Bilder unter manual/static/images/<Sprache>/. Auf dem Host musst du dafür nichts installieren; alle Wege laufen in Containern.
Vorschau im Dev-Stack
Der Entwicklungs-Stack startet den Dienst manual-dev (hugo server im Image hugomods/hugo:exts-0.154.5). Die Vorschau erreichst du über den Vite-Proxy:
http://localhost:3100/manual/
Live-Reload ist abgeschaltet; lade die Seite nach einer Änderung neu. Bei Problemen hilft docker logs opbweb-manual-dev. Den Dev-Stack beschreibt Betrieb und Aktualisierung
.
Website wie im Image bauen
Das Frontend-Image baut das Handbuch selbst. Für eine Prüfung ohne Image kannst du denselben Befehl in einem Container ausführen (Beispiel):
docker run --rm -v "$PWD/manual":/src -w /src hugomods/hugo:exts-0.154.5 \
sh -c 'npm ci --no-audit --no-fund && hugo --minify --baseURL /manual/'
Das Ergebnis liegt in manual/public/. Das Verzeichnis ist von Git und vom Docker-Build ausgeschlossen.
PDFs bauen
manual/build-pdf.sh braucht Hugo extended, Go, npm und Python mit WeasyPrint. Führe es wie in der CI in einem Container aus. Das Skript liest die Versionen aus backend/ und frontend/, daher bindest du das ganze Projektverzeichnis ein. Beispiel für einen x86-64-Host, angelehnt an den CI-Job:
docker run --rm -v "$PWD":/work -w /work python:3 bash -c '
pip install --quiet weasyprint &&
apt-get update -qq && apt-get install -y -qq wget git golang nodejs npm > /dev/null &&
wget -q https://github.com/gohugoio/hugo/releases/download/v0.154.5/hugo_extended_0.154.5_linux-amd64.deb &&
dpkg -i hugo_extended_0.154.5_linux-amd64.deb > /dev/null &&
manual/build-pdf.sh output'
Die PDFs liegen danach in output/. Auf einem ARM-Rechner nimmst du das passende Hugo-Paket (linux-arm64) oder startest den Container mit --platform linux/amd64. Die im Container erzeugten Dateien gehören root.
Für den PDF-Druck erzeugt Hugo zusätzlich die Print-Ausgabe von Docsy; manual/scripts/combine_print_html.py setzt daraus je Teil und Sprache eine HTML-Datei mit Titelseite zusammen, die WeasyPrint in ein PDF umwandelt.
Versionen von Hugo und Docsy
Docsy ist in manual/go.mod auf v0.15.0 festgelegt, passend zu Hugo 0.154.5, das die CI für die PDFs verwendet. Aktualisiere Docsy nur zusammen mit einer dazu passenden Hugo-Version und prüfe danach Website und PDFs.
Hinweis
Die Hugo-Version steht an drei Stellen: im Frontend-Dockerfile (hugomods/hugo:exts-0.154.5), im Dienst manual-dev in docker-compose.dev.yml und als HUGO_VERSION in .gitlab-ci.yml. Ändere sie immer an allen drei Stellen gemeinsam, sonst fallen Website und PDFs unterschiedlich aus.