From 209da8fb615f7aed75e9b078bd3a5c4e7558d60b Mon Sep 17 00:00:00 2001 From: SimpleTest Date: Tue, 21 Jul 2026 17:59:57 +0300 Subject: [PATCH] Add safe deployment revision promotion --- docs/operations.md | 17 +++++ scripts/set-deployment-revision.sh | 104 +++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100755 scripts/set-deployment-revision.sh diff --git a/docs/operations.md b/docs/operations.md index e319bf8..e03d5a0 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -83,6 +83,14 @@ TEST_GOOGLE_OAUTH_CLIENT_SECRET=YOUR_TEST_CLIENT_SECRET \ ./scripts/deploy-up.sh .env ``` +For later test updates, check out the desired clean revision and update only +the image tags. Existing deployment secrets remain unchanged: + +```bash +./scripts/set-deployment-revision.sh .env +./scripts/deploy-up.sh .env +``` + Test always uses its own `who_need_help_test` PostGIS container/volume and Mailpit. Its messages cannot be sent by the production UniSender account. @@ -103,6 +111,15 @@ PRODUCTION_CODEX_SESSION_ID=YOUR_MAIN_CODEX_SESSION_ID \ ./scripts/validate-production-env.sh .env whoneedhelp.com ``` +Promote the exact revision already verified in test by checking out that SHA in +the independent production clone, then update only the production image tags: + +```bash +git checkout --detach +./scripts/set-deployment-revision.sh .env +./scripts/deploy-up.sh .env +``` + Before either public switch, prove cross-environment isolation without printing credentials: diff --git a/scripts/set-deployment-revision.sh b/scripts/set-deployment-revision.sh new file mode 100755 index 0000000..ca0bfab --- /dev/null +++ b/scripts/set-deployment-revision.sh @@ -0,0 +1,104 @@ +#!/bin/sh +set -eu +umask 077 + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +env_file=${1:-"$ROOT/.env"} + +case "$env_file" in + /*) ;; + *) env_file="$ROOT/$env_file" ;; +esac + +if [ ! -f "$env_file" ]; then + echo "Deployment environment does not exist: $env_file" >&2 + exit 1 +fi + +if [ "$(stat -c '%a' "$env_file")" != 600 ]; then + echo "Deployment environment must have mode 0600: $env_file" >&2 + exit 1 +fi + +if [ -n "$(git -C "$ROOT" status --porcelain --untracked-files=no)" ]; then + echo "Refusing to select a deployment revision from a dirty tracked checkout." >&2 + exit 1 +fi + +read_env_value() { + key=$1 + + awk -v key="$key" ' + index($0, key "=") == 1 { + print substr($0, length(key) + 2) + found = 1 + exit + } + END { if (!found) exit 1 } + ' "$env_file" +} + +require_single_key() { + key=$1 + count=$(awk -v key="$key" 'index($0, key "=") == 1 { count++ } END { print count + 0 }' "$env_file") + + if [ "$count" -ne 1 ]; then + echo "$key must occur exactly once in $env_file." >&2 + exit 1 + fi +} + +deployment_env=$(read_env_value DEPLOYMENT_ENV) +domain=$(read_env_value PHX_HOST) +git_sha=$(git -C "$ROOT" rev-parse --short=12 HEAD) + +case "$deployment_env" in + test) + required_keys='APP_IMAGE SOCKET_PROXY_IMAGE POSTGIS_IMAGE' + ;; + production) + required_keys='APP_IMAGE SOCKET_PROXY_IMAGE POSTGIS_IMAGE CADDY_IMAGE' + ;; + *) + echo "DEPLOYMENT_ENV must be test or production." >&2 + exit 1 + ;; +esac + +for key in $required_keys; do + require_single_key "$key" +done + +target_dir=$(dirname -- "$env_file") +tmp=$(mktemp "$target_dir/.deployment-revision.XXXXXX") +trap 'rm -f "$tmp"' EXIT HUP INT TERM +chmod 600 "$tmp" + +DEPLOYMENT_ENV_VALUE=$deployment_env GIT_SHA_VALUE=$git_sha awk ' + BEGIN { + prefix = ENVIRON["DEPLOYMENT_ENV_VALUE"] + sha = ENVIRON["GIT_SHA_VALUE"] + replacement["APP_IMAGE"] = "who-need-help:" prefix "-" sha + replacement["SOCKET_PROXY_IMAGE"] = "who-need-help:socket-proxy-" prefix "-" sha + replacement["POSTGIS_IMAGE"] = "who-need-help:postgis-" prefix "-" sha + if (prefix == "production") { + replacement["CADDY_IMAGE"] = "who-need-help:caddy-production-" sha + } + } + { + separator = index($0, "=") + key = separator > 1 ? substr($0, 1, separator - 1) : "" + print (key in replacement) ? key "=" replacement[key] : $0 + } +' "$env_file" >"$tmp" + +mv "$tmp" "$env_file" +chmod 600 "$env_file" +trap - EXIT HUP INT TERM + +case "$deployment_env" in + test) "$ROOT/scripts/validate-test-env.sh" "$env_file" "$domain" ;; + production) "$ROOT/scripts/validate-production-env.sh" "$env_file" "$domain" ;; +esac + +echo "Selected $deployment_env deployment revision $git_sha without rotating secrets."