chore: rename cameleer3 to cameleer
Some checks failed
CI / build (push) Failing after 18s
CI / docker (push) Has been skipped

Rename Java packages from net.siegeln.cameleer3 to net.siegeln.cameleer,
update all references in workflows, Docker configs, docs, and bootstrap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-15 15:28:44 +02:00
parent 44a0e413e9
commit 63c194dab7
113 changed files with 6190 additions and 582 deletions

View File

@@ -111,17 +111,17 @@ jobs:
- name: Build and push runtime base image - name: Build and push runtime base image
run: | run: |
AGENT_VERSION=$(curl -sf "https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer3/cameleer3-agent/1.0-SNAPSHOT/maven-metadata.xml" \ AGENT_VERSION=$(curl -sf "https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer/cameleer-agent/1.0-SNAPSHOT/maven-metadata.xml" \
| sed -n 's/.*<value>\([^<]*\)<\/value>.*/\1/p' | tail -1) | sed -n 's/.*<value>\([^<]*\)<\/value>.*/\1/p' | tail -1)
echo "Agent version: $AGENT_VERSION" echo "Agent version: $AGENT_VERSION"
curl -sf -o docker/runtime-base/agent.jar \ curl -sf -o docker/runtime-base/agent.jar \
"https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer3/cameleer3-agent/1.0-SNAPSHOT/cameleer3-agent-${AGENT_VERSION}-shaded.jar" "https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer/cameleer-agent/1.0-SNAPSHOT/cameleer-agent-${AGENT_VERSION}-shaded.jar"
APPENDER_VERSION=$(curl -sf "https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer3/cameleer3-log-appender/1.0-SNAPSHOT/maven-metadata.xml" \ APPENDER_VERSION=$(curl -sf "https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer/cameleer-log-appender/1.0-SNAPSHOT/maven-metadata.xml" \
| sed -n 's/.*<value>\([^<]*\)<\/value>.*/\1/p' | tail -1) | sed -n 's/.*<value>\([^<]*\)<\/value>.*/\1/p' | tail -1)
echo "Log appender version: $APPENDER_VERSION" echo "Log appender version: $APPENDER_VERSION"
curl -sf -o docker/runtime-base/cameleer3-log-appender.jar \ curl -sf -o docker/runtime-base/cameleer-log-appender.jar \
"https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer3/cameleer3-log-appender/1.0-SNAPSHOT/cameleer3-log-appender-${APPENDER_VERSION}.jar" "https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer/cameleer-log-appender/1.0-SNAPSHOT/cameleer-log-appender-${APPENDER_VERSION}.jar"
ls -la docker/runtime-base/agent.jar docker/runtime-base/cameleer3-log-appender.jar ls -la docker/runtime-base/agent.jar docker/runtime-base/cameleer-log-appender.jar
TAGS="-t gitea.siegeln.net/cameleer/cameleer-runtime-base:${{ github.sha }}" TAGS="-t gitea.siegeln.net/cameleer/cameleer-runtime-base:${{ github.sha }}"
for TAG in $IMAGE_TAGS; do for TAG in $IMAGE_TAGS; do
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer-runtime-base:$TAG" TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer-runtime-base:$TAG"

7
.gitignore vendored
View File

@@ -22,7 +22,12 @@ Thumbs.db
# Worktrees # Worktrees
.worktrees/ .worktrees/
# Claude
.claude/
.superpowers/
.playwright-mcp/
.gitnexus
# Generated by postinstall from @cameleer/design-system # Generated by postinstall from @cameleer/design-system
ui/public/favicon.svg ui/public/favicon.svg
docker/runtime-base/agent.jar docker/runtime-base/agent.jar
.gitnexus

View File

@@ -4,18 +4,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project ## Project
Cameleer SaaS — **vendor management plane** for the Cameleer observability stack. Two personas: **vendor** (platform:admin) manages the platform and provisions tenants; **tenant admin** (tenant:manage) manages their observability instance. The vendor creates tenants, which provisions per-tenant cameleer3-server + UI instances via Docker API. No example tenant — clean slate bootstrap, vendor creates everything. Cameleer SaaS — **vendor management plane** for the Cameleer observability stack. Two personas: **vendor** (platform:admin) manages the platform and provisions tenants; **tenant admin** (tenant:manage) manages their observability instance. The vendor creates tenants, which provisions per-tenant cameleer-server + UI instances via Docker API. No example tenant — clean slate bootstrap, vendor creates everything.
## Ecosystem ## Ecosystem
This repo is the SaaS layer on top of two proven components: This repo is the SaaS layer on top of two proven components:
- **cameleer3** (sibling repo) — Java agent using ByteBuddy for zero-code instrumentation of Camel apps. Captures route executions, processor traces, payloads, metrics, and route graph topology. Deploys as `-javaagent` JAR. - **cameleer** (sibling repo) — Java agent using ByteBuddy for zero-code instrumentation of Camel apps. Captures route executions, processor traces, payloads, metrics, and route graph topology. Deploys as `-javaagent` JAR.
- **cameleer3-server** (sibling repo) — Spring Boot observability backend. Receives agent data via HTTP, pushes config/commands via SSE. PostgreSQL + ClickHouse storage. React SPA dashboard. JWT auth with Ed25519 config signing. Docker container orchestration for app deployments. - **cameleer-server** (sibling repo) — Spring Boot observability backend. Receives agent data via HTTP, pushes config/commands via SSE. PostgreSQL + ClickHouse storage. React SPA dashboard. JWT auth with Ed25519 config signing. Docker container orchestration for app deployments.
- **cameleer-website** — Marketing site (Astro 5) - **cameleer-website** — Marketing site (Astro 5)
- **design-system** — Shared React component library (`@cameleer/design-system` on Gitea npm registry) - **design-system** — Shared React component library (`@cameleer/design-system` on Gitea npm registry)
Agent-server protocol is defined in `cameleer3/cameleer3-common/PROTOCOL.md`. The agent and server are mature, proven components — this repo wraps them with multi-tenancy, billing, and self-service onboarding. Agent-server protocol is defined in `cameleer/cameleer-common/PROTOCOL.md`. The agent and server are mature, proven components — this repo wraps them with multi-tenancy, billing, and self-service onboarding.
## Key Classes ## Key Classes
@@ -70,7 +70,7 @@ Agent-server protocol is defined in `cameleer3/cameleer3-common/PROTOCOL.md`. Th
**identity/** — Logto & server integration **identity/** — Logto & server integration
- `LogtoConfig.java` — Logto endpoint, M2M credentials (reads from bootstrap file) - `LogtoConfig.java` — Logto endpoint, M2M credentials (reads from bootstrap file)
- `LogtoManagementClient.java` — Logto Management API calls (create org, create user, add to org, get user, SSO connectors, JIT provisioning, password updates via `PATCH /api/users/{id}/password`) - `LogtoManagementClient.java` — Logto Management API calls (create org, create user, add to org, get user, SSO connectors, JIT provisioning, password updates via `PATCH /api/users/{id}/password`)
- `ServerApiClient.java` — M2M client for cameleer3-server API (Logto M2M token, `X-Cameleer-Protocol-Version: 1` header). Health checks, license/OIDC push, agent count, environment count, server admin password reset per tenant server. - `ServerApiClient.java` — M2M client for cameleer-server API (Logto M2M token, `X-Cameleer-Protocol-Version: 1` header). Health checks, license/OIDC push, agent count, environment count, server admin password reset per tenant server.
**audit/** — Audit logging **audit/** — Audit logging
- `AuditEntity.java` — JPA entity (actor_id, actor_email, tenant_id, action, resource, status) - `AuditEntity.java` — JPA entity (actor_id, actor_email, tenant_id, action, resource, status)
@@ -97,7 +97,7 @@ Agent-server protocol is defined in `cameleer3/cameleer3-common/PROTOCOL.md`. Th
## Architecture Context ## Architecture Context
The SaaS platform is a **vendor management plane**. It does not proxy requests to servers — instead it provisions dedicated per-tenant cameleer3-server instances via Docker API. Each tenant gets isolated server + UI containers with their own database schemas, networks, and Traefik routing. The SaaS platform is a **vendor management plane**. It does not proxy requests to servers — instead it provisions dedicated per-tenant cameleer-server instances via Docker API. Each tenant gets isolated server + UI containers with their own database schemas, networks, and Traefik routing.
### Routing (single-domain, path-based via Traefik) ### Routing (single-domain, path-based via Traefik)
@@ -141,7 +141,7 @@ Server containers join three networks: tenant network (primary), shared services
### Custom sign-in UI (`ui/sign-in/`) ### Custom sign-in UI (`ui/sign-in/`)
Separate Vite+React SPA replacing Logto's default sign-in page. Visually matches cameleer3-server LoginPage. Separate Vite+React SPA replacing Logto's default sign-in page. Visually matches cameleer-server LoginPage.
- Built as custom Logto Docker image (`cameleer-logto`): `ui/sign-in/Dockerfile` = node build stage + `FROM ghcr.io/logto-io/logto:latest` + COPY dist over `/etc/logto/packages/experience/dist/` - Built as custom Logto Docker image (`cameleer-logto`): `ui/sign-in/Dockerfile` = node build stage + `FROM ghcr.io/logto-io/logto:latest` + COPY dist over `/etc/logto/packages/experience/dist/`
- Uses `@cameleer/design-system` components (Card, Input, Button, FormField, Alert) - Uses `@cameleer/design-system` components (Card, Input, Button, FormField, Alert)
@@ -178,7 +178,7 @@ These env vars are injected into provisioned per-tenant server containers:
| Env var | Value | Purpose | | Env var | Value | Purpose |
|---------|-------|---------| |---------|-------|---------|
| `SPRING_DATASOURCE_URL` | `jdbc:postgresql://cameleer-postgres:5432/cameleer3?currentSchema=tenant_{slug}&ApplicationName=tenant_{slug}` | Per-tenant schema isolation + diagnostic query scoping | | `SPRING_DATASOURCE_URL` | `jdbc:postgresql://cameleer-postgres:5432/cameleer?currentSchema=tenant_{slug}&ApplicationName=tenant_{slug}` | Per-tenant schema isolation + diagnostic query scoping |
| `SPRING_DATASOURCE_USERNAME` | `tenant_{slug}` | Per-tenant PG user (owns only its schema) | | `SPRING_DATASOURCE_USERNAME` | `tenant_{slug}` | Per-tenant PG user (owns only its schema) |
| `SPRING_DATASOURCE_PASSWORD` | (generated, stored in `TenantEntity.dbPassword`) | Per-tenant PG password | | `SPRING_DATASOURCE_PASSWORD` | (generated, stored in `TenantEntity.dbPassword`) | Per-tenant PG password |
| `CAMELEER_SERVER_SECURITY_OIDCISSUERURI` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/oidc` | Token issuer claim validation | | `CAMELEER_SERVER_SECURITY_OIDCISSUERURI` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/oidc` | Token issuer claim validation |
@@ -188,7 +188,7 @@ These env vars are injected into provisioned per-tenant server containers:
| `CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` | Allow browser requests through Traefik | | `CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` | Allow browser requests through Traefik |
| `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | (generated) | Bootstrap auth token for M2M communication | | `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | (generated) | Bootstrap auth token for M2M communication |
| `CAMELEER_SERVER_RUNTIME_ENABLED` | `true` | Enable Docker orchestration | | `CAMELEER_SERVER_RUNTIME_ENABLED` | `true` | Enable Docker orchestration |
| `CAMELEER_SERVER_RUNTIME_SERVERURL` | `http://cameleer3-server-{slug}:8081` | Per-tenant server URL (DNS alias on tenant network) | | `CAMELEER_SERVER_RUNTIME_SERVERURL` | `http://cameleer-server-{slug}:8081` | Per-tenant server URL (DNS alias on tenant network) |
| `CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN` | `${PUBLIC_HOST}` | Domain for Traefik routing labels | | `CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN` | `${PUBLIC_HOST}` | Domain for Traefik routing labels |
| `CAMELEER_SERVER_RUNTIME_ROUTINGMODE` | `path` | `path` or `subdomain` routing | | `CAMELEER_SERVER_RUNTIME_ROUTINGMODE` | `path` | `path` or `subdomain` routing |
| `CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH` | `/data/jars` | Directory for uploaded JARs | | `CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH` | `/data/jars` | Directory for uploaded JARs |
@@ -240,7 +240,7 @@ The server's OIDC config (`OidcConfig`) includes `audience` (RFC 8707 resource i
### Deployment pipeline ### Deployment pipeline
App deployment is handled by the cameleer3-server's `DeploymentExecutor` (7-stage async flow): App deployment is handled by the cameleer-server's `DeploymentExecutor` (7-stage async flow):
1. PRE_FLIGHT — validate config, check JAR exists 1. PRE_FLIGHT — validate config, check JAR exists
2. PULL_IMAGE — pull base image if missing 2. PULL_IMAGE — pull base image if missing
3. CREATE_NETWORK — ensure cameleer-traefik and cameleer-env-{slug} networks 3. CREATE_NETWORK — ensure cameleer-traefik and cameleer-env-{slug} networks
@@ -250,8 +250,8 @@ App deployment is handled by the cameleer3-server's `DeploymentExecutor` (7-stag
7. COMPLETE — mark RUNNING or DEGRADED 7. COMPLETE — mark RUNNING or DEGRADED
Key files: Key files:
- `DeploymentExecutor.java` (in cameleer3-server) — async staged deployment - `DeploymentExecutor.java` (in cameleer-server) — async staged deployment
- `DockerRuntimeOrchestrator.java` (in cameleer3-server) — Docker client, container lifecycle - `DockerRuntimeOrchestrator.java` (in cameleer-server) — Docker client, container lifecycle
- `docker/runtime-base/Dockerfile` — base image with agent JAR, maps env vars to `-D` system properties - `docker/runtime-base/Dockerfile` — base image with agent JAR, maps env vars to `-D` system properties
- `ServerApiClient.java` — M2M token acquisition for SaaS->server API calls (agent status). Uses `X-Cameleer-Protocol-Version: 1` header - `ServerApiClient.java` — M2M token acquisition for SaaS->server API calls (agent status). Uses `X-Cameleer-Protocol-Version: 1` header
- Docker socket access: `group_add: ["0"]` in docker-compose.dev.yml (not root group membership in Dockerfile) - Docker socket access: `group_add: ["0"]` in docker-compose.dev.yml (not root group membership in Dockerfile)
@@ -272,7 +272,7 @@ Idempotent script run inside the Logto container entrypoint. **Clean slate** —
10. Write bootstrap results to `/data/logto-bootstrap.json` 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. Create `saas-vendor` global role with all API scopes and assign to admin user (always runs — admin IS the platform admin).
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 multi-tenant compose stack is: Traefik + PostgreSQL + ClickHouse + Logto (with bootstrap entrypoint) + cameleer-saas. No `cameleer-server` or `cameleer-server-ui` in compose — those are provisioned per-tenant by `DockerTenantProvisioner`.
### Deployment Modes (installer) ### Deployment Modes (installer)
@@ -340,7 +340,7 @@ PostgreSQL (Flyway): `src/main/resources/db/migration/`
- Docker images: CI builds and pushes all images — Dockerfiles use multi-stage builds, no local builds needed - Docker images: CI builds and pushes all images — Dockerfiles use multi-stage builds, no local builds needed
- `cameleer-saas` — SaaS vendor management plane (frontend + JAR baked in) - `cameleer-saas` — SaaS vendor management plane (frontend + JAR baked in)
- `cameleer-logto` — custom Logto with sign-in UI baked in - `cameleer-logto` — custom Logto with sign-in UI baked in
- `cameleer3-server` / `cameleer3-server-ui` — provisioned per-tenant (not in compose, created by `DockerTenantProvisioner`) - `cameleer-server` / `cameleer-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). - `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 builds: `--no-cache`, `--provenance=false` for Gitea compatibility
- `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. - `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.

View File

@@ -48,7 +48,7 @@ The platform runs as a Docker Compose stack:
*Ports exposed to host only with `docker-compose.dev.yml` overlay. *Ports exposed to host only with `docker-compose.dev.yml` overlay.
Per-tenant `cameleer3-server` and `cameleer3-server-ui` containers are provisioned dynamically by `DockerTenantProvisioner` — they are NOT part of the compose stack. Per-tenant `cameleer-server` and `cameleer-server-ui` containers are provisioned dynamically by `DockerTenantProvisioner` — they are NOT part of the compose stack.
## Installation ## Installation
@@ -222,7 +222,7 @@ To disable routing, set `exposedPort` to `null`.
### View the Observability Dashboard ### View the Observability Dashboard
The cameleer3-server React SPA dashboard is available at: The cameleer-server React SPA dashboard is available at:
``` ```
http://localhost/dashboard http://localhost/dashboard
@@ -233,7 +233,7 @@ This shows execution traces, route topology graphs, metrics, and logs for all de
### Check Agent & Observability Status ### Check Agent & Observability Status
```bash ```bash
# Is the agent registered with cameleer3-server? # Is the agent registered with cameleer-server?
curl "http://localhost:8080/api/apps/$APP_ID/agent-status" \ curl "http://localhost:8080/api/apps/$APP_ID/agent-status" \
-H "Authorization: Bearer $TOKEN" -H "Authorization: Bearer $TOKEN"
# Returns: registered, state (ACTIVE/STALE/DEAD/UNKNOWN), routeIds # Returns: registered, state (ACTIVE/STALE/DEAD/UNKNOWN), routeIds
@@ -303,7 +303,7 @@ Query params: `since`, `until` (ISO timestamps), `limit` (default 500), `stream`
### Dashboard ### Dashboard
| Path | Description | | Path | Description |
|------|-------------| |------|-------------|
| `/dashboard` | cameleer3-server observability dashboard (forward-auth protected) | | `/dashboard` | cameleer-server observability dashboard (forward-auth protected) |
### Vendor: Certificates (platform:admin) ### Vendor: Certificates (platform:admin)
| Method | Path | Description | | Method | Path | Description |
@@ -404,7 +404,7 @@ Output goes to `src/main/resources/static/` (configured in `vite.config.ts`). Th
### SPA Routing ### SPA Routing
Spring Boot serves `index.html` for all non-API routes via `SpaController.java`. React Router handles client-side routing. The SPA lives at `/`, while the observability dashboard (cameleer3-server) is at `/dashboard`. Spring Boot serves `index.html` for all non-API routes via `SpaController.java`. React Router handles client-side routing. The SPA lives at `/`, while the observability dashboard (cameleer-server) is at `/dashboard`.
## Development ## Development

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
audit/03-login-page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
audit/04-login-error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
audit/06-license-page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

BIN
audit/11-search-modal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
audit/21-sidebar-detail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -26,10 +26,10 @@
| Severity | Issue | Element | | Severity | Issue | Element |
|----------|-------|---------| |----------|-------|---------|
| Important | **No password visibility toggle** -- the Password input uses `type="password"` with no eye icon to reveal. Most modern login forms offer this. | Password field | | Important | **No password visibility toggle** -- the Password input uses `type="password"` with no eye icon to reveal. Most modern login forms offer this. | Password field |
| Important | **Branding says "cameleer3"** not "Cameleer" or "Cameleer SaaS" -- the product name on the login page is the internal repo name, not the user-facing brand | `.logo` text content | | Important | **Branding says "cameleer"** not "Cameleer" or "Cameleer SaaS" -- the product name on the login page is the internal repo name, not the user-facing brand | `.logo` text content |
| Nice-to-have | **No "Forgot password" link** -- even if it goes to a "contact admin" page, users expect this | Below password field | | Nice-to-have | **No "Forgot password" link** -- even if it goes to a "contact admin" page, users expect this | Below password field |
| Nice-to-have | **No Enter-key submit hint** -- though Enter does work via form submit, there's no visual affordance | Form area | | Nice-to-have | **No Enter-key submit hint** -- though Enter does work via form submit, there's no visual affordance | Form area |
| Nice-to-have | **Page title is "Sign in -- cameleer3"** -- should match product branding ("Cameleer SaaS") | `<title>` tag | | Nice-to-have | **Page title is "Sign in -- cameleer"** -- should match product branding ("Cameleer SaaS") | `<title>` tag |
--- ---
@@ -216,7 +216,7 @@
### Important (17) ### Important (17)
1. No password visibility toggle on login 1. No password visibility toggle on login
2. Branding says "cameleer3" instead of product name on login 2. Branding says "cameleer" instead of product name on login
3. Breadcrumbs always empty on platform pages 3. Breadcrumbs always empty on platform pages
4. Massive empty space below dashboard content 4. Massive empty space below dashboard content
5. Tier badge color mapping inconsistent between Dashboard and License pages 5. Tier badge color mapping inconsistent between Dashboard and License pages
@@ -235,7 +235,7 @@
### Nice-to-have (8) ### Nice-to-have (8)
1. No "Forgot password" link on login 1. No "Forgot password" link on login
2. Login page title uses "cameleer3" branding 2. Login page title uses "cameleer" branding
3. No external link icon on "Open Server Dashboard" 3. No external link icon on "Open Server Dashboard"
4. Avatar shows "AD" for "admin" 4. Avatar shows "AD" for "admin"
5. No units on limit values 5. No units on limit values

View File

@@ -356,7 +356,7 @@ These use **different tier names** (enterprise/pro/starter vs BUSINESS/HIGH/MID/
3. **Hardcoded branding** (`SignInPage.tsx:61`): 3. **Hardcoded branding** (`SignInPage.tsx:61`):
```tsx ```tsx
cameleer3 cameleer
``` ```
The brand name is hardcoded text, not sourced from configuration. The brand name is hardcoded text, not sourced from configuration.

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
audit/verify-02-license.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

4974
ci-docker-log.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -26,8 +26,8 @@ services:
SPRING_WEB_RESOURCES_STATIC_LOCATIONS: file:/app/static/,classpath:/static/ SPRING_WEB_RESOURCES_STATIC_LOCATIONS: file:/app/static/,classpath:/static/
CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost}
CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https} CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https}
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: gitea.siegeln.net/cameleer/cameleer3-server:${VERSION:-latest} CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: gitea.siegeln.net/cameleer/cameleer-server:${VERSION:-latest}
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: gitea.siegeln.net/cameleer/cameleer3-server-ui:${VERSION:-latest} CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: gitea.siegeln.net/cameleer/cameleer-server-ui:${VERSION:-latest}
CAMELEER_SAAS_PROVISIONING_NETWORKNAME: cameleer-saas_cameleer CAMELEER_SAAS_PROVISIONING_NETWORKNAME: cameleer-saas_cameleer
CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik

View File

@@ -3,7 +3,7 @@ set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE DATABASE logto; CREATE DATABASE logto;
CREATE DATABASE cameleer3; CREATE DATABASE cameleer;
GRANT ALL PRIVILEGES ON DATABASE logto TO $POSTGRES_USER; GRANT ALL PRIVILEGES ON DATABASE logto TO $POSTGRES_USER;
GRANT ALL PRIVILEGES ON DATABASE cameleer3 TO $POSTGRES_USER; GRANT ALL PRIVILEGES ON DATABASE cameleer TO $POSTGRES_USER;
EOSQL EOSQL

View File

@@ -4,7 +4,7 @@ set -e
# Cameleer SaaS — Bootstrap Script # Cameleer SaaS — Bootstrap Script
# Creates Logto apps, users, organizations, roles. # Creates Logto apps, users, organizations, roles.
# Seeds cameleer_saas DB with tenant, environment, license. # Seeds cameleer_saas DB with tenant, environment, license.
# Configures cameleer3-server OIDC. # Configures cameleer-server OIDC.
# Idempotent: checks existence before creating. # Idempotent: checks existence before creating.
LOGTO_ENDPOINT="${LOGTO_ENDPOINT:-http://cameleer-logto:3001}" LOGTO_ENDPOINT="${LOGTO_ENDPOINT:-http://cameleer-logto:3001}"
@@ -174,7 +174,7 @@ else
log "Created SPA app: $SPA_ID" log "Created SPA app: $SPA_ID"
fi fi
# --- Traditional Web App (for cameleer3-server OIDC) --- # --- Traditional Web App (for cameleer-server OIDC) ---
TRAD_ID=$(echo "$EXISTING_APPS" | jq -r ".[] | select(.name == \"$TRAD_APP_NAME\" and .type == \"Traditional\") | .id") TRAD_ID=$(echo "$EXISTING_APPS" | jq -r ".[] | select(.name == \"$TRAD_APP_NAME\" and .type == \"Traditional\") | .id")
TRAD_SECRET="" TRAD_SECRET=""
if [ -n "$TRAD_ID" ]; then if [ -n "$TRAD_ID" ]; then

View File

@@ -3,7 +3,7 @@ WORKDIR /app
# Agent JAR and log appender JAR are copied during CI build from Gitea Maven registry # Agent JAR and log appender JAR are copied during CI build from Gitea Maven registry
COPY agent.jar /app/agent.jar COPY agent.jar /app/agent.jar
COPY cameleer3-log-appender.jar /app/cameleer3-log-appender.jar COPY cameleer-log-appender.jar /app/cameleer-log-appender.jar
ENTRYPOINT exec java \ ENTRYPOINT exec java \
-Dcameleer.export.type=${CAMELEER_EXPORT_TYPE:-HTTP} \ -Dcameleer.export.type=${CAMELEER_EXPORT_TYPE:-HTTP} \

View File

@@ -15,12 +15,12 @@ infrastructure themselves.
The system comprises three components: The system comprises three components:
**Cameleer Agent** (`cameleer3` repo) -- A Java agent using ByteBuddy for **Cameleer Agent** (`cameleer` repo) -- A Java agent using ByteBuddy for
zero-code bytecode instrumentation. Captures route executions, processor traces, zero-code bytecode instrumentation. Captures route executions, processor traces,
payloads, metrics, and route graph topology. Deployed as a `-javaagent` JAR payloads, metrics, and route graph topology. Deployed as a `-javaagent` JAR
alongside the customer's application. alongside the customer's application.
**Cameleer Server** (`cameleer3-server` repo) -- A Spring Boot observability **Cameleer Server** (`cameleer-server` repo) -- A Spring Boot observability
backend. Receives telemetry from agents via HTTP, pushes configuration and backend. Receives telemetry from agents via HTTP, pushes configuration and
commands to agents via SSE. Stores data in PostgreSQL and ClickHouse. Provides commands to agents via SSE. Stores data in PostgreSQL and ClickHouse. Provides
a React SPA dashboard for direct observability access. JWT auth with Ed25519 a React SPA dashboard for direct observability access. JWT auth with Ed25519
@@ -50,7 +50,7 @@ logging. Serves a React SPA that wraps the full user experience.
| | /interaction) | | | /interaction) |
v v v v v v v v
+--------------+ +--------------+ +-----------+ +------------------+ +--------------+ +--------------+ +-----------+ +------------------+
| cameleer-saas| | cameleer-saas| | Logto | | cameleer3-server | | cameleer-saas| | cameleer-saas| | Logto | | cameleer-server |
| (API) | | (SPA) | | | | | | (API) | | (SPA) | | | | |
| :8080 | | :8080 | | :3001 | | :8081 | | :8080 | | :8080 | | :3001 | | :8081 |
+--------------+ +--------------+ +-----------+ +------------------+ +--------------+ +--------------+ +-----------+ +------------------+
@@ -80,14 +80,14 @@ logging. Serves a React SPA that wraps the full user experience.
| logto | `ghcr.io/logto-io/logto:latest` | 3001 | cameleer | OIDC identity provider | | logto | `ghcr.io/logto-io/logto:latest` | 3001 | cameleer | OIDC identity provider |
| logto-bootstrap | `postgres:16-alpine` (ephemeral) | -- | cameleer | One-shot bootstrap script | | logto-bootstrap | `postgres:16-alpine` (ephemeral) | -- | cameleer | One-shot bootstrap script |
| cameleer-saas | `gitea.siegeln.net/cameleer/cameleer-saas` | 8080 | cameleer | SaaS API + SPA serving | | cameleer-saas | `gitea.siegeln.net/cameleer/cameleer-saas` | 8080 | cameleer | SaaS API + SPA serving |
| cameleer3-server | `gitea.siegeln.net/cameleer/cameleer3-server`| 8081 | cameleer | Observability backend | | cameleer-server | `gitea.siegeln.net/cameleer/cameleer-server`| 8081 | cameleer | Observability backend |
| clickhouse | `clickhouse/clickhouse-server:latest` | 8123 | cameleer | Time-series telemetry storage | | clickhouse | `clickhouse/clickhouse-server:latest` | 8123 | cameleer | Time-series telemetry storage |
### Docker Network ### Docker Network
All services share a single Docker bridge network named `cameleer`. Customer app All services share a single Docker bridge network named `cameleer`. Customer app
containers are also attached to this network so agents can reach the containers are also attached to this network so agents can reach the
cameleer3-server. cameleer-server.
### Volumes ### Volumes
@@ -105,7 +105,7 @@ The shared PostgreSQL instance hosts three databases:
- `cameleer_saas` -- SaaS platform tables (tenants, environments, apps, etc.) - `cameleer_saas` -- SaaS platform tables (tenants, environments, apps, etc.)
- `logto` -- Logto identity provider data - `logto` -- Logto identity provider data
- `cameleer3` -- cameleer3-server operational data - `cameleer` -- cameleer-server operational data
The `docker/init-databases.sh` init script creates all three during first start. The `docker/init-databases.sh` init script creates all three during first start.
@@ -128,9 +128,9 @@ The `docker/init-databases.sh` init script creates all three during first start.
|--------------------|-----------------|------------------|----------------------|--------------------------------| |--------------------|-----------------|------------------|----------------------|--------------------------------|
| Logto user JWT | Logto | ES384 (asymmetric)| Any service via JWKS | SaaS UI users, server users | | Logto user JWT | Logto | ES384 (asymmetric)| Any service via JWKS | SaaS UI users, server users |
| Logto M2M JWT | Logto | ES384 (asymmetric)| Any service via JWKS | SaaS platform -> server calls | | Logto M2M JWT | Logto | ES384 (asymmetric)| Any service via JWKS | SaaS platform -> server calls |
| Server internal JWT| cameleer3-server| HS256 (symmetric) | Issuing server only | Agents (after registration) | | Server internal JWT| cameleer-server| HS256 (symmetric) | Issuing server only | Agents (after registration) |
| API key (opaque) | SaaS platform | N/A (SHA-256 hash)| cameleer3-server | Agent initial registration | | API key (opaque) | SaaS platform | N/A (SHA-256 hash)| cameleer-server | Agent initial registration |
| Ed25519 signature | cameleer3-server| EdDSA | Agent | Server -> agent command signing| | Ed25519 signature | cameleer-server| EdDSA | Agent | Server -> agent command signing|
### 3.3 Scope Model ### 3.3 Scope Model
@@ -183,7 +183,7 @@ the bootstrap script (`docker/logto-bootstrap.sh`):
4. `organization_id` claim in JWT resolves to internal tenant ID via 4. `organization_id` claim in JWT resolves to internal tenant ID via
`TenantIsolationInterceptor`. `TenantIsolationInterceptor`.
**SaaS platform -> cameleer3-server API (M2M):** **SaaS platform -> cameleer-server API (M2M):**
1. SaaS platform obtains Logto M2M token (`client_credentials` grant) via 1. SaaS platform obtains Logto M2M token (`client_credentials` grant) via
`LogtoManagementClient`. `LogtoManagementClient`.
@@ -191,7 +191,7 @@ the bootstrap script (`docker/logto-bootstrap.sh`):
3. Server validates via Logto JWKS (OIDC resource server support). 3. Server validates via Logto JWKS (OIDC resource server support).
4. Server grants ADMIN role to valid M2M tokens. 4. Server grants ADMIN role to valid M2M tokens.
**Agent -> cameleer3-server:** **Agent -> cameleer-server:**
1. Agent reads `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` environment variable (API key). 1. Agent reads `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` environment variable (API key).
2. Calls `POST /api/v1/agents/register` with the key as Bearer token. 2. Calls `POST /api/v1/agents/register` with the key as Bearer token.
@@ -458,9 +458,9 @@ Defined in `AuditAction.java`:
### 5.1 Server-Per-Tenant ### 5.1 Server-Per-Tenant
Each tenant gets a dedicated cameleer3-server instance. The SaaS platform Each tenant gets a dedicated cameleer-server instance. The SaaS platform
provisions and manages these servers. In the current Docker Compose topology, a provisions and manages these servers. In the current Docker Compose topology, a
single shared cameleer3-server is used for the default tenant. Production single shared cameleer-server is used for the default tenant. Production
deployments will run per-tenant servers as separate containers or K8s pods. deployments will run per-tenant servers as separate containers or K8s pods.
### 5.2 Customer App Deployment Flow ### 5.2 Customer App Deployment Flow
@@ -495,7 +495,7 @@ The deployment lifecycle is managed by `DeploymentService`:
|-----------------------------|----------------------------------------| |-----------------------------|----------------------------------------|
| `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | API key for agent registration | | `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` | API key for agent registration |
| `CAMELEER_EXPORT_TYPE` | `HTTP` | | `CAMELEER_EXPORT_TYPE` | `HTTP` |
| `CAMELEER_SERVER_RUNTIME_SERVERURL` | cameleer3-server internal URL | | `CAMELEER_SERVER_RUNTIME_SERVERURL` | cameleer-server internal URL |
| `CAMELEER_APPLICATION_ID` | App slug | | `CAMELEER_APPLICATION_ID` | App slug |
| `CAMELEER_ENVIRONMENT_ID` | Environment slug | | `CAMELEER_ENVIRONMENT_ID` | Environment slug |
| `CAMELEER_DISPLAY_NAME` | `{tenant}-{env}-{app}` | | `CAMELEER_DISPLAY_NAME` | `{tenant}-{env}-{app}` |
@@ -524,14 +524,14 @@ Configured via `RuntimeConfig`:
## 6. Agent-Server Protocol ## 6. Agent-Server Protocol
The agent-server protocol is defined in full in The agent-server protocol is defined in full in
`cameleer3/cameleer3-common/PROTOCOL.md`. This section summarizes the key `cameleer/cameleer-common/PROTOCOL.md`. This section summarizes the key
aspects relevant to the SaaS platform. aspects relevant to the SaaS platform.
### 6.1 Agent Registration ### 6.1 Agent Registration
1. Agent starts with `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` environment variable (an API key 1. Agent starts with `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` environment variable (an API key
generated by the SaaS platform, prefixed with `cmk_`). generated by the SaaS platform, prefixed with `cmk_`).
2. Agent calls `POST /api/v1/agents/register` on the cameleer3-server with the 2. Agent calls `POST /api/v1/agents/register` on the cameleer-server with the
API key as a Bearer token. API key as a Bearer token.
3. Server validates the key and returns: 3. Server validates the key and returns:
- HMAC JWT access token (short-lived, ~1 hour) - HMAC JWT access token (short-lived, ~1 hour)
@@ -744,7 +744,7 @@ leaks regardless of whether the request succeeded or failed.
|----------------------|-------------|------------------------------------| |----------------------|-------------|------------------------------------|
| Logto access token | ~1 hour | Configured in Logto, refreshed by SDK | | Logto access token | ~1 hour | Configured in Logto, refreshed by SDK |
| Logto refresh token | ~14 days | Used by `@logto/react` for silent refresh | | Logto refresh token | ~14 days | Used by `@logto/react` for silent refresh |
| Server agent JWT | ~1 hour | cameleer3-server `CAMELEER_JWT_SECRET` | | Server agent JWT | ~1 hour | cameleer-server `CAMELEER_JWT_SECRET` |
| Server refresh token | ~7 days | Agent re-registers when expired | | Server refresh token | ~7 days | Agent re-registers when expired |
### 8.4 Audit Logging ### 8.4 Audit Logging
@@ -876,22 +876,22 @@ state (`currentTenantId`). Provides `logout` and `signIn` callbacks.
| Variable | Default | Description | | Variable | Default | Description |
|-----------------------------------|------------------------------------|----------------------------------| |-----------------------------------|------------------------------------|----------------------------------|
| `CAMELEER_SAAS_PROVISIONING_SERVERIMAGE` | `gitea.siegeln.net/cameleer/cameleer3-server:latest` | Docker image for per-tenant server | | `CAMELEER_SAAS_PROVISIONING_SERVERIMAGE` | `gitea.siegeln.net/cameleer/cameleer-server:latest` | Docker image for per-tenant server |
| `CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE` | `gitea.siegeln.net/cameleer/cameleer3-server-ui:latest` | Docker image for per-tenant UI | | `CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE` | `gitea.siegeln.net/cameleer/cameleer-server-ui:latest` | Docker image for per-tenant UI |
| `CAMELEER_SAAS_PROVISIONING_NETWORKNAME` | `cameleer-saas_cameleer` | Shared services Docker network | | `CAMELEER_SAAS_PROVISIONING_NETWORKNAME` | `cameleer-saas_cameleer` | Shared services Docker network |
| `CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK` | `cameleer-traefik` | Traefik Docker network | | `CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK` | `cameleer-traefik` | Traefik Docker network |
| `CAMELEER_SAAS_PROVISIONING_PUBLICHOST` | `localhost` | Public hostname (same as infrastructure `PUBLIC_HOST`) | | `CAMELEER_SAAS_PROVISIONING_PUBLICHOST` | `localhost` | Public hostname (same as infrastructure `PUBLIC_HOST`) |
| `CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL` | `https` | Public protocol (same as infrastructure `PUBLIC_PROTOCOL`) | | `CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL` | `https` | Public protocol (same as infrastructure `PUBLIC_PROTOCOL`) |
| `CAMELEER_SAAS_PROVISIONING_DATASOURCEURL` | `jdbc:postgresql://cameleer-postgres:5432/cameleer3` | PostgreSQL URL passed to tenant servers | | `CAMELEER_SAAS_PROVISIONING_DATASOURCEURL` | `jdbc:postgresql://cameleer-postgres:5432/cameleer` | PostgreSQL URL passed to tenant servers |
| `CAMELEER_SAAS_PROVISIONING_CLICKHOUSEURL` | `jdbc:clickhouse://cameleer-clickhouse:8123/cameleer` | ClickHouse URL passed to tenant servers | | `CAMELEER_SAAS_PROVISIONING_CLICKHOUSEURL` | `jdbc:clickhouse://cameleer-clickhouse:8123/cameleer` | ClickHouse URL passed to tenant servers |
### 10.2 cameleer3-server (per-tenant) ### 10.2 cameleer-server (per-tenant)
Env vars injected into provisioned per-tenant server containers by `DockerTenantProvisioner`. All server properties use the `cameleer.server.*` prefix (env vars: `CAMELEER_SERVER_*`). Env vars injected into provisioned per-tenant server containers by `DockerTenantProvisioner`. All server properties use the `cameleer.server.*` prefix (env vars: `CAMELEER_SERVER_*`).
| Variable | Default / Value | Description | | Variable | Default / Value | Description |
|------------------------------|----------------------------------------------|----------------------------------| |------------------------------|----------------------------------------------|----------------------------------|
| `SPRING_DATASOURCE_URL` | `jdbc:postgresql://cameleer-postgres:5432/cameleer3` | PostgreSQL JDBC URL | | `SPRING_DATASOURCE_URL` | `jdbc:postgresql://cameleer-postgres:5432/cameleer` | PostgreSQL JDBC URL |
| `SPRING_DATASOURCE_USERNAME`| `cameleer` | PostgreSQL user | | `SPRING_DATASOURCE_USERNAME`| `cameleer` | PostgreSQL user |
| `SPRING_DATASOURCE_PASSWORD`| `cameleer_dev` | PostgreSQL password | | `SPRING_DATASOURCE_PASSWORD`| `cameleer_dev` | PostgreSQL password |
| `CAMELEER_SERVER_CLICKHOUSE_URL` | `jdbc:clickhouse://cameleer-clickhouse:8123/cameleer` | ClickHouse JDBC URL | | `CAMELEER_SERVER_CLICKHOUSE_URL` | `jdbc:clickhouse://cameleer-clickhouse:8123/cameleer` | ClickHouse JDBC URL |

View File

@@ -80,7 +80,7 @@ Note: Phase 9 (Frontend) can be developed in parallel with Phases 3-8, building
**PRD Sections:** 6 (Tenant Provisioning), 11 (Networking & Tenant Isolation) **PRD Sections:** 6 (Tenant Provisioning), 11 (Networking & Tenant Isolation)
**Gitea Epics:** #3 (Tenant Provisioning), #8 (Networking) **Gitea Epics:** #3 (Tenant Provisioning), #8 (Networking)
**Depends on:** Phase 2 **Depends on:** Phase 2
**Produces:** Automated tenant provisioning pipeline. Signup creates tenant → Flux HelmRelease generated → namespace provisioned → cameleer3-server deployed → PostgreSQL schema + OpenSearch index created → tenant ACTIVE. NetworkPolicies enforced. **Produces:** Automated tenant provisioning pipeline. Signup creates tenant → Flux HelmRelease generated → namespace provisioned → cameleer-server deployed → PostgreSQL schema + OpenSearch index created → tenant ACTIVE. NetworkPolicies enforced.
**Key deliverables:** **Key deliverables:**
- Provisioning state machine (idempotent, retryable) - Provisioning state machine (idempotent, retryable)
@@ -91,7 +91,7 @@ Note: Phase 9 (Frontend) can be developed in parallel with Phases 3-8, building
- Readiness checking (poll tenant server health) - Readiness checking (poll tenant server health)
- Tenant lifecycle operations (suspend, reactivate, delete) - Tenant lifecycle operations (suspend, reactivate, delete)
- K8s NetworkPolicy templates (default deny + allow rules) - K8s NetworkPolicy templates (default deny + allow rules)
- Helm chart for cameleer3-server tenant deployment - Helm chart for cameleer-server tenant deployment
--- ---
@@ -143,11 +143,11 @@ Note: Phase 9 (Frontend) can be developed in parallel with Phases 3-8, building
**PRD Sections:** 8 (Observability Integration) **PRD Sections:** 8 (Observability Integration)
**Gitea Epics:** #6 (Observability Integration), #13 (Exchange Replay — gating only) **Gitea Epics:** #6 (Observability Integration), #13 (Exchange Replay — gating only)
**Depends on:** Phase 3 (server already deployed per tenant), Phase 2 (license for feature gating) **Depends on:** Phase 3 (server already deployed per tenant), Phase 2 (license for feature gating)
**Produces:** Tenants see their cameleer3-server UI embedded in the SaaS shell. API gateway routes to tenant server. MOAT features gated by license tier. **Produces:** Tenants see their cameleer-server UI embedded in the SaaS shell. API gateway routes to tenant server. MOAT features gated by license tier.
**Key deliverables:** **Key deliverables:**
- Ingress routing rules: `/t/{tenant}/api/*` → tenant's cameleer3-server - Ingress routing rules: `/t/{tenant}/api/*` → tenant's cameleer-server
- cameleer3-server "managed mode" configuration (trust SaaS JWT, report metrics) - cameleer-server "managed mode" configuration (trust SaaS JWT, report metrics)
- Bootstrap token generation API - Bootstrap token generation API
- MOAT feature gating via license (topology=all, lineage=limited/full, correlation=mid+, debugger=high+, replay=high+) - MOAT feature gating via license (topology=all, lineage=limited/full, correlation=mid+, debugger=high+, replay=high+)
- Server UI embedding approach (iframe or reverse proxy with path rewriting) - Server UI embedding approach (iframe or reverse proxy with path rewriting)
@@ -211,7 +211,7 @@ Note: Phase 9 (Frontend) can be developed in parallel with Phases 3-8, building
- SaaS shell (navigation, tenant switcher, user menu) - SaaS shell (navigation, tenant switcher, user menu)
- Dashboard (platform overview) - Dashboard (platform overview)
- Apps list + App deployment page (upload, config, secrets, status, logs, versions) - Apps list + App deployment page (upload, config, secrets, status, logs, versions)
- Observability section (embedded cameleer3-server UI) - Observability section (embedded cameleer-server UI)
- Team management pages - Team management pages
- Settings pages (tenant config, SSO/OIDC, vault connections) - Settings pages (tenant config, SSO/OIDC, vault connections)
- Billing pages (usage, invoices, plan management) - Billing pages (usage, invoices, plan management)

View File

@@ -2006,7 +2006,7 @@ available throughout request lifecycle."
**Files:** **Files:**
- Create: `src/main/java/net/siegeln/cameleer/saas/config/ForwardAuthController.java` - Create: `src/main/java/net/siegeln/cameleer/saas/config/ForwardAuthController.java`
This endpoint is called by Traefik's ForwardAuth middleware to validate requests routed to non-platform services (e.g., cameleer3-server). It validates the JWT, resolves the tenant, and returns tenant context headers. This endpoint is called by Traefik's ForwardAuth middleware to validate requests routed to non-platform services (e.g., cameleer-server). It validates the JWT, resolves the tenant, and returns tenant context headers.
- [ ] **Step 1: Create ForwardAuthController** - [ ] **Step 1: Create ForwardAuthController**
@@ -2455,8 +2455,8 @@ services:
networks: networks:
- cameleer - cameleer
cameleer3-server: cameleer-server:
image: ${CAMELEER3_SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer3-server}:${VERSION:-latest} image: ${CAMELEER_SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server}:${VERSION:-latest}
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
postgres: postgres:
@@ -2539,9 +2539,9 @@ git add docker-compose.yml docker-compose.dev.yml traefik.yml docker/init-databa
git commit -m "feat: add Docker Compose production stack with Traefik + Logto git commit -m "feat: add Docker Compose production stack with Traefik + Logto
7-container stack: Traefik (reverse proxy), PostgreSQL (shared), 7-container stack: Traefik (reverse proxy), PostgreSQL (shared),
Logto (identity), cameleer-saas (control plane), cameleer3-server Logto (identity), cameleer-saas (control plane), cameleer-server
(observability), ClickHouse (traces). ForwardAuth middleware for (observability), ClickHouse (traces). ForwardAuth middleware for
tenant-aware routing to cameleer3-server." tenant-aware routing to cameleer-server."
``` ```
--- ---

View File

@@ -2,7 +2,7 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Customers can upload a Camel JAR, the platform builds a container image with cameleer3 agent auto-injected, and deploys it to a logical environment with full lifecycle management. **Goal:** Customers can upload a Camel JAR, the platform builds a container image with cameleer agent auto-injected, and deploys it to a logical environment with full lifecycle management.
**Architecture:** Environment → App → Deployment entity hierarchy. `RuntimeOrchestrator` interface with `DockerRuntimeOrchestrator` (docker-java) implementation. Async deployment pipeline with status polling. Container logs streamed to ClickHouse. Pre-built `cameleer-runtime-base` image for fast (~1-3s) customer image builds. **Architecture:** Environment → App → Deployment entity hierarchy. `RuntimeOrchestrator` interface with `DockerRuntimeOrchestrator` (docker-java) implementation. Async deployment pipeline with status polling. Container logs streamed to ClickHouse. Pre-built `cameleer-runtime-base` image for fast (~1-3s) customer image builds.
@@ -164,8 +164,8 @@ public class RuntimeConfig {
@Value("${cameleer.runtime.bootstrap-token:${CAMELEER_AUTH_TOKEN:}}") @Value("${cameleer.runtime.bootstrap-token:${CAMELEER_AUTH_TOKEN:}}")
private String bootstrapToken; private String bootstrapToken;
@Value("${cameleer.runtime.cameleer3-server-endpoint:http://cameleer3-server:8081}") @Value("${cameleer.runtime.cameleer-server-endpoint:http://cameleer-server:8081}")
private String cameleer3ServerEndpoint; private String cameleerServerEndpoint;
public long getMaxJarSize() { return maxJarSize; } public long getMaxJarSize() { return maxJarSize; }
public String getJarStoragePath() { return jarStoragePath; } public String getJarStoragePath() { return jarStoragePath; }
@@ -177,7 +177,7 @@ public class RuntimeConfig {
public String getContainerMemoryLimit() { return containerMemoryLimit; } public String getContainerMemoryLimit() { return containerMemoryLimit; }
public int getContainerCpuShares() { return containerCpuShares; } public int getContainerCpuShares() { return containerCpuShares; }
public String getBootstrapToken() { return bootstrapToken; } public String getBootstrapToken() { return bootstrapToken; }
public String getCameleer3ServerEndpoint() { return cameleer3ServerEndpoint; } public String getCameleerServerEndpoint() { return cameleerServerEndpoint; }
public long parseMemoryLimitBytes() { public long parseMemoryLimitBytes() {
var limit = containerMemoryLimit.trim().toLowerCase(); var limit = containerMemoryLimit.trim().toLowerCase();
@@ -270,7 +270,7 @@ Append to the existing `cameleer:` section in `src/main/resources/application.ym
container-memory-limit: ${CAMELEER_CONTAINER_MEMORY_LIMIT:512m} container-memory-limit: ${CAMELEER_CONTAINER_MEMORY_LIMIT:512m}
container-cpu-shares: ${CAMELEER_CONTAINER_CPU_SHARES:512} container-cpu-shares: ${CAMELEER_CONTAINER_CPU_SHARES:512}
bootstrap-token: ${CAMELEER_AUTH_TOKEN:} bootstrap-token: ${CAMELEER_AUTH_TOKEN:}
cameleer3-server-endpoint: ${CAMELEER3_SERVER_ENDPOINT:http://cameleer3-server:8081} cameleer-server-endpoint: ${CAMELEER_SERVER_ENDPOINT:http://cameleer-server:8081}
clickhouse: clickhouse:
url: ${CLICKHOUSE_URL:jdbc:clickhouse://clickhouse:8123/cameleer} url: ${CLICKHOUSE_URL:jdbc:clickhouse://clickhouse:8123/cameleer}
``` ```
@@ -2788,7 +2788,7 @@ public class DeploymentService {
var envVars = Map.of( var envVars = Map.of(
"CAMELEER_AUTH_TOKEN", env.getBootstrapToken(), "CAMELEER_AUTH_TOKEN", env.getBootstrapToken(),
"CAMELEER_EXPORT_TYPE", "HTTP", "CAMELEER_EXPORT_TYPE", "HTTP",
"CAMELEER_EXPORT_ENDPOINT", runtimeConfig.getCameleer3ServerEndpoint(), "CAMELEER_EXPORT_ENDPOINT", runtimeConfig.getCameleerServerEndpoint(),
"CAMELEER_APPLICATION_ID", app.getSlug(), "CAMELEER_APPLICATION_ID", app.getSlug(),
"CAMELEER_ENVIRONMENT_ID", env.getSlug(), "CAMELEER_ENVIRONMENT_ID", env.getSlug(),
"CAMELEER_DISPLAY_NAME", containerName); "CAMELEER_DISPLAY_NAME", containerName);
@@ -3418,7 +3418,7 @@ volumes:
Add to the cameleer-saas service environment: Add to the cameleer-saas service environment:
```yaml ```yaml
CAMELEER_AUTH_TOKEN: ${CAMELEER_AUTH_TOKEN:-default-bootstrap-token} CAMELEER_AUTH_TOKEN: ${CAMELEER_AUTH_TOKEN:-default-bootstrap-token}
CAMELEER3_SERVER_ENDPOINT: http://cameleer3-server:8081 CAMELEER_SERVER_ENDPOINT: http://cameleer-server:8081
CLICKHOUSE_URL: jdbc:clickhouse://clickhouse:8123/cameleer CLICKHOUSE_URL: jdbc:clickhouse://clickhouse:8123/cameleer
``` ```
@@ -3427,7 +3427,7 @@ Add to the cameleer-saas service volumes:
- jardata:/data/jars - jardata:/data/jars
``` ```
Add `CAMELEER_AUTH_TOKEN` to the cameleer3-server service environment: Add `CAMELEER_AUTH_TOKEN` to the cameleer-server service environment:
```yaml ```yaml
CAMELEER_AUTH_TOKEN: ${CAMELEER_AUTH_TOKEN:-default-bootstrap-token} CAMELEER_AUTH_TOKEN: ${CAMELEER_AUTH_TOKEN:-default-bootstrap-token}
``` ```
@@ -3448,7 +3448,7 @@ FROM eclipse-temurin:21-jre-alpine
WORKDIR /app WORKDIR /app
# Agent JAR is copied during CI build from Gitea Maven registry # Agent JAR is copied during CI build from Gitea Maven registry
# ARG AGENT_JAR=cameleer3-agent-1.0-SNAPSHOT-shaded.jar # ARG AGENT_JAR=cameleer-agent-1.0-SNAPSHOT-shaded.jar
COPY agent.jar /app/agent.jar COPY agent.jar /app/agent.jar
ENTRYPOINT exec java \ ENTRYPOINT exec java \

View File

@@ -2,9 +2,9 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Complete the deploy → hit endpoint → see traces loop. Serve the existing cameleer3-server dashboard, add agent connectivity verification, enable optional inbound HTTP routing for customer apps, and wire up observability data health checks. **Goal:** Complete the deploy → hit endpoint → see traces loop. Serve the existing cameleer-server dashboard, add agent connectivity verification, enable optional inbound HTTP routing for customer apps, and wire up observability data health checks.
**Architecture:** Wiring phase — cameleer3-server already has full observability. Phase 4 adds Traefik routing for the dashboard + customer app endpoints, new API endpoints in cameleer-saas for agent-status and observability-status, and configures `CAMELEER_TENANT_ID` on the server. **Architecture:** Wiring phase — cameleer-server already has full observability. Phase 4 adds Traefik routing for the dashboard + customer app endpoints, new API endpoints in cameleer-saas for agent-status and observability-status, and configures `CAMELEER_TENANT_ID` on the server.
**Tech Stack:** Spring Boot 3.4.3, docker-java 3.4.1, ClickHouse JDBC, Traefik v3 labels, Spring RestClient **Tech Stack:** Spring Boot 3.4.3, docker-java 3.4.1, ClickHouse JDBC, Traefik v3 labels, Spring RestClient
@@ -14,7 +14,7 @@
### New Files ### New Files
- `src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusService.java` — Queries cameleer3-server for agent registration - `src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusService.java` — Queries cameleer-server for agent registration
- `src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusController.java` — Agent status + observability status endpoints - `src/main/java/net/siegeln/cameleer/saas/observability/AgentStatusController.java` — Agent status + observability status endpoints
- `src/main/java/net/siegeln/cameleer/saas/observability/dto/AgentStatusResponse.java` — Response DTO - `src/main/java/net/siegeln/cameleer/saas/observability/dto/AgentStatusResponse.java` — Response DTO
- `src/main/java/net/siegeln/cameleer/saas/observability/dto/ObservabilityStatusResponse.java` — Response DTO - `src/main/java/net/siegeln/cameleer/saas/observability/dto/ObservabilityStatusResponse.java` — Response DTO
@@ -359,7 +359,7 @@ class AgentStatusServiceTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
when(runtimeConfig.getCameleer3ServerEndpoint()).thenReturn("http://cameleer3-server:8081"); when(runtimeConfig.getCameleerServerEndpoint()).thenReturn("http://cameleer-server:8081");
agentStatusService = new AgentStatusService(appRepository, environmentRepository, runtimeConfig); agentStatusService = new AgentStatusService(appRepository, environmentRepository, runtimeConfig);
} }
@@ -439,7 +439,7 @@ public class AgentStatusService {
this.environmentRepository = environmentRepository; this.environmentRepository = environmentRepository;
this.runtimeConfig = runtimeConfig; this.runtimeConfig = runtimeConfig;
this.restClient = RestClient.builder() this.restClient = RestClient.builder()
.baseUrl(runtimeConfig.getCameleer3ServerEndpoint()) .baseUrl(runtimeConfig.getCameleerServerEndpoint())
.build(); .build();
} }
@@ -475,7 +475,7 @@ public class AgentStatusService {
return new AgentStatusResponse(false, "NOT_REGISTERED", null, return new AgentStatusResponse(false, "NOT_REGISTERED", null,
List.of(), app.getSlug(), env.getSlug()); List.of(), app.getSlug(), env.getSlug());
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to query agent status from cameleer3-server: {}", e.getMessage()); log.warn("Failed to query agent status from cameleer-server: {}", e.getMessage());
return new AgentStatusResponse(false, "UNKNOWN", null, return new AgentStatusResponse(false, "UNKNOWN", null,
List.of(), app.getSlug(), env.getSlug()); List.of(), app.getSlug(), env.getSlug());
} }
@@ -651,28 +651,28 @@ public class ConnectivityHealthCheck {
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void verifyConnectivity() { public void verifyConnectivity() {
checkCameleer3Server(); checkCameleerServer();
} }
private void checkCameleer3Server() { private void checkCameleerServer() {
try { try {
var client = RestClient.builder() var client = RestClient.builder()
.baseUrl(runtimeConfig.getCameleer3ServerEndpoint()) .baseUrl(runtimeConfig.getCameleerServerEndpoint())
.build(); .build();
var response = client.get() var response = client.get()
.uri("/actuator/health") .uri("/actuator/health")
.retrieve() .retrieve()
.toBodilessEntity(); .toBodilessEntity();
if (response.getStatusCode().is2xxSuccessful()) { if (response.getStatusCode().is2xxSuccessful()) {
log.info("cameleer3-server connectivity: OK ({})", log.info("cameleer-server connectivity: OK ({})",
runtimeConfig.getCameleer3ServerEndpoint()); runtimeConfig.getCameleerServerEndpoint());
} else { } else {
log.warn("cameleer3-server connectivity: HTTP {} ({})", log.warn("cameleer-server connectivity: HTTP {} ({})",
response.getStatusCode(), runtimeConfig.getCameleer3ServerEndpoint()); response.getStatusCode(), runtimeConfig.getCameleerServerEndpoint());
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("cameleer3-server connectivity: FAILED ({}) - {}", log.warn("cameleer-server connectivity: FAILED ({}) - {}",
runtimeConfig.getCameleer3ServerEndpoint(), e.getMessage()); runtimeConfig.getCameleerServerEndpoint(), e.getMessage());
} }
} }
} }
@@ -686,7 +686,7 @@ Run: `mvn compile -B -q`
```bash ```bash
git add src/main/java/net/siegeln/cameleer/saas/observability/ConnectivityHealthCheck.java git add src/main/java/net/siegeln/cameleer/saas/observability/ConnectivityHealthCheck.java
git commit -m "feat: add cameleer3-server startup connectivity check" git commit -m "feat: add cameleer-server startup connectivity check"
``` ```
--- ---
@@ -700,7 +700,7 @@ git commit -m "feat: add cameleer3-server startup connectivity check"
- [ ] **Step 1: Update docker-compose.yml — add dashboard route and CAMELEER_TENANT_ID** - [ ] **Step 1: Update docker-compose.yml — add dashboard route and CAMELEER_TENANT_ID**
In the `cameleer3-server` service: In the `cameleer-server` service:
Add to environment section: Add to environment section:
```yaml ```yaml
@@ -774,7 +774,7 @@ git commit -m "docs: update HOWTO with observability dashboard, routing, and age
| Spec Requirement | Task | | Spec Requirement | Task |
|---|---| |---|---|
| Serve cameleer3-server dashboard via Traefik | Task 7 (dashboard Traefik labels) | | Serve cameleer-server dashboard via Traefik | Task 7 (dashboard Traefik labels) |
| CAMELEER_TENANT_ID configuration | Task 7 (docker-compose env) | | CAMELEER_TENANT_ID configuration | Task 7 (docker-compose env) |
| Agent connectivity verification endpoint | Task 4 (AgentStatusService + Controller) | | Agent connectivity verification endpoint | Task 4 (AgentStatusService + Controller) |
| Observability data health endpoint | Task 4 (ObservabilityStatusResponse) | | Observability data health endpoint | Task 4 (ObservabilityStatusResponse) |

View File

@@ -4,7 +4,7 @@
**Goal:** Build a React SPA for managing tenants, environments, apps, and deployments. All backend APIs exist — this is the UI layer. **Goal:** Build a React SPA for managing tenants, environments, apps, and deployments. All backend APIs exist — this is the UI layer.
**Architecture:** React 19 + Vite + React Router + Zustand + TanStack Query + @cameleer/design-system. Sidebar layout matching cameleer3-server SPA. Shared Logto OIDC session. RBAC on all actions. Lives in `ui/` directory, built into Spring Boot static resources. **Architecture:** React 19 + Vite + React Router + Zustand + TanStack Query + @cameleer/design-system. Sidebar layout matching cameleer-server SPA. Shared Logto OIDC session. RBAC on all actions. Lives in `ui/` directory, built into Spring Boot static resources.
**Tech Stack:** React 19, Vite 8, TypeScript, React Router 7, Zustand, TanStack React Query, @cameleer/design-system 0.1.31, Lucide React **Tech Stack:** React 19, Vite 8, TypeScript, React Router 7, Zustand, TanStack React Query, @cameleer/design-system 0.1.31, Lucide React
@@ -332,7 +332,7 @@ git commit -m "feat: scaffold React SPA with Vite, design system, and TypeScript
- [ ] **Step 1: Create auth-store.ts** - [ ] **Step 1: Create auth-store.ts**
Zustand store for auth state. Same localStorage keys as cameleer3-server SPA for SSO. Zustand store for auth state. Same localStorage keys as cameleer-server SPA for SSO.
```typescript ```typescript
import { create } from 'zustand'; import { create } from 'zustand';
@@ -1145,7 +1145,7 @@ git commit -m "feat: add SPA controller, Traefik route, CI frontend build, and H
|---|---| |---|---|
| Project scaffolding (Vite, React, TS, design system) | Task 1 | | Project scaffolding (Vite, React, TS, design system) | Task 1 |
| TypeScript API types | Task 1 | | TypeScript API types | Task 1 |
| Auth store (Zustand, same keys as cameleer3-server) | Task 2 | | Auth store (Zustand, same keys as cameleer-server) | Task 2 |
| Login / Logto OIDC redirect / callback | Task 2 | | Login / Logto OIDC redirect / callback | Task 2 |
| Protected route | Task 2 | | Protected route | Task 2 |
| API client with auth middleware | Task 3 | | API client with auth middleware | Task 3 |

View File

@@ -2,35 +2,35 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace the incoherent three-system auth in cameleer-saas with Logto-centric architecture, and add OIDC resource server support to cameleer3-server for M2M. **Goal:** Replace the incoherent three-system auth in cameleer-saas with Logto-centric architecture, and add OIDC resource server support to cameleer-server for M2M.
**Architecture:** Logto is the single identity provider for all humans. Spring OAuth2 Resource Server validates Logto JWTs in both the SaaS platform and cameleer3-server. Agents authenticate with per-environment API keys exchanged for server-issued JWTs. Ed25519 command signing is unchanged. Zero trust: every service validates tokens independently via JWKS. **Architecture:** Logto is the single identity provider for all humans. Spring OAuth2 Resource Server validates Logto JWTs in both the SaaS platform and cameleer-server. Agents authenticate with per-environment API keys exchanged for server-issued JWTs. Ed25519 command signing is unchanged. Zero trust: every service validates tokens independently via JWKS.
**Tech Stack:** Spring Boot 3.4, Spring Security OAuth2 Resource Server, Nimbus JOSE+JWT, Logto, React + @logto/react, Zustand, PostgreSQL, Flyway **Tech Stack:** Spring Boot 3.4, Spring Security OAuth2 Resource Server, Nimbus JOSE+JWT, Logto, React + @logto/react, Zustand, PostgreSQL, Flyway
**Spec:** `docs/superpowers/specs/2026-04-05-auth-overhaul-design.md` **Spec:** `docs/superpowers/specs/2026-04-05-auth-overhaul-design.md`
**Repos:** **Repos:**
- cameleer3-server: `C:\Users\Hendrik\Documents\projects\cameleer3-server` (Phase 1) - cameleer-server: `C:\Users\Hendrik\Documents\projects\cameleer-server` (Phase 1)
- cameleer-saas: `C:\Users\Hendrik\Documents\projects\cameleer-saas` (Phases 2-3) - cameleer-saas: `C:\Users\Hendrik\Documents\projects\cameleer-saas` (Phases 2-3)
- cameleer3 (agent): NO CHANGES - cameleer (agent): NO CHANGES
--- ---
## Phase 1: cameleer3-server — OIDC Resource Server Support ## Phase 1: cameleer-server — OIDC Resource Server Support
All Phase 1 work is in `C:\Users\Hendrik\Documents\projects\cameleer3-server`. All Phase 1 work is in `C:\Users\Hendrik\Documents\projects\cameleer-server`.
### Task 1: Add OAuth2 Resource Server dependency and config properties ### Task 1: Add OAuth2 Resource Server dependency and config properties
**Files:** **Files:**
- Modify: `cameleer3-server-app/pom.xml` - Modify: `cameleer-server-app/pom.xml`
- Modify: `cameleer3-server-app/src/main/resources/application.yml` - Modify: `cameleer-server-app/src/main/resources/application.yml`
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityProperties.java`
- [ ] **Step 1: Add dependency to pom.xml** - [ ] **Step 1: Add dependency to pom.xml**
In `cameleer3-server-app/pom.xml`, add after the `spring-boot-starter-security` dependency (around line 88): In `cameleer-server-app/pom.xml`, add after the `spring-boot-starter-security` dependency (around line 88):
```xml ```xml
<dependency> <dependency>
@@ -41,7 +41,7 @@ In `cameleer3-server-app/pom.xml`, add after the `spring-boot-starter-security`
- [ ] **Step 2: Add OIDC properties to application.yml** - [ ] **Step 2: Add OIDC properties to application.yml**
In `cameleer3-server-app/src/main/resources/application.yml`, add two new properties under the `security:` block (after line 52): In `cameleer-server-app/src/main/resources/application.yml`, add two new properties under the `security:` block (after line 52):
```yaml ```yaml
oidc-issuer-uri: ${CAMELEER_OIDC_ISSUER_URI:} oidc-issuer-uri: ${CAMELEER_OIDC_ISSUER_URI:}
@@ -50,7 +50,7 @@ In `cameleer3-server-app/src/main/resources/application.yml`, add two new proper
- [ ] **Step 3: Add fields to SecurityProperties.java** - [ ] **Step 3: Add fields to SecurityProperties.java**
In `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java`, add after the `jwtSecret` field (line 19): In `cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityProperties.java`, add after the `jwtSecret` field (line 19):
```java ```java
private String oidcIssuerUri; private String oidcIssuerUri;
@@ -64,13 +64,13 @@ public void setOidcAudience(String oidcAudience) { this.oidcAudience = oidcAudie
- [ ] **Step 4: Verify build compiles** - [ ] **Step 4: Verify build compiles**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && ./mvnw compile -pl cameleer3-server-app -q` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && ./mvnw compile -pl cameleer-server-app -q`
Expected: BUILD SUCCESS Expected: BUILD SUCCESS
- [ ] **Step 5: Commit** - [ ] **Step 5: Commit**
```bash ```bash
git add cameleer3-server-app/pom.xml cameleer3-server-app/src/main/resources/application.yml cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java git add cameleer-server-app/pom.xml cameleer-server-app/src/main/resources/application.yml cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityProperties.java
git commit -m "feat: add oauth2-resource-server dependency and OIDC config properties" git commit -m "feat: add oauth2-resource-server dependency and OIDC config properties"
``` ```
@@ -79,14 +79,14 @@ git commit -m "feat: add oauth2-resource-server dependency and OIDC config prope
### Task 2: Add conditional OIDC JwtDecoder bean ### Task 2: Add conditional OIDC JwtDecoder bean
**Files:** **Files:**
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityBeanConfig.java`
- [ ] **Step 1: Write the failing test** - [ ] **Step 1: Write the failing test**
Create `cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/OidcJwtDecoderBeanTest.java`: Create `cameleer-server-app/src/test/java/com/cameleer/server/app/security/OidcJwtDecoderBeanTest.java`:
```java ```java
package com.cameleer3.server.app.security; package com.cameleer.server.app.security;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
@@ -123,12 +123,12 @@ class OidcJwtDecoderBeanTest {
- [ ] **Step 2: Run test to verify it fails** - [ ] **Step 2: Run test to verify it fails**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && ./mvnw test -pl cameleer3-server-app -Dtest=OidcJwtDecoderBeanTest -q` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && ./mvnw test -pl cameleer-server-app -Dtest=OidcJwtDecoderBeanTest -q`
Expected: FAIL — method `oidcJwtDecoder` does not exist Expected: FAIL — method `oidcJwtDecoder` does not exist
- [ ] **Step 3: Add the oidcJwtDecoder method to SecurityBeanConfig** - [ ] **Step 3: Add the oidcJwtDecoder method to SecurityBeanConfig**
In `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java`, add these imports at the top: In `cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityBeanConfig.java`, add these imports at the top:
```java ```java
import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSAlgorithm;
@@ -216,13 +216,13 @@ Update the test to match: the test calls `config.oidcJwtDecoder(properties)` dir
- [ ] **Step 5: Run test to verify it passes** - [ ] **Step 5: Run test to verify it passes**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && ./mvnw test -pl cameleer3-server-app -Dtest=OidcJwtDecoderBeanTest -q` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && ./mvnw test -pl cameleer-server-app -Dtest=OidcJwtDecoderBeanTest -q`
Expected: PASS Expected: PASS
- [ ] **Step 6: Commit** - [ ] **Step 6: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/OidcJwtDecoderBeanTest.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityBeanConfig.java cameleer-server-app/src/test/java/com/cameleer/server/app/security/OidcJwtDecoderBeanTest.java
git commit -m "feat: add conditional OIDC JwtDecoder factory for Logto token validation" git commit -m "feat: add conditional OIDC JwtDecoder factory for Logto token validation"
``` ```
@@ -231,18 +231,18 @@ git commit -m "feat: add conditional OIDC JwtDecoder factory for Logto token val
### Task 3: Update JwtAuthenticationFilter with OIDC fallback ### Task 3: Update JwtAuthenticationFilter with OIDC fallback
**Files:** **Files:**
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java`
- [ ] **Step 1: Write the failing test** - [ ] **Step 1: Write the failing test**
Create `cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtAuthenticationFilterOidcTest.java`: Create `cameleer-server-app/src/test/java/com/cameleer/server/app/security/JwtAuthenticationFilterOidcTest.java`:
```java ```java
package com.cameleer3.server.app.security; package com.cameleer.server.app.security;
import com.cameleer3.server.core.agent.AgentRegistryService; import com.cameleer.server.core.agent.AgentRegistryService;
import com.cameleer3.server.core.security.InvalidTokenException; import com.cameleer.server.core.security.InvalidTokenException;
import com.cameleer3.server.core.security.JwtService; import com.cameleer.server.core.security.JwtService;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -369,19 +369,19 @@ class JwtAuthenticationFilterOidcTest {
- [ ] **Step 2: Run test to verify it fails** - [ ] **Step 2: Run test to verify it fails**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && ./mvnw test -pl cameleer3-server-app -Dtest=JwtAuthenticationFilterOidcTest -q` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && ./mvnw test -pl cameleer-server-app -Dtest=JwtAuthenticationFilterOidcTest -q`
Expected: FAIL — constructor doesn't accept 3 args Expected: FAIL — constructor doesn't accept 3 args
- [ ] **Step 3: Update JwtAuthenticationFilter** - [ ] **Step 3: Update JwtAuthenticationFilter**
Replace `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java` with: Replace `cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java` with:
```java ```java
package com.cameleer3.server.app.security; package com.cameleer.server.app.security;
import com.cameleer3.server.core.agent.AgentRegistryService; import com.cameleer.server.core.agent.AgentRegistryService;
import com.cameleer3.server.core.security.JwtService; import com.cameleer.server.core.security.JwtService;
import com.cameleer3.server.core.security.JwtService.JwtValidationResult; import com.cameleer.server.core.security.JwtService.JwtValidationResult;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@@ -508,13 +508,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
- [ ] **Step 4: Run tests** - [ ] **Step 4: Run tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && ./mvnw test -pl cameleer3-server-app -Dtest=JwtAuthenticationFilterOidcTest -q` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && ./mvnw test -pl cameleer-server-app -Dtest=JwtAuthenticationFilterOidcTest -q`
Expected: PASS (all 4 tests) Expected: PASS (all 4 tests)
- [ ] **Step 5: Commit** - [ ] **Step 5: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtAuthenticationFilterOidcTest.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java cameleer-server-app/src/test/java/com/cameleer/server/app/security/JwtAuthenticationFilterOidcTest.java
git commit -m "feat: add OIDC token fallback to JwtAuthenticationFilter" git commit -m "feat: add OIDC token fallback to JwtAuthenticationFilter"
``` ```
@@ -523,8 +523,8 @@ git commit -m "feat: add OIDC token fallback to JwtAuthenticationFilter"
### Task 4: Wire OIDC decoder into SecurityConfig ### Task 4: Wire OIDC decoder into SecurityConfig
**Files:** **Files:**
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java`
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityBeanConfig.java`
- [ ] **Step 1: Add OIDC decoder bean creation to SecurityBeanConfig** - [ ] **Step 1: Add OIDC decoder bean creation to SecurityBeanConfig**
@@ -595,13 +595,13 @@ import org.springframework.security.oauth2.jwt.JwtDecoder;
- [ ] **Step 3: Run existing tests** - [ ] **Step 3: Run existing tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && ./mvnw test -pl cameleer3-server-app -q` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && ./mvnw test -pl cameleer-server-app -q`
Expected: All existing tests PASS (no OIDC env vars set, decoder is null, filter behaves as before) Expected: All existing tests PASS (no OIDC env vars set, decoder is null, filter behaves as before)
- [ ] **Step 4: Commit** - [ ] **Step 4: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityBeanConfig.java
git commit -m "feat: wire optional OIDC JwtDecoder into security filter chain" git commit -m "feat: wire optional OIDC JwtDecoder into security filter chain"
``` ```
@@ -1685,9 +1685,9 @@ In `docker-compose.yml`, remove these two labels from `cameleer-saas` (lines 122
- traefik.http.services.forwardauth.loadbalancer.server.port=8080 - traefik.http.services.forwardauth.loadbalancer.server.port=8080
``` ```
- [ ] **Step 2: Remove ForwardAuth middleware from cameleer3-server** - [ ] **Step 2: Remove ForwardAuth middleware from cameleer-server**
In `docker-compose.yml`, remove the forward-auth middleware labels from `cameleer3-server` (lines 158-159): In `docker-compose.yml`, remove the forward-auth middleware labels from `cameleer-server` (lines 158-159):
```yaml ```yaml
- traefik.http.routers.observe.middlewares=forward-auth - traefik.http.routers.observe.middlewares=forward-auth
@@ -1719,7 +1719,7 @@ In `cameleer-saas` environment, remove:
CAMELEER_AUTH_TOKEN: ${CAMELEER_AUTH_TOKEN:-default-bootstrap-token} CAMELEER_AUTH_TOKEN: ${CAMELEER_AUTH_TOKEN:-default-bootstrap-token}
``` ```
In `cameleer3-server` environment, add: In `cameleer-server` environment, add:
```yaml ```yaml
CAMELEER_OIDC_ISSUER_URI: ${LOGTO_ISSUER_URI:-http://logto:3001/oidc} CAMELEER_OIDC_ISSUER_URI: ${LOGTO_ISSUER_URI:-http://logto:3001/oidc}
CAMELEER_OIDC_AUDIENCE: ${CAMELEER_OIDC_AUDIENCE:-https://api.cameleer.local} CAMELEER_OIDC_AUDIENCE: ${CAMELEER_OIDC_AUDIENCE:-https://api.cameleer.local}

View File

@@ -8,41 +8,41 @@
**Tech Stack:** Java 17, Spring Boot 3.4.3, PostgreSQL 16, Flyway, JUnit 5, Testcontainers, AssertJ **Tech Stack:** Java 17, Spring Boot 3.4.3, PostgreSQL 16, Flyway, JUnit 5, Testcontainers, AssertJ
**Repo:** `C:\Users\Hendrik\Documents\projects\cameleer3-server` **Repo:** `C:\Users\Hendrik\Documents\projects\cameleer-server`
--- ---
## File Map ## File Map
### New Files ### New Files
- `cameleer3-server-app/src/main/resources/db/migration/V2__claim_mapping.sql` - `cameleer-server-app/src/main/resources/db/migration/V2__claim_mapping.sql`
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingRule.java` - `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingRule.java`
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingRepository.java` - `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingRepository.java`
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingService.java` - `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingService.java`
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/AssignmentOrigin.java` - `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/AssignmentOrigin.java`
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresClaimMappingRepository.java` - `cameleer-server-app/src/main/java/com/cameleer/server/app/storage/PostgresClaimMappingRepository.java`
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ClaimMappingAdminController.java` - `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ClaimMappingAdminController.java`
- `cameleer3-server-app/src/test/java/com/cameleer3/server/core/rbac/ClaimMappingServiceTest.java` - `cameleer-server-app/src/test/java/com/cameleer/server/core/rbac/ClaimMappingServiceTest.java`
- `cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ClaimMappingAdminControllerIT.java` - `cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ClaimMappingAdminControllerIT.java`
- `cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/OidcOnlyModeIT.java` - `cameleer-server-app/src/test/java/com/cameleer/server/app/security/OidcOnlyModeIT.java`
### Modified Files ### Modified Files
- `cameleer3-server-app/src/main/resources/db/migration/V1__init.sql` — no changes (immutable) - `cameleer-server-app/src/main/resources/db/migration/V1__init.sql` — no changes (immutable)
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/rbac/RbacServiceImpl.java` — add origin-aware query methods - `cameleer-server-app/src/main/java/com/cameleer/server/app/rbac/RbacServiceImpl.java` — add origin-aware query methods
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresUserRepository.java` — add origin-aware queries - `cameleer-server-app/src/main/java/com/cameleer/server/app/storage/PostgresUserRepository.java` — add origin-aware queries
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/OidcAuthController.java` — replace syncOidcRoles with claim mapping - `cameleer-server-app/src/main/java/com/cameleer/server/app/security/OidcAuthController.java` — replace syncOidcRoles with claim mapping
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java` — disable internal token path in OIDC-only mode - `cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java` — disable internal token path in OIDC-only mode
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java` — conditional endpoint registration - `cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java` — conditional endpoint registration
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java` — disable in OIDC-only mode - `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/UserAdminController.java` — disable in OIDC-only mode
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryBeanConfig.java` — wire ClaimMappingService - `cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryBeanConfig.java` — wire ClaimMappingService
- `cameleer3-server-app/src/main/resources/application.yml` — no new properties needed (OIDC config already exists) - `cameleer-server-app/src/main/resources/application.yml` — no new properties needed (OIDC config already exists)
--- ---
### Task 1: Database Migration — Add Origin Tracking and Claim Mapping Rules ### Task 1: Database Migration — Add Origin Tracking and Claim Mapping Rules
**Files:** **Files:**
- Create: `cameleer3-server-app/src/main/resources/db/migration/V2__claim_mapping.sql` - Create: `cameleer-server-app/src/main/resources/db/migration/V2__claim_mapping.sql`
- [ ] **Step 1: Write the migration** - [ ] **Step 1: Write the migration**
@@ -90,14 +90,14 @@ CREATE INDEX idx_user_groups_origin ON user_groups(user_id, origin);
- [ ] **Step 2: Run migration to verify** - [ ] **Step 2: Run migration to verify**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn flyway:migrate -pl cameleer3-server-app -Dflyway.url=jdbc:postgresql://localhost:5432/cameleer3 -Dflyway.user=cameleer -Dflyway.password=cameleer_dev` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn flyway:migrate -pl cameleer-server-app -Dflyway.url=jdbc:postgresql://localhost:5432/cameleer -Dflyway.user=cameleer -Dflyway.password=cameleer_dev`
If no local PostgreSQL, verify syntax by running the existing test suite which uses Testcontainers. If no local PostgreSQL, verify syntax by running the existing test suite which uses Testcontainers.
- [ ] **Step 3: Commit** - [ ] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/resources/db/migration/V2__claim_mapping.sql git add cameleer-server-app/src/main/resources/db/migration/V2__claim_mapping.sql
git commit -m "feat: add claim mapping rules table and origin tracking to RBAC assignments" git commit -m "feat: add claim mapping rules table and origin tracking to RBAC assignments"
``` ```
@@ -106,14 +106,14 @@ git commit -m "feat: add claim mapping rules table and origin tracking to RBAC a
### Task 2: Core Domain — ClaimMappingRule, AssignmentOrigin, Repository Interface ### Task 2: Core Domain — ClaimMappingRule, AssignmentOrigin, Repository Interface
**Files:** **Files:**
- Create: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/AssignmentOrigin.java` - Create: `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/AssignmentOrigin.java`
- Create: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingRule.java` - Create: `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingRule.java`
- Create: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingRepository.java` - Create: `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingRepository.java`
- [ ] **Step 1: Create AssignmentOrigin enum** - [ ] **Step 1: Create AssignmentOrigin enum**
```java ```java
package com.cameleer3.server.core.rbac; package com.cameleer.server.core.rbac;
public enum AssignmentOrigin { public enum AssignmentOrigin {
direct, managed direct, managed
@@ -123,7 +123,7 @@ public enum AssignmentOrigin {
- [ ] **Step 2: Create ClaimMappingRule record** - [ ] **Step 2: Create ClaimMappingRule record**
```java ```java
package com.cameleer3.server.core.rbac; package com.cameleer.server.core.rbac;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
@@ -146,7 +146,7 @@ public record ClaimMappingRule(
- [ ] **Step 3: Create ClaimMappingRepository interface** - [ ] **Step 3: Create ClaimMappingRepository interface**
```java ```java
package com.cameleer3.server.core.rbac; package com.cameleer.server.core.rbac;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -164,9 +164,9 @@ public interface ClaimMappingRepository {
- [ ] **Step 4: Commit** - [ ] **Step 4: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/AssignmentOrigin.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/AssignmentOrigin.java
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingRule.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingRule.java
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingRepository.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingRepository.java
git commit -m "feat: add ClaimMappingRule domain model and repository interface" git commit -m "feat: add ClaimMappingRule domain model and repository interface"
``` ```
@@ -175,13 +175,13 @@ git commit -m "feat: add ClaimMappingRule domain model and repository interface"
### Task 3: Core Domain — ClaimMappingService ### Task 3: Core Domain — ClaimMappingService
**Files:** **Files:**
- Create: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingService.java` - Create: `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingService.java`
- Create: `cameleer3-server-app/src/test/java/com/cameleer3/server/core/rbac/ClaimMappingServiceTest.java` - Create: `cameleer-server-app/src/test/java/com/cameleer/server/core/rbac/ClaimMappingServiceTest.java`
- [ ] **Step 1: Write tests for ClaimMappingService** - [ ] **Step 1: Write tests for ClaimMappingService**
```java ```java
package com.cameleer3.server.core.rbac; package com.cameleer.server.core.rbac;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -300,13 +300,13 @@ class ClaimMappingServiceTest {
- [ ] **Step 2: Run tests to verify they fail** - [ ] **Step 2: Run tests to verify they fail**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=ClaimMappingServiceTest -Dsurefire.failIfNoSpecifiedTests=false` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=ClaimMappingServiceTest -Dsurefire.failIfNoSpecifiedTests=false`
Expected: Compilation error — ClaimMappingService does not exist yet. Expected: Compilation error — ClaimMappingService does not exist yet.
- [ ] **Step 3: Implement ClaimMappingService** - [ ] **Step 3: Implement ClaimMappingService**
```java ```java
package com.cameleer3.server.core.rbac; package com.cameleer.server.core.rbac;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -377,14 +377,14 @@ public class ClaimMappingService {
- [ ] **Step 4: Run tests to verify they pass** - [ ] **Step 4: Run tests to verify they pass**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=ClaimMappingServiceTest` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=ClaimMappingServiceTest`
Expected: All 7 tests PASS. Expected: All 7 tests PASS.
- [ ] **Step 5: Commit** - [ ] **Step 5: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/ClaimMappingService.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/ClaimMappingService.java
git add cameleer3-server-app/src/test/java/com/cameleer3/server/core/rbac/ClaimMappingServiceTest.java git add cameleer-server-app/src/test/java/com/cameleer/server/core/rbac/ClaimMappingServiceTest.java
git commit -m "feat: implement ClaimMappingService with equals/contains/regex matching" git commit -m "feat: implement ClaimMappingService with equals/contains/regex matching"
``` ```
@@ -393,15 +393,15 @@ git commit -m "feat: implement ClaimMappingService with equals/contains/regex ma
### Task 4: PostgreSQL Repository — ClaimMappingRepository Implementation ### Task 4: PostgreSQL Repository — ClaimMappingRepository Implementation
**Files:** **Files:**
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresClaimMappingRepository.java` - Create: `cameleer-server-app/src/main/java/com/cameleer/server/app/storage/PostgresClaimMappingRepository.java`
- [ ] **Step 1: Implement PostgresClaimMappingRepository** - [ ] **Step 1: Implement PostgresClaimMappingRepository**
```java ```java
package com.cameleer3.server.app.storage; package com.cameleer.server.app.storage;
import com.cameleer3.server.core.rbac.ClaimMappingRepository; import com.cameleer.server.core.rbac.ClaimMappingRepository;
import com.cameleer3.server.core.rbac.ClaimMappingRule; import com.cameleer.server.core.rbac.ClaimMappingRule;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List; import java.util.List;
@@ -479,7 +479,7 @@ public class PostgresClaimMappingRepository implements ClaimMappingRepository {
- [ ] **Step 2: Wire the bean in AgentRegistryBeanConfig (or a new RbacBeanConfig)** - [ ] **Step 2: Wire the bean in AgentRegistryBeanConfig (or a new RbacBeanConfig)**
Add to `cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryBeanConfig.java` (or create a new `RbacBeanConfig.java`): Add to `cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryBeanConfig.java` (or create a new `RbacBeanConfig.java`):
```java ```java
@Bean @Bean
@@ -496,8 +496,8 @@ public ClaimMappingService claimMappingService() {
- [ ] **Step 3: Commit** - [ ] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresClaimMappingRepository.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/storage/PostgresClaimMappingRepository.java
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryBeanConfig.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/config/AgentRegistryBeanConfig.java
git commit -m "feat: implement PostgresClaimMappingRepository and wire beans" git commit -m "feat: implement PostgresClaimMappingRepository and wire beans"
``` ```
@@ -506,11 +506,11 @@ git commit -m "feat: implement PostgresClaimMappingRepository and wire beans"
### Task 5: Modify RbacServiceImpl — Origin-Aware Assignments ### Task 5: Modify RbacServiceImpl — Origin-Aware Assignments
**Files:** **Files:**
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/rbac/RbacServiceImpl.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/rbac/RbacServiceImpl.java`
- [ ] **Step 1: Add managed assignment methods to RbacService interface** - [ ] **Step 1: Add managed assignment methods to RbacService interface**
In `cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/RbacService.java`, add: In `cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/RbacService.java`, add:
```java ```java
void clearManagedAssignments(String userId); void clearManagedAssignments(String userId);
@@ -592,14 +592,14 @@ public List<RoleSummary> getDirectRolesForUser(String userId) {
- [ ] **Step 5: Run existing tests** - [ ] **Step 5: Run existing tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app`
Expected: All existing tests still pass (migration adds columns with defaults). Expected: All existing tests still pass (migration adds columns with defaults).
- [ ] **Step 6: Commit** - [ ] **Step 6: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/rbac/RbacService.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/rbac/RbacService.java
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/rbac/RbacServiceImpl.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/rbac/RbacServiceImpl.java
git commit -m "feat: add origin-aware managed/direct assignment methods to RbacService" git commit -m "feat: add origin-aware managed/direct assignment methods to RbacService"
``` ```
@@ -608,7 +608,7 @@ git commit -m "feat: add origin-aware managed/direct assignment methods to RbacS
### Task 6: Modify OidcAuthController — Replace syncOidcRoles with Claim Mapping ### Task 6: Modify OidcAuthController — Replace syncOidcRoles with Claim Mapping
**Files:** **Files:**
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/OidcAuthController.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/security/OidcAuthController.java`
- [ ] **Step 1: Inject ClaimMappingService and ClaimMappingRepository** - [ ] **Step 1: Inject ClaimMappingService and ClaimMappingRepository**
@@ -676,13 +676,13 @@ Note: `extractAllClaims` needs to be added to `OidcTokenExchanger` — it return
- [ ] **Step 4: Run existing tests** - [ ] **Step 4: Run existing tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app`
Expected: PASS (OIDC tests may need adjustment if they test syncOidcRoles directly). Expected: PASS (OIDC tests may need adjustment if they test syncOidcRoles directly).
- [ ] **Step 5: Commit** - [ ] **Step 5: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/OidcAuthController.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/security/OidcAuthController.java
git commit -m "feat: replace syncOidcRoles with claim mapping evaluation on OIDC login" git commit -m "feat: replace syncOidcRoles with claim mapping evaluation on OIDC login"
``` ```
@@ -691,8 +691,8 @@ git commit -m "feat: replace syncOidcRoles with claim mapping evaluation on OIDC
### Task 7: OIDC-Only Mode — Disable Local Auth When OIDC Configured ### Task 7: OIDC-Only Mode — Disable Local Auth When OIDC Configured
**Files:** **Files:**
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java`
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java` - Modify: `cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java`
- [ ] **Step 1: Add isOidcEnabled() helper to SecurityConfig** - [ ] **Step 1: Add isOidcEnabled() helper to SecurityConfig**
@@ -760,15 +760,15 @@ public ResponseEntity<?> resetPassword(@PathVariable String userId, @RequestBody
- [ ] **Step 5: Run full test suite** - [ ] **Step 5: Run full test suite**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app`
Expected: PASS. Expected: PASS.
- [ ] **Step 6: Commit** - [ ] **Step 6: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/security/SecurityConfig.java
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtAuthenticationFilter.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/security/JwtAuthenticationFilter.java
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/UserAdminController.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/controller/UserAdminController.java
git commit -m "feat: disable local auth when OIDC is configured (resource server mode)" git commit -m "feat: disable local auth when OIDC is configured (resource server mode)"
``` ```
@@ -777,15 +777,15 @@ git commit -m "feat: disable local auth when OIDC is configured (resource server
### Task 8: Claim Mapping Admin Controller ### Task 8: Claim Mapping Admin Controller
**Files:** **Files:**
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ClaimMappingAdminController.java` - Create: `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ClaimMappingAdminController.java`
- [ ] **Step 1: Implement the controller** - [ ] **Step 1: Implement the controller**
```java ```java
package com.cameleer3.server.app.controller; package com.cameleer.server.app.controller;
import com.cameleer3.server.core.rbac.ClaimMappingRepository; import com.cameleer.server.core.rbac.ClaimMappingRepository;
import com.cameleer3.server.core.rbac.ClaimMappingRule; import com.cameleer.server.core.rbac.ClaimMappingRule;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -867,13 +867,13 @@ In `SecurityConfig.filterChain()`, the `/api/v1/admin/**` path already requires
- [ ] **Step 3: Run full test suite** - [ ] **Step 3: Run full test suite**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app`
Expected: PASS. Expected: PASS.
- [ ] **Step 4: Commit** - [ ] **Step 4: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ClaimMappingAdminController.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/controller/ClaimMappingAdminController.java
git commit -m "feat: add ClaimMappingAdminController for CRUD on mapping rules" git commit -m "feat: add ClaimMappingAdminController for CRUD on mapping rules"
``` ```
@@ -882,14 +882,14 @@ git commit -m "feat: add ClaimMappingAdminController for CRUD on mapping rules"
### Task 9: Integration Test — Claim Mapping End-to-End ### Task 9: Integration Test — Claim Mapping End-to-End
**Files:** **Files:**
- Create: `cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ClaimMappingAdminControllerIT.java` - Create: `cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ClaimMappingAdminControllerIT.java`
- [ ] **Step 1: Write integration test** - [ ] **Step 1: Write integration test**
```java ```java
package com.cameleer3.server.app.controller; package com.cameleer.server.app.controller;
import com.cameleer3.server.app.AbstractPostgresIT; import com.cameleer.server.app.AbstractPostgresIT;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -954,13 +954,13 @@ class ClaimMappingAdminControllerIT extends AbstractPostgresIT {
- [ ] **Step 2: Run integration tests** - [ ] **Step 2: Run integration tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=ClaimMappingAdminControllerIT` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=ClaimMappingAdminControllerIT`
Expected: PASS. Expected: PASS.
- [ ] **Step 3: Commit** - [ ] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ClaimMappingAdminControllerIT.java git add cameleer-server-app/src/test/java/com/cameleer/server/app/controller/ClaimMappingAdminControllerIT.java
git commit -m "test: add integration tests for claim mapping admin API" git commit -m "test: add integration tests for claim mapping admin API"
``` ```
@@ -970,12 +970,12 @@ git commit -m "test: add integration tests for claim mapping admin API"
- [ ] **Step 1: Run all tests** - [ ] **Step 1: Run all tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn clean verify` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn clean verify`
Expected: All tests PASS. Build succeeds. Expected: All tests PASS. Build succeeds.
- [ ] **Step 2: Verify migration applies cleanly on fresh database** - [ ] **Step 2: Verify migration applies cleanly on fresh database**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=AbstractPostgresIT` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=AbstractPostgresIT`
Expected: Testcontainers starts fresh PostgreSQL, Flyway applies V1 + V2, context loads. Expected: Testcontainers starts fresh PostgreSQL, Flyway applies V1 + V2, context loads.
- [ ] **Step 3: Commit any remaining fixes** - [ ] **Step 3: Commit any remaining fixes**

View File

@@ -8,37 +8,37 @@
**Tech Stack:** Java 17, Spring Boot 3.4.3, Ed25519 (JDK built-in), Nimbus JOSE JWT, JUnit 5, AssertJ **Tech Stack:** Java 17, Spring Boot 3.4.3, Ed25519 (JDK built-in), Nimbus JOSE JWT, JUnit 5, AssertJ
**Repo:** `C:\Users\Hendrik\Documents\projects\cameleer3-server` **Repo:** `C:\Users\Hendrik\Documents\projects\cameleer-server`
--- ---
## File Map ## File Map
### New Files ### New Files
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseInfo.java` - `cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java`
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseValidator.java` - `cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java`
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseGate.java` - `cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java`
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/Feature.java` - `cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java`
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/LicenseBeanConfig.java` - `cameleer-server-app/src/main/java/com/cameleer/server/app/config/LicenseBeanConfig.java`
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/LicenseAdminController.java` - `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/LicenseAdminController.java`
- `cameleer3-server-app/src/test/java/com/cameleer3/server/core/license/LicenseValidatorTest.java` - `cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java`
- `cameleer3-server-app/src/test/java/com/cameleer3/server/core/license/LicenseGateTest.java` - `cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java`
### Modified Files ### Modified Files
- `cameleer3-server-app/src/main/resources/application.yml` — add license config properties - `cameleer-server-app/src/main/resources/application.yml` — add license config properties
--- ---
### Task 1: Core Domain — LicenseInfo, Feature Enum ### Task 1: Core Domain — LicenseInfo, Feature Enum
**Files:** **Files:**
- Create: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/Feature.java` - Create: `cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java`
- Create: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseInfo.java` - Create: `cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java`
- [ ] **Step 1: Create Feature enum** - [ ] **Step 1: Create Feature enum**
```java ```java
package com.cameleer3.server.core.license; package com.cameleer.server.core.license;
public enum Feature { public enum Feature {
topology, topology,
@@ -52,7 +52,7 @@ public enum Feature {
- [ ] **Step 2: Create LicenseInfo record** - [ ] **Step 2: Create LicenseInfo record**
```java ```java
package com.cameleer3.server.core.license; package com.cameleer.server.core.license;
import java.time.Instant; import java.time.Instant;
import java.util.Map; import java.util.Map;
@@ -87,8 +87,8 @@ public record LicenseInfo(
- [ ] **Step 3: Commit** - [ ] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/Feature.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/license/Feature.java
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseInfo.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseInfo.java
git commit -m "feat: add LicenseInfo and Feature domain model" git commit -m "feat: add LicenseInfo and Feature domain model"
``` ```
@@ -97,13 +97,13 @@ git commit -m "feat: add LicenseInfo and Feature domain model"
### Task 2: LicenseValidator — Ed25519 JWT Verification ### Task 2: LicenseValidator — Ed25519 JWT Verification
**Files:** **Files:**
- Create: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseValidator.java` - Create: `cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java`
- Create: `cameleer3-server-app/src/test/java/com/cameleer3/server/core/license/LicenseValidatorTest.java` - Create: `cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java`
- [ ] **Step 1: Write tests** - [ ] **Step 1: Write tests**
```java ```java
package com.cameleer3.server.core.license; package com.cameleer.server.core.license;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -194,13 +194,13 @@ class LicenseValidatorTest {
- [ ] **Step 2: Run tests to verify they fail** - [ ] **Step 2: Run tests to verify they fail**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=LicenseValidatorTest -Dsurefire.failIfNoSpecifiedTests=false` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=LicenseValidatorTest -Dsurefire.failIfNoSpecifiedTests=false`
Expected: Compilation error — LicenseValidator does not exist. Expected: Compilation error — LicenseValidator does not exist.
- [ ] **Step 3: Implement LicenseValidator** - [ ] **Step 3: Implement LicenseValidator**
```java ```java
package com.cameleer3.server.core.license; package com.cameleer.server.core.license;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@@ -298,14 +298,14 @@ public class LicenseValidator {
- [ ] **Step 4: Run tests** - [ ] **Step 4: Run tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=LicenseValidatorTest` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=LicenseValidatorTest`
Expected: All 3 tests PASS. Expected: All 3 tests PASS.
- [ ] **Step 5: Commit** - [ ] **Step 5: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseValidator.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseValidator.java
git add cameleer3-server-app/src/test/java/com/cameleer3/server/core/license/LicenseValidatorTest.java git add cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseValidatorTest.java
git commit -m "feat: implement LicenseValidator with Ed25519 signature verification" git commit -m "feat: implement LicenseValidator with Ed25519 signature verification"
``` ```
@@ -314,13 +314,13 @@ git commit -m "feat: implement LicenseValidator with Ed25519 signature verificat
### Task 3: LicenseGate — Feature Check Service ### Task 3: LicenseGate — Feature Check Service
**Files:** **Files:**
- Create: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseGate.java` - Create: `cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java`
- Create: `cameleer3-server-app/src/test/java/com/cameleer3/server/core/license/LicenseGateTest.java` - Create: `cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java`
- [ ] **Step 1: Write tests** - [ ] **Step 1: Write tests**
```java ```java
package com.cameleer3.server.core.license; package com.cameleer.server.core.license;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -366,7 +366,7 @@ class LicenseGateTest {
- [ ] **Step 2: Implement LicenseGate** - [ ] **Step 2: Implement LicenseGate**
```java ```java
package com.cameleer3.server.core.license; package com.cameleer.server.core.license;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -405,14 +405,14 @@ public class LicenseGate {
- [ ] **Step 3: Run tests** - [ ] **Step 3: Run tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest=LicenseGateTest` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app -Dtest=LicenseGateTest`
Expected: PASS. Expected: PASS.
- [ ] **Step 4: Commit** - [ ] **Step 4: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/license/LicenseGate.java git add cameleer-server-core/src/main/java/com/cameleer/server/core/license/LicenseGate.java
git add cameleer3-server-app/src/test/java/com/cameleer3/server/core/license/LicenseGateTest.java git add cameleer-server-app/src/test/java/com/cameleer/server/core/license/LicenseGateTest.java
git commit -m "feat: implement LicenseGate for feature checking" git commit -m "feat: implement LicenseGate for feature checking"
``` ```
@@ -421,8 +421,8 @@ git commit -m "feat: implement LicenseGate for feature checking"
### Task 4: License Loading — Bean Config and Startup ### Task 4: License Loading — Bean Config and Startup
**Files:** **Files:**
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/LicenseBeanConfig.java` - Create: `cameleer-server-app/src/main/java/com/cameleer/server/app/config/LicenseBeanConfig.java`
- Modify: `cameleer3-server-app/src/main/resources/application.yml` - Modify: `cameleer-server-app/src/main/resources/application.yml`
- [ ] **Step 1: Add license config properties to application.yml** - [ ] **Step 1: Add license config properties to application.yml**
@@ -436,11 +436,11 @@ license:
- [ ] **Step 2: Implement LicenseBeanConfig** - [ ] **Step 2: Implement LicenseBeanConfig**
```java ```java
package com.cameleer3.server.app.config; package com.cameleer.server.app.config;
import com.cameleer3.server.core.license.LicenseGate; import com.cameleer.server.core.license.LicenseGate;
import com.cameleer3.server.core.license.LicenseInfo; import com.cameleer.server.core.license.LicenseInfo;
import com.cameleer3.server.core.license.LicenseValidator; import com.cameleer.server.core.license.LicenseValidator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -509,8 +509,8 @@ public class LicenseBeanConfig {
- [ ] **Step 3: Commit** - [ ] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/LicenseBeanConfig.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/config/LicenseBeanConfig.java
git add cameleer3-server-app/src/main/resources/application.yml git add cameleer-server-app/src/main/resources/application.yml
git commit -m "feat: add license loading at startup from env var or file" git commit -m "feat: add license loading at startup from env var or file"
``` ```
@@ -519,16 +519,16 @@ git commit -m "feat: add license loading at startup from env var or file"
### Task 5: License Admin API — Runtime License Update ### Task 5: License Admin API — Runtime License Update
**Files:** **Files:**
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/LicenseAdminController.java` - Create: `cameleer-server-app/src/main/java/com/cameleer/server/app/controller/LicenseAdminController.java`
- [ ] **Step 1: Implement controller** - [ ] **Step 1: Implement controller**
```java ```java
package com.cameleer3.server.app.controller; package com.cameleer.server.app.controller;
import com.cameleer3.server.core.license.LicenseGate; import com.cameleer.server.core.license.LicenseGate;
import com.cameleer3.server.core.license.LicenseInfo; import com.cameleer.server.core.license.LicenseInfo;
import com.cameleer3.server.core.license.LicenseValidator; import com.cameleer.server.core.license.LicenseValidator;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -581,13 +581,13 @@ public class LicenseAdminController {
- [ ] **Step 2: Run full test suite** - [ ] **Step 2: Run full test suite**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn clean verify` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn clean verify`
Expected: PASS. Expected: PASS.
- [ ] **Step 3: Commit** - [ ] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/LicenseAdminController.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/controller/LicenseAdminController.java
git commit -m "feat: add license admin API for runtime license updates" git commit -m "feat: add license admin API for runtime license updates"
``` ```
@@ -611,5 +611,5 @@ public ResponseEntity<?> listDebugSessions() {
- [ ] **Step 2: Final verification** - [ ] **Step 2: Final verification**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn clean verify` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn clean verify`
Expected: All tests PASS. Expected: All tests PASS.

View File

@@ -1,6 +1,6 @@
# Plan 3: Runtime Management in the Server # Plan 3: Runtime Management in the Server
> **Status: COMPLETED** — Verified 2026-04-09. All runtime management fully ported to cameleer3-server with enhancements beyond the original plan. > **Status: COMPLETED** — Verified 2026-04-09. All runtime management fully ported to cameleer-server with enhancements beyond the original plan.
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
@@ -10,7 +10,7 @@
**Tech Stack:** Java 17, Spring Boot 3.4.3, docker-java (zerodep transport), PostgreSQL 16, Flyway, JUnit 5, Testcontainers **Tech Stack:** Java 17, Spring Boot 3.4.3, docker-java (zerodep transport), PostgreSQL 16, Flyway, JUnit 5, Testcontainers
**Repo:** `C:\Users\Hendrik\Documents\projects\cameleer3-server` **Repo:** `C:\Users\Hendrik\Documents\projects\cameleer-server`
**Source reference:** Code ported from `C:\Users\Hendrik\Documents\projects\cameleer-saas` (environment, app, deployment, runtime packages) **Source reference:** Code ported from `C:\Users\Hendrik\Documents\projects\cameleer-saas` (environment, app, deployment, runtime packages)
@@ -18,10 +18,10 @@
## File Map ## File Map
### New Files — Core Module (`cameleer3-server-core`) ### New Files — Core Module (`cameleer-server-core`)
``` ```
src/main/java/com/cameleer3/server/core/runtime/ src/main/java/com/cameleer/server/core/runtime/
├── Environment.java Record: id, slug, displayName, status, createdAt ├── Environment.java Record: id, slug, displayName, status, createdAt
├── EnvironmentStatus.java Enum: ACTIVE, SUSPENDED ├── EnvironmentStatus.java Enum: ACTIVE, SUSPENDED
├── EnvironmentRepository.java Interface: CRUD + findBySlug ├── EnvironmentRepository.java Interface: CRUD + findBySlug
@@ -42,10 +42,10 @@ src/main/java/com/cameleer3/server/core/runtime/
└── RoutingMode.java Enum: path, subdomain └── RoutingMode.java Enum: path, subdomain
``` ```
### New Files — App Module (`cameleer3-server-app`) ### New Files — App Module (`cameleer-server-app`)
``` ```
src/main/java/com/cameleer3/server/app/runtime/ src/main/java/com/cameleer/server/app/runtime/
├── DockerRuntimeOrchestrator.java Docker implementation using docker-java ├── DockerRuntimeOrchestrator.java Docker implementation using docker-java
├── DisabledRuntimeOrchestrator.java No-op implementation (observability-only mode) ├── DisabledRuntimeOrchestrator.java No-op implementation (observability-only mode)
├── RuntimeOrchestratorAutoConfig.java @Configuration: auto-detects Docker vs K8s vs disabled ├── RuntimeOrchestratorAutoConfig.java @Configuration: auto-detects Docker vs K8s vs disabled
@@ -53,13 +53,13 @@ src/main/java/com/cameleer3/server/app/runtime/
├── JarStorageService.java File-system JAR storage with versioning ├── JarStorageService.java File-system JAR storage with versioning
└── ContainerLogCollector.java Collects Docker container stdout/stderr └── ContainerLogCollector.java Collects Docker container stdout/stderr
src/main/java/com/cameleer3/server/app/storage/ src/main/java/com/cameleer/server/app/storage/
├── PostgresEnvironmentRepository.java ├── PostgresEnvironmentRepository.java
├── PostgresAppRepository.java ├── PostgresAppRepository.java
├── PostgresAppVersionRepository.java ├── PostgresAppVersionRepository.java
└── PostgresDeploymentRepository.java └── PostgresDeploymentRepository.java
src/main/java/com/cameleer3/server/app/controller/ src/main/java/com/cameleer/server/app/controller/
├── EnvironmentAdminController.java CRUD endpoints under /api/v1/admin/environments ├── EnvironmentAdminController.java CRUD endpoints under /api/v1/admin/environments
├── AppController.java App + version CRUD + JAR upload ├── AppController.java App + version CRUD + JAR upload
└── DeploymentController.java Deploy, stop, restart, promote, logs └── DeploymentController.java Deploy, stop, restart, promote, logs
@@ -70,7 +70,7 @@ src/main/resources/db/migration/
### Modified Files ### Modified Files
- `pom.xml` (parent) — add docker-java dependency - `pom.xml` (parent) — add docker-java dependency
- `cameleer3-server-app/pom.xml` — add docker-java dependency - `cameleer-server-app/pom.xml` — add docker-java dependency
- `application.yml` — add runtime config properties - `application.yml` — add runtime config properties
--- ---
@@ -78,7 +78,7 @@ src/main/resources/db/migration/
### Task 1: Add docker-java Dependency ### Task 1: Add docker-java Dependency
**Files:** **Files:**
- Modify: `cameleer3-server-app/pom.xml` - Modify: `cameleer-server-app/pom.xml`
- [x] **Step 1: Add docker-java dependency** - [x] **Step 1: Add docker-java dependency**
@@ -97,13 +97,13 @@ src/main/resources/db/migration/
- [x] **Step 2: Verify build** - [x] **Step 2: Verify build**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn compile -pl cameleer3-server-app` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn compile -pl cameleer-server-app`
Expected: BUILD SUCCESS. Expected: BUILD SUCCESS.
- [x] **Step 3: Commit** - [x] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-app/pom.xml git add cameleer-server-app/pom.xml
git commit -m "chore: add docker-java dependency for runtime orchestration" git commit -m "chore: add docker-java dependency for runtime orchestration"
``` ```
@@ -112,7 +112,7 @@ git commit -m "chore: add docker-java dependency for runtime orchestration"
### Task 2: Database Migration — Runtime Management Tables ### Task 2: Database Migration — Runtime Management Tables
**Files:** **Files:**
- Create: `cameleer3-server-app/src/main/resources/db/migration/V3__runtime_management.sql` - Create: `cameleer-server-app/src/main/resources/db/migration/V3__runtime_management.sql`
- [x] **Step 1: Write migration** - [x] **Step 1: Write migration**
@@ -176,7 +176,7 @@ INSERT INTO environments (slug, display_name) VALUES ('default', 'Default');
- [x] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/resources/db/migration/V3__runtime_management.sql git add cameleer-server-app/src/main/resources/db/migration/V3__runtime_management.sql
git commit -m "feat: add runtime management database schema (environments, apps, versions, deployments)" git commit -m "feat: add runtime management database schema (environments, apps, versions, deployments)"
``` ```
@@ -185,36 +185,36 @@ git commit -m "feat: add runtime management database schema (environments, apps,
### Task 3: Core Domain — Environment, App, AppVersion, Deployment Records ### Task 3: Core Domain — Environment, App, AppVersion, Deployment Records
**Files:** **Files:**
- Create all records in `cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/` - Create all records in `cameleer-server-core/src/main/java/com/cameleer/server/core/runtime/`
- [x] **Step 1: Create all domain records** - [x] **Step 1: Create all domain records**
```java ```java
// Environment.java // Environment.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
public record Environment(UUID id, String slug, String displayName, EnvironmentStatus status, Instant createdAt) {} public record Environment(UUID id, String slug, String displayName, EnvironmentStatus status, Instant createdAt) {}
// EnvironmentStatus.java // EnvironmentStatus.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
public enum EnvironmentStatus { ACTIVE, SUSPENDED } public enum EnvironmentStatus { ACTIVE, SUSPENDED }
// App.java // App.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
public record App(UUID id, UUID environmentId, String slug, String displayName, Instant createdAt) {} public record App(UUID id, UUID environmentId, String slug, String displayName, Instant createdAt) {}
// AppVersion.java // AppVersion.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
public record AppVersion(UUID id, UUID appId, int version, String jarPath, String jarChecksum, public record AppVersion(UUID id, UUID appId, int version, String jarPath, String jarChecksum,
String jarFilename, Long jarSizeBytes, Instant uploadedAt) {} String jarFilename, Long jarSizeBytes, Instant uploadedAt) {}
// Deployment.java // Deployment.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
public record Deployment(UUID id, UUID appId, UUID appVersionId, UUID environmentId, public record Deployment(UUID id, UUID appId, UUID appVersionId, UUID environmentId,
@@ -227,18 +227,18 @@ public record Deployment(UUID id, UUID appId, UUID appVersionId, UUID environmen
} }
// DeploymentStatus.java // DeploymentStatus.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
public enum DeploymentStatus { STARTING, RUNNING, FAILED, STOPPED } public enum DeploymentStatus { STARTING, RUNNING, FAILED, STOPPED }
// RoutingMode.java // RoutingMode.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
public enum RoutingMode { path, subdomain } public enum RoutingMode { path, subdomain }
``` ```
- [x] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ git add cameleer-server-core/src/main/java/com/cameleer/server/core/runtime/
git commit -m "feat: add runtime management domain records" git commit -m "feat: add runtime management domain records"
``` ```
@@ -253,7 +253,7 @@ git commit -m "feat: add runtime management domain records"
```java ```java
// EnvironmentRepository.java // EnvironmentRepository.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.util.*; import java.util.*;
public interface EnvironmentRepository { public interface EnvironmentRepository {
List<Environment> findAll(); List<Environment> findAll();
@@ -266,7 +266,7 @@ public interface EnvironmentRepository {
} }
// AppRepository.java // AppRepository.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.util.*; import java.util.*;
public interface AppRepository { public interface AppRepository {
List<App> findByEnvironmentId(UUID environmentId); List<App> findByEnvironmentId(UUID environmentId);
@@ -277,7 +277,7 @@ public interface AppRepository {
} }
// AppVersionRepository.java // AppVersionRepository.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.util.*; import java.util.*;
public interface AppVersionRepository { public interface AppVersionRepository {
List<AppVersion> findByAppId(UUID appId); List<AppVersion> findByAppId(UUID appId);
@@ -287,7 +287,7 @@ public interface AppVersionRepository {
} }
// DeploymentRepository.java // DeploymentRepository.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.util.*; import java.util.*;
public interface DeploymentRepository { public interface DeploymentRepository {
List<Deployment> findByAppId(UUID appId); List<Deployment> findByAppId(UUID appId);
@@ -305,7 +305,7 @@ public interface DeploymentRepository {
```java ```java
// RuntimeOrchestrator.java // RuntimeOrchestrator.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -319,7 +319,7 @@ public interface RuntimeOrchestrator {
} }
// ContainerRequest.java // ContainerRequest.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.util.Map; import java.util.Map;
public record ContainerRequest( public record ContainerRequest(
String containerName, String containerName,
@@ -334,7 +334,7 @@ public record ContainerRequest(
) {} ) {}
// ContainerStatus.java // ContainerStatus.java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
public record ContainerStatus(String state, boolean running, int exitCode, String error) { public record ContainerStatus(String state, boolean running, int exitCode, String error) {
public static ContainerStatus notFound() { public static ContainerStatus notFound() {
return new ContainerStatus("not_found", false, -1, "Container not found"); return new ContainerStatus("not_found", false, -1, "Container not found");
@@ -345,7 +345,7 @@ public record ContainerStatus(String state, boolean running, int exitCode, Strin
- [x] **Step 3: Commit** - [x] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ git add cameleer-server-core/src/main/java/com/cameleer/server/core/runtime/
git commit -m "feat: add runtime repository interfaces and RuntimeOrchestrator" git commit -m "feat: add runtime repository interfaces and RuntimeOrchestrator"
``` ```
@@ -359,7 +359,7 @@ git commit -m "feat: add runtime repository interfaces and RuntimeOrchestrator"
- [x] **Step 1: Create EnvironmentService** - [x] **Step 1: Create EnvironmentService**
```java ```java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -395,7 +395,7 @@ public class EnvironmentService {
- [x] **Step 2: Create AppService** - [x] **Step 2: Create AppService**
```java ```java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -478,7 +478,7 @@ public class AppService {
- [x] **Step 3: Create DeploymentService** - [x] **Step 3: Create DeploymentService**
```java ```java
package com.cameleer3.server.core.runtime; package com.cameleer.server.core.runtime;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -536,7 +536,7 @@ public class DeploymentService {
- [x] **Step 4: Commit** - [x] **Step 4: Commit**
```bash ```bash
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ git add cameleer-server-core/src/main/java/com/cameleer/server/core/runtime/
git commit -m "feat: add EnvironmentService, AppService, DeploymentService" git commit -m "feat: add EnvironmentService, AppService, DeploymentService"
``` ```
@@ -598,14 +598,14 @@ public class RuntimeBeanConfig {
- [x] **Step 3: Run tests** - [x] **Step 3: Run tests**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn test -pl cameleer-server-app`
Expected: PASS (Flyway applies V3 migration, context loads). Expected: PASS (Flyway applies V3 migration, context loads).
- [x] **Step 4: Commit** - [x] **Step 4: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/Postgres*Repository.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/storage/Postgres*Repository.java
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/RuntimeBeanConfig.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/config/RuntimeBeanConfig.java
git commit -m "feat: implement PostgreSQL repositories for runtime management" git commit -m "feat: implement PostgreSQL repositories for runtime management"
``` ```
@@ -614,16 +614,16 @@ git commit -m "feat: implement PostgreSQL repositories for runtime management"
### Task 7: Docker Runtime Orchestrator ### Task 7: Docker Runtime Orchestrator
**Files:** **Files:**
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java` - Create: `cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DockerRuntimeOrchestrator.java`
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DisabledRuntimeOrchestrator.java` - Create: `cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DisabledRuntimeOrchestrator.java`
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/RuntimeOrchestratorAutoConfig.java` - Create: `cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/RuntimeOrchestratorAutoConfig.java`
- [x] **Step 1: Implement DisabledRuntimeOrchestrator** - [x] **Step 1: Implement DisabledRuntimeOrchestrator**
```java ```java
package com.cameleer3.server.app.runtime; package com.cameleer.server.app.runtime;
import com.cameleer3.server.core.runtime.*; import com.cameleer.server.core.runtime.*;
import java.util.stream.Stream; import java.util.stream.Stream;
public class DisabledRuntimeOrchestrator implements RuntimeOrchestrator { public class DisabledRuntimeOrchestrator implements RuntimeOrchestrator {
@@ -685,9 +685,9 @@ public String startContainer(ContainerRequest request) {
- [x] **Step 3: Implement RuntimeOrchestratorAutoConfig** - [x] **Step 3: Implement RuntimeOrchestratorAutoConfig**
```java ```java
package com.cameleer3.server.app.runtime; package com.cameleer.server.app.runtime;
import com.cameleer3.server.core.runtime.RuntimeOrchestrator; import com.cameleer.server.core.runtime.RuntimeOrchestrator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -718,7 +718,7 @@ public class RuntimeOrchestratorAutoConfig {
- [x] **Step 4: Commit** - [x] **Step 4: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/ git add cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/
git commit -m "feat: implement DockerRuntimeOrchestrator with volume-mount JAR deployment" git commit -m "feat: implement DockerRuntimeOrchestrator with volume-mount JAR deployment"
``` ```
@@ -727,14 +727,14 @@ git commit -m "feat: implement DockerRuntimeOrchestrator with volume-mount JAR d
### Task 8: DeploymentExecutor — Async Deployment Pipeline ### Task 8: DeploymentExecutor — Async Deployment Pipeline
**Files:** **Files:**
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java` - Create: `cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java`
- [x] **Step 1: Implement async deployment pipeline** - [x] **Step 1: Implement async deployment pipeline**
```java ```java
package com.cameleer3.server.app.runtime; package com.cameleer.server.app.runtime;
import com.cameleer3.server.core.runtime.*; import com.cameleer.server.core.runtime.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
@@ -841,7 +841,7 @@ public TaskExecutor deploymentTaskExecutor() {
- [x] **Step 3: Commit** - [x] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DeploymentExecutor.java
git commit -m "feat: implement async DeploymentExecutor pipeline" git commit -m "feat: implement async DeploymentExecutor pipeline"
``` ```
@@ -907,9 +907,9 @@ Add to `SecurityConfig.filterChain()`:
- [x] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/EnvironmentAdminController.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/controller/EnvironmentAdminController.java
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AppController.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/controller/AppController.java
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DeploymentController.java git add cameleer-server-app/src/main/java/com/cameleer/server/app/controller/DeploymentController.java
git commit -m "feat: add REST controllers for environment, app, and deployment management" git commit -m "feat: add REST controllers for environment, app, and deployment management"
``` ```
@@ -918,7 +918,7 @@ git commit -m "feat: add REST controllers for environment, app, and deployment m
### Task 10: Configuration and Application Properties ### Task 10: Configuration and Application Properties
**Files:** **Files:**
- Modify: `cameleer3-server-app/src/main/resources/application.yml` - Modify: `cameleer-server-app/src/main/resources/application.yml`
- [x] **Step 1: Add runtime config properties** - [x] **Step 1: Add runtime config properties**
@@ -939,13 +939,13 @@ cameleer:
- [x] **Step 2: Run full test suite** - [x] **Step 2: Run full test suite**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn clean verify` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn clean verify`
Expected: PASS. Expected: PASS.
- [x] **Step 3: Commit** - [x] **Step 3: Commit**
```bash ```bash
git add cameleer3-server-app/src/main/resources/application.yml git add cameleer-server-app/src/main/resources/application.yml
git commit -m "feat: add runtime management configuration properties" git commit -m "feat: add runtime management configuration properties"
``` ```
@@ -968,7 +968,7 @@ Test deployment creation (with `DisabledRuntimeOrchestrator` — verifies the de
- [x] **Step 4: Commit** - [x] **Step 4: Commit**
```bash ```bash
git add cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/ git add cameleer-server-app/src/test/java/com/cameleer/server/app/controller/
git commit -m "test: add integration tests for runtime management API" git commit -m "test: add integration tests for runtime management API"
``` ```
@@ -978,7 +978,7 @@ git commit -m "test: add integration tests for runtime management API"
- [x] **Step 1: Run full build** - [x] **Step 1: Run full build**
Run: `cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn clean verify` Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-server && mvn clean verify`
Expected: All tests PASS. Expected: All tests PASS.
- [x] **Step 2: Verify schema applies cleanly** - [x] **Step 2: Verify schema applies cleanly**

View File

@@ -10,7 +10,7 @@
**Repo:** `C:\Users\Hendrik\Documents\projects\cameleer-saas` **Repo:** `C:\Users\Hendrik\Documents\projects\cameleer-saas`
**Prerequisite:** Plans 1-3 must be implemented in cameleer3-server first. **Prerequisite:** Plans 1-3 must be implemented in cameleer-server first.
--- ---
@@ -212,7 +212,7 @@ git commit -m "feat: remove migrated environment/app/deployment/runtime code fro
```sql ```sql
-- V010__drop_migrated_tables.sql -- V010__drop_migrated_tables.sql
-- Drop tables that have been migrated to cameleer3-server -- Drop tables that have been migrated to cameleer-server
DROP TABLE IF EXISTS deployments CASCADE; DROP TABLE IF EXISTS deployments CASCADE;
DROP TABLE IF EXISTS apps CASCADE; DROP TABLE IF EXISTS apps CASCADE;
@@ -242,7 +242,7 @@ group_add:
- "0" - "0"
``` ```
The Docker socket mount now belongs to the `cameleer3-server` service instead. The Docker socket mount now belongs to the `cameleer-server` service instead.
- [ ] **Step 2: Remove docker-java dependency from pom.xml** - [ ] **Step 2: Remove docker-java dependency from pom.xml**
@@ -328,7 +328,7 @@ git commit -m "feat: expand ServerApiClient with license push and health check m
- [ ] **Step 1: Create integration contract document** - [ ] **Step 1: Create integration contract document**
Create `docs/SAAS-INTEGRATION.md` in the cameleer3-server repo documenting: Create `docs/SAAS-INTEGRATION.md` in the cameleer-server repo documenting:
- Which server API endpoints the SaaS calls - Which server API endpoints the SaaS calls
- Required auth (M2M token with `server:admin` scope) - Required auth (M2M token with `server:admin` scope)
- License injection mechanism (`POST /api/v1/admin/license`) - License injection mechanism (`POST /api/v1/admin/license`)
@@ -339,7 +339,7 @@ Create `docs/SAAS-INTEGRATION.md` in the cameleer3-server repo documenting:
- [ ] **Step 2: Commit** - [ ] **Step 2: Commit**
```bash ```bash
cd /c/Users/Hendrik/Documents/projects/cameleer3-server cd /c/Users/Hendrik/Documents/projects/cameleer-server
git add docs/SAAS-INTEGRATION.md git add docs/SAAS-INTEGRATION.md
git commit -m "docs: add SaaS integration contract documentation" git commit -m "docs: add SaaS integration contract documentation"
``` ```

View File

@@ -2,7 +2,7 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Redesign the cameleer-saas platform from a read-only viewer into a vendor management plane that provisions per-tenant cameleer3-server instances, with vendor CRUD and customer self-service. **Goal:** Redesign the cameleer-saas platform from a read-only viewer into a vendor management plane that provisions per-tenant cameleer-server instances, with vendor CRUD and customer self-service.
**Architecture:** Two-persona split (vendor console at `/vendor/*`, tenant portal at `/tenant/*`). Pluggable `TenantProvisioner` interface with Docker implementation. Backend orchestrates provisioning + Logto + licensing in a single create-tenant flow. Frontend adapts sidebar/routes by persona. **Architecture:** Two-persona split (vendor console at `/vendor/*`, tenant portal at `/tenant/*`). Pluggable `TenantProvisioner` interface with Docker implementation. Backend orchestrates provisioning + Logto + licensing in a single create-tenant flow. Frontend adapts sidebar/routes by persona.
@@ -410,13 +410,13 @@ Append to `application.yml`:
```yaml ```yaml
cameleer: cameleer:
provisioning: provisioning:
server-image: ${CAMELEER_SERVER_IMAGE:gitea.siegeln.net/cameleer/cameleer3-server:latest} server-image: ${CAMELEER_SERVER_IMAGE:gitea.siegeln.net/cameleer/cameleer-server:latest}
server-ui-image: ${CAMELEER_SERVER_UI_IMAGE:gitea.siegeln.net/cameleer/cameleer3-server-ui:latest} server-ui-image: ${CAMELEER_SERVER_UI_IMAGE:gitea.siegeln.net/cameleer/cameleer-server-ui:latest}
network-name: ${CAMELEER_NETWORK:cameleer-saas_cameleer} network-name: ${CAMELEER_NETWORK:cameleer-saas_cameleer}
traefik-network: ${CAMELEER_TRAEFIK_NETWORK:cameleer-traefik} traefik-network: ${CAMELEER_TRAEFIK_NETWORK:cameleer-traefik}
public-host: ${PUBLIC_HOST:localhost} public-host: ${PUBLIC_HOST:localhost}
public-protocol: ${PUBLIC_PROTOCOL:https} public-protocol: ${PUBLIC_PROTOCOL:https}
datasource-url: ${CAMELEER_SERVER_DB_URL:jdbc:postgresql://postgres:5432/cameleer3} datasource-url: ${CAMELEER_SERVER_DB_URL:jdbc:postgresql://postgres:5432/cameleer}
oidc-issuer-uri: ${PUBLIC_PROTOCOL:https}://${PUBLIC_HOST:localhost}/oidc oidc-issuer-uri: ${PUBLIC_PROTOCOL:https}://${PUBLIC_HOST:localhost}/oidc
oidc-jwk-set-uri: http://logto:3001/oidc/jwks oidc-jwk-set-uri: http://logto:3001/oidc/jwks
cors-origins: ${PUBLIC_PROTOCOL:https}://${PUBLIC_HOST:localhost} cors-origins: ${PUBLIC_PROTOCOL:https}://${PUBLIC_HOST:localhost}
@@ -1877,7 +1877,7 @@ import { LayoutDashboard, ShieldCheck, Server, Users, Settings, KeyRound, Buildi
import { useAuth } from '../auth/useAuth'; import { useAuth } from '../auth/useAuth';
import { useScopes } from '../auth/useScopes'; import { useScopes } from '../auth/useScopes';
import { useOrgStore } from '../auth/useOrganization'; import { useOrgStore } from '../auth/useOrganization';
import logo from '@cameleer/design-system/assets/cameleer3-logo.svg'; import logo from '@cameleer/design-system/assets/cameleer-logo.svg';
export function Layout() { export function Layout() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -2940,8 +2940,8 @@ This gives the SaaS container access to the Docker daemon for provisioning.
Add to the `cameleer-saas` environment section: Add to the `cameleer-saas` environment section:
```yaml ```yaml
CAMELEER_SERVER_IMAGE: gitea.siegeln.net/cameleer/cameleer3-server:${VERSION:-latest} CAMELEER_SERVER_IMAGE: gitea.siegeln.net/cameleer/cameleer-server:${VERSION:-latest}
CAMELEER_SERVER_UI_IMAGE: gitea.siegeln.net/cameleer/cameleer3-server-ui:${VERSION:-latest} CAMELEER_SERVER_UI_IMAGE: gitea.siegeln.net/cameleer/cameleer-server-ui:${VERSION:-latest}
CAMELEER_NETWORK: cameleer-saas_cameleer CAMELEER_NETWORK: cameleer-saas_cameleer
CAMELEER_TRAEFIK_NETWORK: cameleer-traefik CAMELEER_TRAEFIK_NETWORK: cameleer-traefik
``` ```

View File

@@ -581,7 +581,7 @@ In `ui/sign-in/src/SignInPage.tsx`, find the logo text (line ~61):
// BEFORE: // BEFORE:
<div className={styles.logo}> <div className={styles.logo}>
<img src={cameleerLogo} alt="" className={styles.logoImg} /> <img src={cameleerLogo} alt="" className={styles.logoImg} />
cameleer3 cameleer
</div> </div>
// AFTER: // AFTER:

View File

@@ -0,0 +1,210 @@
# Fleet Health at a Glance Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add agent count, environment count, and agent limit columns to the vendor tenant list so the vendor can see fleet utilization at a glance.
**Architecture:** Extend the existing `VendorTenantSummary` record with three int fields. The list endpoint fetches counts from each active tenant's server via existing M2M API methods (`getAgentCount`, `getEnvironmentCount`), parallelized with `CompletableFuture`. Frontend adds two columns (Agents, Envs) to the DataTable.
**Tech Stack:** Java 21, Spring Boot, CompletableFuture, React, TypeScript, @cameleer/design-system DataTable
---
### Task 1: Extend backend — VendorTenantSummary + parallel fetch
**Files:**
- Modify: `src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantController.java`
- [ ] **Step 1: Extend the VendorTenantSummary record**
In `VendorTenantController.java`, replace the record at lines 39-48:
```java
public record VendorTenantSummary(
UUID id,
String name,
String slug,
String tier,
String status,
String serverState,
String licenseExpiry,
String provisionError,
int agentCount,
int environmentCount,
int agentLimit
) {}
```
- [ ] **Step 2: Update the listAll() endpoint to fetch counts in parallel**
Replace the `listAll()` method at lines 60-77:
```java
@GetMapping
public ResponseEntity<List<VendorTenantSummary>> listAll() {
var tenants = vendorTenantService.listAll();
// Parallel health fetch for active tenants
var futures = tenants.stream().map(tenant -> java.util.concurrent.CompletableFuture.supplyAsync(() -> {
ServerStatus status = vendorTenantService.getServerStatus(tenant);
String licenseExpiry = vendorTenantService
.getLicenseForTenant(tenant.getId())
.map(l -> l.getExpiresAt() != null ? l.getExpiresAt().toString() : null)
.orElse(null);
int agentCount = 0;
int environmentCount = 0;
int agentLimit = -1;
String endpoint = tenant.getServerEndpoint();
boolean isActive = "ACTIVE".equals(tenant.getStatus().name());
if (isActive && endpoint != null && !endpoint.isBlank() && "RUNNING".equals(status.state().name())) {
var serverApi = vendorTenantService.getServerApiClient();
agentCount = serverApi.getAgentCount(endpoint);
environmentCount = serverApi.getEnvironmentCount(endpoint);
}
var license = vendorTenantService.getLicenseForTenant(tenant.getId());
if (license.isPresent() && license.get().getLimits() != null) {
var limits = license.get().getLimits();
if (limits.containsKey("agents")) {
agentLimit = ((Number) limits.get("agents")).intValue();
}
}
return new VendorTenantSummary(
tenant.getId(), tenant.getName(), tenant.getSlug(),
tenant.getTier().name(), tenant.getStatus().name(),
status.state().name(), licenseExpiry, tenant.getProvisionError(),
agentCount, environmentCount, agentLimit
);
})).toList();
List<VendorTenantSummary> summaries = futures.stream()
.map(java.util.concurrent.CompletableFuture::join)
.toList();
return ResponseEntity.ok(summaries);
}
```
- [ ] **Step 3: Expose ServerApiClient from VendorTenantService**
Add a getter in `src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantService.java`:
```java
public ServerApiClient getServerApiClient() {
return serverApiClient;
}
```
(The `serverApiClient` field already exists in VendorTenantService — check around line 30.)
- [ ] **Step 4: Verify compilation**
Run: `./mvnw compile -pl . -q`
Expected: BUILD SUCCESS
- [ ] **Step 5: Commit**
```bash
git add src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantController.java \
src/main/java/net/siegeln/cameleer/saas/vendor/VendorTenantService.java
git commit -m "feat: add agent/env counts to vendor tenant list endpoint"
```
---
### Task 2: Update frontend types and columns
**Files:**
- Modify: `ui/src/types/api.ts`
- Modify: `ui/src/pages/vendor/VendorTenantsPage.tsx`
- [ ] **Step 1: Add fields to VendorTenantSummary TypeScript type**
In `ui/src/types/api.ts`, update the `VendorTenantSummary` interface:
```typescript
export interface VendorTenantSummary {
id: string;
name: string;
slug: string;
tier: string;
status: string;
serverState: string;
licenseExpiry: string | null;
provisionError: string | null;
agentCount: number;
environmentCount: number;
agentLimit: number;
}
```
- [ ] **Step 2: Add Agents and Envs columns to VendorTenantsPage**
In `ui/src/pages/vendor/VendorTenantsPage.tsx`, add a helper function after `statusColor`:
```typescript
function formatUsage(used: number, limit: number): string {
return limit < 0 ? `${used} / ∞` : `${used} / ${limit}`;
}
```
Then add two column entries in the `columns` array, after the `serverState` column (after line 54) and before the `licenseExpiry` column:
```typescript
{
key: 'agentCount',
header: 'Agents',
render: (_v, row) => (
<span style={{ fontFamily: 'monospace', fontSize: '0.875rem' }}>
{formatUsage(row.agentCount, row.agentLimit)}
</span>
),
},
{
key: 'environmentCount',
header: 'Envs',
render: (_v, row) => (
<span style={{ fontFamily: 'monospace', fontSize: '0.875rem' }}>
{row.environmentCount}
</span>
),
},
```
- [ ] **Step 3: Build the UI**
Run: `cd ui && npm run build`
Expected: Build succeeds with no errors.
- [ ] **Step 4: Commit**
```bash
git add ui/src/types/api.ts ui/src/pages/vendor/VendorTenantsPage.tsx
git commit -m "feat: show agent/env counts in vendor tenant list"
```
---
### Task 3: Verify end-to-end
- [ ] **Step 1: Run backend tests**
Run: `./mvnw test -pl . -q`
Expected: All tests pass. (Existing tests use mocks, the new parallel fetch doesn't break them since it only affects the controller's list mapping.)
- [ ] **Step 2: Verify in browser**
Navigate to the vendor tenant list. Confirm:
- "Agents" column shows "0 / ∞" (or actual count if agents are connected)
- "Envs" column shows "1" (or actual count)
- PROVISIONING/SUSPENDED tenants show "0" for both
- 30s auto-refresh still works
- [ ] **Step 3: Final commit and push**
```bash
git push
```

View File

@@ -1572,8 +1572,8 @@ VENDOR_PASS=${VENDOR_PASS:-}
DOCKER_SOCKET=${DOCKER_SOCKET} DOCKER_SOCKET=${DOCKER_SOCKET}
# Provisioning images # Provisioning images
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer3-server:${VERSION} CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer-server:${VERSION}
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer3-server-ui:${VERSION} CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer-server-ui:${VERSION}
EOF EOF
log_info "Generated .env" log_info "Generated .env"
@@ -1793,8 +1793,8 @@ EOF
CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost}
CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer
CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server:latest} CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer-server:latest}
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server-ui:latest} CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui:latest}
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.saas.rule=PathPrefix(`/platform`) - traefik.http.routers.saas.rule=PathPrefix(`/platform`)
@@ -2109,7 +2109,7 @@ EOF
| `logto` | OIDC identity provider + bootstrap | | `logto` | OIDC identity provider + bootstrap |
| `cameleer-saas` | SaaS platform (Spring Boot + React) | | `cameleer-saas` | SaaS platform (Spring Boot + React) |
Per-tenant `cameleer3-server` and `cameleer3-server-ui` containers are provisioned dynamically when tenants are created. Per-tenant `cameleer-server` and `cameleer-server-ui` containers are provisioned dynamically when tenants are created.
## Networking ## Networking
@@ -2656,7 +2656,7 @@ Tasks 8-16 ────── can run in parallel with Phase 1
## Follow-up (out of scope) ## Follow-up (out of scope)
- Bake `docker/server-ui-entrypoint.sh` into the `cameleer3-server-ui` image (separate repo) - Bake `docker/server-ui-entrypoint.sh` into the `cameleer-server-ui` image (separate repo)
- Set up `install.cameleer.io` distribution endpoint - Set up `install.cameleer.io` distribution endpoint
- Create release automation (tag → publish installer scripts to distribution endpoint) - Create release automation (tag → publish installer scripts to distribution endpoint)
- Add `docker-compose.dev.yml` overlay generation for the installer's expert mode - Add `docker-compose.dev.yml` overlay generation for the installer's expert mode

View File

@@ -87,7 +87,7 @@ import java.sql.Statement;
/** /**
* Creates and drops per-tenant PostgreSQL users and schemas * Creates and drops per-tenant PostgreSQL users and schemas
* on the shared cameleer3 database for DB-level tenant isolation. * on the shared cameleer database for DB-level tenant isolation.
*/ */
@Service @Service
public class TenantDatabaseService { public class TenantDatabaseService {

View File

@@ -3,7 +3,7 @@
**Date:** 2026-03-29 **Date:** 2026-03-29
**Status:** Draft — Awaiting Review **Status:** Draft — Awaiting Review
**Author:** Boardroom simulation (Strategist, Skeptic, Architect, Growth Hacker) **Author:** Boardroom simulation (Strategist, Skeptic, Architect, Growth Hacker)
**Gitea Issues:** cameleer/cameleer3 #57-#72 (label: MOAT) **Gitea Issues:** cameleer/cameleer #57-#72 (label: MOAT)
## Executive Summary ## Executive Summary
@@ -32,14 +32,14 @@ Week 8-14: Live Route Debugger (agent + server + UI)
- #59 — Cross-Service Trace Correlation + Topology Map - #59 — Cross-Service Trace Correlation + Topology Map
**Debugger sub-issues:** **Debugger sub-issues:**
- #60 — Protocol: Debug session command types (`cameleer3-common`) - #60 — Protocol: Debug session command types (`cameleer-common`)
- #61 — Agent: DebugSessionManager + breakpoint InterceptStrategy integration - #61 — Agent: DebugSessionManager + breakpoint InterceptStrategy integration
- #62 — Agent: ExchangeStateSerializer + synthetic direct route wrapper - #62 — Agent: ExchangeStateSerializer + synthetic direct route wrapper
- #63 — Server: DebugSessionService + WebSocket + REST API - #63 — Server: DebugSessionService + WebSocket + REST API
- #70 — UI: Debug session frontend components - #70 — UI: Debug session frontend components
**Lineage sub-issues:** **Lineage sub-issues:**
- #64 — Protocol: Lineage command types (`cameleer3-common`) - #64 — Protocol: Lineage command types (`cameleer-common`)
- #65 — Agent: LineageManager + capture mode integration - #65 — Agent: LineageManager + capture mode integration
- #66 — Server: LineageService + DiffEngine + REST API - #66 — Server: LineageService + DiffEngine + REST API
- #71 — UI: Lineage timeline + diff viewer components - #71 — UI: Lineage timeline + diff viewer components
@@ -69,14 +69,14 @@ Browser (SaaS UI)
WebSocket <--------------------------------------+ WebSocket <--------------------------------------+
| | | |
v | v |
cameleer3-server | cameleer-server |
| POST /api/v1/debug/sessions | | POST /api/v1/debug/sessions |
| POST /api/v1/debug/sessions/{id}/step | | POST /api/v1/debug/sessions/{id}/step |
| POST /api/v1/debug/sessions/{id}/resume | | POST /api/v1/debug/sessions/{id}/resume |
| DELETE /api/v1/debug/sessions/{id} | | DELETE /api/v1/debug/sessions/{id} |
| | | |
v | v |
SSE Command Channel --> cameleer3 agent | SSE Command Channel --> cameleer agent |
| | | | | |
| "start-debug" | | | "start-debug" | |
| command v | | command v |
@@ -101,7 +101,7 @@ SSE Command Channel --> cameleer3 agent |
| Continue to next processor | Continue to next processor
``` ```
### 1.3 Protocol Additions (cameleer3-common) ### 1.3 Protocol Additions (cameleer-common)
#### New SSE Commands #### New SSE Commands
@@ -160,11 +160,11 @@ SSE Command Channel --> cameleer3 agent |
} }
``` ```
### 1.4 Agent Implementation (cameleer3-agent) ### 1.4 Agent Implementation (cameleer-agent)
#### DebugSessionManager #### DebugSessionManager
- Location: `com.cameleer3.agent.debug.DebugSessionManager` - Location: `com.cameleer.agent.debug.DebugSessionManager`
- Stores active sessions: `ConcurrentHashMap<sessionId, DebugSession>` - Stores active sessions: `ConcurrentHashMap<sessionId, DebugSession>`
- Enforces max concurrent sessions (default 3, configurable via `cameleer.debug.maxSessions`) - Enforces max concurrent sessions (default 3, configurable via `cameleer.debug.maxSessions`)
- Allocates **dedicated Thread** per session (NOT from Camel thread pool) - Allocates **dedicated Thread** per session (NOT from Camel thread pool)
@@ -213,7 +213,7 @@ For non-direct routes (timer, jms, http, file):
3. Debug exchange enters via `ProducerTemplate.send()` 3. Debug exchange enters via `ProducerTemplate.send()`
4. Remove temporary route on session completion 4. Remove temporary route on session completion
### 1.5 Server Implementation (cameleer3-server) ### 1.5 Server Implementation (cameleer-server)
#### REST Endpoints #### REST Endpoints
@@ -308,7 +308,7 @@ Capture the full transformation history of a message flowing through a route. At
### 2.2 Architecture ### 2.2 Architecture
``` ```
cameleer3 agent cameleer agent
| |
| On lineage-enabled exchange: | On lineage-enabled exchange:
| Before processor: capture INPUT | Before processor: capture INPUT
@@ -319,7 +319,7 @@ cameleer3 agent
POST /api/v1/data/executions (processors carry full snapshots) POST /api/v1/data/executions (processors carry full snapshots)
| |
v v
cameleer3-server cameleer-server
| |
| LineageService: | LineageService:
| > Flatten processor tree to ordered list | > Flatten processor tree to ordered list
@@ -334,7 +334,7 @@ GET /api/v1/executions/{id}/lineage
Browser: LineageTimeline + DiffViewer Browser: LineageTimeline + DiffViewer
``` ```
### 2.3 Protocol Additions (cameleer3-common) ### 2.3 Protocol Additions (cameleer-common)
#### New SSE Commands #### New SSE Commands
@@ -370,11 +370,11 @@ Browser: LineageTimeline + DiffViewer
| `EXPRESSION` | Any exchange matching a Simple/JsonPath predicate | | `EXPRESSION` | Any exchange matching a Simple/JsonPath predicate |
| `NEXT_N` | Next N exchanges on the route (countdown) | | `NEXT_N` | Next N exchanges on the route (countdown) |
### 2.4 Agent Implementation (cameleer3-agent) ### 2.4 Agent Implementation (cameleer-agent)
#### LineageManager #### LineageManager
- Location: `com.cameleer3.agent.lineage.LineageManager` - Location: `com.cameleer.agent.lineage.LineageManager`
- Stores active configs: `ConcurrentHashMap<lineageId, LineageConfig>` - Stores active configs: `ConcurrentHashMap<lineageId, LineageConfig>`
- Tracks capture count per lineageId: auto-disables at `maxCaptures` - Tracks capture count per lineageId: auto-disables at `maxCaptures`
- Duration timeout via `ScheduledExecutorService`: auto-disables after expiry - Duration timeout via `ScheduledExecutorService`: auto-disables after expiry
@@ -412,7 +412,7 @@ cameleer.lineage.maxBodySize=65536 # 64KB for lineage captures (vs 4KB normal
cameleer.lineage.enabled=true # master switch cameleer.lineage.enabled=true # master switch
``` ```
### 2.5 Server Implementation (cameleer3-server) ### 2.5 Server Implementation (cameleer-server)
#### LineageService #### LineageService
@@ -548,7 +548,7 @@ New (added):
| Direct/SEDA | URI prefix `direct:`, `seda:`, `vm:` | Exchange property (in-process) | | Direct/SEDA | URI prefix `direct:`, `seda:`, `vm:` | Exchange property (in-process) |
| File/FTP | URI prefix `file:`, `ftp:` | Not propagated (async) | | File/FTP | URI prefix `file:`, `ftp:` | Not propagated (async) |
### 3.3 Agent Implementation (cameleer3-agent) ### 3.3 Agent Implementation (cameleer-agent)
#### Outgoing Propagation (InterceptStrategy) #### Outgoing Propagation (InterceptStrategy)
@@ -597,7 +597,7 @@ execution.setHopIndex(...); // depth in distributed trace
- Parse failure: log warning, continue without context (no exchange failure) - Parse failure: log warning, continue without context (no exchange failure)
- Only inject on outgoing processors, never on FROM consumers - Only inject on outgoing processors, never on FROM consumers
### 3.4 Server Implementation: Trace Assembly (cameleer3-server) ### 3.4 Server Implementation: Trace Assembly (cameleer-server)
#### CorrelationService #### CorrelationService
@@ -665,7 +665,7 @@ CREATE INDEX idx_executions_parent_span
- **Fan-out:** parallel multicast creates multiple children from same processor - **Fan-out:** parallel multicast creates multiple children from same processor
- **Circular calls:** detected via hopIndex (max depth 20) - **Circular calls:** detected via hopIndex (max depth 20)
### 3.5 Server Implementation: Topology Graph (cameleer3-server) ### 3.5 Server Implementation: Topology Graph (cameleer-server)
#### DependencyGraphService #### DependencyGraphService
@@ -799,11 +799,11 @@ Reserve `sourceTenantHash` in TraceContext for future use:
| Work | Repo | Issue | | Work | Repo | Issue |
|------|------|-------| |------|------|-------|
| Service topology materialized view | cameleer3-server | #69 | | Service topology materialized view | cameleer-server | #69 |
| Topology REST API | cameleer3-server | #69 | | Topology REST API | cameleer-server | #69 |
| ServiceTopologyGraph.tsx | cameleer3-server + saas | #72 | | ServiceTopologyGraph.tsx | cameleer-server + saas | #72 |
| WebSocket infrastructure (for debugger) | cameleer3-server | #63 | | WebSocket infrastructure (for debugger) | cameleer-server | #63 |
| TraceContext DTO in cameleer3-common | cameleer3 | #67 | | TraceContext DTO in cameleer-common | cameleer | #67 |
**Ship:** Topology graph visible from existing data. Zero agent changes. Immediate visual payoff. **Ship:** Topology graph visible from existing data. Zero agent changes. Immediate visual payoff.
@@ -811,10 +811,10 @@ Reserve `sourceTenantHash` in TraceContext for future use:
| Work | Repo | Issue | | Work | Repo | Issue |
|------|------|-------| |------|------|-------|
| Lineage protocol DTOs | cameleer3-common | #64 | | Lineage protocol DTOs | cameleer-common | #64 |
| LineageManager + capture integration | cameleer3-agent | #65 | | LineageManager + capture integration | cameleer-agent | #65 |
| LineageService + DiffEngine | cameleer3-server | #66 | | LineageService + DiffEngine | cameleer-server | #66 |
| Lineage UI components | cameleer3-server + saas | #71 | | Lineage UI components | cameleer-server + saas | #71 |
**Ship:** Payload flow lineage independently usable. **Ship:** Payload flow lineage independently usable.
@@ -822,10 +822,10 @@ Reserve `sourceTenantHash` in TraceContext for future use:
| Work | Repo | Issue | | Work | Repo | Issue |
|------|------|-------| |------|------|-------|
| Trace context header propagation | cameleer3-agent | #67 | | Trace context header propagation | cameleer-agent | #67 |
| Executions table migration (new columns) | cameleer3-server | #68 | | Executions table migration (new columns) | cameleer-server | #68 |
| CorrelationService + trace assembly | cameleer3-server | #68 | | CorrelationService + trace assembly | cameleer-server | #68 |
| DistributedTraceView + TraceSearch UI | cameleer3-server + saas | #72 | | DistributedTraceView + TraceSearch UI | cameleer-server + saas | #72 |
**Ship:** Distributed traces + topology — full correlation story. **Ship:** Distributed traces + topology — full correlation story.
@@ -833,11 +833,11 @@ Reserve `sourceTenantHash` in TraceContext for future use:
| Work | Repo | Issue | | Work | Repo | Issue |
|------|------|-------| |------|------|-------|
| Debug protocol DTOs | cameleer3-common | #60 | | Debug protocol DTOs | cameleer-common | #60 |
| DebugSessionManager + InterceptStrategy | cameleer3-agent | #61 | | DebugSessionManager + InterceptStrategy | cameleer-agent | #61 |
| ExchangeStateSerializer + synthetic wrapper | cameleer3-agent | #62 | | ExchangeStateSerializer + synthetic wrapper | cameleer-agent | #62 |
| DebugSessionService + WS + REST | cameleer3-server | #63 | | DebugSessionService + WS + REST | cameleer-server | #63 |
| Debug UI components | cameleer3-server + saas | #70 | | Debug UI components | cameleer-server + saas | #70 |
**Ship:** Full browser-based route debugger with integration to lineage and correlation. **Ship:** Full browser-based route debugger with integration to lineage and correlation.

