Pulse/docs/AUTO_UPDATE.md
rcourtman 8a99b16d6c docs: correct customer docs that drifted from the source
Audited the customer-facing docs against the code: about 5,200 concrete
claims (env vars, endpoints, flags, defaults, ports, UI labels), plus every
environment variable the code reads. Each discrepancy was re-verified
against the source before editing, and every edit was reviewed again.

What users would have hit following the old text:
- UI paths and labels that no longer exist (Settings > Relay, Platform
  connections, Access Control, SSO Providers, Create Organization) and
  Patrol mode names (now Ask first, Safe auto-fix, Autopilot).
- Wrong values and behaviour: LOG_MAX_AGE=0 does not disable cleanup,
  Patrol evidence budget is 10 calls and 12 turns, ALLOW_UNPROTECTED_EXPORT
  covers private networks too, PULSE_AUDIT_ASYNC defaults on.
- Agent install: install.sh ignores PULSE_ENABLE_KUBERNETES and
  PULSE_ENABLE_PROXMOX, rejects --disable-auto-update, and the token
  fallback lives in the state directory.
- API: audit endpoints answer 402 on Community, onboarding QR is GET,
  type=container is rejected, several routes need admin plus a scope.
- HTTP_REDIRECT_PORT was not documented anywhere.

Docs are mirrored under frontend-modern/public/docs, so each change is
applied to both copies. The tests that pinned the old wording (including
three that asserted claims the code contradicts) are updated alongside.

Contract-Neutral: Customer docs corrected to match shipped behaviour; no runtime, API or contract change.
2026-10-11 13:59:56 +01:00

22 KiB

Pulse Server Automatic Updates

Pulse supports one-click server updates for supported deployment types. This document describes the Pulse server runtime, not installed Pulse Agents.

See Releases and update channels for Stable and Preview expectations, beta and RC testing stages, and the checks required before a release becomes stable.

Eligible v6 agents update asynchronously through their own update client. A server update changes their target version but does not prove fleet convergence. For v5, PVE, disabled, or failed agent updates, use Agent Doctor at /settings/infrastructure?agentDoctor=1 or the installer in Settings → Infrastructure → Install on a host. See Unified Agent.

Supported Deployment Types

Deployment Auto-Update Method
ProxmoxVE LXC ✅ Yes In-app update button
Systemd Service ✅ Yes In-app update button
Docker ❌ Manual Pull new image
Source Build ❌ Manual Build, then install through the existing deployment procedure

Using One-Click Updates

When an Update is Available

  1. Navigate to Settings → System → Updates
  2. If an update is available, you'll see an "Install Update" button
  3. Click the button to open the confirmation dialog
  4. Review the update details:
    • Current version → New version
    • Major-version or pre-release warning, when the update carries one
    • Estimated time and prerequisites, when listed
    • Root-access notice, when the update requires root
    • Automatic backup notice, when rollback is supported
  5. Click "Start Update" in the confirmation dialog to begin

Update Process

  1. Download: New version is downloaded, its signature and checksum are verified
  2. Validate: The new binary is executed with --version to prove it runs on this host and reports the expected version, before anything is touched
  3. Backup: Current installation is backed up
  4. Apply: Files are updated
  5. Restart: Service restarts automatically
  6. Verify: Health check confirms success

Progress Tracking

A real-time progress modal shows:

  • Current step
  • Download progress
  • Any warnings or errors
  • Automatic page reload on success

Configuration

Update Preferences

In Settings → System → Updates:

Setting Description
Update Channel Stable (recommended for production) or Preview (opt-in beta builds and release candidates)
Automatic Stable Updates Allow unattended stable updates on supported host installations with an installed update timer

The update timer must already be installed and enabled for unattended updates. Saving the UI preference does not provision or start a missing timer. Check the existing deployment rather than editing system.json or creating another update service to make the preference take effect.

For a failed or uncertain unattended update, use timer-only containment to pause further scheduled attempts without stopping Pulse.

Stored Settings (system.json)

Auto-update preferences are stored in system.json and edited via the UI.

{
  "autoUpdateEnabled": false,
  "updateChannel": "stable"
}

Note: The update schedule itself lives in the systemd timer (daily at 02:00 plus up to 4 hours of random delay), not in system.json. The legacy autoUpdateCheckInterval and autoUpdateTime fields were never consumed by anything and are ignored if present in older files.

Channel policy note: stable is the default and only recommended channel for paid or production environments. rc remains the internal channel key, but the user-facing meaning is an explicit pre-release preview path. In v6, unattended systemd auto-updates remain stable-only even if updateChannel is set to rc.

Manual Update Methods

Before updating, record the running server version and edition, preserve the existing deployment definition privately, and keep a consistent full-state backup with its matching keys. An update interrupts monitoring and alert delivery. Follow update preparation and recovery limits; do not update during a backup, freeze/thaw or an unresponsive-guest incident.

Docker

Use Docker server updates. Change the configured image to the exact release first, then pull and recreate only the Pulse service with the same mounted data. Pulling another tag alone does not change the image in your Compose file. Do not run docker compose down for a Pulse-only update or remove its data volume.

Private Pro installs must keep the private image supplied by the download page; replacing it with rcourtman/pulse changes the runtime edition. Server updates and monitored container updates are separate operations.

ProxmoxVE LXC (Manual)

Work inside the existing Pulse LXC, not on the Proxmox host or a monitored guest. Follow the existing-installation update procedure, including its helper-ownership check. On a community-scripts installation, /bin/update can be a different updater; its filename does not establish that it uses Pulse's signed installer or accepts an exact target version.

Systemd Service (Manual)

Follow the existing-installation update procedure using the actual installed server service, paths and update helper. Do not run a helper whose owner is unknown or apply default installation paths over a custom deployment. Server updates do not install or update pulse-agent; agent installation commands come from Settings → Infrastructure → Install on a host.

A helper selects a Helm-chart release

A Pulse server release uses a tag such as vX.Y.Z. A helm-chart-* release contains a Kubernetes chart, not the Linux server archive. A helper selecting a chart does not establish that Pulse's server release is missing or that GitHub's latest-release pointer is wrong; preserve the selected tag and error.

Do not supply a GitHub personal access token, bypass signatures or change permissions to make that helper continue. An unknown helper may ignore --version; even a Pulse-owned helper must download its installer before the installer can apply that option. Adding the flag is not proof of an exact or successful update.

Before another attempt, check the running version and service health and preserve the existing container, configuration, keys and history. Do not recreate the LXC or install a second server to recover from release selection. For a public Community server, the existing-installation procedure links the signed server-installer flow with an exact PULSE_VERSION; use it only after checking the installed service and paths. Private Pro installations must retain their private runtime, not substitute that public installer. Use update failure checks if the attempted update's result is uncertain. Guidance is not confirmation that the helper or installation is repaired.

Source Build

make build writes a pulse binary in the source checkout. It does not install that binary into the running service's executable path. Restarting the service after a build can therefore restart the old installed binary; if the service runs directly from the checkout, building there can instead overwrite its live executable. Do not build in the active installation directory.

For a production update, prefer the exact signed release and the existing-installation update procedure. Keep private Pro installations on their private runtime; a public source build is not a replacement for it.

If you intentionally maintain a source-built deployment:

  1. Select the intended source tag or commit explicitly in a separate build checkout, preserving local changes. A moving main branch is development source, not the latest published stable release.
  2. Use that checkout's declared toolchain and locked dependencies. Build and check the resulting binary's version before stopping the running service; a successful build alone is not an installed update.
  3. Install through your existing deployment procedure, preserving its service identity, executable path, configuration, credentials and data. Stage the replacement separately; do not copy onto a running executable or remove the previous binary and recovery snapshots first. If the active executable path or recovery procedure is unknown, stop before changing the service.
  4. Restore only a service that was active before the update, then confirm the running version, ordinary collection and notification delivery. Retain the previous binary and state backup until recovery is verified; a restart or version check alone does not establish healthy monitoring.

Do not pull, rebuild or restart just to reproduce an update failure. Follow update preparation and recovery limits before another attempt.

Rollback

An update error or a stuck progress modal does not prove that the old version is still running. Check the running version and service health before retrying or restoring anything; preserve the update error, logs and history. Do not run another update just to reproduce a failure.

Before any rollback, take a consistent private backup of the current state and check the proposed backup's scope. Keep the failed installation and update snapshots until recovery is verified. Returning to an older binary is not a full-state recovery. Restoring a matching data snapshot can discard settings, alert changes and history written since that snapshot.

Automatic Rollback

The in-app updater creates an installation snapshot before applying files. If applying files fails, it attempts to restore that snapshot and logs any restore error. This is not a guarantee that every update failure restores all state or restarts successfully. Confirm the running version, connections and service health afterwards; a progress message alone is not recovery evidence.

Roll back from Update History

If your installed version offers it, open Settings → System → Updates → Update History. Roll back is offered for a successful in-app update with a retained backup. The confirmation names the version before that update and Pulse restarts to use the restored binary. Check that version and the actual backup contents before confirming.

Update History is not a full-state recovery tool. Its restore scope depends on the installed updater. Older updaters may replace data/, config/ and .env under the installation directory, but omit active data stored elsewhere. Do not assume later settings and alert changes will be reverted, even if the confirmation says so. If active data shares those installation paths, do not restore it while Pulse is running; use the stopped-service procedure in Manual Rollback.

Older versions may not have this control. An absent button or backup does not justify deleting data or creating an empty configuration. Docker server installs use their previous image and matching data backup, not this in-app rollback.

What an update snapshot contains

In-app snapshots normally live at backup-<timestamp>/ under the runtime data directory (usually /etc/pulse), with /tmp/pulse-backup-<timestamp> as a low-space fallback. Use the backup_path recorded for the actual update in update-history.jsonl, rather than guessing a timestamp. The updater retains the most recent three snapshots; a /tmp snapshot may also be lost on reboot.

Snapshot scope is version-specific. The published v6.4.5 and v6.4.6-rc.1 in-app updaters use the limited installation snapshot described below. Do not assume a backup made by another updater version has the same contents or restore behaviour.

The limited snapshot copies the running server binary, available VERSION and .env files, and available data/ and config/ directories under PULSE_INSTALL_DIR (default /opt/pulse). It is not necessarily a complete backup of the active PULSE_DATA_DIR, external metrics/audit stores, service units, bundled agents or update helpers. Copy errors can leave a partial snapshot: a recorded path is not proof that every file was saved.

A backup's existence, or the presence of a database file, does not establish completeness, consistency or that Update History will restore it. A snapshot that includes active data still needs a verified, stopped-service recovery procedure; its presence is not permission to rewind live runtime stores.

Verify the backup against your effective service paths and update logs. Preserve the active data directory, matching .encryption.key, audit history and its signing key together, including custom paths and organisation directories. Use a stopped, consistent filesystem/volume backup; copying a live .db file alone can omit SQLite sidecar files. Keep these backups private, not in an issue or diagnostic attachment. See configuration paths and audit storage and safe recovery.

Manual Rollback

If Pulse cannot start, work inside the Pulse host or LXC, not on the Proxmox host itself. Start with read-only service discovery:

systemctl is-active pulse
systemctl show pulse --property=FragmentPath --property=DropInPaths

An inactive service makes is-active return non-zero; that is useful evidence, not a reason to reset it. Inspect the named unit and drop-ins locally for the effective executable, PULSE_INSTALL_DIR and PULSE_DATA_DIR. Do not post full service environments or .env contents; they may contain credentials.

  1. Stop Pulse before taking the failed-state backup or replacing files. Preserve the actual executable, distribution files, unit/drop-ins and every active data path, not just a guessed /opt/pulse/data directory.
  2. Match the retained backup to the intended pre-update version. Check its files and the update log for missing/copy-failed entries. If the backup is missing, partial or has the wrong scope, stop here; do not delete live directories to make a restore fit.
  3. Prefer restoring a verified pre-update filesystem/volume snapshot with your host backup tooling. Check it in an isolated recovery instance first, with no access to monitored systems or notification destinations. Do not start a second connected copy with the same agent identities.
  4. For a file-level repair, stage verified replacements and keep reversible copies of the failed state. Restore only the confirmed paths, ownership and permissions for this deployment. Do not merge unrelated snapshots, delete data/configuration directories, or regenerate encryption or signing keys.
  5. Start Pulse and verify the running version and edition, service health, saved connections, agent identities and retained history. If any check fails, preserve the error and backups rather than retrying the update blindly.

For Docker, change the configured image back to the recorded previous tag or digest and recreate only Pulse with its existing volume. An older image may not understand data migrated by the newer version; use a verified matching data backup when required. Never remove the volume or initialise a fresh one as a rollback shortcut. An image change alone is not a data rollback.

Update History

History entries are stored in update-history.jsonl under the Pulse data directory (/etc/pulse or /data), and exposed via GET /api/updates/history (admin auth required).

Systemd/LXC update runs write detailed logs to /var/log/pulse/update-<timestamp>.log.

Troubleshooting

Update button not showing

  1. Check if your deployment supports auto-update
  2. Verify an update is actually available
  3. Ensure you have the latest frontend loaded (hard refresh)

Update failed

First identify what was updated: this guide covers the Pulse server. An installed agent has its own update path. For a monitored Docker workload, use the failed or pending workload checks on that workload's host, not inside Pulse.

Keep the original target version, attempt time, last displayed step and redacted error from the progress modal or updater log. If Pulse still opens, check Settings → System → Updates for its running version and retain the existing Update History entry when available. Check the ordinary UI and monitoring freshness too. A failed updater exit, disconnected progress stream or stuck modal does not establish which version is running or whether rollback succeeded. Do not rerun an update, restart or reinstall merely to obtain evidence.

For server logs, use the bounded Pulse log readers for the actual deployment. Run them on the Pulse host, or inside the Pulse LXC, not on a monitored guest. They have a deadline as well as time and record limits; do not substitute a log-follow command or an unbounded reader if they fail. Inspect the relevant part of the original /var/log/pulse/update-<timestamp>.log privately when that systemd/LXC update produced one; a server journal alone may not contain the installer failure. Missing logs are unavailable evidence, not proof that no change occurred. Do not enable Debug or repeat the failing action.

If the error names disk space, check headroom on the actual staging, installation and data filesystems before planning another attempt; the release payload and rollback snapshot both need space. Do not delete configuration backups, keys or history to clear the error. A download error needs its original HTTP, connectivity or certificate evidence; do not disable TLS verification or switch a paid Pro installation to the public Community runtime as a workaround.

Share only the relevant times, versions, failed step and redacted error. Keep full logs, service environments, configuration, credentials and private backup paths out of public reports.

Pause further unattended attempts without stopping Pulse

If an automatic server update failed or its result is uncertain, you can pause the existing update timer while keeping Pulse's monitoring service running. This is containment, not rollback or a fix. It does not cancel an updater already running, prevent a manual or in-app update, or recover a stopped LXC. Do not stop the updater midway through replacement merely to collect evidence.

Work on the Pulse server, inside its LXC when applicable, not on the Proxmox host or a monitored guest. First identify the timer from the existing deployment. The default signed installation uses pulse-update.timer; legacy installations may use pulse-backend-update.timer, and custom names can differ. A name alone does not establish ownership. If the timer's identity is unknown, stop here; do not try every matching unit or run the installer to create one.

For the confirmed default timer, privately record its state before changing it:

timeout --signal=TERM --kill-after=1s 5s systemctl show pulse-update.timer \
  --property=LoadState --property=ActiveState --property=UnitFileState \
  --property=Triggers

Use the actual known timer name in both commands on a legacy or custom install. This read prints only unit state and trigger names, not service environments. LoadState=not-found, a failed read or a timeout is not an inactive timer: stop rather than guessing. If timeout is unavailable, do not substitute an unbounded reader. Keep a masked timer masked; an inactive or disabled timer must not be enabled just to follow these instructions. Enabled state and current active state are separate; preserve both for later recovery. If the timer is runtime-enabled or managed by another scheduler, use that deployment's pause procedure instead of assuming the default command applies.

To pause that confirmed timer, use your authorised administrative account:

sudo systemctl disable --now pulse-update.timer

This stops and disables only the timer, not pulse.service, pulse-backend.service or pulse-update.service. Re-read its bounded state above: it should be inactive and disabled, or remain masked. If the command fails or its result is uncertain, retain the error and current state; do not assume the schedule was paused or issue broader stop/reset commands. Keep independent monitoring and notifications available if Pulse itself is down.

Retain the original update's starting and intended versions, last recorded result, time and timezone, and any recovery already performed. Today's running version is not proof of the version at failure. A verified signature is not a completed install or rollback; Broken pipe does not explain a host stop, and a later boot does not supply its time or cause. If the whole LXC or host stopped, use only an already available, relevant Proxmox task or host event to distinguish that from a Pulse-service failure; keep unknowns unknown. Do not repeat the update, reboot, run Diagnostics or delete rollback backups to fill these gaps.

After the incident is understood and recovery is verified, restore the separately recorded enabled and active states through the existing deployment: enable only if previously enabled, and start only if previously active. Do not unmask a unit, enable a previously disabled timer or create a second schedule. A paused timer does not undo the saved Automatic Stable Updates preference; restoring the schedule may allow the next stable update. Keep configuration, keys, history and backups private and preserved until recovery is verified.

Service won't restart after update

Distinguish the Pulse service from the separate updater service: a failed pulse-update.service is not proof that pulse is stopped. Use the read-only service discovery in Manual Rollback, substituting the actual service name (pulse-backend on some older installs), and the bounded log readers above. For Docker, use the current container-state and image checks with the Pulse server's container name. A running process or container alone is not proof that the UI, saved connections and history are healthy.

If recovery is needed, follow Manual Rollback: preserve the failed state, verify the intended backup's version and complete scope, and use the stopped-service recovery procedure. An absent or partial backup is a reason to stop, not to overwrite live data, create an empty data directory or retry the update. Do not restore a guessed snapshot just because the progress modal failed.