1 { config, pkgs, lib, options, utils, ... }:
4 inherit (lib) concatStrings foldl foldl' genAttrs literalExpression maintainers
5 mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
6 optional types mkOptionDefault flip attrNames xor;
8 cfg = config.services.prometheus.exporters;
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
15 # - extraOpts (types.attrs): extra configuration options to
16 # configure the exporter with, which
17 # are appended to the default options
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`
23 exporterOpts = (genAttrs [
100 import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options utils; }
103 import (./. + "/exporters/${params.name}.nix") { inherit config lib pkgs options utils; type = params.type ; })
113 exportarr-prowlarr = {
121 exportarr-readarr = {
132 mkExporterOpts = ({ name, port }: {
133 enable = mkEnableOption "the prometheus ${name} exporter";
141 listenAddress = mkOption {
145 Address to listen on.
148 extraFlags = mkOption {
149 type = types.listOf types.str;
152 Extra commandline options to pass to the ${name} exporter.
155 openFirewall = mkOption {
159 Open port in firewall for incoming connections.
162 firewallFilter = mkOption {
163 type = types.nullOr types.str;
165 example = literalExpression ''
166 "-i eth0 -p tcp -m tcp --dport ${toString port}"
169 Specify a filter for iptables to use when
170 {option}`services.prometheus.exporters.${name}.openFirewall`
171 is true. It is used as `ip46tables -I nixos-fw firewallFilter -j nixos-fw-accept`.
174 firewallRules = mkOption {
175 type = types.nullOr types.lines;
177 example = literalExpression ''
178 iifname "eth0" tcp dport ${toString port} counter accept
181 Specify rules for nftables to add to the input chain
182 when {option}`services.prometheus.exporters.${name}.openFirewall` is true.
187 default = "${name}-exporter";
189 User name under which the ${name} exporter shall be run.
194 default = "${name}-exporter";
196 Group under which the ${name} exporter shall be run.
201 mkSubModule = { name, port, extraOpts, imports }: {
203 type = types.submodule [{
205 options = (mkExporterOpts {
208 } ({ config, ... }: mkIf config.openFirewall {
209 firewallFilter = mkDefault "-p tcp -m tcp --dport ${toString config.port}";
210 firewallRules = mkDefault ''tcp dport ${toString config.port} accept comment "${name}-exporter"'';
217 mkSubModules = (foldl' (a: b: a//b) {}
218 (mapAttrsToList (name: opts: mkSubModule {
221 extraOpts = opts.extraOpts or {};
222 imports = opts.imports or [];
226 mkExporterConf = { name, conf, serviceOpts }:
228 enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true;
229 nftables = config.networking.nftables.enable;
232 warnings = conf.warnings or [];
233 assertions = conf.assertions or [];
234 users.users."${name}-exporter" = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) {
235 description = "Prometheus ${name} exporter service user";
237 inherit (conf) group;
239 users.groups = mkMerge [
240 (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) {
241 "${name}-exporter" = {};
243 (mkIf (name == "smartctl") {
244 "smartctl-exporter-access" = {};
247 services.udev.extraRules = mkIf (name == "smartctl") ''
248 ACTION=="add", SUBSYSTEM=="nvme", KERNEL=="nvme[0-9]*", RUN+="${pkgs.acl}/bin/setfacl -m g:smartctl-exporter-access:rw /dev/$kernel"
250 networking.firewall.extraCommands = mkIf (conf.openFirewall && !nftables) (concatStrings [
251 "ip46tables -A nixos-fw ${conf.firewallFilter} "
252 "-m comment --comment ${name}-exporter -j nixos-fw-accept"
254 networking.firewall.extraInputRules = mkIf (conf.openFirewall && nftables) conf.firewallRules;
255 systemd.services."prometheus-${name}-exporter" = mkMerge ([{
256 wantedBy = [ "multi-user.target" ];
257 after = [ "network.target" ];
258 serviceConfig.Restart = mkDefault "always";
259 serviceConfig.PrivateTmp = mkDefault true;
260 serviceConfig.WorkingDirectory = mkDefault /tmp;
261 serviceConfig.DynamicUser = mkDefault enableDynamicUser;
262 serviceConfig.User = mkDefault conf.user;
263 serviceConfig.Group = conf.group;
265 serviceConfig.CapabilityBoundingSet = mkDefault [ "" ];
266 serviceConfig.DeviceAllow = [ "" ];
267 serviceConfig.LockPersonality = true;
268 serviceConfig.MemoryDenyWriteExecute = true;
269 serviceConfig.NoNewPrivileges = true;
270 serviceConfig.PrivateDevices = mkDefault true;
271 serviceConfig.ProtectClock = mkDefault true;
272 serviceConfig.ProtectControlGroups = true;
273 serviceConfig.ProtectHome = true;
274 serviceConfig.ProtectHostname = true;
275 serviceConfig.ProtectKernelLogs = true;
276 serviceConfig.ProtectKernelModules = true;
277 serviceConfig.ProtectKernelTunables = true;
278 serviceConfig.ProtectSystem = mkDefault "strict";
279 serviceConfig.RemoveIPC = true;
280 serviceConfig.RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
281 serviceConfig.RestrictNamespaces = true;
282 serviceConfig.RestrictRealtime = true;
283 serviceConfig.RestrictSUIDSGID = true;
284 serviceConfig.SystemCallArchitectures = "native";
285 serviceConfig.UMask = "0077";
291 options.services.prometheus.exporters = mkOption {
292 type = types.submodule {
293 options = (mkSubModules);
295 ../../../misc/assertions.nix
296 (lib.mkRenamedOptionModule [ "unifi-poller" ] [ "unpoller" ])
297 (lib.mkRemovedOptionModule [ "minio" ] ''
298 The Minio exporter has been removed, as it was broken and unmaintained.
299 See the 24.11 release notes for more information.
303 description = "Prometheus exporter configuration";
305 example = literalExpression ''
309 enabledCollectors = [ "systemd" ];
311 varnish.enable = true;
318 assertion = cfg.ipmi.enable -> (cfg.ipmi.configFile != null) -> (
319 !(lib.hasPrefix "/tmp/" cfg.ipmi.configFile)
322 Config file specified in `services.prometheus.exporters.ipmi.configFile' must
323 not reside within /tmp - it won't be visible to the systemd service.
326 assertion = cfg.ipmi.enable -> (cfg.ipmi.webConfigFile != null) -> (
327 !(lib.hasPrefix "/tmp/" cfg.ipmi.webConfigFile)
330 Config file specified in `services.prometheus.exporters.ipmi.webConfigFile' must
331 not reside within /tmp - it won't be visible to the systemd service.
334 assertion = cfg.snmp.enable -> (
335 (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null)
338 Please ensure you have either `services.prometheus.exporters.snmp.configuration'
339 or `services.prometheus.exporters.snmp.configurationPath' set!
342 assertion = cfg.mikrotik.enable -> (
343 (cfg.mikrotik.configFile == null) != (cfg.mikrotik.configuration == null)
346 Please specify either `services.prometheus.exporters.mikrotik.configuration'
347 or `services.prometheus.exporters.mikrotik.configFile'.
350 assertion = cfg.mail.enable -> (
351 (cfg.mail.configFile == null) != (cfg.mail.configuration == null)
354 Please specify either 'services.prometheus.exporters.mail.configuration'
355 or 'services.prometheus.exporters.mail.configFile'.
358 assertion = cfg.mysqld.runAsLocalSuperUser -> config.services.mysql.enable;
360 The exporter is configured to run as 'services.mysql.user', but
361 'services.mysql.enable' is set to false.
364 assertion = cfg.nextcloud.enable -> (
365 (cfg.nextcloud.passwordFile == null) != (cfg.nextcloud.tokenFile == null)
368 Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or
369 'services.prometheus.exporters.nextcloud.tokenFile'
372 assertion = cfg.sql.enable -> (
373 (cfg.sql.configFile == null) != (cfg.sql.configuration == null)
376 Please specify either 'services.prometheus.exporters.sql.configuration' or
377 'services.prometheus.exporters.sql.configFile'
380 assertion = cfg.scaphandre.enable -> (pkgs.stdenv.targetPlatform.isx86_64 == true);
382 Scaphandre only support x86_64 architectures.
385 assertion = cfg.scaphandre.enable -> ((lib.kernel.whenHelpers pkgs.linux.version).whenOlder "5.11" true).condition == false;
387 Scaphandre requires a kernel version newer than '5.11', '${pkgs.linux.version}' given.
390 assertion = cfg.scaphandre.enable -> (builtins.elem "intel_rapl_common" config.boot.kernelModules);
392 Scaphandre needs 'intel_rapl_common' kernel module to be enabled. Please add it in 'boot.kernelModules'.
395 assertion = cfg.idrac.enable -> (
396 (cfg.idrac.configurationPath == null) != (cfg.idrac.configuration == null)
399 Please ensure you have either `services.prometheus.exporters.idrac.configuration'
400 or `services.prometheus.exporters.idrac.configurationPath' set!
403 assertion = cfg.deluge.enable -> (
404 (cfg.deluge.delugePassword == null) != (cfg.deluge.delugePasswordFile == null)
407 Please ensure you have either `services.prometheus.exporters.deluge.delugePassword'
408 or `services.prometheus.exporters.deluge.delugePasswordFile' set!
411 assertion = cfg.pgbouncer.enable -> (
412 xor (cfg.pgbouncer.connectionEnvFile == null) (cfg.pgbouncer.connectionString == null)
415 Options `services.prometheus.exporters.pgbouncer.connectionEnvFile` and
416 `services.prometheus.exporters.pgbouncer.connectionString` are mutually exclusive!
418 }] ++ (flip map (attrNames exporterOpts) (exporter: {
419 assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
421 The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless
422 `openFirewall' is set to `true'!
424 })) ++ config.services.prometheus.exporters.assertions;
426 (mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) ''
427 Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override
428 `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`.
429 Consider using `services.prometheus.exporters.idrac.configuration` instead.
432 ] ++ config.services.prometheus.exporters.warnings;
433 }] ++ [(mkIf config.services.prometheus.exporters.rtl_433.enable {
434 hardware.rtl-sdr.enable = mkDefault true;
435 })] ++ [(mkIf config.services.postfix.enable {
436 services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup;
437 })] ++ [(mkIf config.services.prometheus.exporters.deluge.enable {
438 system.activationScripts = {
439 deluge-exported.text = ''
440 mkdir -p /etc/deluge-exporter
441 echo "DELUGE_PASSWORD=$(cat ${config.services.prometheus.exporters.deluge.delugePasswordFile})" > /etc/deluge-exporter/password
444 })] ++ (mapAttrsToList (name: conf:
447 inherit (conf) serviceOpts;
453 doc = ./exporters.md;
454 maintainers = [ maintainers.willibutz ];