#!/usr/bin/env bash set -euo pipefail umask 077 ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) action=${1:-plan} ssh_target=${2:-whoneedhelp} remote_root=${WNH_TEST_REMOTE_ROOT:-/srv/who_need_help-test} production_root=${WNH_PRODUCTION_REMOTE_ROOT:-/srv/who_need_help-production} expected_domain=${WNH_TEST_DOMAIN:-test.whoneedhelp.com} case "$action" in plan | prepare | apply) ;; *) echo "Usage: $0 [plan|prepare|apply] [SSH_TARGET]" >&2 exit 2 ;; esac for command in git gzip mktemp pg_restore realpath scp sha256sum ssh; do command -v "$command" >/dev/null 2>&1 || { echo "Required command is unavailable: $command" >&2 exit 2 } done local_commit=$(git -C "$ROOT" rev-parse --verify HEAD) remote_commit=$( ssh -o BatchMode=yes "$ssh_target" \ "git -C '$remote_root' rev-parse --verify HEAD" ) printf 'Local candidate commit: %s\n' "$local_commit" printf 'Current test commit: %s\n' "$remote_commit" git -C "$ROOT" cat-file -e "$remote_commit^{commit}" 2>/dev/null || { echo "The test commit is not present in the local object database." >&2 exit 2 } git -C "$ROOT" merge-base --is-ancestor "$remote_commit" "$local_commit" || { echo "The local candidate is not a fast-forward from the test commit." >&2 exit 2 } printf 'Pending commits: %s\n' \ "$(git -C "$ROOT" rev-list --count "$remote_commit..$local_commit")" legacy_edge_paths=(compose.edge.yaml deploy/caddy/Caddyfile) if ! git -C "$ROOT" diff --quiet \ "$remote_commit" "$local_commit" -- "${legacy_edge_paths[@]}"; then echo "The test application release contains shared edge routing changes." >&2 echo "Move route changes through the independent server-edge workflow." >&2 exit 2 fi echo "Shared edge routing files are unchanged; the test release will not manage Caddy." migration_policy_output=$( "$ROOT/scripts/release-migration-policy.sh" "$remote_commit" "$local_commit" ) printf '%s\n' "$migration_policy_output" migration_policy=$(awk -F= '$1 == "migration_policy" {print $2}' <<<"$migration_policy_output") plan_failed=0 if ! ssh -o BatchMode=yes "$ssh_target" \ "cd '$remote_root' && ./scripts/validate-test-env.sh .env '$expected_domain'"; then plan_failed=1 fi if ! ssh -o BatchMode=yes "$ssh_target" \ "cd '$remote_root' && ./scripts/validate-deployment-isolation.sh '$remote_root' '$production_root'"; then plan_failed=1 fi if ! ssh -o BatchMode=yes "$ssh_target" \ "bash -s -- plan '$remote_root' '$expected_domain'" \ <"$ROOT/scripts/test-release-remote.sh"; then plan_failed=1 fi if ! ssh -o BatchMode=yes "$ssh_target" \ "cd '$remote_root' && ./scripts/check-environment-readiness.sh .env --require-server-release"; then plan_failed=1 fi if [[ "$plan_failed" -ne 0 ]]; then echo "Test release plan has blocking checks; no remote state was changed." >&2 exit 1 fi if [[ "$action" == plan ]]; then echo "Test release plan passed; no remote state was changed." exit 0 fi [[ -z "$(git -C "$ROOT" status --porcelain --untracked-files=normal)" ]] || { echo "Refusing to release a dirty checkout." >&2 exit 2 } artifact_root=${WNH_TEST_RELEASE_ARTIFACT_ROOT:-"$ROOT/output/test-releases"} case "$artifact_root" in /*) ;; *) echo "WNH_TEST_RELEASE_ARTIFACT_ROOT must be an absolute path." >&2 exit 2 ;; esac mkdir -p "$artifact_root" artifact_root=$(realpath --canonicalize-existing "$artifact_root") WNH_PRODUCTION_RELEASE_ARTIFACT_ROOT="$artifact_root" \ "$ROOT/scripts/prepare-production-release.sh" release_dir="$artifact_root/$local_commit" bundle="$release_dir/who_need_help-$local_commit.bundle" image_archive="$release_dir/who_need_help-$local_commit-test-images-linux-amd64.tar.gz" image_checksum="$image_archive.sha256" image_manifest="$release_dir/who_need_help-$local_commit-test-images.manifest" test_env=$(mktemp) cleanup_test_env() { trap - EXIT HUP INT TERM rm -f "$test_env" } trap cleanup_test_env EXIT HUP INT TERM scp -p "$ssh_target:$remote_root/.env" "$test_env" chmod 600 "$test_env" WNH_TEST_RELEASE_ARTIFACT_ROOT="$artifact_root" \ "$ROOT/scripts/prepare-test-images.sh" "$test_env" cleanup_test_env if [[ "$action" == prepare ]]; then echo "Test release artifacts are prepared and verified locally; no remote state was changed." exit 0 fi remote_main=$(git -C "$ROOT" ls-remote origin refs/heads/main | awk '{print $1}') [[ "$remote_main" == "$local_commit" ]] || { echo "origin/main does not contain the exact approved test candidate." >&2 echo "Expected: $local_commit" >&2 echo "Observed: ${remote_main:-missing}" >&2 exit 2 } confirmation="$expected_domain:$local_commit" [[ "${WNH_TEST_RELEASE_CONFIRM:-}" == "$confirmation" ]] || { echo "Test release execution requires exact approval:" >&2 echo "WNH_TEST_RELEASE_CONFIRM=$confirmation $0 apply $ssh_target" >&2 exit 2 } if [[ "$migration_policy" == forward_only ]]; then forward_confirmation="$expected_domain:$local_commit:forward-only" [[ "${WNH_TEST_FORWARD_ONLY_CONFIRM:-}" == "$forward_confirmation" ]] || { echo "This test release contains forward-only migrations." >&2 echo "WNH_TEST_FORWARD_ONLY_CONFIRM=$forward_confirmation \\" >&2 echo " WNH_TEST_RELEASE_CONFIRM=$confirmation $0 apply $ssh_target" >&2 exit 2 } fi remote_release_dir="$remote_root/output/releases/incoming" remote_bundle="$remote_release_dir/$(basename -- "$bundle")" remote_image_archive="$remote_release_dir/$(basename -- "$image_archive")" remote_image_manifest="$remote_release_dir/$(basename -- "$image_manifest")" timestamp=$(date -u +%Y%m%dT%H%M%SZ) remote_backup="$remote_root/output/backups/test/pre-$timestamp-${local_commit:0:12}.dump" ssh -o BatchMode=yes "$ssh_target" "install -d -m 700 '$remote_release_dir'" scp -p \ "$bundle" "$bundle.sha256" \ "$image_archive" "$image_checksum" "$image_manifest" \ "$ssh_target:$remote_release_dir/" ssh -o BatchMode=yes "$ssh_target" \ "cd '$remote_root' && ./scripts/backup-compose.sh '$remote_backup'" local_backup_dir="$ROOT/output/test-backups/$timestamp-${local_commit:0:12}" mkdir -p "$local_backup_dir" chmod 700 "$ROOT/output" "$ROOT/output/test-backups" "$local_backup_dir" scp -p \ "$ssh_target:$remote_backup" \ "$ssh_target:$remote_backup.sha256" \ "$local_backup_dir/" ( cd "$local_backup_dir" sha256sum --check "$(basename -- "$remote_backup.sha256")" >/dev/null ) pg_restore --list "$local_backup_dir/$(basename -- "$remote_backup")" >/dev/null echo "Copied and independently verified the pre-release test backup outside the server." quoted_confirmation=$(printf '%q' "$confirmation") quoted_forward_confirmation=$(printf '%q' "${WNH_TEST_FORWARD_ONLY_CONFIRM:-}") ssh -o BatchMode=yes "$ssh_target" \ "WNH_TEST_RELEASE_CONFIRM=$quoted_confirmation WNH_TEST_FORWARD_ONLY_CONFIRM=$quoted_forward_confirmation bash -s -- apply '$remote_root' '$expected_domain' '$remote_bundle' '$local_commit' '$remote_backup' '$migration_policy' '$remote_image_archive' '$remote_image_manifest'" \ <"$ROOT/scripts/test-release-remote.sh" echo "Test release and public health verification completed."