# Plan 4: SaaS Cleanup — Strip to Vendor Management Plane > **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:** Remove all migrated code from the SaaS layer (environments, apps, deployments, ClickHouse access) and strip it down to a thin vendor management plane: tenant lifecycle, license generation, billing, and Logto organization management. **Architecture:** The SaaS retains only vendor-level concerns. All runtime management, observability, and user management is now in the server. The SaaS communicates with server instances exclusively via REST API (ServerApiClient). ClickHouse dependency is removed entirely. **Tech Stack:** Java 21, Spring Boot 3.4.3, PostgreSQL 16 **Repo:** `C:\Users\Hendrik\Documents\projects\cameleer-saas` **Prerequisite:** Plans 1-3 must be implemented in cameleer-server first. --- ## Summary of Changes ### Files to DELETE (migrated to server or no longer needed) ``` src/main/java/net/siegeln/cameleer/saas/environment/ ├── EnvironmentEntity.java ├── EnvironmentService.java ├── EnvironmentController.java ├── EnvironmentRepository.java ├── EnvironmentStatus.java └── dto/ ├── CreateEnvironmentRequest.java ├── UpdateEnvironmentRequest.java └── EnvironmentResponse.java src/main/java/net/siegeln/cameleer/saas/app/ ├── AppEntity.java ├── AppService.java ├── AppController.java ├── AppRepository.java └── dto/ ├── CreateAppRequest.java └── AppResponse.java src/main/java/net/siegeln/cameleer/saas/deployment/ ├── DeploymentEntity.java ├── DeploymentService.java ├── DeploymentController.java ├── DeploymentRepository.java ├── DeploymentExecutor.java ├── DesiredStatus.java ├── ObservedStatus.java └── dto/ └── DeploymentResponse.java src/main/java/net/siegeln/cameleer/saas/runtime/ ├── RuntimeOrchestrator.java ├── DockerRuntimeOrchestrator.java ├── RuntimeConfig.java ├── BuildImageRequest.java ├── StartContainerRequest.java ├── ContainerStatus.java └── LogConsumer.java src/main/java/net/siegeln/cameleer/saas/log/ ├── ClickHouseConfig.java ├── ClickHouseProperties.java ├── ContainerLogService.java ├── LogController.java └── dto/ └── LogEntry.java src/main/java/net/siegeln/cameleer/saas/observability/ ├── AgentStatusService.java ├── AgentStatusController.java └── dto/ ├── AgentStatusResponse.java └── ObservabilityStatusResponse.java ``` ### Files to MODIFY ``` src/main/java/net/siegeln/cameleer/saas/config/AsyncConfig.java — remove deploymentExecutor bean src/main/java/net/siegeln/cameleer/saas/tenant/TenantService.java — remove createDefaultForTenant() call src/main/resources/application.yml — remove clickhouse + runtime config sections docker-compose.yml — remove Docker socket mount from SaaS, update routing ``` ### Files to KEEP (vendor management plane) ``` src/main/java/net/siegeln/cameleer/saas/tenant/ — Tenant CRUD, lifecycle src/main/java/net/siegeln/cameleer/saas/license/ — License generation src/main/java/net/siegeln/cameleer/saas/identity/ — Logto org management, ServerApiClient src/main/java/net/siegeln/cameleer/saas/config/ — SecurityConfig, SpaController src/main/java/net/siegeln/cameleer/saas/audit/ — Vendor audit logging src/main/java/net/siegeln/cameleer/saas/apikey/ — API key management (if used) ui/ — Vendor management dashboard ``` ### Flyway Migrations to KEEP The existing migrations (V001-V009) can remain since they're already applied. Add a new cleanup migration: ``` src/main/resources/db/migration/V010__drop_migrated_tables.sql ``` --- ### Task 1: Remove ClickHouse Dependency - [ ] **Step 1: Delete ClickHouse files** ```bash rm -rf src/main/java/net/siegeln/cameleer/saas/log/ClickHouseConfig.java rm -rf src/main/java/net/siegeln/cameleer/saas/log/ClickHouseProperties.java rm -rf src/main/java/net/siegeln/cameleer/saas/log/ContainerLogService.java rm -rf src/main/java/net/siegeln/cameleer/saas/log/LogController.java rm -rf src/main/java/net/siegeln/cameleer/saas/log/dto/ ``` - [ ] **Step 2: Remove ClickHouse from AgentStatusService** Delete `AgentStatusService.java` and `AgentStatusController.java` entirely (agent status is now a server concern). ```bash rm -rf src/main/java/net/siegeln/cameleer/saas/observability/ ``` - [ ] **Step 3: Remove ClickHouse config from application.yml** Remove the entire `cameleer.clickhouse:` section. - [ ] **Step 4: Remove ClickHouse JDBC dependency from pom.xml** Remove: ```xml com.clickhouse clickhouse-jdbc ``` - [ ] **Step 5: Verify build** Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-saas && mvn compile` Expected: BUILD SUCCESS. Fix any remaining import errors. - [ ] **Step 6: Commit** ```bash git add -A git commit -m "feat: remove all ClickHouse dependencies from SaaS layer" ``` --- ### Task 2: Remove Environment/App/Deployment Code - [ ] **Step 1: Delete environment package** ```bash rm -rf src/main/java/net/siegeln/cameleer/saas/environment/ ``` - [ ] **Step 2: Delete app package** ```bash rm -rf src/main/java/net/siegeln/cameleer/saas/app/ ``` - [ ] **Step 3: Delete deployment package** ```bash rm -rf src/main/java/net/siegeln/cameleer/saas/deployment/ ``` - [ ] **Step 4: Delete runtime package** ```bash rm -rf src/main/java/net/siegeln/cameleer/saas/runtime/ ``` - [ ] **Step 5: Remove AsyncConfig deploymentExecutor bean** In `AsyncConfig.java`, remove the `deploymentExecutor` bean (or delete AsyncConfig if it only had that bean). - [ ] **Step 6: Update TenantService** Remove any calls to `EnvironmentService.createDefaultForTenant()` from `TenantService.java`. The server now handles default environment creation. - [ ] **Step 7: Remove runtime config from application.yml** Remove the entire `cameleer.runtime:` section. - [ ] **Step 8: Verify build** Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-saas && mvn compile` Expected: BUILD SUCCESS. Fix any remaining import errors. - [ ] **Step 9: Commit** ```bash git add -A git commit -m "feat: remove migrated environment/app/deployment/runtime code from SaaS" ``` --- ### Task 3: Database Cleanup Migration - [ ] **Step 1: Create cleanup migration** ```sql -- V010__drop_migrated_tables.sql -- Drop tables that have been migrated to cameleer-server DROP TABLE IF EXISTS deployments CASCADE; DROP TABLE IF EXISTS apps CASCADE; DROP TABLE IF EXISTS environments CASCADE; DROP TABLE IF EXISTS api_keys CASCADE; ``` - [ ] **Step 2: Commit** ```bash git add src/main/resources/db/migration/V010__drop_migrated_tables.sql git commit -m "feat: drop migrated tables from SaaS database" ``` --- ### Task 4: Remove Docker Socket Dependency - [ ] **Step 1: Update docker-compose.yml** Remove from `cameleer-saas` service: ```yaml volumes: - /var/run/docker.sock:/var/run/docker.sock - jardata:/data/jars group_add: - "0" ``` The Docker socket mount now belongs to the `cameleer-server` service instead. - [ ] **Step 2: Remove docker-java dependency from pom.xml** Remove: ```xml com.github.docker-java docker-java-core com.github.docker-java docker-java-transport-zerodep ``` - [ ] **Step 3: Commit** ```bash git add docker-compose.yml pom.xml git commit -m "feat: remove Docker socket dependency from SaaS layer" ``` --- ### Task 5: Update SaaS UI - [ ] **Step 1: Remove environment/app/deployment pages from SaaS frontend** Remove pages that now live in the server UI: - `EnvironmentsPage` - `EnvironmentDetailPage` - `AppDetailPage` The SaaS UI retains: - `DashboardPage` — vendor overview (tenant list, status) - `AdminTenantsPage` — tenant management - `LicensePage` — license management - [ ] **Step 2: Update navigation** Remove links to environments/apps/deployments. The SaaS UI should link to the tenant's server instance for those features (e.g., "Open Dashboard" link to `https://{tenant-slug}.cameleer.example.com/server/`). - [ ] **Step 3: Commit** ```bash git add ui/ git commit -m "feat: strip SaaS UI to vendor management dashboard" ``` --- ### Task 6: Expand ServerApiClient - [ ] **Step 1: Add provisioning-related API calls** The `ServerApiClient` should gain methods for tenant provisioning: ```java public void pushLicense(String serverEndpoint, String licenseToken) { post(serverEndpoint + "/api/v1/admin/license") .body(Map.of("token", licenseToken)) .retrieve() .toBodilessEntity(); } public Map getHealth(String serverEndpoint) { return get(serverEndpoint + "/api/v1/health") .retrieve() .body(Map.class); } ``` - [ ] **Step 2: Commit** ```bash git add src/main/java/net/siegeln/cameleer/saas/identity/ServerApiClient.java git commit -m "feat: expand ServerApiClient with license push and health check methods" ``` --- ### Task 7: Write SAAS-INTEGRATION.md - [ ] **Step 1: Create integration contract document** Create `docs/SAAS-INTEGRATION.md` in the cameleer-server repo documenting: - Which server API endpoints the SaaS calls - Required auth (M2M token with `server:admin` scope) - License injection mechanism (`POST /api/v1/admin/license`) - Health check endpoint (`GET /api/v1/health`) - What the server exposes vs what the SaaS must never access directly - Env vars the SaaS sets when provisioning a server instance - [ ] **Step 2: Commit** ```bash cd /c/Users/Hendrik/Documents/projects/cameleer-server git add docs/SAAS-INTEGRATION.md git commit -m "docs: add SaaS integration contract documentation" ``` --- ### Task 8: Final Verification - [ ] **Step 1: Build SaaS** Run: `cd /c/Users/Hendrik/Documents/projects/cameleer-saas && mvn clean verify` Expected: BUILD SUCCESS with reduced dependency footprint. - [ ] **Step 2: Verify SaaS starts without ClickHouse** The SaaS should start with only PostgreSQL (and Logto). No ClickHouse required. - [ ] **Step 3: Verify remaining code footprint** The SaaS source should now contain approximately: - `tenant/` — ~4 files - `license/` — ~5 files - `identity/` — ~3 files (LogtoConfig, ServerApiClient, M2M token) - `config/` — ~3 files (SecurityConfig, SpaController, TLS) - `audit/` — ~3 files - `ui/` — stripped dashboard Total: ~20 Java files (down from ~75). - [ ] **Step 4: Final commit** ```bash git add -A git commit -m "chore: finalize SaaS cleanup — vendor management plane only" ```