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

  1. Architektur – Dienste, Netze, Datenfluss, Aufbau des Objektspeichers, Worker und Jobs, Auslieferung des Handbuchs.
  2. Konfiguration – Variablen in der .env, Secrets, TLS und COOKIE_SECURE, Ports, Zeitlimits, Image-Overrides und Einstellungen ohne .env-Variable.
  3. LDAP-Anmeldung – Anmeldung über LDAP oder Active Directory, Zuordnung von Gruppen zu Rollen, CA-Zertifikat, Fehlersuche.
  4. Betrieb und Aktualisierung – start.sh und stop.sh, Container, Logs, Health-Endpunkte, Update, Versionierung, Entwicklungs-Stack, Cron-Jobs.
  5. Sicherung und Wiederherstellung – was du sicherst, Befehle, Reihenfolge und Konsistenz bei der Wiederherstellung.
  6. Fehlersuche – typische Symptome mit Ursache und Abhilfe, Tabelle wichtiger Fehlermeldungen.
  7. 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

DienstContainerImageAufgabe
frontendopbweb-frontendopbweb-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
apiopbweb-apiopbweb-backend:latest (python:3.13-slim)REST-API und WebSocket (FastAPI/uvicorn auf Port 8000), Anmeldung, Datenbankmigrationen beim Start, erstes Administrator-Konto
workeropbweb-workeropbweb-backend:latestführt alle Jobs aus (arq auf Redis); einziger Dienst, der mit den opsi-Configservern spricht und deren Passwörter entschlüsselt
dbopbweb-dbmariadb:11.4alle Anwendungsdaten (Pakete, Versionen, Benutzer, Jobs, Protokoll, Einstellungen)
redisopbweb-redisredis:7-alpineJob-Warteschlange, Heartbeat des Workers, Status der opsi-Verbindung, Sperrliste abgemeldeter Tokens, Depot-Sperren
garageopbweb-garagedxflrs/garage:v2.2.0S3-kompatibler Objektspeicher (ein Knoten, Replikationsfaktor 1) für Dateiinhalte und gebaute Pakete
garage-initopbweb-garage-initopbweb-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

  1. db, redis und garage starten und melden sich über ihre Healthchecks als bereit.
  2. garage-init wartet auf die Admin-API von Garage, legt fehlende Ressourcen an und schreibt die S3-Zugangsdaten in das Volume garage-credentials.
  3. 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.
  4. worker startet nach der API, schreibt einen ersten Heartbeat und prüft die Verbindung zu allen Configservern.
  5. frontend startet nach der API und wählt anhand der Zertifikate HTTPS oder HTTP.

Netze

NetzEigenschaftTeilnehmer
frontend-netnormalfrontend, api
backend-netinternal: true (kein Zugang nach außen)api, worker, db, redis, garage
opsi-netnormal, ausgehendworker

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:

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äfixInhaltLebensdauer
blobs/<2 Zeichen>/<sha256>Inhalte der Paketdateien, nach SHA-256 adressiertdauerhaft; identische Dateien mehrerer Versionen oder Pakete liegen nur einmal vor (Deduplizierung)
artifacts/<productId>/<Dateiname>gebaute Pakete: .opsi, .md5, .zsyncdauerhaft; ein neuer Build ersetzt das vorherige Ergebnis der Version
uploads/<id>/<Dateiname>hochgeladene Dateien bis zur Verarbeitungtemporär, nach 24 Stunden gelöscht
downloads/<Job-ID>/<Dateiname>vom Depot geholte Dateien zum Herunterladentemporä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

VolumeDienstInhalt
mariadb-datadbDatenbankdateien
redis-dataredisWarteschlange und Status (AOF-Persistenz)
garage-metagarageMetadaten des Objektspeichers
garage-datagarageDatenblöcke des Objektspeichers
garage-credentialsgarage-init, api, workerS3-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:

Zusätzlich laufen im Worker zeitgesteuerte Aufgaben:

AufgabeZeitpunktWirkung
heartbeatalle 30 Sekundenschreibt ein Lebenszeichen nach Redis; älter als 90 Sekunden gilt der Worker im Systemstatus als gestört
check_opsialle 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_storagetä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:

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:

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

VariableStandardDienstBedeutung
TZEurope/BerlinalleZeitzone der Container, auch für die Uhrzeit des täglichen Aufräumens (siehe Einstellungen → Worker )
LOG_LEVELINFOapi, workerDetailgrad 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
DEBUGfalseapi, workertrue 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_LANGLocale des SystemsSkripteSprache 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_USERNAMEadminapiName des ersten Administrator-Kontos; wirkt nur, solange die Benutzertabelle leer ist
COOKIE_SECUREfalseapi, workersetup-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"]apierlaubte Ursprünge als JSON-Liste; nur für den Vite-Dev-Server nötig, im Produktivbetrieb kommt alles über denselben Ursprung
ACCESS_TOKEN_MINUTES15apiGültigkeit des Zugriffs-Tokens in Minuten (wird im Browser automatisch erneuert)
REFRESH_TOKEN_DAYS7apiGültigkeit des Refresh-Cookies in Tagen, also wie lange eine Anmeldung ohne erneute Passworteingabe hält
ACCOUNT_MAX_FAILED_ATTEMPTS5apiFehlversuche, 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_MINUTES15apiDauer der Sperren und Zeitraum, in dem Fehlversuche zählen
LDAP_*–apiAnmeldung über LDAP/AD, siehe LDAP-Anmeldung

opsi und Pakete

VariableStandardDienstBedeutung
OPSI_TIMEOUT_SECONDS60workerZeitlimit in Sekunden für die übrigen opsi-Aufrufe (JSON-RPC, Auflisten per WebDAV)
OPSI_INSTALL_TIMEOUT_SECONDS3600workerZeitlimit in Sekunden für den Upload ins Repository und für depot_installPackage bzw. depot_uninstallPackage
JOB_TIMEOUT_SECONDS28800workerObergrenze in Sekunden für einen ganzen Job, z. B. eine Installation auf mehreren Depots einschließlich Wartezeit auf belegte Depots
BUILD_COMPRESSIONzstdworkerKompression 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

VariableStandardBedeutung
FRONTEND_HTTP_PORT80Host-Port für HTTP
FRONTEND_HTTPS_PORT443Host-Port für HTTPS
BACKEND_IMAGEopbweb-backend:latest (Dev: :dev)Image für api und worker
FRONTEND_IMAGEopbweb-frontend:latestImage für frontend
GARAGE_INIT_IMAGEopbweb-garage-init:1.0.0Image für garage-init
OPBWEB_REGISTRYRegistry 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.

VariableStandardBedeutung
DB_NAMEopbwebName der Datenbank, die MariaDB beim ersten Start anlegt
DB_USERopbwebDatenbankbenutzer
DATABASE_URLmysql+asyncmy://opbweb@db:3306/opbwebVerbindung der Anwendung (ohne Passwort; das Passwort fügt die Anwendung aus dem Secret ein)
DATABASE_URL_SYNCmysql+pymysql://opbweb@db:3306/opbwebVerbindung für die Migrationen
REDIS_URLredis://redis:6379/0Redis (ohne Passwort)
S3_ENDPOINTgarage:3900S3-Endpunkt von Garage
S3_BUCKETpackagesBucket; garage-init legt ihn unter diesem Namen an
S3_SECUREfalseTLS zum S3-Endpunkt

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.

DateiErzeugtVerwendet vonZweck
db_password.txtZufallswert (hex)db, api, workerPasswort des MariaDB-Benutzers opbweb
db_root_password.txtZufallswert (hex)dbRoot-Passwort von MariaDB, auch für Dumps
redis_password.txtZufallswert (hex)redis, api, workerPasswort für Redis
redis.aclaus redis_password.txtredisACL-Datei von Redis
jwt_secret_key.txtZufallswert (hex, 64 Zeichen)apiSignatur der Anmelde-Tokens
opsi_key_public.pemRSA-Schlüssel (3072 Bit), öffentlicher Teilapi, workerverschlüsselt das gespeicherte opsi-Passwort
opsi_key_private.pemRSA-Schlüssel (3072 Bit), privater Teilworkerentschlüsselt das opsi-Passwort; nur der Worker spricht mit opsi
settings_encryption_key.txtZufallswert (hex, 64 Zeichen)workerAES-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.txtZufallswert (hex, 64 Zeichen)garageRPC-Secret von Garage
garage_admin_token.txtZufallswert (hex, 64 Zeichen)garage, garage-initToken der Garage-Admin-API
admin_password.txt20 ZeichenapiPasswort des ersten Administrators; wird beim Anlegen einmal angezeigt
ldap_bind_password.txtleer; setup-opbweb.sh schreibt das eingegebene Passwort hineinapiPasswort 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:

