Commit Graph

237 Commits

Author SHA1 Message Date
rcourtman 6b1fc6eaaf Collect LXC filesystems without pmxcfs or LXC socket IPC
The typed helper runs with PrivateNetwork=true. pct list, pct df and
lxc-info reach pmxcfs and the LXC monitor through abstract Unix
sockets, which belong to the network namespace, so inside the helper
they always fail. Least-privilege installs using the helper therefore
reported provider_unavailable for proxmox.lxc_filesystems on every
report and never got per-mount guest usage (#2511).

Discover running guests without commands: list /etc/pve/lxc through the
pmxcfs mount, find each guest's init in /sys/fs/cgroup/lxc/<vmid> (NSpid
exactly "<pid> 1", cgroup inside lxc/<vmid>), and name it by hostname or
CT<vmid> as pct list does. Discovery hands the whole node back to pct
whenever a guest's state cannot be established, so a guest it could not
read never disappears from a healthy-looking inventory.

Read usage through the shared filesystem prober instead of a second,
plain statfs path. It now accepts lxc requests: it pins the process,
requires the guest's exact cgroup and init (a nested namespace's PID 1
cannot stand in), skips declared mounts that are not mounted, and keeps
one bounded probe per guest. The helper's RestrictSUIDSGID makes
systemd answer openat2 with ENOSYS, so the prober falls back to a
component walk that refuses every symbolic link rather than resolving
it; Docker and Podman reads gain the same fallback.

Exercised on a disposable PVE 9.2.18 node under the helper unit's
sandbox properties: unprivileged and privileged guests matched pct df,
a stopped guest was omitted, and the prober's Linux tests passed as
root. A second opinion reviewed the design and the first draft; this
version addresses its findings.
2026-10-06 11:14:21 +01:00
rcourtman d52131e0d1 Close the harness command client before Windows removes its state dir
Since #2550, three command identity tests fail on Windows x64:
TempDir cleanup cannot delete operation-receipts.db because the agent's
command client still holds it open. Those tests never call run, so
nothing closes the client that New built, and Windows refuses to delete
an open file. Other platforms delete it anyway.

Stop and close the command client in a harness cleanup. It is
registered after the state TempDir, so it runs first, and Close is
idempotent for the tests that do run the agent. Production already
closes the client when Run exits or an identity supersedes it.
2026-10-06 10:21:05 +01:00
rcourtman f77c8b2f3d Register agent command channels under the identity Pulse resolves
A token binds the first agent ID its command channel presents and never
moves. The agent registered that channel before its first report, under
its configured --agent-id, or an ID Pulse had forked for a cloned
machine ID or kept from an earlier enrollment. Pulse meanwhile resolved
its reports to another host ID and acknowledged it, and the agent
persisted that ID. The config gate, which judges the resolved ID, then
turned the operator's commands off, and the next restart under the
persisted ID was refused for good.

Report acknowledgements now name commandAgentId: the resolved host ID,
only when the token's binding already holds it or a registration
presenting it would take the first bind, backfill, legacy migration,
hostname rebind or deploy repair it would get anyway. The credential
must also be one command admission takes, and the host must be held
under the reporting token. Naming grants nothing that registration
would not. An established binding to another ID is never moved from
report contents, because resolution can map a report onto another
host's continuity record by machine ID, so a forged report and a stale
one look the same. The config gate takes the same decision.

The agent keeps its channel staged until a report is acknowledged and
registers under the named identity, or under its own ID when none is
named, which is what an older Pulse or an already-bound token sees.
An unreadable ack keeps it waiting, and a 403 releases it under its own
ID because Pulse can never answer that token. A running client is never
rebuilt, since that would reopen its receipt store under in-flight
operations. A superseded client can no longer take the run slot.