View File

@@ -10,12 +10,12 @@
## 1. Product Definition ## 1. Product Definition
**Cameleer SaaS** is a Camel application runtime platform with built-in observability. Customers deploy Apache Camel applications and get zero-configuration tracing, topology mapping, payload lineage, distributed correlation, live debugging, and exchange replay — powered by the cameleer3 agent (auto-injected) and cameleer3-server (managed per tenant). **Cameleer SaaS** is a Camel application runtime platform with built-in observability. Customers deploy Apache Camel applications and get zero-configuration tracing, topology mapping, payload lineage, distributed correlation, live debugging, and exchange replay — powered by the cameleer agent (auto-injected) and cameleer-server (managed per tenant).
### Three Pillars ### Three Pillars
1. **Runtime** — Deploy and run Camel applications with automatic agent injection 1. **Runtime** — Deploy and run Camel applications with automatic agent injection
2. **Observability** — Per-tenant cameleer3-server (traces, topology, lineage, correlation, debugger, replay) 2. **Observability** — Per-tenant cameleer-server (traces, topology, lineage, correlation, debugger, replay)
3. **Management** — Auth, billing, teams, provisioning, secrets, environments 3. **Management** — Auth, billing, teams, provisioning, secrets, environments
### Two Deployment Modes ### Two Deployment Modes
@@ -27,8 +27,8 @@
| Component | Role | Changes Required | | Component | Role | Changes Required |
|-----------|------|------------------| |-----------|------|------------------|
| cameleer3 (agent) | Zero-code Camel instrumentation, auto-injected into customer JARs | MOAT features (lineage, correlation, debugger, replay) | | cameleer (agent) | Zero-code Camel instrumentation, auto-injected into customer JARs | MOAT features (lineage, correlation, debugger, replay) |
| cameleer3-server | Per-tenant observability backend | Managed mode (trust SaaS JWT), license module, MOAT features | | cameleer-server | Per-tenant observability backend | Managed mode (trust SaaS JWT), license module, MOAT features |
| cameleer-saas (this repo) | SaaS management platform — control plane | New: everything in this document | | cameleer-saas (this repo) | SaaS management platform — control plane | New: everything in this document |
| design-system | Shared React component library | Used by both SaaS shell and server UI | | design-system | Shared React component library | Used by both SaaS shell and server UI |
@@ -81,7 +81,7 @@ Single Spring Boot application with well-bounded internal modules. K8s ingress h
``` ```
[Browser] → [Ingress (Traefik/Envoy)] → [SaaS Platform (modular Spring Boot)] [Browser] → [Ingress (Traefik/Envoy)] → [SaaS Platform (modular Spring Boot)]
↓ (tenant routes) ↓ (provisioning) ↓ (tenant routes) ↓ (provisioning)
[Tenant cameleer3-server] [Flux CD → K8s] [Tenant cameleer-server] [Flux CD → K8s]
``` ```
### Component Map ### Component Map
@@ -114,7 +114,7 @@ Single Spring Boot application with well-bounded internal modules. K8s ingress h
│ (PostgreSQL) │ │ API │ │ │ │ (PostgreSQL) │ │ API │ │ │
│ - tenants │ └────────┘ │ ┌─────────────────────┐ │ │ - tenants │ └────────┘ │ ┌─────────────────────┐ │
│ - users │ │ │ tenant-a namespace │ │ │ - users │ │ │ tenant-a namespace │ │
│ - teams │ ┌─────┐ │ │ ├─ cameleer3-server │ │ │ - teams │ ┌─────┐ │ │ ├─ cameleer-server │ │
│ - audit log │ │Flux │ │ │ ├─ camel-app-1 │ │ │ - audit log │ │Flux │ │ │ ├─ camel-app-1 │ │
│ - licenses │ │ CD │ │ │ ├─ camel-app-2 │ │ │ - licenses │ │ CD │ │ │ ├─ camel-app-2 │ │
└──────────────┘ └──┬──┘ │ │ └─ NetworkPolicies │ │ └──────────────┘ └──┬──┘ │ │ └─ NetworkPolicies │ │
@@ -144,7 +144,7 @@ Same management platform routes to dedicated cluster(s) per customer. Dedicated
| Management Platform backend | Spring Boot 3, Java 21 | | Management Platform backend | Spring Boot 3, Java 21 |
| Management Platform frontend | React, @cameleer/design-system | | Management Platform frontend | React, @cameleer/design-system |
| Platform database | PostgreSQL | | Platform database | PostgreSQL |
| Tenant observability | cameleer3-server (Spring Boot), PostgreSQL, OpenSearch | | Tenant observability | cameleer-server (Spring Boot), PostgreSQL, OpenSearch |
| GitOps | Flux CD | | GitOps | Flux CD |
| K8s distribution | Talos (production), k3s (dev) | | K8s distribution | Talos (production), k3s (dev) |
| Ingress | Traefik or Envoy | | Ingress | Traefik or Envoy |
@@ -192,7 +192,7 @@ Stores all SaaS control plane data — completely separate from tenant observabi
### Tenant Data (Shared PostgreSQL) ### Tenant Data (Shared PostgreSQL)
Each tenant's cameleer3-server uses its own PostgreSQL schema on the shared instance (dedicated instance for high/business). This is the existing cameleer3-server data model — unchanged: Each tenant's cameleer-server uses its own PostgreSQL schema on the shared instance (dedicated instance for high/business). This is the existing cameleer-server data model — unchanged:
- Route executions, processor traces, metrics - Route executions, processor traces, metrics
- Route graph topology - Route graph topology
@@ -215,12 +215,12 @@ Completely separate: Prometheus TSDB for metrics, Loki for logs.
### Architecture ### Architecture
The SaaS management platform is the single identity plane. It owns authentication and authorization. Per-tenant cameleer3-server instances trust SaaS-issued tokens. The SaaS management platform is the single identity plane. It owns authentication and authorization. Per-tenant cameleer-server instances trust SaaS-issued tokens.
- Spring Security OAuth2 for OIDC federation with customer IdPs - Spring Security OAuth2 for OIDC federation with customer IdPs
- Ed25519 JWT signing (consistent with existing cameleer3-server pattern) - Ed25519 JWT signing (consistent with existing cameleer-server pattern)
- Tokens carry: tenant ID, user ID, roles, feature entitlements - Tokens carry: tenant ID, user ID, roles, feature entitlements
- cameleer3-server validates SaaS-issued JWTs in managed mode - cameleer-server validates SaaS-issued JWTs in managed mode
- Standalone mode retains its own auth for air-gapped deployments - Standalone mode retains its own auth for air-gapped deployments
### RBAC Model ### RBAC Model
@@ -252,7 +252,7 @@ Customer signs up + payment
→ Create tenant record + Stripe customer/subscription → Create tenant record + Stripe customer/subscription
→ Generate signed license token (Ed25519) → Generate signed license token (Ed25519)
→ Create Flux HelmRelease CR → Create Flux HelmRelease CR
→ Flux reconciles: namespace, ResourceQuota, NetworkPolicies, cameleer3-server → Flux reconciles: namespace, ResourceQuota, NetworkPolicies, cameleer-server
→ Provision PostgreSQL schema + per-tenant credentials → Provision PostgreSQL schema + per-tenant credentials
→ Provision OpenSearch index template + per-tenant credentials → Provision OpenSearch index template + per-tenant credentials
→ Readiness check: server healthy, DB migrated, auth working → Readiness check: server healthy, DB migrated, auth working
@@ -297,7 +297,7 @@ Full Cluster API automation deferred to future release.
### JAR Upload → Immutable Image ### JAR Upload → Immutable Image
1. **Validation** — File type check, size limit per tier, SHA-256 checksum, Trivy security scan, secret detection (reject JARs with embedded credentials) 1. **Validation** — File type check, size limit per tier, SHA-256 checksum, Trivy security scan, secret detection (reject JARs with embedded credentials)
2. **Image Build** — Templated Dockerfile: distroless JRE base + customer JAR + cameleer3-agent.jar + `-javaagent` flag + agent pre-configured for tenant server. Image tagged: `registry/{tenant}/{app}:v{N}-{sha256short}`. Signed with cosign. SBOM attached. 2. **Image Build** — Templated Dockerfile: distroless JRE base + customer JAR + cameleer-agent.jar + `-javaagent` flag + agent pre-configured for tenant server. Image tagged: `registry/{tenant}/{app}:v{N}-{sha256short}`. Signed with cosign. SBOM attached.
3. **Registry Push** — Per-tenant repository in platform container registry 3. **Registry Push** — Per-tenant repository in platform container registry
4. **Deploy** — K8s Deployment in tenant namespace with resource limits, secrets mounted, config injected, NetworkPolicy applied, liveness/readiness probes 4. **Deploy** — K8s Deployment in tenant namespace with resource limits, secrets mounted, config injected, NetworkPolicy applied, liveness/readiness probes
@@ -350,7 +350,7 @@ Central UI for managing each deployed application:
### Architecture ### Architecture
Each tenant gets a dedicated cameleer3-server instance: Each tenant gets a dedicated cameleer-server instance:
- Shared tiers: deployed in tenant's namespace - Shared tiers: deployed in tenant's namespace
- Dedicated tiers: deployed in tenant's cluster - Dedicated tiers: deployed in tenant's cluster
@@ -359,7 +359,7 @@ The SaaS API gateway routes `/t/{tenant}/api/*` to the correct server instance.
### Agent Connection ### Agent Connection
- Agent bootstrap tokens generated by the SaaS platform - Agent bootstrap tokens generated by the SaaS platform
- Agents connect directly to their tenant's cameleer3-server instance - Agents connect directly to their tenant's cameleer-server instance
- Agent auto-injected into customer Camel apps deployed on the platform - Agent auto-injected into customer Camel apps deployed on the platform
- External agents (customer-hosted Camel apps) can also connect using bootstrap tokens - External agents (customer-hosted Camel apps) can also connect using bootstrap tokens
@@ -448,7 +448,7 @@ K8s NetworkPolicies per tenant namespace:
- **Allow:** tenant namespace → shared PostgreSQL/OpenSearch (authenticated per-tenant credentials) - **Allow:** tenant namespace → shared PostgreSQL/OpenSearch (authenticated per-tenant credentials)
- **Allow:** tenant namespace → public internet (Camel app external connectivity) - **Allow:** tenant namespace → public internet (Camel app external connectivity)
- **Allow:** SaaS platform namespace → all tenant namespaces (management access) - **Allow:** SaaS platform namespace → all tenant namespaces (management access)
- **Allow:** tenant Camel apps → tenant cameleer3-server (intra-namespace) - **Allow:** tenant Camel apps → tenant cameleer-server (intra-namespace)
### Zero-Trust Tenant Boundary ### Zero-Trust Tenant Boundary
@@ -546,7 +546,7 @@ Completely separate from tenant observability data.
- TLS certificate expiry < 14 days - TLS certificate expiry < 14 days
- Metering pipeline stale > 1 hour - Metering pipeline stale > 1 hour
- Disk usage > 80% on any PV - Disk usage > 80% on any PV
- Tenant cameleer3-server unhealthy > 5 minutes - Tenant cameleer-server unhealthy > 5 minutes
- OOMKill on any tenant workload - OOMKill on any tenant workload
### Dashboards ### Dashboards
@@ -577,7 +577,7 @@ K8s Metrics → Metrics Collector → Usage Aggregator (hourly) → Stripe Usage
|-----------|------|--------| |-----------|------|--------|
| CPU | core·hours | K8s metrics (namespace aggregate) | | CPU | core·hours | K8s metrics (namespace aggregate) |
| RAM | GB·hours | K8s metrics (namespace aggregate) | | RAM | GB·hours | K8s metrics (namespace aggregate) |
| Data volume | GB ingested | cameleer3-server reports | | Data volume | GB ingested | cameleer-server reports |
- Aggregated per tenant, per hour, stored in platform DB before Stripe submission - Aggregated per tenant, per hour, stored in platform DB before Stripe submission
- Idempotent aggregation (safe to re-run) - Idempotent aggregation (safe to re-run)
@@ -613,7 +613,7 @@ K8s Metrics → Metrics Collector → Usage Aggregator (hourly) → Stripe Usage
| **App → Status** | Pod health, resource usage, agent connection, events | | **App → Status** | Pod health, resource usage, agent connection, events |
| **App → Logs** | Live stdout/stderr stream | | **App → Logs** | Live stdout/stderr stream |
| **App → Versions** | Image history, promotion log, rollback | | **App → Versions** | Image history, promotion log, rollback |
| **Observe** | Embedded cameleer3-server UI (topology, traces, lineage, correlation, debugger, replay) | | **Observe** | Embedded cameleer-server UI (topology, traces, lineage, correlation, debugger, replay) |
| **Team** | Users, roles, invites | | **Team** | Users, roles, invites |
| **Settings** | Tenant config, SSO/OIDC, vault connections | | **Settings** | Tenant config, SSO/OIDC, vault connections |
| **Billing** | Usage, invoices, plan management | | **Billing** | Usage, invoices, plan management |
@@ -621,7 +621,7 @@ K8s Metrics → Metrics Collector → Usage Aggregator (hourly) → Stripe Usage
### Design ### Design
- SaaS shell built with `@cameleer/design-system` - SaaS shell built with `@cameleer/design-system`
- cameleer3-server React UI embedded (same design system, visual consistency) - cameleer-server React UI embedded (same design system, visual consistency)
- Responsive but desktop-primary (observability tooling is a desktop workflow) - Responsive but desktop-primary (observability tooling is a desktop workflow)
--- ---
@@ -681,4 +681,4 @@ K8s Metrics → Metrics Collector → Usage Aggregator (hourly) → Stripe Usage
| 12 | Platform Operations & Self-Monitoring | epic, ops | | 12 | Platform Operations & Self-Monitoring | epic, ops |
| 13 | MOAT: Exchange Replay | epic, observability | | 13 | MOAT: Exchange Replay | epic, observability |
MOAT features (Debugger, Lineage, Correlation) tracked in cameleer/cameleer3 #57#72. MOAT features (Debugger, Lineage, Correlation) tracked in cameleer/cameleer #57#72.

