#!/bin/sh set -eu umask 077 ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) domain=${1:-} target=${2:-"$ROOT/.env.production"} usage() { echo "Usage: $0 DOMAIN [OUTPUT_FILE]" >&2 } if [ -z "$domain" ]; then usage exit 1 fi if ! printf '%s\n' "$domain" | grep -Eq '^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$'; then echo "DOMAIN must be a lowercase ASCII DNS hostname without a scheme, port, or path." >&2 exit 1 fi for command in awk docker grep mktemp openssl stat; do if ! command -v "$command" >/dev/null 2>&1; then echo "Required command is unavailable: $command" >&2 exit 1 fi done if [ ! -S /var/run/docker.sock ]; then echo "/var/run/docker.sock is unavailable; run this on the target Docker host." >&2 exit 1 fi if [ -e "$target" ]; then echo "Refusing to overwrite existing deployment environment: $target" >&2 exit 1 fi target_dir=$(dirname -- "$target") if [ ! -d "$target_dir" ]; then echo "Output directory does not exist: $target_dir" >&2 exit 1 fi database_mode=${PRODUCTION_DATABASE_MODE:-container} app_topology=${PRODUCTION_APP_TOPOLOGY:-compact} compose_project_name=${PRODUCTION_COMPOSE_PROJECT_NAME:-who_need_help} public_edge_enabled=${PRODUCTION_PUBLIC_EDGE_ENABLED:-true} public_edge_network=${PRODUCTION_PUBLIC_EDGE_NETWORK:-who_need_help_public_edge} public_upstream_name=${PRODUCTION_PUBLIC_UPSTREAM_NAME:-who-need-help-production} codex_session_id=${PRODUCTION_CODEX_SESSION_ID:-} if [ -z "$codex_session_id" ]; then echo "PRODUCTION_CODEX_SESSION_ID is required for the Build Week feedback page." >&2 exit 1 fi case "$compose_project_name" in *[!a-zA-Z0-9_-]* | '') echo "PRODUCTION_COMPOSE_PROJECT_NAME must contain only letters, numbers, underscores, or hyphens." >&2 exit 1 ;; esac case "$database_mode" in container) postgres_password=$(openssl rand -hex 32) database_url="ecto://postgres:$postgres_password@db/who_need_help" ;; external) postgres_password= database_url=${PRODUCTION_DATABASE_URL:-} if [ -z "$database_url" ]; then echo "PRODUCTION_DATABASE_URL is required when PRODUCTION_DATABASE_MODE=external." >&2 exit 1 fi ;; *) echo "PRODUCTION_DATABASE_MODE must be container or external." >&2 exit 1 ;; esac database_socket_dir=${PRODUCTION_DATABASE_SOCKET_DIR:-} if [ -n "$database_socket_dir" ]; then case "$database_socket_dir" in /*) ;; *) echo "PRODUCTION_DATABASE_SOCKET_DIR must be an absolute path." >&2; exit 1 ;; esac if [ "$database_mode" != external ]; then echo "PRODUCTION_DATABASE_SOCKET_DIR is only valid when PRODUCTION_DATABASE_MODE=external." >&2 exit 1 fi fi case "$app_topology" in compact | split) ;; *) echo "PRODUCTION_APP_TOPOLOGY must be compact or split." >&2; exit 1 ;; esac case "$public_edge_enabled" in true | false) ;; *) echo "PRODUCTION_PUBLIC_EDGE_ENABLED must be true or false." >&2; exit 1 ;; esac secret_key_base=$(openssl rand -hex 64) handover_secret=$(openssl rand -hex 64) release_cookie=$(openssl rand -hex 64) metrics_token=$(openssl rand -hex 32) docker_socket_gid=$(stat -c '%g' /var/run/docker.sock) http_bind_address=${PRODUCTION_HTTP_BIND_ADDRESS:-127.0.0.1} http_port=${PRODUCTION_HTTP_PORT:-4010} trusted_proxy_ips=${PRODUCTION_TRAEFIK_TRUSTED_IPS:-REPLACE_WITH_VERIFIED_PROXY_IP_OR_CIDR} smtp_relay=${PRODUCTION_SMTP_RELAY:-REPLACE_WITH_TRANSACTIONAL_SMTP_RELAY} smtp_port=${PRODUCTION_SMTP_PORT:-587} smtp_username=${PRODUCTION_SMTP_USERNAME:-} smtp_password=${PRODUCTION_SMTP_PASSWORD:-} smtp_auth=${PRODUCTION_SMTP_AUTH:-always} smtp_tls=${PRODUCTION_SMTP_TLS:-always} smtp_ssl=${PRODUCTION_SMTP_SSL:-false} email_from_address=${PRODUCTION_EMAIL_FROM_ADDRESS:-"contact@$domain"} support_inbox_address=${PRODUCTION_SUPPORT_INBOX_ADDRESS:-} tmp=$(mktemp "$target_dir/.production-env.XXXXXX") trap 'rm -f "$tmp"' EXIT HUP INT TERM chmod 600 "$tmp" DOMAIN=$domain \ DATABASE_MODE_VALUE=$database_mode \ APP_TOPOLOGY_VALUE=$app_topology \ COMPOSE_PROJECT_NAME_VALUE=$compose_project_name \ HTTP_BIND_ADDRESS_VALUE=$http_bind_address \ HTTP_PORT_VALUE=$http_port \ PUBLIC_EDGE_ENABLED_VALUE=$public_edge_enabled \ PUBLIC_EDGE_NETWORK_VALUE=$public_edge_network \ PUBLIC_UPSTREAM_NAME_VALUE=$public_upstream_name \ DOCKER_SOCKET_GID_VALUE=$docker_socket_gid \ TRUSTED_PROXY_IPS_VALUE=$trusted_proxy_ips \ POSTGRES_PASSWORD_VALUE=$postgres_password \ DATABASE_URL_VALUE=$database_url \ DATABASE_SOCKET_DIR_VALUE=$database_socket_dir \ SECRET_KEY_BASE_VALUE=$secret_key_base \ HANDOVER_SECRET_VALUE=$handover_secret \ RELEASE_COOKIE_VALUE=$release_cookie \ METRICS_TOKEN_VALUE=$metrics_token \ SMTP_RELAY_VALUE=$smtp_relay \ SMTP_PORT_VALUE=$smtp_port \ SMTP_USERNAME_VALUE=$smtp_username \ SMTP_PASSWORD_VALUE=$smtp_password \ SMTP_AUTH_VALUE=$smtp_auth \ SMTP_TLS_VALUE=$smtp_tls \ SMTP_SSL_VALUE=$smtp_ssl \ EMAIL_FROM_ADDRESS_VALUE=$email_from_address \ SUPPORT_INBOX_ADDRESS_VALUE=$support_inbox_address \ CODEX_SESSION_ID_VALUE=$codex_session_id \ awk ' BEGIN { replacement["DEPLOYMENT_TARGET"] = "compose" replacement["DEPLOYMENT_ENV"] = "production" replacement["COMPOSE_PROJECT_NAME"] = ENVIRON["COMPOSE_PROJECT_NAME_VALUE"] replacement["APP_TOPOLOGY"] = ENVIRON["APP_TOPOLOGY_VALUE"] replacement["DATABASE_MODE"] = ENVIRON["DATABASE_MODE_VALUE"] replacement["HTTP_BIND_ADDRESS"] = ENVIRON["HTTP_BIND_ADDRESS_VALUE"] replacement["HTTP_PORT"] = ENVIRON["HTTP_PORT_VALUE"] replacement["PUBLIC_EDGE_ENABLED"] = ENVIRON["PUBLIC_EDGE_ENABLED_VALUE"] replacement["PUBLIC_EDGE_NETWORK"] = ENVIRON["PUBLIC_EDGE_NETWORK_VALUE"] replacement["PUBLIC_UPSTREAM_NAME"] = ENVIRON["PUBLIC_UPSTREAM_NAME_VALUE"] replacement["DOCKER_SOCKET_GID"] = ENVIRON["DOCKER_SOCKET_GID_VALUE"] replacement["TRAEFIK_TRUSTED_IPS"] = ENVIRON["TRUSTED_PROXY_IPS_VALUE"] replacement["TRAEFIK_PROJECT_CONSTRAINT"] = ENVIRON["COMPOSE_PROJECT_NAME_VALUE"] replacement["TRAEFIK_DOCKER_NETWORK"] = ENVIRON["COMPOSE_PROJECT_NAME_VALUE"] "_ingress" replacement["PHX_HOST"] = ENVIRON["DOMAIN"] replacement["PHX_SCHEME"] = "https" replacement["PHX_URL_PORT"] = "443" replacement["WNH_DEBUG_BASE_URL"] = "https://" ENVIRON["DOMAIN"] replacement["WNH_BASE_URL"] = "https://" ENVIRON["DOMAIN"] replacement["POSTGRES_PASSWORD"] = ENVIRON["POSTGRES_PASSWORD_VALUE"] replacement["DATABASE_URL"] = ENVIRON["DATABASE_URL_VALUE"] replacement["DATABASE_SOCKET_DIR"] = ENVIRON["DATABASE_SOCKET_DIR_VALUE"] replacement["SECRET_KEY_BASE"] = ENVIRON["SECRET_KEY_BASE_VALUE"] replacement["HANDOVER_SECRET"] = ENVIRON["HANDOVER_SECRET_VALUE"] replacement["RELEASE_COOKIE"] = ENVIRON["RELEASE_COOKIE_VALUE"] replacement["METRICS_TOKEN"] = ENVIRON["METRICS_TOKEN_VALUE"] replacement["SMTP_RELAY"] = ENVIRON["SMTP_RELAY_VALUE"] replacement["SMTP_PORT"] = ENVIRON["SMTP_PORT_VALUE"] replacement["SMTP_USERNAME"] = ENVIRON["SMTP_USERNAME_VALUE"] replacement["SMTP_PASSWORD"] = ENVIRON["SMTP_PASSWORD_VALUE"] replacement["SMTP_AUTH"] = ENVIRON["SMTP_AUTH_VALUE"] replacement["SMTP_TLS"] = ENVIRON["SMTP_TLS_VALUE"] replacement["SMTP_SSL"] = ENVIRON["SMTP_SSL_VALUE"] replacement["EMAIL_FROM_ADDRESS"] = ENVIRON["EMAIL_FROM_ADDRESS_VALUE"] replacement["SUPPORT_INBOX_ADDRESS"] = ENVIRON["SUPPORT_INBOX_ADDRESS_VALUE"] replacement["CODEX_SESSION_ID"] = ENVIRON["CODEX_SESSION_ID_VALUE"] } { separator = index($0, "=") key = separator > 1 ? substr($0, 1, separator - 1) : "" if (key in replacement) { print key "=" replacement[key] } else { print } } ' "$ROOT/.env.example" >"$tmp" mv "$tmp" "$target" chmod 600 "$target" trap - EXIT HUP INT TERM unset postgres_password secret_key_base handover_secret release_cookie metrics_token echo "Generated independent deployment secrets without printing them." echo "Created mode-0600 environment: $target" echo "Run scripts/validate-production-env.sh '$target' '$domain' after configuring the verified proxy and SMTP values."