Tokens already bound to a diverged ID stay as they are; re-running the
install command issues a fresh token that binds the resolved identity.
2026-10-06 00:36:41 +01:00
rcourtman 9d4ca81271 Exchange the bootstrap token before any agent module starts
A deployed agent's first run gave the remote config client, the updater, and
the Docker / Podman and Kubernetes modules the bootstrap token, which only the
host module exchanged. Pulse revokes that token on exchange, so those clients
failed with 401 until the service restarted. Enroll once at startup and build
every module from the runtime token and the server-assigned agent ID.
2026-10-05 16:01:15 +01:00
pulse-triage[bot] a2608c9fad Match guest Discovery integration proof to native platform capabilities
Native Windows and macOS correctly advertise no PVE guest guard, so their real-server fixture must assert refusal before any config or QGA handoff. Keep the existing Linux admission, outcome and resumption assertions and exercise foreign platform labels without changing runtime capability policy.

Contract-Neutral: Test-only platform evidence repair; guest execution and Discovery runtime contracts, capability advertisement and safety gates are unchanged.
Change-source: pulse-maintainer
2026-10-04 22:49:53 +01:00
pulse-triage[bot] d7ef00f2f2 Keep VM command and Discovery evidence truthful
Project verified guest status and decoded output instead of qm transport success. Reject ambiguous terminal outcomes without replay; retain known failed or truncated results as failures without inventing uncertainty. Admit only successful stdout to Discovery and preserve saved results when attempted probes yield no evidence. Keep backup precautions, source bounds and native acceptance dependencies unchanged.

Change-source: pulse-maintainer
2026-10-04 19:46:32 +01:00
pulse-triage[bot] 4188c2a9ba Reproduce false VM command and Discovery success
Add source controls for actual guest exit/output projection, ambiguous terminal receipts, failed probe evidence exclusion, and saved manual/background Discovery through explicit recovery. The fixtures never invoke PVE or QGA.

Contract-Neutral: Test-only regression controls exercise existing command and Discovery boundaries without changing production code, payloads, capabilities or budgets.
Change-source: pulse-maintainer
2026-10-04 19:35:32 +01:00
pulse-triage[bot] 419d80e2e4 Require terminal guest status before releasing VM execution admission
A zero qm CLI exit may return only a handed-off PID or an unverified guest result. Preserve actual CLI output/exit semantics but pause uncertain terminal dictionaries without another QGA command; accept complete canonical termination evidence and keep native thaw acceptance separate.

Change-source: pulse-maintainer
2026-10-04 18:20:25 +01:00
pulse-triage[bot] 2a734a5c53 Keep canceled VM lock evidence an explicit execution deferral
A cancellation before or during postflight config verification must not become an ordinary optional Discovery failure with usable output, especially on the final probe. Preserve fixed safety classification and no-handoff truth on canceled local admission.

Contract-Neutral: Completes the existing cancellation and unavailable-evidence requirements without changing protocol fields, guard version, lock/identity admission, time budgets or command catalogues.
Change-source: pulse-maintainer
2026-10-04 18:15:30 +01:00
pulse-triage[bot] d20b15b1e6 Complete Discovery compilation and accept canonical PVE config keys
The first exact proof established all ten parent no-admission failures and registered-session controls, then stopped on a missing strings import. Complete that import and preserve ordinary hyphenated PVE dictionary keys while keeping duplicate/case-conflicting lock rejection.

Contract-Neutral: Preserves the guest safety protocol and its lock, identity, budget and dispatch requirements; completes compilation and accepts canonical hyphenated config keys.
Change-source: pulse-maintainer
2026-10-04 18:05:57 +01:00
pulse-triage[bot] be319e8204 Guard VM Discovery and command execution at the owning host agent
Require local current-node lock evidence and registered guard support before QGA execution. Serialise VM probes, stop on deferral, retain actual in-flight outcomes and saved Discovery evidence, and pause uncertain completion without replay. Config checks do not reserve QGA or prove native thaw; the existing backup monitoring-outage precaution remains.

Change-source: pulse-maintainer
2026-10-04 17:59:09 +01:00
pulse-triage[bot] def087ed90 Reproduce unverified VM command admission at the host agent
Approvals and trusted Discovery commands currently cross QGA handoff without evidence of a local unlocked guest. Assert explicit no-handoff deferral without contacting a native target.

Change-source: pulse-maintainer
2026-10-04 17:41:40 +01:00
pulse-triage[bot] 14d3dc3b6c Fix MD RAID spare counts without hiding member failures
Retain configured RAID members separately from source-native totals through collection, reports and canonical read views. Correct the healthy legacy mdadm tuple from #2369 without subtracting spares from mdstat requirements or suppressing real deficits, failures and recovery warnings. Preserve observed zero-active counts through fallback, and verify collector, wire, ingestion, health and canonical activation/resolution boundaries.

Change-source: pulse-maintainer
2026-10-01 19:56:47 +01:00
pulse-triage[bot] 2fd114b3ac Keep Proxmox bootstrap credentials out of copied commands
Separate PVE/PBS setup and telemetry credentials from shell source and download URLs. Use silent root/sudo input, private-file handoff, complete downloads and the agent preflight; preserve scope, TLS defaults, single-line paste and coherent rolling-upgrade metadata. Reveal tokens through the existing dialog and discard late issuance after close. Pin executable shell, history, TLS/registration and browser contracts without using real credentials.

Change-source: pulse-maintainer
2026-10-01 05:23:08 +01:00
pulse-triage[bot] 60f455fd2a Read identified ARM CPU temperatures without lm-sensors
Fall back to bounded, type-checked thermal-zone and hwmon sysfs readings when lm-sensors is absent or empty. Classify Armada thermal JSON as a CPU reading, preserve nonempty sensor output, and cover units, invalid values, and the agent mapping.

Change-source: pulse-maintainer
2026-09-28 20:26:28 +01:00
courtmanr@gmail.com 9e2ee3d535 Fail typed dispatches fast when their agent session drops
The agentexec read loop removed a dead session from the agent map but never
closed its done channel, so any dispatch already sent on that session kept
waiting for a result that could never arrive and only returned at its
operation timeout. That's what made the Proxmox cancellation replay test
flaky on the shared CI runner. The restarted action runner dropped its first
session right after registering, the replay had already been sent there,
and the server waited the full 30s while the runner sat reconnected on a
new session.

The read loop now signals the session done on exit, so in-flight typed
dispatches and commands report the disconnect straight away. The
integration test sends the same request again on the runner's next
session when that happens, which is the same durable replay it is there
to prove, still with exactly one mutation. It also keeps the runner's log
and prints it on failure so the cause of any future drop is visible.
2026-09-28 10:31:58 +01:00
Jamin W. Collins 410134450d feat(agent): allow disabling Proxmox peer sensor collection 2026-09-23 08:18:44 -06:00
pulse-triage[bot] 92d6eae77f fix(hostagent): recover Windows host info when MachineGuid is braced
The Windows unified agent exits at startup when HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid is a braced GUID. gopsutil v4.26.6 reads the value through a fixed 74-byte buffer and rejects any length other than 36, so a braced 38-character GUID fails the whole InfoWithContext call and newAgent aborts with 'fetch host info'. Recover the host info on Windows when the host-ID read is the failing field: read MachineGuid directly, strip braces and fill the other fields from gopsutil accessors, while keeping any non-host-ID failure fatal. Canonicalise Windows machine IDs by removing braces so both forms map to one stable agent identity. Includes agent-lifecycle contract section and focused proof in the registry-accepted agent_new_test.go. Windows recovery is cross-compiled with GOOS=windows; runtime execution on Windows is not qualified in this environment.

Change-source: pulse-maintainer
2026-09-20 08:29:15 +01:00
pulse-triage[bot] c8cead7d5a fix(agents): preserve explicit disk includes at ingestion
Carry the operator include override in disk reports so server filtering does not discard selected tmpfs mounts again. Keep automatic filtering for unmarked reports and agent exclusion precedence. Cover both ingest paths, collection, forwarding and wire compatibility for #1875.

Change-source: pulse-maintainer
2026-09-09 11:35:51 +01:00
pulse-triage[bot] 3334cccfd9 fix(unraid): suppress parity warning for explicit pool-only arrays
Issue #1895 reports parity alerts when mdNumDisks=0 on a pool-only Unraid system. Array service state alone does not establish that a parity array exists.

