Compare commits

...

3 Commits

Author SHA1 Message Date
hsiegeln
4380aa790d fix: single-quote passwords in .env to handle special characters
Passwords with $, &, ;, [, etc. were written unquoted to .env and
cameleer.conf, causing Docker Compose to mangle them. Now all password
and secret fields are written as KEY='value' with embedded single
quotes escaped as '\''.

Also removes inline DB_URL from docker-compose.saas.yml — the Logto
entrypoint now builds it from PG_USER/PG_PASSWORD/PG_HOST using
node's encodeURIComponent for URL-safe encoding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 16:33:43 +02:00
hsiegeln
a9aee77077 fix: make get-cameleer.ps1 safe under irm | iex
iex evaluates the script body in the caller's scope, so top-level
side effects leak into the user's interactive PowerShell. Two leaks
fixed:

- Dropped 'exit $LASTEXITCODE' — would close the user's shell window.
- Dropped 'Set-Location $Dir' — would leave the user sitting in
  .\installer\ after the run. install.ps1 uses $PSScriptRoot to find
  its templates, so it can be invoked from any CWD via its full path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:44:01 +02:00
hsiegeln
0b092065c5 feat: bootstrap scripts auto-launch the installer
get-cameleer.sh and get-cameleer.ps1 now download the installer files
and exec install.sh / install.ps1 immediately instead of just printing
a "run this next" hint. Extra arguments are forwarded to the installer.

PowerShell bootstrap fetches install.ps1 (not install.sh) so Windows
users no longer need bash. README updated to use the bash -c "$(curl ...)"
form so install.sh's interactive prompts inherit the user's TTY.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:35:32 +02:00
6 changed files with 88 additions and 60 deletions

View File

@@ -7,23 +7,29 @@ One-line installer for the [Cameleer](https://cameleer.io) observability platfor
**Linux / macOS:** **Linux / macOS:**
```bash ```bash
curl -fsSL https://registry.cameleer.io/cameleer/cameleer-saas-installer/raw/branch/main/get-cameleer.sh | bash bash -c "$(curl -fsSL https://registry.cameleer.io/cameleer/cameleer-saas-installer/raw/branch/main/get-cameleer.sh)"
cd installer && ./install.sh
``` ```
**Windows (PowerShell):** **Windows (PowerShell):**
```powershell ```powershell
irm https://registry.cameleer.io/cameleer/cameleer-saas-installer/raw/branch/main/get-cameleer.ps1 | iex irm https://registry.cameleer.io/cameleer/cameleer-saas-installer/raw/branch/main/get-cameleer.ps1 | iex
cd installer; .\install.sh
``` ```
The bootstrap downloads the installer into `./installer/` and launches it immediately. The interactive prompts run in your terminal.
**Pin a version:** **Pin a version:**
```bash ```bash
curl -fsSL .../get-cameleer.sh | bash -s -- --version=v1.0.0 bash -c "$(curl -fsSL .../get-cameleer.sh)" -- --version=v1.0.0
``` ```
```powershell
& ([scriptblock]::Create((irm .../get-cameleer.ps1))) -Version v1.0.0
```
Any extra arguments are forwarded to `install.sh` / `install.ps1` (e.g. `--silent`, `--expert`, `--public-host=…`).
## Deployment Modes ## Deployment Modes
| | Multi-tenant SaaS | Standalone | | | Multi-tenant SaaS | Standalone |
@@ -216,9 +222,10 @@ All services share a single hostname. Routing:
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `get-cameleer.sh` | Bootstrap script (bash) — downloads installer files | | `get-cameleer.sh` | Bootstrap script (bash) — downloads installer files and launches `install.sh` |
| `get-cameleer.ps1` | Bootstrap script (PowerShell) — downloads installer files | | `get-cameleer.ps1` | Bootstrap script (PowerShell) — downloads installer files and launches `install.ps1` |
| `install.sh` | Main installer — interactive or silent deployment | | `install.sh` | Main installer (Linux / macOS) — interactive or silent deployment |
| `install.ps1` | Main installer (Windows PowerShell) — interactive or silent deployment |
| `templates/docker-compose.yml` | Base infrastructure (Traefik, PostgreSQL, ClickHouse) | | `templates/docker-compose.yml` | Base infrastructure (Traefik, PostgreSQL, ClickHouse) |
| `templates/docker-compose.saas.yml` | SaaS mode (Logto + management plane) | | `templates/docker-compose.saas.yml` | SaaS mode (Logto + management plane) |
| `templates/docker-compose.server.yml` | Standalone mode (server + UI) | | `templates/docker-compose.server.yml` | Standalone mode (server + UI) |

View File

@@ -4,12 +4,13 @@
Bootstrap script — downloads the Cameleer installer and runs it. Bootstrap script — downloads the Cameleer installer and runs it.
.EXAMPLE .EXAMPLE
irm https://registry.cameleer.io/cameleer/cameleer-saas-installer/raw/branch/main/get-cameleer.ps1 | iex irm https://registry.cameleer.io/cameleer/cameleer-saas-installer/raw/branch/main/get-cameleer.ps1 | iex
.\get-cameleer.ps1 -Version v1.2.0 & ([scriptblock]::Create((irm https://.../get-cameleer.ps1))) -Version v1.2.0
#> #>
param( param(
[string]$Version, [string]$Version,
[string]$Ref, [string]$Ref,
[switch]$Run [Parameter(ValueFromRemainingArguments = $true)]
[string[]]$InstallerArgs
) )
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
@@ -23,7 +24,7 @@ $Base = "$Repo/$RefPath"
$Dir = '.\installer' $Dir = '.\installer'
$Files = @( $Files = @(
'install.sh' 'install.ps1'
'templates/docker-compose.yml' 'templates/docker-compose.yml'
'templates/docker-compose.saas.yml' 'templates/docker-compose.saas.yml'
'templates/docker-compose.server.yml' 'templates/docker-compose.server.yml'
@@ -47,11 +48,12 @@ foreach ($file in $Files) {
} }
Write-Host '' Write-Host ''
Write-Host "Installer ready in $Dir\" Write-Host "Installer downloaded to $Dir\ — launching..."
Write-Host 'Run: cd installer; .\install.sh'
Write-Host '' Write-Host ''
if ($Run) { $installerPath = Join-Path $Dir 'install.ps1'
Set-Location $Dir if ($InstallerArgs) {
& .\install.sh @args & $installerPath @InstallerArgs
} else {
& $installerPath
} }

View File

@@ -3,18 +3,20 @@ set -euo pipefail
# Bootstrap script — downloads the Cameleer installer and runs it. # Bootstrap script — downloads the Cameleer installer and runs it.
# Usage: # Usage:
# curl -fsSL https://get.cameleer.io/install | bash # bash -c "$(curl -fsSL https://get.cameleer.io/install)"
# curl -fsSL https://get.cameleer.io/install | bash -s -- --version v1.2.0 # bash -c "$(curl -fsSL https://get.cameleer.io/install)" -- --version v1.2.0
REPO="https://registry.cameleer.io/cameleer/cameleer-saas-installer/raw" REPO="https://registry.cameleer.io/cameleer/cameleer-saas-installer/raw"
REF="branch/main" REF="branch/main"
DIR="./installer" DIR="./installer"
# Parse --version / --ref # Parse --version / --ref (consume them; remaining args are forwarded to install.sh)
PASS_ARGS=()
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--version=*) REF="tag/${arg#*=}"; shift ;; --version=*) REF="tag/${arg#*=}" ;;
--ref=*) REF="branch/${arg#*=}"; shift ;; --ref=*) REF="branch/${arg#*=}" ;;
*) PASS_ARGS+=("$arg") ;;
esac esac
done done
@@ -43,13 +45,8 @@ done
chmod +x "$DIR/install.sh" chmod +x "$DIR/install.sh"
echo "" echo ""
echo "Installer ready in $DIR/" echo "Installer downloaded to $DIR/ — launching..."
echo "Run: cd $DIR && ./install.sh"
echo "" echo ""
# Auto-run if not piped with extra args that look like they want manual control cd "$DIR"
if [ "${1:-}" = "--run" ]; then exec ./install.sh "${PASS_ARGS[@]}"
shift
cd "$DIR"
exec ./install.sh "$@"
fi

View File

@@ -255,7 +255,14 @@ function Write-Utf8File {
$enc = New-Object System.Text.UTF8Encoding $false $enc = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($Path, $Content, $enc) [System.IO.File]::WriteAllText($Path, $Content, $enc)
} }
# Single-quote a value for .env/.conf files so special characters ($, &, ;, etc.)
# are preserved. Embedded single quotes are escaped as '\''.
function Format-EnvVal([string]$Key, [string]$Val) {
$escaped = $Val -replace "'", "'\\''"
return "$Key='$escaped'"
}
# --- Config file --- # --- Config file ---
function Load-ConfigFile { function Load-ConfigFile {
@@ -266,6 +273,12 @@ function Load-ConfigFile {
if ($line -match '^\s*([^=]+?)\s*=\s*(.*?)\s*$') { if ($line -match '^\s*([^=]+?)\s*=\s*(.*?)\s*$') {
$key = $Matches[1].Trim() $key = $Matches[1].Trim()
$val = $Matches[2].Trim() $val = $Matches[2].Trim()
# Strip surrounding quotes and unescape '\''
if ($val.Length -ge 2 -and $val[0] -eq "'" -and $val[-1] -eq "'") {
$val = $val.Substring(1, $val.Length - 2) -replace "'\\''" , "'"
} elseif ($val.Length -ge 2 -and $val[0] -eq '"' -and $val[-1] -eq '"') {
$val = $val.Substring(1, $val.Length - 2)
}
switch ($key) { switch ($key) {
'install_dir' { if (-not $script:cfg.InstallDir) { $script:cfg.InstallDir = $val } } 'install_dir' { if (-not $script:cfg.InstallDir) { $script:cfg.InstallDir = $val } }
'public_host' { if (-not $script:cfg.PublicHost) { $script:cfg.PublicHost = $val } } 'public_host' { if (-not $script:cfg.PublicHost) { $script:cfg.PublicHost = $val } }
@@ -669,15 +682,10 @@ HTTPS_PORT=$($c.HttpsPort)
# PostgreSQL # PostgreSQL
POSTGRES_USER=cameleer POSTGRES_USER=cameleer
POSTGRES_PASSWORD=$($c.PostgresPassword)
POSTGRES_DB=cameleer POSTGRES_DB=cameleer
# ClickHouse
CLICKHOUSE_PASSWORD=$($c.ClickhousePassword)
# Server admin # Server admin
SERVER_ADMIN_USER=$($c.AdminUser) SERVER_ADMIN_USER=$($c.AdminUser)
SERVER_ADMIN_PASS=$($c.AdminPass)
# Bootstrap token # Bootstrap token
BOOTSTRAP_TOKEN=$bt BOOTSTRAP_TOKEN=$bt
@@ -697,6 +705,10 @@ CLICKHOUSE_IMAGE=$($c.Registry)/cameleer-clickhouse
SERVER_IMAGE=$($c.Registry)/cameleer-server SERVER_IMAGE=$($c.Registry)/cameleer-server
SERVER_UI_IMAGE=$($c.Registry)/cameleer-server-ui SERVER_UI_IMAGE=$($c.Registry)/cameleer-server-ui
"@ "@
# Passwords appended with single-quoting for special character safety
$content += "`n$(Format-EnvVal 'POSTGRES_PASSWORD' $c.PostgresPassword)"
$content += "`n$(Format-EnvVal 'CLICKHOUSE_PASSWORD' $c.ClickhousePassword)"
$content += "`n$(Format-EnvVal 'SERVER_ADMIN_PASS' $c.AdminPass)"
if ($c.TlsMode -eq 'custom') { if ($c.TlsMode -eq 'custom') {
$content += "`nCERT_FILE=/user-certs/cert.pem" $content += "`nCERT_FILE=/user-certs/cert.pem"
$content += "`nKEY_FILE=/user-certs/key.pem" $content += "`nKEY_FILE=/user-certs/key.pem"
@@ -728,16 +740,11 @@ LOGTO_CONSOLE_BIND=$consoleBind
# PostgreSQL # PostgreSQL
POSTGRES_USER=cameleer POSTGRES_USER=cameleer
POSTGRES_PASSWORD=$($c.PostgresPassword)
POSTGRES_DB=cameleer_saas POSTGRES_DB=cameleer_saas
# ClickHouse
CLICKHOUSE_PASSWORD=$($c.ClickhousePassword)
# Admin user # Admin user
SAAS_ADMIN_USER=$($c.AdminUser) SAAS_ADMIN_USER=$($c.AdminUser)
SAAS_ADMIN_PASS=$($c.AdminPass)
# TLS # TLS
NODE_TLS_REJECT=$($c.NodeTlsReject) NODE_TLS_REJECT=$($c.NodeTlsReject)
"@ "@
@@ -772,10 +779,14 @@ CAMELEER_SERVER_SECURITY_JWTSECRET=$jwtSecret
SMTP_HOST=$($c.SmtpHost) SMTP_HOST=$($c.SmtpHost)
SMTP_PORT=$(if ($c.SmtpPort) { $c.SmtpPort } else { '587' }) SMTP_PORT=$(if ($c.SmtpPort) { $c.SmtpPort } else { '587' })
SMTP_USER=$($c.SmtpUser) SMTP_USER=$($c.SmtpUser)
SMTP_PASS=$($c.SmtpPass)
SMTP_FROM_EMAIL=$(if ($c.SmtpFromEmail) { $c.SmtpFromEmail } else { "noreply@$($c.PublicHost)" }) SMTP_FROM_EMAIL=$(if ($c.SmtpFromEmail) { $c.SmtpFromEmail } else { "noreply@$($c.PublicHost)" })
"@ "@
$content += $provisioningBlock $content += $provisioningBlock
# Passwords appended with single-quoting for special character safety
$content += "`n$(Format-EnvVal 'POSTGRES_PASSWORD' $c.PostgresPassword)"
$content += "`n$(Format-EnvVal 'CLICKHOUSE_PASSWORD' $c.ClickhousePassword)"
$content += "`n$(Format-EnvVal 'SAAS_ADMIN_PASS' $c.AdminPass)"
$content += "`n$(Format-EnvVal 'SMTP_PASS' $c.SmtpPass)"
$composeFile = 'docker-compose.yml;docker-compose.saas.yml' $composeFile = 'docker-compose.yml;docker-compose.saas.yml'
if ($c.TlsMode -eq 'custom') { $composeFile += ';docker-compose.tls.yml' } if ($c.TlsMode -eq 'custom') { $composeFile += ';docker-compose.tls.yml' }
if ($c.MonitoringNetwork) { $composeFile += ';docker-compose.monitoring.yml' } if ($c.MonitoringNetwork) { $composeFile += ';docker-compose.monitoring.yml' }
@@ -1017,12 +1028,13 @@ deployment_mode=$($c.DeploymentMode)
smtp_host=$($c.SmtpHost) smtp_host=$($c.SmtpHost)
smtp_port=$($c.SmtpPort) smtp_port=$($c.SmtpPort)
smtp_user=$($c.SmtpUser) smtp_user=$($c.SmtpUser)
smtp_pass=$($c.SmtpPass)
smtp_from_email=$($c.SmtpFromEmail) smtp_from_email=$($c.SmtpFromEmail)
registry=$($c.Registry) registry=$($c.Registry)
registry_user=$($c.RegistryUser) registry_user=$($c.RegistryUser)
registry_token=$($c.RegistryToken)
"@ "@
# Passwords appended with single-quoting for special character safety
$txt += "`n$(Format-EnvVal 'smtp_pass' $c.SmtpPass)"
$txt += "`n$(Format-EnvVal 'registry_token' $c.RegistryToken)"
Write-Utf8File $f $txt Write-Utf8File $f $txt
Log-Info 'Saved installer config to cameleer.conf' Log-Info 'Saved installer config to cameleer.conf'
} }

View File

@@ -156,6 +156,14 @@ generate_password() {
tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32 || : tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32 || :
} }
# Write KEY='escaped_value' to a file. Single-quotes prevent Docker Compose
# from interpreting $, &, ;, etc. Embedded single quotes are escaped as '\''.
env_val() {
local file="$1" key="$2" val="$3"
val="${val//\'/\'\\\'\'}"
printf "%s='%s'\n" "$key" "$val" >> "$file"
}
# --- Argument parsing --- # --- Argument parsing ---
parse_args() { parse_args() {
@@ -259,8 +267,9 @@ load_config_file() {
case "$key" in case "$key" in
\#*|"") continue ;; \#*|"") continue ;;
esac esac
key=$(echo "$key" | tr -d ' ') key=$(printf '%s' "$key" | tr -d ' ')
value=$(echo "$value" | sed 's/^[ ]*//;s/[ ]*$//') # Trim whitespace, strip surrounding quotes, unescape '\''
value=$(printf '%s' "$value" | sed "s/^[ ]*//;s/[ ]*$//;s/^'\\(.*\\)'$/\\1/;s/^\"\\(.*\\)\"$/\\1/" | sed "s/'\\\\''/'/g")
case "$key" in case "$key" in
install_dir) [ -z "$INSTALL_DIR" ] && INSTALL_DIR="$value" ;; install_dir) [ -z "$INSTALL_DIR" ] && INSTALL_DIR="$value" ;;
public_host) [ -z "$PUBLIC_HOST" ] && PUBLIC_HOST="$value" ;; public_host) [ -z "$PUBLIC_HOST" ] && PUBLIC_HOST="$value" ;;
@@ -663,15 +672,10 @@ HTTPS_PORT=${HTTPS_PORT}
# PostgreSQL # PostgreSQL
POSTGRES_USER=cameleer POSTGRES_USER=cameleer
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_DB=cameleer POSTGRES_DB=cameleer
# ClickHouse
CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
# Server admin # Server admin
SERVER_ADMIN_USER=${ADMIN_USER} SERVER_ADMIN_USER=${ADMIN_USER}
SERVER_ADMIN_PASS=${ADMIN_PASS}
# Bootstrap token (required by server, not used externally in standalone mode) # Bootstrap token (required by server, not used externally in standalone mode)
BOOTSTRAP_TOKEN=$(generate_password) BOOTSTRAP_TOKEN=$(generate_password)
@@ -694,6 +698,10 @@ SERVER_UI_IMAGE=${REGISTRY}/cameleer-server-ui
# Compose file assembly # Compose file assembly
COMPOSE_FILE=docker-compose.yml:docker-compose.server.yml$([ "$TLS_MODE" = "custom" ] && echo ":docker-compose.tls.yml")$([ -n "$MONITORING_NETWORK" ] && echo ":docker-compose.monitoring.yml") COMPOSE_FILE=docker-compose.yml:docker-compose.server.yml$([ "$TLS_MODE" = "custom" ] && echo ":docker-compose.tls.yml")$([ -n "$MONITORING_NETWORK" ] && echo ":docker-compose.monitoring.yml")
EOF EOF
# Passwords are appended with single-quoting to handle special characters
env_val "$f" POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
env_val "$f" CLICKHOUSE_PASSWORD "$CLICKHOUSE_PASSWORD"
env_val "$f" SERVER_ADMIN_PASS "$ADMIN_PASS"
if [ "$TLS_MODE" = "custom" ]; then if [ "$TLS_MODE" = "custom" ]; then
echo "CERT_FILE=/user-certs/cert.pem" >> "$f" echo "CERT_FILE=/user-certs/cert.pem" >> "$f"
echo "KEY_FILE=/user-certs/key.pem" >> "$f" echo "KEY_FILE=/user-certs/key.pem" >> "$f"
@@ -729,15 +737,10 @@ LOGTO_CONSOLE_BIND=$([ "$LOGTO_CONSOLE_EXPOSED" = "true" ] && echo "0.0.0.0" ||
# PostgreSQL # PostgreSQL
POSTGRES_USER=cameleer POSTGRES_USER=cameleer
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_DB=cameleer_saas POSTGRES_DB=cameleer_saas
# ClickHouse
CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
# Admin user # Admin user
SAAS_ADMIN_USER=${ADMIN_USER} SAAS_ADMIN_USER=${ADMIN_USER}
SAAS_ADMIN_PASS=${ADMIN_PASS}
# TLS # TLS
NODE_TLS_REJECT=${NODE_TLS_REJECT} NODE_TLS_REJECT=${NODE_TLS_REJECT}
@@ -778,13 +781,18 @@ CAMELEER_SERVER_SECURITY_JWTSECRET=$(generate_password)
SMTP_HOST=${SMTP_HOST} SMTP_HOST=${SMTP_HOST}
SMTP_PORT=${SMTP_PORT:-587} SMTP_PORT=${SMTP_PORT:-587}
SMTP_USER=${SMTP_USER} SMTP_USER=${SMTP_USER}
SMTP_PASS=${SMTP_PASS}
SMTP_FROM_EMAIL=${SMTP_FROM_EMAIL:-noreply@${PUBLIC_HOST}} SMTP_FROM_EMAIL=${SMTP_FROM_EMAIL:-noreply@${PUBLIC_HOST}}
# Compose file assembly # Compose file assembly
COMPOSE_FILE=docker-compose.yml:docker-compose.saas.yml$([ "$TLS_MODE" = "custom" ] && echo ":docker-compose.tls.yml")$([ -n "$MONITORING_NETWORK" ] && echo ":docker-compose.monitoring.yml") COMPOSE_FILE=docker-compose.yml:docker-compose.saas.yml$([ "$TLS_MODE" = "custom" ] && echo ":docker-compose.tls.yml")$([ -n "$MONITORING_NETWORK" ] && echo ":docker-compose.monitoring.yml")
EOF EOF
# Passwords are appended with single-quoting to handle special characters
env_val "$f" POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
env_val "$f" CLICKHOUSE_PASSWORD "$CLICKHOUSE_PASSWORD"
env_val "$f" SAAS_ADMIN_PASS "$ADMIN_PASS"
env_val "$f" SMTP_PASS "$SMTP_PASS"
if [ -n "$MONITORING_NETWORK" ]; then if [ -n "$MONITORING_NETWORK" ]; then
echo "" >> "$f" echo "" >> "$f"
echo "# Monitoring" >> "$f" echo "# Monitoring" >> "$f"
@@ -967,12 +975,13 @@ deployment_mode=${DEPLOYMENT_MODE}
smtp_host=${SMTP_HOST} smtp_host=${SMTP_HOST}
smtp_port=${SMTP_PORT} smtp_port=${SMTP_PORT}
smtp_user=${SMTP_USER} smtp_user=${SMTP_USER}
smtp_pass=${SMTP_PASS}
smtp_from_email=${SMTP_FROM_EMAIL} smtp_from_email=${SMTP_FROM_EMAIL}
registry=${REGISTRY} registry=${REGISTRY}
registry_user=${REGISTRY_USER} registry_user=${REGISTRY_USER}
registry_token=${REGISTRY_TOKEN}
EOF EOF
# Passwords appended with single-quoting for special character safety
env_val "$f" smtp_pass "$SMTP_PASS"
env_val "$f" registry_token "$REGISTRY_TOKEN"
log_info "Saved installer config to cameleer.conf" log_info "Saved installer config to cameleer.conf"
} }

View File

@@ -9,7 +9,8 @@ services:
cameleer-postgres: cameleer-postgres:
condition: service_healthy condition: service_healthy
environment: environment:
DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD}@cameleer-postgres:5432/logto # DB_URL is built by the entrypoint from PG_USER/PG_PASSWORD/PG_HOST
# to safely handle special characters in the password
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost} ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}
ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002} ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}
TRUST_PROXY_HEADER: 1 TRUST_PROXY_HEADER: 1