From fe6682e520af05c1cbf95fe2d2887c9f90e5a54e Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 13 Apr 2026 21:04:57 +0200 Subject: [PATCH] docs: update CLAUDE.md for deployment modes and admin merge - Document standalone vs multi-tenant deployment modes - Replace vendor references with SaaS admin - Update bootstrap phases (Phase 12 always runs, no VENDOR_SEED_ENABLED) - Update provisioning flow (no separate vendor user) - Remove VENDOR_SEED_ENABLED from dev compose reference Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 010848b..68f5fd9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -155,7 +155,7 @@ Separate Vite+React SPA replacing Logto's default sign-in page. Visually matches - 13 OAuth2 scopes on the Logto API resource (`https://api.cameleer.local`): 10 platform scopes + 3 server scopes (`server:admin`, `server:operator`, `server:viewer`), served to the frontend from `GET /platform/api/config` - Server scopes map to server RBAC roles via JWT `scope` claim (SaaS platform path) or `roles` claim (server-ui OIDC login path) - Org roles: `owner` -> `server:admin` + `tenant:manage`, `operator` -> `server:operator`, `viewer` -> `server:viewer` -- `saas-vendor` global role injected via `docker/vendor-seed.sh` (`VENDOR_SEED_ENABLED=true` in dev) — has `platform:admin` + all tenant scopes +- `saas-vendor` global role created by bootstrap Phase 12 and always assigned to the admin user — has `platform:admin` + all tenant scopes - Custom `JwtDecoder` in `SecurityConfig.java` — ES384 algorithm, `at+jwt` token type, split issuer-uri (string validation) / jwk-set-uri (Docker-internal fetch), audience validation (`https://api.cameleer.local`) - Logto Custom JWT (Phase 7b in bootstrap) injects a `roles` claim into access tokens based on org roles and global roles — this makes role data available to the server without Logto-specific code @@ -163,7 +163,7 @@ Separate Vite+React SPA replacing Logto's default sign-in page. Visually matches | Persona | Logto role | Key scope | Landing route | |---------|-----------|-----------|---------------| -| Vendor | `saas-vendor` (global) | `platform:admin` | `/vendor/tenants` | +| SaaS admin | `saas-vendor` (global) | `platform:admin` | `/vendor/tenants` | | Tenant admin | org `owner` | `tenant:manage` | `/tenant` (dashboard) | | Regular user (operator/viewer) | org member | `server:operator` or `server:viewer` | Redirected to server dashboard directly | @@ -255,31 +255,43 @@ Key files: ### Bootstrap (`docker/logto-bootstrap.sh`) -Idempotent script run via `logto-bootstrap` init container. **Clean slate** — no example tenant, no viewer user, no server configuration. Phases: +Idempotent script run inside the Logto container entrypoint. **Clean slate** — no example tenant, no viewer user, no server configuration. Phases: 1. Wait for Logto health (no server to wait for — servers are provisioned per-tenant) 2. Get Management API token (reads `m-default` secret from DB) 3. Create Logto apps (SPA, Traditional Web App with `skipConsent`, M2M with Management API role + server API role) 3b. Create API resource scopes (10 platform + 3 server scopes) 4. Create org roles (owner, operator, viewer with API resource scope assignments) + M2M server role (`cameleer-m2m-server` with `server:admin` scope) -5. Create admin user (platform owner with Logto console access) +5. Create admin user (SaaS admin with Logto console access) 7b. Configure Logto Custom JWT for access tokens (maps org roles -> `roles` claim: owner->server:admin, operator->server:operator, viewer->server:viewer; saas-vendor global role -> server:admin) 8. Configure Logto sign-in branding (Cameleer colors `#C6820E`/`#D4941E`, logo from `/platform/logo.svg`) 9. Cleanup seeded Logto apps 10. Write bootstrap results to `/data/logto-bootstrap.json` +12. Create `saas-vendor` global role with all API scopes and assign to admin user (always runs — admin IS the platform admin). -12. (Optional) Vendor seed: create `saas-vendor` global role, vendor user, grant Logto console access (`VENDOR_SEED_ENABLED=true` in dev). +The multi-tenant compose stack is: Traefik + PostgreSQL + ClickHouse + Logto (with bootstrap entrypoint) + cameleer-saas. No `cameleer3-server` or `cameleer3-server-ui` in compose — those are provisioned per-tenant by `DockerTenantProvisioner`. -The compose stack is: Traefik + traefik-certs (init) + PostgreSQL + ClickHouse + Logto + logto-bootstrap (init) + cameleer-saas. No `cameleer3-server` or `cameleer3-server-ui` in compose — those are provisioned per-tenant by `DockerTenantProvisioner`. +### Deployment Modes (installer) + +The installer (`installer/install.sh`) supports two deployment modes: + +| | Multi-tenant SaaS (`DEPLOYMENT_MODE=saas`) | Standalone (`DEPLOYMENT_MODE=standalone`) | +|---|---|---| +| **Containers** | traefik, postgres, clickhouse, logto, cameleer-saas | traefik, postgres, clickhouse, server, server-ui | +| **Auth** | Logto OIDC (SaaS admin + tenant users) | Local auth (built-in admin, no identity provider) | +| **Tenant management** | SaaS admin creates/manages tenants via UI | Single server instance, no fleet management | +| **PostgreSQL** | `cameleer-postgres` image (multi-DB init) | Stock `postgres:16-alpine` (server creates schema via Flyway) | +| **Use case** | Platform vendor managing multiple customers | Single customer running the product directly | + +Standalone mode generates a simpler compose with the server running directly. No Logto, no SaaS management plane, no bootstrap. The admin logs in with local credentials at `/`. ### Tenant Provisioning Flow -When vendor creates a tenant via `VendorTenantService`: +When SaaS admin creates a tenant via `VendorTenantService`: **Synchronous (in `createAndProvision`):** 1. Create `TenantEntity` (status=PROVISIONING) + Logto organization -2. Create admin user in Logto with owner org role -3. Add vendor user to new org for support access -4. Register OIDC redirect URIs for `/t/{slug}/oidc/callback` on Logto Traditional Web App +2. Create admin user in Logto with owner org role (if credentials provided) +3. Register OIDC redirect URIs for `/t/{slug}/oidc/callback` on Logto Traditional Web App 5. Generate license (tier-appropriate, 365 days) 6. Return immediately — UI shows provisioning spinner, polls via `refetchInterval` @@ -292,12 +304,12 @@ When vendor creates a tenant via `VendorTenantService`: 12. Push OIDC config (Traditional Web App credentials + `additionalScopes: [urn:logto:scope:organizations, urn:logto:scope:organization_roles]`) to server for SSO 13. Update tenant status -> ACTIVE (or set `provisionError` on failure) -**Server restart** (available to vendor + tenant admin): -- `POST /api/vendor/tenants/{id}/restart` (vendor) and `POST /api/tenant/server/restart` (tenant) +**Server restart** (available to SaaS admin + tenant admin): +- `POST /api/vendor/tenants/{id}/restart` (SaaS admin) and `POST /api/tenant/server/restart` (tenant) - Calls `TenantProvisioner.stop(slug)` then `start(slug)` — restarts server + UI containers only (same image) -**Server upgrade** (available to vendor + tenant admin): -- `POST /api/vendor/tenants/{id}/upgrade` (vendor) and `POST /api/tenant/server/upgrade` (tenant) +**Server upgrade** (available to SaaS admin + tenant admin): +- `POST /api/vendor/tenants/{id}/upgrade` (SaaS admin) and `POST /api/tenant/server/upgrade` (tenant) - Calls `TenantProvisioner.upgrade(slug)` — removes server + UI containers, force-pulls latest images (preserves app containers, volumes, networks), then `provisionAsync()` re-creates containers with the new image + pushes license + OIDC config **Tenant delete** cleanup: @@ -336,7 +348,7 @@ PostgreSQL (Flyway): `src/main/resources/db/migration/` - `cameleer3-server` / `cameleer3-server-ui` — provisioned per-tenant (not in compose, created by `DockerTenantProvisioner`) - `cameleer-runtime-base` — base image for deployed apps (agent JAR + JRE). CI downloads latest agent SNAPSHOT from Gitea Maven registry. Uses `CAMELEER_SERVER_RUNTIME_SERVERURL` env var (not CAMELEER_EXPORT_ENDPOINT). - Docker builds: `--no-cache`, `--provenance=false` for Gitea compatibility -- `docker-compose.dev.yml` — exposes ports for direct access, sets `SPRING_PROFILES_ACTIVE: dev`, `VENDOR_SEED_ENABLED: true`. Volume-mounts `./ui/dist` into the container so local UI builds are served without rebuilding the Docker image (`SPRING_WEB_RESOURCES_STATIC_LOCATIONS` overrides classpath). Adds Docker socket mount for tenant provisioning. +- `docker-compose.dev.yml` — exposes ports for direct access, sets `SPRING_PROFILES_ACTIVE: dev`. Volume-mounts `./ui/dist` into the container so local UI builds are served without rebuilding the Docker image (`SPRING_WEB_RESOURCES_STATIC_LOCATIONS` overrides classpath). Adds Docker socket mount for tenant provisioning. - Design system: import from `@cameleer/design-system` (Gitea npm registry) ## Disabled Skills @@ -346,7 +358,7 @@ PostgreSQL (Flyway): `src/main/resources/db/migration/` # GitNexus — Code Intelligence -This project is indexed by GitNexus as **cameleer-saas** (2468 symbols, 5458 relationships, 207 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **cameleer-saas** (2675 symbols, 5767 relationships, 224 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.