Preserve the optional disk count from collection through canonical runtime conversion and suppress only the no-parity warning for an explicit zero. Missing or malformed counts retain legacy behaviour, and disk failure reasons remain active.

Validated focused Unraid tests in hostagent, storagehealth, monitoring, unifiedresources and alerts, including JSON zero preservation and canonical round trip. The new pool-only regression fails against the previous warning condition. Both agent and server need this change; no release or reporter retest is claimed.

Change-source: pulse-maintainer
2026-09-04 23:41:22 +01:00
pulse-triage[bot] 0f972f42f2 Isolate host and Docker CPU sampling baselines (#1894)
Near-synchronous host and Docker reports consumed a shared CPU baseline, measuring collection bursts rather than each module's reporting interval. Retain a collector per host collector and a separate Docker module collector while preserving the package-level convenience API and disk filters.

Add an interleaved-counter regression covering both collection entry points. It fails when routed through the shared baseline and passes with isolated state. All hostmetrics, hostagent and dockeragent tests pass, as do the focused CPU regression tests under the race detector.

Change-source: pulse-maintainer
2026-09-04 22:29:32 +01:00
pulse-triage[bot] ace72f8442 Ignore Unraid auto filesystem on empty slots
Treat Unraid's fsType=auto value as a placeholder rather than disk assignment evidence at both agent collection and server ingestion boundaries. Preserve real assigned and explicit missing members.

Change-source: pulse-maintainer
Contract-Neutral: Unraid fsType=auto placeholder normalization fixes false missing-slot alerts without changing wire or subsystem contracts
(cherry picked from commit fd843da7dfc51bba29bb5ddcbcdb4480d0e67079)
(cherry picked from commit 7a6456969d)
2026-09-02 05:40:46 +01:00
rcourtman b1044cd8a4 Let replayed request ids wait for the in-flight handler instead of dropping
Since 60d0651a88 every typed request registers a per-connection cancellable
slot that its handler goroutine releases in a deferred cleanup after sending
its result. The server replays a request id when it wants the durable receipt
again, and that replay can reach the reader before the previous handler's
deferred release runs. launchCancellableRequest treated that as a duplicate
and dropped it, so the server waited out the operation's full timeout for a
result the agent already held. The Linux x64 native-verification leg failed
this way on 12 of the last 25 main runs, always on a "replay 1" dispatch of
host update, storage cleanup, or Docker lifecycle.

Give each slot a done channel that closes on release. A replay whose id is
still registered on the same connection now waits for that release and then
runs, answering from the durable receipt. Invalid ids and over-capacity
requests are still dropped. A unit test pins the wait-then-run behaviour and
the agent-lifecycle contract records the replay rule.
2026-09-01 23:22:55 +01:00
rcourtman e1e4c3e700 Wait for the server to observe runner disconnect before replay reconnect
The cancellation replay test stopped the first action runner and started a
second one as soon as the client goroutine exited. The server observes the
socket close on its own reader, so under GOMAXPROCS=1 with the race detector
the second startRunner saw the stale session as still connected and the
replay was dispatched to the dead socket, timing out after 30s on the
sharded release preflight while passing on multi-core hosts. Wait until the
server reports the agent disconnected before reconnecting, as the agentexec
server tests already do.
2026-09-01 22:51:56 +01:00
rcourtman 60d0651a88 Cancel abandoned typed agent operations 2026-09-01 11:21:26 +01:00
rcourtman 4e22a49f02 Add native PVE action qualification harness 2026-09-01 10:43:35 +01:00
rcourtman 01ff388c7e Preserve native Proxmox guest handoff 2026-09-01 09:31:40 +01:00
rcourtman 2ad094c927 Deflake typed action containment tests
The group-writable executable check wrote its fixture through os.WriteFile,
which filters the mode through the process umask, so under the runner's 022
umask the group-write bit never reached disk and validateTrustedExecutable
correctly trusted the file. Chmod the fixture to the asserted mode.

The Proxmox guest executor tests stamped their after observations one second
past the test-start clock, but actionStartedAt is stamped inside
ExecuteAction after handler setup, so a loaded runner overran the margin and
the independent observation was discarded as pre-action. Widen the
observation offset to a minute.

Both failures broke build-and-test on main (run 33454838531). Reproduced the
hostagent failure on Linux under umask 022 and verified both packages green
after the fix.
2026-09-01 06:15:46 +01:00
rcourtman f9ae289bd5 Contain typed action subprocesses with systemd 2026-09-01 01:26:39 +01:00
rcourtman f967857928 Harden secure agent recovery transports 2026-08-31 23:12:00 +01:00
rcourtman cf021bc9ce Surface typed helper container degradation 2026-08-31 15:53:50 +01:00
rcourtman d19398d82a Surface typed helper degradation in Agent Doctor 2026-08-31 13:06:28 +01:00
Richard Courtman 08f7c5f0d5 Harden secure agent runtime boundaries 2026-08-31 00:06:24 +01:00
Richard Courtman 770733fc92 Make action runner rotation activation-safe 2026-08-30 18:00:33 +01:00
Pulse Test c0b190bfa1 Add application response contracts to availability checks 2026-08-30 17:38:59 +01:00
Pulse Test 712f1a3bfe Add availability history and fleet view 2026-08-30 15:38:34 +01:00
pulse-triage[bot] 95a7191ca9 Enforce native private state permissions 2026-08-30 03:31:00 +01:00
pulse-triage[bot] 184b3cbfa6 Restore Windows native agent verification 2026-08-30 02:05:30 +01:00
Pulse Test d06ffc233d Harden secure agent runtime transitions 2026-08-30 01:41:57 +01:00
Pulse Test 5b2e377843 Expose secure agent runtime posture 2026-08-30 00:38:44 +01:00
Pulse Test d607d5cf46 Separate agent remediation runtime 2026-08-29 23:48:28 +01:00
Pulse Test 6d4ee48000 Add typed agent privilege helper 2026-08-29 22:51:58 +01:00
Pulse Test a966264bb1 Contain agent command authority 2026-08-29 22:12:41 +01:00
pulse-triage[bot] 5570ac1e2e fix(agent): silence unchanged remote config refreshes 2026-08-28 21:56:26 +01:00
pulse-triage[bot] 9e820355b6 Fix Unraid sentinel empty slot reporting 2026-08-28 14:20:43 +01:00
pulse-triage[bot] cb27b8eb24 Fix Unraid empty slot health reporting 2026-08-28 01:23:12 +01:00
rcourtman a9076de15a fix(patrol): require Docker health recovery 2026-08-27 20:43:54 +01:00
rcourtman 6163a2a564 feat(patrol): verify Docker actions independently 2026-08-27 20:14:38 +01:00
pulse-triage[bot] 8fde82b8a2 Keep LXC filesystem paths native-proof
Join Proxmox config paths with Linux semantics across native test platforms, add the regression proof, and update the canonical agent lifecycle contract for the new namespace-statfs collector.

Change-source: pulse-maintainer
2026-08-27 06:51:36 +01:00
rcourtman 8318c74687 fix(hostagent): stop pct df from starving LXC filesystem collection
pct df takes the container config lock and costs over a second per
guest, and every per-container query shared one 10-second budget with
pct list, so hosts with more than a handful of running containers only
ever reported filesystem usage for the first few (signal: killed on
the rest).

Resolve per-mount usage with statfs through /proc/<pid>/root instead:
read the config-declared mounts from /etc/pve/lxc, resolve the init
pid via lxc-info, and drop config mounts whose device identity matches
their parent directory rather than inventing the parent's numbers.
pct df remains as a per-container fallback for least-privilege
installs that cannot traverse /proc/<pid>/root, each container now
carries its own deadline inside a bounded collection window, and
exhausting that window logs how many containers were skipped instead
of failing silently at debug level.

Refs #1477
2026-08-27 06:22:10 +01:00