diff --git a/.env.example b/.env.example index e8f4256..ba0ff0a 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index f3c868a..034369d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/docker/cameleer-traefik/entrypoint.sh b/docker/cameleer-traefik/entrypoint.sh index bf2c0b8..235e872 100644 --- a/docker/cameleer-traefik/entrypoint.sh +++ b/docker/cameleer-traefik/entrypoint.sh @@ -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 diff --git a/docker/cameleer-traefik/traefik-dynamic.yml b/docker/cameleer-traefik/traefik-dynamic.yml index e26bfdf..b2a8787 100644 --- a/docker/cameleer-traefik/traefik-dynamic.yml +++ b/docker/cameleer-traefik/traefik-dynamic.yml @@ -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: diff --git a/docker/logto-bootstrap.sh b/docker/logto-bootstrap.sh index 375ffb6..c6b3372 100644 --- a/docker/logto-bootstrap.sh +++ b/docker/logto-bootstrap.sh @@ -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) diff --git a/installer/install.ps1 b/installer/install.ps1 index a14ef88..07f78da 100644 --- a/installer/install.ps1 +++ b/installer/install.ps1 @@ -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" diff --git a/installer/install.sh b/installer/install.sh index 34ce17c..4e1744e 100644 --- a/installer/install.sh +++ b/installer/install.sh @@ -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 diff --git a/installer/templates/.env.example b/installer/templates/.env.example index 2863bca..34bb45f 100644 --- a/installer/templates/.env.example +++ b/installer/templates/.env.example @@ -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 diff --git a/installer/templates/docker-compose.saas.yml b/installer/templates/docker-compose.saas.yml index e22a14f..69d2d69 100644 --- a/installer/templates/docker-compose.saas.yml +++ b/installer/templates/docker-compose.saas.yml @@ -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" diff --git a/installer/templates/docker-compose.yml b/installer/templates/docker-compose.yml index 99a93b4..dd60d37 100644 --- a/installer/templates/docker-compose.yml +++ b/installer/templates/docker-compose.yml @@ -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:-} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 21b58aa..5091ebd 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -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: