Architektur — Container & URL-Routing¶
Container-Architektur¶
calServer v2 ist eigenständig: Frontend, API und ihre Dienste bilden den kompletten Stack, eine V1-Installation wird nicht gebraucht. Durchgezogene Kästen laufen immer, gestrichelte nur, wenn sie eingerichtet oder lizenziert sind.
| Container | Aufgabe |
|---|---|
nginx-proxy |
TLS-Abschluss (Let's Encrypt) und Routing nach Pfad |
calserver-frontend |
Nuxt-4-Oberfläche |
calserver-api-v2 |
Laravel-API mit Queue-Worker und Scheduler, OAuth und MCP-Endpoint |
calserver-postgres |
V2-Datenbank (PostgreSQL 16); alternativ eine vorhandene MySQL- oder SQL-Server-Instanz, siehe Datenbank-Konfiguration |
redis |
Cache, Queue und Sessions |
report-runner |
Erzeugt PDF-Berichte, Kalibrierscheine und DCC |
calserver-seal |
Elektronisches Siegel für Kalibrierscheine (optional) |
calserver-updater |
Updates aus der Oberfläche (optional) |
calserver-mcp, calserver-metbase, calserver-metteam |
Erweiterungen je nach Lizenz (optional) |
Die Daten aus einer bestehenden V1-Installation übernimmt V2 einmalig beim Umstieg, siehe Installation. Danach greift V2 nicht mehr auf die V1-Datenbank zu.
URL-Routing¶
| Pfad | Service | Beschreibung |
|---|---|---|
/ |
calserver-frontend | Oberfläche |
/api/v2/* |
calserver-api-v2 | REST-API v2 (inkl. MCP-Endpoint /api/v2/mcp) |
/oauth/* |
calserver-api-v2 | OAuth 2.1: authorize, token, register (DCR), Login-Bruecke |
/.well-known/oauth-* |
calserver-api-v2 | OAuth-Discovery (RFC 8414 / RFC 9728) |
/mcp, /mcp-server |
— | 308-Weiterleitung auf /api/v2/mcp |
/internal/updater/* |
calserver-updater | Update-Dienst (nur wenn eingerichtet) |
Bestehende Anlagen mit V1 daneben
Läuft auf einer Anlage noch V1 mit, liegt die V2-Oberfläche unter /v2/
und V1 auf /. Der Schalter docker/nginx/v2-primary.sh on legt V2 auch
dort auf die Wurzel. Neue Anlagen kennen diesen Fall nicht.
Die Routing-Regeln stehen in docker/nginx/vhost.d/default_location und
werden vom nginx-proxy in den Server-Block jedes VIRTUAL_HOST eingebunden.
Diese Datei liegt in keinem Image
Der nginx-proxy laeuft aus dem Upstream-Image nginxproxy/nginx-proxy;
dieses Repo baut es nicht. Die Routing-Datei kommt per Bind-Mount vom
Host (./nginx/vhost.d), ein Image-Update zieht sie also nicht mit. Der
Deploy synchronisiert sie ueber docker/sync-nginx-vhost.sh — inklusive
nginx -t und Rollback bei Syntaxfehlern. Der V2-primary-Schalter in
derselben Datei ist host-verwaltet und bleibt dabei erhalten. Manuell auf
dem Server: Datei tauschen, dann
docker exec nginx-proxy nginx -t && docker exec nginx-proxy nginx -s reload.
MCP-Endpoint und OAuth¶
Der MCP-Server (Model Context Protocol) liegt unter
https://<domain>/api/v2/mcp — das ist die URL, die im
claude.ai-Connector eingetragen wird. /mcp und /mcp-server leiten per
308 dorthin weiter (Methode und Body des JSON-RPC-POSTs bleiben erhalten).
Der Endpoint allein reicht nicht: ein MCP-Client startet mit einer
Discovery-Anfrage auf die Origin-Wurzel, nicht auf den Endpoint-Pfad.
Diese Pfade muessen deshalb ebenfalls beim Laravel-Container landen, sonst
bricht der Handshake ab, obwohl /api/v2/mcp korrekt antwortet:
| Pfad | Zweck |
|---|---|
GET /.well-known/oauth-protected-resource/api/v2/mcp |
Resource-Metadaten (RFC 9728) |
GET /.well-known/oauth-authorization-server |
Authorization-Server-Metadaten (RFC 8414) |
POST /oauth/register |
Dynamic Client Registration (RFC 7591) |
GET /oauth/authorize, POST /oauth/token |
Passport |
GET/POST /oauth/login |
Session-Bruecke fuer den Consent-Screen |
/.well-known/acme-challenge/ bleibt bewusst ausgeklammert (Prefix ist
/.well-known/oauth-), damit die Let's-Encrypt-Erneuerung weiterlaeuft.
Weil TLS am Proxy endet, muss Laravel dem Proxy vertrauen
(trustProxies in bootstrap/app.php) — sonst wirbt die Discovery mit
http://-URLs und der Client verweigert die Verbindung. Weicht die
oeffentliche Origin von dem ab, was Laravel ableitet, setzt
MCP_AUTHORIZATION_SERVER sie explizit.
Alternative zum in-App-Endpoint ist der eigenstaendige
calserver-mcp-Container auf einer eigenen Subdomain
(MCP_HOST, Connector-URL dann https://<MCP_HOST>/mcp) — Details in
mcp-server/README.md.
Beide gleichzeitig zu betreiben ist nicht vorgesehen: dann MCP_ENABLED=false
setzen, damit nur der Container als MCP-Server gilt.
Start¶
Der einfachste Weg ist der Ein-Klick-Installer, siehe
Probe mit einem Befehl. Von Hand, im
Verzeichnis docker/:
docker compose -f docker-compose.v2.yml -f docker-compose.v2-standalone.yml up -d
# mit Let's Encrypt
docker compose -f docker-compose.v2.yml -f docker-compose.v2-standalone.yml \
-f docker-compose.https.yml up -d
Mit V2_STANDALONE=true in der .env wählt deploy.sh diese Dateien selbst.
Services¶
calserver-api-v2 (Laravel)¶
- Image:
calhelp/calserver-api-v2 - Base: PHP 8.3 FPM (Debian bookworm)
- Includes: Nginx, PHP-FPM, Queue Worker (Supervisor)
- Health:
GET /health→{"status":"ok"} - Volumes: filemanager (read-only)
- Env: DB credentials, Pusher keys, Redis connection
calserver-frontend (Nuxt)¶
- Image:
calhelp/calserver-frontend - Base: Node 22 Alpine
- Includes: Nitro Server (Nuxt SSR runtime, SPA mode)
- Health:
GET /→ 200 - Env: API base URL, Pusher keys
redis¶
- Image: Redis 8 Alpine
- Purpose: Cache, Queue, Session Store für Laravel
- Persistent: Volume
redis_data