mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
Ship the Pulse server install.sh as the GitHub Release asset
Every v6 RC (rc.1 through rc.5, ~30 days) shipped the wrong install.sh. build-release.sh was copying the rendered AGENT installer into release/install.sh, but adapter_installsh, scripts/pulse-auto-update.sh, the root install.sh's own --rc/--stable/--version flows, and the README quickstart all fetch that asset and run `bash install.sh --version vX.Y.Z`. Since the agent installer rejects --version with "Unknown argument", the LXC quickstart, the in-product "Update Pulse" button on systemd/proxmoxve deployments, and the pulse-auto-update.sh systemd timer were all broken for every RC. Fix the build-release.sh copy to publish the root server installer. The agent installer continues to ship inside tarballs at ./scripts/install.sh and inside Docker images at /opt/pulse/scripts/install.sh, and is served at the running Pulse server's /install.sh endpoint — none of those paths change. Only the top-level GitHub Releases asset moves from agent to server. Update build_release_assets_test.go to lock in the new publishing rule and ban the reverse drift, replacing the March 18 "legacy root install.sh" guard that was the original mistake. Add a validate-release.sh smoke that catches the regression mode this hid: the published install.sh must have the Pulse server banner, the --version) arg handler, must not contain the agent banner, and `bash install.sh --help` must print the server installer's version-pinning help line. These checks run as part of validate-release-assets.yml against the post-publish asset bundle so a future swap back cannot slip through. Document the asset identity rule and the validate-release.sh guard in the deployment-installability contract so any future change to the publishing pipeline has to update the contract or trip the shape guard.
This commit is contained in:
parent
89379c4b5c
commit
49412357af
4 changed files with 84 additions and 4 deletions
|
|
@ -120,6 +120,26 @@ server-side update execution surfaces.
|
|||
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`
|
||||
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
|
||||
accepts `--version vX.Y.Z`, `--rc`, `--stable`, and friends). The rendered
|
||||
AGENT installer (`scripts/install.sh`) ships only inside release tarballs
|
||||
at `./scripts/install.sh` and inside Docker images at
|
||||
`/opt/pulse/scripts/install.sh`, and is served at the running server's
|
||||
`/install.sh` endpoint; it is intentionally never the top-level GitHub
|
||||
Releases asset. `internal/updates/adapter_installsh.go`,
|
||||
`scripts/pulse-auto-update.sh`, and the root `install.sh`'s own
|
||||
`--rc` / `--stable` / `--version` self-refetch flows all fetch
|
||||
`releases/<tag>/install.sh` and execute it via `bash -s -- --version vX.Y.Z`,
|
||||
and the README quickstart documents the same pattern. Publishing the agent
|
||||
installer in that slot silently breaks every one of those flows because the
|
||||
agent installer rejects `--version` as an unknown argument; this drift
|
||||
shipped across v6 rc.1 → rc.5 (April 12 → May 11, 2026) before being caught.
|
||||
`scripts/validate-release.sh` must therefore fail the release if the
|
||||
published `install.sh` does not carry the server-installer banner, does not
|
||||
handle `--version)` in its argument parser, contains the agent installer
|
||||
banner string, or does not print the server installer's version-pinning
|
||||
help line when invoked with `--help`.
|
||||
Deployment bootstrap token behavior remains a deployment-installability
|
||||
trust boundary even when the handler is API-owned. `internal/api/deploy_handlers.go`
|
||||
must preserve server-derived `owner_user_id` lineage on bootstrap tokens and
|
||||
|
|
|
|||
|
|
@ -441,7 +441,13 @@ fi
|
|||
# curl -fsSL https://github.com/rcourtman/Pulse/releases/latest/download/install.sh | bash
|
||||
# instead of pulling from main branch (which may have newer, incompatible changes)
|
||||
echo "Copying install scripts to release directory..."
|
||||
cp "${RENDERED_INSTALLERS_DIR}/install.sh" "$RELEASE_DIR/install.sh"
|
||||
# The published install.sh is the Pulse SERVER installer (LXC/systemd/Proxmox VE).
|
||||
# In-product self-update (internal/updates/adapter_installsh.go), scripts/pulse-auto-update.sh,
|
||||
# the root install.sh's own --rc/--stable/--version flows, and the README quickstart all
|
||||
# fetch this asset and run `bash install.sh --version vX.Y.Z`. The rendered AGENT installer
|
||||
# (scripts/install.sh) ships inside tarballs and Docker images at ./scripts/install.sh and
|
||||
# is served at the running server's /install.sh endpoint — it is not a GitHub Releases asset.
|
||||
cp install.sh "$RELEASE_DIR/install.sh"
|
||||
[ -f "${RENDERED_INSTALLERS_DIR}/install.ps1" ] && cp "${RENDERED_INSTALLERS_DIR}/install.ps1" "$RELEASE_DIR/install.ps1"
|
||||
cp scripts/install-docker.sh "$RELEASE_DIR/"
|
||||
cp scripts/pulse-auto-update.sh "$RELEASE_DIR/"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,14 @@ func TestBuildReleaseUsesV6InstallScripts(t *testing.T) {
|
|||
`source "${SCRIPT_DIR}/release_asset_common.sh"`,
|
||||
`RENDERED_INSTALLERS_DIR="${BUILD_DIR}/rendered-installers"`,
|
||||
`go run ./scripts/render_installers.go \`,
|
||||
`cp "${RENDERED_INSTALLERS_DIR}/install.sh" "$RELEASE_DIR/install.sh"`,
|
||||
// The published install.sh asset is the server installer (root install.sh).
|
||||
// The rendered AGENT installer is shipped inside tarballs and Docker images
|
||||
// at ./scripts/install.sh and served at the running server's /install.sh
|
||||
// endpoint, but is intentionally not a top-level GitHub Releases asset:
|
||||
// adapter_installsh, pulse-auto-update.sh, the root install.sh's own --rc/
|
||||
// --version flows, and the README quickstart all expect releases/<tag>/install.sh
|
||||
// to be the server installer that accepts --version vX.Y.Z.
|
||||
`cp install.sh "$RELEASE_DIR/install.sh"`,
|
||||
`[ -f "${RENDERED_INSTALLERS_DIR}/install.ps1" ] && cp "${RENDERED_INSTALLERS_DIR}/install.ps1" "$RELEASE_DIR/install.ps1"`,
|
||||
`cp "$BUILD_DIR/pulse-agent-linux-amd64" "$RELEASE_DIR/"`,
|
||||
`cp "$BUILD_DIR/pulse-agent-linux-arm64" "$RELEASE_DIR/"`,
|
||||
|
|
@ -42,8 +49,11 @@ func TestBuildReleaseUsesV6InstallScripts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if strings.Contains(script, `cp install.sh "$RELEASE_DIR/install.sh"`) {
|
||||
t.Fatal("build-release.sh still copies the legacy root install.sh into release assets")
|
||||
// Sanity-check the opposite drift: the rendered AGENT installer must NOT be
|
||||
// the published install.sh asset. Publishing it there shipped a broken LXC
|
||||
// install + auto-update path across every v6 RC (rc.1 → rc.5).
|
||||
if strings.Contains(script, `cp "${RENDERED_INSTALLERS_DIR}/install.sh" "$RELEASE_DIR/install.sh"`) {
|
||||
t.Fatal("build-release.sh must not publish the rendered agent install.sh as the top-level release asset")
|
||||
}
|
||||
|
||||
requiredScriptWiring := []string{
|
||||
|
|
@ -264,6 +274,15 @@ func TestReleaseValidationRequiresSignedSidecars(t *testing.T) {
|
|||
`Install script endpoints returned required signature headers`,
|
||||
`Download endpoints returned binaries with checksum and signature headers for all platforms/architectures`,
|
||||
`Offline self-heal: download endpoint works with checksum and signature headers without outbound network`,
|
||||
// Server installer identity guard — see the rc.1 → rc.5 regression where
|
||||
// the rendered agent installer shipped as the top-level install.sh asset
|
||||
// for 30 days before anyone noticed. Removing any of these unpins the asset.
|
||||
`Validating install.sh is the Pulse server installer`,
|
||||
`grep -qE '^# Pulse Installer Script'`,
|
||||
`grep -qE '^[[:space:]]*--version\)'`,
|
||||
`Pulse Unified Agent Installer`,
|
||||
`bash "$install_sh_path" --help`,
|
||||
`Install specific version (e.g.`,
|
||||
}
|
||||
for _, needle := range localRequired {
|
||||
if !strings.Contains(localValidator, needle) {
|
||||
|
|
|
|||
|
|
@ -397,6 +397,41 @@ if [ $missing_count -gt 0 ]; then
|
|||
fi
|
||||
success "All ${#required_assets[@]} required release assets present"
|
||||
|
||||
# Validate published install.sh is the Pulse SERVER installer.
|
||||
# Across v6 rc.1 → rc.5 the rendered AGENT installer was published here by
|
||||
# mistake, silently breaking the LXC quickstart, the in-product Update Pulse
|
||||
# button, and the pulse-auto-update.sh systemd timer for 30 days before anyone
|
||||
# noticed. This guard pins the asset identity so it cannot drift back.
|
||||
info "Validating install.sh is the Pulse server installer..."
|
||||
install_sh_path="install.sh"
|
||||
if [ ! -s "$install_sh_path" ]; then
|
||||
error "install.sh is missing or empty"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -qE '^# Pulse Installer Script' "$install_sh_path"; then
|
||||
error "install.sh banner does not match the Pulse server installer"
|
||||
error "If this fires, build-release.sh is publishing the wrong file (likely the agent installer)"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -qE '^[[:space:]]*--version\)' "$install_sh_path"; then
|
||||
error "install.sh is missing the --version arg handler — required by adapter_installsh, pulse-auto-update.sh, and the README quickstart"
|
||||
exit 1
|
||||
fi
|
||||
if grep -q 'Pulse Unified Agent Installer' "$install_sh_path"; then
|
||||
error "install.sh is the agent installer, not the server installer — releases must publish the root install.sh"
|
||||
exit 1
|
||||
fi
|
||||
# Smoke: actually invoke `bash install.sh --help` and confirm it prints the
|
||||
# server-installer help text. Catches parse-time syntax breakage and confirms
|
||||
# the script is structurally executable, not just textually correct.
|
||||
install_help_output=$(bash "$install_sh_path" --help 2>&1 || true)
|
||||
if ! echo "$install_help_output" | grep -qF "Install specific version (e.g."; then
|
||||
error "bash install.sh --help did not print the server installer's version-pinning help line"
|
||||
error "Help output captured: $install_help_output"
|
||||
exit 1
|
||||
fi
|
||||
success "install.sh is the Pulse server installer (handles --version, --help prints server help)"
|
||||
|
||||
# Validate tarball contents
|
||||
section "Validating tarball contents"
|
||||
tar_arches=(linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-386)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue