mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
Prepare v6.3.0-rc.4 release
This commit is contained in:
@@ -2,9 +2,9 @@ apiVersion: v2
|
||||
name: pulse
|
||||
description: Helm chart for deploying the Pulse hub and optional Docker, Kubernetes, or OpenShift monitoring agent.
|
||||
type: application
|
||||
version: 6.3.0-rc.3
|
||||
appVersion: "6.3.0-rc.3"
|
||||
icon: https://raw.githubusercontent.com/rcourtman/Pulse/v6.3.0-rc.3/docs/images/pulse-logo.svg
|
||||
version: 6.3.0-rc.4
|
||||
appVersion: "6.3.0-rc.4"
|
||||
icon: https://raw.githubusercontent.com/rcourtman/Pulse/v6.3.0-rc.4/docs/images/pulse-logo.svg
|
||||
keywords:
|
||||
- monitoring
|
||||
- proxmox
|
||||
@@ -32,7 +32,7 @@ annotations:
|
||||
description: Smoke tests with kind cluster deployment
|
||||
artifacthub.io/links: |
|
||||
- name: Documentation
|
||||
url: https://github.com/rcourtman/Pulse/blob/v6.3.0-rc.3/docs/KUBERNETES.md
|
||||
url: https://github.com/rcourtman/Pulse/blob/v6.3.0-rc.4/docs/KUBERNETES.md
|
||||
- name: Support
|
||||
url: https://github.com/rcourtman/Pulse/discussions
|
||||
artifacthub.io/maintainers: |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# pulse
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
Helm chart for deploying the Pulse hub and optional Docker, Kubernetes, or OpenShift monitoring agent.
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ version: '3.8'
|
||||
|
||||
services:
|
||||
pulse:
|
||||
image: ${PULSE_IMAGE:-rcourtman/pulse:6.3.0-rc.3}
|
||||
image: ${PULSE_IMAGE:-rcourtman/pulse:6.3.0-rc.4}
|
||||
container_name: pulse
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
|
||||
@@ -6,10 +6,10 @@ Pulse release notes live on GitHub:
|
||||
For historical v4 notes that previously lived in this repo, see:
|
||||
`docs/releases/RELEASE_NOTES_v4.md`
|
||||
|
||||
For the current v6 release candidate packet (the current v6 support release
|
||||
candidate packet), see:
|
||||
- `docs/releases/RELEASE_NOTES_v6.3.0-rc.3.md`
|
||||
- `docs/releases/V6_CHANGELOG_v6.3.0-rc.3.md`
|
||||
For the current v6 release candidate packet
|
||||
(the current v6 support release candidate packet), see:
|
||||
- `docs/releases/RELEASE_NOTES_v6.3.0-rc.4.md`
|
||||
- `docs/releases/V6_CHANGELOG_v6.3.0-rc.4.md`
|
||||
|
||||
For the current stable v6 packet, see:
|
||||
- `docs/releases/RELEASE_NOTES_v6.2.1.md`
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ This guide covers practical upgrade steps for existing Pulse installs moving to
|
||||
|
||||
For the current v6 release candidate packet, see:
|
||||
|
||||
- `docs/releases/RELEASE_NOTES_v6.3.0-rc.3.md`
|
||||
- `docs/releases/V6_CHANGELOG_v6.3.0-rc.3.md`
|
||||
- `docs/releases/RELEASE_NOTES_v6.3.0-rc.4.md`
|
||||
- `docs/releases/V6_CHANGELOG_v6.3.0-rc.4.md`
|
||||
|
||||
For the current stable v6 packet, see:
|
||||
|
||||
|
||||
@@ -437,6 +437,15 @@ removed because a saved view was only ever the page's URL query string, which
|
||||
the browser's own bookmarks already capture and share. History narrowing stays
|
||||
URL-owned so a filtered history page remains a shareable, bookmarkable link.
|
||||
|
||||
The alert history severity facet derives each option count through
|
||||
`useAlertHistoryState.countForSeverity`, using the same
|
||||
`filterAlertHistoryItems` predicate that supplies the rendered list for the
|
||||
current fetched period and search term. It must not count an unfiltered or
|
||||
separately reduced collection that can disagree with the selected chip's
|
||||
result. These counts follow the shared Inventory totals visibility preference;
|
||||
the Period facet remains uncounted because it selects the fetched time scope
|
||||
rather than filtering the already-fetched rows.
|
||||
|
||||
Alert history row timestamps render clock time in the viewer's own locale and
|
||||
must carry the absolute date and time as a title. The date otherwise lives
|
||||
only in the day group header, which scrolls out of sight, and a hardcoded
|
||||
|
||||
@@ -4331,7 +4331,10 @@ first — `entries` carrying `notificationId`, `type`, `destinationId`,
|
||||
counts are retention-bounded and must not read as lifetime history; webhook
|
||||
secrets are redacted from error text before the payload leaves the API. The
|
||||
frontend transport in `frontend-modern/src/api/notifications.ts` validates
|
||||
entry shape and drops malformed rows rather than rendering them. Successful
|
||||
entry shape and drops malformed rows rather than rendering them. It must route
|
||||
`entries` through the shared `arrayOrEmpty` normalization helper instead of
|
||||
creating a notifications-local array fallback that can drift from other API
|
||||
clients. Successful
|
||||
test-send responses from `POST /api/notifications/test` and
|
||||
`POST /api/notifications/webhooks/test` must include `deliveryPaused: true`
|
||||
whenever real alert delivery is gated off, so a passing test cannot be
|
||||
|
||||
@@ -1461,10 +1461,10 @@ diagnostics. The same release workflow also executes the generated self-signed
|
||||
and custom-CA Windows installer commands through Windows PowerShell 5.1 before
|
||||
release assembly, so the first HTTPS fetch is release proof rather than a
|
||||
string-shape assertion.
|
||||
The active prerelease `v6.3.0-rc.3` cut sets the repo-root `VERSION`,
|
||||
The active prerelease `v6.3.0-rc.4` cut sets the repo-root `VERSION`,
|
||||
repo-root `docker-compose.yml` image default, `scripts/install-docker.sh`
|
||||
fallback, and Helm chart release metadata to the same `6.3.0-rc.3` release
|
||||
version. It follows stable `v6.2.1`, opens the published 6.3 candidate line, uses
|
||||
fallback, and Helm chart release metadata to the same `6.3.0-rc.4` release
|
||||
version. It follows `v6.3.0-rc.3` on the published 6.3 candidate line, uses
|
||||
`rollback_version=v6.2.1`, and keeps stable/latest pointers on `v6.2.1`. This
|
||||
candidate establishes the new minor-release packet with durable scoped Patrol
|
||||
objectives, validated read-only observers, verified work receipts, agent action
|
||||
@@ -1473,8 +1473,11 @@ monitoring correctness fixes. The advanced branch also carries the decision-firs
|
||||
Patrol inbox, first-class Actions workspace, canonical platform-admission
|
||||
projection, and bounded concurrent unified-resource hydration. Subscription-backed
|
||||
Patrol turns bound command cleanup after an idle deadline so descendant-held
|
||||
output pipes cannot extend the caller-owned stall budget. The changes since
|
||||
`v6.3.0-rc.2` do not require a Pulse Mobile client change and preserve the
|
||||
output pipes cannot extend the caller-owned stall budget. This candidate also
|
||||
adds canonical estate summaries and search, a real-delivery activity log,
|
||||
least-privilege agent installation, and bounded Docker-in-LXC discovery while
|
||||
preserving unreadable settings and AI state. The changes since
|
||||
`v6.3.0-rc.3` do not require a Pulse Mobile client change and preserve the
|
||||
existing mobile, Relay, onboarding, and mobile-facing API contracts, so the
|
||||
server cut is classified `no-mobile-impact`; no companion upload or public
|
||||
mobile-store rollout is part of this candidate.
|
||||
@@ -1760,8 +1763,8 @@ For the active stable `v6.1.2` cut, the repo-root compose default and
|
||||
`scripts/install-docker.sh` fallback must both pin `6.1.2` whenever the
|
||||
governed `VERSION` is that stable cut. The stable promotion guard remains in
|
||||
force and rejects leftover `-rc.` defaults.
|
||||
For the active prerelease `v6.3.0-rc.3` cut, the repo-root compose default and
|
||||
`scripts/install-docker.sh` fallback must both pin `6.3.0-rc.3`
|
||||
For the active prerelease `v6.3.0-rc.4` cut, the repo-root compose default and
|
||||
`scripts/install-docker.sh` fallback must both pin `6.3.0-rc.4`
|
||||
until the next governed stable cut moves them forward. Each new release moves
|
||||
these two pins together with the repo-root `VERSION` and the Helm chart metadata
|
||||
in the same commit; a cut that leaves any of the four on a superseded value is a
|
||||
|
||||
@@ -4301,6 +4301,13 @@ search-aware active-state accessor to `FilterBar`; the shared shell owns where
|
||||
the contextual Clear filters action appears, while the alerts feature must not
|
||||
fall back to sequential per-control URL writes or hide that action for a
|
||||
search-only result set.
|
||||
Alert History severity option counts also follow the shared estate-orientation
|
||||
contract: the alerts feature supplies counts from the exact predicate used by
|
||||
its list, while `FilterBar` renders the values and the shared Inventory totals
|
||||
visibility preference decides whether they are shown. A feature must not build
|
||||
counts from page-wide alert totals after search or another active facet has
|
||||
narrowed the rendered rows, and a time-scope option must not claim a count from
|
||||
an unfetched period.
|
||||
Because that popover combines view application, default selection, removal,
|
||||
and an inline naming form, it is a labelled non-modal dialog rather than an
|
||||
ARIA menu. Its trigger exposes the dialog relationship, Escape returns focus
|
||||
|
||||
@@ -344,6 +344,10 @@ unreadable queue is an error, never an empty log. This per-attempt surface is
|
||||
deliberately distinct from the content-free telemetry aggregate above, which
|
||||
remains identity-free. Test sends bypass the queue and must not appear in the
|
||||
delivery log, and the destinations UI says so where the log renders.
|
||||
The frontend client normalizes the `entries` collection through the shared API
|
||||
collection helper before validating each record, so a malformed collection or
|
||||
row cannot bypass the canonical client boundary or create a module-local
|
||||
fallback shape.
|
||||
|
||||
Because test sends also bypass the alert activation gate, a bare success
|
||||
result is exactly how installs come to believe delivery works while every
|
||||
|
||||
+31
-9
@@ -1,15 +1,15 @@
|
||||
# Pulse v6.3.0-rc.3 Release Notes
|
||||
# Pulse v6.3.0-rc.4 Release Notes
|
||||
|
||||
`v6.3.0-rc.3` is a release candidate for the next Pulse v6 minor release,
|
||||
following `v6.3.0-rc.2` in the 6.3 line and stable `v6.2.1`. It carries the
|
||||
`v6.3.0-rc.4` is a release candidate for the next Pulse v6 minor release,
|
||||
following `v6.3.0-rc.3` in the 6.3 line and stable `v6.2.1`. It carries the
|
||||
Patrol v2 operating model, a first-class Actions workspace, guarded agent
|
||||
action preflight, large-estate response improvements, and monitoring
|
||||
correctness fixes.
|
||||
|
||||
This candidate adds a group of alert-delivery repairs. Production telemetry
|
||||
showed installs with a configured notification destination that had never
|
||||
delivered anything and had no way to find out, because the channel that would
|
||||
carry such a warning is the one that had failed.
|
||||
This candidate adds estate-first platform summaries and search, an operator-
|
||||
visible notification delivery log, a supported least-privilege agent profile,
|
||||
and safer Docker-in-LXC discovery. It also prevents failed settings or AI-state
|
||||
reads from silently overwriting preserved data.
|
||||
|
||||
## Highlights
|
||||
|
||||
@@ -19,6 +19,14 @@ carry such a warning is the one that had failed.
|
||||
without granting mutation authority.
|
||||
- Approved actions gain agent preflight and stable refusal telemetry; large
|
||||
installations gain compressed APIs and indexed lookups.
|
||||
- Platform pages now lead with estate totals, status facets, and search that
|
||||
share the same predicates as their underlying tables.
|
||||
- Notification settings show the outcome of real delivery attempts instead of
|
||||
relying on test sends as a proxy for live delivery health.
|
||||
- Docker-in-LXC discovery is explicitly controlled and backs off against slow
|
||||
or failing Proxmox hosts instead of creating a probe storm.
|
||||
- Unified Agent installs can opt into a supported least-privilege profile with
|
||||
narrowly scoped elevation for the capabilities that require it.
|
||||
|
||||
## Added
|
||||
|
||||
@@ -35,6 +43,12 @@ carry such a warning is the one that had failed.
|
||||
- Unified Agent preflight contracts for package updates, package-cache cleanup,
|
||||
and Docker lifecycle or update operations.
|
||||
- Production security deployment guidance and a focused security-review packet.
|
||||
- Estate overviews and infrastructure search across Proxmox, Docker,
|
||||
Kubernetes, TrueNAS, VMware, and standalone agent surfaces.
|
||||
- A seven-day notification delivery activity log with destination and outcome
|
||||
details for queued live alerts.
|
||||
- An administrator setting for Docker-in-LXC discovery and a supported
|
||||
least-privilege Unified Agent installation profile.
|
||||
|
||||
## Improved
|
||||
|
||||
@@ -65,6 +79,10 @@ carry such a warning is the one that had failed.
|
||||
limits, readiness checks, deadlines, and continuation latency more reliably.
|
||||
- Subscription-backed turns now complete their idle timeout promptly even when
|
||||
a canceled CLI descendant still holds an inherited output pipe open.
|
||||
- Platform and alert-history facet counts are derived from each table's own
|
||||
filter predicate, keeping summary totals aligned with the visible rows.
|
||||
- Failed settings and AI-state reads preserve the last known data and surface
|
||||
the failure instead of treating unreadable storage as an empty value.
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -85,6 +103,10 @@ carry such a warning is the one that had failed.
|
||||
- vSphere backup status, agent thermal history, explicit cluster-member address
|
||||
overrides, and discovery-analysis request timeouts now reflect their actual
|
||||
runtime state.
|
||||
- Docker-in-LXC discovery no longer retries overlapping probes against slow
|
||||
Proxmox hosts, and command enablement after install is reflected without a
|
||||
reinstall.
|
||||
- In-progress Proxmox backups are no longer presented as completed backups.
|
||||
- The notifications surface now states when alert delivery is paused, and that
|
||||
a passing test send does not prove live alerts are getting through. Test
|
||||
sends bypass the delivery pause, so a configured destination could look
|
||||
@@ -110,7 +132,7 @@ carry such a warning is the one that had failed.
|
||||
|
||||
## Upgrade Notes
|
||||
|
||||
Use the normal v6 install or update flow for `v6.3.0-rc.3` only when you are
|
||||
Use the normal v6 install or update flow for `v6.3.0-rc.4` only when you are
|
||||
comfortable testing a release candidate. The rollback target is `v6.2.1`.
|
||||
|
||||
The exact rollback reinstall command is:
|
||||
@@ -119,7 +141,7 @@ The exact rollback reinstall command is:
|
||||
./scripts/install.sh --version v6.2.1
|
||||
```
|
||||
|
||||
The changes since `v6.3.0-rc.2` do not require a Pulse Mobile client change
|
||||
The changes since `v6.3.0-rc.3` do not require a Pulse Mobile client change
|
||||
and preserve the existing mobile, Relay, onboarding, and mobile-facing API
|
||||
contracts. No companion mobile build upload or public mobile-store rollout is
|
||||
part of this candidate.
|
||||
+19
-6
@@ -1,7 +1,7 @@
|
||||
# Pulse v6.3.0-rc.3
|
||||
# Pulse v6.3.0-rc.4
|
||||
|
||||
_This changelog describes the changes since `v6.3.0-rc.2` and carries forward
|
||||
the complete cumulative 6.3 packet since stable `v6.2.1`. `v6.3.0-rc.3` is a
|
||||
_This changelog describes the changes since `v6.3.0-rc.3` and carries forward
|
||||
the complete cumulative 6.3 packet since stable `v6.2.1`. `v6.3.0-rc.4` is a
|
||||
prerelease and rolls back to stable `v6.2.1`._
|
||||
|
||||
## Added
|
||||
@@ -19,6 +19,11 @@ prerelease and rolls back to stable `v6.2.1`._
|
||||
- System-scoped alerts, so Pulse can report a fault in itself rather than only
|
||||
in a monitored resource. Broken notification delivery is the first, raised as
|
||||
an ordinary alert so it reaches the alert list and navigation badge.
|
||||
- Estate summaries, status facets, and canonical search on the primary
|
||||
infrastructure platform pages.
|
||||
- A seven-day delivery log for real alert notification attempts.
|
||||
- A supported least-privilege Unified Agent installation profile and an
|
||||
administrator-controlled Docker-in-LXC discovery mode.
|
||||
|
||||
## Changed
|
||||
|
||||
@@ -39,6 +44,10 @@ prerelease and rolls back to stable `v6.2.1`._
|
||||
source-tree value.
|
||||
- Buffered subscription CLI turns cannot extend a caller-owned idle deadline
|
||||
while a canceled descendant keeps an inherited output pipe open.
|
||||
- Platform and alert-history counts now use the same canonical filter
|
||||
predicates as their visible result tables.
|
||||
- Failed settings and AI-state reads retain preserved data and expose the read
|
||||
failure instead of silently replacing state with defaults.
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -67,6 +76,10 @@ prerelease and rolls back to stable `v6.2.1`._
|
||||
drained and a resource oscillating just under the threshold was never damped.
|
||||
- Mobile tables keep narrow values readable, preserve compact replication
|
||||
values, and use a consistent density across platform surfaces.
|
||||
- Docker-in-LXC discovery serializes and backs off slow probes, preserves
|
||||
post-install command enablement, and surfaces failures without host churn.
|
||||
- In-progress Proxmox backups remain in progress rather than being shown as
|
||||
completed.
|
||||
|
||||
## Security
|
||||
|
||||
@@ -79,8 +92,8 @@ prerelease and rolls back to stable `v6.2.1`._
|
||||
|
||||
## Release Metadata
|
||||
|
||||
- Version: `v6.3.0-rc.3`
|
||||
- Previous release: `v6.2.1`
|
||||
- Version: `v6.3.0-rc.4`
|
||||
- Previous release: `v6.3.0-rc.3`
|
||||
- Previous stable: `v6.2.1`
|
||||
- Rollback target: `v6.2.1`
|
||||
- Rollback command: `./scripts/install.sh --version v6.2.1`
|
||||
@@ -89,6 +102,6 @@ prerelease and rolls back to stable `v6.2.1`._
|
||||
- Windows signing decision: the standing prerelease path publishes exact-SHA,
|
||||
checksum, and detached-signature verified Windows agents without
|
||||
Authenticode; stable `v6.3.0` restores mandatory SignPath signing
|
||||
- Mobile decision: `no-mobile-impact`; changes since `v6.3.0-rc.2` preserve the
|
||||
- Mobile decision: `no-mobile-impact`; changes since `v6.3.0-rc.3` preserve the
|
||||
existing mobile, Relay, onboarding, and mobile-facing API contracts, and no
|
||||
companion upload or public store rollout is required
|
||||
@@ -1,36 +1,35 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "ee4cd560b6352fac50b988104e3ee370db8669c1",
|
||||
"verified_at": "2026-08-20T21:08:35Z",
|
||||
"base_sha": "4c7b1a2434385f13c5c89dd3a113cd894636ac4d",
|
||||
"verified_at": "2026-08-20T22:47:56Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/features/alerts/AlertHistoryFiltersCard.tsx",
|
||||
"frontend-modern/src/features/alerts/useAlertHistoryState.ts"
|
||||
"frontend-modern/src/api/notifications.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/features/alerts/AlertHistoryFiltersCard.tsx": "07836c5b85ea484cfe984b2d0de5c302202ef6b844eafdc776b3f979f0938acb",
|
||||
"frontend-modern/src/features/alerts/useAlertHistoryState.ts": "7528bd735ff84961e8bd880d89f0253255c698ab5910c1eb2fb10cf0255e39e8"
|
||||
"frontend-modern/src/api/notifications.ts": "ac714c8f87c1e26f1abf523b4c4c7b83c0ce75250c92741b69dc6a36b35557ff"
|
||||
},
|
||||
"routes": [
|
||||
"/alerts/history"
|
||||
"/alerts/notifications"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 720
|
||||
"width": 1440,
|
||||
"height": 1000
|
||||
},
|
||||
{
|
||||
"width": 375,
|
||||
"height": 812
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Alert history severity facet now counted from the live history data: All 47 / Critical 8 / Warning 13, with Period chips left uncounted as time scopes",
|
||||
"Critical selected: the page's own summary reads '8 alerts', matching the chip count exactly (counts share filterAlertHistoryItems with the list)",
|
||||
"Chips verified through the mobile Filters sheet (the hidden preview pane renders the responsive mobile layout); the same options render inline at desktop width"
|
||||
"Notifications page loaded with delivery paused and all destination configuration sections available",
|
||||
"Recent delivery activity rendered its seven-day empty state with the refresh control enabled",
|
||||
"Desktop and narrow layouts kept the delivery card, explanatory copy, refresh control, and empty state visible without clipping"
|
||||
],
|
||||
"interactions": [
|
||||
"Opened the Filters sheet on /alerts/history and read the counted severity chips",
|
||||
"Clicked the Critical chip, confirmed the '8 alerts' summary, then reset the facet back to All"
|
||||
"Refreshed delivery status at desktop width and confirmed the empty state remained available",
|
||||
"Scrolled through the narrow notification layout and refreshed delivery status again",
|
||||
"Checked the browser console after both refresh interactions and found no errors"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,15 +2,26 @@
|
||||
|
||||
Pulse agents incorporate several security mechanisms to ensure that the code running on your infrastructure is authentic and untampered with.
|
||||
|
||||
**Start with the least privilege that answers your monitoring question.** For
|
||||
Proxmox VE, PBS, and PMG, that is usually no agent at all: API-only monitoring
|
||||
with a read-only token covers inventory, status, and metrics, and the
|
||||
generated setup script creates a privilege-separated monitoring user for it
|
||||
(see [Proxmox Deployment Choices](#proxmox-deployment-choices)). Install a
|
||||
host agent only where you want data the platform API cannot provide, and on
|
||||
Linux consider the supported
|
||||
[least-privilege profile](#least-privilege-agent-profile) before the root
|
||||
default.
|
||||
|
||||
## Agent Privilege Model
|
||||
|
||||
Pulse's Linux/systemd installer runs the unified agent as `root` by default.
|
||||
That is intentional for full host telemetry: disk SMART data, mdadm/RAID state,
|
||||
temperature sensors, Docker or Podman socket reads, Proxmox host-local details
|
||||
that are not available through the API, and some NAS/platform integrations
|
||||
commonly require root or equivalent local privileges. Running the service as a
|
||||
lower-privilege user may work for a narrow subset of metrics, but it is not a
|
||||
supported full-telemetry profile today.
|
||||
commonly require root or equivalent local privileges. On Linux/systemd hosts,
|
||||
the supported alternative is the least-privilege profile documented below; it
|
||||
trades the root-only collectors it has not been granted for a dedicated
|
||||
non-root service user.
|
||||
|
||||
Treat a host agent like other infrastructure monitoring software with local
|
||||
root read access:
|
||||
@@ -123,12 +134,44 @@ mirrors the generated read/monitoring ACLs onto both the service user and the
|
||||
token. For PBS, the generated script grants the `Audit` ACL to both the service
|
||||
user and token.
|
||||
|
||||
Running `pulse-agent` as a custom non-root systemd user is possible by editing
|
||||
the service unit, but it is not a supported full-telemetry mode today. Expect
|
||||
gaps in SMART, temperature, Docker socket, ZFS/Ceph/mdadm, mount, and platform
|
||||
integration data unless you deliberately grant equivalent capabilities or group
|
||||
access. If you choose that route, treat it as a local hardening profile and
|
||||
verify the exact metrics you care about after the change.
|
||||
## Least-Privilege Agent Profile
|
||||
|
||||
On standard Linux systemd hosts, `install.sh --least-privilege` is a supported
|
||||
alternative to the root profile. It runs the service as a dedicated
|
||||
`pulse-agent` system user (nologin shell, owning only its state directory and
|
||||
binary), joins the `docker` group when Docker monitoring is enabled so socket
|
||||
reads keep working, and keeps every hardening directive of the root unit while
|
||||
dropping the LXC-attach ambient capability grant entirely.
|
||||
|
||||
Two optional flags restore the collectors that genuinely need elevation, each
|
||||
through an exact-command sudoers grant validated with `visudo` and a
|
||||
root-owned wrapper the agent is pointed at via an absolute-path-only
|
||||
environment override. Because `NoNewPrivileges` blocks `sudo` entirely, a
|
||||
unit with an active grant sets `NoNewPrivileges=false` while keeping the
|
||||
remaining hardening; a grantless least-privilege install keeps
|
||||
`NoNewPrivileges=true`. Choose grants deliberately: each one is a scoped,
|
||||
auditable widening of the profile.
|
||||
|
||||
- `--grant-smart` allows exactly `smartctl`, restoring SMART disk health.
|
||||
- `--grant-pct` allows exactly `pct list` and `pct df`, restoring Proxmox LXC
|
||||
filesystem capacity. The grant deliberately excludes `pct exec`, `start`,
|
||||
`stop`, and `enter`, so guest Docker inventory stays a root-profile feature.
|
||||
|
||||
What the profile gives up: command execution (`--enable-commands` is refused
|
||||
and a later server-side enable requires reinstalling the root profile),
|
||||
`pct exec` guest Docker inventory, and any platform integration that needs
|
||||
device or socket access you have not granted. Core metrics, mounts, `/proc`
|
||||
RAID state, hwmon temperatures, and Docker socket reads work without root.
|
||||
Ungranted collectors fail soft, and the agent reports its privilege profile so
|
||||
**Settings → Infrastructure → Agent Doctor** shows the service user and active
|
||||
helpers instead of presenting missing collectors as a fault. Appliance
|
||||
platforms (TrueNAS, Synology, QNAP, Unraid) and non-systemd init systems keep
|
||||
the root profile; the installer refuses `--least-privilege` there rather than
|
||||
silently falling back to root.
|
||||
|
||||
`--update` preserves an existing least-privilege profile and its grants
|
||||
without the flags being repeated. Uninstall removes the sudoers file and
|
||||
helpers; the inert system user is left behind deliberately.
|
||||
|
||||
## Supply-Chain Boundary
|
||||
|
||||
|
||||
@@ -48,10 +48,13 @@ The default posture limits that boundary:
|
||||
- Proxmox guest Docker inventory through `pct exec` is disabled by default and
|
||||
requires an explicit server setting.
|
||||
|
||||
A custom non-root systemd user is possible, but it is not currently a supported
|
||||
full-telemetry profile. Expect gaps unless you deliberately grant equivalent
|
||||
device, filesystem, or socket access. If API data is sufficient, API-only
|
||||
monitoring is the cleaner least-privilege choice.
|
||||
On standard Linux systemd hosts the installer also offers a supported
|
||||
least-privilege profile: `--least-privilege` runs the service as a dedicated
|
||||
`pulse-agent` system user, with optional `--grant-smart` and `--grant-pct`
|
||||
flags that restore SMART and Proxmox LXC filesystem collection through
|
||||
exact-command sudoers grants. Command execution and `pct exec` guest inventory
|
||||
stay root-profile features. If API data is sufficient, API-only monitoring
|
||||
remains the cleanest least-privilege choice of all — it needs no agent.
|
||||
|
||||
See [Agent Security](AGENT_SECURITY.md) for the precise command, guest-access,
|
||||
update, and service-hardening boundaries.
|
||||
|
||||
@@ -4,8 +4,8 @@ This guide covers practical upgrade steps for existing Pulse installs moving to
|
||||
|
||||
For the current v6 release candidate packet, see:
|
||||
|
||||
- `docs/releases/RELEASE_NOTES_v6.3.0-rc.3.md`
|
||||
- `docs/releases/V6_CHANGELOG_v6.3.0-rc.3.md`
|
||||
- `docs/releases/RELEASE_NOTES_v6.3.0-rc.4.md`
|
||||
- `docs/releases/V6_CHANGELOG_v6.3.0-rc.4.md`
|
||||
|
||||
For the current stable v6 packet, see:
|
||||
|
||||
|
||||
@@ -362,6 +362,17 @@ describe('NotificationsAPI', () => {
|
||||
expect(log.windowDays).toBe(7);
|
||||
});
|
||||
|
||||
it('normalizes a malformed delivery-log collection through the shared API boundary', async () => {
|
||||
apiFetchJSONMock.mockResolvedValueOnce({
|
||||
entries: { notificationId: 'not-a-collection' },
|
||||
window_days: 7,
|
||||
} as any);
|
||||
|
||||
const log = await NotificationsAPI.getDeliveryLog();
|
||||
|
||||
expect(log).toEqual({ entries: [], windowDays: 7 });
|
||||
});
|
||||
|
||||
it('passes the deliveryPaused flag through from test-send responses', async () => {
|
||||
apiFetchJSONMock.mockResolvedValueOnce({
|
||||
status: 'success',
|
||||
|
||||
@@ -339,7 +339,7 @@ export class NotificationsAPI {
|
||||
const payload = await apiFetchJSON<Record<string, unknown>>(
|
||||
`${this.baseUrl}/delivery-log${query}`,
|
||||
);
|
||||
const rawEntries = Array.isArray(payload.entries) ? payload.entries : [];
|
||||
const rawEntries = arrayOrEmpty<unknown>(payload.entries);
|
||||
const entries: NotificationDeliveryLogEntry[] = [];
|
||||
for (const rawEntry of rawEntries) {
|
||||
const entry = normalizeDeliveryLogEntry(rawEntry);
|
||||
|
||||
@@ -713,9 +713,7 @@ describe('workloadSelectors', () => {
|
||||
|
||||
it('returns the input list when no scope or exclusions apply', () => {
|
||||
const guests = [makeGuest(1), makeGuest(2)];
|
||||
expect(
|
||||
selectVisibleWorkloadInventory({ guests, excludedTypes: noExclusions }),
|
||||
).toBe(guests);
|
||||
expect(selectVisibleWorkloadInventory({ guests, excludedTypes: noExclusions })).toBe(guests);
|
||||
expect(
|
||||
selectVisibleWorkloadInventory({
|
||||
guests,
|
||||
|
||||
@@ -20684,13 +20684,8 @@ func TestContract_AgentFleetDiagnosticsEndpointSurfacesStableShape(t *testing.T)
|
||||
if err != nil {
|
||||
t.Fatalf("read router_routes_registration.go: %v", err)
|
||||
}
|
||||
monitoringSource, err := os.ReadFile("../monitoring/agent_fleet_doctor.go")
|
||||
if err != nil {
|
||||
t.Fatalf("read monitoring agent_fleet_doctor.go: %v", err)
|
||||
}
|
||||
handlerSrc := string(handler)
|
||||
routerSrc := string(router)
|
||||
monitoringSrc := string(monitoringSource)
|
||||
|
||||
if !strings.Contains(routerSrc, `"/api/agents/diagnostics"`) ||
|
||||
!strings.Contains(routerSrc, `RequireAdmin(r.config, RequireScope(config.ScopeSettingsRead, r.handleAgentFleetDiagnostics))`) {
|
||||
@@ -20699,21 +20694,34 @@ func TestContract_AgentFleetDiagnosticsEndpointSurfacesStableShape(t *testing.T)
|
||||
if !strings.Contains(handlerSrc, "GetAgentFleetDiagnosticsForTarget(serverVersion, agentUpdateTargetVersion, time.Now().UTC())") {
|
||||
t.Error("agent fleet diagnostics handler must delegate to the monitoring-owned read-only producer")
|
||||
}
|
||||
for _, required := range []string{
|
||||
"SchemaVersion int `json:\"schemaVersion\"`",
|
||||
"GeneratedAt int64 `json:\"generatedAt\"`",
|
||||
"ServerVersion string `json:\"serverVersion,omitempty\"`",
|
||||
"AgentUpdateTargetVersion string `json:\"agentUpdateTargetVersion,omitempty\"`",
|
||||
"Summary AgentFleetDiagnosticSummary `json:\"summary\"`",
|
||||
"Agents []AgentFleetAgentDiagnostic `json:\"agents\"`",
|
||||
"ConnectionID string `json:\"connectionId,omitempty\"`",
|
||||
"AgentUpdate *AgentFleetDiagnosticUpdate `json:\"agentUpdate,omitempty\"`",
|
||||
"AgentModules []AgentFleetDiagnosticModule `json:\"agentModules,omitempty\"`",
|
||||
"Reasons []AgentFleetDiagnosticReason `json:\"reasons\"`",
|
||||
"RepairActions []AgentFleetDiagnosticRepair `json:\"repairActions,omitempty\"`",
|
||||
for _, required := range []struct {
|
||||
typeOf reflect.Type
|
||||
name string
|
||||
goType reflect.Type
|
||||
jsonTag string
|
||||
}{
|
||||
{reflect.TypeOf(monitoring.AgentFleetDiagnostics{}), "SchemaVersion", reflect.TypeOf(int(0)), "schemaVersion"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetDiagnostics{}), "GeneratedAt", reflect.TypeOf(int64(0)), "generatedAt"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetDiagnostics{}), "ServerVersion", reflect.TypeOf(""), "serverVersion,omitempty"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetDiagnostics{}), "AgentUpdateTargetVersion", reflect.TypeOf(""), "agentUpdateTargetVersion,omitempty"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetDiagnostics{}), "Summary", reflect.TypeOf(monitoring.AgentFleetDiagnosticSummary{}), "summary"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetDiagnostics{}), "Agents", reflect.TypeOf([]monitoring.AgentFleetAgentDiagnostic(nil)), "agents"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetAgentDiagnostic{}), "ConnectionID", reflect.TypeOf(""), "connectionId,omitempty"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetAgentDiagnostic{}), "AgentUpdate", reflect.TypeOf((*monitoring.AgentFleetDiagnosticUpdate)(nil)), "agentUpdate,omitempty"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetAgentDiagnostic{}), "AgentModules", reflect.TypeOf([]monitoring.AgentFleetDiagnosticModule(nil)), "agentModules,omitempty"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetAgentDiagnostic{}), "Reasons", reflect.TypeOf([]monitoring.AgentFleetDiagnosticReason(nil)), "reasons"},
|
||||
{reflect.TypeOf(monitoring.AgentFleetAgentDiagnostic{}), "RepairActions", reflect.TypeOf([]monitoring.AgentFleetDiagnosticRepair(nil)), "repairActions,omitempty"},
|
||||
} {
|
||||
if !strings.Contains(monitoringSrc, required) {
|
||||
t.Errorf("agent fleet diagnostics payload missing stable field %q", required)
|
||||
field, ok := required.typeOf.FieldByName(required.name)
|
||||
if !ok {
|
||||
t.Errorf("%s payload missing stable field %s", required.typeOf.Name(), required.name)
|
||||
continue
|
||||
}
|
||||
if field.Type != required.goType {
|
||||
t.Errorf("%s.%s type = %s, want %s", required.typeOf.Name(), required.name, field.Type, required.goType)
|
||||
}
|
||||
if got := field.Tag.Get("json"); got != required.jsonTag {
|
||||
t.Errorf("%s.%s json tag = %q, want %q", required.typeOf.Name(), required.name, got, required.jsonTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ func TestTransportCommandCatalogsResolveToRegistry(t *testing.T) {
|
||||
"MsgTypeExecuteCmd": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.raw-command", DurableAuthorityID: "assistant.resource-action"},
|
||||
"MsgTypeHostStorageCleanup": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.host-package-cache-cleanup", DurableAuthorityID: "resource.host.package-cache-cleanup"},
|
||||
"MsgTypeReadFile": {Role: TransportRoleProtocol},
|
||||
"MsgTypeCancelCmd": {Role: TransportRoleProtocol},
|
||||
"MsgTypeHostUpdate": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.host-package-update", DurableAuthorityID: "resource.host.package-update"},
|
||||
"MsgTypeDockerContainerLifecycle": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.docker-container-lifecycle", DurableAuthorityID: "resource.docker.container-lifecycle"},
|
||||
"MsgTypeDockerContainerUpdate": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.docker-container-update", DurableAuthorityID: "resource.docker.container-update"},
|
||||
|
||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
DOCKER_IMAGE_REPO="${DOCKER_IMAGE_REPO:-rcourtman/pulse}"
|
||||
CANONICAL_DEFAULT_PULSE_VERSION="6.3.0-rc.3"
|
||||
CANONICAL_DEFAULT_PULSE_VERSION="6.3.0-rc.4"
|
||||
|
||||
resolve_default_pulse_version() {
|
||||
if [ -n "${PULSE_IMAGE_VERSION:-}" ]; then
|
||||
|
||||
@@ -205,6 +205,9 @@ func renderLifecycleService(t *testing.T, stateDir, stateSource, unitPath, pulse
|
||||
SERVER_FINGERPRINT=""
|
||||
OBSERVERS_FILE=""
|
||||
ENABLE_COMMANDS="` + commandFlag + `"
|
||||
LEAST_PRIVILEGE="false"
|
||||
GRANT_SMART="false"
|
||||
GRANT_PCT="false"
|
||||
HEALTH_ADDR_SET="true"
|
||||
HEALTH_ADDR=""
|
||||
ENROLL="true"
|
||||
@@ -234,6 +237,7 @@ func renderLifecycleService(t *testing.T, stateDir, stateSource, unitPath, pulse
|
||||
` + extractInstallShellFunction(t, "join_exec_arg_items") + `
|
||||
` + extractInstallShellFunction(t, "build_exec_args") + `
|
||||
` + extractInstallShellFunction(t, "systemd_agent_requires_lxc_attach") + `
|
||||
` + extractInstallShellFunction(t, "systemd_agent_may_attach_lxc") + `
|
||||
` + extractInstallShellFunction(t, "render_systemd_agent_unit") + `
|
||||
` + extractInstallShellFunction(t, "save_connection_info") + recovery + `
|
||||
ensure_runtime_token_file "$STATE_DIR"
|
||||
|
||||
@@ -658,6 +658,10 @@ func TestCurrentPrereleasePacketTracksInstallMetadata(t *testing.T) {
|
||||
"Actions and Patrol identify whether a decision originated from a finding, alert, objective, or explicit operator request",
|
||||
"Action refusal telemetry now classifies target changes, prerequisites, contract failures",
|
||||
"Subscription-backed turns now complete their idle timeout promptly",
|
||||
"Platform pages now lead with estate totals, status facets, and search",
|
||||
"Notification settings show the outcome of real delivery attempts",
|
||||
"Docker-in-LXC discovery is explicitly controlled and backs off against slow or failing Proxmox hosts",
|
||||
"Unified Agent installs can opt into a supported least-privilege profile",
|
||||
"The rollback target is `v"+previous+"`",
|
||||
"The changes since `v"+comparisonVersion+"` do not require a Pulse Mobile client change",
|
||||
"preserve the existing mobile, Relay, onboarding, and mobile-facing API contracts",
|
||||
@@ -675,6 +679,9 @@ func TestCurrentPrereleasePacketTracksInstallMetadata(t *testing.T) {
|
||||
"A primary Actions workspace for approvals, governed plans, and action records",
|
||||
"Typed Unified Agent action preflight for supported host and Docker operations",
|
||||
"Stable pre-mutation refusal codes and fleet telemetry buckets",
|
||||
"Estate summaries, status facets, and canonical search on the primary infrastructure platform pages",
|
||||
"A seven-day delivery log for real alert notification attempts",
|
||||
"A supported least-privilege Unified Agent installation profile",
|
||||
"Windows signing decision: the standing prerelease path publishes exact-SHA, checksum, and detached-signature verified Windows agents without Authenticode; stable `v"+stableTarget+"` restores mandatory SignPath signing",
|
||||
"Mobile decision: `no-mobile-impact`",
|
||||
"changes since `v"+comparisonVersion+"` preserve the existing mobile, Relay, onboarding, and mobile-facing API contracts",
|
||||
|
||||
@@ -358,6 +358,7 @@ func TestInstallDockerProofTracksPrereleaseContract(t *testing.T) {
|
||||
|
||||
assertFileContainsAllNormalized(t, repoFile("docs", "release-control", "v6", "internal", "subsystems", "deployment-installability.md"),
|
||||
"The active prerelease `v"+version+"` cut sets the repo-root `VERSION`, repo-root `docker-compose.yml` image default, `scripts/install-docker.sh` fallback, and Helm chart release metadata to the same `"+version+"` release version.",
|
||||
"It follows `v"+comparisonVersion+"` on the published 6.3 candidate line",
|
||||
"This prerelease keeps `rollback_version=v"+previous+"`, publishes a versioned public GitHub prerelease plus versioned Docker and Helm artifacts, and does not move stable/latest install pointers or stable semver aliases.",
|
||||
"The changes since `v"+comparisonVersion+"` do not require a Pulse Mobile client change and preserve the existing mobile, Relay, onboarding, and mobile-facing API contracts, so the server cut is classified `no-mobile-impact`; no companion upload or public mobile-store rollout is part of this candidate.",
|
||||
"The prerelease Windows path retains exact-SHA, checksum, and detached-signature verification without Authenticode; stable `v"+stableTarget+"` restores mandatory SignPath signing unless a new version-bound decision is recorded.",
|
||||
|
||||
Reference in New Issue
Block a user