115 lines
4.6 KiB
Bash
Executable File
115 lines
4.6 KiB
Bash
Executable File
#!/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 |