- Remove docker-java-core and docker-java-transport-zerodep from pom.xml
- Remove Docker socket mount, group_add, jardata volume from docker-compose.yml
- Remove CAMELEER_DOCKER_NETWORK and CLICKHOUSE_URL env vars from SaaS service
- Remove jardata volume definition
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>
Docker Compose prefixes network names with the project name, so the
actual network is cameleer-saas_cameleer, not just cameleer. Pass
CAMELEER_DOCKER_NETWORK env var using COMPOSE_PROJECT_NAME.
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>
Traefik couldn't auto-link the logto router when two services
(logto, logto-console) exist on the same container. This broke
ALL default tenant routing (sign-in, OIDC, API).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The admin console (port 3002) calls the Management API on the
default tenant (port 443). Add Traefik CORS headers to allow
cross-origin requests from the admin console origin.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ADMIN_ENDPOINT set to HTTPS so OIDC issuer matches browser URL.
NODE_TLS_REJECT_UNAUTHORIZED=0 lets Logto's internal ky-based
OIDC self-discovery accept the self-signed cert through Traefik.
Remove in production with real certs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Admin console HTTPS via Traefik conflicts with Logto's
ADMIN_ENDPOINT self-discovery. Parking this for now.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ADMIN_ENDPOINT=http://localhost:3002 for Logto self-calls.
TRUST_PROXY_HEADER makes Logto use X-Forwarded-Proto from Traefik
to generate HTTPS URLs for browser-facing OIDC flows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add PUBLIC_HOST as network alias on the logto container so its
internal ADMIN_ENDPOINT calls (http://PUBLIC_HOST:3002) resolve
inside Docker directly, bypassing Traefik. Browser traffic goes
through Traefik on host port 3002 with TLS termination.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove tls=true from the logto-console router so the entrypoint
accepts plain HTTP. Logto's internal self-calls via ADMIN_ENDPOINT
use HTTP and pass through Traefik transparently. Browsers can
access via HTTP on port 3002.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use separate port 3443 for TLS-terminated admin console access.
Port 3002 stays directly mapped from logto in dev for Logto's
internal OIDC self-discovery via ADMIN_ENDPOINT.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Traefik-only change: new entrypoint + router for TLS termination.
No changes to Logto ADMIN_ENDPOINT or bootstrap script.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert all Traefik port 3002 and ADMIN_ENDPOINT changes that broke
bootstrap. Admin console HTTPS access needs a different approach.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto calls ADMIN_ENDPOINT internally for OIDC discovery. Using
PUBLIC_HOST resolved to the host machine where Traefik now owns
port 3002, causing a routing loop. localhost resolves inside the
container directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add admin-console entrypoint to Traefik with TLS termination.
Route port 3002 through Traefik to logto:3002. Update Logto
ADMIN_ENDPOINT to use HTTPS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add "Build and push Logto image" step to docker job
- Remove build: directive from logto service in docker-compose
- docker-compose now only pulls pre-built images, no local builds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add docker/logto.Dockerfile: builds custom Logto image with sign-in
UI baked into /etc/logto/packages/experience/dist/
- Remove sign-in-ui init container, signinui volume, CUSTOM_UI_PATH
(CUSTOM_UI_PATH is Logto Cloud only, not available in OSS)
- Remove sign-in build stage from SaaS Dockerfile (now in logto.Dockerfile)
- Remove docker/saas-entrypoint.sh (no longer needed)
- LoginPage auto-redirects to Logto OIDC on mount instead of showing
"Sign in with Logto" button — seamless sign-in experience
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>
Replace Logto's default sign-in page with a custom React SPA that
matches the cameleer3-server login page using @cameleer/design-system.
- New Vite+React app at ui/sign-in/ with Experience API integration
- 4-step auth flow: init → verify password → identify → submit
- Design-system components: Card, Input, Button, FormField, Alert
- Same witty random subtitles as cameleer3-server LoginPage
- Dockerfile: add sign-in-frontend build stage, copy dist to image
- docker-compose: CUSTOM_UI_PATH on Logto, shared signinui volume
- SaaS entrypoint copies sign-in dist to shared volume on startup
- Add .gitattributes for LF line endings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Browser sends Origin header on fetch calls even same-origin. Server
needs the public host in its CORS allowlist. Derived from PUBLIC_HOST.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Self-signed certs cause PKIX errors when the server fetches OIDC
discovery. CAMELEER_OIDC_TLS_SKIP_VERIFY=true disables cert
verification for OIDC calls only (server-team feature, pending build).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nginx needs to see /assets/... not /server/assets/... to find the files.
Strip-prefix + BASE_PATH=/server now works correctly with the fixed image.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server team fixed the BASE_PATH sed ordering bug. Remove our entrypoint
override and let the image's own entrypoint handle it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The server-ui's entrypoint inserts <base href> THEN rewrites all
href="/" — including the just-inserted base tag, causing doubling.
Patched entrypoint rewrites asset paths first, then inserts <base>.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server-ui needs BASE_PATH for React Router basename. Without strip-prefix,
no X-Forwarded-Prefix doubling. Server-ui handles full /server/ path itself.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server-ui injects BASE_PATH=/server/ into <base href>. Without strip-prefix,
Traefik forwards /server/ path AND server-ui adds /server/ again = double prefix.
Strip /server before forwarding so server-ui sees / and produces correct <base href="/server/">.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server-side path rewrite breaks React Router (browser URL stays at /
but basename is /platform). The SPA entry point is /platform/ — users
bookmark that. Root / goes to Logto catch-all which is correct for
direct OIDC flows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Users hitting the root URL now get redirected to the SaaS app instead
of seeing Logto's unknown-session page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto's sign-in form calls /api/interaction/* and /api/experience/* to
submit credentials, but these were routed to cameleer-saas by the /api
catch-all. Added explicit Logto API paths with higher Traefik priority.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto uses root-level paths for its sign-in UI (/sign-in, /register,
/consent, /single-sign-on, /social, /unknown-session) that were falling
through to the SPA catch-all and getting 401.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server-ui now handles base path natively via BASE_PATH env var.
Traefik forwards full path without stripping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move SPA assets from /assets/ to /_app/ (Vite assetsDir config) so
Traefik can route /assets/* to Logto without conflict. All services
on one hostname with path-based routing:
- /oidc/*, /interaction/*, /assets/* → Logto
- /server/* → server-ui (prefix stripped)
- /api/* → cameleer-saas
- /* (catch-all) → cameleer-saas SPA
Customer needs only 1 DNS record. Server gets OIDC_JWK_SET_URI for
Docker-internal JWK fetch (standard Spring split config).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Standard OIDC architecture: subdomain routing (auth.HOST, server.HOST),
TLS via Traefik, self-signed cert auto-generated on first boot.
- Add traefik-certs init container (generates wildcard self-signed cert)
- Enable TLS on all Traefik routers (websecure entrypoint)
- HTTP→HTTPS redirect in traefik.yml
- Host-based routing for all services (no more path conflicts)
- PUBLIC_PROTOCOL env var (https default, configurable)
- Protocol-aware redirect URIs in bootstrap
- Protocol-aware UI fallbacks
Customer bootstrap: set PUBLIC_HOST + DNS records + docker compose up.
For production TLS, configure Traefik ACME (Let's Encrypt).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server UI assets also use absolute /assets/* paths that conflict with
the SPA catch-all. Same fix as Logto: Host-based routing at
server.localhost gives it its own namespace.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto's login page references /assets/* which conflicts with the SPA's
assets at the same path. Using Host-based routing (auth.localhost) gives
Logto its own namespace - all paths on that subdomain go to Logto,
eliminating the conflict. *.localhost resolves to 127.0.0.1 and is a
secure context.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto ENDPOINT now points at Traefik (http://localhost) instead of
directly at port 3001. All services share the same base URL, eliminating
OIDC issuer mismatches and crypto.subtle secure context issues.
- Remove :3001 from all public-facing Logto URLs
- Add cameleer3-server-ui to Traefik at /server/ with prefix strip
- Dashboard link uses /server/ path instead of port 8082
- Bootstrap Host headers match Logto ENDPOINT (no port)
- Redirect URIs simplified (Traefik handles port 80)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crypto.subtle requires a secure context, so the browser must access
everything via localhost. The custom JwtDecoder already supports this
split: jwk-set-uri uses Docker-internal logto:3001 for network fetch,
while issuer-uri uses localhost:3001 for string-only claim validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All public-facing URLs (Logto OIDC, redirect URIs, dashboard links) now
derive from PUBLIC_HOST in .env instead of scattered localhost references.
Resolves Docker networking ambiguity where localhost inside containers
doesn't reach the host machine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cameleer3-server deploys backend and UI as separate containers.
Add the cameleer3-server-ui image (nginx SPA + API reverse proxy)
to the Compose stack, exposed on port 8082 in dev. Update sidebar
"View Dashboard" link to point to the UI container.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>