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>
This commit is contained in:
44
install.ps1
44
install.ps1
@@ -255,7 +255,14 @@ function Write-Utf8File {
|
||||
$enc = New-Object System.Text.UTF8Encoding $false
|
||||
[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 ---
|
||||
|
||||
function Load-ConfigFile {
|
||||
@@ -266,6 +273,12 @@ function Load-ConfigFile {
|
||||
if ($line -match '^\s*([^=]+?)\s*=\s*(.*?)\s*$') {
|
||||
$key = $Matches[1].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) {
|
||||
'install_dir' { if (-not $script:cfg.InstallDir) { $script:cfg.InstallDir = $val } }
|
||||
'public_host' { if (-not $script:cfg.PublicHost) { $script:cfg.PublicHost = $val } }
|
||||
@@ -669,15 +682,10 @@ HTTPS_PORT=$($c.HttpsPort)
|
||||
|
||||
# PostgreSQL
|
||||
POSTGRES_USER=cameleer
|
||||
POSTGRES_PASSWORD=$($c.PostgresPassword)
|
||||
POSTGRES_DB=cameleer
|
||||
|
||||
# ClickHouse
|
||||
CLICKHOUSE_PASSWORD=$($c.ClickhousePassword)
|
||||
|
||||
# Server admin
|
||||
SERVER_ADMIN_USER=$($c.AdminUser)
|
||||
SERVER_ADMIN_PASS=$($c.AdminPass)
|
||||
|
||||
# Bootstrap token
|
||||
BOOTSTRAP_TOKEN=$bt
|
||||
@@ -697,6 +705,10 @@ CLICKHOUSE_IMAGE=$($c.Registry)/cameleer-clickhouse
|
||||
SERVER_IMAGE=$($c.Registry)/cameleer-server
|
||||
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') {
|
||||
$content += "`nCERT_FILE=/user-certs/cert.pem"
|
||||
$content += "`nKEY_FILE=/user-certs/key.pem"
|
||||
@@ -728,16 +740,11 @@ LOGTO_CONSOLE_BIND=$consoleBind
|
||||
|
||||
# PostgreSQL
|
||||
POSTGRES_USER=cameleer
|
||||
POSTGRES_PASSWORD=$($c.PostgresPassword)
|
||||
POSTGRES_DB=cameleer_saas
|
||||
|
||||
# ClickHouse
|
||||
CLICKHOUSE_PASSWORD=$($c.ClickhousePassword)
|
||||
|
||||
|
||||
# Admin user
|
||||
SAAS_ADMIN_USER=$($c.AdminUser)
|
||||
SAAS_ADMIN_PASS=$($c.AdminPass)
|
||||
|
||||
|
||||
# TLS
|
||||
NODE_TLS_REJECT=$($c.NodeTlsReject)
|
||||
"@
|
||||
@@ -772,10 +779,14 @@ CAMELEER_SERVER_SECURITY_JWTSECRET=$jwtSecret
|
||||
SMTP_HOST=$($c.SmtpHost)
|
||||
SMTP_PORT=$(if ($c.SmtpPort) { $c.SmtpPort } else { '587' })
|
||||
SMTP_USER=$($c.SmtpUser)
|
||||
SMTP_PASS=$($c.SmtpPass)
|
||||
SMTP_FROM_EMAIL=$(if ($c.SmtpFromEmail) { $c.SmtpFromEmail } else { "noreply@$($c.PublicHost)" })
|
||||
"@
|
||||
$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'
|
||||
if ($c.TlsMode -eq 'custom') { $composeFile += ';docker-compose.tls.yml' }
|
||||
if ($c.MonitoringNetwork) { $composeFile += ';docker-compose.monitoring.yml' }
|
||||
@@ -1017,12 +1028,13 @@ deployment_mode=$($c.DeploymentMode)
|
||||
smtp_host=$($c.SmtpHost)
|
||||
smtp_port=$($c.SmtpPort)
|
||||
smtp_user=$($c.SmtpUser)
|
||||
smtp_pass=$($c.SmtpPass)
|
||||
smtp_from_email=$($c.SmtpFromEmail)
|
||||
registry=$($c.Registry)
|
||||
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
|
||||
Log-Info 'Saved installer config to cameleer.conf'
|
||||
}
|
||||
|
||||
39
install.sh
39
install.sh
@@ -156,6 +156,14 @@ generate_password() {
|
||||
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 ---
|
||||
|
||||
parse_args() {
|
||||
@@ -259,8 +267,9 @@ load_config_file() {
|
||||
case "$key" in
|
||||
\#*|"") continue ;;
|
||||
esac
|
||||
key=$(echo "$key" | tr -d ' ')
|
||||
value=$(echo "$value" | sed 's/^[ ]*//;s/[ ]*$//')
|
||||
key=$(printf '%s' "$key" | tr -d ' ')
|
||||
# Trim whitespace, strip surrounding quotes, unescape '\''
|
||||
value=$(printf '%s' "$value" | sed "s/^[ ]*//;s/[ ]*$//;s/^'\\(.*\\)'$/\\1/;s/^\"\\(.*\\)\"$/\\1/" | sed "s/'\\\\''/'/g")
|
||||
case "$key" in
|
||||
install_dir) [ -z "$INSTALL_DIR" ] && INSTALL_DIR="$value" ;;
|
||||
public_host) [ -z "$PUBLIC_HOST" ] && PUBLIC_HOST="$value" ;;
|
||||
@@ -663,15 +672,10 @@ HTTPS_PORT=${HTTPS_PORT}
|
||||
|
||||
# PostgreSQL
|
||||
POSTGRES_USER=cameleer
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB=cameleer
|
||||
|
||||
# ClickHouse
|
||||
CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
|
||||
|
||||
# Server admin
|
||||
SERVER_ADMIN_USER=${ADMIN_USER}
|
||||
SERVER_ADMIN_PASS=${ADMIN_PASS}
|
||||
|
||||
# Bootstrap token (required by server, not used externally in standalone mode)
|
||||
BOOTSTRAP_TOKEN=$(generate_password)
|
||||
@@ -694,6 +698,10 @@ SERVER_UI_IMAGE=${REGISTRY}/cameleer-server-ui
|
||||
# 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")
|
||||
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
|
||||
echo "CERT_FILE=/user-certs/cert.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
|
||||
POSTGRES_USER=cameleer
|
||||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB=cameleer_saas
|
||||
|
||||
# ClickHouse
|
||||
CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
|
||||
|
||||
# Admin user
|
||||
SAAS_ADMIN_USER=${ADMIN_USER}
|
||||
SAAS_ADMIN_PASS=${ADMIN_PASS}
|
||||
|
||||
# TLS
|
||||
NODE_TLS_REJECT=${NODE_TLS_REJECT}
|
||||
@@ -778,13 +781,18 @@ CAMELEER_SERVER_SECURITY_JWTSECRET=$(generate_password)
|
||||
SMTP_HOST=${SMTP_HOST}
|
||||
SMTP_PORT=${SMTP_PORT:-587}
|
||||
SMTP_USER=${SMTP_USER}
|
||||
SMTP_PASS=${SMTP_PASS}
|
||||
SMTP_FROM_EMAIL=${SMTP_FROM_EMAIL:-noreply@${PUBLIC_HOST}}
|
||||
|
||||
# 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")
|
||||
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
|
||||
echo "" >> "$f"
|
||||
echo "# Monitoring" >> "$f"
|
||||
@@ -967,12 +975,13 @@ deployment_mode=${DEPLOYMENT_MODE}
|
||||
smtp_host=${SMTP_HOST}
|
||||
smtp_port=${SMTP_PORT}
|
||||
smtp_user=${SMTP_USER}
|
||||
smtp_pass=${SMTP_PASS}
|
||||
smtp_from_email=${SMTP_FROM_EMAIL}
|
||||
registry=${REGISTRY}
|
||||
registry_user=${REGISTRY_USER}
|
||||
registry_token=${REGISTRY_TOKEN}
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ services:
|
||||
cameleer-postgres:
|
||||
condition: service_healthy
|
||||
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}
|
||||
ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${AUTH_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}
|
||||
TRUST_PROXY_HEADER: 1
|
||||
|
||||
Reference in New Issue
Block a user