- V010: drop deployments, apps, environments, api_keys tables
- Tables have been migrated to cameleer3-server
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete environment/, app/, deployment/, runtime/ packages (source + tests)
- Delete apikey/ package (tied to environments, table will be dropped)
- Strip AsyncConfig to empty @EnableAsync (no more deploymentExecutor bean)
- Remove EnvironmentService dependency from TenantService
- Remove environment/app isolation from TenantIsolationInterceptor
- Remove environment seeding from BootstrapDataSeeder
- Refactor ServerApiClient to use LogtoConfig instead of RuntimeConfig
- Add server-endpoint property to LogtoConfig (was in RuntimeConfig)
- Remove runtime config section and multipart config from application.yml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ServerApiClient: use X-Cameleer-Protocol-Version: 1 (server expects "1", not "2")
- Disable Hibernate show-sql in dev profile (too verbose)
- CLAUDE.md: document deployment pipeline architecture, M2M server role in bootstrap,
runtime-base image in CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The default cameleer-runtime-base:latest has no registry prefix, so
Docker can't pull it. Use the full gitea.siegeln.net/cameleer/ path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The @Bean named 'deploymentExecutor' (ThreadPoolTaskExecutor) collided
with the @Service class DeploymentExecutor. Rename the bean to
'deploymentTaskExecutor'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The serverApiClient.isAvailable() stubbing in setUp() is unused by
the observability test, causing UnnecessaryStubbingException in CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Docker socket security: remove root group from Dockerfile, use
group_add in docker-compose.yml for runtime-only socket access
2. M2M server communication: create ServerApiClient using Logto
client_credentials grant with API resource scope. Add M2M server
role in bootstrap. Replace hacky admin/admin login in
AgentStatusService.
3. Async deployment: extract DeploymentExecutor as separate @Service
so Spring's @Async proxy works (self-invocation bypasses proxy).
Deploy now returns immediately, health check runs in background.
4. Bootstrap: M2M server role (cameleer-m2m-server) with server:admin
scope, idempotent creation outside the M2M app creation block.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for the deployment pipeline:
1. Health check path: /health -> /cameleer/health (matches agent)
2. Container cleanup: stop AND remove old container before starting
new one, plus orphan cleanup by container name to prevent conflicts
3. Container status: read health.status instead of state.status so
waitForHealthy correctly detects the "healthy" state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The httpclient5 transport needs junixsocket for Unix domain sockets.
Switch to docker-java-transport-zerodep which has built-in Unix socket
support with zero external dependencies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default docker-java config resolved to localhost:2375 (TCP) inside the
container. Explicitly set docker host to unix:///var/run/docker.sock
which is volume-mounted from the host.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add CI step to build cameleer-runtime-base image by downloading the
agent shaded JAR from Gitea Maven registry and pushing the image.
Wire CAMELEER_AUTH_TOKEN from docker-compose into RuntimeConfig so
deployed containers authenticate with cameleer3-server. Add agent.jar
to gitignore for local builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spring Boot defaults to 1MB max file size which rejected all JAR
uploads. Set to 200MB to match the configured max-jar-size. Also
create /data/jars with cameleer user ownership in the Dockerfile
so the non-root process can write uploaded JARs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-app memory limit and CPU shares (stored on AppEntity, used by
DeploymentService with fallback to global defaults). JAR upload is now
optional at creation time. Both create modals show the computed slug in
the dialog title and use consistent Cancel-left/Action-right button
layout with inline styles to avoid Modal CSS conflicts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sidebar, sign-in page, and favicons all use the single SVG
- Postinstall copies SVG for SaaS HTML favicon (gitignored)
- Sign-in favicon committed (baked into Logto Docker image)
- Remove old PNG favicon references
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sidebar and sign-in logos use Vite import from @cameleer/design-system
- HTML favicons copied by postinstall script (gitignored)
- Remove manually copied PNGs from repo
- Clean up SecurityConfig permitAll (bundled assets under /_app/**)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Browser img tags don't send Bearer tokens, so the sidebar logo
needs to be in the permitAll list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace favicon SVG with official camel-logo.svg from design-system
- Add PNG favicons (32px, 192px) with proper link tags in index.html
- Replace sidebar logo with 48px brand icon (cameleer-logo-48.png)
- Replace sign-in page logo with 48px brand icon
- Permit favicon PNGs in SecurityConfig
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CUSTOM_UI_PATH is a Logto Cloud feature, not available in OSS.
The correct approach for self-hosted Logto is to volume-mount
over /etc/logto/packages/experience/dist/.
- Use init container (sign-in-ui) to copy dist to shared volume
as root (fixes permission denied with cameleer user)
- Logto mounts signinui volume at experience/dist path
- Logto depends on sign-in-ui init container completion
- Remove saas-entrypoint.sh approach (no longer needed)
- Revert Dockerfile entrypoint to direct java -jar
- Permit /favicon.svg in SecurityConfig for sign-in page logo
Tested: full OIDC flow works end-to-end via Playwright.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add server:admin/operator/viewer scopes to bootstrap and org roles
- Grant SaaS admin Logto console access via admin:admin role
- Configure sign-in experience with Cameleer branding (colors + logos)
- Add rolesClaim and audience to server OIDC config
- Add server scopes to PublicConfigController for token inclusion
- Permit logo SVGs in SecurityConfig (fix 401 on /platform/logo.svg)
- Add cameleer3 logo SVGs (light + dark) to ui/public/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SPA assets moved from /assets/ to /_app/ for single-domain routing,
but SecurityConfig still permitted the old path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConnectivityHealthCheck: /actuator/health → /api/v1/health (actuator
now requires auth on cameleer3-server after OIDC was added).
Bootstrap: POST → PUT for /api/v1/admin/oidc (server expects PUT,
POST returned 405 causing OIDC config to silently fail).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace TenantResolutionFilter + TenantOwnershipValidator (15 manual
calls across 5 controllers) with a single TenantIsolationInterceptor
that uses Spring HandlerMapping path variables for fail-closed tenant
isolation. New endpoints with {tenantId}, {environmentId}, or {appId}
path variables are automatically isolated without manual code.
Simplify OrgResolver from dual-token fetch to single token — Logto
merges all scopes into either token type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add @PreAuthorize annotations to all API controllers (14 endpoints
across 6 controllers) enforcing OAuth2 scopes: apps:manage, apps:deploy,
billing:manage, observe:read, platform:admin.
Enforce tenant isolation: TenantResolutionFilter now rejects cross-tenant
access on /api/tenants/{id}/* paths. New TenantOwnershipValidator checks
environment/app ownership for paths without tenantId. Platform admins
bypass both layers.
Fix frontend: OrgResolver split into two useEffect hooks so scopes
refresh on org switch. Scopes now served from /api/config (single source
of truth). Bootstrap cleaned — standalone org permissions removed.
Update docs/architecture.md, docs/user-manual.md, and CLAUDE.md to
reflect all auth hardening changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add @Primary + @ConditionalOnMissingBean so TestSecurityConfig.jwtDecoder()
wins over SecurityConfig.jwtDecoder() without needing a real OIDC endpoint
- Add spring.main.allow-bean-definition-overriding=true and
cameleer.clickhouse.enabled=false to src/test/resources/application-test.yml
so Testcontainers @ServiceConnection can supply the datasource
- Disable ClickHouse in test profile (src/main/resources/application-test.yml)
so the explicit ClickHouseConfig DataSource bean is not created, allowing
@ServiceConnection to wire the Testcontainers Postgres datasource
- Fix TenantControllerTest and LicenseControllerTest to explicitly grant
ROLE_platform-admin authority via .authorities() on the test JWT, since
spring-security-test does not run the custom JwtAuthenticationConverter
- Fix EnvironmentService.createDefaultForTenant() to use an internal
bootstrap path that skips license enforcement (chicken-and-egg: no license
exists at tenant creation time yet)
- Remove now-unnecessary license stub from EnvironmentServiceTest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove bootstrapToken field/getter/setter from EnvironmentEntity and drop
the RuntimeConfig dependency from EnvironmentService. DeploymentService and
AgentStatusService now use a TODO-api-key placeholder until the ApiKeyService
wiring is complete. All test references to setBootstrapToken removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove LogtoManagementClient dependency from TenantController; gate
listAll and create with @PreAuthorize("hasRole('platform-admin')"),
relying on the JWT roles claim already mapped by JwtAuthenticationConverter.
Update TenantControllerTest to supply the platform-admin role via jwt()
on all POST requests that expect 201/409.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop JwtConfig dependency from LicenseService; generate license tokens as
random UUIDs instead. Add findByToken to LicenseRepository and update
verifyLicenseToken to do a DB lookup. Update LicenseServiceTest to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bootstrap dynamically creates the M2M app and writes credentials
to the JSON file. LogtoConfig now falls back to the bootstrap file
when LOGTO_M2M_CLIENT_ID/SECRET env vars are not set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto issues access tokens with typ "at+jwt" (RFC 9068) but Spring
Security's default NimbusJwtDecoder only allows "JWT". Custom decoder
accepts any type. Also removed hard 401 redirect from API client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bootstrap script runs before cameleer-saas (Flyway), so tenant
tables don't exist yet. Moved DB seeding to BootstrapDataSeeder
ApplicationRunner which runs after Flyway migrations complete.
Reads bootstrap JSON and creates tenant/environment/license if missing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bootstrap script now creates:
- SaaS Owner (admin/admin) with platform-admin role
- Tenant Admin (camel/camel) in Example Tenant org
- Traditional Web App for cameleer3-server OIDC
- DB records: tenant, default environment, license
- Configures cameleer3-server OIDC via its admin API
All credentials configurable via env vars.
Backend:
- Fix LogtoManagementClient resource URL (https://default.logto.app/api)
- Add getUserRoles/getUserOrganizations to LogtoManagementClient
- Add GET /api/me endpoint (user info, platform admin status, tenants)
- Add GET /api/tenants list-all for platform admins
- Remove insecure X-header forwarding from Traefik
Frontend:
- Org-scoped tokens: getAccessToken(resource, orgId) for tenant context
- OrgResolver component populates org store from /api/me
- useOrganization Zustand store (currentOrgId + currentTenantId)
- Platform admin sidebar section + AdminTenantsPage
- View Dashboard link points to cameleer3-server on port 8081
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto returns opaque access tokens when no resource is specified.
Added API resource creation to bootstrap, included resource indicator
in /api/config, and SPA now passes resource parameter in auth request.
Also fixed issuer-uri to match Logto's public endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Separate LOGTO_PUBLIC_ENDPOINT (browser-facing, defaults to
http://localhost:3001) from LOGTO_ENDPOINT (Docker-internal).
Also fix bootstrap M2M verification by using correct Host header
for default tenant token endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- logto-bootstrap.sh: API-driven init script that creates SPA app,
M2M app, and default user (camel/camel) via Logto Management API.
Reads m-default secret from DB, then removes seeded apps with
known secrets (security hardening). Idempotent.
- PublicConfigController: /api/config public endpoint serves Logto
client ID from bootstrap output file (runtime, not build-time)
- Frontend: LoginPage + CallbackPage fetch config from /api/config
instead of import.meta.env (fixes Vite build-time baking issue)
- Docker Compose: logto-bootstrap init service with health-gated
dependency chain, shared volume for bootstrap config
- SecurityConfig: permit /api/config without auth
Flow: docker compose up → bootstrap creates apps/user → SPA fetches
config → login page shows → sign in with Logto → camel/camel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SPA (index.html, /login, /callback, /assets/*) must be accessible
without authentication. API routes remain protected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Explicit spring.flyway.url/user/password used SPRING_DATASOURCE_URL env
var but Flyway resolves its own defaults independently, falling back to
localhost when the env var mapping doesn't match. Removing the explicit
Flyway connection config lets it inherit from the datasource, which is
correctly configured.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add ClickHouseProperties with @ConfigurationProperties
- @ConditionalOnProperty to toggle ClickHouse
- @Primary DataSource + JdbcTemplate for PostgreSQL (prevents Spring
Boot from routing JPA/Flyway to ClickHouse)
- HikariDataSource for ClickHouse with explicit credentials
- Remove separate DataSourceConfig.java (merged into ClickHouseConfig)
- Remove database-platform override (no longer needed with @Primary)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
With two DataSource beans (PostgreSQL + ClickHouse), Flyway was picking
up the ClickHouse DataSource and failing with auth errors. Explicitly
configure Flyway's url/user/password to target PostgreSQL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without an explicit spring.datasource.url, Spring Boot falls back to
jdbc:postgresql://localhost:5432 when the SPRING_DATASOURCE_URL env var
is missing or not picked up. Default now points to the docker-compose
service name (postgres:5432/cameleer_saas).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>