99 Commits

Author SHA1 Message Date
rcourtman dbca44937b Add privacy-safe telemetry lifecycle and outcome signals 2026-07-23 01:10:38 +01:00
rcourtman c5bf3adebf Fail closed instead of auto-downgrading, and back up config to a writable path under the hardened update unit
Customer report against v6.0.5 (2026-07-13):
- resolve_target_release fell back to hardcoded v4.5.1 when the GitHub API
  and the /releases/latest redirect both failed, silently downgrading a
  v5.0.17 install two major versions. Now: fail with --version guidance,
  and refuse any auto-resolved target older than the installed version.
- backup_existing wrote /etc/pulse.backup.<ts>, which is read-only under
  pulse-update.service (ProtectSystem=strict, ReadWritePaths=/opt/pulse
  /etc/pulse /tmp), so every unattended update on systemd/LXC failed at
  the backup step. Now: fall back to $INSTALL_DIR/config-backups when the
  config parent is not writable.
2026-07-13 16:13:29 +01:00
rcourtman 5a6871813f Grant CAP_NET_RAW to the installed systemd unit for ICMP probes
The installer's unit hardens with NoNewPrivileges=true, which strips the
ping binary's setuid/file capabilities, so ICMP availability probes could
never work on a systemd install. Grant the capability ambiently instead,
document the systemctl edit override for existing units, and pin the
hardening block with an install test and contract clause.

Addresses #1554 (discussion)
2026-07-11 18:15:22 +01:00
rcourtman c98acb9e85 Harden the installers against piped execution and v-prefixed versions
Two installer bugs reported after the v5 to v6 upgrades:

- The server installer (install.sh) guarded its "am I being sourced?" check
  with a bare ${BASH_SOURCE[0]}. When piped to bash (curl ... | bash) there is
  no source file, so under `set -u` the run aborted with "BASH_SOURCE[0]:
  unbound variable" before the installer body. Default the lookup and only
  early-return on a genuine source. Regression of the v5 fix in #1396. (#1526)

