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