mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +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:
@@ -120,6 +120,26 @@ server-side update execution surfaces.
|
|||||||
1. Add or change deployment-type detection, update planning, or apply behavior through `internal/updates/`
|
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, 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`
|
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
|
Deployment bootstrap token behavior remains a deployment-installability
|
||||||
trust boundary even when the handler is API-owned. `internal/api/deploy_handlers.go`
|
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
|
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
|
# 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)
|
# instead of pulling from main branch (which may have newer, incompatible changes)
|
||||||
echo "Copying install scripts to release directory..."
|
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"
|
[ -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/install-docker.sh "$RELEASE_DIR/"
|
||||||
cp scripts/pulse-auto-update.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"`,
|
`source "${SCRIPT_DIR}/release_asset_common.sh"`,
|
||||||
`RENDERED_INSTALLERS_DIR="${BUILD_DIR}/rendered-installers"`,
|
`RENDERED_INSTALLERS_DIR="${BUILD_DIR}/rendered-installers"`,
|
||||||
`go run ./scripts/render_installers.go \`,
|
`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"`,
|
`[ -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-amd64" "$RELEASE_DIR/"`,
|
||||||
`cp "$BUILD_DIR/pulse-agent-linux-arm64" "$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"`) {
|
// Sanity-check the opposite drift: the rendered AGENT installer must NOT be
|
||||||
t.Fatal("build-release.sh still copies the legacy root install.sh into release assets")
|
// 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{
|
requiredScriptWiring := []string{
|
||||||
@@ -264,6 +274,15 @@ func TestReleaseValidationRequiresSignedSidecars(t *testing.T) {
|
|||||||
`Install script endpoints returned required signature headers`,
|
`Install script endpoints returned required signature headers`,
|
||||||
`Download endpoints returned binaries with checksum and signature headers for all platforms/architectures`,
|
`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`,
|
`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 {
|
for _, needle := range localRequired {
|
||||||
if !strings.Contains(localValidator, needle) {
|
if !strings.Contains(localValidator, needle) {
|
||||||
|
|||||||
@@ -397,6 +397,41 @@ if [ $missing_count -gt 0 ]; then
|
|||||||
fi
|
fi
|
||||||
success "All ${#required_assets[@]} required release assets present"
|
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
|
# Validate tarball contents
|
||||||
section "Validating tarball contents"
|
section "Validating tarball contents"
|
||||||
tar_arches=(linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-386)
|
tar_arches=(linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-386)
|
||||||
|
|||||||
Reference in New Issue
Block a user