DateiFolge einer Änderung
db_password.txt, db_root_password.txtMariaDB ü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.txtstart.sh erzeugt redis.acl beim nächsten Start aus dem neuen Passwort und startet Redis neu.
jwt_secret_key.txtAlle Sitzungen werden ungültig, alle müssen sich neu anmelden.
opsi_key_private.pem, opsi_key_public.pemNur 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.txtWird 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.txtNicht ändern; sie gehören zum vorhandenen Objektspeicher.
admin_password.txtWirkt nur, solange kein Benutzer existiert. Später ändert der Administrator sein Passwort in der Oberfläche.
ldap_bind_password.txtUnkritisch; 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/:

Vorhandennginx liefert aus
certs/fullchain.pem und certs/key.pemHTTPS 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 fehlennur 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:

BetriebCOOKIE_SECURE
HTTPStrue
nur HTTPfalse; bei true speichert der Browser das Cookie nicht, und die Sitzung lässt sich nicht erneuern

Ports

Port im ContainerHost-PortDienst
80FRONTEND_HTTP_PORT (80)nginx, HTTP
443FRONTEND_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

GrenzeWertEinstellbar
Upload und Installation/Deinstallation auf dem Depot3600 sOPSI_INSTALL_TIMEOUT_SECONDS
Übrige opsi-Aufrufe60 sOPSI_TIMEOUT_SECONDS
Laufzeit eines Jobs8 h (28800 s)JOB_TIMEOUT_SECONDS
Parallele Jobs im Worker10nein
Dateimanager-Aufrufe API → Worker60 snein
Warten auf ein belegtes Depotbis 6 hnein
Uploadgröße über nginx20 GBnein (client_max_body_size in frontend/nginx/common.inc)
nginx-Timeout zur API660 s ohne Datenflussnein
Download-Links2 minnein
Textdateien im Editor5 MB (Paketdateien), 2 MB (Dateimanager)nein

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

  1. 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.
  2. Sonst, bei LDAP_ENABLED=true, verbindet sich oPBWeb mit dem Dienstkonto (LDAP_BIND_DN) oder anonym, wenn kein Bind-DN gesetzt ist.
  3. oPBWeb sucht mit LDAP_USER_FILTER unterhalb von LDAP_USER_SEARCH_BASE (leer: LDAP_BASE_DN). Die Suche muss genau einen Eintrag liefern.
  4. Mit dem gefundenen DN und dem eingegebenen Passwort meldet sich oPBWeb als Benutzer an und prüft so das Passwort.
  5. Aus dem Attribut memberOf bestimmt oPBWeb die Rolle (siehe unten). Ohne passende Gruppe gibt es keinen Zugang.
  6. 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

VariableStandardBedeutung
LDAP_ENABLEDfalseLDAP-Anmeldung einschalten
LDAP_HOST–Hostname des Domänencontrollers bzw. LDAP-Servers
LDAP_PORT636Port
LDAP_USE_SSLtrueLDAPS (TLS ab Verbindungsaufbau); StartTLS wird nicht unterstützt
LDAP_TLS_VERIFYtrueServerzertifikat 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.

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:

ZweckFilter
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

  1. Trage die Variablen wie oben in die .env ein.

  2. 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).

  3. 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.

  4. Starte den Stack neu, damit die API die neuen Werte und das Secret liest:

    ./stop.sh
    ./start.sh
    
  5. 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

Was die Rollen im Einzelnen dürfen, beschreibt Benutzer und Rollen .

Verhalten von Verzeichniskonten

