Keep stderr and trailing blank records in service-state and running-PID admission. Incomplete reads must not authorise installation, restart, rollback or update success. Require production caller and real-process controls for both read boundaries; preserve existing recovery, trust and deadlines.
Change-source: pulse-maintainer
Give each installer and signature transfer a five-second connection and sixty-second total budget, ignore ambient curl settings and preserve HTTPS through redirects. Failed or partial downloads leave the prior install, service and identity unchanged; pinned signature admission and recovery deadlines are unchanged.
Change-source: pulse-maintainer
Adapt rcourtman’s Pulse PR2855 (aef53d0d3e), which diagnosed systemd’s silent exit-one absence. The original contributor commit is unchanged and explicitly credited, not imported, amended or re-parented in this maintainer candidate.
One bounded stdout/stderr observation accepts genuine absence but retains failed first reads, other statuses and blank-line output. Include the existing registered installer/updater verification hooks, real-empty-root controls and missing-unit recovery double in this same source commit. This replaces the rejected foreign-history composition with a normal maintainer-reviewed implementation; no provenance policy, credential or host installation changes.
Source-attribution: rcourtman, https://github.com/rcourtman/Pulse/pull/2855, aef53d0d3e
Change-source: pulse-maintainer
An installed target version and active service can conceal an old managed executable. Compare the actual process bytes between complete bounded MainPID observations before accepting an update or restored active service. Retain the backup and current service on uncertainty without an automatic restart or rollback grant. Register real owned-process and conditional transaction controls; keep synthetic/native and customer acceptance distinct.
Change-source: pulse-maintainer
Conditional Bash callers hid a failed logger write behind successful stdout. Attempt both sinks, return the first failed sink, and exercise the unchanged draining collector and recovery path with the real log primitive and confined external logger. Preserve the distinction between observed pipeline completion and native or customer recovery.
Change-source: pulse-maintainer
Drain the installer pipe after a failed log write, retain its first observed failure across later successes and collect an unterminated final line. Reuse the guarded restoration path without changing signed-input, service-state, mask or prior-activity safeguards; bind the regression controls to package acceptance.
Change-source: pulse-maintainer
Capture both pipeline statuses before logging overwrites them. Keep recovery, signature and prior-service safeguards unchanged; cover installer, collector, simultaneous and successful observations without attributing a past host interruption.
Change-source: pulse-maintainer
Read bounded, complete unit state before mutation and confirm inactive state before rollback replacement. Keep the verified backup and stop automatic recovery when service state cannot be reconciled; preserve prior-active restoration, masks, signatures and configuration.
Exercise failed/partial reads, deadlines, no-op stops and restoration through confined production-shell fixtures and the existing signed transaction controls. Fix fixture path and manager observations so registered Go controls execute the actual source.
Refs: #2785
Change-source: pulse-maintainer
The helper selects and passes an explicit service name, bypassing installer discovery. Apply the same bounded exact-unit admission there and preserve its error before any prior-active query, backup, installer call or restart trap. Execute both producers and the real helper handoff in the registered unattended updater proof artifact.
Refs: #2785
Change-source: pulse-maintainer
Reject successful installers whose replacement cannot identify itself as the target community runtime. VERSION metadata and failed or unrecognised probes cannot establish acceptance; retain the existing verified rollback and prior-active restoration. Exercise the complete locally signed installer transaction and bounded executable read.
Change-source: pulse-maintainer
Require a successful bounded executable identity probe before release discovery. Keep Pro and unknown runtime editions untouched even when VERSION offers a usable community version, and do not defer a broken primary binary to a legacy one. Exercise the real shell classifier and main boundary with adverse controls and a TERM-resistant deadline.
Change-source: pulse-maintainer
Text matching admitted truncated configuration, nested opt-in and padded RC channels. Parse one configuration object with the installed jq dependency and fail closed before downloads when consent cannot be verified. Preserve the legacy no-config timer opt-in, manual updates and disabled-timer gate; add executed-shell regression cases and the deployment contract.
Change-source: pulse-maintainer
Keep the original archive-copy metadata semantics while checking every snapshot and restore. A content-only copy would lose extended attributes. Exercise that recovery and reject a non-regular restored destination before byte comparison or activation; qualify the restart backstop comments for incomplete recovery.
Change-source: pulse-maintainer
A successful rename is not by itself the recovered-byte postcondition. Keep the snapshot and suppress activation when the recorded destination is still a link or no longer matches the saved bytes; connected no-op and changed-destination controls exercise that boundary.
Change-source: pulse-maintainer
Reject directory destinations before the same-directory file rename, use portable mode inspection in the connected transaction tests, and teach the existing service fakes that stopping and failed activation are separate operations. The recorded first candidate proof remains bound to its own source.
Contract-Neutral: Preserve the reviewed backup and rollback semantics; portable utility invocation and faithful test boundary only.
Change-source: pulse-maintainer
The conditional updater caller disables errexit, but binary and VERSION backups and restore copies were best-effort and their only backup was deleted even when recovery failed. Require a private verified snapshot before installer execution, consolidate the three rollback branches, stage exact original paths and absence, and retain the backup without automatic activation when recovery is incomplete. Exercise the complete signed offline path and prior-active-only service recovery.
Change-source: pulse-maintainer
A RETURN trap is not scoped to the function that installs it. perform_update
left its trap referencing function-local installer_tmp/signature_tmp (and
service_name/service_was_active) installed, so it re-ran when a later function
returned. With those locals gone and 'set -u' active, the updater aborted with
'installer_tmp: unbound variable' immediately after logging 'Auto-update check
completed' — pulse-update.service entered failed state even though the update
had succeeded (reported #2128, affects v6.3.2/v6.4.1).
Disarm the trap with 'trap - RETURN' on its first invocation, and use
${installer_tmp:-}/${signature_tmp:-} so a stray invocation cannot abort the
script. Add a regression test asserting no RETURN trap remains after both the
successful and the prerelease-refusal paths.
Change-source: pulse-maintainer
Two bugs from #1630 that combined to take installs down silently:
1. perform_update()'s install-failed rollback branch restored the backup
but never restarted pulse.service. Since the generated
pulse-update.service gates on ExecCondition=systemctl is-active,
every later timer run was then skipped and the install stayed down
until manual intervention. Restart is now guaranteed by a
service_was_active-guarded restart in that branch plus an
ensure_service_restarted RETURN trap so no exit path can miss it
(re-fix of #1323, originally c0b3a0e66, lost in 778a2577b and only
partially restored in 672e81985).
2. install.sh aborted under errexit when writing the /bin/update helper
on a read-only filesystem - after the new binary was installed and
the service stopped, landing in bug 1's no-restart branch. The stock
pulse-update.service uses ProtectSystem=strict, so /bin and
/usr/local/bin are read-only on stock unattended updates; transient
read-only remounts hit the same path. The helper write, PATH
appends, and the /usr/local/bin/pulse symlink are now idempotent and
non-fatal with a warning (install_binary_symlink).
Contract: deployment-installability now pins fail-closed service
availability for unattended updates and non-fatal writes outside the
hardened unit's writable set, with proofs in pulse_auto_update_test.go
and root_install_sh_test.go plus shell regression coverage in
scripts/tests/test-pulse-auto-update.sh (installer-exits-nonzero path)
and scripts/tests/test-install-update-resilience.sh (read-only helper
and symlink paths, verified under set -e, root-safe via ENOTDIR).
Fixes#1630
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Production telemetry (7d, 2026-07-17) shows a persistent tail of live
installs on 6.0.0-rc.x and 6.0.3/6.0.4 while 6.0.5 has been stable since
2026-07-09. Root cause for the reachable stranding class: release
selection trusted GitHub's created_at ordering, and this repo interleaves
v5-line maintenance releases with v6 releases (v5.1.36 was created the
day before v6.0.5). Whenever a v5.1.x release is the most recently
created stable:
- stable channel (getLatestReleaseForChannel) returned the first
non-prerelease in list order -> "no update" for every v6 install
until the next v6 release ships
- the RSS rate-limit fallback returned the first feed entry matching the
channel, same failure, both channels
- the unattended updater trusted /releases/latest, which GitHub defines
as most-recently-created stable -> timer no-ops in the same window
All three now select the highest eligible version: the API path tracks
newest stable + newest prerelease in one pass (prerelease = GitHub flag
OR tag shape), the feed fallback picks the max matching the channel, and
pulse-auto-update.sh scans /releases pairing each tag with its own
draft/prerelease flags before the fail-closed shape filter, keeping
/releases/latest as fallback. helm-chart-* tags fail semver parsing and
are dropped everywhere.
Verified not broken (pinned by new tests): rc-channel installs are
offered the newer stable (6.0.0-rc.6 -> 6.0.5) and move onto the next rc
line when one opens (-> 6.1.0-rc.2); the auto-update prerelease filter
(fail-closed is_prerelease_tag, metadata-flag refusal) still refuses
prerelease targets on stable. Go tests fail on the pre-fix code
(returned 5.1.37 / "no update").
Contract: deployment-installability now pins version-max release
selection with proofs in manager_stranded_upgrade_test.go and
pulse_auto_update_test.go.
The in-app updater and the unattended timer both target the public
rcourtman/Pulse community assets, so a Pro install that used them was
silently downgraded to community. Guard 2 (983a89326) blocked in-app
apply on the Pro edition, which stopped the downgrade but left Pro
installs with no update path except manual portal downloads. That
friction is a plausible driver of the runtime split: as of 2026-07-08
only 10 of 66 active paid licenses have any Pro-runtime install.
Root fix: the compiled Pro binary now checks and applies updates
through the license server download broker (GET /v1/downloads/pulse-pro
with the installation token and instance fingerprint). The check
compares against the broker's pinned private release instead of GitHub,
respecting the stable/rc channel guard. Apply re-resolves fresh signed
R2 URLs at apply time, verifies the archive against the same pinned
pulse-installer SSHSIG key plus the broker manifest sha256, and refuses
GitHub-shaped download URLs outright. An unactivated Pro binary still
refuses with the portal fallback. The community edition path is
unchanged.
The update banner restores in-app apply for auto-updatable Pro
deployments and keeps the portal instructions for deployments the
updater cannot drive (Docker). scripts/pulse-auto-update.sh now skips
when the installed binary reports Pulse Pro so the unattended timer can
never reinstall community over Pro.
Note: internal/updates/pro_update.go and manager_pro_update_test.go for
this change landed one commit early inside 313552deb via a parallel
session committing a shared staged index; this commit completes the
wiring they belong to.
Back-port the pulse-auto-update.sh half of v5 fix 0f2982ce3 to v6. After a
verified update, if Pulse was running beforehand, wait_for_service_active
polls up to 20s, tries one explicit start, and if it still does not come
back up restores the backup binary/VERSION, restarts, and returns failure
instead of leaving Pulse stopped (common on unprivileged LXC where the
installer's restart silently fails). Adds a BASH_SOURCE guard so the
script can be sourced, plus scripts/tests/test-pulse-auto-update.sh.
Note: the install.sh start_pulse STOPPED_PULSE_SERVICE guard (interactive
installer path) still needs the stop-tracking infrastructure wired into
v6's update flow; tracked as a follow-up.
Refuse prerelease-shaped tags and explicit GitHub prerelease responses in the unattended stable updater before installer invocation.
Add installability tests and proof routing for the auto-update prerelease refusal guard.
The auto-update flow stops the Pulse service before applying updates.
If the update fails, the rollback path restored files but never
restarted the service. Since the main unit was explicitly stopped
(not crashed), systemd's Restart=always didn't rescue it.
Add restart-on-failure guards to both pulse-auto-update.sh and
install.sh so Pulse is always restarted after a failed update attempt.
Scripts like install.sh and install-sensor-proxy.sh are now attached
as release assets and downloaded from releases/latest/download/ URLs.
This ensures users always get scripts compatible with their installed
version, even while development continues on main.
Changes:
- build-release.sh: copy install scripts to release directory
- create-release.yml: upload scripts as release assets
- Updated all documentation and code references to use release URLs
- Scripts reference each other via release URLs for consistency
- Remove confusing --main flag, use --source for clarity
- Fix timeout issues when building from source in LXC containers
- Increase timeout from 5min to 20min for source builds
- Add PULSE_CONTAINER_TIMEOUT env var for custom timeouts
- Support PULSE_CONTAINER_TIMEOUT=0 to disable timeout
- Fix misleading "Latest version: vX.X.X" message during source builds
- Update documentation to use --source instead of --main
- Simplify auto-update script logic for source builds
Changes:
- install.sh: Check BUILD_FROM_SOURCE early to skip version detection
- install.sh: Adaptive timeout (300s binary, 1200s source builds)
- install.sh: Better timeout error messages with recovery instructions
- README.md: Replace --main with --source in examples
- docs/INSTALL.md: Replace --main with --source in examples
- scripts/pulse-auto-update.sh: Remove --main special case