The server now requires a non-empty JWT secret. The installer (bash + ps1)
generates a random value for both SaaS and standalone modes, and the compose
templates map it into the respective containers. Also fixes container names
in generated INSTALL.md docs to use the cameleer- prefix consistently.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The @Lazy self-proxy pattern requires a non-null reference in tests.
Construct the instance then re-create with itself as the self param.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing runtimeBaseImage arg to ProvisioningProperties constructor
calls in tests. Also add missing self-proxy arg to VendorTenantService
constructor (pre-existing from async provisioning commit).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CAMELEER_SERVER_RUNTIME_BASEIMAGE was never set on provisioned
per-tenant server containers, causing them to fall back to the
server's hardcoded default. Added CAMELEER_SAAS_PROVISIONING_RUNTIMEBASEIMAGE
as a configurable property that gets forwarded during provisioning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Async on provisionAsync() was bypassed because all call sites were
internal (this.provisionAsync), skipping the Spring proxy. Inject self
via @Lazy to route through the proxy so provisioning runs in a
background thread and the API returns immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Installers now use `--pull always --force-recreate` on `docker compose up`
to ensure fresh images are used on every install/reinstall, preventing
stale containers from missing schema changes like db_password.
Fix VendorTenantServiceTest to expect two repository saves in provisioning
tests (one for dbPassword, one for final status).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Constructor gained an 11th parameter (TenantDatabaseService) but the
test was not updated, breaking CI compilation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 14 incremental migrations (V001-V015) with a single V001__init.sql
representing the final schema. Tables that were created and later dropped
(environments, api_keys, apps, deployments) are excluded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TenantDataCleanupService now handles only ClickHouse GDPR erasure;
the dropPostgresSchema private method is removed and the public method
renamed cleanupClickHouse(). VendorTenantService updated accordingly
with the TODO comment removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inject TenantDatabaseService; call createTenantDatabase() at the start
of provisionAsync() (stores generated password on TenantEntity), and
dropTenantDatabase() in delete() before GDPR data erasure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same hardcoded dev credentials bug as InfrastructureService —
TenantDataCleanupService.dropPostgresSchema() used "cameleer"/"cameleer_dev"
instead of the provisioning properties, causing schema DROP to fail on
production installs during tenant deletion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pgConnection() had hardcoded dev credentials ("cameleer"/"cameleer_dev")
instead of using the provisioning properties, causing "password
authentication failed" on production installs where the password is
generated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add datasourceUsername and datasourcePassword to test constructors
to match the updated record definition.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The new ProvisioningProperties record fields need defaults in
application.yml or Spring Boot fails to bind the configuration.
Defaults to POSTGRES_USER/POSTGRES_PASSWORD env vars with
fallback to cameleer/cameleer_dev.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The DockerTenantProvisioner hardcoded SPRING_DATASOURCE_USERNAME
and SPRING_DATASOURCE_PASSWORD as "cameleer" / "cameleer_dev".
With the installer generating random passwords, tenant servers
failed to connect to PostgreSQL.
Add datasourceUsername and datasourcePassword to ProvisioningProperties,
pass them from the compose env vars, and use them in the provisioner.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouse: enable built-in Prometheus exporter at :9363/metrics via
config.d/prometheus.xml with metrics, events, and async_metrics.
Docker labels added for docker_sd_configs auto-discovery.
Tenant servers: add prometheus.scrape/path/port labels to provisioned
server containers pointing to /api/v1/prometheus:8081.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vendor has platform:admin scope globally and manages tenants through the
SaaS console — no need to be a member of each tenant's Logto org.
Removes the step that failed with Logto's varchar(21) user ID limit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ClickHouse: pass user/password via ProvisioningProperties instead of
baking into JDBC URLs. All consumers (InfrastructureService,
TenantDataCleanupService, DockerTenantProvisioner) use the same source.
- Bootstrap: remove dead tenant config (CAMELEER_AUTH_TOKEN, t-default
org, example tenant vars) — tenants are created dynamically by vendor.
- Bootstrap JSON: remove unused fields (tenantName, tenantSlug,
bootstrapToken, tenantAdminUser, organizationId).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouse default user had no password, causing auth failures on recent
CH versions. Set password via from_env in clickhouse-users.xml, pass
credentials in JDBC URLs to SaaS services and tenant server containers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace absolute UNIQUE constraint on tenants.slug with a partial unique
index that excludes DELETED rows. This allows re-creating a tenant with
the same slug after deletion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds CAMELEER_SERVER_SECURITY_INFRASTRUCTUREENDPOINTS=false to the env
var list injected into provisioned tenant server containers, disabling
the Database and ClickHouse admin endpoints (returns 404) on SaaS-
managed instances. The server defaults to true (standalone mode).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Align DockerTenantProvisioner env vars with the server's new
cameleer.server.security.oidc.* namespace:
CAMELEER_SERVER_SECURITY_OIDC_ISSUERURI
CAMELEER_SERVER_SECURITY_OIDC_JWKSETURI
CAMELEER_SERVER_SECURITY_OIDC_AUDIENCE
CAMELEER_SERVER_SECURITY_OIDC_TLSSKIPVERIFY
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move all SaaS configuration properties under the cameleer.saas.*
namespace with all-lowercase dot-separated names and mechanical env var
mapping. Aligns with the server (cameleer.server.*) and agent
(cameleer.agent.*) conventions.
Changes:
- Move cameleer.identity.* → cameleer.saas.identity.*
- Move cameleer.provisioning.* → cameleer.saas.provisioning.*
- Move cameleer.certs.* → cameleer.saas.certs.*
- Rename kebab-case properties to concatenated lowercase
- Update all env vars to CAMELEER_SAAS_* mechanical mapping
- Update DockerTenantProvisioner to pass CAMELEER_SERVER_* env vars
to provisioned server containers (matching server's new convention)
- Spring JWT config now derives from SaaS properties via cross-reference
- Clean up orphaned properties in application-local.yml
- Update docker-compose.yml, docker-compose.dev.yml, .env.example
- Update CLAUDE.md, HOWTO.md, architecture.md, user-manual.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PATCH /api/users/{id}/password, not /api/users/{id}. The general user
update endpoint rejected the password field with 422.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restart only stops/starts existing containers with the same image. The new
upgrade action removes server + UI containers, force-pulls the latest
Docker images, then re-provisions (preserving app containers, volumes, and
networks). Available to both vendor (tenant detail) and tenant admin
(dashboard).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- POST /api/tenant/server/admin-password — resets server's built-in
admin password via M2M API call to the tenant's server
- Settings page: "Server Admin Password" card
- ServerApiClient.resetServerAdminPassword() calls server's password
reset endpoint with M2M token
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- POST /api/tenant/password — change own Logto password
- POST /api/tenant/team/{userId}/password — reset team member password
- Settings page: "Change Password" card with confirm field
- Team page: "Reset Password" button per member with inline form
- LogtoManagementClient.updateUserPassword() via Logto Management API
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DockerTenantProvisioner.remove() now cleans up all tenant Docker resources:
containers (by cameleer.tenant label), env networks, tenant network, JAR volume.
TenantDataCleanupService drops the tenant's PostgreSQL schema and deletes all
ClickHouse data for GDPR compliance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Docker containers have been removed (e.g. manual cleanup or image
update), restart now falls back to full re-provisioning instead of
failing with 404. Applies to both vendor and tenant portal restart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The self-signed bootstrap cert has no CA bundle, so newly created tenants
with ca_applied_at=NULL are not actually stale. Skip the count when the
active cert has hasCa=false.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend VendorTenantSummary with agentCount, environmentCount, and
agentLimit fields. Fetch counts in parallel using CompletableFuture
per tenant, only calling server API for ACTIVE tenants with RUNNING
servers. Agent limit extracted from license limits JSONB.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dashboard was showing hardcoded zeroes for agent and environment usage.
Now fetches real counts via M2M API from the tenant's server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All private key writes now use writeAtomicRestricted which sets POSIX
owner-read/write permissions after writing. Gracefully skips on
non-POSIX filesystems (Windows dev).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Encrypted PKCS#8 private keys are decrypted during staging using the
provided password. The decrypted key is stored for Traefik (which needs
cleartext PEM). Unencrypted keys continue to work without a password.
- CertificateManager.stage() accepts optional keyPassword
- DockerCertificateManager handles EncryptedPrivateKeyInfo decryption
- UI: password field in upload form (vendor CertificatesPage)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tenants can upload multiple CA certificates for enterprise SSO providers
that use private certificate authorities.
- New tenant_ca_certs table (V013) with PEM storage in DB
- Stage/activate/delete lifecycle per CA cert
- Aggregated ca.pem rebuild on activate/delete (atomic .wip swap)
- REST API: GET/POST/DELETE on /api/tenant/ca
- UI: CA Certificates section on SSO page with upload, activate, remove
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AuditService now looks up username/name/email from Logto Management API
when actorEmail is null, with an in-memory cache to avoid repeated calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>