at 23.11-beta 15 kB view raw
1{ config, pkgs, lib, options, ... }: 2 3let 4 inherit (lib) concatStrings foldl foldl' genAttrs literalExpression maintainers 5 mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption 6 optional types mkOptionDefault flip attrNames; 7 8 cfg = config.services.prometheus.exporters; 9 10 # each attribute in `exporterOpts` is expected to have specified: 11 # - port (types.int): port on which the exporter listens 12 # - serviceOpts (types.attrs): config that is merged with the 13 # default definition of the exporter's 14 # systemd service 15 # - extraOpts (types.attrs): extra configuration options to 16 # configure the exporter with, which 17 # are appended to the default options 18 # 19 # Note that `extraOpts` is optional, but a script for the exporter's 20 # systemd service must be provided by specifying either 21 # `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart` 22 23 exporterOpts = (genAttrs [ 24 "apcupsd" 25 "artifactory" 26 "bind" 27 "bird" 28 "bitcoin" 29 "blackbox" 30 "buildkite-agent" 31 "collectd" 32 "dmarc" 33 "dnsmasq" 34 "domain" 35 "dovecot" 36 "fastly" 37 "flow" 38 "fritzbox" 39 "graphite" 40 "idrac" 41 "imap-mailstat" 42 "influxdb" 43 "ipmi" 44 "jitsi" 45 "json" 46 "junos-czerwonk" 47 "kea" 48 "keylight" 49 "knot" 50 "lnd" 51 "mail" 52 "mikrotik" 53 "minio" 54 "modemmanager" 55 "mysqld" 56 "nextcloud" 57 "nginx" 58 "nginxlog" 59 "node" 60 "nut" 61 "openldap" 62 "openvpn" 63 "pgbouncer" 64 "php-fpm" 65 "pihole" 66 "postfix" 67 "postgres" 68 "process" 69 "pve" 70 "py-air-control" 71 "redis" 72 "rspamd" 73 "rtl_433" 74 "sabnzbd" 75 "scaphandre" 76 "script" 77 "shelly" 78 "smartctl" 79 "smokeping" 80 "snmp" 81 "sql" 82 "statsd" 83 "surfboard" 84 "systemd" 85 "tor" 86 "unbound" 87 "unifi" 88 "unpoller" 89 "v2ray" 90 "varnish" 91 "wireguard" 92 "zfs" 93 ] 94 (name: 95 import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; } 96 )) // (mapAttrs 97 (name: params: 98 import (./. + "/exporters/${params.name}.nix") { inherit config lib pkgs options; type = params.type ; }) 99 { 100 exportarr-bazarr = { 101 name = "exportarr"; 102 type = "bazarr"; 103 }; 104 exportarr-lidarr = { 105 name = "exportarr"; 106 type = "lidarr"; 107 }; 108 exportarr-prowlarr = { 109 name = "exportarr"; 110 type = "prowlarr"; 111 }; 112 exportarr-radarr = { 113 name = "exportarr"; 114 type = "radarr"; 115 }; 116 exportarr-readarr = { 117 name = "exportarr"; 118 type = "readarr"; 119 }; 120 exportarr-sonarr = { 121 name = "exportarr"; 122 type = "sonarr"; 123 }; 124 } 125 ); 126 127 mkExporterOpts = ({ name, port }: { 128 enable = mkEnableOption (lib.mdDoc "the prometheus ${name} exporter"); 129 port = mkOption { 130 type = types.port; 131 default = port; 132 description = lib.mdDoc '' 133 Port to listen on. 134 ''; 135 }; 136 listenAddress = mkOption { 137 type = types.str; 138 default = "0.0.0.0"; 139 description = lib.mdDoc '' 140 Address to listen on. 141 ''; 142 }; 143 extraFlags = mkOption { 144 type = types.listOf types.str; 145 default = []; 146 description = lib.mdDoc '' 147 Extra commandline options to pass to the ${name} exporter. 148 ''; 149 }; 150 openFirewall = mkOption { 151 type = types.bool; 152 default = false; 153 description = lib.mdDoc '' 154 Open port in firewall for incoming connections. 155 ''; 156 }; 157 firewallFilter = mkOption { 158 type = types.nullOr types.str; 159 default = null; 160 example = literalExpression '' 161 "-i eth0 -p tcp -m tcp --dport ${toString port}" 162 ''; 163 description = lib.mdDoc '' 164 Specify a filter for iptables to use when 165 {option}`services.prometheus.exporters.${name}.openFirewall` 166 is true. It is used as `ip46tables -I nixos-fw firewallFilter -j nixos-fw-accept`. 167 ''; 168 }; 169 user = mkOption { 170 type = types.str; 171 default = "${name}-exporter"; 172 description = lib.mdDoc '' 173 User name under which the ${name} exporter shall be run. 174 ''; 175 }; 176 group = mkOption { 177 type = types.str; 178 default = "${name}-exporter"; 179 description = lib.mdDoc '' 180 Group under which the ${name} exporter shall be run. 181 ''; 182 }; 183 }); 184 185 mkSubModule = { name, port, extraOpts, imports }: { 186 ${name} = mkOption { 187 type = types.submodule [{ 188 inherit imports; 189 options = (mkExporterOpts { 190 inherit name port; 191 } // extraOpts); 192 } ({ config, ... }: mkIf config.openFirewall { 193 firewallFilter = mkDefault "-p tcp -m tcp --dport ${toString config.port}"; 194 })]; 195 internal = true; 196 default = {}; 197 }; 198 }; 199 200 mkSubModules = (foldl' (a: b: a//b) {} 201 (mapAttrsToList (name: opts: mkSubModule { 202 inherit name; 203 inherit (opts) port; 204 extraOpts = opts.extraOpts or {}; 205 imports = opts.imports or []; 206 }) exporterOpts) 207 ); 208 209 mkExporterConf = { name, conf, serviceOpts }: 210 let 211 enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true; 212 in 213 mkIf conf.enable { 214 warnings = conf.warnings or []; 215 users.users."${name}-exporter" = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) { 216 description = "Prometheus ${name} exporter service user"; 217 isSystemUser = true; 218 inherit (conf) group; 219 }); 220 users.groups = (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) { 221 "${name}-exporter" = {}; 222 }); 223 networking.firewall.extraCommands = mkIf conf.openFirewall (concatStrings [ 224 "ip46tables -A nixos-fw ${conf.firewallFilter} " 225 "-m comment --comment ${name}-exporter -j nixos-fw-accept" 226 ]); 227 systemd.services."prometheus-${name}-exporter" = mkMerge ([{ 228 wantedBy = [ "multi-user.target" ]; 229 after = [ "network.target" ]; 230 serviceConfig.Restart = mkDefault "always"; 231 serviceConfig.PrivateTmp = mkDefault true; 232 serviceConfig.WorkingDirectory = mkDefault /tmp; 233 serviceConfig.DynamicUser = mkDefault enableDynamicUser; 234 serviceConfig.User = mkDefault conf.user; 235 serviceConfig.Group = conf.group; 236 # Hardening 237 serviceConfig.CapabilityBoundingSet = mkDefault [ "" ]; 238 serviceConfig.DeviceAllow = [ "" ]; 239 serviceConfig.LockPersonality = true; 240 serviceConfig.MemoryDenyWriteExecute = true; 241 serviceConfig.NoNewPrivileges = true; 242 serviceConfig.PrivateDevices = mkDefault true; 243 serviceConfig.ProtectClock = mkDefault true; 244 serviceConfig.ProtectControlGroups = true; 245 serviceConfig.ProtectHome = true; 246 serviceConfig.ProtectHostname = true; 247 serviceConfig.ProtectKernelLogs = true; 248 serviceConfig.ProtectKernelModules = true; 249 serviceConfig.ProtectKernelTunables = true; 250 serviceConfig.ProtectSystem = mkDefault "strict"; 251 serviceConfig.RemoveIPC = true; 252 serviceConfig.RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; 253 serviceConfig.RestrictNamespaces = true; 254 serviceConfig.RestrictRealtime = true; 255 serviceConfig.RestrictSUIDSGID = true; 256 serviceConfig.SystemCallArchitectures = "native"; 257 serviceConfig.UMask = "0077"; 258 } serviceOpts ]); 259 }; 260in 261{ 262 263 imports = (lib.forEach [ "blackboxExporter" "collectdExporter" "fritzboxExporter" 264 "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter" 265 "snmpExporter" "unifiExporter" "varnishExporter" ] 266 (opt: lib.mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] '' 267 The prometheus exporters are now configured using `services.prometheus.exporters'. 268 See the 18.03 release notes for more information. 269 '' )); 270 271 options.services.prometheus.exporters = mkOption { 272 type = types.submodule { 273 options = (mkSubModules); 274 imports = [ 275 ../../../misc/assertions.nix 276 (lib.mkRenamedOptionModule [ "unifi-poller" ] [ "unpoller" ]) 277 ]; 278 }; 279 description = lib.mdDoc "Prometheus exporter configuration"; 280 default = {}; 281 example = literalExpression '' 282 { 283 node = { 284 enable = true; 285 enabledCollectors = [ "systemd" ]; 286 }; 287 varnish.enable = true; 288 } 289 ''; 290 }; 291 292 config = mkMerge ([{ 293 assertions = [ { 294 assertion = cfg.ipmi.enable -> (cfg.ipmi.configFile != null) -> ( 295 !(lib.hasPrefix "/tmp/" cfg.ipmi.configFile) 296 ); 297 message = '' 298 Config file specified in `services.prometheus.exporters.ipmi.configFile' must 299 not reside within /tmp - it won't be visible to the systemd service. 300 ''; 301 } { 302 assertion = cfg.ipmi.enable -> (cfg.ipmi.webConfigFile != null) -> ( 303 !(lib.hasPrefix "/tmp/" cfg.ipmi.webConfigFile) 304 ); 305 message = '' 306 Config file specified in `services.prometheus.exporters.ipmi.webConfigFile' must 307 not reside within /tmp - it won't be visible to the systemd service. 308 ''; 309 } { 310 assertion = cfg.snmp.enable -> ( 311 (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null) 312 ); 313 message = '' 314 Please ensure you have either `services.prometheus.exporters.snmp.configuration' 315 or `services.prometheus.exporters.snmp.configurationPath' set! 316 ''; 317 } { 318 assertion = cfg.mikrotik.enable -> ( 319 (cfg.mikrotik.configFile == null) != (cfg.mikrotik.configuration == null) 320 ); 321 message = '' 322 Please specify either `services.prometheus.exporters.mikrotik.configuration' 323 or `services.prometheus.exporters.mikrotik.configFile'. 324 ''; 325 } { 326 assertion = cfg.mail.enable -> ( 327 (cfg.mail.configFile == null) != (cfg.mail.configuration == null) 328 ); 329 message = '' 330 Please specify either 'services.prometheus.exporters.mail.configuration' 331 or 'services.prometheus.exporters.mail.configFile'. 332 ''; 333 } { 334 assertion = cfg.mysqld.runAsLocalSuperUser -> config.services.mysql.enable; 335 message = '' 336 The exporter is configured to run as 'services.mysql.user', but 337 'services.mysql.enable' is set to false. 338 ''; 339 } { 340 assertion = cfg.nextcloud.enable -> ( 341 (cfg.nextcloud.passwordFile == null) != (cfg.nextcloud.tokenFile == null) 342 ); 343 message = '' 344 Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or 345 'services.prometheus.exporters.nextcloud.tokenFile' 346 ''; 347 } { 348 assertion = cfg.pgbouncer.enable -> ( 349 (cfg.pgbouncer.connectionStringFile != null || cfg.pgbouncer.connectionString != "") 350 ); 351 message = '' 352 PgBouncer exporter needs either connectionStringFile or connectionString configured" 353 ''; 354 } { 355 assertion = cfg.pgbouncer.enable -> ( 356 config.services.pgbouncer.ignoreStartupParameters != null && builtins.match ".*extra_float_digits.*" config.services.pgbouncer.ignoreStartupParameters != null 357 ); 358 message = '' 359 Prometheus PgBouncer exporter requires including `extra_float_digits` in services.pgbouncer.ignoreStartupParameters 360 361 Example: 362 services.pgbouncer.ignoreStartupParameters = extra_float_digits; 363 364 See https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration 365 ''; 366 } { 367 assertion = cfg.sql.enable -> ( 368 (cfg.sql.configFile == null) != (cfg.sql.configuration == null) 369 ); 370 message = '' 371 Please specify either 'services.prometheus.exporters.sql.configuration' or 372 'services.prometheus.exporters.sql.configFile' 373 ''; 374 } { 375 assertion = cfg.scaphandre.enable -> (pkgs.stdenv.targetPlatform.isx86_64 == true); 376 message = '' 377 Scaphandre only support x86_64 architectures. 378 ''; 379 } { 380 assertion = cfg.scaphandre.enable -> ((lib.kernel.whenHelpers pkgs.linux.version).whenOlder "5.11" true).condition == false; 381 message = '' 382 Scaphandre requires a kernel version newer than '5.11', '${pkgs.linux.version}' given. 383 ''; 384 } { 385 assertion = cfg.scaphandre.enable -> (builtins.elem "intel_rapl_common" config.boot.kernelModules); 386 message = '' 387 Scaphandre needs 'intel_rapl_common' kernel module to be enabled. Please add it in 'boot.kernelModules'. 388 ''; 389 } { 390 assertion = cfg.idrac.enable -> ( 391 (cfg.idrac.configurationPath == null) != (cfg.idrac.configuration == null) 392 ); 393 message = '' 394 Please ensure you have either `services.prometheus.exporters.idrac.configuration' 395 or `services.prometheus.exporters.idrac.configurationPath' set! 396 ''; 397 } ] ++ (flip map (attrNames exporterOpts) (exporter: { 398 assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall; 399 message = '' 400 The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless 401 `openFirewall' is set to `true'! 402 ''; 403 })) ++ config.services.prometheus.exporters.assertions; 404 warnings = [ 405 (mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) '' 406 Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override 407 `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`. 408 Consider using `services.prometheus.exporters.idrac.configuration` instead. 409 '' 410 ) 411 (mkIf 412 (cfg.pgbouncer.enable && cfg.pgbouncer.connectionString != "") '' 413 config.services.prometheus.exporters.pgbouncer.connectionString is insecure. Use connectionStringFile instead. 414 '' 415 ) 416 (mkIf 417 (cfg.pgbouncer.enable && config.services.pgbouncer.authType != "any") '' 418 Admin user (with password or passwordless) MUST exist in the services.pgbouncer.authFile if authType other than any is used. 419 '' 420 ) 421 ] ++ config.services.prometheus.exporters.warnings; 422 }] ++ [(mkIf config.services.minio.enable { 423 services.prometheus.exporters.minio.minioAddress = mkDefault "http://localhost:9000"; 424 services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey; 425 services.prometheus.exporters.minio.minioAccessSecret = mkDefault config.services.minio.secretKey; 426 })] ++ [(mkIf config.services.prometheus.exporters.rtl_433.enable { 427 hardware.rtl-sdr.enable = mkDefault true; 428 })] ++ [(mkIf config.services.postfix.enable { 429 services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup; 430 })] ++ (mapAttrsToList (name: conf: 431 mkExporterConf { 432 inherit name; 433 inherit (conf) serviceOpts; 434 conf = cfg.${name}; 435 }) exporterOpts) 436 ); 437 438 meta = { 439 doc = ./exporters.md; 440 maintainers = [ maintainers.willibutz ]; 441 }; 442}