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