feat: split auth domain — Logto gets dedicated AUTH_HOST
All checks were successful
CI / build (push) Successful in 1m22s
CI / docker (push) Successful in 48s

Support separate auth domain (e.g. auth.cameleer.io) for Logto while
keeping the SaaS app on PUBLIC_HOST (e.g. app.cameleer.io). AUTH_HOST
defaults to PUBLIC_HOST for backward-compatible single-domain setups.

- Logto routing: Host(AUTH_HOST) replaces PathPrefix('/') catch-all
- Root redirect moved from traefik-dynamic.yml to Docker labels with
  Host(PUBLIC_HOST) scope so it doesn't intercept auth domain
- Self-signed cert generates SANs for both domains
- Bootstrap Host header uses AUTH_HOST for Logto endpoint validation
- Spring issuer-uri and oidcissueruri use new authhost property
- Both installers (sh + ps1) prompt for AUTH_HOST in expert mode

Local dev: AUTH_HOST=auth.localhost (resolves to 127.0.0.1, no hosts file)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-24 18:11:47 +02:00
parent 1fbafbb16d
commit dc7ac3a1ec
11 changed files with 93 additions and 44 deletions

View File

@@ -7,6 +7,9 @@ VERSION=latest
# Public access
PUBLIC_HOST=localhost
PUBLIC_PROTOCOL=https
# Auth domain (Logto). Defaults to PUBLIC_HOST for single-domain setups.
# Set to a separate subdomain (e.g. auth.cameleer.io) to split auth from the app.
# AUTH_HOST=localhost
# Ports
HTTP_PORT=80

View File

@@ -8,6 +8,7 @@ services:
- "${LOGTO_CONSOLE_PORT:-3002}:3002"
environment:
PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
AUTH_HOST: ${AUTH_HOST:-localhost}
CERT_FILE: ${CERT_FILE:-}
KEY_FILE: ${KEY_FILE:-}
CA_FILE: ${CA_FILE:-}
@@ -62,14 +63,15 @@ services:
condition: service_healthy
environment:
DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD:-cameleer_dev}@cameleer-postgres:5432/logto
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}
ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}
TRUST_PROXY_HEADER: 1
NODE_TLS_REJECT_UNAUTHORIZED: "${NODE_TLS_REJECT:-0}"
LOGTO_ENDPOINT: http://cameleer-logto:3001
LOGTO_ADMIN_ENDPOINT: http://cameleer-logto:3002
LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}
PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
AUTH_HOST: ${AUTH_HOST:-localhost}
PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https}
PG_HOST: cameleer-postgres
PG_USER: ${POSTGRES_USER:-cameleer}
@@ -85,13 +87,12 @@ services:
start_period: 30s
labels:
- traefik.enable=true
- traefik.http.routers.cameleer-logto.rule=PathPrefix(`/`)
- traefik.http.routers.cameleer-logto.priority=1
- "traefik.http.routers.cameleer-logto.rule=Host(`${AUTH_HOST:-localhost}`)"
- traefik.http.routers.cameleer-logto.entrypoints=websecure
- traefik.http.routers.cameleer-logto.tls=true
- traefik.http.routers.cameleer-logto.service=cameleer-logto
- traefik.http.routers.cameleer-logto.middlewares=cameleer-logto-cors
- "traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}"
- "traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}"
- traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS
- traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowHeaders=Authorization,Content-Type
- traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowCredentials=true
@@ -123,7 +124,8 @@ services:
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:-cameleer_dev}
# Identity (Logto)
CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: ${LOGTO_ENDPOINT:-http://cameleer-logto:3001}
CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}
CAMELEER_SAAS_IDENTITY_AUTHHOST: ${AUTH_HOST:-localhost}
CAMELEER_SAAS_IDENTITY_M2MCLIENTID: ${LOGTO_M2M_CLIENT_ID:-}
CAMELEER_SAAS_IDENTITY_M2MCLIENTSECRET: ${LOGTO_M2M_CLIENT_SECRET:-}
CAMELEER_SERVER_SECURITY_JWTSECRET: ${CAMELEER_SERVER_SECURITY_JWTSECRET:-cameleer-dev-jwt-secret}
@@ -139,6 +141,16 @@ services:
- traefik.http.routers.saas.entrypoints=websecure
- traefik.http.routers.saas.tls=true
- traefik.http.services.saas.loadbalancer.server.port=8080
# Root redirect: / → /platform/ (scoped to app host so it doesn't catch auth domain)
- "traefik.http.routers.saas-root.rule=Host(`${PUBLIC_HOST:-localhost}`) && Path(`/`)"
- traefik.http.routers.saas-root.priority=100
- traefik.http.routers.saas-root.entrypoints=websecure
- traefik.http.routers.saas-root.tls=true
- traefik.http.routers.saas-root.middlewares=root-to-platform
- traefik.http.routers.saas-root.service=saas
- "traefik.http.middlewares.root-to-platform.redirectRegex.regex=^(https?://[^/]+)/?$$"
- "traefik.http.middlewares.root-to-platform.redirectRegex.replacement=$${1}/platform/"
- traefik.http.middlewares.root-to-platform.redirectRegex.permanent=false
group_add:
- "${DOCKER_GID:-0}"
networks:

