1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9let
10
11 cfg = config.services.smokeping;
12 smokepingHome = "/var/lib/smokeping";
13 smokepingPidDir = "/run";
14 configFile =
15 if cfg.config == null then
16 ''
17 *** General ***
18 cgiurl = ${cfg.cgiUrl}
19 contact = ${cfg.ownerEmail}
20 datadir = ${smokepingHome}/data
21 imgcache = ${smokepingHome}/cache
22 imgurl = ${cfg.imgUrl}
23 linkstyle = ${cfg.linkStyle}
24 ${lib.optionalString (cfg.mailHost != "") "mailhost = ${cfg.mailHost}"}
25 owner = ${cfg.owner}
26 pagedir = ${smokepingHome}/cache
27 piddir = ${smokepingPidDir}
28 ${lib.optionalString (cfg.sendmail != null) "sendmail = ${cfg.sendmail}"}
29 smokemail = ${cfg.smokeMailTemplate}
30 *** Presentation ***
31 template = ${cfg.presentationTemplate}
32 ${cfg.presentationConfig}
33 *** Alerts ***
34 ${cfg.alertConfig}
35 *** Database ***
36 ${cfg.databaseConfig}
37 *** Probes ***
38 ${cfg.probeConfig}
39 *** Targets ***
40 ${cfg.targetConfig}
41 ${cfg.extraConfig}
42 ''
43 else
44 cfg.config;
45
46 configPath = pkgs.writeText "smokeping.conf" configFile;
47 cgiHome = pkgs.writeScript "smokeping.fcgi" ''
48 #!${pkgs.bash}/bin/bash
49 ${cfg.package}/bin/smokeping_cgi /etc/smokeping.conf
50 '';
51in
52
53{
54 imports = [
55 (mkRemovedOptionModule [ "services" "smokeping" "port" ] ''
56 The smokeping web service is now served by nginx.
57 In order to change the port, you need to change the nginx configuration under `services.nginx.virtualHosts.smokeping.listen.*.port`.
58 '')
59 ];
60
61 options = {
62 services.smokeping = {
63 enable = mkEnableOption "smokeping service";
64
65 alertConfig = mkOption {
66 type = types.lines;
67 default = ''
68 to = root@localhost
69 from = smokeping@localhost
70 '';
71 example = ''
72 to = alertee@address.somewhere
73 from = smokealert@company.xy
74
75 +someloss
76 type = loss
77 # in percent
78 pattern = >0%,*12*,>0%,*12*,>0%
79 comment = loss 3 times in a row;
80 '';
81 description = "Configuration for alerts.";
82 };
83 cgiUrl = mkOption {
84 type = types.str;
85 default = "http://${cfg.hostName}/smokeping.cgi";
86 defaultText = literalExpression ''"http://''${hostName}/smokeping.cgi"'';
87 example = "https://somewhere.example.com/smokeping.cgi";
88 description = "URL to the smokeping cgi.";
89 };
90 config = mkOption {
91 type = types.nullOr types.lines;
92 default = null;
93 description = ''
94 Full smokeping config supplied by the user. Overrides
95 and replaces any other configuration supplied.
96 '';
97 };
98 databaseConfig = mkOption {
99 type = types.lines;
100 default = ''
101 step = 300
102 pings = 20
103 # consfn mrhb steps total
104 AVERAGE 0.5 1 1008
105 AVERAGE 0.5 12 4320
106 MIN 0.5 12 4320
107 MAX 0.5 12 4320
108 AVERAGE 0.5 144 720
109 MAX 0.5 144 720
110 MIN 0.5 144 720
111
112 '';
113 example = ''
114 # near constant pings.
115 step = 30
116 pings = 20
117 # consfn mrhb steps total
118 AVERAGE 0.5 1 10080
119 AVERAGE 0.5 12 43200
120 MIN 0.5 12 43200
121 MAX 0.5 12 43200
122 AVERAGE 0.5 144 7200
123 MAX 0.5 144 7200
124 MIN 0.5 144 7200
125 '';
126 description = ''
127 Configure the ping frequency and retention of the rrd files.
128 Once set, changing the interval will require deletion or migration of all
129 the collected data.'';
130 };
131 extraConfig = mkOption {
132 type = types.lines;
133 default = "";
134 description = "Any additional customization not already included.";
135 };
136 hostName = mkOption {
137 type = types.str;
138 default = config.networking.fqdn;
139 defaultText = literalExpression "config.networking.fqdn";
140 example = "somewhere.example.com";
141 description = "DNS name for the urls generated in the cgi.";
142 };
143 imgUrl = mkOption {
144 type = types.str;
145 default = "cache";
146 defaultText = literalExpression ''"cache"'';
147 example = "https://somewhere.example.com/cache";
148 description = ''
149 Base url for images generated in the cgi.
150
151 The default is a relative URL to ensure it works also when e.g. forwarding
152 the GUI port via SSH.
153 '';
154 };
155 linkStyle = mkOption {
156 type = types.enum [
157 "original"
158 "absolute"
159 "relative"
160 ];
161 default = "relative";
162 example = "absolute";
163 description = "DNS name for the urls generated in the cgi.";
164 };
165 mailHost = mkOption {
166 type = types.str;
167 default = "";
168 example = "localhost";
169 description = "Use this SMTP server to send alerts";
170 };
171 owner = mkOption {
172 type = types.str;
173 default = "nobody";
174 example = "Bob Foobawr";
175 description = "Real name of the owner of the instance";
176 };
177 ownerEmail = mkOption {
178 type = types.str;
179 default = "no-reply@${cfg.hostName}";
180 defaultText = literalExpression ''"no-reply@''${hostName}"'';
181 example = "no-reply@yourdomain.com";
182 description = "Email contact for owner";
183 };
184 package = mkPackageOption pkgs "smokeping" { };
185 host = mkOption {
186 type = types.nullOr types.str;
187 default = "localhost";
188 example = "192.0.2.1"; # rfc5737 example IP for documentation
189 description = ''
190 Host/IP to bind to for the web server.
191
192 Setting it to `null` skips passing the -h option to thttpd,
193 which makes it bind to all interfaces.
194 '';
195 };
196 presentationConfig = mkOption {
197 type = types.lines;
198 default = ''
199 + charts
200 menu = Charts
201 title = The most interesting destinations
202 ++ stddev
203 sorter = StdDev(entries=>4)
204 title = Top Standard Deviation
205 menu = Std Deviation
206 format = Standard Deviation %f
207 ++ max
208 sorter = Max(entries=>5)
209 title = Top Max Roundtrip Time
210 menu = by Max
211 format = Max Roundtrip Time %f seconds
212 ++ loss
213 sorter = Loss(entries=>5)
214 title = Top Packet Loss
215 menu = Loss
216 format = Packets Lost %f
217 ++ median
218 sorter = Median(entries=>5)
219 title = Top Median Roundtrip Time
220 menu = by Median
221 format = Median RTT %f seconds
222 + overview
223 width = 600
224 height = 50
225 range = 10h
226 + detail
227 width = 600
228 height = 200
229 unison_tolerance = 2
230 "Last 3 Hours" 3h
231 "Last 30 Hours" 30h
232 "Last 10 Days" 10d
233 "Last 360 Days" 360d
234 '';
235 description = "presentation graph style";
236 };
237 presentationTemplate = mkOption {
238 type = types.str;
239 default = "${pkgs.smokeping}/etc/basepage.html.dist";
240 defaultText = literalExpression ''"''${pkgs.smokeping}/etc/basepage.html.dist"'';
241 description = "Default page layout for the web UI.";
242 };
243 probeConfig = mkOption {
244 type = types.lines;
245 default = ''
246 + FPing
247 binary = ${config.security.wrapperDir}/fping
248 '';
249 defaultText = literalExpression ''
250 '''
251 + FPing
252 binary = ''${config.security.wrapperDir}/fping
253 '''
254 '';
255 description = "Probe configuration";
256 };
257 sendmail = mkOption {
258 type = types.nullOr types.path;
259 default = null;
260 example = "/run/wrappers/bin/sendmail";
261 description = "Use this sendmail compatible script to deliver alerts";
262 };
263 smokeMailTemplate = mkOption {
264 type = types.str;
265 default = "${cfg.package}/etc/smokemail.dist";
266 defaultText = literalExpression ''"''${package}/etc/smokemail.dist"'';
267 description = "Specify the smokemail template for alerts.";
268 };
269 targetConfig = mkOption {
270 type = types.lines;
271 default = ''
272 probe = FPing
273 menu = Top
274 title = Network Latency Grapher
275 remark = Welcome to the SmokePing website of xxx Company. \
276 Here you will learn all about the latency of our network.
277 + Local
278 menu = Local
279 title = Local Network
280 ++ LocalMachine
281 menu = Local Machine
282 title = This host
283 host = localhost
284 '';
285 description = "Target configuration";
286 };
287 user = mkOption {
288 type = types.str;
289 default = "smokeping";
290 description = "User that runs smokeping and (optionally) thttpd. A group of the same name will be created as well.";
291 };
292 webService = mkOption {
293 type = types.bool;
294 default = true;
295 description = "Enable a smokeping web interface";
296 };
297 };
298
299 };
300
301 config = mkIf cfg.enable {
302 assertions = [
303 {
304 assertion = !(cfg.sendmail != null && cfg.mailHost != "");
305 message = "services.smokeping: sendmail and Mailhost cannot both be enabled.";
306 }
307 ];
308 security.wrappers = {
309 fping = {
310 setuid = true;
311 owner = "root";
312 group = "root";
313 source = "${pkgs.fping}/bin/fping";
314 };
315 };
316 environment.etc."smokeping.conf".source = configPath;
317 environment.systemPackages = [ pkgs.fping ];
318 users.users.${cfg.user} = {
319 isNormalUser = false;
320 isSystemUser = true;
321 group = cfg.user;
322 description = "smokeping daemon user";
323 home = smokepingHome;
324 };
325
326 users.users.${config.services.nginx.user} = mkIf cfg.webService {
327 extraGroups = [
328 cfg.user # # user == group in this module
329 ];
330 };
331
332 users.groups.${cfg.user} = { };
333
334 systemd.services.smokeping = {
335 reloadTriggers = [ configPath ];
336 requiredBy = [ "multi-user.target" ];
337 serviceConfig = {
338 User = cfg.user;
339 Restart = "on-failure";
340 ExecStart = "${cfg.package}/bin/smokeping --config=/etc/smokeping.conf --nodaemon";
341 };
342 preStart = ''
343 ${cfg.package}/bin/smokeping --check --config=${configPath}
344 ${cfg.package}/bin/smokeping --static --config=${configPath}
345 '';
346 };
347
348 systemd.tmpfiles.rules = [
349 # create cache and data directories
350 "d ${smokepingHome}/cache 0750 ${cfg.user} ${cfg.user}"
351 "d ${smokepingHome}/data 0750 ${cfg.user} ${cfg.user}"
352 # create symlings
353 "L+ ${smokepingHome}/css - - - - ${cfg.package}/htdocs/css"
354 "L+ ${smokepingHome}/js - - - - ${cfg.package}/htdocs/js"
355 "L+ ${smokepingHome}/smokeping.fcgi - - - - ${cgiHome}"
356 # recursively adjust access mode and ownership (in case config change)
357 "Z ${smokepingHome} 0750 ${cfg.user} ${cfg.user}"
358 ];
359
360 # use nginx to serve the smokeping web service
361 services.fcgiwrap.instances.smokeping = mkIf cfg.webService {
362 process.user = cfg.user;
363 process.group = cfg.user;
364 socket = { inherit (config.services.nginx) user group; };
365 };
366 services.nginx = mkIf cfg.webService {
367 enable = true;
368 virtualHosts."smokeping" = {
369 serverName = mkDefault cfg.host;
370 locations."/" = {
371 root = smokepingHome;
372 index = "smokeping.fcgi";
373 };
374 locations."/smokeping.fcgi" = {
375 extraConfig = ''
376 include ${config.services.nginx.package}/conf/fastcgi_params;
377 fastcgi_pass unix:${config.services.fcgiwrap.instances.smokeping.socket.address};
378 fastcgi_param SCRIPT_FILENAME ${smokepingHome}/smokeping.fcgi;
379 fastcgi_param DOCUMENT_ROOT ${smokepingHome};
380 '';
381 };
382 };
383 };
384 };
385
386 meta.maintainers = with lib.maintainers; [ nh2 ];
387}