Files
sciagent/scripts/verify-prod-env.sh
Thinh Lam 688fac73e9
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped
sciagent code + Gitea Actions CI/CD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:38:30 +07:00

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