feat(agent): allow disabling Proxmox peer sensor collection

This commit is contained in:
Jamin W. Collins
2026-09-22 16:43:54 -06:00
parent 2d2d272cb4
commit 410134450d
10 changed files with 132 additions and 38 deletions
+40 -35
View File
@@ -590,38 +590,39 @@ func run(ctx context.Context, args []string, getenv func(string) string) error {
}
}
hostCfg := hostagent.Config{
PulseURL: cfg.PulseURL,
APIToken: cfg.APIToken,
Interval: cfg.Interval,
HostnameOverride: cfg.HostnameOverride,
AgentID: cfg.AgentID,
AgentType: "unified",
AgentVersion: Version,
Tags: cfg.Tags,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CACertPath: cfg.CACertPath,
ServerFingerprint: cfg.ServerFingerprint,
CustomSensorsFile: cfg.CustomSensorsFile,
DeploySSHUser: cfg.DeploySSHUser,
LogLevel: cfg.LogLevel,
Logger: &logger,
EnableProxmox: cfg.EnableProxmox,
ProxmoxType: cfg.ProxmoxType,
EnableCommands: cfg.EnableCommands,
CommandAuthorityProfile: cfg.CommandAuthorityProfile,
Enroll: cfg.Enroll,
DiskExclude: cfg.DiskExclude,
DiskInclude: cfg.DiskInclude,
StateDir: cfg.StateDir,
ReportIP: cfg.ReportIP,
DisableCeph: cfg.DisableCeph,
AvailabilityTargets: cfg.AvailabilityTargets,
AppliedConfig: cfg.AppliedConfig,
ModuleStatus: runtimeStatus.moduleStatuses,
PrivilegeHelperStatus: privilegeHelperStatus,
Observers: hostObserverTargets(cfg.Observers),
PrivilegedTelemetry: privilegedTelemetry,
UpdatedFromVersion: pendingUpdatePreviousVersion(pendingUpdate),
PulseURL: cfg.PulseURL,
APIToken: cfg.APIToken,
Interval: cfg.Interval,
HostnameOverride: cfg.HostnameOverride,
AgentID: cfg.AgentID,
AgentType: "unified",
AgentVersion: Version,
Tags: cfg.Tags,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CACertPath: cfg.CACertPath,
ServerFingerprint: cfg.ServerFingerprint,
CustomSensorsFile: cfg.CustomSensorsFile,
DeploySSHUser: cfg.DeploySSHUser,
LogLevel: cfg.LogLevel,
Logger: &logger,
EnableProxmox: cfg.EnableProxmox,
ProxmoxType: cfg.ProxmoxType,
EnableCommands: cfg.EnableCommands,
CommandAuthorityProfile: cfg.CommandAuthorityProfile,
Enroll: cfg.Enroll,
DiskExclude: cfg.DiskExclude,
DiskInclude: cfg.DiskInclude,
StateDir: cfg.StateDir,
ReportIP: cfg.ReportIP,
DisableCeph: cfg.DisableCeph,
DisableClusterPeerSensors: cfg.DisableClusterPeerSensors,
AvailabilityTargets: cfg.AvailabilityTargets,
AppliedConfig: cfg.AppliedConfig,
ModuleStatus: runtimeStatus.moduleStatuses,
PrivilegeHelperStatus: privilegeHelperStatus,
Observers: hostObserverTargets(cfg.Observers),
PrivilegedTelemetry: privilegedTelemetry,
UpdatedFromVersion: pendingUpdatePreviousVersion(pendingUpdate),
DockerContainerUpdater: dockerUpdaterBridge,
DockerContainerLifecycleOperator: dockerUpdaterBridge,
@@ -1106,9 +1107,10 @@ type Config struct {
DiskInclude []string // Devices or mount points to opt into monitoring despite automatic filtering
// Network configuration
ReportIP string // IP address to report (for multi-NIC systems)
DisableCeph bool // Disable local Ceph status polling
SelfTest bool // Perform self-test and exit
ReportIP string // IP address to report (for multi-NIC systems)
DisableCeph bool // Disable local Ceph status polling
DisableClusterPeerSensors bool // Keep Proxmox monitoring local and skip SSH sensor collection from cluster peers
SelfTest bool // Perform self-test and exit
// AvailabilityTargets are externally probed availability checks assigned to
// this agent by the server. Remote config is the only source.
@@ -1242,6 +1244,7 @@ func loadConfig(args []string, getenv func(string) string) (Config, error) {
envDiskInclude := strings.TrimSpace(getenv("PULSE_DISK_INCLUDE"))
envReportIP := strings.TrimSpace(getenv("PULSE_REPORT_IP"))
envDisableCeph := strings.TrimSpace(getenv("PULSE_DISABLE_CEPH"))
envDisableClusterPeerSensors := strings.TrimSpace(getenv("PULSE_DISABLE_CLUSTER_PEER_SENSORS"))
envObserversFile := strings.TrimSpace(getenv("PULSE_OBSERVERS_FILE"))
envCustomSensorsFile := strings.TrimSpace(getenv("PULSE_CUSTOM_SENSORS_FILE"))
@@ -1325,6 +1328,7 @@ func loadConfig(args []string, getenv func(string) string) (Config, error) {
stateDirFlag := fs.String("state-dir", envStateDir, "Persistent state directory (default: platform service state directory)")
reportIPFlag := fs.String("report-ip", envReportIP, "IP address to report (for multi-NIC systems)")
disableCephFlag := fs.Bool("disable-ceph", utils.ParseBool(envDisableCeph), "Disable local Ceph status polling")
disableClusterPeerSensorsFlag := fs.Bool("disable-cluster-peer-sensors", utils.ParseBool(envDisableClusterPeerSensors), "Skip SSH sensor collection from Proxmox cluster peers")
showVersion := fs.Bool("version", false, "Print the agent version and exit")
selfTest := fs.Bool("self-test", false, "Perform self-test and exit (used during auto-update)")
@@ -1496,6 +1500,7 @@ func loadConfig(args []string, getenv func(string) string) (Config, error) {
DiskInclude: diskInclude,
ReportIP: strings.TrimSpace(*reportIPFlag),
DisableCeph: *disableCephFlag,
DisableClusterPeerSensors: *disableClusterPeerSensorsFlag,
SelfTest: *selfTest,
}, nil
}
+10
View File
@@ -348,6 +348,16 @@ func TestApplyRemoteSettings(t *testing.T) {
}
}
func TestLoadConfigDisableClusterPeerSensorsFlag(t *testing.T) {
cfg, err := loadConfig([]string{"--disable-cluster-peer-sensors"}, func(string) string { return "" })
if err != nil {
t.Fatalf("loadConfig returned unexpected error: %v", err)
}
if !cfg.DisableClusterPeerSensors {
t.Fatal("expected --disable-cluster-peer-sensors to disable peer sensor collection")
}
}
func TestApplyRemoteSettingsHonorsLocalDockerOptOut(t *testing.T) {
logger := zerolog.New(io.Discard)
cfg := &Config{
+6
View File
@@ -19,6 +19,12 @@ verification guidance.
> Note: For agent-based temperature monitoring, use `pulse-agent --enable-proxmox` or SSH-based collection. The legacy sensor proxy has been removed. See `docs/TEMPERATURE_MONITORING.md`.
When each Proxmox cluster node runs an agent, add
`--disable-cluster-peer-sensors` (or set
`PULSE_DISABLE_CLUSTER_PEER_SENSORS=true`) to keep temperature collection local
to each node. This preserves local host telemetry and Proxmox integration while
preventing duplicate root-SSH sensor polling between cluster peers.
## Quick Start
Generate an installation command in the UI:
@@ -164,6 +164,15 @@ management, and fleet control surfaces. Pulse v6 has one host-installed
infrastructure agent binary, `pulse-agent`; host, Docker / Podman,
Kubernetes, Proxmox-local, and other node-local telemetry are modules inside
that binary, not separate customer-facing agent products.
In a Proxmox VE cluster, an enabled agent may collect peer temperature data
through its existing SSH sensor path. `--disable-cluster-peer-sensors` and
`PULSE_DISABLE_CLUSTER_PEER_SENSORS=true` are local, default-off operator
controls that stop only that peer discovery and SSH collection path. They do
not disable the local host, SMART, Ceph, or Proxmox reporting modules, alter
agent enrollment or identity, or add remote command authority. The disabled
path is pinned by `TestCollectClusterSensors_Disabled`; configuration parsing
is pinned by `TestLoadConfigDisableClusterPeerSensorsFlag`.
On supported Linux systemd hosts, the opt-in safe runtime is a root-owned,
unprivileged monitoring collector plus the no-network typed helper, with
remediation installed only as the separate root-owned `pulse-agent-runner`.
@@ -202,6 +202,11 @@ configuration. It may override Pulse's automatic filesystem suppression only;
it must not override a matching operator exclusion or introduce remote
authority to expand the reported filesystem set silently.
The local `--disable-cluster-peer-sensors` control may suppress only the
agent's outbound Proxmox peer sensor SSH probes. It neither accepts a remote
command nor changes SSH credentials, target authority, agent scopes, token
handling, enrollment, or the local host/SMART/Ceph/Proxmox reporting boundary.
Retained Patrol objective briefs and optional context are operator-authored AI
content. They are encrypted at rest in the organization-scoped Pulse data
directory and loading fails closed if decryption fails. Audit and telemetry
@@ -19,6 +19,12 @@ verification guidance.
> Note: For agent-based temperature monitoring, use `pulse-agent --enable-proxmox` or SSH-based collection. The legacy sensor proxy has been removed. See `docs/TEMPERATURE_MONITORING.md`.
When each Proxmox cluster node runs an agent, add
`--disable-cluster-peer-sensors` (or set
`PULSE_DISABLE_CLUSTER_PEER_SENSORS=true`) to keep temperature collection local
to each node. This preserves local host telemetry and Proxmox integration while
preventing duplicate root-SSH sensor polling between cluster peers.
## Quick Start
Generate an installation command in the UI:
+3 -2
View File
@@ -80,8 +80,9 @@ type Config struct {
DeploySSHUser string // Default: root. Non-root users must support passwordless sudo for install steps.
// Network configuration
ReportIP string // IP address to report instead of auto-detected (for multi-NIC systems)
DisableCeph bool // If true, disables local Ceph status polling
ReportIP string // IP address to report instead of auto-detected (for multi-NIC systems)
DisableCeph bool // If true, disables local Ceph status polling
DisableClusterPeerSensors bool // If true, keeps Proxmox monitoring local and skips SSH sensor collection from cluster peers
// AvailabilityTargets are the externally probed availability checks the
// server assigned to this agent at startup. Later assignments arrive
+22
View File
@@ -60,6 +60,28 @@ func TestNew_AllowsMissingAPITokenWhenEnrollmentDisabled(t *testing.T) {
}
}
func TestNew_PreservesDisableClusterPeerSensors(t *testing.T) {
mc := &mockCollector{
hostInfoFn: func(context.Context) (*gohost.InfoStat, error) {
return &gohost.InfoStat{Hostname: "pve-host", HostID: "hid", KernelArch: runtime.GOARCH}, nil
},
}
agent, err := New(Config{
APIToken: "token",
EnableProxmox: true,
DisableClusterPeerSensors: true,
LogLevel: zerolog.InfoLevel,
Collector: mc,
})
if err != nil {
t.Fatalf("New: %v", err)
}
if !agent.cfg.DisableClusterPeerSensors {
t.Fatal("expected peer sensor collection opt-out to be preserved")
}
}
func TestNew_AllowsLocalNetworkHTTPPulseURL(t *testing.T) {
mc := &mockCollector{
hostInfoFn: func(context.Context) (*gohost.InfoStat, error) {
+1 -1
View File
@@ -36,7 +36,7 @@ const (
// collectClusterSensors discovers Proxmox cluster siblings and collects temperature
// data from each via SSH. Returns nil if not in a cluster or no peers are reachable.
func (a *Agent) collectClusterSensors(ctx context.Context) []agentshost.ClusterNodeSensors {
if !a.cfg.EnableProxmox {
if !a.cfg.EnableProxmox || a.cfg.DisableClusterPeerSensors {
return nil
}
@@ -182,6 +182,36 @@ func TestCollectClusterSensors_NotProxmox(t *testing.T) {
}
}
func TestCollectClusterSensors_Disabled(t *testing.T) {
logger := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.Out = os.Stderr
})).Level(zerolog.DebugLevel)
commandCalled := false
a := &Agent{
logger: logger,
cfg: Config{
EnableProxmox: true,
DisableClusterPeerSensors: true,
},
collector: &mockCollector{
goos: "linux",
commandCombinedOutputFn: func(ctx context.Context, name string, arg ...string) (string, error) {
commandCalled = true
return "", fmt.Errorf("cluster peer collection must be disabled")
},
},
}
result := a.collectClusterSensors(context.Background())
if result != nil {
t.Errorf("expected nil when cluster peer sensors are disabled, got %v", result)
}
if commandCalled {
t.Error("cluster peer collection ran despite being disabled")
}
}
func TestCollectClusterSensors_NotLinux(t *testing.T) {
logger := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.Out = os.Stderr