Files
cameleer-saas/docs/superpowers/specs/2026-04-13-install-script-design.md
hsiegeln 0a06615ae2 Fix spec self-review issues in install script design
Resolve TBD placeholder (Docker minimum versions), clarify TLS cert
flow after traefik-certs init container merge, note Traefik env var
substitution for dynamic config, and document Docker socket path
differences between Linux and Windows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:38:59 +02:00

17 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 (uses Traefik env var substitution for dynamic values like ports), 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:

  1. Logto starts and seeds its own database (npm run cli db seed -- --swe)
  2. Entrypoint runs bootstrap logic (create apps, users, roles, scopes, branding)
  3. Bootstrap checks for cached results in a Docker volume — skips if already done
  4. Writes logto-bootstrap.json to shared volume
  5. If VENDOR_SEED_ENABLED=true, creates vendor user and global role
  6. 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 Engine
  • install.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 24+ (Linux) or Docker Desktop 4.25+ (Windows)
  • Docker Compose v2 (docker compose subcommand)
  • 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:

  1. Install directory (default: ./cameleer)
  2. Public hostname (auto-detected, default: localhost)
  3. Admin username (default: admin)
  4. Admin password (default: auto-generated)
  5. Use custom TLS certificates? (default: no → self-signed)
    • If yes: paths to cert.pem, key.pem, optional ca.pem
  6. 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_UNAUTHORIZED setting

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_network with Prometheus labels on services
  • Health checks on all services
  • depends_on with 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:

  1. Installation Summary — version, date, mode, install directory
  2. Service URLs — platform UI, Logto admin console, API endpoint
  3. First Steps — log in as admin, create first tenant
  4. Architecture Overview — containers running, purpose of each
  5. Networking — ports, monitoring network, Docker networks
  6. TLS — self-signed or custom, cert location, how to replace via vendor UI
  7. Data & Backups — Docker volume names, backup commands (pg_dump, clickhouse-backup)
  8. Upgrading — re-run installer with --version, what gets preserved
  9. Troubleshooting — common issues with docker compose logs commands
  10. 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

  1. Generated (or user-provided) during install
  2. Written to .env (consumed by Docker Compose)
  3. Written to credentials.txt in plain text
  4. Printed to terminal once at end of installation
  5. Never shown again — re-runs preserve existing credentials without displaying them

Monitoring Network Integration

When a monitoring network is configured (simple or expert mode):

  1. 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
  2. The network is added as an external network in the generated docker-compose.yml
  3. 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 --reinstall is explicitly chosen
  • --reinstall requires double opt-in: --reinstall --confirm-destroy
  • The script never runs docker volume rm without 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., "check docker 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.

TLS Certificate Flow (Simplified)

With the traefik-certs init container merged into cameleer-traefik, the certificate flow works as follows:

Shared certs Docker volume remains the mechanism for sharing TLS state between cameleer-traefik and cameleer-saas (which mounts it read-only for per-tenant server provisioning).

Self-signed mode (default):

  1. cameleer-traefik entrypoint checks if /certs/cert.pem exists in the volume
  2. If not, generates a self-signed cert for ${PUBLIC_HOST} with wildcard SAN using openssl
  3. Writes cert.pem, key.pem, meta.json to the certs volume
  4. Starts Traefik normally

Custom cert mode:

  1. The installer copies user-supplied cert files to ./cameleer/certs/ on the host
  2. The generated docker-compose.yml bind-mounts ./certs/:/user-certs:ro on the cameleer-traefik service
  3. cameleer-traefik entrypoint detects CERT_FILE=/user-certs/cert.pem and KEY_FILE=/user-certs/key.pem
  4. Validates and copies them to the shared certs Docker volume
  5. Writes meta.json with certificate metadata
  6. Starts Traefik normally

Runtime cert replacement (via vendor UI) continues to work unchanged — cameleer-saas writes to the certs volume's staged/ directory and performs atomic swaps.

Docker Socket Path

The generated docker-compose.yml uses the platform-appropriate Docker socket path:

  • Linux: /var/run/docker.sock:/var/run/docker.sock
  • Windows (Docker Desktop): //./pipe/docker_engine://./pipe/docker_engine

The installer detects the platform and generates the correct bind mount. The docker_socket config key allows overriding this in expert mode.