refactor(installer): replace ps1 compose generation with template copying

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-15 21:08:34 +02:00
parent 2ed527ac74
commit 6170f61eeb

View File

@@ -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