at 25.11-pre 18 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 options, 6 utils, 7 ... 8}: 9 10let 11 inherit (lib) 12 concatStrings 13 foldl 14 foldl' 15 genAttrs 16 literalExpression 17 maintainers 18 mapAttrs 19 mapAttrsToList 20 mkDefault 21 mkEnableOption 22 mkIf 23 mkMerge 24 mkOption 25 optional 26 types 27 mkOptionDefault 28 flip 29 attrNames 30 xor 31 ; 32 33 cfg = config.services.prometheus.exporters; 34 35 # each attribute in `exporterOpts` is expected to have specified: 36 # - port (types.int): port on which the exporter listens 37 # - serviceOpts (types.attrs): config that is merged with the 38 # default definition of the exporter's 39 # systemd service 40 # - extraOpts (types.attrs): extra configuration options to 41 # configure the exporter with, which 42 # are appended to the default options 43 # 44 # Note that `extraOpts` is optional, but a script for the exporter's 45 # systemd service must be provided by specifying either 46 # `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart` 47 48 exporterOpts = 49 (genAttrs 50 [ 51 "apcupsd" 52 "artifactory" 53 "bind" 54 "bird" 55 "bitcoin" 56 "blackbox" 57 "borgmatic" 58 "buildkite-agent" 59 "ecoflow" 60 "chrony" 61 "collectd" 62 "deluge" 63 "dmarc" 64 "dnsmasq" 65 "dnssec" 66 "domain" 67 "dovecot" 68 "ebpf" 69 "fastly" 70 "flow" 71 "fritz" 72 "fritzbox" 73 "frr" 74 "graphite" 75 "idrac" 76 "imap-mailstat" 77 "influxdb" 78 "ipmi" 79 "jitsi" 80 "json" 81 "junos-czerwonk" 82 "kea" 83 "keylight" 84 "klipper" 85 "knot" 86 "libvirt" 87 "lnd" 88 "mail" 89 "mikrotik" 90 "modemmanager" 91 "mongodb" 92 "mqtt" 93 "mysqld" 94 "nats" 95 "nextcloud" 96 "nginx" 97 "nginxlog" 98 "node" 99 "node-cert" 100 "nut" 101 "nvidia-gpu" 102 "pgbouncer" 103 "php-fpm" 104 "pihole" 105 "ping" 106 "postfix" 107 "postgres" 108 "process" 109 "pve" 110 "py-air-control" 111 "rasdaemon" 112 "redis" 113 "restic" 114 "rspamd" 115 "rtl_433" 116 "sabnzbd" 117 "scaphandre" 118 "script" 119 "shelly" 120 "smartctl" 121 "smokeping" 122 "snmp" 123 "sql" 124 "statsd" 125 "surfboard" 126 "systemd" 127 "tibber" 128 "unbound" 129 "unpoller" 130 "v2ray" 131 "varnish" 132 "wireguard" 133 "zfs" 134 ] 135 ( 136 name: 137 import (./. + "/exporters/${name}.nix") { 138 inherit 139 config 140 lib 141 pkgs 142 options 143 utils 144 ; 145 } 146 ) 147 ) 148 // (mapAttrs 149 ( 150 name: params: 151 import (./. + "/exporters/${params.name}.nix") { 152 inherit 153 config 154 lib 155 pkgs 156 options 157 utils 158 ; 159 type = params.type; 160 } 161 ) 162 { 163 exportarr-bazarr = { 164 name = "exportarr"; 165 type = "bazarr"; 166 }; 167 exportarr-lidarr = { 168 name = "exportarr"; 169 type = "lidarr"; 170 }; 171 exportarr-prowlarr = { 172 name = "exportarr"; 173 type = "prowlarr"; 174 }; 175 exportarr-radarr = { 176 name = "exportarr"; 177 type = "radarr"; 178 }; 179 exportarr-readarr = { 180 name = "exportarr"; 181 type = "readarr"; 182 }; 183 exportarr-sonarr = { 184 name = "exportarr"; 185 type = "sonarr"; 186 }; 187 } 188 ); 189 190 mkExporterOpts = ( 191 { name, port }: 192 { 193 enable = mkEnableOption "the prometheus ${name} exporter"; 194 port = mkOption { 195 type = types.port; 196 default = port; 197 description = '' 198 Port to listen on. 199 ''; 200 }; 201 listenAddress = mkOption { 202 type = types.str; 203 default = "0.0.0.0"; 204 description = '' 205 Address to listen on. 206 ''; 207 }; 208 extraFlags = mkOption { 209 type = types.listOf types.str; 210 default = [ ]; 211 description = '' 212 Extra commandline options to pass to the ${name} exporter. 213 ''; 214 }; 215 openFirewall = mkOption { 216 type = types.bool; 217 default = false; 218 description = '' 219 Open port in firewall for incoming connections. 220 ''; 221 }; 222 firewallFilter = mkOption { 223 type = types.nullOr types.str; 224 default = null; 225 example = literalExpression '' 226 "-i eth0 -p tcp -m tcp --dport ${toString port}" 227 ''; 228 description = '' 229 Specify a filter for iptables to use when 230 {option}`services.prometheus.exporters.${name}.openFirewall` 231 is true. It is used as `ip46tables -I nixos-fw firewallFilter -j nixos-fw-accept`. 232 ''; 233 }; 234 firewallRules = mkOption { 235 type = types.nullOr types.lines; 236 default = null; 237 example = literalExpression '' 238 iifname "eth0" tcp dport ${toString port} counter accept 239 ''; 240 description = '' 241 Specify rules for nftables to add to the input chain 242 when {option}`services.prometheus.exporters.${name}.openFirewall` is true. 243 ''; 244 }; 245 user = mkOption { 246 type = types.str; 247 default = "${name}-exporter"; 248 description = '' 249 User name under which the ${name} exporter shall be run. 250 ''; 251 }; 252 group = mkOption { 253 type = types.str; 254 default = "${name}-exporter"; 255 description = '' 256 Group under which the ${name} exporter shall be run. 257 ''; 258 }; 259 } 260 ); 261 262 mkSubModule = 263 { 264 name, 265 port, 266 extraOpts, 267 imports, 268 }: 269 { 270 ${name} = mkOption { 271 type = types.submodule [ 272 { 273 inherit imports; 274 options = ( 275 mkExporterOpts { 276 inherit name port; 277 } 278 // extraOpts 279 ); 280 } 281 ( 282 { config, ... }: 283 mkIf config.openFirewall { 284 firewallFilter = mkDefault "-p tcp -m tcp --dport ${toString config.port}"; 285 firewallRules = mkDefault ''tcp dport ${toString config.port} accept comment "${name}-exporter"''; 286 } 287 ) 288 ]; 289 internal = true; 290 default = { }; 291 }; 292 }; 293 294 mkSubModules = ( 295 foldl' (a: b: a // b) { } ( 296 mapAttrsToList ( 297 name: opts: 298 mkSubModule { 299 inherit name; 300 inherit (opts) port; 301 extraOpts = opts.extraOpts or { }; 302 imports = opts.imports or [ ]; 303 } 304 ) exporterOpts 305 ) 306 ); 307 308 mkExporterConf = 309 { 310 name, 311 conf, 312 serviceOpts, 313 }: 314 let 315 enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true; 316 nftables = config.networking.nftables.enable; 317 in 318 mkIf conf.enable { 319 warnings = conf.warnings or [ ]; 320 assertions = conf.assertions or [ ]; 321 users.users."${name}-exporter" = ( 322 mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) { 323 description = "Prometheus ${name} exporter service user"; 324 isSystemUser = true; 325 inherit (conf) group; 326 } 327 ); 328 users.groups = mkMerge [ 329 (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) { 330 "${name}-exporter" = { }; 331 }) 332 (mkIf (name == "smartctl") { 333 "smartctl-exporter-access" = { }; 334 }) 335 ]; 336 services.udev.extraRules = mkIf (name == "smartctl") '' 337 ACTION=="add", SUBSYSTEM=="nvme", KERNEL=="nvme[0-9]*", RUN+="${pkgs.acl}/bin/setfacl -m g:smartctl-exporter-access:rw /dev/$kernel" 338 ''; 339 networking.firewall.extraCommands = mkIf (conf.openFirewall && !nftables) (concatStrings [ 340 "ip46tables -A nixos-fw ${conf.firewallFilter} " 341 "-m comment --comment ${name}-exporter -j nixos-fw-accept" 342 ]); 343 networking.firewall.extraInputRules = mkIf (conf.openFirewall && nftables) conf.firewallRules; 344 systemd.services."prometheus-${name}-exporter" = mkMerge ([ 345 { 346 wantedBy = [ "multi-user.target" ]; 347 after = [ "network.target" ]; 348 serviceConfig.Restart = mkDefault "always"; 349 serviceConfig.PrivateTmp = mkDefault true; 350 serviceConfig.WorkingDirectory = mkDefault /tmp; 351 serviceConfig.DynamicUser = mkDefault enableDynamicUser; 352 serviceConfig.User = mkDefault conf.user; 353 serviceConfig.Group = conf.group; 354 # Hardening 355 serviceConfig.CapabilityBoundingSet = mkDefault [ "" ]; 356 serviceConfig.DeviceAllow = [ "" ]; 357 serviceConfig.LockPersonality = true; 358 serviceConfig.MemoryDenyWriteExecute = true; 359 serviceConfig.NoNewPrivileges = true; 360 serviceConfig.PrivateDevices = mkDefault true; 361 serviceConfig.ProtectClock = mkDefault true; 362 serviceConfig.ProtectControlGroups = true; 363 serviceConfig.ProtectHome = true; 364 serviceConfig.ProtectHostname = true; 365 serviceConfig.ProtectKernelLogs = true; 366 serviceConfig.ProtectKernelModules = true; 367 serviceConfig.ProtectKernelTunables = true; 368 serviceConfig.ProtectSystem = mkDefault "strict"; 369 serviceConfig.RemoveIPC = true; 370 serviceConfig.RestrictAddressFamilies = [ 371 "AF_INET" 372 "AF_INET6" 373 ]; 374 serviceConfig.RestrictNamespaces = true; 375 serviceConfig.RestrictRealtime = true; 376 serviceConfig.RestrictSUIDSGID = true; 377 serviceConfig.SystemCallArchitectures = "native"; 378 serviceConfig.UMask = "0077"; 379 } 380 serviceOpts 381 ]); 382 }; 383in 384{ 385 386 options.services.prometheus.exporters = mkOption { 387 type = types.submodule { 388 options = (mkSubModules); 389 imports = [ 390 ../../../misc/assertions.nix 391 (lib.mkRenamedOptionModule [ "unifi-poller" ] [ "unpoller" ]) 392 (lib.mkRemovedOptionModule [ "minio" ] '' 393 The Minio exporter has been removed, as it was broken and unmaintained. 394 See the 24.11 release notes for more information. 395 '') 396 (lib.mkRemovedOptionModule [ "tor" ] '' 397 The Tor exporter has been removed, as it was broken and unmaintained. 398 '') 399 ]; 400 }; 401 description = "Prometheus exporter configuration"; 402 default = { }; 403 example = literalExpression '' 404 { 405 node = { 406 enable = true; 407 enabledCollectors = [ "systemd" ]; 408 }; 409 varnish.enable = true; 410 } 411 ''; 412 }; 413 414 config = mkMerge ( 415 [ 416 { 417 assertions = 418 [ 419 { 420 assertion = 421 cfg.ipmi.enable -> (cfg.ipmi.configFile != null) -> (!(lib.hasPrefix "/tmp/" cfg.ipmi.configFile)); 422 message = '' 423 Config file specified in `services.prometheus.exporters.ipmi.configFile' must 424 not reside within /tmp - it won't be visible to the systemd service. 425 ''; 426 } 427 { 428 assertion = 429 cfg.ipmi.enable 430 -> (cfg.ipmi.webConfigFile != null) 431 -> (!(lib.hasPrefix "/tmp/" cfg.ipmi.webConfigFile)); 432 message = '' 433 Config file specified in `services.prometheus.exporters.ipmi.webConfigFile' must 434 not reside within /tmp - it won't be visible to the systemd service. 435 ''; 436 } 437 { 438 assertion = 439 cfg.restic.enable -> ((cfg.restic.repository == null) != (cfg.restic.repositoryFile == null)); 440 message = '' 441 Please specify either 'services.prometheus.exporters.restic.repository' 442 or 'services.prometheus.exporters.restic.repositoryFile'. 443 ''; 444 } 445 { 446 assertion = 447 cfg.snmp.enable -> ((cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null)); 448 message = '' 449 Please ensure you have either `services.prometheus.exporters.snmp.configuration' 450 or `services.prometheus.exporters.snmp.configurationPath' set! 451 ''; 452 } 453 { 454 assertion = 455 cfg.mikrotik.enable -> ((cfg.mikrotik.configFile == null) != (cfg.mikrotik.configuration == null)); 456 message = '' 457 Please specify either `services.prometheus.exporters.mikrotik.configuration' 458 or `services.prometheus.exporters.mikrotik.configFile'. 459 ''; 460 } 461 { 462 assertion = cfg.mail.enable -> ((cfg.mail.configFile == null) != (cfg.mail.configuration == null)); 463 message = '' 464 Please specify either 'services.prometheus.exporters.mail.configuration' 465 or 'services.prometheus.exporters.mail.configFile'. 466 ''; 467 } 468 { 469 assertion = cfg.mysqld.runAsLocalSuperUser -> config.services.mysql.enable; 470 message = '' 471 The exporter is configured to run as 'services.mysql.user', but 472 'services.mysql.enable' is set to false. 473 ''; 474 } 475 { 476 assertion = 477 cfg.nextcloud.enable -> ((cfg.nextcloud.passwordFile == null) != (cfg.nextcloud.tokenFile == null)); 478 message = '' 479 Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or 480 'services.prometheus.exporters.nextcloud.tokenFile' 481 ''; 482 } 483 { 484 assertion = cfg.sql.enable -> ((cfg.sql.configFile == null) != (cfg.sql.configuration == null)); 485 message = '' 486 Please specify either 'services.prometheus.exporters.sql.configuration' or 487 'services.prometheus.exporters.sql.configFile' 488 ''; 489 } 490 { 491 assertion = cfg.scaphandre.enable -> (pkgs.stdenv.targetPlatform.isx86_64 == true); 492 message = '' 493 Scaphandre only support x86_64 architectures. 494 ''; 495 } 496 { 497 assertion = 498 cfg.scaphandre.enable 499 -> ((lib.kernel.whenHelpers pkgs.linux.version).whenOlder "5.11" true).condition == false; 500 message = '' 501 Scaphandre requires a kernel version newer than '5.11', '${pkgs.linux.version}' given. 502 ''; 503 } 504 { 505 assertion = cfg.scaphandre.enable -> (builtins.elem "intel_rapl_common" config.boot.kernelModules); 506 message = '' 507 Scaphandre needs 'intel_rapl_common' kernel module to be enabled. Please add it in 'boot.kernelModules'. 508 ''; 509 } 510 { 511 assertion = 512 cfg.idrac.enable -> ((cfg.idrac.configurationPath == null) != (cfg.idrac.configuration == null)); 513 message = '' 514 Please ensure you have either `services.prometheus.exporters.idrac.configuration' 515 or `services.prometheus.exporters.idrac.configurationPath' set! 516 ''; 517 } 518 { 519 assertion = 520 cfg.deluge.enable 521 -> ((cfg.deluge.delugePassword == null) != (cfg.deluge.delugePasswordFile == null)); 522 message = '' 523 Please ensure you have either `services.prometheus.exporters.deluge.delugePassword' 524 or `services.prometheus.exporters.deluge.delugePasswordFile' set! 525 ''; 526 } 527 { 528 assertion = 529 cfg.pgbouncer.enable 530 -> (xor (cfg.pgbouncer.connectionEnvFile == null) (cfg.pgbouncer.connectionString == null)); 531 message = '' 532 Options `services.prometheus.exporters.pgbouncer.connectionEnvFile` and 533 `services.prometheus.exporters.pgbouncer.connectionString` are mutually exclusive! 534 ''; 535 } 536 ] 537 ++ (flip map (attrNames exporterOpts) (exporter: { 538 assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall; 539 message = '' 540 The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless 541 `openFirewall' is set to `true'! 542 ''; 543 })) 544 ++ config.services.prometheus.exporters.assertions; 545 warnings = [ 546 (mkIf 547 ( 548 config.services.prometheus.exporters.idrac.enable 549 && config.services.prometheus.exporters.idrac.configurationPath != null 550 ) 551 '' 552 Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override 553 `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`. 554 Consider using `services.prometheus.exporters.idrac.configuration` instead. 555 '' 556 ) 557 ] ++ config.services.prometheus.exporters.warnings; 558 } 559 ] 560 ++ [ 561 (mkIf config.services.prometheus.exporters.rtl_433.enable { 562 hardware.rtl-sdr.enable = mkDefault true; 563 }) 564 ] 565 ++ [ 566 (mkIf config.services.postfix.enable { 567 services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup; 568 }) 569 ] 570 ++ [ 571 (mkIf config.services.prometheus.exporters.deluge.enable { 572 system.activationScripts = { 573 deluge-exported.text = '' 574 mkdir -p /etc/deluge-exporter 575 echo "DELUGE_PASSWORD=$(cat ${config.services.prometheus.exporters.deluge.delugePasswordFile})" > /etc/deluge-exporter/password 576 ''; 577 }; 578 }) 579 ] 580 ++ (mapAttrsToList ( 581 name: conf: 582 mkExporterConf { 583 inherit name; 584 inherit (conf) serviceOpts; 585 conf = cfg.${name}; 586 } 587 ) exporterOpts) 588 ); 589 590 meta = { 591 doc = ./exporters.md; 592 maintainers = [ maintainers.willibutz ]; 593 }; 594}