Prepare production artifacts before release

This commit is contained in:
SimpleTest 2026-08-13 08:18:10 +03:00
parent d79e4e436b
commit 9aa371105f
4 changed files with 58 additions and 18 deletions

View File

@ -1272,6 +1272,21 @@ allows only the absent Play App Signing certificate; the stricter
publishing Android through Google Play. The plan neither uploads a bundle nor publishing Android through Google Play. The plan neither uploads a bundle nor
creates a backup. creates a backup.
Prepare and verify the immutable Git bundle and `linux/amd64` image archive on
the development workstation before authorising any production mutation:
```bash
./scripts/production-release-clean.sh prepare whoneedhelp
```
`prepare` repeats the read-only production and environment checks, reads the
production image-build inputs into a mode-`0600` temporary file, and then
creates or verifies the commit-bound artifacts under `output/releases/`. It
does not upload an artifact, create a production backup, change the remote
checkout, load an image, run a migration, or restart a service. A later
`apply` for the same commit verifies and reuses those exact artifacts instead
of compiling them again.
After reviewing the exact commit printed by the plan, execution additionally After reviewing the exact commit printed by the plan, execution additionally
requires an explicit per-commit confirmation: requires an explicit per-commit confirmation:
@ -1361,6 +1376,8 @@ including those edits:
```bash ```bash
./scripts/production-release-clean.sh plan whoneedhelp ./scripts/production-release-clean.sh plan whoneedhelp
./scripts/production-release-clean.sh prepare whoneedhelp
WNH_PRODUCTION_RELEASE_CONFIRM='whoneedhelp.com:EXACT_COMMIT' \ WNH_PRODUCTION_RELEASE_CONFIRM='whoneedhelp.com:EXACT_COMMIT' \
./scripts/production-release-clean.sh apply whoneedhelp ./scripts/production-release-clean.sh apply whoneedhelp
``` ```

View File

@ -7,9 +7,9 @@ action=${1:-plan}
ssh_target=${2:-whoneedhelp} ssh_target=${2:-whoneedhelp}
case "$action" in case "$action" in
plan | apply) ;; plan | prepare | apply) ;;
*) *)
echo "Usage: $0 [plan|apply] [SSH_TARGET]" >&2 echo "Usage: $0 [plan|prepare|apply] [SSH_TARGET]" >&2
exit 2 exit 2
;; ;;
esac esac

View File

@ -8,9 +8,9 @@ remote_root=${WNH_PRODUCTION_REMOTE_ROOT:-/srv/who_need_help-production}
expected_domain=${WNH_PRODUCTION_DOMAIN:-whoneedhelp.com} expected_domain=${WNH_PRODUCTION_DOMAIN:-whoneedhelp.com}
case "$action" in case "$action" in
plan | apply) ;; plan | prepare | apply) ;;
*) *)
echo "Usage: $0 [plan|apply] [SSH_TARGET]" >&2 echo "Usage: $0 [plan|prepare|apply] [SSH_TARGET]" >&2
exit 2 exit 2
;; ;;
esac esac
@ -106,13 +106,14 @@ if [[ -n "$(git -C "$ROOT" status --porcelain --untracked-files=no)" ]]; then
fi fi
confirmation="$expected_domain:$local_commit" confirmation="$expected_domain:$local_commit"
if [[ "${WNH_PRODUCTION_RELEASE_CONFIRM:-}" != "$confirmation" ]]; then if [[ "$action" == "apply" ]]; then
if [[ "${WNH_PRODUCTION_RELEASE_CONFIRM:-}" != "$confirmation" ]]; then
echo "Release execution requires explicit approval in this exact process:" >&2 echo "Release execution requires explicit approval in this exact process:" >&2
echo "WNH_PRODUCTION_RELEASE_CONFIRM=$confirmation $0 apply $ssh_target" >&2 echo "WNH_PRODUCTION_RELEASE_CONFIRM=$confirmation $0 apply $ssh_target" >&2
exit 2 exit 2
fi fi
if [[ "$migration_policy" == "forward_only" ]]; then if [[ "$migration_policy" == "forward_only" ]]; then
forward_confirmation="$expected_domain:$local_commit:forward-only" forward_confirmation="$expected_domain:$local_commit:forward-only"
if [[ "${WNH_PRODUCTION_FORWARD_ONLY_CONFIRM:-}" != "$forward_confirmation" ]]; then if [[ "${WNH_PRODUCTION_FORWARD_ONLY_CONFIRM:-}" != "$forward_confirmation" ]]; then
echo "This release contains migrations that are not safe for an automatic old-image rollback." >&2 echo "This release contains migrations that are not safe for an automatic old-image rollback." >&2
@ -122,6 +123,7 @@ if [[ "$migration_policy" == "forward_only" ]]; then
echo " WNH_PRODUCTION_RELEASE_CONFIRM=$confirmation $0 apply $ssh_target" >&2 echo " WNH_PRODUCTION_RELEASE_CONFIRM=$confirmation $0 apply $ssh_target" >&2
exit 2 exit 2
fi fi
fi
fi fi
"$ROOT/scripts/prepare-production-release.sh" "$ROOT/scripts/prepare-production-release.sh"
@ -151,6 +153,11 @@ chmod 600 "$production_env"
"$ROOT/scripts/prepare-production-images.sh" "$production_env" "$ROOT/scripts/prepare-production-images.sh" "$production_env"
cleanup_production_env cleanup_production_env
if [[ "$action" == "prepare" ]]; then
echo "Production release artifacts are prepared and verified locally; no remote state was changed."
exit 0
fi
remote_release_dir="$remote_root/output/releases/incoming" remote_release_dir="$remote_root/output/releases/incoming"
remote_bundle="$remote_release_dir/$(basename -- "$bundle")" remote_bundle="$remote_release_dir/$(basename -- "$bundle")"
remote_image_archive="$remote_release_dir/$(basename -- "$image_archive")" remote_image_archive="$remote_release_dir/$(basename -- "$image_archive")"

View File

@ -119,6 +119,22 @@ class ProductionReleaseCleanTest(unittest.TestCase):
self.assertEqual(result.returncode, 23) self.assertEqual(result.returncode, 23)
self.assert_release_worktree_removed() self.assert_release_worktree_removed()
def test_prepare_is_forwarded_through_the_same_clean_checkout(self):
dirty = self.project / "README.md"
dirty.write_text("user-owned change\n", encoding="utf-8")
self.run_command(
[str(self.scripts / WRAPPER.name), "prepare", "production-alias"],
env=self.environment(),
)
captured = self.capture.read_text(encoding="utf-8")
self.assertIn("status=\n", captured)
self.assertIn("args=prepare production-alias", captured)
self.assertIn(f"commit={self.commit}", captured)
self.assertEqual(dirty.read_text(encoding="utf-8"), "user-owned change\n")
self.assert_release_worktree_removed()
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()