Build und Auslieferung
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.
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.