Zum Inhalt

Architektur — Container & URL-Routing

Container-Architektur

Container von calServer v2: nginx-proxy verteilt auf Frontend, API und Updater; die API nutzt Datenbank, Redis, report-runner und Siegel-Dienst

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