Add install script design spec
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>
This commit is contained in:
413
docs/superpowers/specs/2026-04-13-install-script-design.md
Normal file
413
docs/superpowers/specs/2026-04-13-install-script-design.md
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
# 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:
|
||||||
|
|
||||||
|
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 (Linux) or Docker Desktop (Windows) — minimum version TBD
|
||||||
|
- 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`)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user