Zum Inhalt

calServer v2 — Deployment

Docker Images bauen

# Laravel API v2
docker build -f Dockerfile.laravel -t calhelp/calserver-api-v2:local .

# Frontend v2
docker build -f Dockerfile.frontend -t calhelp/calserver-frontend:local .

Docker Compose (Entwicklung + Produktion)

Nur v1 (bestehende Kunden — keine Aenderung)

cd docker
docker compose up -d

v1 + v2 parallel

cd docker
docker compose -f docker-compose.yml -f docker-compose.v2.yml up -d

Nur v2 Services neustarten

cd docker
docker compose -f docker-compose.yml -f docker-compose.v2.yml restart calserver-api-v2 calserver-frontend

Zusaetzliche Umgebungsvariablen (.env)

Folgende Variablen muessen zur bestehenden .env hinzugefuegt werden:

# Laravel API v2
LARAVEL_APP_KEY=base64:... # generieren mit: php artisan key:generate --show
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com
DB_PREFIX=thermo_

# Redis (neu)
# Kein separates Passwort noetig im internen Docker-Netzwerk

Health Checks

# API v2
curl http://localhost:8081/api/v2/health
# → {"status":"healthy","services":{"database":"up",…},…}

# Frontend v2
curl http://localhost:3000/
# → HTML response (200)

# Redis
docker exec calserver-redis redis-cli ping
# → PONG

MCP-Endpoint: Connector-URL

Kanonisch ist https://<host>/api/v2/mcp — das ist die URL, die im Claude-Connector eingetragen gehoert. nginx leitet die naheliegenden Fehlgriffe per 308 dorthin um (/mcp, /mcp-server, /v2/mcp), Methode und Body des JSON-RPC-Requests bleiben dabei erhalten.

/v2/mcp ist der haeufigste davon, weil die V2-Oberflaeche unter /v2/ liegt. Ohne die Umleitung faellt der Pfad ins Nuxt-Frontend, und der Fehler zeigt sich erst spaet: die OAuth-Discovery antwortet auch fuer Pfade ohne MCP-Endpoint, der Client kommt also durch Registrierung und Anmeldung und scheitert erst am ersten JSON-RPC-Aufruf.

MCP-Endpoint: OAuth-Signaturschluessel

Der MCP-Endpoint (/api/v2/mcp) authentifiziert Remote-Clients wie den Claude-Connector per OAuth 2.1 (Laravel Passport). Dafuer braucht die Anlage ein RSA-Schluesselpaar. Der Entrypoint erzeugt es beim Start selbst (php artisan passport:ensure-keys, idempotent) und legt es unter storage/app/passport/ ab, also im persistenten Volume.

# Status pruefen bzw. nachtraeglich erzeugen (aendert vorhandene Schluessel nicht):
docker exec calserver-api-v2 php artisan passport:ensure-keys

Zwei Dinge sind wichtig:

  • Das Verzeichnis gehoert ins Backup. Gehen die Schluessel verloren, werden alle ausgegebenen Access-Tokens ungueltig und jeder MCP-Client muss sich neu verbinden.
  • Nicht von Hand passport:keys ausfuehren. Der Befehl ueberschreibt bei --force das vorhandene Paar und entwertet damit alle Tokens.

Alternativ liefert man die Schluessel per PASSPORT_PRIVATE_KEY / PASSPORT_PUBLIC_KEY aus einem Secret-Management; diese Werte haben Vorrang vor den Dateien. PASSPORT_KEY_PATH verschiebt das Verzeichnis, falls das Storage-Layout abweicht.

Schlaegt /oauth/authorize mit HTTP 503 und server_error fehl, fehlen genau diese Schluessel: Volume schreibgeschuetzt, Pfad falsch gesetzt oder die Env-Werte unlesbar (haeufig verlorene Zeilenumbrueche). Details stehen im Anwendungslog. Begruendung der Ablage: ADR 2026-08-07.

Kubernetes / Helm

