# 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"
```