#!/usr/bin/env bash set -euo pipefail ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) env_file=${1:-"$ROOT/.env"} mode=${2:-} if [[ "$env_file" != /* ]]; then env_file="$ROOT/$env_file" fi if [[ "$mode" != "" && "$mode" != "--require-release" ]]; then echo "Usage: $0 [ENV_FILE] [--require-release]" >&2 exit 2 fi if [[ ! -f "$env_file" ]]; then echo "Environment file does not exist: $env_file" >&2 exit 2 fi if [[ "$(stat -c '%a' "$env_file")" != 600 ]]; then echo "Environment file must have mode 0600: $env_file" >&2 exit 2 fi read_value() { local key=$1 awk -v key="$key" ' index($0, key "=") == 1 { value = substr($0, length(key) + 2) if (value ~ /^".*"$/ || value ~ /^'\''.*'\''$/) { value = substr(value, 2, length(value) - 2) } print value found = 1 exit } END { if (!found) exit 1 } ' "$env_file" } value() { read_value "$1" 2>/dev/null || true } is_set() { [[ -n "$(value "$1")" ]] } all_set() { local key for key in "$@"; do is_set "$key" || return 1 done } all_empty() { local key for key in "$@"; do is_set "$key" && return 1 done return 0 } contains_template_marker() { local observed=$1 [[ "$observed" == *REPLACE* || "$observed" == *GENERATE* || "$observed" == *example.com* || "$observed" == *example.invalid* ]] } valid_fcm_service_account_json() { jq -e ' .type == "service_account" and (.project_id | type == "string" and length > 0) and (.client_email | type == "string" and length > 0) and (.private_key | type == "string" and length > 0) ' >/dev/null 2>&1 } failures=0 warnings=0 ready() { printf 'READY %-24s %s\n' "$1" "$2" } local_only() { printf 'LOCAL_ONLY %-24s %s\n' "$1" "$2" warnings=$((warnings + 1)) } missing() { printf 'MISSING %-24s %s\n' "$1" "$2" failures=$((failures + 1)) } invalid() { printf 'INVALID %-24s %s\n' "$1" "$2" failures=$((failures + 1)) } partial() { printf 'PARTIAL %-24s %s\n' "$1" "$2" failures=$((failures + 1)) } deployment_env=$(value DEPLOYMENT_ENV) phx_host=$(value PHX_HOST) phx_scheme=$(value PHX_SCHEME) phx_port=$(value PHX_URL_PORT) base_url=$(value WNH_BASE_URL) debug_base_url=$(value WNH_DEBUG_BASE_URL) if all_set DEPLOYMENT_ENV PHX_HOST PHX_SCHEME PHX_URL_PORT WNH_BASE_URL WNH_DEBUG_BASE_URL && [[ "$base_url" == "$debug_base_url" ]] && [[ "$base_url" == "$phx_scheme://$phx_host" || "$base_url" == "$phx_scheme://$phx_host:$phx_port" ]] && ! contains_template_marker "$base_url"; then ready "public origin" "deployment=$deployment_env; one canonical Android/web origin" else invalid "public origin" "DEPLOYMENT_ENV/PHX_*/WNH_*_BASE_URL are incomplete or inconsistent" fi if all_set SECRET_KEY_BASE HANDOVER_SECRET RELEASE_COOKIE METRICS_TOKEN; then ready "application secrets" "four required independent values are present" else missing "application secrets" "SECRET_KEY_BASE, HANDOVER_SECRET, RELEASE_COOKIE, METRICS_TOKEN" fi smtp_relay=$(value SMTP_RELAY) email_delivery_provider=$(value EMAIL_DELIVERY_PROVIDER) email_delivery_provider=${email_delivery_provider:-smtp} if [[ "$email_delivery_provider" != "smtp" ]]; then invalid "transactional email" "EMAIL_DELIVERY_PROVIDER must be smtp" elif ! all_set SMTP_RELAY SMTP_PORT SMTP_AUTH SMTP_TLS SMTP_SSL EMAIL_FROM_ADDRESS; then missing "transactional email" "SMTP transport and sender fields" elif [[ "$smtp_relay" == "mailpit" ]]; then local_only "transactional email" "Mailpit captures messages locally; it cannot deliver public email" elif [[ "$(value SMTP_AUTH)" == "always" ]] && ! all_set SMTP_USERNAME SMTP_PASSWORD; then partial "transactional email" "authenticated SMTP requires both username and password" else ready "transactional email" "external SMTP transport is configured" fi if is_set SUPPORT_INBOX_ADDRESS; then ready "support inbox" "operator destination is configured" else missing "support inbox" "SUPPORT_INBOX_ADDRESS" fi if all_empty GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET; then missing "Google sign-in" "GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET" elif all_set GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET; then ready "Google sign-in" "client ID and secret are both configured" else partial "Google sign-in" "client ID and secret must be configured together" fi if all_empty WEB_PUSH_VAPID_PUBLIC_KEY WEB_PUSH_VAPID_PRIVATE_KEY WEB_PUSH_VAPID_SUBJECT; then missing "browser Web Push" "VAPID public/private keys and subject" elif all_set WEB_PUSH_VAPID_PUBLIC_KEY WEB_PUSH_VAPID_PRIVATE_KEY WEB_PUSH_VAPID_SUBJECT; then case "$(value WEB_PUSH_VAPID_SUBJECT)" in mailto:* | https://*) ready "browser Web Push" "complete VAPID configuration" ;; *) invalid "browser Web Push" "WEB_PUSH_VAPID_SUBJECT must use mailto: or https://" ;; esac else partial "browser Web Push" "all three VAPID values are required together" fi if all_empty WNH_FIREBASE_APPLICATION_ID WNH_FIREBASE_API_KEY \ WNH_FIREBASE_PROJECT_ID WNH_FIREBASE_GCM_SENDER_ID; then missing "Android Firebase client" "four WNH_FIREBASE_* Android client values" elif all_set WNH_FIREBASE_APPLICATION_ID WNH_FIREBASE_API_KEY \ WNH_FIREBASE_PROJECT_ID WNH_FIREBASE_GCM_SENDER_ID; then ready "Android Firebase client" "complete client configuration" else partial "Android Firebase client" "all four WNH_FIREBASE_* values are required together" fi fcm_file=$(value FCM_SERVICE_ACCOUNT_FILE) fcm_base64=$(value FCM_SERVICE_ACCOUNT_JSON_BASE64) if [[ -z "$(value FCM_PROJECT_ID)" && -z "$fcm_file" && -z "$fcm_base64" ]]; then missing "Android FCM delivery" "FCM project ID and one service-account source" elif [[ -z "$(value FCM_PROJECT_ID)" || (-n "$fcm_file" && -n "$fcm_base64") || (-z "$fcm_file" && -z "$fcm_base64") ]]; then partial "Android FCM delivery" "project ID and exactly one credential source are required" elif [[ -n "$fcm_file" ]]; then if [[ "$fcm_file" == /* && -r "$fcm_file" ]] && valid_fcm_service_account_json <"$fcm_file"; then ready "Android FCM delivery" "complete service-account file is configured" else invalid "Android FCM delivery" \ "FCM_SERVICE_ACCOUNT_FILE must be an absolute readable complete service-account JSON file" fi elif printf '%s' "$fcm_base64" | base64 --decode 2>/dev/null | valid_fcm_service_account_json; then ready "Android FCM delivery" "complete Base64 service-account document is configured" else invalid "Android FCM delivery" \ "Base64 credential is not a complete service-account JSON document" fi if all_empty ANDROID_APP_LINKS_PACKAGE_NAME ANDROID_APP_LINKS_SHA256_CERT_FINGERPRINTS; then missing "Android App Links" "package name and signing certificate fingerprint" elif all_set ANDROID_APP_LINKS_PACKAGE_NAME ANDROID_APP_LINKS_SHA256_CERT_FINGERPRINTS; then ready "Android App Links" "package and signing fingerprints are configured" else partial "Android App Links" "package and signing fingerprints must be configured together" fi if all_set WNH_ANDROID_VERSION_CODE WNH_ANDROID_VERSION_NAME \ WNH_ANDROID_SIGNING_KEY_ALIAS WNH_ANDROID_STAGING_SIGNING_KEY_ALIAS; then ready "Android release inputs" "version and separate production/staging signing aliases are present" else missing "Android release inputs" "version code/name and both signing aliases" fi printf '\nEnvironment readiness: %d blocking item(s), %d local-only warning(s).\n' \ "$failures" "$warnings" if [[ "$mode" == "--require-release" && ($failures -ne 0 || $warnings -ne 0) ]]; then exit 1 fi