1{ config, pkgs, lib, options, ... }:
2
3let
4 inherit (lib) concatStrings foldl foldl' genAttrs literalExample maintainers
5 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 "collectd"
31 "dnsmasq"
32 "domain"
33 "dovecot"
34 "fritzbox"
35 "json"
36 "jitsi"
37 "kea"
38 "keylight"
39 "knot"
40 "lnd"
41 "mail"
42 "mikrotik"
43 "minio"
44 "modemmanager"
45 "nextcloud"
46 "nginx"
47 "nginxlog"
48 "node"
49 "openldap"
50 "openvpn"
51 "postfix"
52 "postgres"
53 "py-air-control"
54 "redis"
55 "rspamd"
56 "rtl_433"
57 "snmp"
58 "smokeping"
59 "sql"
60 "surfboard"
61 "systemd"
62 "tor"
63 "unbound"
64 "unifi"
65 "unifi-poller"
66 "varnish"
67 "wireguard"
68 "flow"
69 ] (name:
70 import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
71 );
72
73 mkExporterOpts = ({ name, port }: {
74 enable = mkEnableOption "the prometheus ${name} exporter";
75 port = mkOption {
76 type = types.port;
77 default = port;
78 description = ''
79 Port to listen on.
80 '';
81 };
82 listenAddress = mkOption {
83 type = types.str;
84 default = "0.0.0.0";
85 description = ''
86 Address to listen on.
87 '';
88 };
89 extraFlags = mkOption {
90 type = types.listOf types.str;
91 default = [];
92 description = ''
93 Extra commandline options to pass to the ${name} exporter.
94 '';
95 };
96 openFirewall = mkOption {
97 type = types.bool;
98 default = false;
99 description = ''
100 Open port in firewall for incoming connections.
101 '';
102 };
103 firewallFilter = mkOption {
104 type = types.nullOr types.str;
105 default = null;
106 example = literalExample ''
107 "-i eth0 -p tcp -m tcp --dport ${toString port}"
108 '';
109 description = ''
110 Specify a filter for iptables to use when
111 <option>services.prometheus.exporters.${name}.openFirewall</option>
112 is true. It is used as `ip46tables -I nixos-fw <option>firewallFilter</option> -j nixos-fw-accept`.
113 '';
114 };
115 user = mkOption {
116 type = types.str;
117 default = "${name}-exporter";
118 description = ''
119 User name under which the ${name} exporter shall be run.
120 '';
121 };
122 group = mkOption {
123 type = types.str;
124 default = "${name}-exporter";
125 description = ''
126 Group under which the ${name} exporter shall be run.
127 '';
128 };
129 });
130
131 mkSubModule = { name, port, extraOpts, imports }: {
132 ${name} = mkOption {
133 type = types.submodule [{
134 inherit imports;
135 options = (mkExporterOpts {
136 inherit name port;
137 } // extraOpts);
138 } ({ config, ... }: mkIf config.openFirewall {
139 firewallFilter = mkDefault "-p tcp -m tcp --dport ${toString config.port}";
140 })];
141 internal = true;
142 default = {};
143 };
144 };
145
146 mkSubModules = (foldl' (a: b: a//b) {}
147 (mapAttrsToList (name: opts: mkSubModule {
148 inherit name;
149 inherit (opts) port;
150 extraOpts = opts.extraOpts or {};
151 imports = opts.imports or [];
152 }) exporterOpts)
153 );
154
155 mkExporterConf = { name, conf, serviceOpts }:
156 let
157 enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true;
158 in
159 mkIf conf.enable {
160 warnings = conf.warnings or [];
161 users.users."${name}-exporter" = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) {
162 description = "Prometheus ${name} exporter service user";
163 isSystemUser = true;
164 inherit (conf) group;
165 });
166 users.groups = (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) {
167 "${name}-exporter" = {};
168 });
169 networking.firewall.extraCommands = mkIf conf.openFirewall (concatStrings [
170 "ip46tables -A nixos-fw ${conf.firewallFilter} "
171 "-m comment --comment ${name}-exporter -j nixos-fw-accept"
172 ]);
173 systemd.services."prometheus-${name}-exporter" = mkMerge ([{
174 wantedBy = [ "multi-user.target" ];
175 after = [ "network.target" ];
176 serviceConfig.Restart = mkDefault "always";
177 serviceConfig.PrivateTmp = mkDefault true;
178 serviceConfig.WorkingDirectory = mkDefault /tmp;
179 serviceConfig.DynamicUser = mkDefault enableDynamicUser;
180 serviceConfig.User = conf.user;
181 serviceConfig.Group = conf.group;
182 } serviceOpts ]);
183 };
184in
185{
186
187 imports = (lib.forEach [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
188 "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
189 "snmpExporter" "unifiExporter" "varnishExporter" ]
190 (opt: lib.mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''
191 The prometheus exporters are now configured using `services.prometheus.exporters'.
192 See the 18.03 release notes for more information.
193 '' ));
194
195 options.services.prometheus.exporters = mkOption {
196 type = types.submodule {
197 options = (mkSubModules);
198 };
199 description = "Prometheus exporter configuration";
200 default = {};
201 example = literalExample ''
202 {
203 node = {
204 enable = true;
205 enabledCollectors = [ "systemd" ];
206 };
207 varnish.enable = true;
208 }
209 '';
210 };
211
212 config = mkMerge ([{
213 assertions = [ {
214 assertion = cfg.snmp.enable -> (
215 (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null)
216 );
217 message = ''
218 Please ensure you have either `services.prometheus.exporters.snmp.configuration'
219 or `services.prometheus.exporters.snmp.configurationPath' set!
220 '';
221 } {
222 assertion = cfg.mikrotik.enable -> (
223 (cfg.mikrotik.configFile == null) != (cfg.mikrotik.configuration == null)
224 );
225 message = ''
226 Please specify either `services.prometheus.exporters.mikrotik.configuration'
227 or `services.prometheus.exporters.mikrotik.configFile'.
228 '';
229 } {
230 assertion = cfg.mail.enable -> (
231 (cfg.mail.configFile == null) != (cfg.mail.configuration == null)
232 );
233 message = ''
234 Please specify either 'services.prometheus.exporters.mail.configuration'
235 or 'services.prometheus.exporters.mail.configFile'.
236 '';
237 } {
238 assertion = cfg.sql.enable -> (
239 (cfg.sql.configFile == null) != (cfg.sql.configuration == null)
240 );
241 message = ''
242 Please specify either 'services.prometheus.exporters.sql.configuration' or
243 'services.prometheus.exporters.sql.configFile'
244 '';
245 } ] ++ (flip map (attrNames cfg) (exporter: {
246 assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
247 message = ''
248 The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless
249 `openFirewall' is set to `true'!
250 '';
251 }));
252 }] ++ [(mkIf config.services.minio.enable {
253 services.prometheus.exporters.minio.minioAddress = mkDefault "http://localhost:9000";
254 services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
255 services.prometheus.exporters.minio.minioAccessSecret = mkDefault config.services.minio.secretKey;
256 })] ++ [(mkIf config.services.prometheus.exporters.rtl_433.enable {
257 hardware.rtl-sdr.enable = mkDefault true;
258 })] ++ [(mkIf config.services.postfix.enable {
259 services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup;
260 })] ++ (mapAttrsToList (name: conf:
261 mkExporterConf {
262 inherit name;
263 inherit (conf) serviceOpts;
264 conf = cfg.${name};
265 }) exporterOpts)
266 );
267
268 meta = {
269 doc = ./exporters.xml;
270 maintainers = [ maintainers.willibutz ];
271 };
272}