#!/usr/bin/env bash set -euo pipefail ENV_FILE="${1:-.env.production}" if [ ! -f "$ENV_FILE" ]; then echo "production environment file is missing: $ENV_FILE" >&2 exit 1 fi if [ -L "$ENV_FILE" ]; then echo "production environment file must not be a symlink" >&2 exit 1 fi if MODE="$(stat -c '%a' "$ENV_FILE" 2>/dev/null)"; then : else MODE="$(stat -f '%Lp' "$ENV_FILE")" fi if [ "$MODE" != "600" ]; then echo "production environment file must have mode 0600" >&2 exit 1 fi if OWNER="$(stat -c '%u' "$ENV_FILE" 2>/dev/null)"; then : else OWNER="$(stat -f '%u' "$ENV_FILE")" fi EXPECTED_OWNER_UID="${EXPECTED_PRODUCTION_ENV_OWNER_UID:-$(id -u)}" if [[ ! "$EXPECTED_OWNER_UID" =~ ^[0-9]+$ ]] || [ "$OWNER" != "$EXPECTED_OWNER_UID" ]; then echo "production environment file has an invalid owner" >&2 exit 1 fi require_selector() { local key="$1" local expected="$2" local count local definition_pattern definition_pattern="^[[:space:]]*(export[[:space:]]+)?${key}([[:space:]]*=|[[:space:]]*$)" count="$(grep -Ec "$definition_pattern" "$ENV_FILE" || true)" if [ "$count" -ne 1 ] || ! grep -Fqx "${key}=${expected}" "$ENV_FILE"; then echo "invalid production selector: $key" >&2 exit 1 fi } require_selector APP_ENV_FILE ../.env.production require_selector CADDYFILE_PATH ./Caddyfile.production.selfhosted require_selector SITE_ADDRESS https://jyotisha.chat require_selector AUTH_PROVIDER self-hosted require_selector SELF_HOSTED_IDENTITY_ENABLED true require_selector AUTH_USER_ORIGIN https://jyotisha.chat require_selector ADMIN_USER_ORIGIN https://admin.jyotisha.chat require_literal() { local key="$1" local minimum_length="$2" local count value count="$(grep -Ec "^${key}=" "$ENV_FILE" || true)" if [ "$count" -ne 1 ]; then echo "invalid production identity setting: $key" >&2 exit 1 fi value="$(grep -E "^${key}=" "$ENV_FILE")" value="${value#*=}" if [ "${#value}" -lt "$minimum_length" ] || [[ "$value" == *'$'* || "$value" == *'"'* || "$value" == *"'"* ]]; then echo "invalid production identity setting: $key" >&2 exit 1 fi LITERAL_VALUE="$value" } require_literal IDENTITY_DATABASE_URL 50 identity_database_url="$LITERAL_VALUE" if ! [[ "$identity_database_url" =~ ^postgresql://identity_runtime:([A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+@postgres:5432/jyotisha$ ]]; then echo "invalid production identity setting: IDENTITY_DATABASE_URL" >&2 exit 1 fi require_literal APP_DATABASE_URL 45 app_database_url="$LITERAL_VALUE" if ! [[ "$app_database_url" =~ ^postgresql://app_runtime:([A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+@postgres:5432/jyotisha$ ]]; then echo "invalid production database setting: APP_DATABASE_URL" >&2 exit 1 fi require_literal SERVICE_DATABASE_URL 49 service_database_url="$LITERAL_VALUE" if ! [[ "$service_database_url" =~ ^postgresql://service_runtime:([A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+@postgres:5432/jyotisha$ ]]; then echo "invalid production database setting: SERVICE_DATABASE_URL" >&2 exit 1 fi require_literal ADMIN_DATABASE_URL 45 admin_database_url="$LITERAL_VALUE" if ! [[ "$admin_database_url" =~ ^postgresql://admin_runtime:([A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+@postgres:5432/jyotisha$ ]]; then echo "invalid production database setting: ADMIN_DATABASE_URL" >&2 exit 1 fi require_literal BETTER_AUTH_USER_SECRET 32 require_literal RESEND_API_KEY 10 require_literal RESEND_FROM_EMAIL 5 if [[ "$LITERAL_VALUE" != *@* ]]; then echo "invalid production identity setting: RESEND_FROM_EMAIL" >&2 exit 1 fi require_literal ADMIN_EMAILS 3 if [[ "$LITERAL_VALUE" != *@* ]]; then echo "invalid production identity setting: ADMIN_EMAILS" >&2 exit 1 fi require_literal EPAY_CONFIG_ENCRYPTION_KEY 44 if [ "${#LITERAL_VALUE}" -ne 44 ] || [[ ! "$LITERAL_VALUE" =~ ^[A-Za-z0-9+/]{43}=$ ]]; then echo "invalid production identity setting: EPAY_CONFIG_ENCRYPTION_KEY" >&2 exit 1 fi require_literal MODEL_PROVIDER_CONFIG_ENCRYPTION_KEY 44 if [ "${#LITERAL_VALUE}" -ne 44 ] || [[ ! "$LITERAL_VALUE" =~ ^[A-Za-z0-9+/]{43}=$ ]]; then echo "invalid production model provider setting: MODEL_PROVIDER_CONFIG_ENCRYPTION_KEY" >&2 exit 1 fi legacy_model_setting_pattern='^(OPENAI_API_KEY|ANTHROPIC_API_KEY|DEEPSEEK_API_KEY|LLM_API_KEY|LLM_MODELS_JSON|LLM_BASE_URL|LLM_MODEL|LLM_DEFAULT_MODEL_ID|LLM_PROVIDER_ID|MASTRA_MODEL|MODEL_PROVIDER_[A-Z0-9_]+_API_KEY)=' if grep -Eq "$legacy_model_setting_pattern" "$ENV_FILE"; then echo "legacy model environment settings are forbidden" >&2 exit 1 fi require_selector EPAY_CHAT_ENABLED false require_literal JYOTISH_DYNAMIC_RECTIFICATION_TOKEN 32 personal_report_enabled_count="$(grep -Ec '^PERSONAL_REPORT_ENABLED=' "$ENV_FILE" || true)" personal_report_enabled="$(grep -E '^PERSONAL_REPORT_ENABLED=' "$ENV_FILE" || true)" personal_report_enabled="${personal_report_enabled#*=}" if [ "$personal_report_enabled_count" -ne 1 ] || [[ "$personal_report_enabled" != "true" && "$personal_report_enabled" != "false" ]]; then echo "invalid production personal report setting: PERSONAL_REPORT_ENABLED" >&2 exit 1 fi personal_report_daily_limit_count="$(grep -Ec '^PERSONAL_REPORT_DAILY_LIMIT=' "$ENV_FILE" || true)" personal_report_daily_limit="$(grep -E '^PERSONAL_REPORT_DAILY_LIMIT=' "$ENV_FILE" || true)" personal_report_daily_limit="${personal_report_daily_limit#*=}" if [ "$personal_report_daily_limit_count" -ne 1 ] || [[ ! "$personal_report_daily_limit" =~ ^[1-9][0-9]*$ ]]; then echo "invalid production personal report setting: PERSONAL_REPORT_DAILY_LIMIT" >&2 exit 1 fi echo "production environment selectors: valid"