chore: rename cameleer3 to cameleer
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:
@@ -3,7 +3,7 @@
|
||||
**Date:** 2026-03-29
|
||||
**Status:** Draft — Awaiting Review
|
||||
**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
|
||||
|
||||
@@ -32,14 +32,14 @@ Week 8-14: Live Route Debugger (agent + server + UI)
|
||||
- #59 — Cross-Service Trace Correlation + Topology Map
|
||||
|
||||
**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
|
||||
- #62 — Agent: ExchangeStateSerializer + synthetic direct route wrapper
|
||||
- #63 — Server: DebugSessionService + WebSocket + REST API
|
||||
- #70 — UI: Debug session frontend components
|
||||
|
||||
**Lineage sub-issues:**
|
||||
- #64 — Protocol: Lineage command types (`cameleer3-common`)
|
||||
- #64 — Protocol: Lineage command types (`cameleer-common`)
|
||||
- #65 — Agent: LineageManager + capture mode integration
|
||||
- #66 — Server: LineageService + DiffEngine + REST API
|
||||
- #71 — UI: Lineage timeline + diff viewer components
|
||||
@@ -69,14 +69,14 @@ Browser (SaaS UI)
|
||||
WebSocket <--------------------------------------+
|
||||
| |
|
||||
v |
|
||||
cameleer3-server |
|
||||
cameleer-server |
|
||||
| POST /api/v1/debug/sessions |
|
||||
| POST /api/v1/debug/sessions/{id}/step |
|
||||
| POST /api/v1/debug/sessions/{id}/resume |
|
||||
| DELETE /api/v1/debug/sessions/{id} |
|
||||
| |
|
||||
v |
|
||||
SSE Command Channel --> cameleer3 agent |
|
||||
SSE Command Channel --> cameleer agent |
|
||||
| | |
|
||||
| "start-debug" | |
|
||||
| command v |
|
||||
@@ -101,7 +101,7 @@ SSE Command Channel --> cameleer3 agent |
|
||||
| Continue to next processor
|
||||
```
|
||||
|
||||
### 1.3 Protocol Additions (cameleer3-common)
|
||||
### 1.3 Protocol Additions (cameleer-common)
|
||||
|
||||
#### 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
|
||||
|
||||
- Location: `com.cameleer3.agent.debug.DebugSessionManager`
|
||||
- Location: `com.cameleer.agent.debug.DebugSessionManager`
|
||||
- Stores active sessions: `ConcurrentHashMap<sessionId, DebugSession>`
|
||||
- Enforces max concurrent sessions (default 3, configurable via `cameleer.debug.maxSessions`)
|
||||
- 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()`
|
||||
4. Remove temporary route on session completion
|
||||
|
||||
### 1.5 Server Implementation (cameleer3-server)
|
||||
### 1.5 Server Implementation (cameleer-server)
|
||||
|
||||
#### REST Endpoints
|
||||
|
||||
@@ -308,7 +308,7 @@ Capture the full transformation history of a message flowing through a route. At
|
||||
### 2.2 Architecture
|
||||
|
||||
```
|
||||
cameleer3 agent
|
||||
cameleer agent
|
||||
|
|
||||
| On lineage-enabled exchange:
|
||||
| Before processor: capture INPUT
|
||||
@@ -319,7 +319,7 @@ cameleer3 agent
|
||||
POST /api/v1/data/executions (processors carry full snapshots)
|
||||
|
|
||||
v
|
||||
cameleer3-server
|
||||
cameleer-server
|
||||
|
|
||||
| LineageService:
|
||||
| > Flatten processor tree to ordered list
|
||||
@@ -334,7 +334,7 @@ GET /api/v1/executions/{id}/lineage
|
||||
Browser: LineageTimeline + DiffViewer
|
||||
```
|
||||
|
||||
### 2.3 Protocol Additions (cameleer3-common)
|
||||
### 2.3 Protocol Additions (cameleer-common)
|
||||
|
||||
#### New SSE Commands
|
||||
|
||||
@@ -370,11 +370,11 @@ Browser: LineageTimeline + DiffViewer
|
||||
| `EXPRESSION` | Any exchange matching a Simple/JsonPath predicate |
|
||||
| `NEXT_N` | Next N exchanges on the route (countdown) |
|
||||
|
||||
### 2.4 Agent Implementation (cameleer3-agent)
|
||||
### 2.4 Agent Implementation (cameleer-agent)
|
||||
|
||||
#### LineageManager
|
||||
|
||||
- Location: `com.cameleer3.agent.lineage.LineageManager`
|
||||
- Location: `com.cameleer.agent.lineage.LineageManager`
|
||||
- Stores active configs: `ConcurrentHashMap<lineageId, LineageConfig>`
|
||||
- Tracks capture count per lineageId: auto-disables at `maxCaptures`
|
||||
- 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
|
||||
```
|
||||
|
||||
### 2.5 Server Implementation (cameleer3-server)
|
||||
### 2.5 Server Implementation (cameleer-server)
|
||||
|
||||
#### LineageService
|
||||
|
||||
@@ -548,7 +548,7 @@ New (added):
|
||||
| Direct/SEDA | URI prefix `direct:`, `seda:`, `vm:` | Exchange property (in-process) |
|
||||
| File/FTP | URI prefix `file:`, `ftp:` | Not propagated (async) |
|
||||
|
||||
### 3.3 Agent Implementation (cameleer3-agent)
|
||||
### 3.3 Agent Implementation (cameleer-agent)
|
||||
|
||||
#### Outgoing Propagation (InterceptStrategy)
|
||||
|
||||
@@ -597,7 +597,7 @@ execution.setHopIndex(...); // depth in distributed trace
|
||||
- Parse failure: log warning, continue without context (no exchange failure)
|
||||
- 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
|
||||
|
||||
@@ -665,7 +665,7 @@ CREATE INDEX idx_executions_parent_span
|
||||
- **Fan-out:** parallel multicast creates multiple children from same processor
|
||||
- **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
|
||||
|
||||
@@ -799,11 +799,11 @@ Reserve `sourceTenantHash` in TraceContext for future use:
|
||||
|
||||
| Work | Repo | Issue |
|
||||
|------|------|-------|
|
||||
| Service topology materialized view | cameleer3-server | #69 |
|
||||
| Topology REST API | cameleer3-server | #69 |
|
||||
| ServiceTopologyGraph.tsx | cameleer3-server + saas | #72 |
|
||||
| WebSocket infrastructure (for debugger) | cameleer3-server | #63 |
|
||||
| TraceContext DTO in cameleer3-common | cameleer3 | #67 |
|
||||
| Service topology materialized view | cameleer-server | #69 |
|
||||
| Topology REST API | cameleer-server | #69 |
|
||||
| ServiceTopologyGraph.tsx | cameleer-server + saas | #72 |
|
||||
| WebSocket infrastructure (for debugger) | cameleer-server | #63 |
|
||||
| TraceContext DTO in cameleer-common | cameleer | #67 |
|
||||
|
||||
**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 |
|
||||
|------|------|-------|
|
||||
| Lineage protocol DTOs | cameleer3-common | #64 |
|
||||
| LineageManager + capture integration | cameleer3-agent | #65 |
|
||||
| LineageService + DiffEngine | cameleer3-server | #66 |
|
||||
| Lineage UI components | cameleer3-server + saas | #71 |
|
||||
| Lineage protocol DTOs | cameleer-common | #64 |
|
||||
| LineageManager + capture integration | cameleer-agent | #65 |
|
||||
| LineageService + DiffEngine | cameleer-server | #66 |
|
||||
| Lineage UI components | cameleer-server + saas | #71 |
|
||||
|
||||
**Ship:** Payload flow lineage independently usable.
|
||||
|
||||
@@ -822,10 +822,10 @@ Reserve `sourceTenantHash` in TraceContext for future use:
|
||||
|
||||
| Work | Repo | Issue |
|
||||
|------|------|-------|
|
||||
| Trace context header propagation | cameleer3-agent | #67 |
|
||||
| Executions table migration (new columns) | cameleer3-server | #68 |
|
||||
| CorrelationService + trace assembly | cameleer3-server | #68 |
|
||||
| DistributedTraceView + TraceSearch UI | cameleer3-server + saas | #72 |
|
||||
| Trace context header propagation | cameleer-agent | #67 |
|
||||
| Executions table migration (new columns) | cameleer-server | #68 |
|
||||
| CorrelationService + trace assembly | cameleer-server | #68 |
|
||||
| DistributedTraceView + TraceSearch UI | cameleer-server + saas | #72 |
|
||||
|
||||
**Ship:** Distributed traces + topology — full correlation story.
|
||||
|
||||
@@ -833,11 +833,11 @@ Reserve `sourceTenantHash` in TraceContext for future use:
|
||||
|
||||
| Work | Repo | Issue |
|
||||
|------|------|-------|
|
||||
| Debug protocol DTOs | cameleer3-common | #60 |
|
||||
| DebugSessionManager + InterceptStrategy | cameleer3-agent | #61 |
|
||||
| ExchangeStateSerializer + synthetic wrapper | cameleer3-agent | #62 |
|
||||
| DebugSessionService + WS + REST | cameleer3-server | #63 |
|
||||
| Debug UI components | cameleer3-server + saas | #70 |
|
||||
| Debug protocol DTOs | cameleer-common | #60 |
|
||||
| DebugSessionManager + InterceptStrategy | cameleer-agent | #61 |
|
||||
| ExchangeStateSerializer + synthetic wrapper | cameleer-agent | #62 |
|
||||
| DebugSessionService + WS + REST | cameleer-server | #63 |
|
||||
| Debug UI components | cameleer-server + saas | #70 |
|
||||
|
||||
**Ship:** Full browser-based route debugger with integration to lineage and correlation.
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
### Two Deployment Modes
|
||||
@@ -27,8 +27,8 @@
|
||||
|
||||
| Component | Role | Changes Required |
|
||||
|-----------|------|------------------|
|
||||
| cameleer3 (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 (agent) | Zero-code Camel instrumentation, auto-injected into customer JARs | MOAT features (lineage, correlation, debugger, replay) |
|
||||
| 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 |
|
||||
| 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)]
|
||||
↓ (tenant routes) ↓ (provisioning)
|
||||
[Tenant cameleer3-server] [Flux CD → K8s]
|
||||
[Tenant cameleer-server] [Flux CD → K8s]
|
||||
```
|
||||
|
||||
### Component Map
|
||||
@@ -114,7 +114,7 @@ Single Spring Boot application with well-bounded internal modules. K8s ingress h
|
||||
│ (PostgreSQL) │ │ API │ │ │
|
||||
│ - tenants │ └────────┘ │ ┌─────────────────────┐ │
|
||||
│ - users │ │ │ tenant-a namespace │ │
|
||||
│ - teams │ ┌─────┐ │ │ ├─ cameleer3-server │ │
|
||||
│ - teams │ ┌─────┐ │ │ ├─ cameleer-server │ │
|
||||
│ - audit log │ │Flux │ │ │ ├─ camel-app-1 │ │
|
||||
│ - licenses │ │ CD │ │ │ ├─ camel-app-2 │ │
|
||||
└──────────────┘ └──┬──┘ │ │ └─ 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 frontend | React, @cameleer/design-system |
|
||||
| Platform database | PostgreSQL |
|
||||
| Tenant observability | cameleer3-server (Spring Boot), PostgreSQL, OpenSearch |
|
||||
| Tenant observability | cameleer-server (Spring Boot), PostgreSQL, OpenSearch |
|
||||
| GitOps | Flux CD |
|
||||
| K8s distribution | Talos (production), k3s (dev) |
|
||||
| Ingress | Traefik or Envoy |
|
||||
@@ -192,7 +192,7 @@ Stores all SaaS control plane data — completely separate from tenant observabi
|
||||
|
||||
### 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 graph topology
|
||||
@@ -215,12 +215,12 @@ Completely separate: Prometheus TSDB for metrics, Loki for logs.
|
||||
|
||||
### 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
|
||||
- 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
|
||||
- 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
|
||||
|
||||
### RBAC Model
|
||||
@@ -252,7 +252,7 @@ Customer signs up + payment
|
||||
→ Create tenant record + Stripe customer/subscription
|
||||
→ Generate signed license token (Ed25519)
|
||||
→ 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 OpenSearch index template + per-tenant credentials
|
||||
→ Readiness check: server healthy, DB migrated, auth working
|
||||
@@ -297,7 +297,7 @@ Full Cluster API automation deferred to future release.
|
||||
### 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)
|
||||
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
|
||||
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
|
||||
|
||||
Each tenant gets a dedicated cameleer3-server instance:
|
||||
Each tenant gets a dedicated cameleer-server instance:
|
||||
- Shared tiers: deployed in tenant's namespace
|
||||
- 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 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
|
||||
- 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 → public internet (Camel app external connectivity)
|
||||
- **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
|
||||
|
||||
@@ -546,7 +546,7 @@ Completely separate from tenant observability data.
|
||||
- TLS certificate expiry < 14 days
|
||||
- Metering pipeline stale > 1 hour
|
||||
- Disk usage > 80% on any PV
|
||||
- Tenant cameleer3-server unhealthy > 5 minutes
|
||||
- Tenant cameleer-server unhealthy > 5 minutes
|
||||
- OOMKill on any tenant workload
|
||||
|
||||
### Dashboards
|
||||
@@ -577,7 +577,7 @@ K8s Metrics → Metrics Collector → Usage Aggregator (hourly) → Stripe Usage
|
||||
|-----------|------|--------|
|
||||
| CPU | core·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
|
||||
- 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 → Logs** | Live stdout/stderr stream |
|
||||
| **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 |
|
||||
| **Settings** | Tenant config, SSO/OIDC, vault connections |
|
||||
| **Billing** | Usage, invoices, plan management |
|
||||
@@ -621,7 +621,7 @@ K8s Metrics → Metrics Collector → Usage Aggregator (hourly) → Stripe Usage
|
||||
### Design
|
||||
|
||||
- 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)
|
||||
|
||||
---
|
||||
@@ -681,4 +681,4 @@ K8s Metrics → Metrics Collector → Usage Aggregator (hourly) → Stripe Usage
|
||||
| 12 | Platform Operations & Self-Monitoring | epic, ops |
|
||||
| 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.
|
||||
|
||||
@@ -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). |
|
||||
| **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). |
|
||||
| **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. |
|
||||
| **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). |
|
||||
@@ -42,14 +42,14 @@ Key constraints:
|
||||
| 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. |
|
||||
| **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. |
|
||||
| **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. |
|
||||
| **Billing integration** | Stripe API calls, subscription lifecycle, webhook handling. Thin integration layer. |
|
||||
| **Observability proxy** | Routing authenticated requests to tenant-specific cameleer3-server instances. |
|
||||
| **MOAT features** | Debugger, Lineage, Correlation — the defensible product. Built in cameleer3 agent + server. |
|
||||
| **Observability proxy** | Routing authenticated requests to tenant-specific cameleer-server instances. |
|
||||
| **MOAT features** | Debugger, Lineage, Correlation — the defensible product. Built in cameleer agent + server. |
|
||||
|
||||
### SKIP / DEFER
|
||||
|
||||
@@ -74,7 +74,7 @@ Key constraints:
|
||||
+--------+---------------------+------------------------+
|
||||
| |
|
||||
+--------v--------+ +---------v-----------+
|
||||
| cameleer-saas | | cameleer3-server |
|
||||
| cameleer-saas | | cameleer-server |
|
||||
| (Spring Boot) | | (observability) |
|
||||
| Control plane | | Per-tenant instance |
|
||||
+---+-------+-----+ +----------+----------+
|
||||
@@ -99,10 +99,10 @@ API request:
|
||||
-> Traefik forwards to upstream service
|
||||
|
||||
Machine auth (agent bootstrap):
|
||||
cameleer3-agent -> cameleer-saas /api/agent/register
|
||||
cameleer-agent -> cameleer-saas /api/agent/register
|
||||
-> Validates bootstrap token (Ed25519)
|
||||
-> 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.
|
||||
@@ -137,9 +137,9 @@ Customer uploads JAR
|
||||
-> Validation (file type, size, SHA-256, security scan)
|
||||
-> Templated Dockerfile generation:
|
||||
FROM eclipse-temurin:21-jre-alpine
|
||||
COPY cameleer3-agent.jar /opt/agent/
|
||||
COPY cameleer-agent.jar /opt/agent/
|
||||
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:
|
||||
Docker mode: docker build via docker-java (local image cache)
|
||||
K8s mode: Kaniko Job -> push to registry
|
||||
@@ -152,7 +152,7 @@ Customer uploads JAR
|
||||
- **Schema-per-tenant** in PostgreSQL for platform data isolation.
|
||||
- **Logto organizations** map 1:1 to tenants. Logto handles user-tenant membership.
|
||||
- **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.
|
||||
|
||||
### Environment Model
|
||||
@@ -232,8 +232,8 @@ services:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.auth.rule=PathPrefix(`/auth`)
|
||||
|
||||
cameleer3-server:
|
||||
image: gitea.siegeln.net/cameleer/cameleer3-server:${VERSION}
|
||||
cameleer-server:
|
||||
image: gitea.siegeln.net/cameleer/cameleer-server:${VERSION}
|
||||
environment:
|
||||
- CLICKHOUSE_URL=jdbc:clickhouse://clickhouse:8123/cameleer
|
||||
labels:
|
||||
@@ -312,9 +312,9 @@ volumes:
|
||||
### Phase 4: Observability Pipeline
|
||||
**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
|
||||
- Observability API proxy (tenant-aware routing to cameleer3-server)
|
||||
- Observability API proxy (tenant-aware routing to cameleer-server)
|
||||
- Basic topology graph endpoint
|
||||
- Agent ↔ server connectivity verification
|
||||
|
||||
@@ -367,13 +367,13 @@ volumes:
|
||||
1. Upload a sample Camel JAR via API
|
||||
2. Platform builds container image
|
||||
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
|
||||
6. Logs are accessible via API
|
||||
7. Deploy same image to "prod" with different config
|
||||
|
||||
### 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
|
||||
3. Topology graph shows route structure
|
||||
4. Different tenant cannot see another tenant's data
|
||||
@@ -393,7 +393,7 @@ docker compose up -d
|
||||
# Create tenant + user via API/Logto
|
||||
# Upload sample Camel JAR
|
||||
# Deploy to environment
|
||||
# Verify agent connects to cameleer3-server
|
||||
# Verify agent connects to cameleer-server
|
||||
# Verify traces in ClickHouse
|
||||
# Verify observability API returns data
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| Container networking | Shared `cameleer` bridge network | Customer containers join the existing network. Agent reaches cameleer3-server at `http://cameleer3-server:8081`. |
|
||||
| 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 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
@@ -157,7 +157,7 @@ Uses `com.github.docker-java:docker-java` library. Connects via Docker socket (`
|
||||
- Environment variables:
|
||||
- `CAMELEER_AUTH_TOKEN={bootstrap-token}`
|
||||
- `CAMELEER_EXPORT_TYPE=HTTP`
|
||||
- `CAMELEER_EXPORT_ENDPOINT=http://cameleer3-server:8081`
|
||||
- `CAMELEER_EXPORT_ENDPOINT=http://cameleer-server:8081`
|
||||
- `CAMELEER_APPLICATION_ID={app-slug}`
|
||||
- `CAMELEER_ENVIRONMENT_ID={env-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
|
||||
WORKDIR /app
|
||||
|
||||
COPY cameleer3-agent-{version}-shaded.jar /app/agent.jar
|
||||
COPY cameleer-agent-{version}-shaded.jar /app/agent.jar
|
||||
|
||||
ENTRYPOINT exec java \
|
||||
-Dcameleer.export.type=${CAMELEER_EXPORT_TYPE:-HTTP} \
|
||||
@@ -250,11 +250,11 @@ ORDER BY (tenant_id, environment_id, app_id, timestamp);
|
||||
|
||||
### 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
|
||||
|
||||
@@ -354,7 +354,7 @@ The cameleer-saas service needs:
|
||||
- JAR storage volume: `jardata:/data/jars`
|
||||
- `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:
|
||||
```yaml
|
||||
@@ -413,7 +413,7 @@ cameleer:
|
||||
3. Poll `GET /api/apps/{aid}/deployments/{did}` — status transitions: `BUILDING` → `STARTING` → `RUNNING`
|
||||
4. Container visible in `docker ps` as `{tenant}-{env}-{app}`
|
||||
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
|
||||
8. Container logs appear in ClickHouse `container_logs` table
|
||||
9. `GET /api/apps/{aid}/logs` returns log entries
|
||||
|
||||
@@ -7,18 +7,18 @@
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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 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. |
|
||||
| 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. |
|
||||
@@ -27,16 +27,16 @@ cameleer3-server already has the complete observability stack — ClickHouse sch
|
||||
## What's Already Working (Phase 3)
|
||||
|
||||
- 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`
|
||||
- cameleer3-server writes traces/metrics/diagrams/logs to ClickHouse
|
||||
- Traefik routes `/observe/*` to cameleer3-server with forward-auth middleware
|
||||
- Agent configured: `CAMELEER_AUTH_TOKEN`, `CAMELEER_EXPORT_ENDPOINT=http://cameleer-server:8081`, `CAMELEER_APPLICATION_ID`, `CAMELEER_ENVIRONMENT_ID`
|
||||
- cameleer-server writes traces/metrics/diagrams/logs to ClickHouse
|
||||
- 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
|
||||
|
||||
## Component 1: Serve cameleer3-server Dashboard
|
||||
## Component 1: Serve cameleer-server Dashboard
|
||||
|
||||
### 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
|
||||
# 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
|
||||
```
|
||||
|
||||
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
|
||||
- traefik.http.middlewares.dashboard-strip.stripprefix.prefixes=/dashboard
|
||||
- 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
|
||||
|
||||
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
|
||||
cameleer3-server:
|
||||
cameleer-server:
|
||||
environment:
|
||||
CAMELEER_TENANT_ID: ${CAMELEER_TENANT_SLUG:-default}
|
||||
```
|
||||
@@ -76,7 +76,7 @@ Add `CAMELEER_TENANT_SLUG` to `.env.example`.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -100,15 +100,15 @@ public record AgentStatusResponse(
|
||||
|
||||
### 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.
|
||||
|
||||
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
|
||||
SELECT max(timestamp) as last_seen
|
||||
@@ -212,17 +212,17 @@ cameleer:
|
||||
|
||||
### 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
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void verifyConnectivity() {
|
||||
// HTTP GET http://cameleer3-server:8081/actuator/health
|
||||
// Log result: "cameleer3-server connectivity: OK" or "FAILED: ..."
|
||||
// HTTP GET http://cameleer-server:8081/actuator/health
|
||||
// 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
|
||||
|
||||
@@ -259,10 +259,10 @@ This requires cameleer-saas to query ClickHouse directly (the `clickHouseDataSou
|
||||
|
||||
## Docker Compose Changes
|
||||
|
||||
### cameleer3-server labels (add dashboard route)
|
||||
### cameleer-server labels (add dashboard route)
|
||||
|
||||
```yaml
|
||||
cameleer3-server:
|
||||
cameleer-server:
|
||||
environment:
|
||||
CAMELEER_TENANT_ID: ${CAMELEER_TENANT_SLUG:-default}
|
||||
labels:
|
||||
@@ -304,18 +304,18 @@ cameleer:
|
||||
1. Deploy a sample Camel REST app with `exposedPort: 8080`
|
||||
2. `curl http://order-svc.default.acme.localhost` hits the Camel app
|
||||
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`
|
||||
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
|
||||
9. Route topology graph shows the Camel route structure
|
||||
10. `CAMELEER_TENANT_ID` is set to the tenant slug in ClickHouse data
|
||||
|
||||
## What Phase 4 Does NOT Touch
|
||||
|
||||
- No changes to cameleer3-server code (works as-is for single-tenant Docker mode)
|
||||
- No changes to the cameleer3 agent
|
||||
- No new ClickHouse schemas (cameleer3-server manages its own)
|
||||
- No changes to cameleer-server code (works as-is for single-tenant Docker mode)
|
||||
- No changes to the cameleer agent
|
||||
- No new ClickHouse schemas (cameleer-server manages its own)
|
||||
- No SaaS management UI (Phase 9)
|
||||
- No K8s-specific changes (Phase 5)
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
|
||||
## 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
|
||||
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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
|
||||
3. Logto callback at `/callback` — exchange code for tokens
|
||||
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
|
||||
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
|
||||
- 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)
|
||||
|
||||
## API Integration
|
||||
@@ -181,7 +181,7 @@ ui/
|
||||
│ ├── main.tsx — React root + providers
|
||||
│ ├── router.tsx — React Router config
|
||||
│ ├── auth/
|
||||
│ │ ├── auth-store.ts — Zustand store (same keys as cameleer3-server)
|
||||
│ │ ├── auth-store.ts — Zustand store (same keys as cameleer-server)
|
||||
│ │ ├── LoginPage.tsx
|
||||
│ │ ├── CallbackPage.tsx
|
||||
│ │ └── ProtectedRoute.tsx
|
||||
@@ -302,14 +302,14 @@ import { ThemeProvider, ToastProvider, BreadcrumbProvider } from '@cameleer/desi
|
||||
6. Deploy triggers async deployment, status polls and updates live
|
||||
7. Agent status shows registered/connected
|
||||
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
|
||||
11. RBAC: VIEWER cannot see deploy button, DEVELOPER cannot delete environments
|
||||
12. Production build: `npm run build` + `mvn package` produces JAR with embedded SPA
|
||||
|
||||
## 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 team management (Logto org admin — deferred)
|
||||
- No tenant settings/profile page
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Date:** 2026-04-05
|
||||
**Status:** Draft
|
||||
**Scope:** cameleer-saas (large), cameleer3-server (small), cameleer3 agent (none)
|
||||
**Scope:** cameleer-saas (large), cameleer-server (small), cameleer agent (none)
|
||||
|
||||
## 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.
|
||||
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.
|
||||
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.
|
||||
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 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) |
|
||||
| API key (opaque) | SaaS platform | N/A (hashed at rest) | cameleer3-server (bootstrap validator) | Agent initial registration |
|
||||
| Ed25519 signature | cameleer3-server | EdDSA | Agent | Server → agent command integrity |
|
||||
| Server internal JWT | cameleer-server | HS256 (symmetric) | Issuing server only | Agents (after registration) |
|
||||
| API key (opaque) | SaaS platform | N/A (hashed at rest) | cameleer-server (bootstrap validator) | Agent initial registration |
|
||||
| Ed25519 signature | cameleer-server | EdDSA | Agent | Server → agent command integrity |
|
||||
|
||||
### 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
|
||||
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)
|
||||
2. Server exchanges auth code for ID token, validates via provider JWKS
|
||||
3. Server issues internal HMAC JWT with mapped roles
|
||||
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)
|
||||
2. Calls tenant server API with `Authorization: Bearer <logto-m2m-token>`
|
||||
3. Server validates via Logto JWKS (new capability — see server changes below)
|
||||
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)
|
||||
2. Calls `POST /api/v1/agents/register` with `Authorization: Bearer <api-key>`
|
||||
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
|
||||
|
||||
### cameleer3 (agent) — NO CHANGES
|
||||
### cameleer (agent) — NO CHANGES
|
||||
|
||||
The agent's authentication flow is correct as designed:
|
||||
- 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.
|
||||
|
||||
### 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.
|
||||
|
||||
#### Change 1: Add `spring-boot-starter-oauth2-resource-server` dependency
|
||||
|
||||
**File:** `cameleer3-server-app/pom.xml`
|
||||
**File:** `cameleer-server-app/pom.xml`
|
||||
|
||||
Add:
|
||||
```xml
|
||||
@@ -124,7 +124,7 @@ Add:
|
||||
|
||||
#### 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:`:
|
||||
```yaml
|
||||
@@ -360,7 +360,7 @@ cameleer:
|
||||
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`
|
||||
|
||||
@@ -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:
|
||||
|
||||
```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_AUDIENCE=https://api.cameleer.local
|
||||
```
|
||||
@@ -727,7 +727,7 @@ This is a new development — no production data exists. All database schemas, m
|
||||
|
||||
### 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.
|
||||
3. **Phase 3**: Update bootstrap script (set OIDC env vars on server, stop reading Logto DB directly).
|
||||
|
||||
|
||||
@@ -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/
|
||||
```
|
||||
@@ -13,7 +13,7 @@ Path-based routing on one domain. SaaS app at `/platform`, server-ui at `/server
|
||||
| Path | Target | Priority | Notes |
|
||||
|------|--------|----------|-------|
|
||||
| `/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` |
|
||||
| `/*` | 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
|
||||
- Redirect URIs: `${PROTO}://${HOST}/platform/callback`
|
||||
|
||||
## Server Integration (cameleer3-server)
|
||||
## Server Integration (cameleer-server)
|
||||
|
||||
| Env var | Value | Purpose |
|
||||
|---------|-------|---------|
|
||||
@@ -64,12 +64,12 @@ Server OIDC requirements:
|
||||
- `X-Forwarded-Prefix` support for correct redirect_uri construction
|
||||
- Branding endpoint (`/api/v1/branding/logo`) must be publicly accessible
|
||||
|
||||
## Server UI (cameleer3-server-ui)
|
||||
## Server UI (cameleer-server-ui)
|
||||
|
||||
| Env var | Value | Purpose |
|
||||
|---------|-------|---------|
|
||||
| `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`.
|
||||
|
||||
@@ -80,7 +80,7 @@ Traefik strip-prefix removes `/server` before forwarding to nginx. Server-ui inj
|
||||
SPA_REDIRECT_URIS=["${PROTO}://${HOST}/platform/callback"]
|
||||
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_POST_LOGOUT_URIS=["${PROTO}://${HOST}","${PROTO}://${HOST}/server"]
|
||||
```
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
@@ -54,9 +54,9 @@ The interaction cookie is set by `/oidc/auth` before the user lands on the sign-
|
||||
|
||||
## Visual Design
|
||||
|
||||
Matches cameleer3-server LoginPage exactly:
|
||||
Matches cameleer-server LoginPage exactly:
|
||||
- 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)
|
||||
- `FormField` + `Input` for username and password
|
||||
- Amber `Button` (primary variant, full-width)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Date:** 2026-04-07
|
||||
**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
|
||||
**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 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.
|
||||
|
||||
@@ -29,17 +29,17 @@ The SaaS layer (cameleer-saas) has taken on too many responsibilities: environme
|
||||
|
||||
## What's Working Well
|
||||
|
||||
### Agent (cameleer3)
|
||||
- Clean separation: core logic in `cameleer3-core`, protocol models in `cameleer3-common`, delivery mechanisms (agent/extension) as thin wrappers
|
||||
### Agent (cameleer)
|
||||
- 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
|
||||
- Dual-mode design (Java agent + Quarkus extension) is elegant
|
||||
- Compatibility matrix across 40 Camel versions demonstrates maturity
|
||||
- No changes needed
|
||||
|
||||
### Server (cameleer3-server)
|
||||
### Server (cameleer-server)
|
||||
- Two-database pattern (PostgreSQL control plane, ClickHouse observability data) is correct
|
||||
- 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
|
||||
- The UI is competitive-grade (per UX audit #100)
|
||||
- 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
|
||||
|
||||
### 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#101 (onboarding experience):** Server UI needs guided setup for standalone users
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ This spec redesigns the platform around two personas — **vendor** (us) and **c
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
@@ -135,7 +135,7 @@ public class TenantProvisionerAutoConfig {
|
||||
|
||||
| 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 |
|
||||
| Network | `cameleer` + `cameleer-traefik` | Fixed networks from compose |
|
||||
| DNS alias | `cameleer-server-${tenant.slug}` | For SaaS→server M2M calls |
|
||||
@@ -146,7 +146,7 @@ public class TenantProvisionerAutoConfig {
|
||||
|
||||
| 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_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 |
|
||||
@@ -168,12 +168,12 @@ traefik.http.services.server-${slug}.loadbalancer.server.port=8081
|
||||
|
||||
**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 |
|
||||
|--------|-------|
|
||||
| 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}` |
|
||||
| Traefik | `PathPrefix(/t/${slug})` with `priority=2` (higher than API) |
|
||||
|
||||
@@ -467,11 +467,11 @@ New migration `V011`:
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -376,9 +376,9 @@ Import from `lucide-react`.
|
||||
|
||||
### 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`
|
||||
|
||||
|
||||
52
docs/superpowers/specs/2026-04-10-fleet-health-design.md
Normal file
52
docs/superpowers/specs/2026-04-10-fleet-health-design.md
Normal 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 |
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -13,7 +13,7 @@ This spec adds per-tenant PostgreSQL users and schemas so each tenant server can
|
||||
|
||||
### 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`.
|
||||
- `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.
|
||||
@@ -40,7 +40,7 @@ public class TenantDatabaseService {
|
||||
|
||||
### `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).
|
||||
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):
|
||||
|
||||
```
|
||||
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_PASSWORD=<generated>
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user