···
8
+
cfg = config.services.pid-fan-controller;
11
+
name = lib.mkOption {
12
+
type = lib.types.uniq lib.types.nonEmptyStr;
13
+
description = "Name of the heat source.";
15
+
wildcardPath = lib.mkOption {
16
+
type = lib.types.nonEmptyStr;
18
+
Path of the heat source's `hwmon` `temp_input` file.
19
+
This path can contain multiple wildcards, but has to resolve to
24
+
setPoint = lib.mkOption {
25
+
type = lib.types.ints.unsigned;
26
+
description = "Set point of the controller in °C.";
29
+
description = "K_p of PID controller.";
30
+
type = lib.types.float;
33
+
description = "K_i of PID controller.";
34
+
type = lib.types.float;
37
+
description = "K_d of PID controller.";
38
+
type = lib.types.float;
46
+
wildcardPath = lib.mkOption {
47
+
type = lib.types.str;
49
+
Wildcard path of the `hwmon` `pwm` file.
50
+
If the fans are not to be found in `/sys/class/hwmon/hwmon*` the corresponding
51
+
kernel module (like `nct6775`) needs to be added to `boot.kernelModules`.
52
+
See the [`hwmon` Documentation](https://www.kernel.org/doc/html/latest/hwmon/index.html).
55
+
minPwm = lib.mkOption {
57
+
type = lib.types.ints.u8;
58
+
description = "Minimum PWM value.";
60
+
maxPwm = lib.mkOption {
62
+
type = lib.types.ints.u8;
63
+
description = "Maximum PWM value.";
65
+
cutoff = lib.mkOption {
67
+
type = lib.types.bool;
68
+
description = "Whether to stop the fan when `minPwm` is reached.";
70
+
heatPressureSrcs = lib.mkOption {
71
+
type = lib.types.nonEmptyListOf lib.types.str;
72
+
description = "Heat pressure sources affected by the fan.";
78
+
options.services.pid-fan-controller = {
79
+
enable = lib.mkEnableOption "the PID fan controller, which controls the configured fans by running a closed-loop PID control loop";
80
+
package = lib.mkPackageOption pkgs "pid-fan-controller" { };
82
+
interval = lib.mkOption {
84
+
type = lib.types.int;
85
+
description = "Interval between controller cycles in milliseconds.";
87
+
heatSources = lib.mkOption {
88
+
type = lib.types.listOf (lib.types.submodule heatSource);
89
+
description = "List of heat sources to be monitored.";
94
+
wildcardPath = "/sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon*/temp1_input";
105
+
fans = lib.mkOption {
106
+
type = lib.types.listOf (lib.types.submodule fan);
107
+
description = "List of fans to be controlled.";
111
+
wildcardPath = "/sys/devices/platform/nct6775.2592/hwmon/hwmon*/pwm1";
114
+
heatPressureSrcs = [
124
+
config = lib.mkIf cfg.enable {
125
+
#map camel cased attrs into snake case for config
126
+
environment.etc."pid-fan-settings.json".text = builtins.toJSON {
127
+
interval = cfg.settings.interval;
128
+
heat_srcs = map (heatSrc: {
129
+
name = heatSrc.name;
130
+
wildcard_path = heatSrc.wildcardPath;
132
+
set_point = heatSrc.pidParams.setPoint;
133
+
P = heatSrc.pidParams.P;
134
+
I = heatSrc.pidParams.I;
135
+
D = heatSrc.pidParams.D;
137
+
}) cfg.settings.heatSources;
139
+
wildcard_path = fan.wildcardPath;
140
+
min_pwm = fan.minPwm;
141
+
max_pwm = fan.maxPwm;
142
+
cutoff = fan.cutoff;
143
+
heat_pressure_srcs = fan.heatPressureSrcs;
144
+
}) cfg.settings.fans;
147
+
systemd.services.pid-fan-controller = {
148
+
wantedBy = [ "multi-user.target" ];
151
+
ExecStart = [ (lib.getExe cfg.package) ];
152
+
ExecStopPost = [ "${lib.getExe cfg.package} disable" ];
153
+
Restart = "always";
154
+
#This service needs to run as root to write to /sys.
155
+
#therefore it should operate with the least amount of privileges needed
156
+
ProtectHome = "yes";
157
+
#strict is not possible as it needs /sys
158
+
ProtectSystem = "full";
159
+
ProtectProc = "invisible";
160
+
PrivateNetwork = "yes";
161
+
NoNewPrivileges = "yes";
162
+
MemoryDenyWriteExecute = "yes";
163
+
RestrictNamespaces = "~user pid net uts mnt";
164
+
ProtectKernelModules = "yes";
165
+
RestrictRealtime = "yes";
166
+
SystemCallFilter = "@system-service";
167
+
CapabilityBoundingSet = "~CAP_KILL CAP_WAKE_ALARM CAP_IPC_LOC CAP_BPF CAP_LINUX_IMMUTABLE CAP_BLOCK_SUSPEND CAP_MKNOD";
169
+
# restart unit if config changed
170
+
restartTriggers = [ config.environment.etc."pid-fan-settings.json".source ];
172
+
#sleep hook to restart the service as it breaks otherwise
173
+
systemd.services.pid-fan-controller-sleep = {
174
+
before = [ "sleep.target" ];
175
+
wantedBy = [ "sleep.target" ];
177
+
StopWhenUnneeded = "yes";
181
+
RemainAfterExit = true;
182
+
ExecStart = [ "systemctl stop pid-fan-controller.service" ];
183
+
ExecStop = [ "systemctl restart pid-fan-controller.service" ];
187
+
meta.maintainers = with lib.maintainers; [ zimward ];