#!/usr/bin/env bash set -euo pipefail test_dir=${1:-} production_dir=${2:-} if [[ -z "$test_dir" || -z "$production_dir" ]]; then echo "Usage: $0 TEST_CHECKOUT PRODUCTION_CHECKOUT" >&2 exit 1 fi test_dir=$(realpath "$test_dir") production_dir=$(realpath "$production_dir") [[ "$test_dir" != "$production_dir" ]] || { echo "Test and production must be different directories." >&2 exit 1 } for dir in "$test_dir" "$production_dir"; do [[ -d "$dir/.git" ]] || { echo "Independent Git metadata is missing in $dir." >&2 exit 1 } [[ -f "$dir/.env" ]] || { echo "The single runtime configuration is missing: $dir/.env" >&2 exit 1 } [[ "$(stat -c '%a' "$dir/.env")" == 600 ]] || { echo "$dir/.env must have mode 0600." >&2 exit 1 } if find "$dir" -maxdepth 1 -type f \ \( -name '.env.edge' -o -name '.env.test' -o -name '.env.staging' -o -name '.env.production' \) \ -print -quit | grep -q .; then echo "$dir contains a second runtime environment file." >&2 exit 1 fi done test_git=$(git -C "$test_dir" rev-parse --absolute-git-dir) production_git=$(git -C "$production_dir" rev-parse --absolute-git-dir) [[ "$test_git" != "$production_git" ]] || { echo "Test and production must not share a Git worktree." >&2 exit 1 } read_env() { local file=$1 key=$2 awk -v key="$key" ' index($0, key "=") == 1 { print substr($0, length(key) + 2) found = 1 exit } END { if (!found) exit 1 } ' "$file" } test_env="$test_dir/.env" production_env="$production_dir/.env" require_different() { local key=$1 test_value production_value test_value=$(read_env "$test_env" "$key") production_value=$(read_env "$production_env" "$key") [[ -n "$test_value" && -n "$production_value" && "$test_value" != "$production_value" ]] || { echo "$key must be present and different between test and production." >&2 exit 1 } } [[ "$(read_env "$test_env" DEPLOYMENT_ENV)" == test ]] || { echo "Test .env must declare DEPLOYMENT_ENV=test." >&2 exit 1 } [[ "$(read_env "$production_env" DEPLOYMENT_ENV)" == production ]] || { echo "Production .env must declare DEPLOYMENT_ENV=production." >&2 exit 1 } for key in COMPOSE_PROJECT_NAME APP_IMAGE SOCKET_PROXY_IMAGE POSTGIS_IMAGE \ PHX_HOST WNH_BASE_URL PUBLIC_UPSTREAM_NAME DATABASE_URL EMAIL_FROM_ADDRESS \ SECRET_KEY_BASE HANDOVER_SECRET RELEASE_COOKIE METRICS_TOKEN; do require_different "$key" done test_google_id=$(read_env "$test_env" GOOGLE_OAUTH_CLIENT_ID 2>/dev/null || true) production_google_id=$(read_env "$production_env" GOOGLE_OAUTH_CLIENT_ID 2>/dev/null || true) if [[ -n "$test_google_id" || -n "$production_google_id" ]]; then [[ -n "$test_google_id" && -n "$production_google_id" && "$test_google_id" != "$production_google_id" ]] || { echo "Configured test and production Google OAuth clients must be different." >&2 exit 1 } fi test_email_provider=$(read_env "$test_env" EMAIL_DELIVERY_PROVIDER) production_email_provider=$(read_env "$production_env" EMAIL_DELIVERY_PROVIDER) if [[ "$test_email_provider" == unisender_go && "$production_email_provider" == unisender_go ]]; then require_different UNISENDER_GO_API_KEY fi if [[ "$test_email_provider" == smtp && "$(read_env "$test_env" SMTP_RELAY)" != mailpit && "$production_email_provider" == smtp && "$(read_env "$test_env" SMTP_RELAY)" == "$(read_env "$production_env" SMTP_RELAY)" ]]; then require_different SMTP_USERNAME require_different SMTP_PASSWORD fi [[ "$(read_env "$production_env" EMAIL_DELIVERY_PROVIDER)" != smtp || "$(read_env "$production_env" SMTP_RELAY)" != mailpit ]] || { echo "Production email must not target test Mailpit." >&2 exit 1 } test_edge_network=$(read_env "$test_env" PUBLIC_EDGE_NETWORK) production_edge_network=$(read_env "$production_env" PUBLIC_EDGE_NETWORK) [[ "$test_edge_network" == "$production_edge_network" ]] || { echo "Both deployments must intentionally join the same public edge network." >&2 exit 1 } test_upstream=$(read_env "$test_env" PUBLIC_UPSTREAM_NAME) production_edge_test_upstream=$(read_env "$production_env" TEST_UPSTREAM) [[ "$production_edge_test_upstream" == "$test_upstream:4000" ]] || { echo "Production edge TEST_UPSTREAM does not point to the test alias." >&2 exit 1 } production_upstream=$(read_env "$production_env" PUBLIC_UPSTREAM_NAME) production_edge_primary_upstream=$(read_env "$production_env" PRIMARY_UPSTREAM) [[ "$production_edge_primary_upstream" == "$production_upstream:4000" ]] || { echo "Production edge PRIMARY_UPSTREAM does not point to production." >&2 exit 1 } edge_project=$(read_env "$production_env" EDGE_COMPOSE_PROJECT_NAME) test_project=$(read_env "$test_env" COMPOSE_PROJECT_NAME) production_project=$(read_env "$production_env" COMPOSE_PROJECT_NAME) [[ "$edge_project" != "$test_project" && "$edge_project" != "$production_project" ]] || { echo "The edge Compose project must be independent from both application projects." >&2 exit 1 } echo "Verified two independent Git checkouts and one isolated .env per deployment." echo "Verified separate Compose projects, images, databases, OAuth clients, email paths, and secrets."