- The agent installer (scripts/install.sh) compared the downloaded binary's
  version ("v6.0.4") against the server /api/version value ("6.0.4") verbatim,
  raising a spurious mismatch warning on matching versions. Strip a leading "v"
  from both sides before comparing so only a genuine difference warns. (#1527)

Refs #1526 #1527
2026-07-09 11:31:43 +01:00
rcourtman 3dd2ecd198 Add Proxmox token smoke diagnostics
Refs #1476
2026-07-07 10:10:16 +01:00
rcourtman d52231855d Fix Proxmox token preservation regressions
Refs #1470, #1476, #1511
2026-07-07 09:49:26 +01:00
rcourtman dc5f0a35c0 Harden installer and demo backup handling 2026-07-04 15:28:42 +01:00
rcourtman 28cee7c467 Prefer PVE 9 guest-agent privileges in setup
Refs #1466
2026-07-03 11:15:51 +01:00
rcourtman d796928969 Install signature verifier dependencies 2026-06-28 03:26:13 +01:00
rcourtman 5e78825f54 fix(install): make unattended auto-updates actually run after install and upgrade
Two GA-blocking bugs in the auto-update install path:

The pulse-update.service unit was rendered through an unquoted heredoc
containing $${PULSE_SERVICE_NAME}, which bash expands to the installer's
PID, so the unit shipped with ExecCondition checking a service like
"24757{PULSE_SERVICE_NAME}". The condition always failed and systemd
silently skipped every scheduled run, for fresh installs and upgrades
alike. The heredoc now renders the detected service name directly:
ExecCondition=/bin/sh -c 'systemctl is-active --quiet pulse'.

Upgrades also never refreshed the updater assets: a v5 box with
auto-updates enabled keeps pulse-update.timer and autoUpdateEnabled=true,
so the update/reinstall flows never re-ran setup_auto_updates and the
v5.1-pinned /usr/local/bin/pulse-auto-update.sh stayed in place, logging
"Already running latest version" forever instead of selecting v6
releases. The asset-install half of setup_auto_updates is now a shared
install_auto_update_assets(), and a new refresh_auto_updates() rewrites
the helper script and units unconditionally whenever the timer already
exists, without touching system.json or the timer's enabled state. All
five install flows (update, reinstall, --version, --source, fresh) are
wired.

Tests now render the real unit and execute the rendered ExecCondition
against a recording systemctl stub instead of asserting source-text
fragments, pin refresh_auto_updates behavior (stale helper replaced,
system.json and enablement untouched), and pin the call-site wiring.
The deployment-installability contract records both invariants.

Note: rewritten message only — the original local commit carried a
parallel agent's licensing commit message due to a shared-index race;
the tree is byte-identical to the hook-verified original.
2026-06-10 22:43:08 +01:00
rcourtman 16f791a656 Harden install-time PVE token extraction with JSON-first parsing (#44/#1312)
The install-time auto-register path (auto_register_pve_node) parsed the API
token secret out of pveum's box-drawing table output with a fragile awk
column-split. The web-setup render path was already hardened to request
'pveum ... --output-format json' first and parse the value field, but this
secondary install.sh path was never ported.

auto_register_pve_node now requests --output-format json first (falling back
to the bare --privsep 1 form only when an older pveum rejects the JSON flag,
which keeps the secure-installer contract pin on that form satisfied) and
extracts the secret via a new extract_pve_token_value helper: JSON value-field
parse first, then a locale-independent box-drawing table fallback (normalizes
the column separator to a plain pipe byte-wise before splitting, so it works
regardless of host locale). This mirrors the hardened render path and removes
the silent-failure / mis-parse risk when pveum table formatting drifts.

Functional + contract tests in root_install_sh_test.go; deployment-installability
contract documents the deterministic extraction. The host-agent path
(internal/hostagent/proxmox_setup.go setupPVEToken) carries an agent-lifecycle
token-permission proof obligation and is left for a governed lane.
2026-06-09 15:47:12 +01:00
rcourtman 2ff2b18c74 Wire legacy sensor-proxy cleanup into install.sh --uninstall (#34)
A Proxmox host upgraded from v5 may still carry the legacy pulse-sensor-proxy
footprint (binary, systemd units, runtime/state dirs, dedicated service user,
and managed SSH keys in root's authorized_keys). install.sh --uninstall removed
everything for the Pulse server itself but left that legacy footprint behind,
so a 'complete uninstall' was not complete -- most notably it left SSH key
entries in /root/.ssh/authorized_keys.

uninstall_pulse now calls cleanup_local_sensor_proxy, which removes the LOCAL
footprint only: stop/disable the units, remove the binary/units/runtime/state
dirs, strip the '# pulse-managed-key' / '# pulse-proxy-key' entries, and remove
the service user/group. It is presence-gated (silent no-op when no proxy was
installed). The aggressive cluster-wide authorized_keys removal and
pulse-monitor@pam API-user deletion stay behind the explicit standalone
scripts/uninstall-sensor-proxy.sh, which we print a pointer to.

Functional + contract tests in scripts/installtests/root_install_sh_test.go;
deployment-installability contract documents the new uninstall removal scope.
2026-06-09 15:25:08 +01:00
rcourtman 1a42011724 Verify Pulse restarts after an interactive update; don't leave it silently stopped (#1323)
Finishes the deferred half of the v5->v6 parity fix for #1323 (the
pulse-auto-update.sh half landed in 672e81985). The interactive install.sh
update/reinstall flow stopped a running Pulse then called start_pulse, which
tolerates a silent start failure (common on unprivileged LXC) by printing a note
and returning 0 — leaving Pulse stopped under an "installation completed!" message.

- stop_pulse_for_update records whether Pulse was running before the update.
- start_pulse, only when Pulse was running before (PULSE_WAS_ACTIVE), no longer
  accepts a silent start failure: it verifies the service became active
  (wait_for_service_active, 20s), retries one explicit start, and surfaces a clear
  error + diagnostics if it still will not come up. Fresh installs are unchanged
  (the flag stays false, so the reassuring container note is kept).
- Wired into all three update/reinstall sites; added a BASH_SOURCE guard so the
  installer's functions can be unit-tested without running the installer.

Scope: fixes the #1323 'restart silently failed' case. Does NOT add a binary
rollback (download_pulse deletes bin/pulse.old right after the swap) — a
bad-release rollback is a separate concern.

Test: scripts/tests/test-install-update-resilience.sh (sources install.sh, stubs
systemctl, asserts was-active capture + retry + clear error). Go installtests +
bash -n confirm the guard does not change installer execution.
2026-06-09 15:05:08 +01:00
rcourtman 7b2cac08bd Fix /install.sh agent wizard on LXC/systemd installs (issue #1470)
The running server serves the unified AGENT installer at its /install.sh
endpoint, which the "Install on Linux" wizard fetches with --url/--token-file.
For published releases handleDownloadInstallScriptCommon only serves the local
/opt/pulse/scripts/install.sh when its .sig and .sshsig sidecars sit next to it;
otherwise it proxies the top-level GitHub install.sh asset. Since 49412357a that
asset is the SERVER installer (LXC/systemd quickstart, --version), so the proxy
now hands the agent wizard a server installer that rejects --url with
"Unknown option".

deploy_agent_scripts copied only the .sh/.ps1 files, never the sidecars, so every
published-release LXC/systemd install always hit the proxy and served the wrong
script. The Docker image already deploys both sidecars (Dockerfile); LXC/systemd
did not. This was latent across rc.1-rc.5 (the proxied asset was the agent
installer then) and surfaced in rc.6 once the asset flipped to the server
installer.

Deploy the .sig/.sshsig sidecars alongside each script so the endpoint serves
the local agent installer and never falls back to the server-installer asset.
Add a behavioral guard that runs deploy_agent_scripts and asserts the sidecars
land next to the served script, and document the served-endpoint sidecar
requirement in the deployment-installability contract.
2026-05-29 13:49:14 +01:00
rcourtman 75d1a0477b Enforce upgrade readiness before v6 updates
Refs #1476
2026-05-28 17:22:01 +01:00
rcourtman 52fd847ac6 Let install.sh smoke harness opt past Docker-environment refusal
install.sh refuses to run inside Docker (correct behavior for end
users), but the install-sh-smoke gate runs the documented systemd
install path inside a privileged systemd-in-Docker container — the
one legitimate bypass case. Added PULSE_INSTALL_ALLOW_DOCKER=1
escape hatch in check_docker_environment() and set it on the docker
exec in the smoke workflow. Takes effect on the next RC; v6.0.0-rc.6's
published install.sh predates this and is signed-frozen, so its
post-publish smoke gate will remain red until the next prerelease.
2026-05-27 22:12:17 +01:00
rcourtman 1dfc4ee6d3 Require .sshsig sidecar for install.sh --archive path
The --archive PATH flag (and the Proxmox LXC bootstrap which
propagates --archive into the container) reached install_pulse_archive
without going through download_release_archive, which is the only
place that previously verified the cryptographic signature on a
Pulse release tarball. That made --archive a clean signature-
verification bypass: anyone who could swap a local tarball
between download and install (shared /tmp on a Proxmox host, a
maintainer testing a swapped local file, etc.) would get
arbitrary root code execution at extract time.

install.sh:
- install_pulse_archive now requires ${archive_path}.sshsig and
  verifies it via the existing verify_release_signature helper
  before tar runs. Missing or invalid signature is a hard
  fail-closed.
- download_release_archive now keeps the .sshsig alongside the
  archive at ${archive_path}.sshsig instead of stashing it in a
  mktemp file that got deleted post-verify. install_pulse_archive
  re-verifies it; no behavior change for the curl|bash path beyond
  the extra integrity check at extract time.
- Proxmox LXC bootstrap now pct push'es ${container_archive_source}.sshsig
  alongside the archive into the container so the in-container
  install_pulse_archive run can verify. Missing sidecar fails the
  bootstrap (cleanup_on_error tears down the half-provisioned
  container).
- Cleanup paths that remove the temp archive on success/failure
  now also remove the sidecar.
- --help advertises the .sshsig requirement.

install-sh-smoke.yml:
- Smoke test now pulls ${tarball}.sshsig alongside the tarball
  before running install.sh --archive inside the test container.

Trade-off: maintainer flows that did `install.sh --archive
/path/to/local.tar.gz` against an unsigned local tarball will now
fail with a clear "Required signature sidecar not found" error.
Maintainers can either pull the corresponding .sshsig from the
release alongside the tarball, or skip --archive in favour of the
canonical download path.
2026-05-21 16:13:57 +01:00
rcourtman 2c51890d01 Harden installer extraction and demo workflow inputs
install.sh:
- find_pulse_binary_in_dir now rejects symlinks, so a malicious
  tarball that smuggled a symlink at the expected binary path
  cannot redirect the cp to $INSTALL_DIR/bin/pulse onto an
  arbitrary file outside the extract dir.
- tar -xzf calls in install_pulse_archive now pass
  --no-same-owner --no-overwrite-dir, defending against archive
  entries that try to chown extracted files or rewrite existing
  directory metadata.

Demo deploy workflows:
- deploy-demo-server.yml routes inputs.target through env: so the
  workflow_dispatch input cannot break out of the case statement,
  even though type: choice constrains it client-side.
- update-demo-server.yml routes inputs.tag, inputs.target, and
  github.event.release.tag_name through env: for the same reason.

All defense-in-depth: download_release_archive still signature-
verifies the tarball, and the demo workflows are gated to write-
access dispatchers. The argument-vs-env distinction matters once
the trigger surface or trust boundary changes.
2026-05-21 15:04:35 +01:00
rcourtman 0f747781fb Support private Pro archive installs 2026-05-07 09:28:38 +01:00
rcourtman d6e96ebeca Fix v6 demo release signing key deployment 2026-05-05 21:40:14 +01:00
rcourtman ce7b459aa7 Harden runtime Proxmox token ACLs 2026-05-05 14:42:05 +01:00
rcourtman c27814d190 Fix stable installer prerelease selection
Refs #1435
2026-05-03 15:20:18 +01:00
rcourtman 87aba32540 Port installer disk preflight from v5 2026-05-01 20:28:11 +01:00
rcourtman 411e8daa4d Port installer bundle fallback fix from v5 2026-05-01 20:28:11 +01:00
rcourtman af8a5f0740 Port RC3 maintenance fixes from v5
Refs #1440, #1444, #1451
2026-05-01 20:28:11 +01:00
rcourtman a60fa03d7f Route operator updates through the local signed helper 2026-04-22 16:18:16 +01:00
rcourtman ce95ef1fc6 Require signed server installer updates 2026-04-22 15:41:54 +01:00
rcourtman 4711d11163 Fix fresh Proxmox LXC installs defaulting to RC 2026-04-20 23:11:46 +01:00
rcourtman fb7b9b01fc Update pinned Go toolchain to 1.25.9 2026-04-18 10:04:34 +01:00
rcourtman e512bb62dc Add explicit auto-update disable install mode 2026-04-10 11:34:58 +01:00
rcourtman 58ad221d47 Add governed v6 preview demo targets 2026-04-10 10:53:17 +01:00
rcourtman d7d757bddd fix(installer): support local release archives on v6 2026-04-09 11:55:03 +01:00
rcourtman a3f99a271b Curate demo-facing mock data across platform views 2026-03-31 18:05:55 +01:00
rcourtman 0b98a0d3e0 fix(deploy): pin docs links to release refs 2026-03-28 21:32:11 +00:00
rcourtman 4b462dff22 Polish prerelease installer help 2026-03-25 11:27:55 +00:00
rcourtman 2c1b90cc4e Reframe prerelease update copy 2026-03-25 11:25:41 +00:00
rcourtman 778a2577b6 feat: Pulse v6 release 2026-03-18 16:06:30 +00:00
rcourtman 572520ebc6 Promote guest-agent /proc/meminfo fallback for accurate VM memory (#1270)
Move the guest-agent file-read of /proc/meminfo earlier in the memory
fallback chain so it runs before RRD, giving real-time MemAvailable that
correctly excludes reclaimable buff/cache on Linux VMs. Also add
VM.GuestAgent.FileRead permission for PVE 9 and fix install.sh to use
comma-separated privilege strings.
2026-03-09 10:04:28 +00:00
rcourtman c0b3a0e665 Restart Pulse service after failed auto-update (#1323)
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.
2026-03-07 10:46:19 +00:00
rcourtman c7e0aa63d2 fix(installer): make proxmox auto-register token rotation deterministic 2026-03-03 19:50:33 +00:00
rcourtman f5365809b3 fix(installer): remove config backup that filled disk on upgrades
The backup_existing function copied the entire config directory
(including metrics.db at ~2.5GB) on every upgrade with no cleanup.
On small VMs this filled the disk within a few releases.

The upgrade only swaps the binary; config files are not modified,
so the backup served no practical purpose.
2026-03-01 23:20:08 +00:00
rcourtman 54a1ace2c5 fix(installer): remove stale sensor-proxy mount entries that prevent LXC start after reboot (#1280)
The v4 installer added mount entries for /run/pulse-sensor-proxy to LXC
container configs. After upgrading to v5 and rebooting, /run (tmpfs) is
wiped and the container fails to start. The installer now detects and
removes these stale mp<N> and lxc.mount.entry references automatically
when run on a PVE host, and the upgrade docs include manual fix steps.
2026-02-22 10:52:12 +00:00
rcourtman 8ffec7c124 Support: Fix audit log permissions and improve UI empty state
- Updated server installer to ensure correct permissions for audit directory
- Added descriptive empty state and filter clearing to AuditLogPanel
2026-01-22 16:41:18 +00:00
rcourtman 098f645b34 chore: update Docker configs and installer
- Dockerfile: remove sensor proxy build target
- docker-compose.yml: remove proxy service configuration
- install.sh: simplify installer without proxy
- updates/manager.go: minor updates
2026-01-21 12:03:52 +00:00
rcourtman 724362504e fix: Add SELinux context restoration for Fedora/RHEL systems. Related to #996
On SELinux-enforcing systems (Fedora, RHEL, CentOS), binaries installed to
non-standard locations need proper security contexts for systemd to execute
them. Without this, systemd fails with 'Permission denied' even when the
binary has correct Unix permissions.

Changes:
- Add restore_selinux_contexts() function to both install scripts
- Uses restorecon (preferred) or chcon (fallback) to set bin_t context
- Only runs when SELinux is detected and enforcing
- Called after binary installation, before systemd service start
2025-12-31 23:12:53 +00:00
rcourtman 5cd6224997 fix(install): align bootstrap token path and data directory config (fixes #962) 2025-12-30 00:28:16 +00:00
rcourtman cba4418be3 fix: Remove Alpine from LXC template options
Alpine uses apk/OpenRC instead of apt/systemd, which the Pulse
LXC installation flow requires. This prevents failed installations.

- Remove Alpine download option from advanced mode
- Add note that Pulse requires Debian-based templates
- Add validation when user selects from template list to catch
  Alpine/Gentoo/Arch/Void and fall back to Debian 12 with warning

Related to #915
2025-12-25 23:58:24 +00:00
rcourtman b562580764 fix: Skip deprecated pulse-sensor-proxy for v5+ installations
The unified agent now handles temperature monitoring in v5+, making
pulse-sensor-proxy unnecessary. This commit:

1. Adds INSTALLER_MAJOR_VERSION constant to declare bundled version
2. Skips 'Temperature Monitoring Setup' prompts for v5+ installs
3. Skips sensor proxy installation entirely for v5+
4. Updates help text to mark --proxy as deprecated for v5+
5. Removes outdated sensor proxy instructions from completion message

Fixes the 'pct pull TASK ERROR: failed to open /opt/pulse/bin/pulse-sensor-proxy-linux-amd64'
error reported by users installing v5.0.0-rc.3.

Reported-by: RLSinRFV (GitHub Discussion #845)
2025-12-17 12:20:03 +00:00
rcourtman 9e847e2a02 fix(install): add retry logic and better error output for script download
Addresses #827

- Added 3-retry logic with 2-second delays between attempts
- Increased timeout from 15s to 30s for slower connections
- Show actual curl error instead of suppressing stderr
- Provide workaround instructions (download manually then run)
- Show the URL being downloaded for easier debugging
2025-12-14 21:38:59 +00:00
rcourtman c6167f08e6 fix: deploy unified agent scripts and binaries during installation
The installer was missing:
1. copy_unified_agent_binaries_from_dir() to extract pulse-agent-* binaries
   from the release tarball to /opt/pulse/bin/
2. install.sh and install.ps1 in the deploy_agent_scripts() array

This caused /install.sh and /download/pulse-agent to return 404 on fresh
installations and upgrades from pre-4.33.0 versions.

Related to #760, #751
2025-11-26 20:35:52 +00:00