Der Helm-Chart k8s/charts/calserver deckt den vollen Stack ab und bringt diese Deployments bereits mit:

  1. calserver-api-v2 — Deployment + Service (Port 8081)
  2. calserver-frontend — Deployment + Service (Port 3000)
  3. Ingress-/Route-Regeln fuer /api/v2/* und /v2/*

Es ist der einzige Chart im Repo. Der frueher daneben stehende helm-chart/calserver — ein einzelnes Deployment des V1-Containers, unter der irrefuehrenden URL /charts-v2 veroeffentlicht — ist entfernt. 4. Redis-Dependency hinzufuegen (z.B. Bitnami Redis Chart)

CI/CD Pipeline

Die Build-Pipeline muss drei Images produzieren:

calhelp/calwebapp_bootstrap:$TAG  → Yii 1 (unveraendert)
calhelp/calserver-api-v2:$TAG     → Laravel API
calhelp/calserver-frontend:$TAG   → Nuxt Frontend

Rollback

Bei Problemen mit v2 kann die Overlay-Compose-Datei einfach weggelassen werden. v1 laeuft dann alleine weiter:

docker compose down calserver-api-v2 calserver-frontend redis
docker compose up -d  # nur v1

Demo-Instanzen (Kunden-Live-Demos)

Eine Demo ist eine normale V2-Installation mit vier Abweichungen in der .env und drei Schaltern in den Demo-Einstellungen. Nichts davon ist ein eigener Betriebsmodus im Deployment:

V2_STANDALONE=true      # V2 ohne V1: kein Yii, keine V1-MySQL, kein Adminer
MAIL_MAILER=log         # eine Demo verschickt nichts nach aussen
# LICENSE_SERVER_URL / LICENSE_PUBLIC_KEY weglassen -> Lizenzpruefung inaktiv
# UPDATER_TOKEN weglassen -> kein Self-Update mitten in einer Vorfuehrung

Inhalt und Demo-Verhalten danach:

docker exec calserver-api-v2 php artisan demo:data seed --force --allow-production
# optional aus einer gespeicherten Vorlage:
docker exec calserver-api-v2 php artisan demo:data seed --template=<slug> --force --allow-production

Die drei Schalter demo_mode_enabled, show_login_credentials und auto_reseed_* sind derzeit nur ueber Administration > Demo-Daten bzw. die API erreichbar (DemoDataController::saveSettings), nicht ueber die Kommandozeile. Ohne show_login_credentials liefert /api/v2/demo-data/login-info bewusst null, und die Loginseite zeigt keine Zugaenge. Fuer eine unbeaufsichtigte Provisionierung fehlt hier ein Kommando; Vorschlag und Aufwand stehen im Flottenkonzept (Abschnitt 11).

Zurueckgesetzt wird eine Demo nicht von aussen: demo:data autoreseed laeuft alle 15 Minuten im Scheduler des API-Containers und stellt einen faelligen Freeze-Point wieder her oder seedet neu (ADR-008).

Wenn aus der Demo ein Kundensystem wird

Eine Demo darf einem Kunden zur Einarbeitung ueberlassen werden und danach produktiv weiterlaufen. Ab dem Moment, in dem der Kunde eigene Daten einpflegt, kippen drei Voreinstellungen ins Gegenteil:

  1. Selbstauffrischung aus. demo:data autoreseed stellt stur den Freeze-Point wieder her. Laeuft er ueber eine Einarbeitung, ist die Arbeit weg, ohne Fehlermeldung. Auto-Reseed abschalten und einen gesetzten Freeze-Point entfernen.
  2. Demo-Zugaenge weg. Die Demo-Benutzer haben oeffentlich dokumentierte Passwoerter. demo:data remove entfernt sie zusammen mit den Demodaten; sollen die Uebungsdaten stehen bleiben, muessen die Benutzer derzeit von Hand entfernt oder umgesetzt werden.
  3. Sicherung an. Die Aufgabe backup.run (Administration > Betrieb) einschalten und eine Wiederherstellung proben, bevor echte Daten entstehen.

Dazu: Demo-Modus aus (sonst kann der Kunde weder Benutzer anlegen noch Mail konfigurieren), MAIL_MAILER auf einen echten Zugang, und fuer den Produktivbetrieb Lizenzschluessel samt LICENSE_*-Variablen (ADR-040).

Die vollstaendige Uebergabe-Checkliste und das Phasenmodell (demo -> test -> prod) stehen im Konzept im Infrastruktur-Repo: calhelp/github-runner, docs/konzept-calserver-demo-provisionierung.md. Dort steht auch, wie die erste provisionierte Instanz den heutigen Demo-Server ersetzt, den promote-to-prod.yml deployt.

Cloud-Mandanten (provisioniert)

Ein Cloud-Mandant ist von Anfang an ein Kundensystem — der Provisionierer (gleiches Infrastruktur-Repo, Auftragsart mandant) legt ihn ohne den Demo-Umweg an. Gegenueber einer Demo-Instanz unterscheidet sich die Host-.env in fuenf Punkten:

  1. LICENSE_SERVER_URL und LICENSE_PUBLIC_KEY sind ab dem ersten Start gesetzt; den Lizenzschluessel spielt der Provisionierer nach dem Boot ueber PUT /api/v2/admin/license ein und prueft auf valid (Begruendung: docs/adr/2026-09-01-lizenzausstellung-fuer-cloud-mandanten.md).
  2. UPDATER_TOKEN ist gesetzt — deploy.sh schaltet damit den Updater-Sidecar (v2-updater) zu, Self-Update ueber Administration > Updates funktioniert ab Werk. UPDATE_CHANNEL=stable.
  3. Kein Demo-Modus, keine Demodaten, kein Auto-Reseed: die Basis kommt aus db:install --v1 (Systemdefaults, Mail-Templates, MetTrack-Feldvorlage, Belegnummern) statt aus demo:data seed.
  4. SUBSCRIPTION bleibt vorerst leer. Kein Seeder fuellt authitem_group.subscriptions — eine gesetzte SUBSCRIPTION naehme dem ersten vom Kunden angelegten Benutzer faktisch alle Rechte. Der gebuchte Plan steht als Anzeige-Claim in der Lizenz und als Label am Server; durchgesetzt wird er erst, wenn db:install --v1 die Plan-Token der Standardgruppen seedet (offener Punkt).
  5. MAIL_MAILER=log bleibt zunaechst stehen — ausgehende Mail konfiguriert der Kunde in der Oberflaeche (Administration > E-Mail). Die Uebergabe-Mail an den Lizenznehmer verschickt deshalb der Provisionierer, nicht die Instanz.