ThemaVerhalten
Rollekommt aus den Gruppen und wird bei jeder Anmeldung neu gesetzt; in der Oberfläche nicht änderbar
Rollenänderung im Verzeichniswirkt erst bei der nächsten Anmeldung mit Passwort; eine laufende Sitzung erneuert sich bis zu 7 Tage lang ohne Rückfrage beim Verzeichnis
Passwortwird im Verzeichnis geändert; Passwort ändern und Passwort setzen gibt es für diese Konten nicht
Sperre nach Fehlversuchendie 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 oPBWebmöglich; das Konto kann sich dann trotz gültigem Verzeichnis-Passwort nicht mehr anmelden
Zugang sofort entziehenKonto 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 AnmeldenUrsacheAbhilfe
„Verzeichnisdienst nicht erreichbar.“Verbindung, TLS oder Suche mit dem Dienstkonto fehlgeschlagenLog 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 EintragFilter 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 passtGruppen-DNs exakt mit memberOf des Benutzers vergleichen; direkte Mitgliedschaft prüfen
„Konto ist deaktiviert.“Konto in oPBWeb deaktiviertunter 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]
OptionWirkung
(keine)startet den Produktiv-Stack (docker-compose.yml) mit den vorhandenen Images
--buildbaut die eigenen Images vor dem Start neu
--logsfolgt nach dem Start den Logs aller Container (logs -f --tail=50); Strg+C beendet nur die Anzeige
--devnimmt docker-compose.dev.yml hinzu (Entwicklungs-Stack, siehe unten); nur im Klon mit Quellen
--testnimmt docker-compose.test.yml hinzu: zwei opsi-Mocks als Configserver Test und Produktion, siehe Testumgebung
--lang de bzw. --lang enSprache der Ausgaben für diesen Aufruf (sonst OPBWEB_LANG aus der .env)
-h, --helpzeigt 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

./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

ContainerDienstHinweis
opbweb-frontendnginxWeboberfläche, Handbuch, Proxy
opbweb-apiAPIführt beim Start die Migrationen aus
opbweb-workerWorkerJobs, Cron, opsi-Verbindung
opbweb-dbMariaDB
opbweb-redisRedis
opbweb-garageGarageObjektspeicher
opbweb-garage-initEinrichtung Garageläuft bei jedem Start kurz und endet mit Exit-Code 0
opbweb-opsi-mocknur Devnachgebauter opsiconfd (erstes opsi-System)
opbweb-opsi-mock2nur Devnachgebauter opsiconfd (zweites opsi-System)
opbweb-frontend-devnur DevVite-Dev-Server
opbweb-manual-devnur DevHugo-Vorschau des Handbuchs
opbweb-launcher-buildnur Devbaut die Launcher-ZIPs beim Start und endet mit Exit-Code 0
opbweb-launcher-devnur Devliefert 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
ContainerWas du dort findest
opbweb-apiMigrationen und Start von uvicorn, Anlegen des ersten Admins, HTTP-Zugriffe (eine Zeile je Anfrage), Probleme bei der LDAP-Anmeldung
opbweb-workerJob-Ausführung, opsi-Prüfung (Standard alle 5 Minuten), Fehler bei Verbindungen zu opsiconfd
opbweb-frontendWahl von HTTP oder HTTPS, Zugriffe und nginx-Fehler
opbweb-garage-initEinrichtung 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

EndpunktAnmeldungAntwort
/healthneinstatisch healthy von nginx; prüft nur, ob nginx antwortet
/api/healthnein{"status":"ok","version":"…"}; die API ist über nginx erreichbar
/api/health/detailsja (Bearer-Token)Prüfungen database, redis, storage, worker, opsi mit ok, Details und Dauer
/api/system/databaseja, AdminZeilen und Größe je Datenbanktabelle
/api/system/storageja, AdminBelegung des Objektspeichers je Bereich, Deduplizierung, nicht verwendete und fehlende Blobs
/api/system/storage/objectsja, AdminObjekte eines Bereichs mit den Paketversionen, die sie verwenden (Grundlage des Bucket-Browsers)
Seite Systemstatusja, alle Rollendieselben 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üfungFehler, wenn …
Datenbankkeine Verbindung; die Details nennen sonst MariaDB-Version und Schema-Revision
Rediskeine Verbindung
Objektspeicherkeine Verbindung oder Bucket fehlt („Bucket ‘…’ fehlt“)
Workerkein Heartbeat („kein Lebenszeichen“) oder letzter Heartbeat älter als 90 Sekunden
opsi-Servernoch 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.

AufgabeZeitpunktWirkung
Heartbeatalle 30 SekundenLebenszeichen für den Systemstatus
opsi-Prüfungalle 5 Minuten (Standard)Verbindung, Depot-Abgleich, CA-Vergleich, Ablaufwarnung 60 Tage vorher
Aufräumen temporärer Dateientä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

  1. Sichere Datenbank und Objektspeicher, siehe Sicherung und Wiederherstellung . Datenbankmigrationen lassen sich nicht ohne Weiteres rückgängig machen.

  2. Prüfe unter Jobs, dass keine Installationen laufen.

  3. 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.

  4. 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.

  5. Aktualisiere die Images und starte den Stack:

    • Lokal gebaute Images:

      ./start.sh --build
      

      --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.

  6. Die API führt beim Start automatisch alembic upgrade head aus. Prüfe danach das Log:

    docker logs opbweb-api 2>&1 | head -50
    
  7. 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).

AufrufWirkung
./set-version.shzeigt die aktuellen Versionen
./set-version.sh 0.14.0setzt alle Komponenten (mit Rückfrage; --force ohne)
./set-version.sh --backend 0.13.1setzt nur das Backend
./set-version.sh --frontend 0.13.1setzt 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):

./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.

WasAdresse
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ächehttp://localhost:8100/api/docs
nginx-FrontendPort aus FRONTEND_HTTP_PORT bzw. FRONTEND_HTTPS_PORT
MariaDBPort 3307
RedisPort 6380
Garage S3Port 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.

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.

Was du sicherst

WasWoSichernHinweis
DatenbankVolume opbweb_mariadb-dataja, als DumpPakete, Versionen, Benutzer, Jobs, Protokoll, Snippets, Configserver und Depots, Einstellungen, verschlüsselte opsi-Passwörter
Objektspeicher-MetadatenVolume opbweb_garage-metaja, bei gestopptem StackBucket, Schlüssel, Objektverzeichnis
Objektspeicher-DatenVolume opbweb_garage-dataja, bei gestopptem StackInhalte der Paketdateien und gebaute Pakete
Secretssecrets/jaohne opsi_key_private.pem ist das opsi-Passwort verloren, ohne die übrigen Secrets passen Datenbank und Objektspeicher nicht mehr zusammen
Konfiguration.envja
Zertifikatecerts/ (inkl. certs/ldap/)jaTLS-Zertifikat und Schlüssel, LDAP-CA
RedisVolume opbweb_redis-dataneinnur Warteschlange und kurzlebiger Status
S3-ZugangsdatenVolume opbweb_garage-credentialsneingarage-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.

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.

  1. Prüfe unter Jobs, dass keine Jobs laufen oder warten.

  2. Lege ein geschütztes Sicherungsverzeichnis an:

    mkdir -p -m 700 backup
    
  3. Stoppe Frontend, Worker und API. Datenbank und Objektspeicher laufen weiter:

    docker stop opbweb-frontend opbweb-worker opbweb-api
    
  4. 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.

  5. Stoppe den Stack:

    ./stop.sh
    
  6. 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 .
    
  7. Sichere Secrets, Konfiguration und Zertifikate:

    tar czf backup/config.tgz secrets .env certs
    
  8. Starte den Stack wieder:

    ./start.sh
    

Ü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.

  1. Stack stoppen.

    ./stop.sh
    
  2. 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.).

  3. 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.

  4. 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…“.

  5. 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.

  6. Stack starten.

    ./start.sh
    

    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.

  7. 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

SituationFolge
Datenbank älter als Objektspeicherüberzählige Objekte; harmlos, Bereinigen … im Systemstatus entfernt sie
Datenbank neuer als Objektspeicherfehlende Blobs oder Build-Ergebnisse; betroffene Dateien lassen sich nicht öffnen, betroffene Versionen müssen neu gebaut werden
neues Schlüsselpaar opsi_key_*.pemopsi-Passwort nicht lesbar; neu eintragen
neue db_password.txt bzw. db_root_password.txt zu einem gesicherten Datenbank-VolumeAPI und Worker erreichen die Datenbank nicht
altes Volume garage-credentials zu zurückgespieltem ObjektspeicherZugriff 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

QuelleInhalt
Seite SystemstatusZustand von Datenbank, Redis, Objektspeicher, Worker und opsi-Server, siehe Systemstatus
Seite Jobsvollständiges Protokoll jedes Jobs mit Fehlermeldung, siehe Jobs
docker compose ps -awelche Container laufen, welche beendet sind (mit Exit-Code)
docker logs opbweb-apiStart, Migrationen, Anmeldung, LDAP
docker logs opbweb-workerJobs, opsi-Verbindung
docker logs opbweb-garage-initEinrichtung 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

