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,
lib,
mylib,
pkgs,
...
}:
@@ -30,29 +29,9 @@ in
node-exporter.extraGroups = [ "rapl" ];
};
systemd.services.rapl-permissions = lib.mkIf config.services.prometheus.exporters.node.enable {
description = "Allow node_exporter to read RAPL energy counters";
wantedBy = [ "multi-user.target" ];
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" ];
};
# udev's GROUP/MODE only apply to device nodes, not sysfs attributes.
services.udev.extraRules = lib.mkIf config.services.prometheus.exporters.node.enable ''
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"
'';
};
}
@@ -41,7 +41,7 @@ in
name:
configs.${name}.config.modules.powerMonitoring.enable
&& 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 (
name:
@@ -51,9 +51,9 @@ in
desktopRaplDisabled =
!configs.ai-niri.config.modules.powerMonitoring.enable
&& !(builtins.elem "rapl" configs.ai-niri.config.services.prometheus.exporters.node.enabledCollectors)
&& !(configs.ai-niri.config.systemd.services ? rapl-permissions);
guestsHaveNoRapl = lib.all (name: !(configs.${name}.config.systemd.services ? rapl-permissions)) (
lib.subtractLists physicalHosts (builtins.attrNames configs)
);
&& !(lib.hasInfix "chgrp rapl" configs.ai-niri.config.services.udev.extraRules);
guestsHaveNoRapl = lib.all (
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;
}
@@ -82,7 +82,7 @@ in
) cfg.environment.systemPackages;
gpuMetricsExported = builtins.elem "drm" 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
# module's entries, so a duplicate restart-loops the unit.
collectorFlagsUnique =
@@ -34,9 +34,6 @@ in
};
privilegedOneshot = {
rapl-permissions = privilegedOneshot // {
CapabilityBoundingSet = [ "CAP_CHOWN" ];
};
hdd-no-spindown = privilegedOneshot // {
CapabilityBoundingSet = [ "CAP_SYS_RAWIO" ];
DeviceAllow = [ "block-sd rw" ];
@@ -38,7 +38,6 @@ units "youko" [
// units "aquamarine" [ "whisper-asr" ]
// {
privilegedOneshot = {
rapl-permissions = privilegedUnit "aquamarine" "rapl-permissions";
hdd-no-spindown = privilegedUnit "youko" "hdd-no-spindown";
};
}