View File

@@ -27,7 +27,7 @@ Key constraints:
| **Identity & Auth** | **Logto** | MPL-2.0 | Lightest IdP (2 containers, ~0.5-1 GB). Orgs, RBAC, M2M tokens, OIDC/SSO federation all in OSS. Replaces ~3-4 months of custom auth build (OIDC, SSO, teams, invites, MFA, password reset, custom roles). | | **Identity & Auth** | **Logto** | MPL-2.0 | Lightest IdP (2 containers, ~0.5-1 GB). Orgs, RBAC, M2M tokens, OIDC/SSO federation all in OSS. Replaces ~3-4 months of custom auth build (OIDC, SSO, teams, invites, MFA, password reset, custom roles). |
| **Reverse Proxy** | **Traefik** | MIT | Native Docker provider (labels) and K8s provider (IngressRoute CRDs). Same mental model in both environments. Already on the k3s cluster. ForwardAuth middleware for tenant-aware routing. Auto-HTTPS via Let's Encrypt. ~256 MB RAM. | | **Reverse Proxy** | **Traefik** | MIT | Native Docker provider (labels) and K8s provider (IngressRoute CRDs). Same mental model in both environments. Already on the k3s cluster. ForwardAuth middleware for tenant-aware routing. Auto-HTTPS via Let's Encrypt. ~256 MB RAM. |
| **Database** | **PostgreSQL** | PostgreSQL License | Already chosen. Platform data + Logto data (separate schemas). | | **Database** | **PostgreSQL** | PostgreSQL License | Already chosen. Platform data + Logto data (separate schemas). |
| **Trace/Metrics Storage** | **ClickHouse** | Apache-2.0 | Replaced OpenSearch in the cameleer3-server stack. Columnar OLAP, excellent for time-series observability data. | | **Trace/Metrics Storage** | **ClickHouse** | Apache-2.0 | Replaced OpenSearch in the cameleer-server stack. Columnar OLAP, excellent for time-series observability data. |
| **Schema Migrations** | **Flyway** | Apache-2.0 | Already in place. | | **Schema Migrations** | **Flyway** | Apache-2.0 | Already in place. |
| **Billing (subscriptions)** | **Stripe** | N/A (API) | Start with Stripe Checkout for fixed-tier subscriptions. No custom billing infrastructure day 1. | | **Billing (subscriptions)** | **Stripe** | N/A (API) | Start with Stripe Checkout for fixed-tier subscriptions. No custom billing infrastructure day 1. |
| **Billing (usage metering)** | **Lago** (deferred) | AGPL-3.0 | Purpose-built for event-based metering. 8 containers — deploy only when usage-based pricing launches. Design event model with Lago's API shape in mind from day 1. Integrate via API only (keeps AGPL safe). | | **Billing (usage metering)** | **Lago** (deferred) | AGPL-3.0 | Purpose-built for event-based metering. 8 containers — deploy only when usage-based pricing launches. Design event model with Lago's API shape in mind from day 1. Integrate via API only (keeps AGPL safe). |
@@ -42,14 +42,14 @@ Key constraints:
| Subsystem | Why Build | | Subsystem | Why Build |
|---|---| |---|---|
| **License signing & validation** | Ed25519 signed JWT with tier, features, limits, expiry. Dual mode: online API check + offline signed file. No off-the-shelf tool does this. Core IP. | | **License signing & validation** | Ed25519 signed JWT with tier, features, limits, expiry. Dual mode: online API check + offline signed file. No off-the-shelf tool does this. Core IP. |
| **Agent bootstrap tokens** | Tightly coupled to the cameleer3 agent protocol (PROTOCOL.md). Custom Ed25519 tokens for agent registration. | | **Agent bootstrap tokens** | Tightly coupled to the cameleer agent protocol (PROTOCOL.md). Custom Ed25519 tokens for agent registration. |
| **Tenant lifecycle** | CRUD, configuration, status management. Core business logic. User management (invites, teams, roles) is delegated to Logto's organization model. | | **Tenant lifecycle** | CRUD, configuration, status management. Core business logic. User management (invites, teams, roles) is delegated to Logto's organization model. |
| **Runtime orchestration** | The core of the "managed Camel runtime" product. `RuntimeOrchestrator` interface with Docker and K8s implementations. No off-the-shelf tool does "managed Camel runtime with agent injection." | | **Runtime orchestration** | The core of the "managed Camel runtime" product. `RuntimeOrchestrator` interface with Docker and K8s implementations. No off-the-shelf tool does "managed Camel runtime with agent injection." |
| **Image build pipeline** | Templated Dockerfile: JRE + cameleer3-agent.jar + customer JAR + `-javaagent` flag. Simple but custom. | | **Image build pipeline** | Templated Dockerfile: JRE + cameleer-agent.jar + customer JAR + `-javaagent` flag. Simple but custom. |
| **Feature gating** | Tier-based feature gating logic. Which features are available at which tier. Business logic. | | **Feature gating** | Tier-based feature gating logic. Which features are available at which tier. Business logic. |
| **Billing integration** | Stripe API calls, subscription lifecycle, webhook handling. Thin integration layer. | | **Billing integration** | Stripe API calls, subscription lifecycle, webhook handling. Thin integration layer. |
| **Observability proxy** | Routing authenticated requests to tenant-specific cameleer3-server instances. | | **Observability proxy** | Routing authenticated requests to tenant-specific cameleer-server instances. |
| **MOAT features** | Debugger, Lineage, Correlation — the defensible product. Built in cameleer3 agent + server. | | **MOAT features** | Debugger, Lineage, Correlation — the defensible product. Built in cameleer agent + server. |
### SKIP / DEFER ### SKIP / DEFER
@@ -74,7 +74,7 @@ Key constraints:
+--------+---------------------+------------------------+ +--------+---------------------+------------------------+
| | | |
+--------v--------+ +---------v-----------+ +--------v--------+ +---------v-----------+
| cameleer-saas | | cameleer3-server | | cameleer-saas | | cameleer-server |
| (Spring Boot) | | (observability) | | (Spring Boot) | | (observability) |
| Control plane | | Per-tenant instance | | Control plane | | Per-tenant instance |
+---+-------+-----+ +----------+----------+ +---+-------+-----+ +----------+----------+
@@ -99,10 +99,10 @@ API request:
-> Traefik forwards to upstream service -> Traefik forwards to upstream service
Machine auth (agent bootstrap): Machine auth (agent bootstrap):
cameleer3-agent -> cameleer-saas /api/agent/register cameleer-agent -> cameleer-saas /api/agent/register
-> Validates bootstrap token (Ed25519) -> Validates bootstrap token (Ed25519)
-> Issues agent session token -> Issues agent session token
-> Agent connects to cameleer3-server -> Agent connects to cameleer-server
``` ```
Logto handles all user-facing identity. The cameleer-saas app handles machine-to-machine auth (agent tokens, license tokens) using Ed25519. Logto handles all user-facing identity. The cameleer-saas app handles machine-to-machine auth (agent tokens, license tokens) using Ed25519.
@@ -137,9 +137,9 @@ Customer uploads JAR
-> Validation (file type, size, SHA-256, security scan) -> Validation (file type, size, SHA-256, security scan)
-> Templated Dockerfile generation: -> Templated Dockerfile generation:
FROM eclipse-temurin:21-jre-alpine FROM eclipse-temurin:21-jre-alpine
COPY cameleer3-agent.jar /opt/agent/ COPY cameleer-agent.jar /opt/agent/
COPY customer-app.jar /opt/app/ COPY customer-app.jar /opt/app/
ENTRYPOINT ["java", "-javaagent:/opt/agent/cameleer3-agent.jar", "-jar", "/opt/app/customer-app.jar"] ENTRYPOINT ["java", "-javaagent:/opt/agent/cameleer-agent.jar", "-jar", "/opt/app/customer-app.jar"]
-> Build: -> Build:
Docker mode: docker build via docker-java (local image cache) Docker mode: docker build via docker-java (local image cache)
K8s mode: Kaniko Job -> push to registry K8s mode: Kaniko Job -> push to registry
@@ -152,7 +152,7 @@ Customer uploads JAR
- **Schema-per-tenant** in PostgreSQL for platform data isolation. - **Schema-per-tenant** in PostgreSQL for platform data isolation.
- **Logto organizations** map 1:1 to tenants. Logto handles user-tenant membership. - **Logto organizations** map 1:1 to tenants. Logto handles user-tenant membership.
- **ClickHouse** data partitioned by tenant_id. - **ClickHouse** data partitioned by tenant_id.
- **cameleer3-server** instances are per-tenant (separate containers/pods). - **cameleer-server** instances are per-tenant (separate containers/pods).
- **K8s bonus:** Namespace-per-tenant for network isolation, resource quotas. - **K8s bonus:** Namespace-per-tenant for network isolation, resource quotas.
### Environment Model ### Environment Model
@@ -232,8 +232,8 @@ services:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.auth.rule=PathPrefix(`/auth`) - traefik.http.routers.auth.rule=PathPrefix(`/auth`)
cameleer3-server: cameleer-server:
image: gitea.siegeln.net/cameleer/cameleer3-server:${VERSION} image: gitea.siegeln.net/cameleer/cameleer-server:${VERSION}
environment: environment:
- CLICKHOUSE_URL=jdbc:clickhouse://clickhouse:8123/cameleer - CLICKHOUSE_URL=jdbc:clickhouse://clickhouse:8123/cameleer
labels: labels:
@@ -312,9 +312,9 @@ volumes:
### Phase 4: Observability Pipeline ### Phase 4: Observability Pipeline
**Goal:** Customer can see traces, metrics, and route topology for deployed apps. **Goal:** Customer can see traces, metrics, and route topology for deployed apps.
- Connect cameleer3-server to customer app containers - Connect cameleer-server to customer app containers
- ClickHouse tenant-scoped data partitioning - ClickHouse tenant-scoped data partitioning
- Observability API proxy (tenant-aware routing to cameleer3-server) - Observability API proxy (tenant-aware routing to cameleer-server)
- Basic topology graph endpoint - Basic topology graph endpoint
- Agent ↔ server connectivity verification - Agent ↔ server connectivity verification
@@ -367,13 +367,13 @@ volumes:
1. Upload a sample Camel JAR via API 1. Upload a sample Camel JAR via API
2. Platform builds container image 2. Platform builds container image
3. Deploy to "dev" environment 3. Deploy to "dev" environment
4. Container starts with cameleer3 agent attached 4. Container starts with cameleer agent attached
5. App is reachable via Traefik routing 5. App is reachable via Traefik routing
6. Logs are accessible via API 6. Logs are accessible via API
7. Deploy same image to "prod" with different config 7. Deploy same image to "prod" with different config
### Phase 4 Verification ### Phase 4 Verification
1. Running Camel app sends traces to cameleer3-server 1. Running Camel app sends traces to cameleer-server
2. Traces visible in ClickHouse with correct tenant_id 2. Traces visible in ClickHouse with correct tenant_id
3. Topology graph shows route structure 3. Topology graph shows route structure
4. Different tenant cannot see another tenant's data 4. Different tenant cannot see another tenant's data
@@ -393,7 +393,7 @@ docker compose up -d
# Create tenant + user via API/Logto # Create tenant + user via API/Logto
# Upload sample Camel JAR # Upload sample Camel JAR
# Deploy to environment # Deploy to environment
# Verify agent connects to cameleer3-server # Verify agent connects to cameleer-server
# Verify traces in ClickHouse # Verify traces in ClickHouse
# Verify observability API returns data # Verify observability API returns data
``` ```

