#!/usr/bin/env bash # Validates variables required by docker-compose.prod.yml before compose runs. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" ENV_FILE="${ENV_FILE:-$ROOT/.env}" if [[ ! -f "$ENV_FILE" ]]; then printf 'Missing %s\nCopy .env.example to .env and fill in secrets.\n' "$ENV_FILE" >&2 exit 1 fi while IFS= read -r line || [[ -n "$line" ]]; do line="${line%$'\r'}" [[ "$line" =~ ^[[:space:]]*# ]] && continue [[ "$line" =~ ^[[:space:]]*$ ]] && continue if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then key="${BASH_REMATCH[1]}" val="${BASH_REMATCH[2]}" val="${val#"${val%%[![:space:]]*}"}" val="${val%"${val##*[![:space:]]}"}" export "${key}=${val}" fi done <"$ENV_FILE" reject_leading_space_after_equals() { local key rhs while IFS= read -r line || [[ -n "$line" ]]; do line="${line%$'\r'}" [[ "$line" =~ ^[[:space:]]*# ]] && continue [[ "$line" =~ ^[[:space:]]*$ ]] && continue if [[ "$line" =~ ^[[:space:]]*(POSTGRES_USER|POSTGRES_PASSWORD|POSTGRES_DB|MINIO_ROOT_USER|MINIO_ROOT_PASSWORD|MINIO_API_PORT|MINIO_CONSOLE_PORT|FE_PORT|PUBLIC_HOST)=(.*)$ ]]; then key="${BASH_REMATCH[1]}" rhs="${BASH_REMATCH[2]}" [[ "$rhs" =~ ^[[:space:]]+ ]] || continue printf 'Invalid format in %s: "%s" has a space (or tab) immediately after "=".\n' "$ENV_FILE" "$key" >&2 printf 'Use KEY=value without a space after the equals sign.\n' >&2 exit 1 fi done <"$ENV_FILE" } reject_leading_space_after_equals MISSING=() need() { local n="$1" local v="${!n-}" if [[ -z "${v//[:space:]}" ]]; then MISSING+=("$n") fi } need PUBLIC_HOST need FE_PORT need MINIO_API_PORT need MINIO_CONSOLE_PORT need MINIO_ROOT_USER need MINIO_ROOT_PASSWORD need POSTGRES_USER need POSTGRES_PASSWORD need POSTGRES_DB need JWT_SECRET need MINIO_API_CORS_ALLOW_ORIGIN if [[ ${#MISSING[@]} -gt 0 ]]; then printf 'Unset or whitespace-only variables in %s:\n %s\n' "$ENV_FILE" "${MISSING[*]}" >&2 printf '(Blank values break MinIO: MINIO_BROWSER_REDIRECT_URL would have an empty host.)\n' >&2 exit 1 fi if ! [[ "${MINIO_CONSOLE_PORT}" =~ ^[0-9]+$ ]] || ! [[ "${MINIO_API_PORT}" =~ ^[0-9]+$ ]] || ! [[ "${FE_PORT}" =~ ^[0-9]+$ ]]; then printf 'Ports must be numeric (FE_PORT, MINIO_*_PORT).\n' >&2 exit 1 fi # Postgres role/database names in URLs/scripts: avoid quoting and “role does not exist” churn. if ! [[ "${POSTGRES_USER}" =~ ^[a-zA-Z_][a-zA-Z0-9_]{0,62}$ ]]; then printf 'POSTGRES_USER must be an unquoted SQL identifier (e.g. initiative): letters, digits, underscore; max 63 chars.\n' >&2 printf 'Avoid special characters like "!". Docker only creates this role when the Postgres data directory is EMPTY.\n' >&2 printf 'Full guide: docs/deploy-production-docker.md (Postgres).\n' >&2 exit 1 fi if ! [[ "${POSTGRES_DB}" =~ ^[a-zA-Z_][a-zA-Z0-9_]{0,62}$ ]]; then printf 'POSTGRES_DB must be an unquoted SQL identifier (e.g. initiatives): letters, digits, underscore; max 63 chars.\n' >&2 printf 'INITIATIVE_DATABASE_URL is built without escaping; exotic names break URLs.\n' >&2 exit 1 fi # INITIATIVE_DATABASE_URL is assembled without encoding; :, @, / break the URL if unescaped. if [[ "${POSTGRES_PASSWORD}" == *"@"* ]] || [[ "${POSTGRES_PASSWORD}" == *":"* ]] || [[ "${POSTGRES_PASSWORD}" == *"/"* ]] || [[ "${POSTGRES_PASSWORD}" == *"%"* ]]; then printf 'POSTGRES_PASSWORD contains @, :, / or %%. URL-unsafe for INITIATIVE_DATABASE_URL; use alphanumeric + punctuation like + or = from openssl rand -base64.\n' >&2 exit 1 fi if [[ "${MINIO_API_CORS_ALLOW_ORIGIN}" == "*" ]]; then printf 'MINIO_API_CORS_ALLOW_ORIGIN must not be * in production — use your SPA origin (e.g. https://www.rcc-ump.com).\n' >&2 exit 1 fi if [[ ${#JWT_SECRET} -lt 32 ]]; then printf 'JWT_SECRET must be at least 32 characters (generate: openssl rand -base64 48).\n' >&2 exit 1 fi preview="http://${PUBLIC_HOST}:${MINIO_CONSOLE_PORT}" echo "OK — required vars set; MINIO_BROWSER_REDIRECT_URL preview: ${preview}" echo " CORS for public UI: http://${PUBLIC_HOST}:${FE_PORT} (+ localhost defaults in be0; optional CORS_ORIGINS_EXTRA in .env)" if [[ -n "${S3_PUBLIC_ENDPOINT_URL:-}" && "${S3_PUBLIC_ENDPOINT_URL}" == https:* ]]; then echo " HTTPS MinIO (S3_PUBLIC_ENDPOINT_URL): ensure TLS proxy → 127.0.0.1:${MINIO_API_PORT}; see docs/minio-behind-https.md" fi if [[ -n "${MINIO_SERVER_URL:-}" && "${MINIO_SERVER_URL}" == https:* && -z "${S3_PUBLIC_ENDPOINT_URL:-}" ]]; then echo " Warn: MINIO_SERVER_URL is https but S3_PUBLIC_ENDPOINT_URL is unset — be0 defaults may still presign HTTP. Set both to match." fi