SymptomUrsacheAbhilfe
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 Volumedocker 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 0Einrichtung des Objektspeichers fehlgeschlagendocker 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 nichtdocker 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 BenutzertabelleDatei 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 .

SymptomUrsacheAbhilfe
„noch nicht geprüft“Worker gerade gestartetbis zum nächsten Prüflauf warten (Standard 5 Minuten) oder Verbindung testen
„Kein Configserver eingerichtet (Depots → Configserver hinzufügen).“kein Configserver hinterlegtunter Depots mit + einen Configserver hinzufügen
„Für den opsi-Server ist kein CA-Zertifikat hinterlegt (Depots → Configserver).“TLS-Prüfung ohne CAVom Server holen, Fingerabdruck vergleichen, Vertrauen, Speichern
TLS- oder Zertifikatsfehler trotz CAURL mit IP-Adresse statt Hostname; das Zertifikat von opsiconfd lautet auf den FQDNURL 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 erneuertunter 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 AblaufCA 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-AdminBenutzer in die Admin-Gruppe von opsi aufnehmen
HTTP 403 bei Aufrufen trotz richtigem Passwortadmin-networks in opsiconfd schließt die Adresse des Workers aussiehe admin-networks
„Configserver nicht erreichbar: …“Netz, DNS oder Firewall zwischen Worker und Port 4447Erreichbarkeit aus dem Worker prüfen (siehe unten)
„Ein Configserver mit dieser URL ist bereits eingerichtet: …“dieselbe URL soll ein zweites Mal angelegt werdenvorhandenen 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 eingerichtetdoppelten Configserver entfernen
Anmeldung am opsi-Server schlägt nach einer Wiederherstellung fehlopsi_key_private.pem passt nicht zur DatenbankPasswort 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.

MeldungAbhilfe
„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 MeldungUrsacheAbhilfe
Installieren … ist ausgegraut bzw. „Die Version wurde noch nicht gebaut oder seit dem letzten Bauen geändert. Bitte zuerst bauen.“kein aktueller Builderneut Bauen
„Für das Depot Depot (Stufe) ist die Rolle Releaser nötig.“produktives Depot, Rolle PackagerReleaser 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 gemeldetAbgleichen unter Depots, ggf. Depot aktivieren
„Installation fehlgeschlagen auf: …“Fehler von opsiconfd beim Upload oder bei depot_installPackageJob-Protokoll und Log von opsiconfd prüfen
Produkt im Depot als „gesperrt“ markierteine abgebrochene Installation hat die Sperre hinterlassenunter 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 Stundenunter Jobs den laufenden Job prüfen; bei hängendem Worker docker restart opbweb-worker
Upload großer Pakete bricht abZeitlimit für Upload und Installation erreichtOPSI_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 gestartetLimit erhöhen bzw. Job neu starten; eine dabei freigegebene, nirgends installierte Version ist wieder ein Entwurf
Version ist freigegeben, obwohl die Installation fehlschlugoPBWeb gibt die Version beim Start des Installations-Jobs freigewollt; 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:

  1. Prüfe, ob der Container läuft: docker compose ps worker.
  2. Lies das Log: docker logs --tail=200 opbweb-worker. Häufige Ursachen sind fehlende Secrets, keine Verbindung zu Redis oder ein Fehler beim Start.
  3. Starte den Worker neu: docker restart opbweb-worker.
  4. „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

SymptomUrsacheAbhilfe
Systemstatus Objektspeicher: „Bucket ‘packages’ fehlt“garage-init lief nicht oder nicht erfolgreichdocker logs opbweb-garage-init, dann ./start.sh
Zugriff auf den Objektspeicher verweigert, etwa nach einer WiederherstellungS3-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 BlobsDatenbank und Objektspeicher stammen aus unterschiedlichen Zeitpunktenkonsistente Sicherung zurückspielen, siehe Sicherung und Wiederherstellung ; betroffene Versionen neu bauen
Speicherplatz auf dem Host wird knappalte Blobs und Build-Ergebnisse gelöschter PaketeBereinigen … im Systemstatus ; Belegung der Volumes mit docker system df -v prüfen

