1{ config, lib, pkgs, options }:
2
3with lib;
4
5let
6 cfg = config.services.prometheus.exporters.mail;
7
8 configFile = if cfg.configuration != null then configurationFile else (escapeShellArg cfg.configFile);
9
10 configurationFile = pkgs.writeText "prometheus-mail-exporter.conf" (builtins.toJSON (
11 # removes the _module attribute, null values and converts attrNames to lowercase
12 mapAttrs' (name: value:
13 if name == "servers"
14 then nameValuePair (toLower name)
15 ((map (srv: (mapAttrs' (n: v: nameValuePair (toLower n) v)
16 (filterAttrs (n: v: !(n == "_module" || v == null)) srv)
17 ))) value)
18 else nameValuePair (toLower name) value
19 ) (filterAttrs (n: _: !(n == "_module")) cfg.configuration)
20 ));
21
22 serverOptions.options = {
23 name = mkOption {
24 type = types.str;
25 description = lib.mdDoc ''
26 Value for label 'configname' which will be added to all metrics.
27 '';
28 };
29 server = mkOption {
30 type = types.str;
31 description = lib.mdDoc ''
32 Hostname of the server that should be probed.
33 '';
34 };
35 port = mkOption {
36 type = types.port;
37 example = 587;
38 description = lib.mdDoc ''
39 Port to use for SMTP.
40 '';
41 };
42 from = mkOption {
43 type = types.str;
44 example = "exporteruser@domain.tld";
45 description = lib.mdDoc ''
46 Content of 'From' Header for probing mails.
47 '';
48 };
49 to = mkOption {
50 type = types.str;
51 example = "exporteruser@domain.tld";
52 description = lib.mdDoc ''
53 Content of 'To' Header for probing mails.
54 '';
55 };
56 detectionDir = mkOption {
57 type = types.path;
58 example = "/var/spool/mail/exporteruser/new";
59 description = lib.mdDoc ''
60 Directory in which new mails for the exporter user are placed.
61 Note that this needs to exist when the exporter starts.
62 '';
63 };
64 login = mkOption {
65 type = types.nullOr types.str;
66 default = null;
67 example = "exporteruser@domain.tld";
68 description = lib.mdDoc ''
69 Username to use for SMTP authentication.
70 '';
71 };
72 passphrase = mkOption {
73 type = types.nullOr types.str;
74 default = null;
75 description = lib.mdDoc ''
76 Password to use for SMTP authentication.
77 '';
78 };
79 };
80
81 exporterOptions.options = {
82 monitoringInterval = mkOption {
83 type = types.str;
84 example = "10s";
85 description = lib.mdDoc ''
86 Time interval between two probe attempts.
87 '';
88 };
89 mailCheckTimeout = mkOption {
90 type = types.str;
91 description = lib.mdDoc ''
92 Timeout until mails are considered "didn't make it".
93 '';
94 };
95 disableFileDeletion = mkOption {
96 type = types.bool;
97 default = false;
98 description = lib.mdDoc ''
99 Disables the exporter's function to delete probing mails.
100 '';
101 };
102 servers = mkOption {
103 type = types.listOf (types.submodule serverOptions);
104 default = [];
105 example = literalExpression ''
106 [ {
107 name = "testserver";
108 server = "smtp.domain.tld";
109 port = 587;
110 from = "exporteruser@domain.tld";
111 to = "exporteruser@domain.tld";
112 detectionDir = "/path/to/Maildir/new";
113 } ]
114 '';
115 description = lib.mdDoc ''
116 List of servers that should be probed.
117
118 *Note:* if your mailserver has {manpage}`rspamd(8)` configured,
119 it can happen that emails from this exporter are marked as spam.
120
121 It's possible to work around the issue with a config like this:
122 ```
123 {
124 services.rspamd.locals."multimap.conf".text = '''
125 ALLOWLIST_PROMETHEUS {
126 filter = "email:domain:tld";
127 type = "from";
128 map = "''${pkgs.writeText "allowmap" "domain.tld"}";
129 score = -100.0;
130 }
131 ''';
132 }
133 ```
134 '';
135 };
136 };
137in
138{
139 port = 9225;
140 extraOpts = {
141 environmentFile = mkOption {
142 type = types.nullOr types.str;
143 default = null;
144 description = lib.mdDoc ''
145 File containing env-vars to be substituted into the exporter's config.
146 '';
147 };
148 configFile = mkOption {
149 type = types.nullOr types.path;
150 default = null;
151 description = lib.mdDoc ''
152 Specify the mailexporter configuration file to use.
153 '';
154 };
155 configuration = mkOption {
156 type = types.nullOr (types.submodule exporterOptions);
157 default = null;
158 description = lib.mdDoc ''
159 Specify the mailexporter configuration file to use.
160 '';
161 };
162 telemetryPath = mkOption {
163 type = types.str;
164 default = "/metrics";
165 description = lib.mdDoc ''
166 Path under which to expose metrics.
167 '';
168 };
169 };
170 serviceOpts = {
171 serviceConfig = {
172 DynamicUser = false;
173 EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
174 RuntimeDirectory = "prometheus-mail-exporter";
175 ExecStartPre = [
176 "${pkgs.writeShellScript "subst-secrets-mail-exporter" ''
177 umask 0077
178 ${pkgs.envsubst}/bin/envsubst -i ${configFile} -o ''${RUNTIME_DIRECTORY}/mail-exporter.json
179 ''}"
180 ];
181 ExecStart = ''
182 ${pkgs.prometheus-mail-exporter}/bin/mailexporter \
183 --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
184 --web.telemetry-path ${cfg.telemetryPath} \
185 --config.file ''${RUNTIME_DIRECTORY}/mail-exporter.json \
186 ${concatStringsSep " \\\n " cfg.extraFlags}
187 '';
188 };
189 };
190}