View File

@@ -7,7 +7,7 @@
## Context ## Context
Phase 2 delivered multi-tenancy, identity (Logto OIDC), and license management. The platform can create tenants and issue licenses, but there is nothing to run yet. Phase 3 is the core product differentiator: customers upload a Camel JAR, the platform builds an immutable container image with the cameleer3 agent auto-injected, and deploys it to a logical environment. This is "managed Camel runtime" — similar to Coolify or MuleSoft CloudHub, but purpose-built for Apache Camel with deep observability. Phase 2 delivered multi-tenancy, identity (Logto OIDC), and license management. The platform can create tenants and issue licenses, but there is nothing to run yet. Phase 3 is the core product differentiator: customers upload a Camel JAR, the platform builds an immutable container image with the cameleer agent auto-injected, and deploys it to a logical environment. This is "managed Camel runtime" — similar to Coolify or MuleSoft CloudHub, but purpose-built for Apache Camel with deep observability.
Docker-first. The `KubernetesRuntimeOrchestrator` is deferred to Phase 5. Docker-first. The `KubernetesRuntimeOrchestrator` is deferred to Phase 5.
@@ -23,10 +23,10 @@ Docker-first. The `KubernetesRuntimeOrchestrator` is deferred to Phase 5.
| Deployment model | Async with polling | Image builds are inherently slow. Deploy returns immediately with deployment ID. Client polls for status. | | Deployment model | Async with polling | Image builds are inherently slow. Deploy returns immediately with deployment ID. Client polls for status. |
| Entity hierarchy | Environment → App → Deployment | User thinks "I'm in dev, deploy my app." Environment is the workspace context. | | Entity hierarchy | Environment → App → Deployment | User thinks "I'm in dev, deploy my app." Environment is the workspace context. |
| Environment provisioning | Hybrid auto + manual | Every tenant gets a `default` environment on creation. Additional environments created manually, tier limit enforced. | | Environment provisioning | Hybrid auto + manual | Every tenant gets a `default` environment on creation. Additional environments created manually, tier limit enforced. |
| Cross-environment isolation | Logical (not network) | Docker single-tenant mode — customer owns the stack. Data separated by `environmentId` in cameleer3-server. Network isolation is a K8s Phase 5 concern. | | Cross-environment isolation | Logical (not network) | Docker single-tenant mode — customer owns the stack. Data separated by `environmentId` in cameleer-server. Network isolation is a K8s Phase 5 concern. |
| Container networking | Shared `cameleer` bridge network | Customer containers join the existing network. Agent reaches cameleer3-server at `http://cameleer3-server:8081`. | | Container networking | Shared `cameleer` bridge network | Customer containers join the existing network. Agent reaches cameleer-server at `http://cameleer-server:8081`. |
| Container naming | `{tenant-slug}-{env-slug}-{app-slug}` | Human-readable, unique, identifies tenant+environment+app at a glance. | | Container naming | `{tenant-slug}-{env-slug}-{app-slug}` | Human-readable, unique, identifies tenant+environment+app at a glance. |
| Bootstrap tokens | Shared `CAMELEER_AUTH_TOKEN` from cameleer3-server config | Platform reads the existing token and injects it into customer containers. Environment separation via agent `environmentId` claim, not token. Per-environment tokens deferred to K8s Phase 5. | | Bootstrap tokens | Shared `CAMELEER_AUTH_TOKEN` from cameleer-server config | Platform reads the existing token and injects it into customer containers. Environment separation via agent `environmentId` claim, not token. Per-environment tokens deferred to K8s Phase 5. |
| Health checking | Agent health endpoint (port 9464) | Guaranteed to exist, no user config needed. User-defined health endpoints deferred. | | Health checking | Agent health endpoint (port 9464) | Guaranteed to exist, no user config needed. User-defined health endpoints deferred. |
| Inbound HTTP routing | Not in Phase 3 | Most Camel apps are consumers (queues, polls), not servers. Traefik routing for customer apps deferred to Phase 4/4.5. | | Inbound HTTP routing | Not in Phase 3 | Most Camel apps are consumers (queues, polls), not servers. Traefik routing for customer apps deferred to Phase 4/4.5. |
| Container logs | Captured via docker-java, written to ClickHouse | Unified log query surface from day 1. Same pattern future app logs will use. | | Container logs | Captured via docker-java, written to ClickHouse | Unified log query surface from day 1. Same pattern future app logs will use. |
@@ -157,7 +157,7 @@ Uses `com.github.docker-java:docker-java` library. Connects via Docker socket (`
- Environment variables: - Environment variables:
- `CAMELEER_AUTH_TOKEN={bootstrap-token}` - `CAMELEER_AUTH_TOKEN={bootstrap-token}`
- `CAMELEER_EXPORT_TYPE=HTTP` - `CAMELEER_EXPORT_TYPE=HTTP`
- `CAMELEER_EXPORT_ENDPOINT=http://cameleer3-server:8081` - `CAMELEER_EXPORT_ENDPOINT=http://cameleer-server:8081`
- `CAMELEER_APPLICATION_ID={app-slug}` - `CAMELEER_APPLICATION_ID={app-slug}`
- `CAMELEER_ENVIRONMENT_ID={env-slug}` - `CAMELEER_ENVIRONMENT_ID={env-slug}`
- `CAMELEER_DISPLAY_NAME={tenant-slug}-{env-slug}-{app-slug}` - `CAMELEER_DISPLAY_NAME={tenant-slug}-{env-slug}-{app-slug}`
@@ -182,7 +182,7 @@ A pre-built Docker image containing everything except the customer JAR:
FROM eclipse-temurin:21-jre-alpine FROM eclipse-temurin:21-jre-alpine
WORKDIR /app WORKDIR /app
COPY cameleer3-agent-{version}-shaded.jar /app/agent.jar COPY cameleer-agent-{version}-shaded.jar /app/agent.jar
ENTRYPOINT exec java \ ENTRYPOINT exec java \
-Dcameleer.export.type=${CAMELEER_EXPORT_TYPE:-HTTP} \ -Dcameleer.export.type=${CAMELEER_EXPORT_TYPE:-HTTP} \
@@ -250,11 +250,11 @@ ORDER BY (tenant_id, environment_id, app_id, timestamp);
### Bootstrap Token Handling ### Bootstrap Token Handling
In Docker single-tenant mode, all environments share the single cameleer3-server instance and its single `CAMELEER_AUTH_TOKEN`. The platform reads this token from its own configuration (`cameleer.runtime.bootstrap-token` / `CAMELEER_AUTH_TOKEN` env var) and injects it into every customer container. No changes to cameleer3-server are needed. In Docker single-tenant mode, all environments share the single cameleer-server instance and its single `CAMELEER_AUTH_TOKEN`. The platform reads this token from its own configuration (`cameleer.runtime.bootstrap-token` / `CAMELEER_AUTH_TOKEN` env var) and injects it into every customer container. No changes to cameleer-server are needed.
Environment-level data separation happens at the agent registration level — the agent sends its `environmentId` claim when it registers, and cameleer3-server uses that to scope all data. The bootstrap token is the same across environments in a Docker stack. Environment-level data separation happens at the agent registration level — the agent sends its `environmentId` claim when it registers, and cameleer-server uses that to scope all data. The bootstrap token is the same across environments in a Docker stack.
The `bootstrap_token` column on the environment entity stores the token value used for that environment's containers. In Docker mode this is the same shared value for all environments. In K8s mode (Phase 5), each environment could have its own cameleer3-server instance with a unique token, enabling true per-environment token isolation. The `bootstrap_token` column on the environment entity stores the token value used for that environment's containers. In Docker mode this is the same shared value for all environments. In K8s mode (Phase 5), each environment could have its own cameleer-server instance with a unique token, enabling true per-environment token isolation.
## API Surface ## API Surface
@@ -354,7 +354,7 @@ The cameleer-saas service needs:
- JAR storage volume: `jardata:/data/jars` - JAR storage volume: `jardata:/data/jars`
- `cameleer-runtime-base` image must be available (pre-pulled or built locally) - `cameleer-runtime-base` image must be available (pre-pulled or built locally)
The cameleer3-server `CAMELEER_AUTH_TOKEN` is read by cameleer-saas from shared environment config and injected into customer containers. The cameleer-server `CAMELEER_AUTH_TOKEN` is read by cameleer-saas from shared environment config and injected into customer containers.
New volume in docker-compose.yml: New volume in docker-compose.yml:
```yaml ```yaml
@@ -413,7 +413,7 @@ cameleer:
3. Poll `GET /api/apps/{aid}/deployments/{did}` — status transitions: `BUILDING` → `STARTING` → `RUNNING` 3. Poll `GET /api/apps/{aid}/deployments/{did}` — status transitions: `BUILDING` → `STARTING` → `RUNNING`
4. Container visible in `docker ps` as `{tenant}-{env}-{app}` 4. Container visible in `docker ps` as `{tenant}-{env}-{app}`
5. Container is on the `cameleer` network 5. Container is on the `cameleer` network
6. cameleer3 agent registers with cameleer3-server (visible in server logs) 6. cameleer agent registers with cameleer-server (visible in server logs)
7. Agent health endpoint responds on port 9464 7. Agent health endpoint responds on port 9464
8. Container logs appear in ClickHouse `container_logs` table 8. Container logs appear in ClickHouse `container_logs` table
9. `GET /api/apps/{aid}/logs` returns log entries 9. `GET /api/apps/{aid}/logs` returns log entries

View File

@@ -7,18 +7,18 @@
## Context ## Context
Phase 3 delivered the managed Camel runtime: customers upload a JAR, the platform builds a container with the cameleer3 agent injected, and deploys it. The agent connects to cameleer3-server and sends traces, metrics, diagrams, and logs to ClickHouse. But there is no way for the user to see this data yet, and customer apps that expose HTTP endpoints are not reachable. Phase 3 delivered the managed Camel runtime: customers upload a JAR, the platform builds a container with the cameleer agent injected, and deploys it. The agent connects to cameleer-server and sends traces, metrics, diagrams, and logs to ClickHouse. But there is no way for the user to see this data yet, and customer apps that expose HTTP endpoints are not reachable.
Phase 4 completes the loop: deploy an app, hit its endpoint, see the traces in the dashboard. Phase 4 completes the loop: deploy an app, hit its endpoint, see the traces in the dashboard.
cameleer3-server already has the complete observability stack — ClickHouse schemas with `tenant_id` partitioning, full search/stats/diagram/log REST APIs, and a React SPA dashboard. Phase 4 is a **wiring phase**, not a build-from-scratch phase. cameleer-server already has the complete observability stack — ClickHouse schemas with `tenant_id` partitioning, full search/stats/diagram/log REST APIs, and a React SPA dashboard. Phase 4 is a **wiring phase**, not a build-from-scratch phase.
## Key Decisions ## Key Decisions
| Decision | Choice | Rationale | | Decision | Choice | Rationale |
|----------|--------|-----------| |----------|--------|-----------|
| Observability UI | Serve existing cameleer3-server React SPA via Traefik | Already built. SaaS management UI is Phase 9 — observability UI is not SaaS-specific. | | Observability UI | Serve existing cameleer-server React SPA via Traefik | Already built. SaaS management UI is Phase 9 — observability UI is not SaaS-specific. |
| API access | Traefik routes directly to cameleer3-server with forward-auth | No proxy layer needed. Forward-auth validates user, injects headers. Server API works as-is. | | API access | Traefik routes directly to cameleer-server with forward-auth | No proxy layer needed. Forward-auth validates user, injects headers. Server API works as-is. |
| Server changes | None | Single-tenant Docker mode works out of the box. `CAMELEER_TENANT_ID` env var already supported. | | Server changes | None | Single-tenant Docker mode works out of the box. `CAMELEER_TENANT_ID` env var already supported. |
| Agent changes | None | Agent already sends `applicationId`, `environmentId`, connects to `CAMELEER_EXPORT_ENDPOINT`. | | Agent changes | None | Agent already sends `applicationId`, `environmentId`, connects to `CAMELEER_EXPORT_ENDPOINT`. |
| Tenant ID | Set `CAMELEER_TENANT_ID` to tenant slug in Docker Compose | Tags ClickHouse data with the real tenant identity from day one. Avoids `'default'` → real-id migration later. | | Tenant ID | Set `CAMELEER_TENANT_ID` to tenant slug in Docker Compose | Tags ClickHouse data with the real tenant identity from day one. Avoids `'default'` → real-id migration later. |
@@ -27,16 +27,16 @@ cameleer3-server already has the complete observability stack — ClickHouse sch
## What's Already Working (Phase 3) ## What's Already Working (Phase 3)
- Customer containers on the `cameleer` bridge network - Customer containers on the `cameleer` bridge network
- Agent configured: `CAMELEER_AUTH_TOKEN`, `CAMELEER_EXPORT_ENDPOINT=http://cameleer3-server:8081`, `CAMELEER_APPLICATION_ID`, `CAMELEER_ENVIRONMENT_ID` - Agent configured: `CAMELEER_AUTH_TOKEN`, `CAMELEER_EXPORT_ENDPOINT=http://cameleer-server:8081`, `CAMELEER_APPLICATION_ID`, `CAMELEER_ENVIRONMENT_ID`
- cameleer3-server writes traces/metrics/diagrams/logs to ClickHouse - cameleer-server writes traces/metrics/diagrams/logs to ClickHouse
- Traefik routes `/observe/*` to cameleer3-server with forward-auth middleware - Traefik routes `/observe/*` to cameleer-server with forward-auth middleware
- Forward-auth endpoint at `/auth/verify` validates JWT, returns `X-Tenant-Id`, `X-User-Id`, `X-User-Email` headers - Forward-auth endpoint at `/auth/verify` validates JWT, returns `X-Tenant-Id`, `X-User-Id`, `X-User-Email` headers
## Component 1: Serve cameleer3-server Dashboard ## Component 1: Serve cameleer-server Dashboard
### Traefik Routing ### Traefik Routing
Add Traefik labels to the cameleer3-server service in `docker-compose.yml` to serve the React SPA: Add Traefik labels to the cameleer-server service in `docker-compose.yml` to serve the React SPA:
```yaml ```yaml
# Existing (Phase 3): # Existing (Phase 3):
@@ -49,23 +49,23 @@ Add Traefik labels to the cameleer3-server service in `docker-compose.yml` to se
- traefik.http.services.dashboard.loadbalancer.server.port=8080 - traefik.http.services.dashboard.loadbalancer.server.port=8080
``` ```
The cameleer3-server SPA is served from its own embedded web server. The SPA already calls the server's API endpoints at relative paths — the existing `/observe/*` Traefik route handles those requests with forward-auth. The cameleer-server SPA is served from its own embedded web server. The SPA already calls the server's API endpoints at relative paths — the existing `/observe/*` Traefik route handles those requests with forward-auth.
**Note:** If the cameleer3-server SPA expects to be served from `/` rather than `/dashboard`, a Traefik StripPrefix middleware may be needed: **Note:** If the cameleer-server SPA expects to be served from `/` rather than `/dashboard`, a Traefik StripPrefix middleware may be needed:
```yaml ```yaml
- traefik.http.middlewares.dashboard-strip.stripprefix.prefixes=/dashboard - traefik.http.middlewares.dashboard-strip.stripprefix.prefixes=/dashboard
- traefik.http.routers.dashboard.middlewares=forward-auth,dashboard-strip - traefik.http.routers.dashboard.middlewares=forward-auth,dashboard-strip
``` ```
This depends on how the cameleer3-server SPA is configured (base path). To be verified during implementation. This depends on how the cameleer-server SPA is configured (base path). To be verified during implementation.
### CAMELEER_TENANT_ID Configuration ### CAMELEER_TENANT_ID Configuration
Set `CAMELEER_TENANT_ID` on the cameleer3-server service so all ingested data is tagged with the real tenant slug: Set `CAMELEER_TENANT_ID` on the cameleer-server service so all ingested data is tagged with the real tenant slug:
```yaml ```yaml
cameleer3-server: cameleer-server:
environment: environment:
CAMELEER_TENANT_ID: ${CAMELEER_TENANT_SLUG:-default} CAMELEER_TENANT_ID: ${CAMELEER_TENANT_SLUG:-default}
``` ```
@@ -76,7 +76,7 @@ Add `CAMELEER_TENANT_SLUG` to `.env.example`.
## Component 2: Agent Connectivity Verification ## Component 2: Agent Connectivity Verification
New endpoint in cameleer-saas to check whether a deployed app's agent has successfully registered with cameleer3-server and is sending data. New endpoint in cameleer-saas to check whether a deployed app's agent has successfully registered with cameleer-server and is sending data.
### API ### API
@@ -100,15 +100,15 @@ public record AgentStatusResponse(
### Implementation ### Implementation
`AgentStatusService` in cameleer-saas calls cameleer3-server's agent registry API: `AgentStatusService` in cameleer-saas calls cameleer-server's agent registry API:
``` ```
GET http://cameleer3-server:8081/api/v1/agents GET http://cameleer-server:8081/api/v1/agents
``` ```
This returns the list of registered agents. The service filters by `applicationId` matching the app's slug and `environmentId` matching the environment's slug. This returns the list of registered agents. The service filters by `applicationId` matching the app's slug and `environmentId` matching the environment's slug.
If the cameleer3-server doesn't expose a public agent listing endpoint, the alternative is to query ClickHouse directly for recent data: If the cameleer-server doesn't expose a public agent listing endpoint, the alternative is to query ClickHouse directly for recent data:
```sql ```sql
SELECT max(timestamp) as last_seen SELECT max(timestamp) as last_seen
@@ -212,17 +212,17 @@ cameleer:
### Startup Verification ### Startup Verification
On application startup, cameleer-saas verifies that cameleer3-server is reachable: On application startup, cameleer-saas verifies that cameleer-server is reachable:
```java ```java
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void verifyConnectivity() { public void verifyConnectivity() {
// HTTP GET http://cameleer3-server:8081/actuator/health // HTTP GET http://cameleer-server:8081/actuator/health
// Log result: "cameleer3-server connectivity: OK" or "FAILED: ..." // Log result: "cameleer-server connectivity: OK" or "FAILED: ..."
} }
``` ```
This is a best-effort check, not a hard dependency. If cameleer3-server is not yet running (e.g., starting up), the SaaS platform still starts. The check is logged for diagnostics. This is a best-effort check, not a hard dependency. If cameleer-server is not yet running (e.g., starting up), the SaaS platform still starts. The check is logged for diagnostics.
### ClickHouse Data Verification ### ClickHouse Data Verification
@@ -259,10 +259,10 @@ This requires cameleer-saas to query ClickHouse directly (the `clickHouseDataSou
## Docker Compose Changes ## Docker Compose Changes
### cameleer3-server labels (add dashboard route) ### cameleer-server labels (add dashboard route)
```yaml ```yaml
cameleer3-server: cameleer-server:
environment: environment:
CAMELEER_TENANT_ID: ${CAMELEER_TENANT_SLUG:-default} CAMELEER_TENANT_ID: ${CAMELEER_TENANT_SLUG:-default}
labels: labels:
@@ -304,18 +304,18 @@ cameleer:
1. Deploy a sample Camel REST app with `exposedPort: 8080` 1. Deploy a sample Camel REST app with `exposedPort: 8080`
2. `curl http://order-svc.default.acme.localhost` hits the Camel app 2. `curl http://order-svc.default.acme.localhost` hits the Camel app
3. The Camel route processes the request 3. The Camel route processes the request
4. cameleer3 agent captures the trace and sends to cameleer3-server 4. cameleer agent captures the trace and sends to cameleer-server
5. `GET /api/apps/{appId}/agent-status` shows `registered: true, state: ACTIVE` 5. `GET /api/apps/{appId}/agent-status` shows `registered: true, state: ACTIVE`
6. `GET /api/apps/{appId}/observability-status` shows `hasTraces: true` 6. `GET /api/apps/{appId}/observability-status` shows `hasTraces: true`
7. Open `http://localhost/dashboard` — cameleer3-server SPA loads 7. Open `http://localhost/dashboard` — cameleer-server SPA loads
8. Traces visible in the dashboard for the deployed app 8. Traces visible in the dashboard for the deployed app
9. Route topology graph shows the Camel route structure 9. Route topology graph shows the Camel route structure
10. `CAMELEER_TENANT_ID` is set to the tenant slug in ClickHouse data 10. `CAMELEER_TENANT_ID` is set to the tenant slug in ClickHouse data
## What Phase 4 Does NOT Touch ## What Phase 4 Does NOT Touch
- No changes to cameleer3-server code (works as-is for single-tenant Docker mode) - No changes to cameleer-server code (works as-is for single-tenant Docker mode)
- No changes to the cameleer3 agent - No changes to the cameleer agent
- No new ClickHouse schemas (cameleer3-server manages its own) - No new ClickHouse schemas (cameleer-server manages its own)
- No SaaS management UI (Phase 9) - No SaaS management UI (Phase 9)
- No K8s-specific changes (Phase 5) - No K8s-specific changes (Phase 5)

View File

@@ -7,19 +7,19 @@
## Context ## Context
Phases 1-4 built the complete backend: tenants, licensing, environments, app deployment with JAR upload, async deployment pipeline, container logs, agent status, observability status, and inbound HTTP routing. The cameleer3-server observability dashboard is already served at `/dashboard`. But there is no management UI — all operations require curl/API calls. Phases 1-4 built the complete backend: tenants, licensing, environments, app deployment with JAR upload, async deployment pipeline, container logs, agent status, observability status, and inbound HTTP routing. The cameleer-server observability dashboard is already served at `/dashboard`. But there is no management UI — all operations require curl/API calls.
Phase 9 adds the SaaS management shell: a React SPA for managing tenants, environments, apps, and deployments. The observability UI is already handled by cameleer3-server — this shell covers everything else. Phase 9 adds the SaaS management shell: a React SPA for managing tenants, environments, apps, and deployments. The observability UI is already handled by cameleer-server — this shell covers everything else.
## Key Decisions ## Key Decisions
| Decision | Choice | Rationale | | Decision | Choice | Rationale |
|----------|--------|-----------| |----------|--------|-----------|
| Location | `ui/` directory in cameleer-saas repo | Matches cameleer3-server pattern. Single build pipeline. Spring Boot serves the SPA. | | Location | `ui/` directory in cameleer-saas repo | Matches cameleer-server pattern. Single build pipeline. Spring Boot serves the SPA. |
| Relationship to dashboard | Two separate SPAs, linked via navigation | SaaS shell at `/`, observability at `/dashboard`. Same design system = cohesive feel. No coupling. | | Relationship to dashboard | Two separate SPAs, linked via navigation | SaaS shell at `/`, observability at `/dashboard`. Same design system = cohesive feel. No coupling. |
| Layout | Sidebar navigation | Consistent with cameleer3-server dashboard. Same AppShell pattern from design system. | | Layout | Sidebar navigation | Consistent with cameleer-server dashboard. Same AppShell pattern from design system. |
| Auth | Shared Logto OIDC session | Same client ID, same localStorage keys. True SSO between SaaS shell and observability dashboard. | | Auth | Shared Logto OIDC session | Same client ID, same localStorage keys. True SSO between SaaS shell and observability dashboard. |
| Tech stack | React 19 + Vite + React Router + Zustand + TanStack Query | Identical to cameleer3-server SPA. Same patterns, same libraries, same conventions. | | Tech stack | React 19 + Vite + React Router + Zustand + TanStack Query | Identical to cameleer-server SPA. Same patterns, same libraries, same conventions. |
| Design system | `@cameleer/design-system` v0.1.31 | Shared component library. CSS Modules + design tokens. Dark theme. | | Design system | `@cameleer/design-system` v0.1.31 | Shared component library. CSS Modules + design tokens. Dark theme. |
| RBAC | Frontend role-based visibility | Roles from JWT claims. Hide/disable UI for unauthorized actions. Backend enforces — frontend is UX only. | | RBAC | Frontend role-based visibility | Roles from JWT claims. Hide/disable UI for unauthorized actions. Backend enforces — frontend is UX only. |
@@ -39,7 +39,7 @@ Phase 9 adds the SaaS management shell: a React SPA for managing tenants, enviro
2. If not authenticated, redirect to Logto OIDC authorize endpoint 2. If not authenticated, redirect to Logto OIDC authorize endpoint
3. Logto callback at `/callback` — exchange code for tokens 3. Logto callback at `/callback` — exchange code for tokens
4. Store `accessToken`, `refreshToken`, `username`, `roles` in Zustand + localStorage 4. Store `accessToken`, `refreshToken`, `username`, `roles` in Zustand + localStorage
5. Tokens stored with same keys as cameleer3-server SPA: `cameleer-access-token`, `cameleer-refresh-token` 5. Tokens stored with same keys as cameleer-server SPA: `cameleer-access-token`, `cameleer-refresh-token`
6. API client injects `Authorization: Bearer {token}` on all requests 6. API client injects `Authorization: Bearer {token}` on all requests
7. On 401, attempt token refresh; on failure, redirect to login 7. On 401, attempt token refresh; on failure, redirect to login
@@ -128,7 +128,7 @@ Frontend RBAC implementation:
- Sidebar uses `Sidebar` + `TreeView` components from design system - Sidebar uses `Sidebar` + `TreeView` components from design system
- Environment → App hierarchy is collapsible - Environment → App hierarchy is collapsible
- "View Dashboard" is an external link to `/dashboard` (cameleer3-server SPA) - "View Dashboard" is an external link to `/dashboard` (cameleer-server SPA)
- Sidebar collapses on small screens (responsive) - Sidebar collapses on small screens (responsive)
## API Integration ## API Integration
@@ -181,7 +181,7 @@ ui/
│ ├── main.tsx — React root + providers │ ├── main.tsx — React root + providers
│ ├── router.tsx — React Router config │ ├── router.tsx — React Router config
│ ├── auth/ │ ├── auth/
│ │ ├── auth-store.ts — Zustand store (same keys as cameleer3-server) │ │ ├── auth-store.ts — Zustand store (same keys as cameleer-server)
│ │ ├── LoginPage.tsx │ │ ├── LoginPage.tsx
│ │ ├── CallbackPage.tsx │ │ ├── CallbackPage.tsx
│ │ └── ProtectedRoute.tsx │ │ └── ProtectedRoute.tsx
@@ -302,14 +302,14 @@ import { ThemeProvider, ToastProvider, BreadcrumbProvider } from '@cameleer/desi
6. Deploy triggers async deployment, status polls and updates live 6. Deploy triggers async deployment, status polls and updates live
7. Agent status shows registered/connected 7. Agent status shows registered/connected
8. Container logs stream in LogViewer 8. Container logs stream in LogViewer
9. "View Dashboard" link navigates to `/dashboard` (cameleer3-server SPA) 9. "View Dashboard" link navigates to `/dashboard` (cameleer-server SPA)
10. Shared auth: no re-login when switching between SPAs 10. Shared auth: no re-login when switching between SPAs
11. RBAC: VIEWER cannot see deploy button, DEVELOPER cannot delete environments 11. RBAC: VIEWER cannot see deploy button, DEVELOPER cannot delete environments
12. Production build: `npm run build` + `mvn package` produces JAR with embedded SPA 12. Production build: `npm run build` + `mvn package` produces JAR with embedded SPA
## What Phase 9 Does NOT Touch ## What Phase 9 Does NOT Touch
- No changes to cameleer3-server or its SPA - No changes to cameleer-server or its SPA
- No billing UI (Phase 6) - No billing UI (Phase 6)
- No team management (Logto org admin — deferred) - No team management (Logto org admin — deferred)
- No tenant settings/profile page - No tenant settings/profile page

View File

@@ -2,7 +2,7 @@
**Date:** 2026-04-05 **Date:** 2026-04-05
**Status:** Draft **Status:** Draft
**Scope:** cameleer-saas (large), cameleer3-server (small), cameleer3 agent (none) **Scope:** cameleer-saas (large), cameleer-server (small), cameleer agent (none)
## Problem Statement ## Problem Statement
@@ -13,7 +13,7 @@ The current cameleer-saas authentication implementation has three overlapping id
1. **Logto is the single identity provider** for all human users across all components. 1. **Logto is the single identity provider** for all human users across all components.
2. **Zero trust** — every service validates tokens independently via JWKS or its own signing key. No identity in HTTP headers. The JWT is the proof. 2. **Zero trust** — every service validates tokens independently via JWKS or its own signing key. No identity in HTTP headers. The JWT is the proof.
3. **No custom crypto** — use standard libraries and protocols (OAuth2, OIDC, JWT). No hand-rolled JWT generation or validation. 3. **No custom crypto** — use standard libraries and protocols (OAuth2, OIDC, JWT). No hand-rolled JWT generation or validation.
4. **Server-per-tenant** — each tenant gets their own cameleer3-server instance. The SaaS platform provisions and manages them. 4. **Server-per-tenant** — each tenant gets their own cameleer-server instance. The SaaS platform provisions and manages them.
5. **API keys for agents** — per-environment opaque secrets, exchanged for server-issued JWTs via the existing bootstrap registration flow. 5. **API keys for agents** — per-environment opaque secrets, exchanged for server-issued JWTs via the existing bootstrap registration flow.
6. **Self-hosted compatible** — same stack, single Logto org, single tenant. No special code paths. 6. **Self-hosted compatible** — same stack, single Logto org, single tenant. No special code paths.
@@ -52,9 +52,9 @@ The current cameleer-saas authentication implementation has three overlapping id
|-------|--------|-----------|-----------|---------| |-------|--------|-----------|-----------|---------|
| Logto user JWT | Logto | ES384 (asymmetric) | Any service via JWKS | SaaS UI users, server dashboard users | | Logto user JWT | Logto | ES384 (asymmetric) | Any service via JWKS | SaaS UI users, server dashboard users |
| Logto M2M JWT | Logto | ES384 (asymmetric) | Any service via JWKS | SaaS platform → server API calls | | Logto M2M JWT | Logto | ES384 (asymmetric) | Any service via JWKS | SaaS platform → server API calls |
| Server internal JWT | cameleer3-server | HS256 (symmetric) | Issuing server only | Agents (after registration) | | Server internal JWT | cameleer-server | HS256 (symmetric) | Issuing server only | Agents (after registration) |
| API key (opaque) | SaaS platform | N/A (hashed at rest) | cameleer3-server (bootstrap validator) | Agent initial registration | | API key (opaque) | SaaS platform | N/A (hashed at rest) | cameleer-server (bootstrap validator) | Agent initial registration |
| Ed25519 signature | cameleer3-server | EdDSA | Agent | Server → agent command integrity | | Ed25519 signature | cameleer-server | EdDSA | Agent | Server → agent command integrity |
### Authentication flows ### Authentication flows
@@ -65,19 +65,19 @@ The current cameleer-saas authentication implementation has three overlapping id
4. `organization_id` claim in JWT → resolves to internal tenant ID 4. `organization_id` claim in JWT → resolves to internal tenant ID
5. Roles come from JWT claims (Logto org roles), not Management API calls 5. Roles come from JWT claims (Logto org roles), not Management API calls
**Human user → cameleer3-server dashboard:** **Human user → cameleer-server dashboard:**
1. User authenticates with Logto (OIDC flow, server configured via existing admin API) 1. User authenticates with Logto (OIDC flow, server configured via existing admin API)
2. Server exchanges auth code for ID token, validates via provider JWKS 2. Server exchanges auth code for ID token, validates via provider JWKS
3. Server issues internal HMAC JWT with mapped roles 3. Server issues internal HMAC JWT with mapped roles
4. Existing flow, no changes needed 4. Existing flow, no changes needed
**SaaS platform → cameleer3-server API (M2M):** **SaaS platform → cameleer-server API (M2M):**
1. SaaS platform obtains Logto M2M access token (`client_credentials` grant) 1. SaaS platform obtains Logto M2M access token (`client_credentials` grant)
2. Calls tenant server API with `Authorization: Bearer <logto-m2m-token>` 2. Calls tenant server API with `Authorization: Bearer <logto-m2m-token>`
3. Server validates via Logto JWKS (new capability — see server changes below) 3. Server validates via Logto JWKS (new capability — see server changes below)
4. Server grants ADMIN role to valid M2M tokens 4. Server grants ADMIN role to valid M2M tokens
**Agent → cameleer3-server:** **Agent → cameleer-server:**
1. Agent reads `CAMELEER_API_KEY` env var (fallback: `CAMELEER_AUTH_TOKEN` for backward compat) 1. Agent reads `CAMELEER_API_KEY` env var (fallback: `CAMELEER_AUTH_TOKEN` for backward compat)
2. Calls `POST /api/v1/agents/register` with `Authorization: Bearer <api-key>` 2. Calls `POST /api/v1/agents/register` with `Authorization: Bearer <api-key>`
3. Server validates via `BootstrapTokenValidator` (constant-time comparison, unchanged) 3. Server validates via `BootstrapTokenValidator` (constant-time comparison, unchanged)
@@ -96,7 +96,7 @@ The current cameleer-saas authentication implementation has three overlapping id
## Component Changes ## Component Changes
### cameleer3 (agent) — NO CHANGES ### cameleer (agent) — NO CHANGES
The agent's authentication flow is correct as designed: The agent's authentication flow is correct as designed:
- Reads API key from environment variable - Reads API key from environment variable
@@ -106,13 +106,13 @@ The agent's authentication flow is correct as designed:
The only optional change is renaming `CAMELEER_AUTH_TOKEN` to `CAMELEER_API_KEY` for clarity, with backward-compatible fallback. This is cosmetic and can be done at any time. The only optional change is renaming `CAMELEER_AUTH_TOKEN` to `CAMELEER_API_KEY` for clarity, with backward-compatible fallback. This is cosmetic and can be done at any time.
### cameleer3-server — SMALL CHANGES ### cameleer-server — SMALL CHANGES
The server needs one new capability: accepting Logto access tokens (asymmetric JWT) in addition to its own internal HMAC JWTs. This enables the SaaS platform to call server APIs using M2M tokens. The server needs one new capability: accepting Logto access tokens (asymmetric JWT) in addition to its own internal HMAC JWTs. This enables the SaaS platform to call server APIs using M2M tokens.
#### Change 1: Add `spring-boot-starter-oauth2-resource-server` dependency #### Change 1: Add `spring-boot-starter-oauth2-resource-server` dependency
**File:** `cameleer3-server-app/pom.xml` **File:** `cameleer-server-app/pom.xml`
Add: Add:
```xml ```xml
@@ -124,7 +124,7 @@ Add:
#### Change 2: Add OIDC resource server properties #### Change 2: Add OIDC resource server properties
**File:** `cameleer3-server-app/src/main/resources/application.yml` **File:** `cameleer-server-app/src/main/resources/application.yml`
Add under `security:`: Add under `security:`:
```yaml ```yaml
@@ -360,7 +360,7 @@ cameleer:
public-key-path: ${CAMELEER_JWT_PUBLIC_KEY_PATH:} public-key-path: ${CAMELEER_JWT_PUBLIC_KEY_PATH:}
``` ```
Remove the `keys/` directory mount from `docker-compose.yml`. The SaaS platform does not sign anything — Ed25519 signing lives in cameleer3-server only. Remove the `keys/` directory mount from `docker-compose.yml`. The SaaS platform does not sign anything — Ed25519 signing lives in cameleer-server only.
#### REWRITE: `SecurityConfig.java` #### REWRITE: `SecurityConfig.java`
@@ -680,7 +680,7 @@ Keep as-is. Serves frontend configuration. No auth changes needed.
Update to set `CAMELEER_OIDC_ISSUER_URI` and `CAMELEER_OIDC_AUDIENCE` on the tenant server: Update to set `CAMELEER_OIDC_ISSUER_URI` and `CAMELEER_OIDC_AUDIENCE` on the tenant server:
```bash ```bash
# Add to the cameleer3-server environment in docker-compose or bootstrap output: # Add to the cameleer-server environment in docker-compose or bootstrap output:
CAMELEER_OIDC_ISSUER_URI=http://logto:3001/oidc CAMELEER_OIDC_ISSUER_URI=http://logto:3001/oidc
CAMELEER_OIDC_AUDIENCE=https://api.cameleer.local CAMELEER_OIDC_AUDIENCE=https://api.cameleer.local
``` ```
@@ -727,7 +727,7 @@ This is a new development — no production data exists. All database schemas, m
### Implementation Order ### Implementation Order
1. **Phase 1**: Update cameleer3-server (add OIDC resource server support). Deploy. 1. **Phase 1**: Update cameleer-server (add OIDC resource server support). Deploy.
2. **Phase 2**: Rewrite cameleer-saas backend (clean security config, API key management, Logto-only auth). Deploy with frontend changes atomically. 2. **Phase 2**: Rewrite cameleer-saas backend (clean security config, API key management, Logto-only auth). Deploy with frontend changes atomically.
3. **Phase 3**: Update bootstrap script (set OIDC env vars on server, stop reading Logto DB directly). 3. **Phase 3**: Update bootstrap script (set OIDC env vars on server, stop reading Logto DB directly).

View File

@@ -0,0 +1,184 @@
# Configurable Base Path for SaaS App
## Problem
Logto uses many root-level paths (`/sign-in`, `/register`, `/consent`, `/social`, `/api/interaction`, `/api/experience`, `/assets`, etc.) that conflict with the SaaS app's catch-all routing. Enumerating Logto's paths in Traefik is fragile and keeps growing.
## Solution
Move the SaaS app to a configurable base path (default: `/platform`). Logto becomes the Traefik catch-all. Zero path enumeration — any path Logto adds in the future just works.
## Routing
| Path | Target | Priority |
|------|--------|----------|
| `/platform/*` | cameleer-saas:8080 | default |
| `/server/*` | cameleer-server-ui:80 | default |
| `/*` | logto:3001 (catch-all) | 1 (lowest) |
## Configuration
```env
# .env
CONTEXT_PATH=/platform # Change to /saas, /app, etc. No rebuild needed.
```
## Implementation
### 1. Spring Boot — `application.yml`
```yaml
server:
servlet:
context-path: ${CONTEXT_PATH:/platform}
```
Spring automatically prefixes all endpoints. Controllers, SecurityConfig matchers, interceptor patterns — all relative to context-path. No changes needed in Java code.
### 2. Vite — `ui/vite.config.ts`
Build with relative base so assets work from any prefix:
```ts
build: {
outDir: 'dist',
emptyOutDir: true,
assetsDir: '_app',
// removed: base (default '/' for dev, entrypoint injects <base> for production)
},
```
Change `base` to `'./'` so index.html references become relative:
```html
<!-- Before: <script src="/_app/index.js"> (absolute, breaks with prefix) -->
<!-- After: <script src="_app/index.js"> (relative, works from any base) -->
```
### 3. Container entrypoint — inject `<base href>`
Create `docker/entrypoint.sh`:
```sh
#!/bin/sh
# Inject <base href> into index.html for runtime base path support
CONTEXT_PATH="${CONTEXT_PATH:-/platform}"
sed -i "s|<head>|<head><base href=\"${CONTEXT_PATH}/\">|" /app/static/index.html
exec java -jar /app/app.jar
```
In Dockerfile (or docker-compose override for dev):
```yaml
entrypoint: ["sh", "/app/entrypoint.sh"]
```
For dev mode (mounted dist), the `docker-compose.dev.yml` entrypoint runs the sed on the mounted file.
### 4. Frontend — derive base path at runtime
**`ui/src/config.ts`** — use `document.baseURI`:
```ts
const basePath = new URL(document.baseURI).pathname.replace(/\/$/, '');
// basePath = "/platform"
fetch(basePath + '/api/config')
```
**`ui/src/api/client.ts`** — dynamic API base:
```ts
const basePath = new URL(document.baseURI).pathname.replace(/\/$/, '');
const API_BASE = basePath + '/api';
```
**`ui/src/main.tsx`** — router basename:
```tsx
const basePath = new URL(document.baseURI).pathname.replace(/\/$/, '') || '/';
<BrowserRouter basename={basePath}>
```
### 5. Docker Compose — Traefik labels
**cameleer-saas:**
```yaml
labels:
- traefik.http.routers.saas.rule=PathPrefix(`${CONTEXT_PATH:-/platform}`)
- traefik.http.routers.saas.entrypoints=websecure
- traefik.http.routers.saas.tls=true
- traefik.http.services.saas.loadbalancer.server.port=8080
```
**logto (catch-all):**
```yaml
labels:
- traefik.http.routers.logto.rule=PathPrefix(`/`)
- traefik.http.routers.logto.priority=1
- traefik.http.routers.logto.entrypoints=websecure
- traefik.http.routers.logto.tls=true
- traefik.http.services.logto.loadbalancer.server.port=3001
```
Remove all the enumerated Logto paths — Logto is now the catch-all.
### 6. Logto ENDPOINT
Logto's ENDPOINT stays at root (no prefix):
```yaml
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
```
OIDC issuer = `https://domain.com/oidc`. Same domain as the SPA.
### 7. Bootstrap — redirect URIs
Update redirect URIs to include the context path:
```sh
SPA_REDIRECT_URIS="[\"${PROTO}://${HOST}${CONTEXT_PATH}/callback\"]"
SPA_POST_LOGOUT_URIS="[\"${PROTO}://${HOST}${CONTEXT_PATH}/login\"]"
```
Pass `CONTEXT_PATH` to the bootstrap container.
### 8. Tests — `application-test.yml`
```yaml
server:
servlet:
context-path: /platform
```
Test MockMvc paths are relative to context-path, so existing test paths (`/api/tenants`, etc.) continue to work without changes.
## Files to modify
- `src/main/resources/application.yml` — add context-path property
- `src/main/resources/application-test.yml` — add context-path for tests
- `ui/vite.config.ts``base: './'` for relative assets
- `ui/src/config.ts` — derive base path from `document.baseURI`
- `ui/src/api/client.ts` — dynamic `API_BASE`
- `ui/src/main.tsx``BrowserRouter basename` from `document.baseURI`
- `docker/entrypoint.sh` — NEW, injects `<base href>` into index.html
- `docker-compose.yml` — Traefik labels (SaaS at `/platform`, Logto catch-all), pass `CONTEXT_PATH` to bootstrap
- `docker/logto-bootstrap.sh` — context path in redirect URIs
## Files that do NOT change
- All 11 Java controllers — Spring context-path handles prefix transparently
- `SecurityConfig.java` — matchers are relative to context-path
- `WebConfig.java` — interceptor pattern relative to context-path
- `ui/src/api/hooks.ts` — uses centralized `API_BASE`
- All test files — MockMvc is context-path aware
## Customer experience
```env
# .env
PUBLIC_HOST=cameleer.mycompany.com
PUBLIC_PROTOCOL=https
CONTEXT_PATH=/platform
# DNS: 1 record
# cameleer.mycompany.com → server IP
# docker compose up -d
# SaaS at https://cameleer.mycompany.com/platform/
# Logto at https://cameleer.mycompany.com/ (login, OIDC)
# Server UI at https://cameleer.mycompany.com/server/
```

View File

@@ -13,7 +13,7 @@ Path-based routing on one domain. SaaS app at `/platform`, server-ui at `/server
| Path | Target | Priority | Notes | | Path | Target | Priority | Notes |
|------|--------|----------|-------| |------|--------|----------|-------|
| `/platform/*` | cameleer-saas:8080 | default | Spring context-path `/platform` | | `/platform/*` | cameleer-saas:8080 | default | Spring context-path `/platform` |
| `/server/*` | cameleer3-server-ui:80 | default | Strip-prefix + `BASE_PATH=/server` | | `/server/*` | cameleer-server-ui:80 | default | Strip-prefix + `BASE_PATH=/server` |
| `/` | redirect → `/platform/` | 100 | Via `docker/traefik-dynamic.yml` | | `/` | redirect → `/platform/` | 100 | Via `docker/traefik-dynamic.yml` |
| `/*` | logto:3001 | 1 (lowest) | Catch-all: sign-in, OIDC, assets | | `/*` | logto:3001 | 1 (lowest) | Catch-all: sign-in, OIDC, assets |
@@ -49,7 +49,7 @@ PUBLIC_PROTOCOL=https
- Custom `JwtDecoder`: ES384 algorithm, `at+jwt` token type, split issuer-uri / jwk-set-uri - Custom `JwtDecoder`: ES384 algorithm, `at+jwt` token type, split issuer-uri / jwk-set-uri
- Redirect URIs: `${PROTO}://${HOST}/platform/callback` - Redirect URIs: `${PROTO}://${HOST}/platform/callback`
## Server Integration (cameleer3-server) ## Server Integration (cameleer-server)
| Env var | Value | Purpose | | Env var | Value | Purpose |
|---------|-------|---------| |---------|-------|---------|
@@ -64,12 +64,12 @@ Server OIDC requirements:
- `X-Forwarded-Prefix` support for correct redirect_uri construction - `X-Forwarded-Prefix` support for correct redirect_uri construction
- Branding endpoint (`/api/v1/branding/logo`) must be publicly accessible - Branding endpoint (`/api/v1/branding/logo`) must be publicly accessible
## Server UI (cameleer3-server-ui) ## Server UI (cameleer-server-ui)
| Env var | Value | Purpose | | Env var | Value | Purpose |
|---------|-------|---------| |---------|-------|---------|
| `BASE_PATH` | `/server` | React Router basename + `<base>` tag | | `BASE_PATH` | `/server` | React Router basename + `<base>` tag |
| `CAMELEER_API_URL` | `http://cameleer3-server:8081` | nginx API proxy target | | `CAMELEER_API_URL` | `http://cameleer-server:8081` | nginx API proxy target |
Traefik strip-prefix removes `/server` before forwarding to nginx. Server-ui injects `<base href="/server/">` via `BASE_PATH`. Traefik strip-prefix removes `/server` before forwarding to nginx. Server-ui injects `<base href="/server/">` via `BASE_PATH`.
@@ -80,7 +80,7 @@ Traefik strip-prefix removes `/server` before forwarding to nginx. Server-ui inj
SPA_REDIRECT_URIS=["${PROTO}://${HOST}/platform/callback"] SPA_REDIRECT_URIS=["${PROTO}://${HOST}/platform/callback"]
SPA_POST_LOGOUT_URIS=["${PROTO}://${HOST}/platform/login"] SPA_POST_LOGOUT_URIS=["${PROTO}://${HOST}/platform/login"]
# Traditional (cameleer3-server) — both variants until X-Forwarded-Prefix is consistent # Traditional (cameleer-server) — both variants until X-Forwarded-Prefix is consistent
TRAD_REDIRECT_URIS=["${PROTO}://${HOST}/oidc/callback","${PROTO}://${HOST}/server/oidc/callback"] TRAD_REDIRECT_URIS=["${PROTO}://${HOST}/oidc/callback","${PROTO}://${HOST}/server/oidc/callback"]
TRAD_POST_LOGOUT_URIS=["${PROTO}://${HOST}","${PROTO}://${HOST}/server"] TRAD_POST_LOGOUT_URIS=["${PROTO}://${HOST}","${PROTO}://${HOST}/server"]
``` ```

View File

@@ -2,11 +2,11 @@
## Problem ## Problem
Logto's default sign-in page uses Logto branding. While we configured colors and logos via `PATCH /api/sign-in-exp`, control over layout, typography, and components is limited. The sign-in experience is visually inconsistent with the cameleer3-server login page. Logto's default sign-in page uses Logto branding. While we configured colors and logos via `PATCH /api/sign-in-exp`, control over layout, typography, and components is limited. The sign-in experience is visually inconsistent with the cameleer-server login page.
## Goal ## Goal
Replace Logto's sign-in UI with a custom React SPA that visually matches the cameleer3-server login page, using `@cameleer/design-system` components for consistency across all deployment models. Replace Logto's sign-in UI with a custom React SPA that visually matches the cameleer-server login page, using `@cameleer/design-system` components for consistency across all deployment models.
## Scope ## Scope
@@ -54,9 +54,9 @@ The interaction cookie is set by `/oidc/auth` before the user lands on the sign-
## Visual Design ## Visual Design
Matches cameleer3-server LoginPage exactly: Matches cameleer-server LoginPage exactly:
- Centered `Card` (400px max-width, 32px padding) - Centered `Card` (400px max-width, 32px padding)
- Logo: favicon.svg + "cameleer3" text (24px bold) - Logo: favicon.svg + "cameleer" text (24px bold)
- Random witty subtitle (13px muted) - Random witty subtitle (13px muted)
- `FormField` + `Input` for username and password - `FormField` + `Input` for username and password
- Amber `Button` (primary variant, full-width) - Amber `Button` (primary variant, full-width)

View File

@@ -2,7 +2,7 @@
## Problem ## Problem
When a Logto user SSOs into the cameleer3-server, they get `VIEWER` role by default (OIDC auto-signup). There's no automatic mapping between Logto organization roles and server roles. A SaaS admin must manually promote users in the server. When a Logto user SSOs into the cameleer-server, they get `VIEWER` role by default (OIDC auto-signup). There's no automatic mapping between Logto organization roles and server roles. A SaaS admin must manually promote users in the server.
## Constraint ## Constraint

View File

@@ -2,7 +2,7 @@
**Date:** 2026-04-07 **Date:** 2026-04-07
**Status:** Ready for review **Status:** Ready for review
**Scope:** cameleer3 (agent), cameleer3-server, cameleer-saas **Scope:** cameleer (agent), cameleer-server, cameleer-saas
**Focus:** Responsibility boundaries, architectural fitness, simplification opportunities **Focus:** Responsibility boundaries, architectural fitness, simplification opportunities
**Not in scope:** Security hardening, code quality, performance **Not in scope:** Security hardening, code quality, performance
@@ -12,9 +12,9 @@
The cameleer ecosystem has a clear vision: a standalone observability and runtime platform for Apache Camel, optionally managed by a thin SaaS vendor layer. Both deployment modes must be first-class. The cameleer ecosystem has a clear vision: a standalone observability and runtime platform for Apache Camel, optionally managed by a thin SaaS vendor layer. Both deployment modes must be first-class.
The agent (cameleer3) is architecturally clean. Single job, well-defined protocol. The agent (cameleer) is architecturally clean. Single job, well-defined protocol.
The server (cameleer3-server) is solid for observability but currently lacks runtime management capabilities (deploying and managing Camel application containers). These capabilities exist in the SaaS layer today but belong in the server, since standalone customers also need them. The server (cameleer-server) is solid for observability but currently lacks runtime management capabilities (deploying and managing Camel application containers). These capabilities exist in the SaaS layer today but belong in the server, since standalone customers also need them.
The SaaS layer (cameleer-saas) has taken on too many responsibilities: environment management, app lifecycle, container orchestration, direct ClickHouse access, and partial auth duplication. It should be a thin vendor management plane: onboard tenants, provision server instances, manage billing. Nothing more. The SaaS layer (cameleer-saas) has taken on too many responsibilities: environment management, app lifecycle, container orchestration, direct ClickHouse access, and partial auth duplication. It should be a thin vendor management plane: onboard tenants, provision server instances, manage billing. Nothing more.
@@ -29,17 +29,17 @@ The SaaS layer (cameleer-saas) has taken on too many responsibilities: environme
## What's Working Well ## What's Working Well
### Agent (cameleer3) ### Agent (cameleer)
- Clean separation: core logic in `cameleer3-core`, protocol models in `cameleer3-common`, delivery mechanisms (agent/extension) as thin wrappers - Clean separation: core logic in `cameleer-core`, protocol models in `cameleer-common`, delivery mechanisms (agent/extension) as thin wrappers
- Well-defined agent-server protocol (PROTOCOL.md) with versioning - Well-defined agent-server protocol (PROTOCOL.md) with versioning
- Dual-mode design (Java agent + Quarkus extension) is elegant - Dual-mode design (Java agent + Quarkus extension) is elegant
- Compatibility matrix across 40 Camel versions demonstrates maturity - Compatibility matrix across 40 Camel versions demonstrates maturity
- No changes needed - No changes needed
### Server (cameleer3-server) ### Server (cameleer-server)
- Two-database pattern (PostgreSQL control plane, ClickHouse observability data) is correct - Two-database pattern (PostgreSQL control plane, ClickHouse observability data) is correct
- In-memory agent registry with heartbeat-based auto-recovery is operationally sound - In-memory agent registry with heartbeat-based auto-recovery is operationally sound
- `cameleer3-server-core` / `cameleer3-server-app` split keeps domain logic framework-free - `cameleer-server-core` / `cameleer-server-app` split keeps domain logic framework-free
- SSE command push with Ed25519 signing is well-designed - SSE command push with Ed25519 signing is well-designed
- The UI is competitive-grade (per UX audit #100) - The UI is competitive-grade (per UX audit #100)
- Independent user/group/role management works for standalone deployments - Independent user/group/role management works for standalone deployments
@@ -392,7 +392,7 @@ Step 8 is the SaaS cleanup after server capabilities are in place.
- **saas#37 (admin tenant creation UI):** SaaS UI becomes vendor-focused, simpler - **saas#37 (admin tenant creation UI):** SaaS UI becomes vendor-focused, simpler
### Issues that become more important: ### Issues that become more important:
- **agent#33 (version cameleer3-common independently):** Critical before server API contract stabilizes - **agent#33 (version cameleer-common independently):** Critical before server API contract stabilizes
- **server#46 (OIDC PKCE for SPA):** Required for server-ui in oidc-only mode - **server#46 (OIDC PKCE for SPA):** Required for server-ui in oidc-only mode
- **server#101 (onboarding experience):** Server UI needs guided setup for standalone users - **server#101 (onboarding experience):** Server UI needs guided setup for standalone users

View File

@@ -30,7 +30,7 @@ This spec redesigns the platform around two personas — **vendor** (us) and **c
| ID | Story | Acceptance Criteria | | ID | Story | Acceptance Criteria |
|----|-------|-------------------| |----|-------|-------------------|
| V1 | As a vendor, I want to create a tenant so I can onboard a new customer | Form collects name, slug, tier. Creates DB record + Logto org. Status = PROVISIONING. | | V1 | As a vendor, I want to create a tenant so I can onboard a new customer | Form collects name, slug, tier. Creates DB record + Logto org. Status = PROVISIONING. |
| V2 | As a vendor, I want to provision a server for a tenant so they have a running Cameleer instance | After tenant creation, SaaS creates a cameleer3-server container via Docker API with correct env vars, network, and Traefik labels. Health check passes → status = ACTIVE. | | V2 | As a vendor, I want to provision a server for a tenant so they have a running Cameleer instance | After tenant creation, SaaS creates a cameleer-server container via Docker API with correct env vars, network, and Traefik labels. Health check passes → status = ACTIVE. |
| V3 | As a vendor, I want to generate and assign a license to a tenant | License created with tier-appropriate features/limits/expiry. Token pushed to tenant's server via M2M API. | | V3 | As a vendor, I want to generate and assign a license to a tenant | License created with tier-appropriate features/limits/expiry. Token pushed to tenant's server via M2M API. |
| V4 | As a vendor, I want to suspend a tenant who hasn't paid | Suspend stops the server container and marks tenant SUSPENDED. Reactivation restarts it. | | V4 | As a vendor, I want to suspend a tenant who hasn't paid | Suspend stops the server container and marks tenant SUSPENDED. Reactivation restarts it. |
| V5 | As a vendor, I want to view fleet health at a glance | Tenant list shows each tenant's server status (running/stopped/error), agent count vs limit, license expiry. | | V5 | As a vendor, I want to view fleet health at a glance | Tenant list shows each tenant's server status (running/stopped/error), agent count vs limit, license expiry. |
@@ -135,7 +135,7 @@ public class TenantProvisionerAutoConfig {
| Config | Value | Source | | Config | Value | Source |
|--------|-------|--------| |--------|-------|--------|
| Image | `gitea.siegeln.net/cameleer/cameleer3-server:${VERSION}` | Global config | | Image | `gitea.siegeln.net/cameleer/cameleer-server:${VERSION}` | Global config |
| Name | `cameleer-server-${tenant.slug}` | Derived from tenant | | Name | `cameleer-server-${tenant.slug}` | Derived from tenant |
| Network | `cameleer` + `cameleer-traefik` | Fixed networks from compose | | Network | `cameleer` + `cameleer-traefik` | Fixed networks from compose |
| DNS alias | `cameleer-server-${tenant.slug}` | For SaaS→server M2M calls | | DNS alias | `cameleer-server-${tenant.slug}` | For SaaS→server M2M calls |
@@ -146,7 +146,7 @@ public class TenantProvisionerAutoConfig {
| Env var | Value | Purpose | | Env var | Value | Purpose |
|---------|-------|---------| |---------|-------|---------|
| `SPRING_DATASOURCE_URL` | `jdbc:postgresql://postgres:5432/cameleer3` | Shared PostgreSQL | | `SPRING_DATASOURCE_URL` | `jdbc:postgresql://postgres:5432/cameleer` | Shared PostgreSQL |
| `CAMELEER_TENANT_ID` | `${tenant.slug}` | Tenant isolation key | | `CAMELEER_TENANT_ID` | `${tenant.slug}` | Tenant isolation key |
| `CAMELEER_OIDC_ISSUER_URI` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/oidc` | Logto as initial OIDC | | `CAMELEER_OIDC_ISSUER_URI` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/oidc` | Logto as initial OIDC |
| `CAMELEER_OIDC_JWK_SET_URI` | `http://logto:3001/oidc/jwks` | Docker-internal JWK | | `CAMELEER_OIDC_JWK_SET_URI` | `http://logto:3001/oidc/jwks` | Docker-internal JWK |
@@ -168,12 +168,12 @@ traefik.http.services.server-${slug}.loadbalancer.server.port=8081
**Server UI container per tenant:** **Server UI container per tenant:**
Each tenant also gets a `cameleer3-server-ui` container: Each tenant also gets a `cameleer-server-ui` container:
| Config | Value | | Config | Value |
|--------|-------| |--------|-------|
| Name | `cameleer-server-ui-${tenant.slug}` | | Name | `cameleer-server-ui-${tenant.slug}` |
| Image | `gitea.siegeln.net/cameleer/cameleer3-server-ui:${VERSION}` | | Image | `gitea.siegeln.net/cameleer/cameleer-server-ui:${VERSION}` |
| Env | `BASE_PATH=/t/${slug}` | | Env | `BASE_PATH=/t/${slug}` |
| Traefik | `PathPrefix(/t/${slug})` with `priority=2` (higher than API) | | Traefik | `PathPrefix(/t/${slug})` with `priority=2` (higher than API) |
@@ -467,11 +467,11 @@ New migration `V011`:
## 8. Existing Compose Stack Changes ## 8. Existing Compose Stack Changes
The default `cameleer3-server` and `cameleer3-server-ui` containers in docker-compose.yml become the "bootstrap" server for the `default` tenant. When provisioning is enabled, new tenants get their own dynamically-created containers. The default `cameleer-server` and `cameleer-server-ui` containers in docker-compose.yml become the "bootstrap" server for the `default` tenant. When provisioning is enabled, new tenants get their own dynamically-created containers.
The existing compose stack continues to work as-is for development. The provisioner creates additional containers alongside the compose-managed ones. The existing compose stack continues to work as-is for development. The provisioner creates additional containers alongside the compose-managed ones.
For the `default` tenant (created by bootstrap), the SaaS recognizes the existing compose-managed server and doesn't try to provision a new one. This is detected by checking if a container named `cameleer-server-default` (or the compose-managed `cameleer3-server`) already exists. For the `default` tenant (created by bootstrap), the SaaS recognizes the existing compose-managed server and doesn't try to provision a new one. This is detected by checking if a container named `cameleer-server-default` (or the compose-managed `cameleer-server`) already exists.
--- ---

View File

@@ -376,9 +376,9 @@ Import from `lucide-react`.
### 4.5 Sign-In Branding ### 4.5 Sign-In Branding
**Problem:** Login says "cameleer3" — internal repo name, not product brand. **Problem:** Login says "cameleer" — internal repo name, not product brand.
**Fix:** Change to "Cameleer" (product name). Update the page title from "Sign in — cameleer3" to "Sign in — Cameleer". **Fix:** Change to "Cameleer" (product name). Update the page title from "Sign in — cameleer" to "Sign in — Cameleer".
**Files:** `ui/sign-in/src/SignInPage.tsx` **Files:** `ui/sign-in/src/SignInPage.tsx`

View File

@@ -0,0 +1,52 @@
# Fleet Health at a Glance (#44)
## Context
The vendor tenant list at `/vendor/tenants` shows basic tenant info (name, slug, tier, status, server state, license expiry) but lacks live usage data. The vendor needs to see agent and environment counts per tenant to understand fleet utilization without clicking into each tenant.
## Design
### Backend
**Extend `VendorTenantSummary`** record in `VendorTenantController.java` with three fields:
```java
int agentCount, int environmentCount, int agentLimit
```
**In the list endpoint mapping**: for each ACTIVE tenant with a server endpoint, call `serverApiClient.getAgentCount(endpoint)` and `serverApiClient.getEnvironmentCount(endpoint)`. Agent limit comes from the tenant's license `limits.agents` field (-1 for unlimited). Use `CompletableFuture` to parallelize the N HTTP calls.
Tenants that are PROVISIONING/SUSPENDED/DELETED get zeroes — skip the HTTP calls.
### Frontend
**Add two columns** to the DataTable in `VendorTenantsPage.tsx`:
| Column | Key | Display |
|--------|-----|---------|
| Agents | `agentCount` | "3 / 10" or "0 / ∞" (uses agentCount + agentLimit) |
| Envs | `environmentCount` | "1" |
Place them after the Server column, before License.
**Update `VendorTenantSummary` type** in `ui/src/types/api.ts` to include `agentCount: number`, `environmentCount: number`, `agentLimit: number`.
### Existing infrastructure reused
- `ServerApiClient.getAgentCount()` and `getEnvironmentCount()` — already implemented for tenant dashboard
- `LicenseService.getActiveLicense()` — already used in the list endpoint for license expiry
- 30-second refetch interval on `useVendorTenants()` — already in place
### No changes to
- ServerStatusBadge column — kept as-is
- License column — kept as-is
- Detail page — already has its own health data
## Files to modify
| File | Change |
|------|--------|
| `VendorTenantController.java` | Extend summary record + parallel health fetch in list endpoint |
| `VendorTenantsPage.tsx` | Add Agents and Envs columns |
| `ui/src/types/api.ts` | Add fields to VendorTenantSummary type |

View File

@@ -5,7 +5,7 @@
## Context ## Context
The cameleer3-server team introduced `currentSchema` and `ApplicationName` JDBC parameters (commit `7a63135`) to scope admin diagnostic queries to a single tenant's connections. Previously, all tenant servers shared one PostgreSQL user and connected to the `cameleer3` database without schema isolation — a tenant's server could theoretically see SQL text from other tenants via `pg_stat_activity`. The cameleer-server team introduced `currentSchema` and `ApplicationName` JDBC parameters (commit `7a63135`) to scope admin diagnostic queries to a single tenant's connections. Previously, all tenant servers shared one PostgreSQL user and connected to the `cameleer` database without schema isolation — a tenant's server could theoretically see SQL text from other tenants via `pg_stat_activity`.
This spec adds per-tenant PostgreSQL users and schemas so each tenant server can only access its own data at the database level. This spec adds per-tenant PostgreSQL users and schemas so each tenant server can only access its own data at the database level.
@@ -13,7 +13,7 @@ This spec adds per-tenant PostgreSQL users and schemas so each tenant server can
### Current State ### Current State
- All tenant servers connect as the shared admin PG user to `cameleer3` database, `public` schema. - All tenant servers connect as the shared admin PG user to `cameleer` database, `public` schema.
- No per-tenant schemas exist — the server's Flyway runs in `public`. - No per-tenant schemas exist — the server's Flyway runs in `public`.
- `TenantDataCleanupService` already attempts `DROP SCHEMA tenant_<slug>` on delete (no-op today since schemas don't exist). - `TenantDataCleanupService` already attempts `DROP SCHEMA tenant_<slug>` on delete (no-op today since schemas don't exist).
- Standalone mode sets `currentSchema=tenant_default` in the compose file and is unaffected by this change. - Standalone mode sets `currentSchema=tenant_default` in the compose file and is unaffected by this change.
@@ -40,7 +40,7 @@ public class TenantDatabaseService {
### `createTenantDatabase(slug, password)` ### `createTenantDatabase(slug, password)`
Connects to `cameleer3` using the admin PG credentials from `ProvisioningProperties`. Executes: Connects to `cameleer` using the admin PG credentials from `ProvisioningProperties`. Executes:
1. Validate slug against `^[a-z0-9-]+$` (reject unexpected characters). 1. Validate slug against `^[a-z0-9-]+$` (reject unexpected characters).
2. `CREATE USER "tenant_<slug>" WITH PASSWORD '<password>'` (skip if user already exists — idempotent for re-provisioning). 2. `CREATE USER "tenant_<slug>" WITH PASSWORD '<password>'` (skip if user already exists — idempotent for re-provisioning).
@@ -90,7 +90,7 @@ The `ProvisionRequest` record gains `dbPassword` field.
**When `dbPassword` is present** (new tenants): **When `dbPassword` is present** (new tenants):
``` ```
SPRING_DATASOURCE_URL=jdbc:postgresql://cameleer-postgres:5432/cameleer3?currentSchema=tenant_<slug>&ApplicationName=tenant_<slug> SPRING_DATASOURCE_URL=jdbc:postgresql://cameleer-postgres:5432/cameleer?currentSchema=tenant_<slug>&ApplicationName=tenant_<slug>
SPRING_DATASOURCE_USERNAME=tenant_<slug> SPRING_DATASOURCE_USERNAME=tenant_<slug>
SPRING_DATASOURCE_PASSWORD=<generated> SPRING_DATASOURCE_PASSWORD=<generated>
``` ```

View File

@@ -68,7 +68,7 @@ The sidebar provides access to all major sections:
| **Environments** | Expandable tree showing all environments and their apps | | **Environments** | Expandable tree showing all environments and their apps |
| **License** | License tier, features, limits, and token | | **License** | License tier, features, limits, and token |
| **Platform** | Platform-wide tenant management (visible only to platform admins) | | **Platform** | Platform-wide tenant management (visible only to platform admins) |
| **View Dashboard** | Opens the observability dashboard (cameleer3-server) in a new tab | | **View Dashboard** | Opens the observability dashboard (cameleer-server) in a new tab |
| **Account** | Log out of the current session | | **Account** | Log out of the current session |
The Environments section in the sidebar renders as a collapsible tree: environments at the top level, with their applications nested underneath. Clicking any item navigates directly to its detail page. The Environments section in the sidebar renders as a collapsible tree: environments at the top level, with their applications nested underneath. Clicking any item navigates directly to its detail page.
@@ -239,10 +239,10 @@ Failed deployments are highlighted with a red accent for quick identification.
### Accessing the Observability Dashboard ### Accessing the Observability Dashboard
The observability dashboard is provided by cameleer3-server and opens in a separate browser tab: The observability dashboard is provided by cameleer-server and opens in a separate browser tab:
1. In the sidebar footer, click **View Dashboard**. 1. In the sidebar footer, click **View Dashboard**.
2. The dashboard opens at the cameleer3-server URL. 2. The dashboard opens at the cameleer-server URL.
Alternatively, from any app detail page, the **Agent Status** card includes a "View in Dashboard" link. Alternatively, from any app detail page, the **Agent Status** card includes a "View in Dashboard" link.
@@ -456,11 +456,11 @@ The platform runs as a Docker Compose stack with these services:
| Service | Purpose | Dev Port | | Service | Purpose | Dev Port |
|---------|---------|----------| |---------|---------|----------|
| **traefik** | Reverse proxy and TLS termination | 80, 443 | | **traefik** | Reverse proxy and TLS termination | 80, 443 |
| **postgres** | Database for platform data, Logto, and cameleer3-server | 5432 | | **postgres** | Database for platform data, Logto, and cameleer-server | 5432 |
| **logto** | Identity provider (OIDC/SSO) | 3001, 3002 | | **logto** | Identity provider (OIDC/SSO) | 3001, 3002 |
| **logto-bootstrap** | One-time setup (runs and exits) | -- | | **logto-bootstrap** | One-time setup (runs and exits) | -- |
| **cameleer-saas** | SaaS API server and frontend | 8080 | | **cameleer-saas** | SaaS API server and frontend | 8080 |
| **cameleer3-server** | Observability backend | 8081 | | **cameleer-server** | Observability backend | 8081 |
| **clickhouse** | Trace, metrics, and log storage | 8123 | | **clickhouse** | Trace, metrics, and log storage | 8123 |
In production mode (`docker compose up`), only ports 80 and 443 are exposed via Traefik. In development mode (`docker compose -f docker-compose.yml -f docker-compose.dev.yml up`), individual service ports are exposed directly for debugging. In production mode (`docker compose up`), only ports 80 and 443 are exposed via Traefik. In development mode (`docker compose -f docker-compose.yml -f docker-compose.dev.yml up`), individual service ports are exposed directly for debugging.
@@ -469,11 +469,11 @@ In production mode (`docker compose up`), only ports 80 and 443 are exposed via
On first boot, the `logto-bootstrap` container automatically: On first boot, the `logto-bootstrap` container automatically:
1. Waits for Logto and cameleer3-server to be healthy. 1. Waits for Logto and cameleer-server to be healthy.
2. Creates three Logto applications: 2. Creates three Logto applications:
- **Cameleer SaaS** (SPA) -- for the management UI frontend. - **Cameleer SaaS** (SPA) -- for the management UI frontend.
- **Cameleer SaaS Backend** (Machine-to-Machine) -- for server-to-Logto API calls. - **Cameleer SaaS Backend** (Machine-to-Machine) -- for server-to-Logto API calls.
- **Cameleer Dashboard** (Traditional Web App) -- for cameleer3-server OIDC login. - **Cameleer Dashboard** (Traditional Web App) -- for cameleer-server OIDC login.
3. Creates an API resource (`https://api.cameleer.local`) with 10 OAuth2 scopes (see Section 9). 3. Creates an API resource (`https://api.cameleer.local`) with 10 OAuth2 scopes (see Section 9).
4. Creates organization roles with **API resource scopes** (not standalone org permissions): 4. Creates organization roles with **API resource scopes** (not standalone org permissions):
- `admin` -- 9 tenant scopes (all except `platform:admin`). - `admin` -- 9 tenant scopes (all except `platform:admin`).
@@ -482,7 +482,7 @@ On first boot, the `logto-bootstrap` container automatically:
- Platform admin (default: `admin` / `admin`) -- has the `admin` org role plus the global `platform-admin` role (which grants `platform:admin` scope). - Platform admin (default: `admin` / `admin`) -- has the `admin` org role plus the global `platform-admin` role (which grants `platform:admin` scope).
- Demo user (default: `camel` / `camel`) -- added to the default organization with the `member` role. - Demo user (default: `camel` / `camel`) -- added to the default organization with the `member` role.
6. Creates a Logto organization ("Example Tenant") and assigns both users. 6. Creates a Logto organization ("Example Tenant") and assigns both users.
7. Configures cameleer3-server with Logto OIDC settings for dashboard authentication. 7. Configures cameleer-server with Logto OIDC settings for dashboard authentication.
8. Writes all generated IDs and secrets to `/data/logto-bootstrap.json` for the SaaS backend to consume. 8. Writes all generated IDs and secrets to `/data/logto-bootstrap.json` for the SaaS backend to consume.
The bootstrap is idempotent -- re-running it will skip resources that already exist. The bootstrap is idempotent -- re-running it will skip resources that already exist.
@@ -571,15 +571,15 @@ The Cameleer SaaS application itself does not need any changes -- all identity c
**Possible causes:** **Possible causes:**
- The agent cannot reach the cameleer3-server endpoint. Check network connectivity between the deployed container and the observability server. - The agent cannot reach the cameleer-server endpoint. Check network connectivity between the deployed container and the observability server.
- The bootstrap token does not match. The agent uses `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` to register with the server. - The bootstrap token does not match. The agent uses `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` to register with the server.
- The cameleer3-server is not healthy. - The cameleer-server is not healthy.
**Resolution:** **Resolution:**
1. Check cameleer3-server health: `docker compose logs cameleer3-server`. 1. Check cameleer-server health: `docker compose logs cameleer-server`.
2. Verify the app container's logs for agent connection errors (use the Logs tab on the app detail page). 2. Verify the app container's logs for agent connection errors (use the Logs tab on the app detail page).
3. Confirm that `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` is the same in both the `cameleer-saas` and `cameleer3-server` service configurations. 3. Confirm that `CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN` is the same in both the `cameleer-saas` and `cameleer-server` service configurations.
### Container Health Check Failing ### Container Health Check Failing

View File

@@ -0,0 +1,33 @@
# Cameleer SaaS Configuration
# Generated by installer v1.0.0 on 2026-04-15 08:55:30 UTC
VERSION=latest
PUBLIC_HOST=desktop-fb5vgj9.siegeln.internal
PUBLIC_PROTOCOL=https
HTTP_PORT=80
HTTPS_PORT=443
LOGTO_CONSOLE_PORT=3002
# PostgreSQL
POSTGRES_USER=cameleer
POSTGRES_PASSWORD=dwnyYXj3bVe6kFcOHERr57SkrkD9476a
POSTGRES_DB=cameleer_saas
# ClickHouse
CLICKHOUSE_PASSWORD=SshXE61qZqB1kVoZpQLbr2mDYokw1ZgJ
# Admin user
SAAS_ADMIN_USER=admin
SAAS_ADMIN_PASS=1J3TrbgIYZbxjav1K14uy5DX8nil6Bdi
# TLS
NODE_TLS_REJECT=0
# Docker
DOCKER_SOCKET=/var/run/docker.sock
DOCKER_GID=0
# Provisioning images
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=gitea.siegeln.net/cameleer/cameleer-server:latest
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=gitea.siegeln.net/cameleer/cameleer-server-ui:latest

View File

@@ -0,0 +1,95 @@
# Cameleer SaaS -- Installation Documentation
## Installation Summary
| | |
|---|---|
| **Version** | latest |
| **Date** | 2026-04-15 08:55:55 UTC |
| **Installer** | v1.0.0 |
| **Install Directory** | C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer |
| **Hostname** | desktop-fb5vgj9.siegeln.internal |
| **TLS** | Self-signed (auto-generated) |
## Service URLs
- **Platform UI:** https://desktop-fb5vgj9.siegeln.internal/platform/
- **API Endpoint:** https://desktop-fb5vgj9.siegeln.internal/platform/api/
- **Logto Admin Console:** https://desktop-fb5vgj9.siegeln.internal:3002
## First Steps
1. Open the Platform UI in your browser
2. Log in as admin with the credentials from `credentials.txt`
3. Create tenants from the admin console
4. The platform will provision a dedicated server instance for each tenant
## Architecture
| Container | Purpose |
|---|---|
| `traefik` | Reverse proxy, TLS termination, routing |
| `postgres` | PostgreSQL database (SaaS + Logto + tenant schemas) |
| `clickhouse` | Time-series storage (traces, metrics, logs) |
| `logto` | OIDC identity provider + bootstrap |
| `cameleer-saas` | SaaS platform (Spring Boot + React) |
Per-tenant `cameleer-server` and `cameleer-server-ui` containers are provisioned dynamically.
## Networking
| Port | Service |
|---|---|
| 80 | HTTP (redirects to HTTPS) |
| 443 | HTTPS (main entry point) |
| 3002 | Logto Admin Console |
## TLS
**Mode:** Self-signed (auto-generated)
The platform generated a self-signed certificate on first boot. To replace it:
1. Log in as admin and navigate to **Certificates** in the admin console
2. Upload your certificate and key via the UI
3. Activate the new certificate (zero-downtime swap)
## Data & Backups
| Docker Volume | Contains |
|---|---|
| `cameleer-pgdata` | PostgreSQL data (tenants, licenses, audit) |
| `cameleer-chdata` | ClickHouse data (traces, metrics, logs) |
| `cameleer-certs` | TLS certificates |
| `cameleer-bootstrapdata` | Logto bootstrap results |
### Backup Commands
```bash
docker compose -p cameleer-saas exec cameleer-postgres pg_dump -U cameleer cameleer_saas > backup.sql
docker compose -p cameleer-saas exec cameleer-clickhouse clickhouse-client --query "SELECT * FROM cameleer.traces FORMAT Native" > traces.native
```
## Upgrading
```powershell
.\install.ps1 -InstallDir C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer -Version NEW_VERSION
```
## Troubleshooting
| Issue | Command |
|---|---|
| Service not starting | `docker compose -p cameleer-saas logs SERVICE_NAME` |
| Bootstrap failed | `docker compose -p cameleer-saas logs cameleer-logto` |
| Routing issues | `docker compose -p cameleer-saas logs cameleer-traefik` |
| Database issues | `docker compose -p cameleer-saas exec cameleer-postgres psql -U cameleer -d cameleer_saas` |
## Uninstalling
```powershell
Set-Location C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer
docker compose -p cameleer-saas down
docker compose -p cameleer-saas down -v
Remove-Item -Recurse -Force C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer
```

View File

@@ -0,0 +1,18 @@
# Cameleer installation config
# Generated by installer v1.0.0 on 2026-04-15 08:55:30 UTC
install_dir=C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer
public_host=desktop-fb5vgj9.siegeln.internal
public_protocol=https
admin_user=admin
tls_mode=self-signed
http_port=80
https_port=443
logto_console_port=3002
logto_console_exposed=true
monitoring_network=
version=latest
compose_project=cameleer-saas
docker_socket=/var/run/docker.sock
node_tls_reject=0
deployment_mode=saas

View File

@@ -0,0 +1,16 @@
===========================================
CAMELEER PLATFORM CREDENTIALS
Generated: 2026-04-15 08:55:55 UTC
SECURE THIS FILE AND DELETE AFTER NOTING
THESE CREDENTIALS CANNOT BE RECOVERED
===========================================
Admin Console: https://desktop-fb5vgj9.siegeln.internal/platform/
Admin User: admin
Admin Password: 1J3TrbgIYZbxjav1K14uy5DX8nil6Bdi
PostgreSQL: cameleer / dwnyYXj3bVe6kFcOHERr57SkrkD9476a
ClickHouse: default / SshXE61qZqB1kVoZpQLbr2mDYokw1ZgJ
Logto Console: https://desktop-fb5vgj9.siegeln.internal:3002

View File

@@ -1,5 +1,5 @@
# Cameleer SaaS Platform # Cameleer SaaS Platform
# Generated by Cameleer installer do not edit manually # Generated by Cameleer installer -- do not edit manually
services: services:
cameleer-traefik: cameleer-traefik:
@@ -112,14 +112,11 @@ services:
cameleer-logto: cameleer-logto:
condition: service_healthy condition: service_healthy
environment: environment:
# SaaS database
SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/cameleer_saas SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/cameleer_saas
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer} SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer}
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
# Identity (Logto)
CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: http://cameleer-logto:3001 CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: http://cameleer-logto:3001
CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
# Provisioning — passed to per-tenant server containers
CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost}
CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https} CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https}
CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer
@@ -127,8 +124,8 @@ services:
CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer} CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer}
CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD} CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD}
CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD} CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD}
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server:latest} CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer-server:latest}
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server-ui:latest} CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui:latest}
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.saas.rule=PathPrefix(`/platform`) - traefik.http.routers.saas.rule=PathPrefix(`/platform`)
@@ -142,7 +139,7 @@ services:
networks: networks:
- cameleer - cameleer
group_add: group_add:
- "1001" - "0"
volumes: volumes:
cameleer-pgdata: cameleer-pgdata:

View File

@@ -592,7 +592,7 @@ HTTPS_PORT=$($c.HttpsPort)
# PostgreSQL # PostgreSQL
POSTGRES_USER=cameleer POSTGRES_USER=cameleer
POSTGRES_PASSWORD=$($c.PostgresPassword) POSTGRES_PASSWORD=$($c.PostgresPassword)
POSTGRES_DB=cameleer3 POSTGRES_DB=cameleer
# ClickHouse # ClickHouse
CLICKHOUSE_PASSWORD=$($c.ClickhousePassword) CLICKHOUSE_PASSWORD=$($c.ClickhousePassword)
@@ -654,8 +654,8 @@ DOCKER_SOCKET=$($c.DockerSocket)
DOCKER_GID=$gid DOCKER_GID=$gid
# Provisioning images # Provisioning images
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer3-server:$($c.Version) CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer-server:$($c.Version)
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer3-server-ui:$($c.Version) CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer-server-ui:$($c.Version)
"@ "@
$content += $provisioningBlock $content += $provisioningBlock
} }
@@ -854,8 +854,8 @@ services:
CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer} CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer}
CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD} CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD}
CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD} CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD}
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server:latest} CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer-server:latest}
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server-ui:latest} CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui:latest}
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.saas.rule=PathPrefix(`/platform`) - traefik.http.routers.saas.rule=PathPrefix(`/platform`)
@@ -953,13 +953,13 @@ services:
image: postgres:16-alpine image: postgres:16-alpine
restart: unless-stopped restart: unless-stopped
environment: environment:
POSTGRES_DB: ${POSTGRES_DB:-cameleer3} POSTGRES_DB: ${POSTGRES_DB:-cameleer}
POSTGRES_USER: ${POSTGRES_USER:-cameleer} POSTGRES_USER: ${POSTGRES_USER:-cameleer}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes: volumes:
- cameleer-pgdata:/var/lib/postgresql/data - cameleer-pgdata:/var/lib/postgresql/data
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d $${POSTGRES_DB:-cameleer3}"] test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d $${POSTGRES_DB:-cameleer}"]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -993,7 +993,7 @@ services:
$serverBlock = @" $serverBlock = @"
cameleer-server: cameleer-server:
image: `${SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer3-server}:`${VERSION:-latest} image: `${SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server}:`${VERSION:-latest}
container_name: cameleer-server container_name: cameleer-server
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
@@ -1001,7 +1001,7 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
CAMELEER_SERVER_TENANT_ID: default CAMELEER_SERVER_TENANT_ID: default
SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/`${POSTGRES_DB:-cameleer3}?currentSchema=tenant_default SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/`${POSTGRES_DB:-cameleer}?currentSchema=tenant_default
SPRING_DATASOURCE_USERNAME: `${POSTGRES_USER:-cameleer} SPRING_DATASOURCE_USERNAME: `${POSTGRES_USER:-cameleer}
SPRING_DATASOURCE_PASSWORD: `${POSTGRES_PASSWORD} SPRING_DATASOURCE_PASSWORD: `${POSTGRES_PASSWORD}
CAMELEER_SERVER_CLICKHOUSE_URL: jdbc:clickhouse://cameleer-clickhouse:8123/cameleer CAMELEER_SERVER_CLICKHOUSE_URL: jdbc:clickhouse://cameleer-clickhouse:8123/cameleer
@@ -1044,7 +1044,7 @@ services:
- cameleer-apps - cameleer-apps
cameleer-server-ui: cameleer-server-ui:
image: `${SERVER_UI_IMAGE:-gitea.siegeln.net/cameleer/cameleer3-server-ui}:`${VERSION:-latest} image: `${SERVER_UI_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui}:`${VERSION:-latest}
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
cameleer-server: cameleer-server:
@@ -1430,7 +1430,7 @@ $logtoConsoleRow
| ``logto`` | OIDC identity provider + bootstrap | | ``logto`` | OIDC identity provider + bootstrap |
| ``cameleer-saas`` | SaaS platform (Spring Boot + React) | | ``cameleer-saas`` | SaaS platform (Spring Boot + React) |
Per-tenant ``cameleer3-server`` and ``cameleer3-server-ui`` containers are provisioned dynamically. Per-tenant ``cameleer-server`` and ``cameleer-server-ui`` containers are provisioned dynamically.
## Networking ## Networking
@@ -1579,7 +1579,7 @@ $tlsSection
### Backup Commands ### Backup Commands
``````bash ``````bash
docker compose -p $($c.ComposeProject) exec cameleer-postgres pg_dump -U cameleer cameleer3 > backup.sql docker compose -p $($c.ComposeProject) exec cameleer-postgres pg_dump -U cameleer cameleer > backup.sql
docker compose -p $($c.ComposeProject) exec cameleer-clickhouse clickhouse-client --query "SELECT * FROM cameleer.traces FORMAT Native" > traces.native docker compose -p $($c.ComposeProject) exec cameleer-clickhouse clickhouse-client --query "SELECT * FROM cameleer.traces FORMAT Native" > traces.native
`````` ``````
@@ -1596,7 +1596,7 @@ docker compose -p $($c.ComposeProject) exec cameleer-clickhouse clickhouse-clien
| Service not starting | ``docker compose -p $($c.ComposeProject) logs SERVICE_NAME`` | | Service not starting | ``docker compose -p $($c.ComposeProject) logs SERVICE_NAME`` |
| Server issues | ``docker compose -p $($c.ComposeProject) logs server`` | | Server issues | ``docker compose -p $($c.ComposeProject) logs server`` |
| Routing issues | ``docker compose -p $($c.ComposeProject) logs cameleer-traefik`` | | Routing issues | ``docker compose -p $($c.ComposeProject) logs cameleer-traefik`` |
| Database issues | ``docker compose -p $($c.ComposeProject) exec cameleer-postgres psql -U cameleer -d cameleer3`` | | Database issues | ``docker compose -p $($c.ComposeProject) exec cameleer-postgres psql -U cameleer -d cameleer`` |
## Uninstalling ## Uninstalling

View File

@@ -588,7 +588,7 @@ HTTPS_PORT=${HTTPS_PORT}
# PostgreSQL # PostgreSQL
POSTGRES_USER=cameleer POSTGRES_USER=cameleer
POSTGRES_PASSWORD=${POSTGRES_PASSWORD} POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_DB=cameleer3 POSTGRES_DB=cameleer
# ClickHouse # ClickHouse
CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD} CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
@@ -663,8 +663,8 @@ DOCKER_SOCKET=${DOCKER_SOCKET}
DOCKER_GID=$(stat -c '%g' "${DOCKER_SOCKET}" 2>/dev/null || echo "0") DOCKER_GID=$(stat -c '%g' "${DOCKER_SOCKET}" 2>/dev/null || echo "0")
# Provisioning images # Provisioning images
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer3-server:${VERSION} CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer-server:${VERSION}
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer3-server-ui:${VERSION} CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer-server-ui:${VERSION}
EOF EOF
log_info "Generated .env" log_info "Generated .env"
@@ -867,8 +867,8 @@ EOF
CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer} CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer}
CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD} CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD}
CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD} CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD}
CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server:latest} CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer-server:latest}
CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer3-server-ui:latest} CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui:latest}
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.saas.rule=PathPrefix(`/platform`) - traefik.http.routers.saas.rule=PathPrefix(`/platform`)
@@ -977,13 +977,13 @@ COMPOSEEOF
image: postgres:16-alpine image: postgres:16-alpine
restart: unless-stopped restart: unless-stopped
environment: environment:
POSTGRES_DB: ${POSTGRES_DB:-cameleer3} POSTGRES_DB: ${POSTGRES_DB:-cameleer}
POSTGRES_USER: ${POSTGRES_USER:-cameleer} POSTGRES_USER: ${POSTGRES_USER:-cameleer}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes: volumes:
- cameleer-pgdata:/var/lib/postgresql/data - cameleer-pgdata:/var/lib/postgresql/data
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d $${POSTGRES_DB:-cameleer3}"] test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d $${POSTGRES_DB:-cameleer}"]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -1024,7 +1024,7 @@ COMPOSEEOF
cat >> "$f" << COMPOSEEOF cat >> "$f" << COMPOSEEOF
cameleer-server: cameleer-server:
image: \${SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer3-server}:\${VERSION:-latest} image: \${SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server}:\${VERSION:-latest}
container_name: cameleer-server container_name: cameleer-server
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
@@ -1032,7 +1032,7 @@ COMPOSEEOF
condition: service_healthy condition: service_healthy
environment: environment:
CAMELEER_SERVER_TENANT_ID: default CAMELEER_SERVER_TENANT_ID: default
SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/\${POSTGRES_DB:-cameleer3}?currentSchema=tenant_default SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/\${POSTGRES_DB:-cameleer}?currentSchema=tenant_default
SPRING_DATASOURCE_USERNAME: \${POSTGRES_USER:-cameleer} SPRING_DATASOURCE_USERNAME: \${POSTGRES_USER:-cameleer}
SPRING_DATASOURCE_PASSWORD: \${POSTGRES_PASSWORD} SPRING_DATASOURCE_PASSWORD: \${POSTGRES_PASSWORD}
CAMELEER_SERVER_CLICKHOUSE_URL: jdbc:clickhouse://cameleer-clickhouse:8123/cameleer CAMELEER_SERVER_CLICKHOUSE_URL: jdbc:clickhouse://cameleer-clickhouse:8123/cameleer
@@ -1075,7 +1075,7 @@ COMPOSEEOF
- cameleer-apps - cameleer-apps
cameleer-server-ui: cameleer-server-ui:
image: \${SERVER_UI_IMAGE:-gitea.siegeln.net/cameleer/cameleer3-server-ui}:\${VERSION:-latest} image: \${SERVER_UI_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui}:\${VERSION:-latest}
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
cameleer-server: cameleer-server:
@@ -1372,7 +1372,7 @@ EOF
| `logto` | OIDC identity provider + bootstrap | | `logto` | OIDC identity provider + bootstrap |
| `cameleer-saas` | SaaS platform (Spring Boot + React) | | `cameleer-saas` | SaaS platform (Spring Boot + React) |
Per-tenant `cameleer3-server` and `cameleer3-server-ui` containers are provisioned dynamically when tenants are created. Per-tenant `cameleer-server` and `cameleer-server-ui` containers are provisioned dynamically when tenants are created.
## Networking ## Networking
@@ -1561,7 +1561,7 @@ EOF
\`\`\`bash \`\`\`bash
# PostgreSQL # PostgreSQL
docker compose -p ${COMPOSE_PROJECT} exec cameleer-postgres pg_dump -U cameleer cameleer3 > backup.sql docker compose -p ${COMPOSE_PROJECT} exec cameleer-postgres pg_dump -U cameleer cameleer > backup.sql
# ClickHouse # ClickHouse
docker compose -p ${COMPOSE_PROJECT} exec cameleer-clickhouse clickhouse-client --query "SELECT * FROM cameleer.traces FORMAT Native" > traces.native docker compose -p ${COMPOSE_PROJECT} exec cameleer-clickhouse clickhouse-client --query "SELECT * FROM cameleer.traces FORMAT Native" > traces.native
@@ -1584,7 +1584,7 @@ The installer preserves your \`.env\`, credentials, and data volumes. Only the c
| Service not starting | \`docker compose -p ${COMPOSE_PROJECT} logs SERVICE_NAME\` | | Service not starting | \`docker compose -p ${COMPOSE_PROJECT} logs SERVICE_NAME\` |
| Server issues | \`docker compose -p ${COMPOSE_PROJECT} logs server\` | | Server issues | \`docker compose -p ${COMPOSE_PROJECT} logs server\` |
| Routing issues | \`docker compose -p ${COMPOSE_PROJECT} logs cameleer-traefik\` | | Routing issues | \`docker compose -p ${COMPOSE_PROJECT} logs cameleer-traefik\` |
| Database issues | \`docker compose -p ${COMPOSE_PROJECT} exec cameleer-postgres psql -U cameleer -d cameleer3\` | | Database issues | \`docker compose -p ${COMPOSE_PROJECT} exec cameleer-postgres psql -U cameleer -d cameleer\` |
## Uninstalling ## Uninstalling

Some files were not shown because too many files have changed in this diff Show More