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