Uploads und Zeitlimits

SymptomUrsacheAbhilfe
Upload bricht mit HTTP 413 abDatei 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 Limitvorgeschaltete Proxys und Firewalls auf Größen- und Zeitlimits prüfen
lange Anfragen brechen nach etwa 11 Minuten abnginx-Timeout von 660 Sekunden ohne Datenfluss zur APIlange 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

SymptomUrsacheAbhilfe
HTTPS antwortet nicht, nur HTTPcerts/fullchain.pem oder certs/key.pem fehlt oder ist falsch benanntDateinamen prüfen; Log von opbweb-frontend zeigt „Kein TLS-Zertifikat – HTTP auf Port 80“
Frontend startet nicht nach ZertifikatswechselZertifikat und Schlüssel passen nicht zusammen oder sind beschädigtdocker logs opbweb-frontend; Paar prüfen
neues Zertifikat wird nicht ausgeliefertnginx liest Zertifikate nur beim Startdocker restart opbweb-frontend
HTTP geht nach Umstellung von HTTPS zurück im Browser nicht mehrHSTS-Header (ein Jahr) im Browser gespeichertbei HTTPS bleiben oder HSTS-Eintrag im Browser löschen
Abmeldung nach NeuladenCOOKIE_SECURE passt nicht zum Protokollsiehe Anmeldung

Wichtige Fehlermeldungen

MeldungBedeutungAbhilfe
Nicht angemeldet.kein Token mitgeschicktneu anmelden
Sitzung abgelaufen.Token ungültig oder widerrufenneu anmelden
Konto nicht verfügbar.Konto gelöscht oder deaktiviertAdministrator fragen
Keine Berechtigung für diese Aktion.Rolle reicht nicht (HTTP 403)Rolle prüfen
Benutzername oder Passwort falsch.falsche Anmeldedaten oder unbekannter BenutzerEingabe prüfen; Sperre nach 5 Fehlversuchen von derselben Adresse
Zu viele fehlgeschlagene Anmeldungen von dieser Adresse.20 Fehlversuche von einer Adresse innerhalb von 15 Minutenwarten
Konto gesperrt bis HH:MM Uhr.lokale Sperre nach Fehlversuchenwarten oder Sperre aufheben
Konto ist deaktiviert.Konto in oPBWeb deaktiviertKonto aktivieren
Verzeichnisdienst nicht erreichbar.LDAP-Verbindung oder -Suche fehlgeschlagenLDAP-Anmeldung
Keine Berechtigung für oPBWeb (keine passende Gruppe).LDAP-Anmeldung ohne passende GruppeGruppenzuordnung prüfen
Der letzte aktive Administrator kann nicht entfernt (bzw. gelöscht) werden.Schutz des letzten Administratorszuerst einen weiteren Administrator anlegen
Version X ist freigegeben (installiert) und schreibgeschützt. …Bearbeiten einer freigegebenen VersionZurück auf Entwurf oder Neue Version
Script Datei (Aktion) fehlt in CLIENT_DATA.Script fehlt beim BauenDatei ergänzen
Die Version wurde noch nicht gebaut oder seit dem letzten Bauen geändert. Bitte zuerst bauen.Installieren ohne aktuellen BuildBauen
Für das Depot Depot (Stufe) ist die Rolle Releaser/Packager nötig.Stufen-Regel der DepotsRolle oder Depot-Stufe prüfen
[Depot] Depot ist zu lange durch einen anderen Job belegt.Depot-Sperre länger als 6 hhängenden Job bzw. Worker prüfen
Installation fehlgeschlagen auf: …Fehler von opsiconfdJob-Protokoll, opsiconfd-Log
Configserver nicht erreichbar: …Netzwerk zum opsi-ServerErreichbarkeit aus dem Worker prüfen
Kein Configserver eingerichtet (Depots → Configserver hinzufügen).kein Configserver konfiguriertConfigserver unter Depots hinzufügen
Für den opsi-Server ist kein CA-Zertifikat hinterlegt (Depots → Configserver).CA fehltVom Server holen
Ein Configserver mit dieser URL ist bereits eingerichtet: NameURL doppeltvorhandenen Configserver verwenden
Der Server liefert ein anderes CA-Zertifikat als hinterlegt (Rotation?)CA-RotationNeues Zertifikat übernehmen
Benutzer hat keine opsi-Admin-Rechtetechnischer Benutzer ohne Admin-RechteRechte in opsi vergeben
{Methode}: HTTP {Code} …opsiconfd lehnt ab, bei 403 meist admin-networksadmin-networks
Worker nicht erreichbar. / Der Worker antwortet nicht.Worker gestoppt oder ausgelastetWorker nicht erreichbar
Download-Link ungültig oder abgelaufen.Link älter als 2 MinutenDownload 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 Probelaufneuen Probelauf starten
Es laufen gerade Importe oder Builds. Bitte nach deren Ende erneut starten.Bereinigung blockiertspäter erneut starten
Unerwarteter Fehler: …nicht abgefangener Fehler im JobJob-Protokoll und Log des Workers prüfen
Secret file not found: PfadSecret-Datei fehlt beim Start./start.sh bzw. init-secrets.sh
Database not reachable: …Datenbank nach 60 s nicht erreichbarLog 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:

