Defines a professional installer for the Cameleer SaaS platform with dual native scripts (bash + PowerShell), three installation modes (simple/expert/silent), and a platform simplification that consolidates 7 services into 5 by baking all init logic into Docker images. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
16 KiB
Cameleer SaaS Install Script Design
Overview
A professional installer for the Cameleer SaaS platform, distributed as two native scripts (install.sh for Linux, install.ps1 for Windows). The installer downloads nothing — it embeds compose templates and generates all configuration from user input. All service initialization logic is baked into Docker images, configured via environment variables.
Distribution model: curl -sfL https://install.cameleer.io | bash (Linux), irm https://install.cameleer.io/windows | iex (Windows).
Platform Simplification (Prerequisites)
The current architecture uses 7 services with 10+ bind-mounted config files. This design consolidates everything into 5 services with zero bind mounts (except Docker socket and optional user-supplied TLS certs).
Image Consolidation
| Image | Base | Bakes in |
|---|---|---|
cameleer-traefik |
traefik:v3 |
Static/dynamic Traefik config, cert generation entrypoint (openssl), self-signed cert logic |
cameleer-postgres |
postgres:16-alpine |
init-databases.sh (creates cameleer_saas, logto databases) |
cameleer-clickhouse |
clickhouse/clickhouse-server |
Init SQL (CREATE DATABASE cameleer), clickhouse-users.xml, clickhouse-config.xml (Prometheus metrics) |
cameleer-logto |
ghcr.io/logto-io/logto |
Custom sign-in UI, bootstrap logic (app/user/role/scope creation), vendor seed (env-var gated). Replaces the separate logto-bootstrap init container. |
cameleer-saas |
eclipse-temurin:21-jre-alpine |
Spring Boot app + React SPA (already exists, no changes) |
All images published to gitea.siegeln.net/cameleer/.
Service Reduction
| Before | After |
|---|---|
| traefik-certs (init container) | Merged into cameleer-traefik entrypoint |
| traefik | cameleer-traefik |
| postgres + bind-mounted init script | cameleer-postgres |
| clickhouse + 3 bind-mounted config files | cameleer-clickhouse |
| logto | cameleer-logto (with bootstrap) |
| logto-bootstrap (init container) | Merged into cameleer-logto entrypoint |
| cameleer-saas + bind-mounted UI | cameleer-saas |
Result: 7 services → 5 services. 10+ bind-mounted files → 0.
Bootstrap Merge
The logto-bootstrap init container logic moves into cameleer-logto's entrypoint as an idempotent startup step:
- Logto starts and seeds its own database (
npm run cli db seed -- --swe) - Entrypoint runs bootstrap logic (create apps, users, roles, scopes, branding)
- Bootstrap checks for cached results in a Docker volume — skips if already done
- Writes
logto-bootstrap.jsonto shared volume - If
VENDOR_SEED_ENABLED=true, creates vendor user and global role - Logto server starts normally
The cameleer-saas service uses depends_on: logto (healthy) and reads bootstrap results from the shared volume on startup — same as today.
Installer Architecture
Distribution
- Linux:
curl -sfL https://install.cameleer.io | bash - Windows:
irm https://install.cameleer.io/windows | iex
The scripts are self-contained. They embed docker-compose templates and generate all files locally. No secondary downloads.
Scripts
install.sh— Bash, targets Linux with Docker Engineinstall.ps1— PowerShell, targets Windows with Docker Desktop (WSL2 backend)
Both implement identical logic and produce identical output. They share a config file format (cameleer.conf) so configurations are portable between platforms.
Prerequisites
The installer checks (does not install) these prerequisites:
- Docker Engine (Linux) or Docker Desktop (Windows) — minimum version TBD
- Docker Compose v2 (
docker composesubcommand) openssl(Linux, for password generation) — PowerShell uses[System.Security.Cryptography.RandomNumberGenerator]- Ports 80, 443, 3002 are free (or custom ports if specified)
- Docker socket accessible
If any prerequisite is missing, the script prints a clear error message with a link to installation instructions and exits.
Installation Modes
Simple Mode (default)
Asks 6 essential questions:
- Install directory (default:
./cameleer) - Public hostname (auto-detected, default:
localhost) - Admin username (default:
admin) - Admin password (default: auto-generated)
- Use custom TLS certificates? (default: no → self-signed)
- If yes: paths to cert.pem, key.pem, optional ca.pem
- Connect to a monitoring network? (default: none)
Everything else uses secure defaults. All passwords auto-generated.
Expert Mode (--expert or chosen at interactive prompt)
Adds these options, grouped by category:
Credentials:
- PostgreSQL password (default: generated)
- ClickHouse password (default: generated)
- Vendor account enable + username + password
Networking:
- HTTP port (default: 80)
- HTTPS port (default: 443)
- Logto admin console port (default: 3002)
Docker:
- Image version/tag (default:
latest) - Compose project name (default:
cameleer-saas) - Docker socket path (auto-detected)
TLS:
- CA bundle path
NODE_TLS_REJECT_UNAUTHORIZEDsetting
Logto:
- Admin console external exposure (default: yes)
Silent Mode (--silent)
No interactive prompts. Uses defaults plus overrides.
Config precedence: CLI flags > environment variables > config file (--config) > defaults.
Configuration Reference
| Config key | CLI flag | Env var | Default | Simple | Expert |
|---|---|---|---|---|---|
install_dir |
--install-dir |
CAMELEER_INSTALL_DIR |
./cameleer |
yes | yes |
public_host |
--public-host |
PUBLIC_HOST |
auto-detect | yes | yes |
public_protocol |
--public-protocol |
PUBLIC_PROTOCOL |
https |
no | yes |
admin_user |
--admin-user |
SAAS_ADMIN_USER |
admin |
yes | yes |
admin_password |
--admin-password |
SAAS_ADMIN_PASS |
generated | yes | yes |
tls_mode |
--tls-mode |
TLS_MODE |
self-signed |
yes | yes |
cert_file |
--cert-file |
CERT_FILE |
none | yes* | yes |
key_file |
--key-file |
KEY_FILE |
none | yes* | yes |
ca_file |
--ca-file |
CA_FILE |
none | no | yes |
monitoring_network |
--monitoring-network |
MONITORING_NETWORK |
none | yes | yes |
postgres_password |
--postgres-password |
POSTGRES_PASSWORD |
generated | no | yes |
clickhouse_password |
--clickhouse-password |
CLICKHOUSE_PASSWORD |
generated | no | yes |
http_port |
--http-port |
HTTP_PORT |
80 |
no | yes |
https_port |
--https-port |
HTTPS_PORT |
443 |
no | yes |
logto_console_port |
--logto-console-port |
LOGTO_CONSOLE_PORT |
3002 |
no | yes |
logto_console_exposed |
--logto-console-exposed |
LOGTO_CONSOLE_EXPOSED |
true |
no | yes |
vendor_enabled |
--vendor-enabled |
VENDOR_ENABLED |
false |
no | yes |
vendor_user |
--vendor-user |
VENDOR_USER |
vendor |
no | yes |
vendor_password |
--vendor-password |
VENDOR_PASS |
generated | no | yes |
version |
--version |
CAMELEER_VERSION |
latest |
no | yes |
compose_project |
--compose-project |
COMPOSE_PROJECT |
cameleer-saas |
no | yes |
docker_socket |
--docker-socket |
DOCKER_SOCKET |
auto-detect | no | yes |
node_tls_reject |
--node-tls-reject |
NODE_TLS_REJECT |
0 (self-signed) / 1 (custom) |
no | yes |
* Only asked in simple mode if the user chooses custom TLS.
Config File Format (cameleer.conf)
# Cameleer installation config
# Generated by installer v1.0.0 on 2026-04-13
install_dir=./cameleer
public_host=cameleer.example.com
public_protocol=https
admin_user=my-admin
version=1.0.0
tls_mode=custom
https_port=443
monitoring_network=prometheus
Plain key=value, # comments. Portable between Linux and Windows.
Auto-Detection
The installer auto-detects sensible defaults:
| Value | Linux | Windows |
|---|---|---|
| Public hostname | hostname -f, reverse DNS of primary IP, fallback localhost |
[System.Net.Dns]::GetHostEntry, fallback localhost |
| Docker socket | /var/run/docker.sock |
//./pipe/docker_engine |
| Port availability | ss -tlnp or netstat check on 80, 443, 3002 |
Test-NetConnection on 80, 443, 3002 |
| Existing install | Check for cameleer.conf in install directory |
Same |
Output Files
The installer generates the following in the install directory:
./cameleer/
docker-compose.yml # Generated from embedded template
.env # All service configuration
.env.bak # Snapshot of .env at install time
cameleer.conf # Installer config (for re-runs, cloning)
credentials.txt # All generated passwords in plain text
INSTALL.md # Tailored documentation
certs/ # Only if user supplies custom TLS certs
cert.pem
key.pem
ca.pem
docker-compose.yml (generated)
The compose file is generated from a template embedded in the script, with values substituted from the user's configuration. Key characteristics:
- All services use
${VARIABLE}references to.env - No bind mounts except Docker socket and optional
certs/directory - Shared volumes:
pgdata,chdata,bootstrapdata,certs - Networks:
cameleer(internal),cameleer-traefik(for dynamic tenant routing) - Optional external
monitoring_networkwith Prometheus labels on services - Health checks on all services
depends_onwith health conditions for startup ordering
credentials.txt
===========================================
CAMELEER PLATFORM CREDENTIALS
Generated: 2026-04-13 14:32:00 UTC
SECURE THIS FILE AND DELETE AFTER NOTING
THESE CREDENTIALS CANNOT BE RECOVERED
===========================================
Admin Console: https://cameleer.example.com/platform/
Admin User: my-admin
Admin Password: aB3x...generated...9Zq
PostgreSQL: cameleer / Kx8m...generated...Wp2
ClickHouse: default / Rm4n...generated...Ht7
Vendor User: acme-admin (not enabled)
Logto Console: https://cameleer.example.com:3002
Printed to terminal once at the end of installation. Never displayed again on re-runs.
INSTALL.md (generated)
Tailored to the actual installation values. Sections:
- Installation Summary — version, date, mode, install directory
- Service URLs — platform UI, Logto admin console, API endpoint
- First Steps — log in as admin, create first tenant
- Architecture Overview — containers running, purpose of each
- Networking — ports, monitoring network, Docker networks
- TLS — self-signed or custom, cert location, how to replace via vendor UI
- Data & Backups — Docker volume names, backup commands (pg_dump, clickhouse-backup)
- Upgrading — re-run installer with
--version, what gets preserved - Troubleshooting — common issues with
docker compose logscommands - Uninstalling — clean removal steps
Password Generation
When no password is provided, the script generates cryptographically secure random passwords:
- Linux:
openssl rand -base64 24(32 characters) - Windows:
[System.Security.Cryptography.RandomNumberGenerator]→ Base64
Passwords Generated
| Credential | Config key | Consumers |
|---|---|---|
| PostgreSQL password | postgres_password |
postgres, logto, cameleer-saas |
| ClickHouse password | clickhouse_password |
clickhouse, cameleer-saas (tenant provisioning) |
| Admin password | admin_password |
Logto admin user |
| Vendor password | vendor_password |
Logto vendor user (only if enabled) |
Credential Lifecycle
- Generated (or user-provided) during install
- Written to
.env(consumed by Docker Compose) - Written to
credentials.txtin plain text - Printed to terminal once at end of installation
- Never shown again — re-runs preserve existing credentials without displaying them
Monitoring Network Integration
When a monitoring network is configured (simple or expert mode):
- The script verifies the network exists via
docker network inspect- If missing in interactive mode: asks whether to create it or skip
- If missing in silent mode: creates it automatically
- The network is added as an external network in the generated
docker-compose.yml - Services are attached to it and labeled for Prometheus Docker SD:
cameleer-saas:
labels:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/platform/actuator/prometheus"
cameleer-traefik:
labels:
prometheus.io/scrape: "true"
prometheus.io/port: "8082"
prometheus.io/path: "/metrics"
cameleer-clickhouse:
labels:
prometheus.io/scrape: "true"
prometheus.io/port: "9363"
prometheus.io/path: "/metrics"
No Prometheus configuration needed on the customer's side — Docker service discovery picks up the labels automatically.
Idempotent Re-run & Upgrade
Detection
The script checks for cameleer.conf in the install directory. If found, it's a re-run.
Interactive Re-run Menu
Existing Cameleer installation detected (v1.0.0)
Install directory: ./cameleer
Public host: cameleer.example.com
[1] Upgrade to v1.1.0 (pull new images, update compose)
[2] Reconfigure (re-run interactive setup, preserve data)
[3] Reinstall (fresh install, WARNING: destroys data volumes)
[4] Cancel
Re-run Behavior
| Action | Preserve | Regenerate | Pull images |
|---|---|---|---|
| Upgrade | .env, cameleer.conf, credentials.txt, certs/, volumes |
docker-compose.yml, INSTALL.md |
yes (new version) |
| Reconfigure | Data volumes, credentials.txt (unless passwords changed) |
.env, docker-compose.yml, cameleer.conf, INSTALL.md |
optional |
| Reinstall | Nothing | Everything | yes |
Silent Re-run
Defaults to upgrade. Override with --reconfigure or --reinstall.
Safety
- Data volumes (
pgdata,chdata,bootstrapdata) are never removed unless--reinstallis explicitly chosen --reinstallrequires double opt-in:--reinstall --confirm-destroy- The script never runs
docker volume rmwithout this confirmation
Health Verification
After docker compose up -d, the script polls services in dependency order:
| Step | Service | Check | Timeout |
|---|---|---|---|
| 1 | PostgreSQL | pg_isready via docker compose exec |
120s |
| 2 | ClickHouse | clickhouse-client query via docker compose exec |
120s |
| 3 | Logto | GET /oidc/.well-known/openid-configuration |
120s |
| 4 | Bootstrap | Check logto-bootstrap.json exists in volume |
120s |
| 5 | Cameleer SaaS | GET /platform/api/config |
120s |
| 6 | Traefik | GET https://{PUBLIC_HOST}/ (expect redirect) |
120s |
Polling interval: 5 seconds. Total timeout: 5 minutes.
Output
Verifying installation...
[ok] PostgreSQL ready (3s)
[ok] ClickHouse ready (5s)
[ok] Logto ready (18s)
[ok] Bootstrap complete (0s)
[ok] Cameleer SaaS ready (8s)
[ok] Traefik routing ready (1s)
Installation complete!
Failure
- Failing service marked with
[FAIL]and a hint (e.g., "checkdocker compose logs logto") - Remaining checks skipped
- Stack left running for inspection
- Script exits with code 1
Script Structure (both platforms)
main()
parse_args()
detect_existing_install()
if existing → show_rerun_menu()
check_prerequisites()
auto_detect_defaults()
select_mode() # simple / expert / silent
if interactive → run_prompts()
merge_config() # CLI > env > config file > defaults
validate_config()
generate_passwords() # for any not provided
if custom_certs → copy_certs()
generate_env_file()
generate_compose_file()
write_config_file() # cameleer.conf
docker_compose_pull()
docker_compose_up()
verify_health()
generate_credentials_file()
generate_install_doc()
print_credentials()
print_summary()
Each function has a direct equivalent in both bash and PowerShell. The logic, prompts, and output are identical across platforms.