From bdb24f8de6e71354a85cc27c6b0d604eeb157560 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:58:54 +0200 Subject: [PATCH 1/9] feat(installer): add infra base docker-compose template Shared infrastructure base (traefik, postgres, clickhouse) always loaded regardless of deployment mode. Uses parameterized images, fail-if-unset password variables, and a noop monitoring network bridge. Co-Authored-By: Claude Opus 4.6 (1M context) --- installer/templates/docker-compose.yml | 79 ++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 installer/templates/docker-compose.yml diff --git a/installer/templates/docker-compose.yml b/installer/templates/docker-compose.yml new file mode 100644 index 0000000..99a93b4 --- /dev/null +++ b/installer/templates/docker-compose.yml @@ -0,0 +1,79 @@ +# Cameleer Infrastructure +# Shared base — always loaded. Mode-specific services in separate compose files. + +services: + cameleer-traefik: + image: ${TRAEFIK_IMAGE:-gitea.siegeln.net/cameleer/cameleer-traefik}:${VERSION:-latest} + restart: unless-stopped + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + - "${LOGTO_CONSOLE_BIND:-127.0.0.1}:${LOGTO_CONSOLE_PORT:-3002}:3002" + environment: + PUBLIC_HOST: ${PUBLIC_HOST:-localhost} + CERT_FILE: ${CERT_FILE:-} + KEY_FILE: ${KEY_FILE:-} + CA_FILE: ${CA_FILE:-} + volumes: + - cameleer-certs:/certs + - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro + labels: + - "prometheus.io/scrape=true" + - "prometheus.io/port=8082" + - "prometheus.io/path=/metrics" + networks: + - cameleer + - cameleer-traefik + - monitoring + + cameleer-postgres: + image: ${POSTGRES_IMAGE:-gitea.siegeln.net/cameleer/cameleer-postgres}:${VERSION:-latest} + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-cameleer_saas} + POSTGRES_USER: ${POSTGRES_USER:-cameleer} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env} + volumes: + - cameleer-pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d $${POSTGRES_DB:-cameleer_saas}"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - cameleer + - monitoring + + cameleer-clickhouse: + image: ${CLICKHOUSE_IMAGE:-gitea.siegeln.net/cameleer/cameleer-clickhouse}:${VERSION:-latest} + restart: unless-stopped + environment: + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:?CLICKHOUSE_PASSWORD must be set in .env} + volumes: + - cameleer-chdata:/var/lib/clickhouse + healthcheck: + test: ["CMD-SHELL", "clickhouse-client --password $${CLICKHOUSE_PASSWORD} --query 'SELECT 1'"] + interval: 10s + timeout: 5s + retries: 3 + labels: + - "prometheus.io/scrape=true" + - "prometheus.io/port=9363" + - "prometheus.io/path=/metrics" + networks: + - cameleer + - monitoring + +volumes: + cameleer-pgdata: + cameleer-chdata: + cameleer-certs: + +networks: + cameleer: + driver: bridge + cameleer-traefik: + name: cameleer-traefik + driver: bridge + monitoring: + name: cameleer-monitoring-noop From 3c343f94417eaa63a8b576cfbdc9e7c2b9d1609d Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:59:00 +0200 Subject: [PATCH 2/9] feat(installer): add SaaS docker-compose template Logto identity provider and cameleer-saas management plane services. Includes Traefik labels, CORS config, bootstrap healthcheck, and all provisioning env vars parameterized from .env. Co-Authored-By: Claude Opus 4.6 (1M context) --- installer/templates/docker-compose.saas.yml | 106 ++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 installer/templates/docker-compose.saas.yml diff --git a/installer/templates/docker-compose.saas.yml b/installer/templates/docker-compose.saas.yml new file mode 100644 index 0000000..a568760 --- /dev/null +++ b/installer/templates/docker-compose.saas.yml @@ -0,0 +1,106 @@ +# Cameleer SaaS — Logto + management plane +# Loaded in SaaS deployment mode + +services: + cameleer-logto: + image: ${LOGTO_IMAGE:-gitea.siegeln.net/cameleer/cameleer-logto}:${VERSION:-latest} + restart: unless-stopped + depends_on: + cameleer-postgres: + condition: service_healthy + environment: + DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD}@cameleer-postgres:5432/logto + ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} + ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002} + TRUST_PROXY_HEADER: 1 + NODE_TLS_REJECT_UNAUTHORIZED: "${NODE_TLS_REJECT:-0}" + LOGTO_ENDPOINT: http://cameleer-logto:3001 + LOGTO_ADMIN_ENDPOINT: http://cameleer-logto:3002 + LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} + PUBLIC_HOST: ${PUBLIC_HOST:-localhost} + PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https} + PG_HOST: cameleer-postgres + PG_USER: ${POSTGRES_USER:-cameleer} + PG_PASSWORD: ${POSTGRES_PASSWORD} + 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} + healthcheck: + test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/oidc/.well-known/openid-configuration', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\" && test -f /data/logto-bootstrap.json"] + interval: 10s + timeout: 5s + retries: 60 + start_period: 30s + labels: + - traefik.enable=true + - traefik.http.routers.cameleer-logto.rule=PathPrefix(`/`) + - traefik.http.routers.cameleer-logto.priority=1 + - traefik.http.routers.cameleer-logto.entrypoints=websecure + - traefik.http.routers.cameleer-logto.tls=true + - traefik.http.routers.cameleer-logto.service=cameleer-logto + - traefik.http.routers.cameleer-logto.middlewares=cameleer-logto-cors + - "traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}" + - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS + - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowHeaders=Authorization,Content-Type + - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowCredentials=true + - traefik.http.services.cameleer-logto.loadbalancer.server.port=3001 + - traefik.http.routers.cameleer-logto-console.rule=PathPrefix(`/`) + - traefik.http.routers.cameleer-logto-console.entrypoints=admin-console + - traefik.http.routers.cameleer-logto-console.tls=true + - traefik.http.routers.cameleer-logto-console.service=cameleer-logto-console + - traefik.http.services.cameleer-logto-console.loadbalancer.server.port=3002 + volumes: + - cameleer-bootstrapdata:/data + networks: + - cameleer + - monitoring + + cameleer-saas: + image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest} + restart: unless-stopped + depends_on: + cameleer-logto: + condition: service_healthy + environment: + # SaaS database + SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/cameleer_saas + SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer} + SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} + # Identity (Logto) + CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: http://cameleer-logto:3001 + CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} + # Provisioning — passed to per-tenant server containers + CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} + CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https} + CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer + CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik + CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer} + CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD} + CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD} + CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer-server:latest} + CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui:latest} + labels: + - traefik.enable=true + - traefik.http.routers.saas.rule=PathPrefix(`/platform`) + - traefik.http.routers.saas.entrypoints=websecure + - traefik.http.routers.saas.tls=true + - traefik.http.services.saas.loadbalancer.server.port=8080 + - "prometheus.io/scrape=true" + - "prometheus.io/port=8080" + - "prometheus.io/path=/platform/actuator/prometheus" + volumes: + - cameleer-bootstrapdata:/data/bootstrap:ro + - cameleer-certs:/certs + - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock + group_add: + - "${DOCKER_GID:-0}" + networks: + - cameleer + - monitoring + +volumes: + cameleer-bootstrapdata: + +networks: + monitoring: + name: cameleer-monitoring-noop From 141b44048cdc4b4efa92254c5c9000260beb06b0 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:59:05 +0200 Subject: [PATCH 3/9] feat(installer): add standalone docker-compose and traefik templates Standalone mode: server + server-ui services with postgres image override to stock postgres:16-alpine. Includes traefik-dynamic.yml for default TLS certificate store configuration. Co-Authored-By: Claude Opus 4.6 (1M context) --- installer/templates/docker-compose.server.yml | 97 +++++++++++++++++++ installer/templates/traefik-dynamic.yml | 6 ++ 2 files changed, 103 insertions(+) create mode 100644 installer/templates/docker-compose.server.yml create mode 100644 installer/templates/traefik-dynamic.yml diff --git a/installer/templates/docker-compose.server.yml b/installer/templates/docker-compose.server.yml new file mode 100644 index 0000000..6f541b1 --- /dev/null +++ b/installer/templates/docker-compose.server.yml @@ -0,0 +1,97 @@ +# Cameleer Server (standalone) +# Loaded in standalone deployment mode + +services: + cameleer-traefik: + volumes: + - ./traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro + + cameleer-postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB:-cameleer} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d $${POSTGRES_DB:-cameleer}"] + + cameleer-server: + image: ${SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server}:${VERSION:-latest} + container_name: cameleer-server + restart: unless-stopped + depends_on: + cameleer-postgres: + condition: service_healthy + environment: + CAMELEER_SERVER_TENANT_ID: default + SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/${POSTGRES_DB:-cameleer}?currentSchema=tenant_default + SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer} + SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} + CAMELEER_SERVER_CLICKHOUSE_URL: jdbc:clickhouse://cameleer-clickhouse:8123/cameleer + CAMELEER_SERVER_CLICKHOUSE_USERNAME: default + CAMELEER_SERVER_CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} + CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN: ${BOOTSTRAP_TOKEN:?BOOTSTRAP_TOKEN must be set in .env} + CAMELEER_SERVER_SECURITY_UIUSER: ${SERVER_ADMIN_USER:-admin} + CAMELEER_SERVER_SECURITY_UIPASSWORD: ${SERVER_ADMIN_PASS:?SERVER_ADMIN_PASS must be set in .env} + CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} + CAMELEER_SERVER_RUNTIME_ENABLED: "true" + CAMELEER_SERVER_RUNTIME_SERVERURL: http://cameleer-server:8081 + CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN: ${PUBLIC_HOST:-localhost} + CAMELEER_SERVER_RUNTIME_ROUTINGMODE: path + CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH: /data/jars + CAMELEER_SERVER_RUNTIME_DOCKERNETWORK: cameleer-apps + CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME: cameleer-jars + CAMELEER_SERVER_RUNTIME_BASEIMAGE: gitea.siegeln.net/cameleer/cameleer-runtime-base:${VERSION:-latest} + labels: + - traefik.enable=true + - traefik.http.routers.server-api.rule=PathPrefix(`/api`) + - traefik.http.routers.server-api.entrypoints=websecure + - traefik.http.routers.server-api.tls=true + - traefik.http.services.server-api.loadbalancer.server.port=8081 + - traefik.docker.network=cameleer-traefik + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:8081/api/v1/health || exit 1"] + interval: 10s + timeout: 5s + retries: 30 + start_period: 30s + volumes: + - jars:/data/jars + - cameleer-certs:/certs:ro + - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock + group_add: + - "${DOCKER_GID:-0}" + networks: + - cameleer + - cameleer-traefik + - cameleer-apps + - monitoring + + cameleer-server-ui: + image: ${SERVER_UI_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui}:${VERSION:-latest} + restart: unless-stopped + depends_on: + cameleer-server: + condition: service_healthy + environment: + CAMELEER_API_URL: http://cameleer-server:8081 + BASE_PATH: "" + labels: + - traefik.enable=true + - traefik.http.routers.ui.rule=PathPrefix(`/`) + - traefik.http.routers.ui.priority=1 + - traefik.http.routers.ui.entrypoints=websecure + - traefik.http.routers.ui.tls=true + - traefik.http.services.ui.loadbalancer.server.port=80 + - traefik.docker.network=cameleer-traefik + networks: + - cameleer-traefik + - monitoring + +volumes: + jars: + +networks: + cameleer-apps: + name: cameleer-apps + driver: bridge + monitoring: + name: cameleer-monitoring-noop diff --git a/installer/templates/traefik-dynamic.yml b/installer/templates/traefik-dynamic.yml new file mode 100644 index 0000000..b2a8787 --- /dev/null +++ b/installer/templates/traefik-dynamic.yml @@ -0,0 +1,6 @@ +tls: + stores: + default: + defaultCertificate: + certFile: /certs/cert.pem + keyFile: /certs/key.pem From 758585cc9a66490050e3413cae23c84f12115f16 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:59:10 +0200 Subject: [PATCH 4/9] feat(installer): add TLS and monitoring overlay templates Optional compose overlays: TLS overlay mounts user-supplied certs into traefik, monitoring overlay replaces the noop bridge with an external Docker network for Prometheus scraping. Co-Authored-By: Claude Opus 4.6 (1M context) --- installer/templates/docker-compose.monitoring.yml | 7 +++++++ installer/templates/docker-compose.tls.yml | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 installer/templates/docker-compose.monitoring.yml create mode 100644 installer/templates/docker-compose.tls.yml diff --git a/installer/templates/docker-compose.monitoring.yml b/installer/templates/docker-compose.monitoring.yml new file mode 100644 index 0000000..ddbb723 --- /dev/null +++ b/installer/templates/docker-compose.monitoring.yml @@ -0,0 +1,7 @@ +# External monitoring network overlay +# Overrides the noop monitoring bridge with a real external network + +networks: + monitoring: + external: true + name: ${MONITORING_NETWORK:?MONITORING_NETWORK must be set in .env} diff --git a/installer/templates/docker-compose.tls.yml b/installer/templates/docker-compose.tls.yml new file mode 100644 index 0000000..489d08c --- /dev/null +++ b/installer/templates/docker-compose.tls.yml @@ -0,0 +1,7 @@ +# Custom TLS certificates overlay +# Adds user-supplied certificate volume to traefik + +services: + cameleer-traefik: + volumes: + - ./certs:/user-certs:ro From cb1f6b8ccf4c946dc5e8c35e92ff74be89736c3d Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:59:15 +0200 Subject: [PATCH 5/9] feat(installer): add .env.example with documented variables Reference .env file documenting all configuration variables across both deployment modes, with section headers for compose assembly, public access, credentials, TLS, Docker, provisioning, and monitoring. Co-Authored-By: Claude Opus 4.6 (1M context) --- installer/templates/.env.example | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 installer/templates/.env.example diff --git a/installer/templates/.env.example b/installer/templates/.env.example new file mode 100644 index 0000000..4833cda --- /dev/null +++ b/installer/templates/.env.example @@ -0,0 +1,88 @@ +# Cameleer Configuration +# Copy this file to .env and fill in the values. +# The installer generates .env automatically — this file is for reference. + +# ============================================================ +# Compose file assembly (set by installer) +# ============================================================ +# SaaS: docker-compose.yml:docker-compose.saas.yml +# Standalone: docker-compose.yml:docker-compose.server.yml +# Add :docker-compose.tls.yml for custom TLS certificates +# Add :docker-compose.monitoring.yml for external monitoring network +COMPOSE_FILE=docker-compose.yml:docker-compose.saas.yml + +# ============================================================ +# Image version +# ============================================================ +VERSION=latest + +# ============================================================ +# Public access +# ============================================================ +PUBLIC_HOST=localhost +PUBLIC_PROTOCOL=https + +# ============================================================ +# Ports +# ============================================================ +HTTP_PORT=80 +HTTPS_PORT=443 +# Set to 0.0.0.0 to expose Logto admin console externally (default: localhost only) +# LOGTO_CONSOLE_BIND=0.0.0.0 +LOGTO_CONSOLE_PORT=3002 + +# ============================================================ +# PostgreSQL +# ============================================================ +POSTGRES_USER=cameleer +POSTGRES_PASSWORD=CHANGE_ME +# SaaS: cameleer_saas, Standalone: cameleer +POSTGRES_DB=cameleer_saas + +# ============================================================ +# ClickHouse +# ============================================================ +CLICKHOUSE_PASSWORD=CHANGE_ME + +# ============================================================ +# Admin credentials (SaaS mode) +# ============================================================ +SAAS_ADMIN_USER=admin +SAAS_ADMIN_PASS=CHANGE_ME + +# ============================================================ +# Admin credentials (standalone mode) +# ============================================================ +# SERVER_ADMIN_USER=admin +# SERVER_ADMIN_PASS=CHANGE_ME +# BOOTSTRAP_TOKEN=CHANGE_ME + +# ============================================================ +# TLS +# ============================================================ +# Set to 1 to reject unauthorized TLS certificates (production) +NODE_TLS_REJECT=0 +# Custom TLS certificate paths (inside container, set by installer) +# CERT_FILE=/user-certs/cert.pem +# KEY_FILE=/user-certs/key.pem +# CA_FILE=/user-certs/ca.pem + +# ============================================================ +# Docker +# ============================================================ +DOCKER_SOCKET=/var/run/docker.sock +# GID of the docker socket — detected by installer, used for container group_add +DOCKER_GID=0 + +# ============================================================ +# Provisioning images (SaaS mode only) +# ============================================================ +# CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=gitea.siegeln.net/cameleer/cameleer-server:latest +# CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=gitea.siegeln.net/cameleer/cameleer-server-ui:latest + +# ============================================================ +# Monitoring (optional) +# ============================================================ +# External Docker network name for Prometheus scraping. +# Only needed when docker-compose.monitoring.yml is in COMPOSE_FILE. +# MONITORING_NETWORK=prometheus From 2ed527ac74545a74f568394505ae315ae045fd1b Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:03:01 +0200 Subject: [PATCH 6/9] refactor(installer): replace sh compose generation with template copying Co-Authored-By: Claude Opus 4.6 (1M context) --- installer/install.sh | 501 ++++--------------------------------------- 1 file changed, 44 insertions(+), 457 deletions(-) diff --git a/installer/install.sh b/installer/install.sh index cc7dfc9..f106c27 100644 --- a/installer/install.sh +++ b/installer/install.sh @@ -603,12 +603,22 @@ BOOTSTRAP_TOKEN=$(generate_password) # Docker DOCKER_SOCKET=${DOCKER_SOCKET} DOCKER_GID=$(stat -c '%g' "${DOCKER_SOCKET}" 2>/dev/null || echo "0") + +POSTGRES_IMAGE=postgres:16-alpine + +# 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") EOF if [ "$TLS_MODE" = "custom" ]; then echo "CERT_FILE=/user-certs/cert.pem" >> "$f" echo "KEY_FILE=/user-certs/key.pem" >> "$f" [ -n "$CA_FILE" ] && echo "CA_FILE=/user-certs/ca.pem" >> "$f" fi + if [ -n "$MONITORING_NETWORK" ]; then + echo "" >> "$f" + echo "# Monitoring" >> "$f" + echo "MONITORING_NETWORK=${MONITORING_NETWORK}" >> "$f" + fi log_info "Generated .env" cp "$f" "$INSTALL_DIR/.env.bak" return @@ -629,6 +639,7 @@ PUBLIC_PROTOCOL=${PUBLIC_PROTOCOL} HTTP_PORT=${HTTP_PORT} HTTPS_PORT=${HTTPS_PORT} LOGTO_CONSOLE_PORT=${LOGTO_CONSOLE_PORT} +LOGTO_CONSOLE_BIND=$([ "$LOGTO_CONSOLE_EXPOSED" = "true" ] && echo "0.0.0.0" || echo "127.0.0.1") # PostgreSQL POSTGRES_USER=cameleer @@ -665,473 +676,46 @@ DOCKER_GID=$(stat -c '%g' "${DOCKER_SOCKET}" 2>/dev/null || echo "0") # Provisioning images CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer-server:${VERSION} CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer-server-ui:${VERSION} + +# 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") EOF + if [ -n "$MONITORING_NETWORK" ]; then + echo "" >> "$f" + echo "# Monitoring" >> "$f" + echo "MONITORING_NETWORK=${MONITORING_NETWORK}" >> "$f" + fi + log_info "Generated .env" cp "$f" "$INSTALL_DIR/.env.bak" } -generate_compose_file() { +copy_templates() { + local src + src="$(cd "$(dirname "$0")" && pwd)/templates" + + # Base infra — always copied + cp "$src/docker-compose.yml" "$INSTALL_DIR/docker-compose.yml" + cp "$src/.env.example" "$INSTALL_DIR/.env.example" + + # Mode-specific if [ "$DEPLOYMENT_MODE" = "standalone" ]; then - generate_compose_file_standalone - return - fi - local f="$INSTALL_DIR/docker-compose.yml" - : > "$f" - - cat >> "$f" << 'EOF' -# Cameleer SaaS Platform -# Generated by Cameleer installer � do not edit manually - -services: - cameleer-traefik: - image: ${TRAEFIK_IMAGE:-gitea.siegeln.net/cameleer/cameleer-traefik}:${VERSION:-latest} - restart: unless-stopped - ports: - - "${HTTP_PORT:-80}:80" - - "${HTTPS_PORT:-443}:443" -EOF - - if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then - cat >> "$f" << 'EOF' - - "${LOGTO_CONSOLE_PORT:-3002}:3002" -EOF + cp "$src/docker-compose.server.yml" "$INSTALL_DIR/docker-compose.server.yml" + cp "$src/traefik-dynamic.yml" "$INSTALL_DIR/traefik-dynamic.yml" + else + cp "$src/docker-compose.saas.yml" "$INSTALL_DIR/docker-compose.saas.yml" fi - cat >> "$f" << 'EOF' - environment: - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - CERT_FILE: ${CERT_FILE:-} - KEY_FILE: ${KEY_FILE:-} - CA_FILE: ${CA_FILE:-} - volumes: - - cameleer-certs:/certs - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro -EOF - + # Optional overlays if [ "$TLS_MODE" = "custom" ]; then - cat >> "$f" << 'EOF' - - ./certs:/user-certs:ro -EOF + cp "$src/docker-compose.tls.yml" "$INSTALL_DIR/docker-compose.tls.yml" fi - - cat >> "$f" << 'EOF' - networks: - - cameleer - - cameleer-traefik -EOF - if [ -n "$MONITORING_NETWORK" ]; then - echo " - ${MONITORING_NETWORK}" >> "$f" - cat >> "$f" << 'EOF' - labels: - - "prometheus.io/scrape=true" - - "prometheus.io/port=8082" - - "prometheus.io/path=/metrics" -EOF + cp "$src/docker-compose.monitoring.yml" "$INSTALL_DIR/docker-compose.monitoring.yml" fi - cat >> "$f" << 'EOF' - - cameleer-postgres: - image: ${POSTGRES_IMAGE:-gitea.siegeln.net/cameleer/cameleer-postgres}:${VERSION:-latest} - restart: unless-stopped - environment: - POSTGRES_DB: cameleer_saas - POSTGRES_USER: ${POSTGRES_USER:-cameleer} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - cameleer-pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d cameleer_saas"] - interval: 5s - timeout: 5s - retries: 5 - networks: - - cameleer -EOF - - if [ -n "$MONITORING_NETWORK" ]; then - echo " - ${MONITORING_NETWORK}" >> "$f" - fi - - cat >> "$f" << 'EOF' - - cameleer-clickhouse: - image: ${CLICKHOUSE_IMAGE:-gitea.siegeln.net/cameleer/cameleer-clickhouse}:${VERSION:-latest} - restart: unless-stopped - environment: - CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} - volumes: - - cameleer-chdata:/var/lib/clickhouse - healthcheck: - test: ["CMD-SHELL", "clickhouse-client --password $${CLICKHOUSE_PASSWORD} --query 'SELECT 1'"] - interval: 10s - timeout: 5s - retries: 3 - networks: - - cameleer -EOF - - if [ -n "$MONITORING_NETWORK" ]; then - echo " - ${MONITORING_NETWORK}" >> "$f" - cat >> "$f" << 'EOF' - labels: - - "prometheus.io/scrape=true" - - "prometheus.io/port=9363" - - "prometheus.io/path=/metrics" -EOF - fi - - cat >> "$f" << 'EOF' - - cameleer-logto: - image: ${LOGTO_IMAGE:-gitea.siegeln.net/cameleer/cameleer-logto}:${VERSION:-latest} - restart: unless-stopped - depends_on: - cameleer-postgres: - condition: service_healthy - environment: - DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD}@cameleer-postgres:5432/logto - ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002} - TRUST_PROXY_HEADER: 1 - NODE_TLS_REJECT_UNAUTHORIZED: "${NODE_TLS_REJECT:-0}" - LOGTO_ENDPOINT: http://cameleer-logto:3001 - LOGTO_ADMIN_ENDPOINT: http://cameleer-logto:3002 - LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https} - PG_HOST: cameleer-postgres - PG_USER: ${POSTGRES_USER:-cameleer} - PG_PASSWORD: ${POSTGRES_PASSWORD} - 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} - healthcheck: - test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/oidc/.well-known/openid-configuration', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\" && test -f /data/logto-bootstrap.json"] - interval: 10s - timeout: 5s - retries: 60 - start_period: 30s - labels: - - traefik.enable=true - - traefik.http.routers.cameleer-logto.rule=PathPrefix(`/`) - - traefik.http.routers.cameleer-logto.priority=1 - - traefik.http.routers.cameleer-logto.entrypoints=websecure - - traefik.http.routers.cameleer-logto.tls=true - - traefik.http.routers.cameleer-logto.service=cameleer-logto - - traefik.http.routers.cameleer-logto.middlewares=cameleer-logto-cors - - "traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}" - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowHeaders=Authorization,Content-Type - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowCredentials=true - - traefik.http.services.cameleer-logto.loadbalancer.server.port=3001 -EOF - - if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then - cat >> "$f" << 'EOF' - - traefik.http.routers.cameleer-logto-console.rule=PathPrefix(`/`) - - traefik.http.routers.cameleer-logto-console.entrypoints=admin-console - - traefik.http.routers.cameleer-logto-console.tls=true - - traefik.http.routers.cameleer-logto-console.service=cameleer-logto-console - - traefik.http.services.cameleer-logto-console.loadbalancer.server.port=3002 -EOF - fi - - cat >> "$f" << 'EOF' - volumes: - - cameleer-bootstrapdata:/data - networks: - - cameleer - - cameleer-saas: - image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest} - restart: unless-stopped - depends_on: - cameleer-logto: - condition: service_healthy - environment: - # SaaS database - SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/cameleer_saas - SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer} - SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} - # Identity (Logto) - CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: http://cameleer-logto:3001 - CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - # Provisioning — passed to per-tenant server containers - CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} - CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https} - CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer - CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik - CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer} - CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD} - CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD} - CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer-server:latest} - CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui:latest} - labels: - - traefik.enable=true - - traefik.http.routers.saas.rule=PathPrefix(`/platform`) - - traefik.http.routers.saas.entrypoints=websecure - - traefik.http.routers.saas.tls=true - - traefik.http.services.saas.loadbalancer.server.port=8080 -EOF - - if [ -n "$MONITORING_NETWORK" ]; then - cat >> "$f" << 'EOF' - - "prometheus.io/scrape=true" - - "prometheus.io/port=8080" - - "prometheus.io/path=/platform/actuator/prometheus" -EOF - fi - - cat >> "$f" << 'EOF' - volumes: - - cameleer-bootstrapdata:/data/bootstrap:ro - - cameleer-certs:/certs - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock - networks: - - cameleer -EOF - - if [ -n "$MONITORING_NETWORK" ]; then - echo " - ${MONITORING_NETWORK}" >> "$f" - fi - - # Detect Docker socket GID for container access - local docker_gid - docker_gid=$(stat -c '%g' "${DOCKER_SOCKET:-/var/run/docker.sock}" 2>/dev/null || echo "0") - cat >> "$f" << EOF - group_add: - - "${docker_gid}" - -volumes: -EOF - cat >> "$f" << 'EOF' - cameleer-pgdata: - cameleer-chdata: - cameleer-certs: - cameleer-bootstrapdata: - -networks: - cameleer: - driver: bridge - cameleer-traefik: - name: cameleer-traefik - driver: bridge -EOF - - if [ -n "$MONITORING_NETWORK" ]; then - cat >> "$f" << EOF - ${MONITORING_NETWORK}: - external: true -EOF - fi - - log_info "Generated docker-compose.yml" -} - -generate_compose_file_standalone() { - local f="$INSTALL_DIR/docker-compose.yml" - : > "$f" - - cat >> "$f" << 'COMPOSEEOF' -# Cameleer Server (standalone) -# Generated by Cameleer installer — do not edit manually - -services: - cameleer-traefik: - image: ${TRAEFIK_IMAGE:-gitea.siegeln.net/cameleer/cameleer-traefik}:${VERSION:-latest} - restart: unless-stopped - ports: - - "${HTTP_PORT:-80}:80" - - "${HTTPS_PORT:-443}:443" - environment: - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - CERT_FILE: ${CERT_FILE:-} - KEY_FILE: ${KEY_FILE:-} - CA_FILE: ${CA_FILE:-} - volumes: - - cameleer-certs:/certs - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro - - ./traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro -COMPOSEEOF - - if [ "$TLS_MODE" = "custom" ]; then - echo " - ./certs:/user-certs:ro" >> "$f" - fi - - cat >> "$f" << 'COMPOSEEOF' - networks: - - cameleer - - cameleer-traefik -COMPOSEEOF - - if [ -n "$MONITORING_NETWORK" ]; then - echo " - ${MONITORING_NETWORK}" >> "$f" - fi - - cat >> "$f" << 'COMPOSEEOF' - - cameleer-postgres: - image: postgres:16-alpine - restart: unless-stopped - environment: - POSTGRES_DB: ${POSTGRES_DB:-cameleer} - POSTGRES_USER: ${POSTGRES_USER:-cameleer} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - cameleer-pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d $${POSTGRES_DB:-cameleer}"] - interval: 5s - timeout: 5s - retries: 5 - networks: - - cameleer -COMPOSEEOF - - if [ -n "$MONITORING_NETWORK" ]; then - echo " - ${MONITORING_NETWORK}" >> "$f" - fi - - cat >> "$f" << 'COMPOSEEOF' - - cameleer-clickhouse: - image: ${CLICKHOUSE_IMAGE:-gitea.siegeln.net/cameleer/cameleer-clickhouse}:${VERSION:-latest} - restart: unless-stopped - environment: - CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} - volumes: - - cameleer-chdata:/var/lib/clickhouse - healthcheck: - test: ["CMD-SHELL", "clickhouse-client --password $${CLICKHOUSE_PASSWORD} --query 'SELECT 1'"] - interval: 10s - timeout: 5s - retries: 3 - networks: - - cameleer -COMPOSEEOF - - if [ -n "$MONITORING_NETWORK" ]; then - echo " - ${MONITORING_NETWORK}" >> "$f" - fi - - # Detect Docker socket GID - local docker_gid - docker_gid=$(stat -c '%g' "${DOCKER_SOCKET:-/var/run/docker.sock}" 2>/dev/null || echo "0") - - cat >> "$f" << COMPOSEEOF - - cameleer-server: - image: \${SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server}:\${VERSION:-latest} - container_name: cameleer-server - restart: unless-stopped - depends_on: - cameleer-postgres: - condition: service_healthy - environment: - CAMELEER_SERVER_TENANT_ID: default - SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/\${POSTGRES_DB:-cameleer}?currentSchema=tenant_default - SPRING_DATASOURCE_USERNAME: \${POSTGRES_USER:-cameleer} - SPRING_DATASOURCE_PASSWORD: \${POSTGRES_PASSWORD} - CAMELEER_SERVER_CLICKHOUSE_URL: jdbc:clickhouse://cameleer-clickhouse:8123/cameleer - CAMELEER_SERVER_CLICKHOUSE_USERNAME: default - CAMELEER_SERVER_CLICKHOUSE_PASSWORD: \${CLICKHOUSE_PASSWORD} - CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN: \${BOOTSTRAP_TOKEN} - CAMELEER_SERVER_SECURITY_UIUSER: \${SERVER_ADMIN_USER:-admin} - CAMELEER_SERVER_SECURITY_UIPASSWORD: \${SERVER_ADMIN_PASS:?SERVER_ADMIN_PASS must be set in .env} - CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS: \${PUBLIC_PROTOCOL:-https}://\${PUBLIC_HOST:-localhost} - CAMELEER_SERVER_RUNTIME_ENABLED: "true" - CAMELEER_SERVER_RUNTIME_SERVERURL: http://cameleer-server:8081 - CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN: \${PUBLIC_HOST:-localhost} - CAMELEER_SERVER_RUNTIME_ROUTINGMODE: path - CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH: /data/jars - CAMELEER_SERVER_RUNTIME_DOCKERNETWORK: cameleer-apps - CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME: cameleer-jars - CAMELEER_SERVER_RUNTIME_BASEIMAGE: gitea.siegeln.net/cameleer/cameleer-runtime-base:\${VERSION:-latest} - labels: - - traefik.enable=true - - traefik.http.routers.server-api.rule=PathPrefix(\`/api\`) - - traefik.http.routers.server-api.entrypoints=websecure - - traefik.http.routers.server-api.tls=true - - traefik.http.services.server-api.loadbalancer.server.port=8081 - - traefik.docker.network=cameleer-traefik - healthcheck: - test: ["CMD-SHELL", "curl -sf http://localhost:8081/api/v1/health || exit 1"] - interval: 10s - timeout: 5s - retries: 30 - start_period: 30s - volumes: - - jars:/data/jars - - cameleer-certs:/certs:ro - - \${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock - group_add: - - "${docker_gid}" - networks: - - cameleer - - cameleer-traefik - - cameleer-apps - - cameleer-server-ui: - image: \${SERVER_UI_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui}:\${VERSION:-latest} - restart: unless-stopped - depends_on: - cameleer-server: - condition: service_healthy - environment: - CAMELEER_API_URL: http://cameleer-server:8081 - BASE_PATH: "" - labels: - - traefik.enable=true - - traefik.http.routers.ui.rule=PathPrefix(\`/\`) - - traefik.http.routers.ui.priority=1 - - traefik.http.routers.ui.entrypoints=websecure - - traefik.http.routers.ui.tls=true - - traefik.http.services.ui.loadbalancer.server.port=80 - - traefik.docker.network=cameleer-traefik - networks: - - cameleer-traefik -COMPOSEEOF - - cat >> "$f" << 'COMPOSEEOF' - -volumes: - cameleer-pgdata: - cameleer-chdata: - cameleer-certs: - jars: - -networks: - cameleer: - driver: bridge - cameleer-traefik: - name: cameleer-traefik - driver: bridge - cameleer-apps: - name: cameleer-apps - driver: bridge -COMPOSEEOF - - if [ -n "$MONITORING_NETWORK" ]; then - cat >> "$f" << EOF - ${MONITORING_NETWORK}: - external: true -EOF - fi - - # Generate standalone traefik dynamic config (overrides baked-in redirect) - cat > "$INSTALL_DIR/traefik-dynamic.yml" << 'TRAEFIKEOF' -tls: - stores: - default: - defaultCertificate: - certFile: /certs/cert.pem - keyFile: /certs/key.pem -TRAEFIKEOF - - log_info "Generated docker-compose.yml (standalone)" + log_info "Copied docker-compose templates to $INSTALL_DIR" } # --- Docker operations --- @@ -1700,7 +1284,7 @@ handle_rerun() { load_config_file "$INSTALL_DIR/cameleer.conf" load_env_overrides merge_config - generate_compose_file + copy_templates docker_compose_pull docker_compose_down docker_compose_up @@ -1725,9 +1309,12 @@ handle_rerun() { log_info "Reinstalling..." docker_compose_down 2>/dev/null || true (cd "$INSTALL_DIR" && docker compose -p "${COMPOSE_PROJECT:-cameleer-saas}" down -v 2>/dev/null || true) - rm -f "$INSTALL_DIR/.env" "$INSTALL_DIR/docker-compose.yml" \ + rm -f "$INSTALL_DIR/.env" "$INSTALL_DIR/.env.bak" "$INSTALL_DIR/.env.example" \ + "$INSTALL_DIR/docker-compose.yml" "$INSTALL_DIR/docker-compose.saas.yml" \ + "$INSTALL_DIR/docker-compose.server.yml" "$INSTALL_DIR/docker-compose.tls.yml" \ + "$INSTALL_DIR/docker-compose.monitoring.yml" "$INSTALL_DIR/traefik-dynamic.yml" \ "$INSTALL_DIR/cameleer.conf" "$INSTALL_DIR/credentials.txt" \ - "$INSTALL_DIR/INSTALL.md" "$INSTALL_DIR/.env.bak" + "$INSTALL_DIR/INSTALL.md" rm -rf "$INSTALL_DIR/certs" IS_RERUN=false return @@ -1788,7 +1375,7 @@ main() { # Generate configuration files generate_env_file - generate_compose_file + copy_templates write_config_file # Pull and start From 6170f61eeb6954b809fba7dd958f56f5e7d5dd94 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:08:34 +0200 Subject: [PATCH 7/9] refactor(installer): replace ps1 compose generation with template copying Co-Authored-By: Claude Opus 4.6 (1M context) --- installer/install.ps1 | 490 +++++------------------------------------- 1 file changed, 49 insertions(+), 441 deletions(-) diff --git a/installer/install.ps1 b/installer/install.ps1 index c6ac684..f8a1887 100644 --- a/installer/install.ps1 +++ b/installer/install.ps1 @@ -607,26 +607,37 @@ BOOTSTRAP_TOKEN=$bt # Docker DOCKER_SOCKET=$($c.DockerSocket) DOCKER_GID=$gid + +POSTGRES_IMAGE=postgres:16-alpine "@ if ($c.TlsMode -eq 'custom') { $content += "`nCERT_FILE=/user-certs/cert.pem" $content += "`nKEY_FILE=/user-certs/key.pem" if ($c.CaFile) { $content += "`nCA_FILE=/user-certs/ca.pem" } } + $composeFile = 'docker-compose.yml:docker-compose.server.yml' + if ($c.TlsMode -eq 'custom') { $composeFile += ':docker-compose.tls.yml' } + if ($c.MonitoringNetwork) { $composeFile += ':docker-compose.monitoring.yml' } + $content += "`n`n# Compose file assembly`nCOMPOSE_FILE=$composeFile" + if ($c.MonitoringNetwork) { + $content += "`n`n# Monitoring`nMONITORING_NETWORK=$($c.MonitoringNetwork)" + } } else { + $consoleBind = if ($c.LogtoConsoleExposed -eq 'true') { '0.0.0.0' } else { '127.0.0.1' } $content = @" # Cameleer SaaS Configuration # Generated by installer v${CAMELEER_INSTALLER_VERSION} on $ts - + VERSION=$($c.Version) - + PUBLIC_HOST=$($c.PublicHost) PUBLIC_PROTOCOL=$($c.PublicProtocol) - + HTTP_PORT=$($c.HttpPort) HTTPS_PORT=$($c.HttpsPort) LOGTO_CONSOLE_PORT=$($c.LogtoConsolePort) - +LOGTO_CONSOLE_BIND=$consoleBind + # PostgreSQL POSTGRES_USER=cameleer POSTGRES_PASSWORD=$($c.PostgresPassword) @@ -658,6 +669,13 @@ CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=${REGISTRY}/cameleer-server:$($c.Version) CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer-server-ui:$($c.Version) "@ $content += $provisioningBlock + $composeFile = 'docker-compose.yml:docker-compose.saas.yml' + if ($c.TlsMode -eq 'custom') { $composeFile += ':docker-compose.tls.yml' } + if ($c.MonitoringNetwork) { $composeFile += ':docker-compose.monitoring.yml' } + $content += "`n`n# Compose file assembly`nCOMPOSE_FILE=$composeFile" + if ($c.MonitoringNetwork) { + $content += "`n`n# Monitoring`nMONITORING_NETWORK=$($c.MonitoringNetwork)" + } } Write-Utf8File $f $content @@ -665,443 +683,33 @@ CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=${REGISTRY}/cameleer-server-ui:$($c.Ver Log-Info 'Generated .env' } -# --- Docker Compose generation --- -# Rule: '@ and "@ closing delimiters must ALWAYS be alone at column 0 on their own line. +# --- Copy docker-compose templates --- -function Generate-ComposeFile { - $c = $script:cfg - if ($c.DeploymentMode -eq 'standalone') { Generate-ComposeFileStandalone; return } - - $f = Join-Path $c.InstallDir 'docker-compose.yml' - $gid = Get-DockerGid $c.DockerSocket - $out = New-Object System.Collections.Generic.List[string] - - $out.Add(@' -# Cameleer SaaS Platform -# Generated by Cameleer installer -- do not edit manually - -services: - cameleer-traefik: - image: ${TRAEFIK_IMAGE:-gitea.siegeln.net/cameleer/cameleer-traefik}:${VERSION:-latest} - restart: unless-stopped - ports: - - "${HTTP_PORT:-80}:80" - - "${HTTPS_PORT:-443}:443" -'@ - ) - if ($c.LogtoConsoleExposed -eq 'true') { - $out.Add(' - "${LOGTO_CONSOLE_PORT:-3002}:3002"') - } - $out.Add(@' - environment: - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - CERT_FILE: ${CERT_FILE:-} - KEY_FILE: ${KEY_FILE:-} - CA_FILE: ${CA_FILE:-} - volumes: - - cameleer-certs:/certs - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro -'@ - ) - if ($c.TlsMode -eq 'custom') { $out.Add(' - ./certs:/user-certs:ro') } - $out.Add(@' - networks: - - cameleer - - cameleer-traefik -'@ - ) - if ($c.MonitoringNetwork) { - $out.Add(" - $($c.MonitoringNetwork)") - $out.Add(@' - labels: - - "prometheus.io/scrape=true" - - "prometheus.io/port=8082" - - "prometheus.io/path=/metrics" -'@ - ) - } - - $out.Add(@' - - cameleer-postgres: - image: ${POSTGRES_IMAGE:-gitea.siegeln.net/cameleer/cameleer-postgres}:${VERSION:-latest} - restart: unless-stopped - environment: - POSTGRES_DB: cameleer_saas - POSTGRES_USER: ${POSTGRES_USER:-cameleer} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - cameleer-pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d cameleer_saas"] - interval: 5s - timeout: 5s - retries: 5 - networks: - - cameleer -'@ - ) - if ($c.MonitoringNetwork) { $out.Add(" - $($c.MonitoringNetwork)") } - - $out.Add(@' - - cameleer-clickhouse: - image: ${CLICKHOUSE_IMAGE:-gitea.siegeln.net/cameleer/cameleer-clickhouse}:${VERSION:-latest} - restart: unless-stopped - environment: - CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} - volumes: - - cameleer-chdata:/var/lib/clickhouse - healthcheck: - test: ["CMD-SHELL", "clickhouse-client --password $${CLICKHOUSE_PASSWORD} --query 'SELECT 1'"] - interval: 10s - timeout: 5s - retries: 3 - networks: - - cameleer -'@ - ) - if ($c.MonitoringNetwork) { - $out.Add(" - $($c.MonitoringNetwork)") - $out.Add(@' - labels: - - "prometheus.io/scrape=true" - - "prometheus.io/port=9363" - - "prometheus.io/path=/metrics" -'@ - ) - } - - $out.Add(@' - - cameleer-logto: - image: ${LOGTO_IMAGE:-gitea.siegeln.net/cameleer/cameleer-logto}:${VERSION:-latest} - restart: unless-stopped - depends_on: - cameleer-postgres: - condition: service_healthy - environment: - DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD}@cameleer-postgres:5432/logto - ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002} - TRUST_PROXY_HEADER: 1 - NODE_TLS_REJECT_UNAUTHORIZED: "${NODE_TLS_REJECT:-0}" - LOGTO_ENDPOINT: http://cameleer-logto:3001 - LOGTO_ADMIN_ENDPOINT: http://cameleer-logto:3002 - LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https} - PG_HOST: cameleer-postgres - PG_USER: ${POSTGRES_USER:-cameleer} - PG_PASSWORD: ${POSTGRES_PASSWORD} - 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} - healthcheck: - test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/oidc/.well-known/openid-configuration', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\" && test -f /data/logto-bootstrap.json"] - interval: 10s - timeout: 5s - retries: 60 - start_period: 30s - labels: - - traefik.enable=true - - traefik.http.routers.cameleer-logto.rule=PathPrefix(`/`) - - traefik.http.routers.cameleer-logto.priority=1 - - traefik.http.routers.cameleer-logto.entrypoints=websecure - - traefik.http.routers.cameleer-logto.tls=true - - traefik.http.routers.cameleer-logto.service=cameleer-logto - - traefik.http.routers.cameleer-logto.middlewares=cameleer-logto-cors - - "traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}" - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowHeaders=Authorization,Content-Type - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowCredentials=true - - traefik.http.services.cameleer-logto.loadbalancer.server.port=3001 -'@ - ) - if ($c.LogtoConsoleExposed -eq 'true') { - $out.Add(@' - - traefik.http.routers.cameleer-logto-console.rule=PathPrefix(`/`) - - traefik.http.routers.cameleer-logto-console.entrypoints=admin-console - - traefik.http.routers.cameleer-logto-console.tls=true - - traefik.http.routers.cameleer-logto-console.service=cameleer-logto-console - - traefik.http.services.cameleer-logto-console.loadbalancer.server.port=3002 -'@ - ) - } - - $out.Add(@' - volumes: - - cameleer-bootstrapdata:/data - networks: - - cameleer - - cameleer-saas: - image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest} - restart: unless-stopped - depends_on: - cameleer-logto: - condition: service_healthy - environment: - SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/cameleer_saas - SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer} - SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} - CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: http://cameleer-logto:3001 - CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} - CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https} - CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer - CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik - CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer} - CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD} - CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD} - CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer-server:latest} - CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui:latest} - labels: - - traefik.enable=true - - traefik.http.routers.saas.rule=PathPrefix(`/platform`) - - traefik.http.routers.saas.entrypoints=websecure - - traefik.http.routers.saas.tls=true - - traefik.http.services.saas.loadbalancer.server.port=8080 -'@ - ) - if ($c.MonitoringNetwork) { - $out.Add(@' - - "prometheus.io/scrape=true" - - "prometheus.io/port=8080" - - "prometheus.io/path=/platform/actuator/prometheus" -'@ - ) - } - $out.Add(@' - volumes: - - cameleer-bootstrapdata:/data/bootstrap:ro - - cameleer-certs:/certs - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock - networks: - - cameleer -'@ - ) - if ($c.MonitoringNetwork) { $out.Add(" - $($c.MonitoringNetwork)") } - $out.Add(" group_add:") - $out.Add(" - `"$gid`"") - - $out.Add(@' - -volumes: - cameleer-pgdata: - cameleer-chdata: - cameleer-certs: - cameleer-bootstrapdata: - -networks: - cameleer: - driver: bridge - cameleer-traefik: - name: cameleer-traefik - driver: bridge -'@ - ) - if ($c.MonitoringNetwork) { - $out.Add(" $($c.MonitoringNetwork):") - $out.Add(' external: true') - } - - Write-Utf8File $f ($out -join "`n") - Log-Info 'Generated docker-compose.yml' -} - -function Generate-ComposeFileStandalone { +function Copy-Templates { $c = $script:cfg - $f = Join-Path $c.InstallDir 'docker-compose.yml' - $gid = Get-DockerGid $c.DockerSocket - $out = New-Object System.Collections.Generic.List[string] - - $out.Add(@' -# Cameleer Server (standalone) -# Generated by Cameleer installer -- do not edit manually - -services: - cameleer-traefik: - image: ${TRAEFIK_IMAGE:-gitea.siegeln.net/cameleer/cameleer-traefik}:${VERSION:-latest} - restart: unless-stopped - ports: - - "${HTTP_PORT:-80}:80" - - "${HTTPS_PORT:-443}:443" - environment: - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - CERT_FILE: ${CERT_FILE:-} - KEY_FILE: ${KEY_FILE:-} - CA_FILE: ${CA_FILE:-} - volumes: - - cameleer-certs:/certs - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro - - ./traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro -'@ - ) - if ($c.TlsMode -eq 'custom') { $out.Add(' - ./certs:/user-certs:ro') } - $out.Add(@' - networks: - - cameleer - - cameleer-traefik -'@ - ) - if ($c.MonitoringNetwork) { $out.Add(" - $($c.MonitoringNetwork)") } - - $out.Add(@' - - cameleer-postgres: - image: postgres:16-alpine - restart: unless-stopped - environment: - POSTGRES_DB: ${POSTGRES_DB:-cameleer} - POSTGRES_USER: ${POSTGRES_USER:-cameleer} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - cameleer-pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d $${POSTGRES_DB:-cameleer}"] - interval: 5s - timeout: 5s - retries: 5 - networks: - - cameleer -'@ - ) - if ($c.MonitoringNetwork) { $out.Add(" - $($c.MonitoringNetwork)") } - - $out.Add(@' - - cameleer-clickhouse: - image: ${CLICKHOUSE_IMAGE:-gitea.siegeln.net/cameleer/cameleer-clickhouse}:${VERSION:-latest} - restart: unless-stopped - environment: - CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} - volumes: - - cameleer-chdata:/var/lib/clickhouse - healthcheck: - test: ["CMD-SHELL", "clickhouse-client --password $${CLICKHOUSE_PASSWORD} --query 'SELECT 1'"] - interval: 10s - timeout: 5s - retries: 3 - networks: - - cameleer -'@ - ) - if ($c.MonitoringNetwork) { $out.Add(" - $($c.MonitoringNetwork)") } - - # Server block: double-quoted so $gid expands; compose ${VAR} uses backtick-dollar - $serverBlock = @" - - cameleer-server: - image: `${SERVER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server}:`${VERSION:-latest} - container_name: cameleer-server - restart: unless-stopped - depends_on: - cameleer-postgres: - condition: service_healthy - environment: - CAMELEER_SERVER_TENANT_ID: default - SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/`${POSTGRES_DB:-cameleer}?currentSchema=tenant_default - SPRING_DATASOURCE_USERNAME: `${POSTGRES_USER:-cameleer} - SPRING_DATASOURCE_PASSWORD: `${POSTGRES_PASSWORD} - CAMELEER_SERVER_CLICKHOUSE_URL: jdbc:clickhouse://cameleer-clickhouse:8123/cameleer - CAMELEER_SERVER_CLICKHOUSE_USERNAME: default - CAMELEER_SERVER_CLICKHOUSE_PASSWORD: `${CLICKHOUSE_PASSWORD} - CAMELEER_SERVER_SECURITY_BOOTSTRAPTOKEN: `${BOOTSTRAP_TOKEN} - CAMELEER_SERVER_SECURITY_UIUSER: `${SERVER_ADMIN_USER:-admin} - CAMELEER_SERVER_SECURITY_UIPASSWORD: `${SERVER_ADMIN_PASS:?SERVER_ADMIN_PASS must be set in .env} - CAMELEER_SERVER_SECURITY_CORSALLOWEDORIGINS: `${PUBLIC_PROTOCOL:-https}://`${PUBLIC_HOST:-localhost} - CAMELEER_SERVER_RUNTIME_ENABLED: "true" - CAMELEER_SERVER_RUNTIME_SERVERURL: http://cameleer-server:8081 - CAMELEER_SERVER_RUNTIME_ROUTINGDOMAIN: `${PUBLIC_HOST:-localhost} - CAMELEER_SERVER_RUNTIME_ROUTINGMODE: path - CAMELEER_SERVER_RUNTIME_JARSTORAGEPATH: /data/jars - CAMELEER_SERVER_RUNTIME_DOCKERNETWORK: cameleer-apps - CAMELEER_SERVER_RUNTIME_JARDOCKERVOLUME: cameleer-jars - CAMELEER_SERVER_RUNTIME_BASEIMAGE: gitea.siegeln.net/cameleer/cameleer-runtime-base:`${VERSION:-latest} - labels: - - traefik.enable=true - - traefik.http.routers.server-api.rule=PathPrefix(``/api``) - - traefik.http.routers.server-api.entrypoints=websecure - - traefik.http.routers.server-api.tls=true - - traefik.http.services.server-api.loadbalancer.server.port=8081 - - traefik.docker.network=cameleer-traefik - healthcheck: - test: ["CMD-SHELL", "curl -sf http://localhost:8081/api/v1/health || exit 1"] - interval: 10s - timeout: 5s - retries: 30 - start_period: 30s - volumes: - - jars:/data/jars - - cameleer-certs:/certs:ro - - `${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock - group_add: - - "$gid" - networks: - - cameleer - - cameleer-traefik - - cameleer-apps - - cameleer-server-ui: - image: `${SERVER_UI_IMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui}:`${VERSION:-latest} - restart: unless-stopped - depends_on: - cameleer-server: - condition: service_healthy - environment: - CAMELEER_API_URL: http://cameleer-server:8081 - BASE_PATH: "" - labels: - - traefik.enable=true - - traefik.http.routers.ui.rule=PathPrefix(``/``) - - traefik.http.routers.ui.priority=1 - - traefik.http.routers.ui.entrypoints=websecure - - traefik.http.routers.ui.tls=true - - traefik.http.services.ui.loadbalancer.server.port=80 - - traefik.docker.network=cameleer-traefik - networks: - - cameleer-traefik -"@ - $out.Add($serverBlock) - - $out.Add(@' - -volumes: - cameleer-pgdata: - cameleer-chdata: - cameleer-certs: - jars: - -networks: - cameleer: - driver: bridge - cameleer-traefik: - name: cameleer-traefik - driver: bridge - cameleer-apps: - name: cameleer-apps - driver: bridge -'@ - ) - if ($c.MonitoringNetwork) { - $out.Add(" $($c.MonitoringNetwork):") - $out.Add(' external: true') + $src = Join-Path $PSScriptRoot 'templates' + + # Base infra -- always copied + Copy-Item (Join-Path $src 'docker-compose.yml') (Join-Path $c.InstallDir 'docker-compose.yml') -Force + Copy-Item (Join-Path $src '.env.example') (Join-Path $c.InstallDir '.env.example') -Force + + # Mode-specific + if ($c.DeploymentMode -eq 'standalone') { + Copy-Item (Join-Path $src 'docker-compose.server.yml') (Join-Path $c.InstallDir 'docker-compose.server.yml') -Force + Copy-Item (Join-Path $src 'traefik-dynamic.yml') (Join-Path $c.InstallDir 'traefik-dynamic.yml') -Force + } else { + Copy-Item (Join-Path $src 'docker-compose.saas.yml') (Join-Path $c.InstallDir 'docker-compose.saas.yml') -Force } - - Write-Utf8File $f ($out -join "`n") - - $traefikDyn = @' -tls: - stores: - default: - defaultCertificate: - certFile: /certs/cert.pem - keyFile: /certs/key.pem -'@ - Write-Utf8File (Join-Path $c.InstallDir 'traefik-dynamic.yml') $traefikDyn - - Log-Info 'Generated docker-compose.yml (standalone)' + + # Optional overlays + if ($c.TlsMode -eq 'custom') { + Copy-Item (Join-Path $src 'docker-compose.tls.yml') (Join-Path $c.InstallDir 'docker-compose.tls.yml') -Force + } + if ($c.MonitoringNetwork) { + Copy-Item (Join-Path $src 'docker-compose.monitoring.yml') (Join-Path $c.InstallDir 'docker-compose.monitoring.yml') -Force + } + + Log-Info "Copied docker-compose templates to $($c.InstallDir)" } # --- Docker operations --- @@ -1713,7 +1321,7 @@ function Handle-Rerun { Merge-Config $script:cfg.InstallDir = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath( $script:cfg.InstallDir) - Generate-ComposeFile + Copy-Templates Invoke-ComposePull Invoke-ComposeDown Invoke-ComposeUp @@ -1743,7 +1351,7 @@ function Handle-Rerun { docker compose -p $proj down -v 2>$null } catch {} finally { Pop-Location } - foreach ($fname in @('.env','.env.bak','docker-compose.yml','cameleer.conf','credentials.txt','INSTALL.md','traefik-dynamic.yml')) { + foreach ($fname in @('.env','.env.bak','.env.example','docker-compose.yml','docker-compose.saas.yml','docker-compose.server.yml','docker-compose.tls.yml','docker-compose.monitoring.yml','traefik-dynamic.yml','cameleer.conf','credentials.txt','INSTALL.md')) { $fp = Join-Path $c.InstallDir $fname if (Test-Path $fp) { Remove-Item $fp -Force } } @@ -1795,7 +1403,7 @@ function Main { if ($script:cfg.TlsMode -eq 'custom') { Copy-Certs } Generate-EnvFile - Generate-ComposeFile + Copy-Templates Write-ConfigFile Invoke-ComposePull From 6c7895b0d6c790a353783aae05a9b12e79fee3dc Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:09:30 +0200 Subject: [PATCH 8/9] chore(installer): remove generated install output, add to gitignore Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 3 + installer/cameleer/.env.bak | 33 ------ installer/cameleer/INSTALL.md | 95 ---------------- installer/cameleer/cameleer.conf | 18 --- installer/cameleer/credentials.txt | 16 --- installer/cameleer/docker-compose.yml | 155 -------------------------- 6 files changed, 3 insertions(+), 317 deletions(-) delete mode 100644 installer/cameleer/.env.bak delete mode 100644 installer/cameleer/INSTALL.md delete mode 100644 installer/cameleer/cameleer.conf delete mode 100644 installer/cameleer/credentials.txt delete mode 100644 installer/cameleer/docker-compose.yml diff --git a/.gitignore b/.gitignore index a928ab0..ed65d4a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ Thumbs.db .playwright-mcp/ .gitnexus +# Installer output (generated by install.sh / install.ps1) +installer/cameleer/ + # Generated by postinstall from @cameleer/design-system ui/public/favicon.svg docker/runtime-base/agent.jar diff --git a/installer/cameleer/.env.bak b/installer/cameleer/.env.bak deleted file mode 100644 index 230e96b..0000000 --- a/installer/cameleer/.env.bak +++ /dev/null @@ -1,33 +0,0 @@ -# Cameleer SaaS Configuration -# Generated by installer v1.0.0 on 2026-04-15 08:55:30 UTC - -VERSION=latest - -PUBLIC_HOST=desktop-fb5vgj9.siegeln.internal -PUBLIC_PROTOCOL=https - -HTTP_PORT=80 -HTTPS_PORT=443 -LOGTO_CONSOLE_PORT=3002 - -# PostgreSQL -POSTGRES_USER=cameleer -POSTGRES_PASSWORD=dwnyYXj3bVe6kFcOHERr57SkrkD9476a -POSTGRES_DB=cameleer_saas - -# ClickHouse -CLICKHOUSE_PASSWORD=SshXE61qZqB1kVoZpQLbr2mDYokw1ZgJ - -# Admin user -SAAS_ADMIN_USER=admin -SAAS_ADMIN_PASS=1J3TrbgIYZbxjav1K14uy5DX8nil6Bdi - -# TLS -NODE_TLS_REJECT=0 -# Docker -DOCKER_SOCKET=/var/run/docker.sock -DOCKER_GID=0 - -# Provisioning images -CAMELEER_SAAS_PROVISIONING_SERVERIMAGE=gitea.siegeln.net/cameleer/cameleer-server:latest -CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE=gitea.siegeln.net/cameleer/cameleer-server-ui:latest \ No newline at end of file diff --git a/installer/cameleer/INSTALL.md b/installer/cameleer/INSTALL.md deleted file mode 100644 index afb5bf7..0000000 --- a/installer/cameleer/INSTALL.md +++ /dev/null @@ -1,95 +0,0 @@ -# Cameleer SaaS -- Installation Documentation - -## Installation Summary - -| | | -|---|---| -| **Version** | latest | -| **Date** | 2026-04-15 08:55:55 UTC | -| **Installer** | v1.0.0 | -| **Install Directory** | C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer | -| **Hostname** | desktop-fb5vgj9.siegeln.internal | -| **TLS** | Self-signed (auto-generated) | - -## Service URLs - -- **Platform UI:** https://desktop-fb5vgj9.siegeln.internal/platform/ -- **API Endpoint:** https://desktop-fb5vgj9.siegeln.internal/platform/api/ -- **Logto Admin Console:** https://desktop-fb5vgj9.siegeln.internal:3002 - -## First Steps - -1. Open the Platform UI in your browser -2. Log in as admin with the credentials from `credentials.txt` -3. Create tenants from the admin console -4. The platform will provision a dedicated server instance for each tenant - -## Architecture - -| Container | Purpose | -|---|---| -| `traefik` | Reverse proxy, TLS termination, routing | -| `postgres` | PostgreSQL database (SaaS + Logto + tenant schemas) | -| `clickhouse` | Time-series storage (traces, metrics, logs) | -| `logto` | OIDC identity provider + bootstrap | -| `cameleer-saas` | SaaS platform (Spring Boot + React) | - -Per-tenant `cameleer-server` and `cameleer-server-ui` containers are provisioned dynamically. - -## Networking - -| Port | Service | -|---|---| -| 80 | HTTP (redirects to HTTPS) | -| 443 | HTTPS (main entry point) | -| 3002 | Logto Admin Console | - - -## TLS - -**Mode:** Self-signed (auto-generated) - -The platform generated a self-signed certificate on first boot. To replace it: -1. Log in as admin and navigate to **Certificates** in the admin console -2. Upload your certificate and key via the UI -3. Activate the new certificate (zero-downtime swap) - -## Data & Backups - -| Docker Volume | Contains | -|---|---| -| `cameleer-pgdata` | PostgreSQL data (tenants, licenses, audit) | -| `cameleer-chdata` | ClickHouse data (traces, metrics, logs) | -| `cameleer-certs` | TLS certificates | -| `cameleer-bootstrapdata` | Logto bootstrap results | - -### Backup Commands - -```bash -docker compose -p cameleer-saas exec cameleer-postgres pg_dump -U cameleer cameleer_saas > backup.sql -docker compose -p cameleer-saas exec cameleer-clickhouse clickhouse-client --query "SELECT * FROM cameleer.traces FORMAT Native" > traces.native -``` - -## Upgrading - -```powershell -.\install.ps1 -InstallDir C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer -Version NEW_VERSION -``` - -## Troubleshooting - -| Issue | Command | -|---|---| -| Service not starting | `docker compose -p cameleer-saas logs SERVICE_NAME` | -| Bootstrap failed | `docker compose -p cameleer-saas logs cameleer-logto` | -| Routing issues | `docker compose -p cameleer-saas logs cameleer-traefik` | -| Database issues | `docker compose -p cameleer-saas exec cameleer-postgres psql -U cameleer -d cameleer_saas` | - -## Uninstalling - -```powershell -Set-Location C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer -docker compose -p cameleer-saas down -docker compose -p cameleer-saas down -v -Remove-Item -Recurse -Force C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer -``` \ No newline at end of file diff --git a/installer/cameleer/cameleer.conf b/installer/cameleer/cameleer.conf deleted file mode 100644 index e862f96..0000000 --- a/installer/cameleer/cameleer.conf +++ /dev/null @@ -1,18 +0,0 @@ -# Cameleer installation config -# Generated by installer v1.0.0 on 2026-04-15 08:55:30 UTC - -install_dir=C:\Users\Hendrik\Documents\projects\cameleer-saas\installer\cameleer -public_host=desktop-fb5vgj9.siegeln.internal -public_protocol=https -admin_user=admin -tls_mode=self-signed -http_port=80 -https_port=443 -logto_console_port=3002 -logto_console_exposed=true -monitoring_network= -version=latest -compose_project=cameleer-saas -docker_socket=/var/run/docker.sock -node_tls_reject=0 -deployment_mode=saas \ No newline at end of file diff --git a/installer/cameleer/credentials.txt b/installer/cameleer/credentials.txt deleted file mode 100644 index db47b3c..0000000 --- a/installer/cameleer/credentials.txt +++ /dev/null @@ -1,16 +0,0 @@ -=========================================== - CAMELEER PLATFORM CREDENTIALS - Generated: 2026-04-15 08:55:55 UTC - - SECURE THIS FILE AND DELETE AFTER NOTING - THESE CREDENTIALS CANNOT BE RECOVERED -=========================================== - -Admin Console: https://desktop-fb5vgj9.siegeln.internal/platform/ -Admin User: admin -Admin Password: 1J3TrbgIYZbxjav1K14uy5DX8nil6Bdi - -PostgreSQL: cameleer / dwnyYXj3bVe6kFcOHERr57SkrkD9476a -ClickHouse: default / SshXE61qZqB1kVoZpQLbr2mDYokw1ZgJ - -Logto Console: https://desktop-fb5vgj9.siegeln.internal:3002 \ No newline at end of file diff --git a/installer/cameleer/docker-compose.yml b/installer/cameleer/docker-compose.yml deleted file mode 100644 index 7d30034..0000000 --- a/installer/cameleer/docker-compose.yml +++ /dev/null @@ -1,155 +0,0 @@ -# Cameleer SaaS Platform -# Generated by Cameleer installer -- do not edit manually - -services: - cameleer-traefik: - image: ${TRAEFIK_IMAGE:-gitea.siegeln.net/cameleer/cameleer-traefik}:${VERSION:-latest} - restart: unless-stopped - ports: - - "${HTTP_PORT:-80}:80" - - "${HTTPS_PORT:-443}:443" - - "${LOGTO_CONSOLE_PORT:-3002}:3002" - environment: - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - CERT_FILE: ${CERT_FILE:-} - KEY_FILE: ${KEY_FILE:-} - CA_FILE: ${CA_FILE:-} - volumes: - - cameleer-certs:/certs - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro - networks: - - cameleer - - cameleer-traefik - - cameleer-postgres: - image: ${POSTGRES_IMAGE:-gitea.siegeln.net/cameleer/cameleer-postgres}:${VERSION:-latest} - restart: unless-stopped - environment: - POSTGRES_DB: cameleer_saas - POSTGRES_USER: ${POSTGRES_USER:-cameleer} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - cameleer-pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-cameleer} -d cameleer_saas"] - interval: 5s - timeout: 5s - retries: 5 - networks: - - cameleer - - cameleer-clickhouse: - image: ${CLICKHOUSE_IMAGE:-gitea.siegeln.net/cameleer/cameleer-clickhouse}:${VERSION:-latest} - restart: unless-stopped - environment: - CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} - volumes: - - cameleer-chdata:/var/lib/clickhouse - healthcheck: - test: ["CMD-SHELL", "clickhouse-client --password $${CLICKHOUSE_PASSWORD} --query 'SELECT 1'"] - interval: 10s - timeout: 5s - retries: 3 - networks: - - cameleer - - cameleer-logto: - image: ${LOGTO_IMAGE:-gitea.siegeln.net/cameleer/cameleer-logto}:${VERSION:-latest} - restart: unless-stopped - depends_on: - cameleer-postgres: - condition: service_healthy - environment: - DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD}@cameleer-postgres:5432/logto - ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - ADMIN_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002} - TRUST_PROXY_HEADER: 1 - NODE_TLS_REJECT_UNAUTHORIZED: "${NODE_TLS_REJECT:-0}" - LOGTO_ENDPOINT: http://cameleer-logto:3001 - LOGTO_ADMIN_ENDPOINT: http://cameleer-logto:3002 - LOGTO_PUBLIC_ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - PUBLIC_HOST: ${PUBLIC_HOST:-localhost} - PUBLIC_PROTOCOL: ${PUBLIC_PROTOCOL:-https} - PG_HOST: cameleer-postgres - PG_USER: ${POSTGRES_USER:-cameleer} - PG_PASSWORD: ${POSTGRES_PASSWORD} - 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} - healthcheck: - test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/oidc/.well-known/openid-configuration', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\" && test -f /data/logto-bootstrap.json"] - interval: 10s - timeout: 5s - retries: 60 - start_period: 30s - labels: - - traefik.enable=true - - traefik.http.routers.cameleer-logto.rule=PathPrefix(`/`) - - traefik.http.routers.cameleer-logto.priority=1 - - traefik.http.routers.cameleer-logto.entrypoints=websecure - - traefik.http.routers.cameleer-logto.tls=true - - traefik.http.routers.cameleer-logto.service=cameleer-logto - - traefik.http.routers.cameleer-logto.middlewares=cameleer-logto-cors - - "traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowOriginList=${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}:${LOGTO_CONSOLE_PORT:-3002}" - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowMethods=GET,POST,PUT,PATCH,DELETE,OPTIONS - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowHeaders=Authorization,Content-Type - - traefik.http.middlewares.cameleer-logto-cors.headers.accessControlAllowCredentials=true - - traefik.http.services.cameleer-logto.loadbalancer.server.port=3001 - - traefik.http.routers.cameleer-logto-console.rule=PathPrefix(`/`) - - traefik.http.routers.cameleer-logto-console.entrypoints=admin-console - - traefik.http.routers.cameleer-logto-console.tls=true - - traefik.http.routers.cameleer-logto-console.service=cameleer-logto-console - - traefik.http.services.cameleer-logto-console.loadbalancer.server.port=3002 - volumes: - - cameleer-bootstrapdata:/data - networks: - - cameleer - - cameleer-saas: - image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest} - restart: unless-stopped - depends_on: - cameleer-logto: - condition: service_healthy - environment: - SPRING_DATASOURCE_URL: jdbc:postgresql://cameleer-postgres:5432/cameleer_saas - SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-cameleer} - SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} - CAMELEER_SAAS_IDENTITY_LOGTOENDPOINT: http://cameleer-logto:3001 - CAMELEER_SAAS_IDENTITY_LOGTOPUBLICENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} - CAMELEER_SAAS_PROVISIONING_PUBLICHOST: ${PUBLIC_HOST:-localhost} - CAMELEER_SAAS_PROVISIONING_PUBLICPROTOCOL: ${PUBLIC_PROTOCOL:-https} - CAMELEER_SAAS_PROVISIONING_NETWORKNAME: ${COMPOSE_PROJECT_NAME:-cameleer-saas}_cameleer - CAMELEER_SAAS_PROVISIONING_TRAEFIKNETWORK: cameleer-traefik - CAMELEER_SAAS_PROVISIONING_DATASOURCEUSERNAME: ${POSTGRES_USER:-cameleer} - CAMELEER_SAAS_PROVISIONING_DATASOURCEPASSWORD: ${POSTGRES_PASSWORD} - CAMELEER_SAAS_PROVISIONING_CLICKHOUSEPASSWORD: ${CLICKHOUSE_PASSWORD} - CAMELEER_SAAS_PROVISIONING_SERVERIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERIMAGE:-gitea.siegeln.net/cameleer/cameleer-server:latest} - CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE: ${CAMELEER_SAAS_PROVISIONING_SERVERUIIMAGE:-gitea.siegeln.net/cameleer/cameleer-server-ui:latest} - labels: - - traefik.enable=true - - traefik.http.routers.saas.rule=PathPrefix(`/platform`) - - traefik.http.routers.saas.entrypoints=websecure - - traefik.http.routers.saas.tls=true - - traefik.http.services.saas.loadbalancer.server.port=8080 - volumes: - - cameleer-bootstrapdata:/data/bootstrap:ro - - cameleer-certs:/certs - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock - networks: - - cameleer - group_add: - - "0" - -volumes: - cameleer-pgdata: - cameleer-chdata: - cameleer-certs: - cameleer-bootstrapdata: - -networks: - cameleer: - driver: bridge - cameleer-traefik: - name: cameleer-traefik - driver: bridge \ No newline at end of file From 186f7639add07276380aab308b8a9590dba04348 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:11:04 +0200 Subject: [PATCH 9/9] docs: update CLAUDE.md with template-based installer architecture Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 9a1f336..17f7351 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -288,6 +288,13 @@ The installer (`installer/install.sh`) supports two deployment modes: Standalone mode generates a simpler compose with the server running directly. No Logto, no SaaS management plane, no bootstrap. The admin logs in with local credentials at `/`. +The installer uses static docker-compose templates in `installer/templates/`. Templates are copied to the install directory and composed via `COMPOSE_FILE` in `.env`: +- `docker-compose.yml` — shared infrastructure (traefik, postgres, clickhouse) +- `docker-compose.saas.yml` — SaaS mode (logto, cameleer-saas) +- `docker-compose.server.yml` — standalone mode (server, server-ui) +- `docker-compose.tls.yml` — overlay: custom TLS cert volume +- `docker-compose.monitoring.yml` — overlay: external monitoring network + ### Tenant Provisioning Flow When SaaS admin creates a tenant via `VendorTenantService`: