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)¶
v1 + v2 parallel¶
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:keysausfuehren. Der Befehl ueberschreibt bei--forcedas 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:
calserver-api-v2— Deployment + Service (Port 8081)calserver-frontend— Deployment + Service (Port 3000)- 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:
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:
- Selbstauffrischung aus.
demo:data autoreseedstellt 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. - Demo-Zugaenge weg. Die Demo-Benutzer haben oeffentlich
dokumentierte Passwoerter.
demo:data removeentfernt sie zusammen mit den Demodaten; sollen die Uebungsdaten stehen bleiben, muessen die Benutzer derzeit von Hand entfernt oder umgesetzt werden. - 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:
LICENSE_SERVER_URLundLICENSE_PUBLIC_KEYsind ab dem ersten Start gesetzt; den Lizenzschluessel spielt der Provisionierer nach dem Boot ueberPUT /api/v2/admin/licenseein und prueft aufvalid(Begruendung:docs/adr/2026-09-01-lizenzausstellung-fuer-cloud-mandanten.md).UPDATER_TOKENist gesetzt —deploy.shschaltet damit den Updater-Sidecar (v2-updater) zu, Self-Update ueber Administration > Updates funktioniert ab Werk.UPDATE_CHANNEL=stable.- Kein Demo-Modus, keine Demodaten, kein Auto-Reseed: die Basis kommt aus
db:install --v1(Systemdefaults, Mail-Templates, MetTrack-Feldvorlage, Belegnummern) statt ausdemo:data seed. SUBSCRIPTIONbleibt vorerst leer. Kein Seeder fuelltauthitem_group.subscriptions— eine gesetzteSUBSCRIPTIONnaehme 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, wenndb:install --v1die Plan-Token der Standardgruppen seedet (offener Punkt).MAIL_MAILER=logbleibt 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.