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