StufeInhalt
basepython: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
developmentzusätzlich Entwicklungswerkzeuge; Quellen kommen im Dev-Stack als Bind-Mount
productionohne 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:

StufeInhalt
developmentnode:22-alpine mit Vite-Dev-Server (nur Dev-Stack)
buildernode:22-alpine; npm ci und npm run build erzeugen die Vue-Oberfläche
manualhugomods/hugo:exts-0.154.5; baut dieses Handbuch aus manual/ mit hugo --minify --baseURL /manual/
launchergolang:1.24-alpine; baut den Launcher für den opsi-setup-detector (tools/opb-launcher/build.sh), eine ZIP-Datei je Plattform
productionnginx: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

StufeJobErgebnis
buildbuild-backendbackend:<Backend-Version> und backend:latest
buildbuild-frontendfrontend:<Frontend-Version> und frontend:latest (mit Handbuch)
buildbuild-garage-initgarage-init:1.0.0, garage-init:latest und garage-init:<Commit-Kurz-SHA>
scancontainer_scanningSchwachstellenbericht für backend:<Version>
scancontainer_scanning-frontendSchwachstellenbericht für frontend:<Version>
releasebuild-manualsPDFs des Handbuchs und Release „manuals-latest“
releasebuild-launcherLauncher-ZIPs als generisches Paket opb-launcher/<Launcher-Version> (vorher go vet und go test)
releasestack-releaseStack-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:

InhaltZweck
setup-opbweb.sh, start.sh, stop.sh, scripts/init-secrets.shEinrichtung, Start und Stopp
docker-compose.yml, garage/garage.tomlder Stack
versions.envRegistry-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.mdTestumgebung mit zwei opsi-Mocks
QUICKSTART_de.md/_en.md, README.mdKurzanleitung 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:

JobAuslöser auf dem Standard-Branch
build-backend, container_scanningbackend/src/core/version.py
build-frontend, container_scanning-frontendfrontend/package.json
build-garage-initgarage/Dockerfile.garage-init, garage/init-garage.sh
build-manualsmanual/content/, manual/hugo.toml, manual/layouts/, manual/assets/, manual/static/, manual/scripts/, manual/build-pdf.sh
build-launchertools/opb-launcher/version.json
stack-releaseBackend- 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_jobWirkung
all (Standard)jeder Job folgt seinen Regeln für den Standard-Branch (siehe oben); auf anderen Branches bleibt die Pipeline leer
build-corealle drei Image-Builds
security-scanbeide Container-Scans
build-backendnur das Backend-Image
build-frontendnur das Frontend-Image
build-garage-initnur das garage-init-Image
build-manualsnur die Handbuch-PDFs
build-launchernur die Launcher-ZIPs
stack-releasenur 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:

TeilDeutschEnglisch
InstallationoPBWeb-Installation-<Version>.pdfoPBWeb-Installation-Guide-<Version>.pdf
BenutzerhandbuchoPBWeb-Benutzerhandbuch-<Version>.pdfoPBWeb-User-Guide-<Version>.pdf
Fachliche AdministrationoPBWeb-Fachl-Administration-<Version>.pdfoPBWeb-Functional-Administration-<Version>.pdf
Technische AdministrationoPBWeb-Techn-Administration-<Version>.pdfoPBWeb-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.