View File

@@ -28,12 +28,20 @@ if [ ! -f "$CERTS_DIR/cert.pem" ]; then
else
# Generate self-signed certificate
HOST="${PUBLIC_HOST:-localhost}"
AUTH="${AUTH_HOST:-$HOST}"
echo "[certs] Generating self-signed certificate for $HOST..."
# Build SAN list; deduplicate when AUTH_HOST equals PUBLIC_HOST
if [ "$AUTH" = "$HOST" ]; then
SAN="DNS:$HOST,DNS:*.$HOST"
else
SAN="DNS:$HOST,DNS:*.$HOST,DNS:$AUTH,DNS:*.$AUTH"
echo "[certs] (+ auth domain: $AUTH)"
fi
openssl req -x509 -newkey rsa:4096 \
-keyout "$CERTS_DIR/key.pem" -out "$CERTS_DIR/cert.pem" \
-days 365 -nodes \
-subj "/CN=$HOST" \
-addext "subjectAltName=DNS:$HOST,DNS:*.$HOST"
-addext "subjectAltName=$SAN"
SELF_SIGNED=true
echo "[certs] Generated self-signed certificate for $HOST."
fi

View File

@@ -1,21 +1,3 @@
http:
routers:
root-redirect:
rule: "Path(`/`)"
priority: 100
entryPoints:
- websecure
tls: {}
middlewares:
- root-to-platform
service: saas@docker
middlewares:
root-to-platform:
redirectRegex:
regex: "^(https?://[^/]+)/?$"
replacement: "${1}/platform/"
permanent: false
tls:
stores:
default:

View File

@@ -32,6 +32,7 @@ SAAS_ADMIN_PASS="${SAAS_ADMIN_PASS:-admin}"
# Redirect URIs (derived from PUBLIC_HOST and PUBLIC_PROTOCOL)
HOST="${PUBLIC_HOST:-localhost}"
AUTH="${AUTH_HOST:-$HOST}"
PROTO="${PUBLIC_PROTOCOL:-https}"
SPA_REDIRECT_URIS="[\"${PROTO}://${HOST}/platform/callback\"]"
SPA_POST_LOGOUT_URIS="[\"${PROTO}://${HOST}/platform/login\",\"${PROTO}://${HOST}/platform/\"]"
@@ -47,8 +48,9 @@ if [ "$BOOTSTRAP_LOCAL" = "true" ]; then
HOST_ARGS=""
ADMIN_HOST_ARGS=""
else
HOST_ARGS="-H Host:${HOST}"
ADMIN_HOST_ARGS="-H Host:${HOST}:3002 -H X-Forwarded-Proto:https"
# Logto validates Host header against its ENDPOINT, which uses AUTH_HOST
HOST_ARGS="-H Host:${AUTH}"
ADMIN_HOST_ARGS="-H Host:${AUTH}:3002 -H X-Forwarded-Proto:https"
fi
# Install jq + curl if not already available (deps are baked into cameleer-logto image)

View File

@@ -17,6 +17,7 @@ param(
[string]$Config,
[string]$InstallDir,
[string]$PublicHost,
[string]$AuthHost,
[string]$PublicProtocol,
[string]$AdminUser,
[string]$AdminPassword,
@@ -66,6 +67,7 @@ $DEFAULT_DOCKER_SOCKET = '/var/run/docker.sock'
# --- Capture env vars before any overrides ---
$_ENV_PUBLIC_HOST = $env:PUBLIC_HOST
$_ENV_AUTH_HOST = $env:AUTH_HOST
$_ENV_PUBLIC_PROTOCOL = $env:PUBLIC_PROTOCOL
$_ENV_POSTGRES_PASSWORD = $env:POSTGRES_PASSWORD
$_ENV_CLICKHOUSE_PASSWORD = $env:CLICKHOUSE_PASSWORD
@@ -88,6 +90,7 @@ $_ENV_DEPLOYMENT_MODE = $env:DEPLOYMENT_MODE
$script:cfg = @{
InstallDir = $InstallDir
PublicHost = $PublicHost
AuthHost = $AuthHost
PublicProtocol = $PublicProtocol
AdminUser = $AdminUser
AdminPass = $AdminPassword
@@ -150,6 +153,7 @@ function Show-Help {
Write-Host 'Options:'
Write-Host ' -InstallDir DIR Install directory (default: ./cameleer)'
Write-Host ' -PublicHost HOST Public hostname (default: auto-detect)'
Write-Host ' -AuthHost HOST Auth domain for Logto (default: same as PublicHost)'
Write-Host ' -AdminUser USER Admin username (default: admin)'
Write-Host ' -AdminPassword PASS Admin password (default: generated)'
Write-Host ' -TlsMode MODE self-signed or custom (default: self-signed)'
@@ -236,6 +240,7 @@ function Load-ConfigFile {
switch ($key) {
'install_dir' { if (-not $script:cfg.InstallDir) { $script:cfg.InstallDir = $val } }
'public_host' { if (-not $script:cfg.PublicHost) { $script:cfg.PublicHost = $val } }
'auth_host' { if (-not $script:cfg.AuthHost) { $script:cfg.AuthHost = $val } }
'public_protocol' { if (-not $script:cfg.PublicProtocol) { $script:cfg.PublicProtocol = $val } }
'admin_user' { if (-not $script:cfg.AdminUser) { $script:cfg.AdminUser = $val } }
'admin_password' { if (-not $script:cfg.AdminPass) { $script:cfg.AdminPass = $val } }
@@ -264,6 +269,7 @@ function Load-EnvOverrides {
$c = $script:cfg
if (-not $c.InstallDir) { $c.InstallDir = $env:CAMELEER_INSTALL_DIR }
if (-not $c.PublicHost) { $c.PublicHost = $_ENV_PUBLIC_HOST }
if (-not $c.AuthHost) { $c.AuthHost = $_ENV_AUTH_HOST }
if (-not $c.PublicProtocol) { $c.PublicProtocol = $_ENV_PUBLIC_PROTOCOL }
if (-not $c.AdminUser) { $c.AdminUser = $env:SAAS_ADMIN_USER }
if (-not $c.AdminPass) { $c.AdminPass = $env:SAAS_ADMIN_PASS }
@@ -474,6 +480,7 @@ function Run-ExpertPrompts {
if ($c.DeploymentMode -eq 'saas') {
Write-Host ''
Write-Host ' Logto:' -ForegroundColor Cyan
$c.AuthHost = Prompt-Value 'Auth domain (Logto) -- same as hostname for single-domain' (Coalesce $c.AuthHost $c.PublicHost)
if (Prompt-YesNo 'Expose Logto admin console externally?' 'y') {
$c.LogtoConsoleExposed = 'true'
} else {
@@ -507,8 +514,12 @@ function Merge-Config {
}
}
# Default AUTH_HOST to PUBLIC_HOST (single-domain setup)
if (-not $c.AuthHost) { $c.AuthHost = $c.PublicHost }
# Force lowercase -- Logto normalises internally; case mismatch breaks JWT validation
$c.PublicHost = $c.PublicHost.ToLower()
$c.AuthHost = $c.AuthHost.ToLower()
if ($c.DeploymentMode -ne 'standalone' -and (-not $c.NodeTlsReject)) {
if ($c.TlsMode -eq 'custom') { $c.NodeTlsReject = '1' } else { $c.NodeTlsReject = '0' }
@@ -636,6 +647,7 @@ POSTGRES_IMAGE=postgres:16-alpine
VERSION=$($c.Version)
PUBLIC_HOST=$($c.PublicHost)
AUTH_HOST=$($c.AuthHost)
PUBLIC_PROTOCOL=$($c.PublicProtocol)
HTTP_PORT=$($c.HttpPort)
@@ -889,6 +901,7 @@ function Write-ConfigFile {
install_dir=$($c.InstallDir)
public_host=$($c.PublicHost)
auth_host=$($c.AuthHost)
public_protocol=$($c.PublicProtocol)
admin_user=$($c.AdminUser)
tls_mode=$($c.TlsMode)
@@ -931,7 +944,7 @@ ClickHouse: default / $($c.ClickhousePassword)
"@
} else {
if ($c.LogtoConsoleExposed -eq 'true') {
$logtoLine = "Logto Console: $($c.PublicProtocol)://$($c.PublicHost):$($c.LogtoConsolePort)"
$logtoLine = "Logto Console: $($c.PublicProtocol)://$($c.AuthHost):$($c.LogtoConsolePort)"
} else {
$logtoLine = 'Logto Console: (not exposed)'
}
@@ -980,7 +993,7 @@ function Generate-InstallDoc {
if ($c.TlsMode -eq 'custom') { $tlsDesc = 'Custom certificate' } else { $tlsDesc = 'Self-signed (auto-generated)' }
if ($c.LogtoConsoleExposed -eq 'true') {
$logtoConsoleRow = "- **Logto Admin Console:** $($c.PublicProtocol)://$($c.PublicHost):$($c.LogtoConsolePort)"
$logtoConsoleRow = "- **Logto Admin Console:** $($c.PublicProtocol)://$($c.AuthHost):$($c.LogtoConsolePort)"
$logtoPortRow = "| $($c.LogtoConsolePort) | Logto Admin Console |"
} else {
$logtoConsoleRow = ''
@@ -1256,7 +1269,7 @@ function Print-Credentials {
Write-Host ''
if ($c.DeploymentMode -eq 'saas' -and $c.LogtoConsoleExposed -eq 'true') {
Write-Host ' Logto Console: ' -NoNewline
Write-Host "$($c.PublicProtocol)://$($c.PublicHost):$($c.LogtoConsolePort)" -ForegroundColor Blue
Write-Host "$($c.PublicProtocol)://$($c.AuthHost):$($c.LogtoConsolePort)" -ForegroundColor Blue
Write-Host ''
}
Write-Host " Credentials saved to: $($c.InstallDir)\credentials.txt"

View File

@@ -29,6 +29,7 @@ DEFAULT_DOCKER_SOCKET="/var/run/docker.sock"
# --- Config values (set by args/env/config/prompts) ---
# Save environment values before initialization (CLI args override these)
_ENV_PUBLIC_HOST="${PUBLIC_HOST:-}"
_ENV_AUTH_HOST="${AUTH_HOST:-}"
_ENV_PUBLIC_PROTOCOL="${PUBLIC_PROTOCOL:-}"
_ENV_POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-}"
_ENV_CLICKHOUSE_PASSWORD="${CLICKHOUSE_PASSWORD:-}"
@@ -48,6 +49,7 @@ _ENV_DEPLOYMENT_MODE="${DEPLOYMENT_MODE:-}"
INSTALL_DIR=""
PUBLIC_HOST=""
AUTH_HOST=""
PUBLIC_PROTOCOL=""
ADMIN_USER=""
ADMIN_PASS=""
@@ -148,6 +150,7 @@ parse_args() {
--config) CONFIG_FILE_PATH="$2"; shift ;;
--install-dir) INSTALL_DIR="$2"; shift ;;
--public-host) PUBLIC_HOST="$2"; shift ;;
--auth-host) AUTH_HOST="$2"; shift ;;
--public-protocol) PUBLIC_PROTOCOL="$2"; shift ;;
--admin-user) ADMIN_USER="$2"; shift ;;
--admin-password) ADMIN_PASS="$2"; shift ;;
@@ -194,6 +197,7 @@ show_help() {
echo "Options:"
echo " --install-dir DIR Install directory (default: ./cameleer)"
echo " --public-host HOST Public hostname (default: auto-detect)"
echo " --auth-host HOST Auth domain for Logto (default: same as public-host)"
echo " --admin-user USER Admin username (default: admin)"
echo " --admin-password PASS Admin password (default: generated)"
echo " --tls-mode MODE self-signed or custom (default: self-signed)"
@@ -231,6 +235,7 @@ load_config_file() {
case "$key" in
install_dir) [ -z "$INSTALL_DIR" ] && INSTALL_DIR="$value" ;;
public_host) [ -z "$PUBLIC_HOST" ] && PUBLIC_HOST="$value" ;;
auth_host) [ -z "$AUTH_HOST" ] && AUTH_HOST="$value" ;;
public_protocol) [ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$value" ;;
admin_user) [ -z "$ADMIN_USER" ] && ADMIN_USER="$value" ;;
admin_password) [ -z "$ADMIN_PASS" ] && ADMIN_PASS="$value" ;;
@@ -257,6 +262,7 @@ load_config_file() {
load_env_overrides() {
[ -z "$INSTALL_DIR" ] && INSTALL_DIR="${CAMELEER_INSTALL_DIR:-}"
[ -z "$PUBLIC_HOST" ] && PUBLIC_HOST="$_ENV_PUBLIC_HOST"
[ -z "$AUTH_HOST" ] && AUTH_HOST="$_ENV_AUTH_HOST"
[ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$_ENV_PUBLIC_PROTOCOL"
[ -z "$ADMIN_USER" ] && ADMIN_USER="${SAAS_ADMIN_USER:-}"
[ -z "$ADMIN_PASS" ] && ADMIN_PASS="${SAAS_ADMIN_PASS:-}"
@@ -463,6 +469,7 @@ run_expert_prompts() {
if [ "$DEPLOYMENT_MODE" = "saas" ]; then
echo ""
echo -e "${BOLD} Logto:${NC}"
prompt AUTH_HOST "Auth domain (Logto) — same as hostname for single-domain" "${AUTH_HOST:-$PUBLIC_HOST}"
if prompt_yesno "Expose Logto admin console externally?" "y"; then
LOGTO_CONSOLE_EXPOSED="true"
else
@@ -493,8 +500,12 @@ merge_config() {
: "${COMPOSE_PROJECT:=$DEFAULT_COMPOSE_PROJECT}"
fi
# Force lowercase hostname — Logto normalizes internally, case mismatch breaks JWT validation
# Default AUTH_HOST to PUBLIC_HOST (single-domain setup)
: "${AUTH_HOST:=$PUBLIC_HOST}"
# Force lowercase hostnames — Logto normalizes internally, case mismatch breaks JWT validation
PUBLIC_HOST=$(echo "$PUBLIC_HOST" | tr '[:upper:]' '[:lower:]')
AUTH_HOST=$(echo "$AUTH_HOST" | tr '[:upper:]' '[:lower:]')
if [ "$DEPLOYMENT_MODE" != "standalone" ]; then
if [ -z "$NODE_TLS_REJECT" ]; then
@@ -636,6 +647,7 @@ VERSION=${VERSION}
# Public access
PUBLIC_HOST=${PUBLIC_HOST}
AUTH_HOST=${AUTH_HOST}
PUBLIC_PROTOCOL=${PUBLIC_PROTOCOL}
# Ports
@@ -841,6 +853,7 @@ write_config_file() {
install_dir=${INSTALL_DIR}
public_host=${PUBLIC_HOST}
auth_host=${AUTH_HOST}
public_protocol=${PUBLIC_PROTOCOL}
admin_user=${ADMIN_USER}
tls_mode=${TLS_MODE}
@@ -902,7 +915,7 @@ ClickHouse: default / ${CLICKHOUSE_PASSWORD}
EOF
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
echo "Logto Console: ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}:${LOGTO_CONSOLE_PORT}" >> "$f"
echo "Logto Console: ${PUBLIC_PROTOCOL}://${AUTH_HOST}:${LOGTO_CONSOLE_PORT}" >> "$f"
else
echo "Logto Console: (not exposed)" >> "$f"
fi
@@ -941,7 +954,7 @@ generate_install_doc() {
EOF
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
echo "- **Logto Admin Console:** ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}:${LOGTO_CONSOLE_PORT}" >> "$f"
echo "- **Logto Admin Console:** ${PUBLIC_PROTOCOL}://${AUTH_HOST}:${LOGTO_CONSOLE_PORT}" >> "$f"
fi
cat >> "$f" << 'EOF'
@@ -1219,7 +1232,7 @@ print_credentials() {
if [ "$DEPLOYMENT_MODE" = "saas" ]; then
if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then
echo -e " Logto Console: ${BLUE}${PUBLIC_PROTOCOL}://${PUBLIC_HOST}:${LOGTO_CONSOLE_PORT}${NC}"
echo -e " Logto Console: ${BLUE}${PUBLIC_PROTOCOL}://${AUTH_HOST}:${LOGTO_CONSOLE_PORT}${NC}"
echo ""
fi
fi

View File

@@ -21,6 +21,9 @@ VERSION=latest
# ============================================================
PUBLIC_HOST=localhost
PUBLIC_PROTOCOL=https
# Auth domain (Logto). Defaults to PUBLIC_HOST for single-domain setups.
# Set to a separate subdomain (e.g. auth.cameleer.io) to split auth from the app.
# AUTH_HOST=localhost
# ============================================================
# Ports

View File

@@ -10,14 +10,15 @@ services:
condition: service_healthy
environment:
DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD}@cameleer-postgres:5432/logto
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}
ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}
TRUST_PROXY_HEADER: 1
NODE_TLS_REJECT_UNAUTHORIZED: "${NODE_TLS_REJECT:-0}"
LOGTO_ENDPOINT: http://cameleer-logto:3001
LOGTO_ADMIN_ENDPOINT: http://cameleer-logto:3002
LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}
PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
AUTH_HOST: ${AUTH_HOST:-localhost}
PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https}
PG_HOST: cameleer-postgres
PG_USER: ${POSTGRES_USER:-cameleer}
@@ -33,13 +34,12 @@ services:
start_period: 30s
labels:
- traefik.enable=true
- traefik.http.routers.cameleer-logto.rule=PathPrefix(`/`)
- traefik.http.routers.cameleer-logto.priority=1
- "traefik.http.routers.cameleer-logto.rule=Host(`${AUTH_HOST:-localhost}`)"
- traefik.http.routers.cameleer-logto.entrypoints=websecure
- traefik.http.routers.cameleer-logto.tls=true
- traefik.http.routers.cameleer-logto.service=cameleer-logto
- traefik.http.routers.cameleer-logto.middlewares=cameleer-logto-cors
- "traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}"
- "traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}"
- traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS
- traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowHeaders=Authorization,Content-Type
- traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowCredentials=true
@@ -68,7 +68,8 @@ services:
SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
# Identity (Logto)
CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: http://cameleer-logto:3001
CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}
CAMELEER_SAAS_IDENTITY_AUTHHOST: ${AUTH_HOST:-localhost}
# Provisioning — passed to per-tenant server containers
CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost}
CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https}
@@ -87,6 +88,16 @@ services:
- traefik.http.routers.saas.entrypoints=websecure
- traefik.http.routers.saas.tls=true
- traefik.http.services.saas.loadbalancer.server.port=8080
# Root redirect: / → /platform/ (scoped to app host so it doesn't catch auth domain)
- "traefik.http.routers.saas-root.rule=Host(`${PUBLIC_HOST:-localhost}`) && Path(`/`)"
- traefik.http.routers.saas-root.priority=100
- traefik.http.routers.saas-root.entrypoints=websecure
- traefik.http.routers.saas-root.tls=true
- traefik.http.routers.saas-root.middlewares=root-to-platform
- traefik.http.routers.saas-root.service=saas
- "traefik.http.middlewares.root-to-platform.redirectRegex.regex=^(https?://[^/]+)/?$$"
- "traefik.http.middlewares.root-to-platform.redirectRegex.replacement=$${1}/platform/"
- traefik.http.middlewares.root-to-platform.redirectRegex.permanent=false
- "prometheus.io/scrape=true"
- "prometheus.io/port=8080"
- "prometheus.io/path=/platform/actuator/prometheus"

View File

@@ -11,6 +11,7 @@ services:
- "${LOGTO_CONSOLE_BIND:-127.0.0.1}:${LOGTO_CONSOLE_PORT:-3002}:3002"
environment:
PUBLIC_HOST: ${PUBLIC_HOST:-localhost}
AUTH_HOST: ${AUTH_HOST:-localhost}
CERT_FILE: ${CERT_FILE:-}
KEY_FILE: ${KEY_FILE:-}
CA_FILE: ${CA_FILE:-}

View File

@@ -20,7 +20,7 @@ spring:
oauth2:
resourceserver:
jwt:
issuer-uri: ${cameleer.saas.provisioning.publicprotocol:https}://${cameleer.saas.provisioning.publichost:localhost}/oidc
issuer-uri: ${cameleer.saas.provisioning.publicprotocol:https}://${cameleer.saas.identity.authhost:localhost}/oidc
jwk-set-uri: ${cameleer.saas.identity.logtoendpoint:http://cameleer-logto:3001}/oidc/jwks
management:
@@ -35,6 +35,7 @@ management:
cameleer:
saas:
identity:
authhost: ${CAMELEER_SAAS_IDENTITY_AUTHHOST:${cameleer.saas.provisioning.publichost:localhost}}
logtoendpoint: ${CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT:}
logtopublicendpoint: ${CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT:}
m2mclientid: ${CAMELEER_SAAS_IDENTITY_M2MCLIENTID:}
@@ -56,7 +57,7 @@ cameleer:
clickhouseurl: ${CAMELEER_SAAS_PROVISIONING_CLICKHOUSEURL:jdbc:clickhouse://cameleer-clickhouse:8123/cameleer}
clickhouseuser: ${CAMELEER_SAAS_PROVISIONING_CLICKHOUSEUSER:default}
clickhousepassword: ${CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD:${CLICKHOUSE_PASSWORD:cameleer_ch}}
oidcissueruri: ${cameleer.saas.provisioning.publicprotocol}://${cameleer.saas.provisioning.publichost}/oidc
oidcissueruri: ${cameleer.saas.provisioning.publicprotocol}://${cameleer.saas.identity.authhost}/oidc
oidcjwkseturi: http://cameleer-logto:3001/oidc/jwks
corsorigins: ${cameleer.saas.provisioning.publicprotocol}://${cameleer.saas.provisioning.publichost}
certs: