refactor(power-monitoring): grant RAPL read access from a udev rule

The boot-time oneshot only fixed the zones present when it ran, and
node_exporter had to Require it. A powercap add rule covers every zone
whenever it appears; the collector reads at scrape time, so no ordering
is needed.

Assisted-by: Claude Code (claude-opus-5-5)
This commit is contained in:
Ryan Yin authored and ryan4yin | 二花 committed 2026-11-01 11:42:57 -06:00
1 parent 42341491f3
commit f8ca25a03b
5 files changed
+10 -35

No files matched your search

+4 -25
View File
@@ -1,7 +1,6 @@
{ {
config, config,
lib, lib,
mylib,
pkgs, pkgs,
... ...
}: }:
@@ -30,29 +29,9 @@ in
node-exporter.extraGroups = [ "rapl" ]; node-exporter.extraGroups = [ "rapl" ];
}; };
systemd.services.rapl-permissions = lib.mkIf config.services.prometheus.exporters.node.enable { # udev's GROUP/MODE only apply to device nodes, not sysfs attributes.
description = "Allow node_exporter to read RAPL energy counters"; services.udev.extraRules = lib.mkIf config.services.prometheus.exporters.node.enable ''
wantedBy = [ "multi-user.target" ]; ACTION=="add", SUBSYSTEM=="powercap", TEST=="energy_uj", RUN+="${pkgs.coreutils}/bin/chgrp rapl /sys%p/energy_uj", RUN+="${pkgs.coreutils}/bin/chmod 0440 /sys%p/energy_uj"
unitConfig.ConditionPathExists = "/sys/class/powercap"; '';
serviceConfig = mylib.systemdHardening.privilegedOneshot // {
Type = "oneshot";
RemainAfterExit = true;
CapabilityBoundingSet = [ "CAP_CHOWN" ];
};
script = ''
shopt -s nullglob
for f in /sys/class/powercap/*/energy_uj; do
chgrp rapl "$f"
chmod 0440 "$f"
done
'';
};
systemd.services.prometheus-node-exporter =
lib.mkIf config.services.prometheus.exporters.node.enable
{
requires = [ "rapl-permissions.service" ];
after = [ "rapl-permissions.service" ];
};
}; };
} }
@@ -41,7 +41,7 @@ in
name: name:
configs.${name}.config.modules.powerMonitoring.enable configs.${name}.config.modules.powerMonitoring.enable
&& builtins.elem "rapl" configs.${name}.config.services.prometheus.exporters.node.enabledCollectors && builtins.elem "rapl" configs.${name}.config.services.prometheus.exporters.node.enabledCollectors
&& configs.${name}.config.systemd.services ? rapl-permissions && lib.hasInfix "chgrp rapl" configs.${name}.config.services.udev.extraRules
); );
serverRaplReaderOnly = lib.all ( serverRaplReaderOnly = lib.all (
name: name:
@@ -51,9 +51,9 @@ in
desktopRaplDisabled = desktopRaplDisabled =
!configs.ai-niri.config.modules.powerMonitoring.enable !configs.ai-niri.config.modules.powerMonitoring.enable
&& !(builtins.elem "rapl" configs.ai-niri.config.services.prometheus.exporters.node.enabledCollectors) && !(builtins.elem "rapl" configs.ai-niri.config.services.prometheus.exporters.node.enabledCollectors)
&& !(configs.ai-niri.config.systemd.services ? rapl-permissions); && !(lib.hasInfix "chgrp rapl" configs.ai-niri.config.services.udev.extraRules);
guestsHaveNoRapl = lib.all (name: !(configs.${name}.config.systemd.services ? rapl-permissions)) ( guestsHaveNoRapl = lib.all (
lib.subtractLists physicalHosts (builtins.attrNames configs) name: !(lib.hasInfix "chgrp rapl" configs.${name}.config.services.udev.extraRules)
); ) (lib.subtractLists physicalHosts (builtins.attrNames configs));
desktopBatteryReporting = configs.ai-niri.config.services.upower.enable; desktopBatteryReporting = configs.ai-niri.config.services.upower.enable;
} }
@@ -82,7 +82,7 @@ in
) cfg.environment.systemPackages; ) cfg.environment.systemPackages;
gpuMetricsExported = builtins.elem "drm" cfg.services.prometheus.exporters.node.enabledCollectors; gpuMetricsExported = builtins.elem "drm" cfg.services.prometheus.exporters.node.enabledCollectors;
raplMetricsExported = builtins.elem "rapl" cfg.services.prometheus.exporters.node.enabledCollectors; raplMetricsExported = builtins.elem "rapl" cfg.services.prometheus.exporters.node.enabledCollectors;
raplPermissionsExposed = cfg.systemd.services ? rapl-permissions; raplPermissionsExposed = lib.hasInfix "chgrp rapl" cfg.services.udev.extraRules;
# node_exporter rejects a repeated flag, and this list merges with the base # node_exporter rejects a repeated flag, and this list merges with the base
# module's entries, so a duplicate restart-loops the unit. # module's entries, so a duplicate restart-loops the unit.
collectorFlagsUnique = collectorFlagsUnique =
@@ -34,9 +34,6 @@ in
}; };
privilegedOneshot = { privilegedOneshot = {
rapl-permissions = privilegedOneshot // {
CapabilityBoundingSet = [ "CAP_CHOWN" ];
};
hdd-no-spindown = privilegedOneshot // { hdd-no-spindown = privilegedOneshot // {
CapabilityBoundingSet = [ "CAP_SYS_RAWIO" ]; CapabilityBoundingSet = [ "CAP_SYS_RAWIO" ];
DeviceAllow = [ "block-sd rw" ]; DeviceAllow = [ "block-sd rw" ];
@@ -38,7 +38,6 @@ units "youko" [
// units "aquamarine" [ "whisper-asr" ] // units "aquamarine" [ "whisper-asr" ]
// { // {
privilegedOneshot = { privilegedOneshot = {
rapl-permissions = privilegedUnit "aquamarine" "rapl-permissions";
hdd-no-spindown = privilegedUnit "youko" "hdd-no-spindown"; hdd-no-spindown = privilegedUnit "youko" "hdd-no-spindown";
}; };
} }