at 25.11-pre 7.8 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 9 cfg = config.services.thinkfan; 10 settingsFormat = pkgs.formats.yaml { }; 11 configFile = settingsFormat.generate "thinkfan.yaml" cfg.settings; 12 thinkfan = pkgs.thinkfan.override { inherit (cfg) smartSupport; }; 13 14 # fan-speed and temperature levels 15 levelType = 16 with lib.types; 17 let 18 tuple = 19 ts: 20 lib.mkOptionType { 21 name = "tuple"; 22 merge = lib.mergeOneOption; 23 check = xs: lib.all lib.id (lib.zipListsWith (t: x: t.check x) ts xs); 24 description = "tuple of" + lib.concatMapStrings (t: " (${t.description})") ts; 25 }; 26 level = ints.unsigned; 27 special = enum [ 28 "level auto" 29 "level full-speed" 30 "level disengaged" 31 ]; 32 in 33 tuple [ 34 (either level special) 35 level 36 level 37 ]; 38 39 # sensor or fan config 40 sensorType = 41 name: 42 lib.types.submodule { 43 freeformType = lib.types.attrsOf settingsFormat.type; 44 options = 45 { 46 type = lib.mkOption { 47 type = lib.types.enum [ 48 "hwmon" 49 "atasmart" 50 "tpacpi" 51 "nvml" 52 ]; 53 description = '' 54 The ${name} type, can be 55 `hwmon` for standard ${name}s, 56 57 `atasmart` to read the temperature via 58 S.M.A.R.T (requires smartSupport to be enabled), 59 60 `tpacpi` for the legacy thinkpac_acpi driver, or 61 62 `nvml` for the (proprietary) nVidia driver. 63 ''; 64 }; 65 query = lib.mkOption { 66 type = lib.types.str; 67 description = '' 68 The query string used to match one or more ${name}s: can be 69 a fullpath to the temperature file (single ${name}) or a fullpath 70 to a driver directory (multiple ${name}s). 71 72 ::: {.note} 73 When multiple ${name}s match, the query can be restricted using the 74 {option}`name` or {option}`indices` options. 75 ::: 76 ''; 77 }; 78 indices = lib.mkOption { 79 type = with lib.types; nullOr (listOf ints.unsigned); 80 default = null; 81 description = '' 82 A list of ${name}s to pick in case multiple ${name}s match the query. 83 84 ::: {.note} 85 Indices start from 0. 86 ::: 87 ''; 88 }; 89 } 90 // lib.optionalAttrs (name == "sensor") { 91 correction = lib.mkOption { 92 type = with lib.types; nullOr (listOf int); 93 default = null; 94 description = '' 95 A list of values to be added to the temperature of each sensor, 96 can be used to equalize small discrepancies in temperature ratings. 97 ''; 98 }; 99 }; 100 }; 101 102 # removes NixOS special and unused attributes 103 sensorToConf = 104 { type, query, ... }@args: 105 (lib.filterAttrs ( 106 k: v: 107 v != null 108 && !(lib.elem k [ 109 "type" 110 "query" 111 ]) 112 ) args) 113 // { 114 "${type}" = query; 115 }; 116 117 syntaxNote = name: '' 118 ::: {.note} 119 This section slightly departs from the thinkfan.conf syntax. 120 The type and path must be specified like this: 121 ``` 122 type = "tpacpi"; 123 query = "/proc/acpi/ibm/${name}"; 124 ``` 125 instead of a single declaration like: 126 ``` 127 - tpacpi: /proc/acpi/ibm/${name} 128 ``` 129 ::: 130 ''; 131 132in 133{ 134 135 options = { 136 137 services.thinkfan = { 138 139 enable = lib.mkOption { 140 type = lib.types.bool; 141 default = false; 142 description = '' 143 Whether to enable thinkfan, a fan control program. 144 145 ::: {.note} 146 This module targets IBM/Lenovo thinkpads by default, for 147 other hardware you will have configure it more carefully. 148 ::: 149 ''; 150 relatedPackages = [ "thinkfan" ]; 151 }; 152 153 smartSupport = lib.mkOption { 154 type = lib.types.bool; 155 default = false; 156 description = '' 157 Whether to build thinkfan with S.M.A.R.T. support to read temperatures 158 directly from hard disks. 159 ''; 160 }; 161 162 sensors = lib.mkOption { 163 type = lib.types.listOf (sensorType "sensor"); 164 default = [ 165 { 166 type = "tpacpi"; 167 query = "/proc/acpi/ibm/thermal"; 168 } 169 ]; 170 description = '' 171 List of temperature sensors thinkfan will monitor. 172 173 ${syntaxNote "thermal"} 174 ''; 175 }; 176 177 fans = lib.mkOption { 178 type = lib.types.listOf (sensorType "fan"); 179 default = [ 180 { 181 type = "tpacpi"; 182 query = "/proc/acpi/ibm/fan"; 183 } 184 ]; 185 description = '' 186 List of fans thinkfan will control. 187 188 ${syntaxNote "fan"} 189 ''; 190 }; 191 192 levels = lib.mkOption { 193 type = lib.types.listOf levelType; 194 default = [ 195 [ 196 0 197 0 198 55 199 ] 200 [ 201 1 202 48 203 60 204 ] 205 [ 206 2 207 50 208 61 209 ] 210 [ 211 3 212 52 213 63 214 ] 215 [ 216 6 217 56 218 65 219 ] 220 [ 221 7 222 60 223 85 224 ] 225 [ 226 "level auto" 227 80 228 32767 229 ] 230 ]; 231 description = '' 232 [LEVEL LOW HIGH] 233 234 LEVEL is the fan level to use: it can be an integer (0-7 with thinkpad_acpi), 235 "level auto" (to keep the default firmware behavior), "level full-speed" or 236 "level disengaged" (to run the fan as fast as possible). 237 LOW is the temperature at which to step down to the previous level. 238 HIGH is the temperature at which to step up to the next level. 239 All numbers are integers. 240 ''; 241 }; 242 243 extraArgs = lib.mkOption { 244 type = lib.types.listOf lib.types.str; 245 default = [ ]; 246 example = [ 247 "-b" 248 "0" 249 ]; 250 description = '' 251 A list of extra command line arguments to pass to thinkfan. 252 Check the {manpage}`thinkfan(1)` manpage for available arguments. 253 ''; 254 }; 255 256 settings = lib.mkOption { 257 type = lib.types.attrsOf settingsFormat.type; 258 default = { }; 259 description = '' 260 Thinkfan settings. Use this option to configure thinkfan 261 settings not exposed in a NixOS option or to bypass one. 262 Before changing this, read the {manpage}`thinkfan.conf(5)` 263 manpage and take a look at the example config file at 264 <https://github.com/vmatare/thinkfan/blob/master/examples/thinkfan.yaml> 265 ''; 266 }; 267 268 }; 269 270 }; 271 272 config = lib.mkIf cfg.enable { 273 274 environment.systemPackages = [ thinkfan ]; 275 276 services.thinkfan.settings = lib.mapAttrs (k: v: lib.mkDefault v) { 277 sensors = map sensorToConf cfg.sensors; 278 fans = map sensorToConf cfg.fans; 279 levels = cfg.levels; 280 }; 281 282 systemd.packages = [ thinkfan ]; 283 284 systemd.services = { 285 thinkfan.environment.THINKFAN_ARGS = lib.escapeShellArgs ( 286 [ 287 "-c" 288 configFile 289 ] 290 ++ cfg.extraArgs 291 ); 292 thinkfan.serviceConfig = { 293 Restart = "on-failure"; 294 RestartSec = "30s"; 295 296 # Hardening 297 PrivateNetwork = true; 298 }; 299 300 # must be added manually, see issue #81138 301 thinkfan.wantedBy = [ "multi-user.target" ]; 302 thinkfan-wakeup.wantedBy = [ "sleep.target" ]; 303 thinkfan-sleep.wantedBy = [ "sleep.target" ]; 304 }; 305 306 boot.extraModprobeConfig = "options thinkpad_acpi experimental=1 fan_control=1"; 307 308 }; 309}