From 9fad6b1595145532fa50557140273cfb6be88c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D1=83=D1=80=D0=BD=D0=B0=D1=82=20=D0=90=D0=BD=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D0=B9?= Date: Fri, 12 Jun 2026 23:02:01 +0300 Subject: [PATCH] Validate server compose env fragments --- PRODUCTION_READINESS.md | 4 +-- scripts/validate-server-production-env.ps1 | 38 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/PRODUCTION_READINESS.md b/PRODUCTION_READINESS.md index 6802b9e2..460399a4 100644 --- a/PRODUCTION_READINESS.md +++ b/PRODUCTION_READINESS.md @@ -50,8 +50,8 @@ Date: 2026-06-08. - Server `qsfera_full` compose files now require explicit admin, Keycloak, and S3/MinIO secrets instead of demo defaults. - Server `qsfera_full/env.production.example` provides a tracked production-safe starting point with TLS validation enabled and demo users disabled. - Server `qsfera_full/validate-production-env.sh` checks production `.env` invariants before compose validation: TLS validation enabled, demo users disabled, pinned production image, required domains, required secrets, and no public-production MinIO. -- Added `scripts/validate-server-production-env.ps1` with the same `qsfera_full` production `.env` checks for the local Windows/PowerShell workflow; it validates required domains, release image pinning, disabled demo/insecure modes, required secrets, optional Keycloak/S3 blocks, and backup-friendly config/data path warnings without printing secret values. -- `scripts/validate-server-production-env.ps1` was verified on 2026-06-12: it rejects `Server/devtools/deployments/qsfera_full/env.production.example` with placeholder values and accepts a temporary filled production-shaped env file with `0 warning(s)`. +- Added `scripts/validate-server-production-env.ps1` with the same `qsfera_full` production `.env` checks for the local Windows/PowerShell workflow; it validates required domains, release image pinning, disabled demo/insecure modes, required secrets, optional Keycloak/S3 blocks, `COMPOSE_PATH_SEPARATOR`, selected `COMPOSE_FILE` fragments, and backup-friendly config/data path warnings without printing secret values. +- `scripts/validate-server-production-env.ps1` was verified on 2026-06-12: it rejects `Server/devtools/deployments/qsfera_full/env.production.example` with placeholder values, accepts a temporary filled production-shaped env file with `0 warning(s)`, and rejects a temporary env file where `COMPOSE_FILE` omits the required `qsfera.yml` fragment. - Server now has a Raspberry Pi 5 Docker path matching the Ikar/Argus deployment style: `Server/docker-compose.rpi5.yml`, `Server/.env.example`, and `Server/docs/raspberry-pi-5-docker.md`. - Server Docker builds now accept explicit production metadata for `QSFERA_VERSION`, `QSFERA_EDITION`, and `QSFERA_BUILD_DATE`, and the Raspberry Pi 5 compose path defaults to pinned image `qsfera-cloud-server:7.0.0-rpi5-stable`. - Windows/Desktop update channel defaults now match the build channel: stable builds default to `stable`, beta builds default to `beta`. diff --git a/scripts/validate-server-production-env.ps1 b/scripts/validate-server-production-env.ps1 index e92a3068..05295d85 100644 --- a/scripts/validate-server-production-env.ps1 +++ b/scripts/validate-server-production-env.ps1 @@ -87,6 +87,21 @@ function Require-Secret([hashtable]$Values, [string]$Key) { } } +function Require-ExactValue([hashtable]$Values, [string]$Key, [string]$ExpectedValue) { + $value = Get-EnvValue $Values $Key + if ($value -ne $ExpectedValue) { + Add-Error "$Key must be $ExpectedValue." + } +} + +function Test-ComposeFileIncludes([string]$ComposeFile, [string]$EnvKey, [string]$Fragment) { + if ([string]::IsNullOrWhiteSpace($ComposeFile)) { + return $false + } + + return $ComposeFile.Contains($Fragment) -or $ComposeFile.Contains('${' + $EnvKey + ':-}') +} + $resolvedEnvPath = (Resolve-Path -LiteralPath $EnvPath -ErrorAction Stop).Path $values = Read-EnvFile $resolvedEnvPath @@ -113,11 +128,27 @@ if ([string]::IsNullOrWhiteSpace($dockerTag) -or $dockerTag -eq "latest" -or ($d Add-Error "OC_DOCKER_TAG must pin a concrete production release tag." } +Require-ExactValue $values "COMPOSE_PATH_SEPARATOR" ":" + +$composeFile = Get-EnvValue $values "COMPOSE_FILE" +if ([string]::IsNullOrWhiteSpace($composeFile) -or -not $composeFile.Contains("docker-compose.yml")) { + Add-Error "COMPOSE_FILE must include docker-compose.yml." +} + if (-not (Test-Enabled $values "QSFERA")) { Add-Error "QSFERA=:qsfera.yml must be enabled." +} else { + Require-ExactValue $values "QSFERA" ":qsfera.yml" + if (-not (Test-ComposeFileIncludes $composeFile "QSFERA" "qsfera.yml")) { + Add-Error "COMPOSE_FILE must include qsfera.yml or the `${QSFERA:-} expansion." + } } if (Test-Enabled $values "DECOMPOSEDS3") { + Require-ExactValue $values "DECOMPOSEDS3" ":decomposeds3.yml" + if (-not (Test-ComposeFileIncludes $composeFile "DECOMPOSEDS3" "decomposeds3.yml")) { + Add-Error "COMPOSE_FILE must include decomposeds3.yml or the `${DECOMPOSEDS3:-} expansion when DECOMPOSEDS3 is enabled." + } Require-Value $values "DECOMPOSEDS3_ENDPOINT" Require-Value $values "DECOMPOSEDS3_REGION" Require-Secret $values "DECOMPOSEDS3_ACCESS_KEY" @@ -127,9 +158,16 @@ if (Test-Enabled $values "DECOMPOSEDS3") { if (Test-Enabled $values "DECOMPOSEDS3_MINIO") { Add-Error "DECOMPOSEDS3_MINIO is for test/lab installs and must not be enabled for public production." + if (-not (Test-ComposeFileIncludes $composeFile "DECOMPOSEDS3_MINIO" "minio.yml")) { + Add-Error "COMPOSE_FILE must include minio.yml or the `${DECOMPOSEDS3_MINIO:-} expansion when DECOMPOSEDS3_MINIO is enabled." + } } if (Test-Enabled $values "KEYCLOAK") { + Require-ExactValue $values "KEYCLOAK" ":keycloak.yml" + if (-not (Test-ComposeFileIncludes $composeFile "KEYCLOAK" "keycloak.yml")) { + Add-Error "COMPOSE_FILE must include keycloak.yml or the `${KEYCLOAK:-} expansion when KEYCLOAK is enabled." + } Require-Value $values "KEYCLOAK_DOMAIN" Require-Value $values "KEYCLOAK_REALM" Require-Secret $values "KEYCLOAK_POSTGRES_PASSWORD"