#!/usr/bin/env sh set -eu ENV_FILE="${1:-.env}" errors=0 warnings=0 if [ ! -f "$ENV_FILE" ]; then echo "ERROR: env file not found: $ENV_FILE" >&2 exit 1 fi value() { key="$1" awk -v key="$key" ' /^[[:space:]]*#/ || /^[[:space:]]*$/ { next } { line = $0 sub(/\r$/, "", line) split(line, parts, "=") current = parts[1] gsub(/^[ \t]+|[ \t]+$/, "", current) if (current == key) { sub(/^[^=]*=/, "", line) gsub(/^"/, "", line) gsub(/"$/, "", line) gsub(/^'\''/, "", line) gsub(/'\''$/, "", line) print line exit } } ' "$ENV_FILE" } is_placeholder() { case "${1:-}" in ""|"<"*">"|*example.com*|"change-me"|"changeme"|"password"|"secret") return 0 ;; *) return 1 ;; esac } error() { errors=$((errors + 1)) echo "ERROR: $1" >&2 } warn() { warnings=$((warnings + 1)) echo "WARN: $1" >&2 } require_value() { key="$1" label="${2:-$1}" current="$(value "$key")" if is_placeholder "$current"; then error "$label must be set to a production value." fi } require_secret() { key="$1" current="$(value "$key")" if is_placeholder "$current"; then error "$key must be set." return fi case "$current" in admin|demo|keycloak|qsfera|qsfera-secret-key) error "$key uses a known demo/default value." return ;; esac if [ "${#current}" -lt 16 ]; then error "$key must be at least 16 characters." fi } enabled() { current="$(value "$1")" [ -n "$current" ] } require_value OC_DOMAIN "OC_DOMAIN" require_value TRAEFIK_ACME_MAIL "TRAEFIK_ACME_MAIL" require_secret ADMIN_PASSWORD case "$(value INSECURE)" in true|TRUE|1|yes|YES) error "INSECURE must be false for production." ;; esac case "$(value DEMO_USERS)" in true|TRUE|1|yes|YES) error "DEMO_USERS must be false for production." ;; esac case "$(value OC_DOCKER_IMAGE)" in qsfera/qsfera) ;; "") warn "OC_DOCKER_IMAGE is empty; compose default must be verified before rollout." ;; *) error "OC_DOCKER_IMAGE must be qsfera/qsfera for production." ;; esac case "$(value OC_DOCKER_TAG)" in ""|latest|"<"*">") error "OC_DOCKER_TAG must pin a concrete production release tag." ;; esac if ! enabled QSFERA; then error "QSFERA=:qsfera.yml must be enabled." fi if enabled DECOMPOSEDS3; then require_value DECOMPOSEDS3_ENDPOINT "DECOMPOSEDS3_ENDPOINT" require_value DECOMPOSEDS3_REGION "DECOMPOSEDS3_REGION" require_secret DECOMPOSEDS3_ACCESS_KEY require_secret DECOMPOSEDS3_SECRET_KEY require_value DECOMPOSEDS3_BUCKET "DECOMPOSEDS3_BUCKET" fi if enabled DECOMPOSEDS3_MINIO; then error "DECOMPOSEDS3_MINIO is for test/lab installs and must not be enabled for public production." fi if enabled KEYCLOAK; then require_value KEYCLOAK_DOMAIN "KEYCLOAK_DOMAIN" require_value KEYCLOAK_REALM "KEYCLOAK_REALM" require_secret KEYCLOAK_POSTGRES_PASSWORD require_value KEYCLOAK_ADMIN_USER "KEYCLOAK_ADMIN_USER" require_secret KEYCLOAK_ADMIN_PASSWORD fi if [ -z "$(value OC_CONFIG_DIR)" ] || [ -z "$(value OC_DATA_DIR)" ]; then warn "OC_CONFIG_DIR and OC_DATA_DIR are not both set; docker volumes are harder to back up and restore." fi if [ "$errors" -gt 0 ]; then echo "Production env validation failed: $errors error(s), $warnings warning(s)." >&2 exit 1 fi echo "Production env validation passed: $warnings warning(s)."