diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index cc45b1c64..8f105a28a 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -1062,3 +1062,30 @@ jobs: release_id: ${{ needs.create_release.outputs.release_id }} draft: ${{ github.event.inputs.draft_only == 'true' }} target_commitish: ${{ needs.create_release.outputs.target_commitish }} + + # End-to-end install.sh smoke against the just-published release. Catches + # runtime regressions in the documented Proxmox-LXC / systemd install flow + # that the build-time validate-release.sh checks cannot see: the script + # parses fine, signs cleanly, but fails to actually install or boot Pulse. + # This class of regression broke silently across v6 rc.1 → rc.5 because no + # existing gate exercised the documented secure-install commands against + # the published GitHub Release URL. + # + # Gated on validate_release_assets success — the smoke depends on the + # published asset bundle being well-formed, so we only run it after the + # cheaper content checks pass. Skipped for the historical-backfill path + # since that flow re-uploads to an already-published release and the + # smoke would just re-confirm what hasn't changed. + install_sh_smoke: + needs: + - prepare + - validate_release_assets + if: ${{ always() && needs.prepare.result == 'success' && needs.validate_release_assets.result == 'success' && needs.prepare.outputs.historical_asset_backfill_only != 'true' }} + permissions: + contents: read + uses: ./.github/workflows/install-sh-smoke.yml + secrets: inherit + with: + tag: ${{ needs.prepare.outputs.tag }} + version: ${{ needs.prepare.outputs.version }} + repository: ${{ github.repository }} diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index b2b09abe1..e51f5166e 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -37,6 +37,7 @@ server-side update execution surfaces. 14. `.github/workflows/release-dry-run.yml` 15. `.github/workflows/update-demo-server.yml` 16. `.github/workflows/validate-release-assets.yml` +17. `.github/workflows/install-sh-smoke.yml` 16. `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml` 17. `docs/RELEASE_NOTES.md` 18. `docs/releases/` @@ -118,7 +119,25 @@ server-side update execution surfaces. ## Extension Points 1. Add or change deployment-type detection, update planning, or apply behavior through `internal/updates/` -2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, historical published-release integrity backfill, release asset validation status publication, download endpoint checksum/signature header proof, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/backfill-release-assets.yml`, `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, `.github/workflows/update-demo-server.yml`, and `.github/workflows/validate-release-assets.yml` +2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, historical published-release integrity backfill, release asset validation status publication, download endpoint checksum/signature header proof, end-to-end install.sh smoke against the published release, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/backfill-release-assets.yml`, `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/install-sh-smoke.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, `.github/workflows/update-demo-server.yml`, and `.github/workflows/validate-release-assets.yml` + The `install-sh-smoke.yml` workflow runs end-to-end against the + published release in a privileged systemd container: it downloads + `install.sh` and `install.sh.sshsig` from the GitHub Release URL, + runs the README-documented `ssh-keygen -Y verify` step against the + real signed asset using the README's pinned key, re-checks the + server-installer banner / `--version)` arg handler / agent-banner + absence against the published bytes (not just the local build), then + actually runs `bash install.sh --archive --disable-auto-updates` + inside the container and asserts `systemctl is-active pulse`, a 200 + from `/api/health`, and a version match from `/api/version`. + `create-release.yml` must call this workflow as a downstream + `workflow_call` after `validate-release-assets.yml` succeeds for every + release that is not a `historical_asset_backfill_only` run; without + that wiring the smoke gate exists but never protects a release. + The README's pinned `pulse-installer` ed25519 key must verify + `install.sh.sshsig` for the published release; this is enforced by + `scripts/validate-release.sh` at build time and re-verified by + `install-sh-smoke.yml` against the served asset. 3. Add or change root server installer, shell installer, Docker bootstrap installer, Windows installer, container-agent installer, repo-root compose defaults, or auto-update script behavior through `install.sh`, `scripts/install.sh`, `scripts/install-docker.sh`, `scripts/install.ps1`, `scripts/install-container-agent.sh`, `docker-compose.yml`, and `scripts/pulse-auto-update.sh` The top-level `install.sh` asset published on GitHub Releases must be the root Pulse SERVER installer (the LXC / systemd / Proxmox VE installer that diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index 234bc8209..d4a392845 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -2810,6 +2810,7 @@ ".github/workflows/backfill-release-assets.yml", ".github/workflows/create-release.yml", ".github/workflows/helm-pages.yml", + ".github/workflows/install-sh-smoke.yml", ".github/workflows/promote-floating-tags.yml", ".github/workflows/publish-docker.yml", ".github/workflows/publish-helm-chart.yml", @@ -2834,6 +2835,7 @@ "allow_same_subsystem_tests": false, "test_prefixes": [], "exact_files": [ + "scripts/installtests/build_release_assets_test.go", "scripts/release_control/internal/record_rc_to_ga_rehearsal_test.py", "scripts/release_control/release_promotion_policy_support_test.py", "scripts/release_control/release_promotion_policy_test.py", diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 08bd43137..98e9f28d2 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -162,6 +162,17 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) { `Draft release ${RELEASE_ID} target_commitish is ${ACTUAL_TARGET_COMMITISH}, expected ${HEAD_SHA}.`, `./scripts/backfill-release-assets.sh --tag "${{ needs.prepare.outputs.tag }}" --repo "${{ github.repository }}"`, `./scripts/validate-published-release.sh "${{ needs.prepare.outputs.tag }}" "${{ github.repository }}"`, + // End-to-end install.sh smoke must run downstream of + // validate_release_assets on every release that is not a + // historical asset backfill. Without this wiring the smoke + // workflow exists but never actually protects a release — + // exactly the regression class that let rc.1 → rc.5 ship with + // broken install.sh. + `uses: ./.github/workflows/install-sh-smoke.yml`, + `install_sh_smoke:`, + `needs.validate_release_assets.result == 'success'`, + `needs.prepare.outputs.historical_asset_backfill_only != 'true'`, + `repository: ${{ github.repository }}`, } for _, needle := range required { if !strings.Contains(workflow, needle) { diff --git a/scripts/release_control/subsystem_lookup_test.py b/scripts/release_control/subsystem_lookup_test.py index 7af87db38..f98443282 100644 --- a/scripts/release_control/subsystem_lookup_test.py +++ b/scripts/release_control/subsystem_lookup_test.py @@ -4201,6 +4201,7 @@ class SubsystemLookupTest(unittest.TestCase): self.assertEqual( match["verification_requirement"]["exact_files"], [ + "scripts/installtests/build_release_assets_test.go", "scripts/release_control/internal/record_rc_to_ga_rehearsal_test.py", "scripts/release_control/release_promotion_policy_support_test.py", "scripts/release_control/release_promotion_policy_test.py", @@ -4235,6 +4236,7 @@ class SubsystemLookupTest(unittest.TestCase): self.assertEqual( match["verification_requirement"]["exact_files"], [ + "scripts/installtests/build_release_assets_test.go", "scripts/release_control/internal/record_rc_to_ga_rehearsal_test.py", "scripts/release_control/release_promotion_policy_support_test.py", "scripts/release_control/release_promotion_policy_test.py", @@ -4276,6 +4278,7 @@ class SubsystemLookupTest(unittest.TestCase): self.assertEqual( match["verification_requirement"]["exact_files"], [ + "scripts/installtests/build_release_assets_test.go", "scripts/release_control/internal/record_rc_to_ga_rehearsal_test.py", "scripts/release_control/release_promotion_policy_support_test.py", "scripts/release_control/release_promotion_policy_test.py", @@ -4310,6 +4313,7 @@ class SubsystemLookupTest(unittest.TestCase): self.assertEqual( match["verification_requirement"]["exact_files"], [ + "scripts/installtests/build_release_assets_test.go", "scripts/release_control/internal/record_rc_to_ga_rehearsal_test.py", "scripts/release_control/release_promotion_policy_support_test.py", "scripts/release_control/release_promotion_policy_test.py", @@ -4344,6 +4348,7 @@ class SubsystemLookupTest(unittest.TestCase): self.assertEqual( match["verification_requirement"]["exact_files"], [ + "scripts/installtests/build_release_assets_test.go", "scripts/release_control/internal/record_rc_to_ga_rehearsal_test.py", "scripts/release_control/release_promotion_policy_support_test.py", "scripts/release_control/release_promotion_policy_test.py", @@ -4378,6 +4383,7 @@ class SubsystemLookupTest(unittest.TestCase): self.assertEqual( match["verification_requirement"]["exact_files"], [ + "scripts/installtests/build_release_assets_test.go", "scripts/release_control/internal/record_rc_to_ga_rehearsal_test.py", "scripts/release_control/release_promotion_policy_support_test.py", "scripts/release_control/release_promotion_policy_test.py",