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