who_need_help/scripts/validate-deployment-isolation.sh
SimpleTest 8c281cc756
Some checks are pending
Quality / full-local-gates (push) Waiting to run
Replace UniSender delivery with isolated SMTP
2026-07-22 00:38:37 +03:00

143 lines
5.0 KiB
Bash
Executable File

#!/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" == 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
# Some relays (including Brevo) issue one account-level SMTP login and
# isolate applications with independently revocable SMTP passwords.
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."