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.