Compare commits

...

14 Commits

Author SHA1 Message Date
hsiegeln
531a17397b fix: validate admin email format in SaaS mode
Require user@domain.tld format (must contain @ and dot in domain).
Interactive mode loops until valid; silent mode exits with error.
Default changed from 'admin' to 'admin@<PUBLIC_HOST>' in SaaS mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 21:03:45 +02:00
hsiegeln
21ea9515a2 feat: unify admin identity — SAAS_ADMIN_USER is the email in SaaS mode
Move deployment mode question before admin credentials so the installer
can validate email format in SaaS mode. Remove separate SAAS_ADMIN_EMAIL
— the admin user value IS the email address. In standalone mode, any
username is still accepted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:45:25 +02:00
hsiegeln
0da26160c6 feat: add SAAS_ADMIN_EMAIL to both installers
Derive admin email from <ADMIN_USER>@<PUBLIC_HOST> by default.
Supports override via --admin-email CLI flag, SAAS_ADMIN_EMAIL env var,
or admin_email in cameleer.conf. Written to .env for bootstrap.
2026-04-25 20:26:38 +02:00
hsiegeln
b2259328d3 feat: enforce email as primary user identity in SaaS mode
Add SAAS_ADMIN_EMAIL env var (defaults to <user>@<host>). Pass to
bootstrap for admin user creation with primaryEmail. Update README
config reference and .env.example to document the email identity
requirement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:23:15 +02:00
8227483580 install.ps1 aktualisiert 2026-04-25 19:50:24 +02:00
hsiegeln
1ef0016965 docs: update README and .env.example for SMTP removal
SMTP configuration is now managed at runtime via the vendor admin UI.
Remove SMTP config reference table, CLI flags from silent install example,
and env vars from .env.example.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 18:16:12 +02:00
hsiegeln
ec1c1f92d7 docs: update installer CLAUDE.md to reflect SMTP removal 2026-04-25 18:09:50 +02:00
hsiegeln
4037fb9dfb feat: remove SMTP configuration from PowerShell installer 2026-04-25 18:08:48 +02:00
hsiegeln
35240e0374 feat: remove SMTP configuration from bash installer 2026-04-25 18:08:44 +02:00
hsiegeln
4885c240e3 feat: remove SMTP env vars from Logto compose template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 18:05:27 +02:00
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
hsiegeln
528c6d1980 feat: restore PowerShell installer lost during submodule migration
install.ps1 was deleted in the refactor that moved the installer to its
own repo but was never copied over. Restored from cameleer-saas history
with registry default updated to registry.cameleer.io.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 15:31:54 +02:00
8 changed files with 1656 additions and 140 deletions

View File

@@ -25,11 +25,9 @@ The installer uses static docker-compose templates in `templates/`. Templates ar
## SMTP configuration ## SMTP configuration
The installer prompts for SMTP settings in SaaS mode when the user opts in ("Configure SMTP for email verification?"). SMTP is required for self-service sign-up — without it, only admin-created users can sign in. SMTP / email connector configuration has been moved from the installer to the SaaS vendor admin UI (Email Connector page at `/vendor/email`). The installer no longer prompts for or persists SMTP settings.
Env vars: `SMTP_HOST`, `SMTP_PORT` (default 587), `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM_EMAIL` (default `noreply@<PUBLIC_HOST>`). Passed to the `cameleer-logto` container. The bootstrap script discovers the SMTP connector factory and creates the connector with Cameleer-branded email templates. Previously, SMTP env vars (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM_EMAIL`) were passed to the `cameleer-logto` container and configured via the bootstrap script. This one-shot approach was fragile — email delivery is now configured at runtime through the Logto Management API.
CLI args: `--smtp-host`, `--smtp-port`, `--smtp-user`, `--smtp-pass`, `--smtp-from-email`. Persisted in `cameleer.conf` for upgrades/reconfigure.
## Registry configuration ## Registry configuration
@@ -45,7 +43,6 @@ For private registries, pass `--registry-user` / `--registry-token`. The install
- `CAMELEER_SERVER_*` — server config (consumed by cameleer-server) - `CAMELEER_SERVER_*` — server config (consumed by cameleer-server)
- `CAMELEER_SAAS_*` — SaaS management plane config - `CAMELEER_SAAS_*` — SaaS management plane config
- `CAMELEER_SAAS_PROVISIONING_*` — "SaaS forwards this to provisioned tenant servers" - `CAMELEER_SAAS_PROVISIONING_*` — "SaaS forwards this to provisioned tenant servers"
- `SMTP_*` — email delivery config for Logto (consumed by bootstrap, SaaS mode only)
- No prefix (e.g. `POSTGRES_PASSWORD`, `PUBLIC_HOST`) — shared infrastructure, consumed by multiple components - No prefix (e.g. `POSTGRES_PASSWORD`, `PUBLIC_HOST`) — shared infrastructure, consumed by multiple components
## Development ## Development

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 |
@@ -78,9 +84,11 @@ Settings can be provided via CLI flags, environment variables, config file (`cam
| Setting | CLI Flag | Env Var | Config Key | Default | | 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@<PUBLIC_HOST>` (SaaS) |
| Admin password | `--admin-password` | `SAAS_ADMIN_PASS` | `admin_password` | auto-generated | | Admin password | `--admin-password` | `SAAS_ADMIN_PASS` | `admin_password` | auto-generated |
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 `@<PUBLIC_HOST>` if the `@` is missing. In standalone mode, any username is accepted.
In standalone mode, the env vars are `SERVER_ADMIN_USER` / `SERVER_ADMIN_PASS`. In standalone mode, the env vars are `SERVER_ADMIN_USER` / `SERVER_ADMIN_PASS`.
### TLS Certificates ### TLS Certificates
@@ -134,17 +142,11 @@ The Docker socket is required for tenant provisioning (SaaS mode) — the platfo
For private registries, provide credentials and the installer runs `docker login` before pulling. The registry prefix is applied to all container images. For private registries, provide credentials and the installer runs `docker login` before pulling. The registry prefix is applied to all container images.
### SMTP (SaaS Mode) ### Email / SMTP
| Setting | CLI Flag | Env Var | Config Key | Default | Email connector configuration (SMTP, SES, etc.) is managed at runtime via the vendor admin UI at `/vendor/email`. The installer does not configure email delivery.
|---------|----------|---------|------------|---------|
| SMTP host | `--smtp-host` | `SMTP_HOST` | `smtp_host` | — |
| SMTP port | `--smtp-port` | `SMTP_PORT` | `smtp_port` | `587` |
| SMTP username | `--smtp-user` | `SMTP_USER` | `smtp_user` | — |
| SMTP password | `--smtp-pass` | `SMTP_PASS` | `smtp_pass` | — |
| From email | `--smtp-from-email` | `SMTP_FROM_EMAIL` | `smtp_from_email` | `noreply@<PUBLIC_HOST>` |
SMTP is required for self-service sign-up (email verification codes). Without it, only admin-created users can sign in. Self-service registration is disabled by default and is enabled automatically when the admin configures an email connector.
### Monitoring ### Monitoring
@@ -216,9 +218,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) |
@@ -239,9 +242,6 @@ All services share a single hostname. Routing:
--tls-mode=custom \ --tls-mode=custom \
--cert-file=/etc/ssl/cert.pem \ --cert-file=/etc/ssl/cert.pem \
--key-file=/etc/ssl/key.pem \ --key-file=/etc/ssl/key.pem \
--smtp-host=smtp.example.com \
--smtp-user=noreply@example.com \
--smtp-pass=mailpass \
--registry=registry.example.com/cameleer \ --registry=registry.example.com/cameleer \
--registry-user=deploy \ --registry-user=deploy \
--registry-token=ghp_xxx --registry-token=ghp_xxx

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

1538
install.ps1 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -46,11 +46,6 @@ _ENV_COMPOSE_PROJECT="${COMPOSE_PROJECT:-}"
_ENV_DOCKER_SOCKET="${DOCKER_SOCKET:-}" _ENV_DOCKER_SOCKET="${DOCKER_SOCKET:-}"
_ENV_NODE_TLS_REJECT="${NODE_TLS_REJECT:-}" _ENV_NODE_TLS_REJECT="${NODE_TLS_REJECT:-}"
_ENV_DEPLOYMENT_MODE="${DEPLOYMENT_MODE:-}" _ENV_DEPLOYMENT_MODE="${DEPLOYMENT_MODE:-}"
_ENV_SMTP_HOST="${SMTP_HOST:-}"
_ENV_SMTP_PORT="${SMTP_PORT:-}"
_ENV_SMTP_USER="${SMTP_USER:-}"
_ENV_SMTP_PASS="${SMTP_PASS:-}"
_ENV_SMTP_FROM_EMAIL="${SMTP_FROM_EMAIL:-}"
_ENV_REGISTRY="${REGISTRY:-}" _ENV_REGISTRY="${REGISTRY:-}"
_ENV_REGISTRY_USER="${REGISTRY_USER:-}" _ENV_REGISTRY_USER="${REGISTRY_USER:-}"
_ENV_REGISTRY_TOKEN="${REGISTRY_TOKEN:-}" _ENV_REGISTRY_TOKEN="${REGISTRY_TOKEN:-}"
@@ -61,6 +56,7 @@ AUTH_HOST=""
PUBLIC_PROTOCOL="" PUBLIC_PROTOCOL=""
ADMIN_USER="" ADMIN_USER=""
ADMIN_PASS="" ADMIN_PASS=""
TLS_MODE="" TLS_MODE=""
CERT_FILE="" CERT_FILE=""
KEY_FILE="" KEY_FILE=""
@@ -77,11 +73,6 @@ COMPOSE_PROJECT=""
DOCKER_SOCKET="" DOCKER_SOCKET=""
NODE_TLS_REJECT="" NODE_TLS_REJECT=""
DEPLOYMENT_MODE="" DEPLOYMENT_MODE=""
SMTP_HOST=""
SMTP_PORT=""
SMTP_USER=""
SMTP_PASS=""
SMTP_FROM_EMAIL=""
REGISTRY="" REGISTRY=""
REGISTRY_USER="" REGISTRY_USER=""
REGISTRY_TOKEN="" REGISTRY_TOKEN=""
@@ -156,6 +147,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() {
@@ -170,6 +169,7 @@ parse_args() {
--public-protocol) PUBLIC_PROTOCOL="$2"; shift ;; --public-protocol) PUBLIC_PROTOCOL="$2"; shift ;;
--admin-user) ADMIN_USER="$2"; shift ;; --admin-user) ADMIN_USER="$2"; shift ;;
--admin-password) ADMIN_PASS="$2"; shift ;; --admin-password) ADMIN_PASS="$2"; shift ;;
--tls-mode) TLS_MODE="$2"; shift ;; --tls-mode) TLS_MODE="$2"; shift ;;
--cert-file) CERT_FILE="$2"; shift ;; --cert-file) CERT_FILE="$2"; shift ;;
--key-file) KEY_FILE="$2"; shift ;; --key-file) KEY_FILE="$2"; shift ;;
@@ -186,11 +186,6 @@ parse_args() {
--docker-socket) DOCKER_SOCKET="$2"; shift ;; --docker-socket) DOCKER_SOCKET="$2"; shift ;;
--node-tls-reject) NODE_TLS_REJECT="$2"; shift ;; --node-tls-reject) NODE_TLS_REJECT="$2"; shift ;;
--deployment-mode) DEPLOYMENT_MODE="$2"; shift ;; --deployment-mode) DEPLOYMENT_MODE="$2"; shift ;;
--smtp-host) SMTP_HOST="$2"; shift ;;
--smtp-port) SMTP_PORT="$2"; shift ;;
--smtp-user) SMTP_USER="$2"; shift ;;
--smtp-pass) SMTP_PASS="$2"; shift ;;
--smtp-from-email) SMTP_FROM_EMAIL="$2"; shift ;;
--registry) REGISTRY="$2"; shift ;; --registry) REGISTRY="$2"; shift ;;
--registry-user) REGISTRY_USER="$2"; shift ;; --registry-user) REGISTRY_USER="$2"; shift ;;
--registry-token) REGISTRY_TOKEN="$2"; shift ;; --registry-token) REGISTRY_TOKEN="$2"; shift ;;
@@ -259,8 +254,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" ;;
@@ -268,6 +264,7 @@ load_config_file() {
public_protocol) [ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$value" ;; public_protocol) [ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$value" ;;
admin_user) [ -z "$ADMIN_USER" ] && ADMIN_USER="$value" ;; admin_user) [ -z "$ADMIN_USER" ] && ADMIN_USER="$value" ;;
admin_password) [ -z "$ADMIN_PASS" ] && ADMIN_PASS="$value" ;; admin_password) [ -z "$ADMIN_PASS" ] && ADMIN_PASS="$value" ;;
tls_mode) [ -z "$TLS_MODE" ] && TLS_MODE="$value" ;; tls_mode) [ -z "$TLS_MODE" ] && TLS_MODE="$value" ;;
cert_file) [ -z "$CERT_FILE" ] && CERT_FILE="$value" ;; cert_file) [ -z "$CERT_FILE" ] && CERT_FILE="$value" ;;
key_file) [ -z "$KEY_FILE" ] && KEY_FILE="$value" ;; key_file) [ -z "$KEY_FILE" ] && KEY_FILE="$value" ;;
@@ -284,11 +281,6 @@ load_config_file() {
docker_socket) [ -z "$DOCKER_SOCKET" ] && DOCKER_SOCKET="$value" ;; docker_socket) [ -z "$DOCKER_SOCKET" ] && DOCKER_SOCKET="$value" ;;
node_tls_reject) [ -z "$NODE_TLS_REJECT" ] && NODE_TLS_REJECT="$value" ;; node_tls_reject) [ -z "$NODE_TLS_REJECT" ] && NODE_TLS_REJECT="$value" ;;
deployment_mode) [ -z "$DEPLOYMENT_MODE" ] && DEPLOYMENT_MODE="$value" ;; deployment_mode) [ -z "$DEPLOYMENT_MODE" ] && DEPLOYMENT_MODE="$value" ;;
smtp_host) [ -z "$SMTP_HOST" ] && SMTP_HOST="$value" ;;
smtp_port) [ -z "$SMTP_PORT" ] && SMTP_PORT="$value" ;;
smtp_user) [ -z "$SMTP_USER" ] && SMTP_USER="$value" ;;
smtp_pass) [ -z "$SMTP_PASS" ] && SMTP_PASS="$value" ;;
smtp_from_email) [ -z "$SMTP_FROM_EMAIL" ] && SMTP_FROM_EMAIL="$value" ;;
registry) [ -z "$REGISTRY" ] && REGISTRY="$value" ;; registry) [ -z "$REGISTRY" ] && REGISTRY="$value" ;;
registry_user) [ -z "$REGISTRY_USER" ] && REGISTRY_USER="$value" ;; registry_user) [ -z "$REGISTRY_USER" ] && REGISTRY_USER="$value" ;;
registry_token) [ -z "$REGISTRY_TOKEN" ] && REGISTRY_TOKEN="$value" ;; registry_token) [ -z "$REGISTRY_TOKEN" ] && REGISTRY_TOKEN="$value" ;;
@@ -303,6 +295,7 @@ load_env_overrides() {
[ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$_ENV_PUBLIC_PROTOCOL" [ -z "$PUBLIC_PROTOCOL" ] && PUBLIC_PROTOCOL="$_ENV_PUBLIC_PROTOCOL"
[ -z "$ADMIN_USER" ] && ADMIN_USER="${SAAS_ADMIN_USER:-}" [ -z "$ADMIN_USER" ] && ADMIN_USER="${SAAS_ADMIN_USER:-}"
[ -z "$ADMIN_PASS" ] && ADMIN_PASS="${SAAS_ADMIN_PASS:-}" [ -z "$ADMIN_PASS" ] && ADMIN_PASS="${SAAS_ADMIN_PASS:-}"
[ -z "$TLS_MODE" ] && TLS_MODE="$_ENV_TLS_MODE" [ -z "$TLS_MODE" ] && TLS_MODE="$_ENV_TLS_MODE"
[ -z "$CERT_FILE" ] && CERT_FILE="$_ENV_CERT_FILE" [ -z "$CERT_FILE" ] && CERT_FILE="$_ENV_CERT_FILE"
[ -z "$KEY_FILE" ] && KEY_FILE="$_ENV_KEY_FILE" [ -z "$KEY_FILE" ] && KEY_FILE="$_ENV_KEY_FILE"
@@ -319,11 +312,6 @@ load_env_overrides() {
[ -z "$DOCKER_SOCKET" ] && DOCKER_SOCKET="$_ENV_DOCKER_SOCKET" [ -z "$DOCKER_SOCKET" ] && DOCKER_SOCKET="$_ENV_DOCKER_SOCKET"
[ -z "$NODE_TLS_REJECT" ] && NODE_TLS_REJECT="$_ENV_NODE_TLS_REJECT" [ -z "$NODE_TLS_REJECT" ] && NODE_TLS_REJECT="$_ENV_NODE_TLS_REJECT"
[ -z "$DEPLOYMENT_MODE" ] && DEPLOYMENT_MODE="$_ENV_DEPLOYMENT_MODE" [ -z "$DEPLOYMENT_MODE" ] && DEPLOYMENT_MODE="$_ENV_DEPLOYMENT_MODE"
[ -z "$SMTP_HOST" ] && SMTP_HOST="$_ENV_SMTP_HOST"
[ -z "$SMTP_PORT" ] && SMTP_PORT="$_ENV_SMTP_PORT"
[ -z "$SMTP_USER" ] && SMTP_USER="$_ENV_SMTP_USER"
[ -z "$SMTP_PASS" ] && SMTP_PASS="$_ENV_SMTP_PASS"
[ -z "$SMTP_FROM_EMAIL" ] && SMTP_FROM_EMAIL="$_ENV_SMTP_FROM_EMAIL"
[ -z "$REGISTRY" ] && REGISTRY="$_ENV_REGISTRY" [ -z "$REGISTRY" ] && REGISTRY="$_ENV_REGISTRY"
[ -z "$REGISTRY_USER" ] && REGISTRY_USER="$_ENV_REGISTRY_USER" [ -z "$REGISTRY_USER" ] && REGISTRY_USER="$_ENV_REGISTRY_USER"
[ -z "$REGISTRY_TOKEN" ] && REGISTRY_TOKEN="$_ENV_REGISTRY_TOKEN" [ -z "$REGISTRY_TOKEN" ] && REGISTRY_TOKEN="$_ENV_REGISTRY_TOKEN"
@@ -441,7 +429,37 @@ run_simple_prompts() {
prompt INSTALL_DIR "Install directory" "${INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" prompt INSTALL_DIR "Install directory" "${INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
prompt PUBLIC_HOST "Public hostname" "${PUBLIC_HOST:-localhost}" 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
while true; do
prompt ADMIN_USER "Admin email" "${ADMIN_USER:-admin@${PUBLIC_HOST:-localhost}}"
# Validate email: must be user@domain.tld format
if echo "$ADMIN_USER" | grep -qE '^[^@]+@[^@]+\.[^@]+$'; then
break
fi
echo -e " ${RED}Invalid email address.${NC} Must be a valid email (e.g. admin@company.com)."
ADMIN_USER=""
done
else
prompt ADMIN_USER "Admin username" "${ADMIN_USER:-$DEFAULT_ADMIN_USER}"
fi
if prompt_yesno "Auto-generate admin password?" "y"; then if prompt_yesno "Auto-generate admin password?" "y"; then
ADMIN_PASS="" ADMIN_PASS=""
@@ -471,33 +489,6 @@ run_simple_prompts() {
prompt_password REGISTRY_TOKEN "Registry token/password" "${REGISTRY_TOKEN:-}" prompt_password REGISTRY_TOKEN "Registry token/password" "${REGISTRY_TOKEN:-}"
fi 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
# SMTP for email verification (SaaS mode only)
if [ "$DEPLOYMENT_MODE" = "saas" ]; then
echo ""
if prompt_yesno "Configure SMTP for email verification? (required for self-service sign-up)"; then
prompt SMTP_HOST "SMTP host" "${SMTP_HOST:-}"
prompt SMTP_PORT "SMTP port" "${SMTP_PORT:-587}"
prompt SMTP_USER "SMTP username" "${SMTP_USER:-}"
prompt_password SMTP_PASS "SMTP password" "${SMTP_PASS:-}"
prompt SMTP_FROM_EMAIL "From email address" "${SMTP_FROM_EMAIL:-noreply@${PUBLIC_HOST}}"
fi
fi
} }
run_expert_prompts() { run_expert_prompts() {
@@ -549,7 +540,16 @@ merge_config() {
: "${INSTALL_DIR:=$DEFAULT_INSTALL_DIR}" : "${INSTALL_DIR:=$DEFAULT_INSTALL_DIR}"
: "${PUBLIC_HOST:=localhost}" : "${PUBLIC_HOST:=localhost}"
: "${PUBLIC_PROTOCOL:=$DEFAULT_PUBLIC_PROTOCOL}" : "${PUBLIC_PROTOCOL:=$DEFAULT_PUBLIC_PROTOCOL}"
: "${ADMIN_USER:=$DEFAULT_ADMIN_USER}" if [ "$DEPLOYMENT_MODE" = "saas" ]; then
: "${ADMIN_USER:=admin@${PUBLIC_HOST}}"
# Validate email format in SaaS mode
if ! echo "$ADMIN_USER" | grep -qE '^[^@]+@[^@]+\.[^@]+$'; then
echo -e "${RED}ERROR:${NC} SAAS_ADMIN_USER must be a valid email address (e.g. admin@company.com), got: '$ADMIN_USER'" >&2
exit 1
fi
else
: "${ADMIN_USER:=$DEFAULT_ADMIN_USER}"
fi
: "${TLS_MODE:=$DEFAULT_TLS_MODE}" : "${TLS_MODE:=$DEFAULT_TLS_MODE}"
: "${HTTP_PORT:=$DEFAULT_HTTP_PORT}" : "${HTTP_PORT:=$DEFAULT_HTTP_PORT}"
: "${HTTPS_PORT:=$DEFAULT_HTTPS_PORT}" : "${HTTPS_PORT:=$DEFAULT_HTTPS_PORT}"
@@ -624,6 +624,7 @@ generate_passwords() {
ADMIN_PASS=$(generate_password) ADMIN_PASS=$(generate_password)
log_info "Generated admin password." log_info "Generated admin password."
fi fi
if [ -z "$POSTGRES_PASSWORD" ]; then if [ -z "$POSTGRES_PASSWORD" ]; then
POSTGRES_PASSWORD=$(generate_password) POSTGRES_PASSWORD=$(generate_password)
log_info "Generated PostgreSQL password." log_info "Generated PostgreSQL password."
@@ -663,15 +664,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 +690,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 +729,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}
@@ -774,17 +769,15 @@ CAMELEER_SAAS_PROVISIONING_RUNTIMEBASEIMAGE=${REGISTRY}/cameleer-runtime-base:${
# JWT signing secret (forwarded to provisioned tenant servers, must be non-empty) # JWT signing secret (forwarded to provisioned tenant servers, must be non-empty)
CAMELEER_SERVER_SECURITY_JWTSECRET=$(generate_password) CAMELEER_SERVER_SECURITY_JWTSECRET=$(generate_password)
# SMTP (for email verification during registration)
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 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"
if [ -n "$MONITORING_NETWORK" ]; then if [ -n "$MONITORING_NETWORK" ]; then
echo "" >> "$f" echo "" >> "$f"
echo "# Monitoring" >> "$f" echo "# Monitoring" >> "$f"
@@ -964,15 +957,11 @@ compose_project=${COMPOSE_PROJECT}
docker_socket=${DOCKER_SOCKET} docker_socket=${DOCKER_SOCKET}
node_tls_reject=${NODE_TLS_REJECT} node_tls_reject=${NODE_TLS_REJECT}
deployment_mode=${DEPLOYMENT_MODE} 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=${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" registry_token "$REGISTRY_TOKEN"
log_info "Saved installer config to cameleer.conf" log_info "Saved installer config to cameleer.conf"
} }

View File

@@ -50,7 +50,9 @@ CLICKHOUSE_PASSWORD=CHANGE_ME
# ============================================================ # ============================================================
# Admin credentials (SaaS mode) # Admin credentials (SaaS mode)
# ============================================================ # ============================================================
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_PASS=CHANGE_ME
# ============================================================ # ============================================================
@@ -61,14 +63,10 @@ SAAS_ADMIN_PASS=CHANGE_ME
# BOOTSTRAP_TOKEN=CHANGE_ME # BOOTSTRAP_TOKEN=CHANGE_ME
# ============================================================ # ============================================================
# SMTP (for email verification during registration) # Email / SMTP
# ============================================================ # ============================================================
# Required for self-service sign-up. Without SMTP, only admin-created users can sign in. # Email connector configuration is managed at runtime via the vendor
SMTP_HOST= # admin UI (Email Connector page at /vendor/email). No SMTP env vars needed.
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_FROM_EMAIL=noreply@cameleer.io
# ============================================================ # ============================================================
# TLS # TLS

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
@@ -26,12 +27,6 @@ services:
PG_DB_SAAS: cameleer_saas PG_DB_SAAS: cameleer_saas
SAAS_ADMIN_USER: ${SAAS_ADMIN_USER:-admin} SAAS_ADMIN_USER: ${SAAS_ADMIN_USER:-admin}
SAAS_ADMIN_PASS: ${SAAS_ADMIN_PASS:?SAAS_ADMIN_PASS must be set in .env} SAAS_ADMIN_PASS: ${SAAS_ADMIN_PASS:?SAAS_ADMIN_PASS must be set in .env}
# SMTP (for email verification during registration)
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASS: ${SMTP_PASS:-}
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-noreply@cameleer.io}
extra_hosts: extra_hosts:
# Logto validates M2M tokens by fetching its own JWKS from ENDPOINT. # Logto validates M2M tokens by fetching its own JWKS from ENDPOINT.
# Route the public hostname back to the Docker host (Traefik on :443) # Route the public hostname back to the Docker host (Traefik on :443)