diff --git a/README.md b/README.md index 821ce7d..bd86459 100644 --- a/README.md +++ b/README.md @@ -84,11 +84,10 @@ Settings can be provided via CLI flags, environment variables, config file (`cam | Setting | CLI Flag | Env Var | Config Key | Default | |---------|----------|---------|------------|---------| -| Admin username | `--admin-user` | `SAAS_ADMIN_USER` | `admin_user` | `admin` | +| Admin login | `--admin-user` | `SAAS_ADMIN_USER` | `admin_user` | `admin` (standalone) / `admin@` (SaaS) | | Admin password | `--admin-password` | `SAAS_ADMIN_PASS` | `admin_password` | auto-generated | -| Admin email | `--admin-email` | `SAAS_ADMIN_EMAIL` | `admin_email` | `@` | -Email is the primary user identity in SaaS mode. All users — including the admin — must have an email address. If `SAAS_ADMIN_EMAIL` is not set, the bootstrap derives it from `@`. +In SaaS mode, `SAAS_ADMIN_USER` must be an email address — it is used as both the Logto username and primaryEmail. The installer validates email format in SaaS mode and auto-appends `@` if the `@` is missing. In standalone mode, any username is accepted. In standalone mode, the env vars are `SERVER_ADMIN_USER` / `SERVER_ADMIN_PASS`. diff --git a/install.ps1 b/install.ps1 index 06791f6..49373f1 100644 --- a/install.ps1 +++ b/install.ps1 @@ -100,7 +100,7 @@ $script:cfg = @{ PublicProtocol = $PublicProtocol AdminUser = $AdminUser AdminPass = $AdminPassword - AdminEmail = $null + TlsMode = $TlsMode CertFile = $CertFile KeyFile = $KeyFile @@ -272,7 +272,7 @@ function Load-ConfigFile { '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 } } - 'admin_email' { if (-not $script:cfg.AdminEmail) { $script:cfg.AdminEmail = $val } } + 'tls_mode' { if (-not $script:cfg.TlsMode) { $script:cfg.TlsMode = $val } } 'cert_file' { if (-not $script:cfg.CertFile) { $script:cfg.CertFile = $val } } 'key_file' { if (-not $script:cfg.KeyFile) { $script:cfg.KeyFile = $val } } @@ -305,7 +305,7 @@ function Load-EnvOverrides { 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 } - if (-not $c.AdminEmail) { $c.AdminEmail = $env:SAAS_ADMIN_EMAIL } + if (-not $c.TlsMode) { $c.TlsMode = $_ENV_TLS_MODE } if (-not $c.CertFile) { $c.CertFile = $_ENV_CERT_FILE } if (-not $c.KeyFile) { $c.KeyFile = $_ENV_KEY_FILE } @@ -450,17 +450,41 @@ function Run-SimplePrompts { Write-Host '' Write-Host '--- Simple Installation ---' -ForegroundColor Cyan Write-Host '' - + $c.InstallDir = Prompt-Value 'Install directory' (Coalesce $c.InstallDir $DEFAULT_INSTALL_DIR) $c.PublicHost = Prompt-Value 'Public hostname' (Coalesce $c.PublicHost 'localhost') - $c.AdminUser = Prompt-Value 'Admin username' (Coalesce $c.AdminUser $DEFAULT_ADMIN_USER) - + + Write-Host '' + Write-Host ' Deployment mode:' + Write-Host ' [1] Multi-tenant SaaS -- manage platform, provision tenants on demand' + Write-Host ' [2] Single-tenant -- one server instance, local auth, no identity provider' + Write-Host '' + $deployChoice = Read-Host ' Select mode [1]' + if ($deployChoice -eq '2') { $c.DeploymentMode = 'standalone' } else { $c.DeploymentMode = 'saas' } + + Write-Host '' + if ($c.DeploymentMode -eq 'saas') { + $defaultEmail = Coalesce $c.AdminUser "admin@$($c.PublicHost)" + if ($defaultEmail -and -not $defaultEmail.Contains('@')) { + $defaultEmail = "admin@$($c.PublicHost)" + } + $c.AdminUser = Prompt-Value 'Admin email' $defaultEmail + # Validate email: must contain @ + if (-not $c.AdminUser.Contains('@')) { + $original = $c.AdminUser + $c.AdminUser = "$($c.AdminUser)@$($c.PublicHost)" + Log-Info "Appended domain: '$original' -> '$($c.AdminUser)'" + } + } else { + $c.AdminUser = Prompt-Value 'Admin username' (Coalesce $c.AdminUser $DEFAULT_ADMIN_USER) + } + if (Prompt-YesNo 'Auto-generate admin password?' 'y') { $c.AdminPass = '' } else { $c.AdminPass = Prompt-Password 'Admin password' } - + Write-Host '' if (Prompt-YesNo 'Use custom TLS certificates? (no = self-signed)') { $c.TlsMode = 'custom' @@ -472,7 +496,7 @@ function Run-SimplePrompts { } else { $c.TlsMode = 'self-signed' } - + Write-Host '' $c.MonitoringNetwork = Prompt-Value 'Monitoring network name (empty = skip)' '' @@ -483,14 +507,6 @@ function Run-SimplePrompts { $c.RegistryToken = Prompt-Password 'Registry token/password' (Coalesce $c.RegistryToken '') } - Write-Host '' - Write-Host ' Deployment mode:' - Write-Host ' [1] Multi-tenant SaaS -- manage platform, provision tenants on demand' - Write-Host ' [2] Single-tenant -- one server instance, local auth, no identity provider' - Write-Host '' - $deployChoice = Read-Host ' Select mode [1]' - if ($deployChoice -eq '2') { $c.DeploymentMode = 'standalone' } else { $c.DeploymentMode = 'saas' } - } function Run-ExpertPrompts { @@ -601,7 +617,6 @@ function Validate-Config { function Generate-Passwords { $c = $script:cfg if (-not $c.AdminPass) { $c.AdminPass = Generate-Password; Log-Info 'Generated admin password.' } - if (-not $c.AdminEmail) { $c.AdminEmail = "$($c.AdminUser)@$($c.PublicHost)" } if (-not $c.PostgresPassword) { $c.PostgresPassword = Generate-Password; Log-Info 'Generated PostgreSQL password.' } if (-not $c.ClickhousePassword) { $c.ClickhousePassword = Generate-Password; Log-Info 'Generated ClickHouse password.' } } @@ -712,7 +727,6 @@ POSTGRES_DB=cameleer_saas # Admin user SAAS_ADMIN_USER=$($c.AdminUser) -SAAS_ADMIN_EMAIL=$($c.AdminEmail) # TLS NODE_TLS_REJECT=$($c.NodeTlsReject) @@ -976,7 +990,6 @@ public_host=$($c.PublicHost) auth_host=$($c.AuthHost) public_protocol=$($c.PublicProtocol) admin_user=$($c.AdminUser) -admin_email=$($c.AdminEmail) tls_mode=$($c.TlsMode) http_port=$($c.HttpPort) https_port=$($c.HttpsPort) @@ -1037,7 +1050,6 @@ ClickHouse: default / $($c.ClickhousePassword) Admin Console: $($c.PublicProtocol)://$($c.PublicHost)/platform/ Admin User: $($c.AdminUser) Admin Password: $($c.AdminPass) -Admin Email: $($c.AdminEmail) PostgreSQL: cameleer / $($c.PostgresPassword) ClickHouse: default / $($c.ClickhousePassword) @@ -1341,7 +1353,6 @@ function Print-Credentials { } Write-Host " Admin User: $($c.AdminUser)" Write-Host " Admin Password: $($c.AdminPass)" - Write-Host " Admin Email: $($c.AdminEmail)" Write-Host '' Write-Host " PostgreSQL: cameleer / $($c.PostgresPassword)" Write-Host " ClickHouse: default / $($c.ClickhousePassword)" diff --git a/install.sh b/install.sh index f73d35d..33248f6 100644 --- a/install.sh +++ b/install.sh @@ -56,7 +56,7 @@ AUTH_HOST="" PUBLIC_PROTOCOL="" ADMIN_USER="" ADMIN_PASS="" -ADMIN_EMAIL="" + TLS_MODE="" CERT_FILE="" KEY_FILE="" @@ -169,7 +169,7 @@ parse_args() { --public-protocol) PUBLIC_PROTOCOL="$2"; shift ;; --admin-user) ADMIN_USER="$2"; shift ;; --admin-password) ADMIN_PASS="$2"; shift ;; - --admin-email) ADMIN_EMAIL="$2"; shift ;; + --tls-mode) TLS_MODE="$2"; shift ;; --cert-file) CERT_FILE="$2"; shift ;; --key-file) KEY_FILE="$2"; shift ;; @@ -264,7 +264,7 @@ load_config_file() { public_protocol) [ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$value" ;; admin_user) [ -z "$ADMIN_USER" ] && ADMIN_USER="$value" ;; admin_password) [ -z "$ADMIN_PASS" ] && ADMIN_PASS="$value" ;; - admin_email) [ -z "$ADMIN_EMAIL" ] && ADMIN_EMAIL="$value" ;; + tls_mode) [ -z "$TLS_MODE" ] && TLS_MODE="$value" ;; cert_file) [ -z "$CERT_FILE" ] && CERT_FILE="$value" ;; key_file) [ -z "$KEY_FILE" ] && KEY_FILE="$value" ;; @@ -295,7 +295,7 @@ load_env_overrides() { [ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$_ENV_PUBLIC_PROTOCOL" [ -z "$ADMIN_USER" ] && ADMIN_USER="${SAAS_ADMIN_USER:-}" [ -z "$ADMIN_PASS" ] && ADMIN_PASS="${SAAS_ADMIN_PASS:-}" - [ -z "$ADMIN_EMAIL" ] && ADMIN_EMAIL="${SAAS_ADMIN_EMAIL:-}" + [ -z "$TLS_MODE" ] && TLS_MODE="$_ENV_TLS_MODE" [ -z "$CERT_FILE" ] && CERT_FILE="$_ENV_CERT_FILE" [ -z "$KEY_FILE" ] && KEY_FILE="$_ENV_KEY_FILE" @@ -429,7 +429,35 @@ run_simple_prompts() { prompt INSTALL_DIR "Install directory" "${INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" prompt PUBLIC_HOST "Public hostname" "${PUBLIC_HOST:-localhost}" - prompt ADMIN_USER "Admin username" "${ADMIN_USER:-$DEFAULT_ADMIN_USER}" + + echo "" + echo " Deployment mode:" + echo " [1] Multi-tenant SaaS — manage platform, provision tenants on demand" + echo " [2] Single-tenant — one server instance, local auth, no identity provider" + echo "" + local deploy_choice + read -rp " Select mode [1]: " deploy_choice + case "${deploy_choice:-1}" in + 2) + DEPLOYMENT_MODE="standalone" + ;; + *) + DEPLOYMENT_MODE="saas" + ;; + esac + + echo "" + if [ "$DEPLOYMENT_MODE" = "saas" ]; then + prompt ADMIN_USER "Admin email" "${ADMIN_USER:-admin@${PUBLIC_HOST:-localhost}}" + # Validate email: must contain @ + if ! echo "$ADMIN_USER" | grep -q '@'; then + local original="$ADMIN_USER" + ADMIN_USER="${ADMIN_USER}@${PUBLIC_HOST:-localhost}" + log_info "Appended domain: '${original}' -> '${ADMIN_USER}'" + fi + else + prompt ADMIN_USER "Admin username" "${ADMIN_USER:-$DEFAULT_ADMIN_USER}" + fi if prompt_yesno "Auto-generate admin password?" "y"; then ADMIN_PASS="" @@ -459,22 +487,6 @@ run_simple_prompts() { prompt_password REGISTRY_TOKEN "Registry token/password" "${REGISTRY_TOKEN:-}" fi - echo "" - echo " Deployment mode:" - echo " [1] Multi-tenant SaaS — manage platform, provision tenants on demand" - echo " [2] Single-tenant — one server instance, local auth, no identity provider" - echo "" - local deploy_choice - read -rp " Select mode [1]: " deploy_choice - case "${deploy_choice:-1}" in - 2) - DEPLOYMENT_MODE="standalone" - ;; - *) - DEPLOYMENT_MODE="saas" - ;; - esac - } run_expert_prompts() { @@ -601,9 +613,7 @@ generate_passwords() { ADMIN_PASS=$(generate_password) log_info "Generated admin password." fi - if [ -z "$ADMIN_EMAIL" ]; then - ADMIN_EMAIL="${ADMIN_USER}@${PUBLIC_HOST:-localhost}" - fi + if [ -z "$POSTGRES_PASSWORD" ]; then POSTGRES_PASSWORD=$(generate_password) log_info "Generated PostgreSQL password." @@ -712,7 +722,6 @@ POSTGRES_DB=cameleer_saas # Admin user SAAS_ADMIN_USER=${ADMIN_USER} -SAAS_ADMIN_EMAIL=${ADMIN_EMAIL} # TLS NODE_TLS_REJECT=${NODE_TLS_REJECT} @@ -926,7 +935,6 @@ public_host=${PUBLIC_HOST} auth_host=${AUTH_HOST} public_protocol=${PUBLIC_PROTOCOL} admin_user=${ADMIN_USER} -admin_email=${ADMIN_EMAIL} tls_mode=${TLS_MODE} http_port=${HTTP_PORT} https_port=${HTTPS_PORT} @@ -983,7 +991,6 @@ EOF Admin Console: ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/platform/ Admin User: ${ADMIN_USER} Admin Password: ${ADMIN_PASS} -Admin Email: ${ADMIN_EMAIL} PostgreSQL: cameleer / ${POSTGRES_PASSWORD} ClickHouse: default / ${CLICKHOUSE_PASSWORD} @@ -1301,7 +1308,6 @@ print_credentials() { fi echo -e " Admin User: ${BOLD}${ADMIN_USER}${NC}" echo -e " Admin Password: ${BOLD}${ADMIN_PASS}${NC}" - echo -e " Admin Email: ${BOLD}${ADMIN_EMAIL}${NC}" echo "" echo -e " PostgreSQL: cameleer / ${POSTGRES_PASSWORD}" echo -e " ClickHouse: default / ${CLICKHOUSE_PASSWORD}" diff --git a/templates/.env.example b/templates/.env.example index f1de7ee..1a8e91d 100644 --- a/templates/.env.example +++ b/templates/.env.example @@ -50,11 +50,10 @@ CLICKHOUSE_PASSWORD=CHANGE_ME # ============================================================ # Admin credentials (SaaS mode) # ============================================================ -# Email is the primary user identity in SaaS mode. The admin email -# defaults to @ if not set explicitly. -SAAS_ADMIN_USER=admin +# In SaaS mode, this must be an email address (primary user identity). +# In standalone mode, any username is accepted. +SAAS_ADMIN_USER=admin@example.com SAAS_ADMIN_PASS=CHANGE_ME -# SAAS_ADMIN_EMAIL=admin@example.com # ============================================================ # Admin credentials (standalone mode) diff --git a/templates/docker-compose.saas.yml b/templates/docker-compose.saas.yml index bf590f8..4cbd6e5 100644 --- a/templates/docker-compose.saas.yml +++ b/templates/docker-compose.saas.yml @@ -27,7 +27,6 @@ services: PG_DB_SAAS: cameleer_saas SAAS_ADMIN_USER: ${SAAS_ADMIN_USER:-admin} SAAS_ADMIN_PASS: ${SAAS_ADMIN_PASS:?SAAS_ADMIN_PASS must be set in .env} - SAAS_ADMIN_EMAIL: ${SAAS_ADMIN_EMAIL:-} extra_hosts: # Logto validates M2M tokens by fetching its own JWKS from ENDPOINT. # Route the public hostname back to the Docker host (Traefik on :443)