1{ config, lib, pkgs, ... }:
2
3# TODO: support munin-async
4# TODO: LWP/Pg perl libs aren't recognized
5
6# TODO: support fastcgi
7# http://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
8# spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
9# spawn-fcgi -s /run/munin/fastcgi-html.sock -U www-data -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
10# https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
11# nginx https://munin.readthedocs.org/en/latest/example/webserver/nginx.html
12
13
14with lib;
15
16let
17 nodeCfg = config.services.munin-node;
18 cronCfg = config.services.munin-cron;
19
20 muninConf = pkgs.writeText "munin.conf"
21 ''
22 dbdir /var/lib/munin
23 htmldir /var/www/munin
24 logdir /var/log/munin
25 rundir /run/munin
26
27 ${lib.optionalString (cronCfg.extraCSS != "") "staticdir ${customStaticDir}"}
28
29 ${cronCfg.extraGlobalConfig}
30
31 ${cronCfg.hosts}
32 '';
33
34 nodeConf = pkgs.writeText "munin-node.conf"
35 ''
36 log_level 3
37 log_file Sys::Syslog
38 port 4949
39 host *
40 background 0
41 user root
42 group root
43 host_name ${config.networking.hostName}
44 setsid 0
45
46 # wrapped plugins by makeWrapper being with dots
47 ignore_file ^\.
48
49 allow ^::1$
50 allow ^127\.0\.0\.1$
51
52 ${nodeCfg.extraConfig}
53 '';
54
55 pluginConf = pkgs.writeText "munin-plugin-conf"
56 ''
57 [hddtemp_smartctl]
58 user root
59 group root
60
61 [meminfo]
62 user root
63 group root
64
65 [ipmi*]
66 user root
67 group root
68
69 [munin*]
70 env.UPDATE_STATSFILE /var/lib/munin/munin-update.stats
71
72 ${nodeCfg.extraPluginConfig}
73 '';
74
75 pluginConfDir = pkgs.stdenv.mkDerivation {
76 name = "munin-plugin-conf.d";
77 buildCommand = ''
78 mkdir $out
79 ln -s ${pluginConf} $out/nixos-config
80 '';
81 };
82
83 # Copy one Munin plugin into the Nix store with a specific name.
84 # This is suitable for use with plugins going directly into /etc/munin/plugins,
85 # i.e. munin.extraPlugins.
86 internOnePlugin = name: path:
87 "cp -a '${path}' '${name}'";
88
89 # Copy an entire tree of Munin plugins into a single directory in the Nix
90 # store, with no renaming.
91 # This is suitable for use with munin-node-configure --suggest, i.e.
92 # munin.extraAutoPlugins.
93 internManyPlugins = name: path:
94 "find '${path}' -type f -perm /a+x -exec cp -a -t . '{}' '+'";
95
96 # Use the appropriate intern-fn to copy the plugins into the store and patch
97 # them afterwards in an attempt to get them to run on NixOS.
98 internAndFixPlugins = name: intern-fn: paths:
99 pkgs.runCommand name {} ''
100 mkdir -p "$out"
101 cd "$out"
102 ${lib.concatStringsSep "\n"
103 (lib.attrsets.mapAttrsToList intern-fn paths)}
104 chmod -R u+w .
105 find . -type f -exec sed -E -i '
106 s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
107 ' '{}' '+'
108 '';
109
110 # TODO: write a derivation for munin-contrib, so that for contrib plugins
111 # you can just refer to them by name rather than needing to include a copy
112 # of munin-contrib in your nixos configuration.
113 extraPluginDir = internAndFixPlugins "munin-extra-plugins.d"
114 internOnePlugin nodeCfg.extraPlugins;
115
116 extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d"
117 internManyPlugins
118 (builtins.listToAttrs
119 (map
120 (path: { name = baseNameOf path; value = path; })
121 nodeCfg.extraAutoPlugins));
122
123 customStaticDir = pkgs.runCommand "munin-custom-static-data" {} ''
124 cp -a "${pkgs.munin}/etc/opt/munin/static" "$out"
125 cd "$out"
126 chmod -R u+w .
127 echo "${cronCfg.extraCSS}" >> style.css
128 echo "${cronCfg.extraCSS}" >> style-new.css
129 '';
130in
131
132{
133
134 options = {
135
136 services.munin-node = {
137
138 enable = mkOption {
139 default = false;
140 type = types.bool;
141 description = lib.mdDoc ''
142 Enable Munin Node agent. Munin node listens on 0.0.0.0 and
143 by default accepts connections only from 127.0.0.1 for security reasons.
144
145 See <http://guide.munin-monitoring.org/en/latest/architecture/index.html>.
146 '';
147 };
148
149 extraConfig = mkOption {
150 default = "";
151 type = types.lines;
152 description = lib.mdDoc ''
153 {file}`munin-node.conf` extra configuration. See
154 <http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
155 '';
156 };
157
158 extraPluginConfig = mkOption {
159 default = "";
160 type = types.lines;
161 description = lib.mdDoc ''
162 {file}`plugin-conf.d` extra plugin configuration. See
163 <http://guide.munin-monitoring.org/en/latest/plugin/use.html>
164 '';
165 example = ''
166 [fail2ban_*]
167 user root
168 '';
169 };
170
171 extraPlugins = mkOption {
172 default = {};
173 type = with types; attrsOf path;
174 description = lib.mdDoc ''
175 Additional Munin plugins to activate. Keys are the name of the plugin
176 symlink, values are the path to the underlying plugin script. You
177 can use the same plugin script multiple times (e.g. for wildcard
178 plugins).
179
180 Note that these plugins do not participate in autoconfiguration. If
181 you want to autoconfigure additional plugins, use
182 {option}`services.munin-node.extraAutoPlugins`.
183
184 Plugins enabled in this manner take precedence over autoconfigured
185 plugins.
186
187 Plugins will be copied into the Nix store, and it will attempt to
188 modify them to run properly by fixing hardcoded references to
189 `/bin`, `/usr/bin`,
190 `/sbin`, and `/usr/sbin`.
191 '';
192 example = literalExpression ''
193 {
194 zfs_usage_bigpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
195 zfs_usage_smallpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
196 zfs_list = /src/munin-contrib/plugins/zfs/zfs_list;
197 };
198 '';
199 };
200
201 extraAutoPlugins = mkOption {
202 default = [];
203 type = with types; listOf path;
204 description = lib.mdDoc ''
205 Additional Munin plugins to autoconfigure, using
206 `munin-node-configure --suggest`. These should be
207 the actual paths to the plugin files (or directories containing them),
208 not just their names.
209
210 If you want to manually enable individual plugins instead, use
211 {option}`services.munin-node.extraPlugins`.
212
213 Note that only plugins that have the 'autoconfig' capability will do
214 anything if listed here, since plugins that cannot autoconfigure
215 won't be automatically enabled by
216 `munin-node-configure`.
217
218 Plugins will be copied into the Nix store, and it will attempt to
219 modify them to run properly by fixing hardcoded references to
220 `/bin`, `/usr/bin`,
221 `/sbin`, and `/usr/sbin`.
222 '';
223 example = literalExpression ''
224 [
225 /src/munin-contrib/plugins/zfs
226 /src/munin-contrib/plugins/ssh
227 ];
228 '';
229 };
230
231 disabledPlugins = mkOption {
232 # TODO: figure out why Munin isn't writing the log file and fix it.
233 # In the meantime this at least suppresses a useless graph full of
234 # NaNs in the output.
235 default = [ "munin_stats" ];
236 type = with types; listOf str;
237 description = lib.mdDoc ''
238 Munin plugins to disable, even if
239 `munin-node-configure --suggest` tries to enable
240 them. To disable a wildcard plugin, use an actual wildcard, as in
241 the example.
242
243 munin_stats is disabled by default as it tries to read
244 `/var/log/munin/munin-update.log` for timing
245 information, and the NixOS build of Munin does not write this file.
246 '';
247 example = [ "diskstats" "zfs_usage_*" ];
248 };
249 };
250
251 services.munin-cron = {
252
253 enable = mkOption {
254 default = false;
255 type = types.bool;
256 description = lib.mdDoc ''
257 Enable munin-cron. Takes care of all heavy lifting to collect data from
258 nodes and draws graphs to html. Runs munin-update, munin-limits,
259 munin-graphs and munin-html in that order.
260
261 HTML output is in {file}`/var/www/munin/`, configure your
262 favourite webserver to serve static files.
263 '';
264 };
265
266 extraGlobalConfig = mkOption {
267 default = "";
268 type = types.lines;
269 description = lib.mdDoc ''
270 {file}`munin.conf` extra global configuration.
271 See <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
272 Useful to setup notifications, see
273 <http://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
274 '';
275 example = ''
276 contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
277 '';
278 };
279
280 hosts = mkOption {
281 default = "";
282 type = types.lines;
283 description = lib.mdDoc ''
284 Definitions of hosts of nodes to collect data from. Needs at least one
285 host for cron to succeed. See
286 <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
287 '';
288 example = literalExpression ''
289 '''
290 [''${config.networking.hostName}]
291 address localhost
292 '''
293 '';
294 };
295
296 extraCSS = mkOption {
297 default = "";
298 type = types.lines;
299 description = lib.mdDoc ''
300 Custom styling for the HTML that munin-cron generates. This will be
301 appended to the CSS files used by munin-cron and will thus take
302 precedence over the builtin styles.
303 '';
304 example = ''
305 /* A simple dark theme. */
306 html, body { background: #222222; }
307 #header, #footer { background: #333333; }
308 img.i, img.iwarn, img.icrit, img.iunkn {
309 filter: invert(100%) hue-rotate(-30deg);
310 }
311 '';
312 };
313
314 };
315
316 };
317
318 config = mkMerge [ (mkIf (nodeCfg.enable || cronCfg.enable) {
319
320 environment.systemPackages = [ pkgs.munin ];
321
322 users.users.munin = {
323 description = "Munin monitoring user";
324 group = "munin";
325 uid = config.ids.uids.munin;
326 home = "/var/lib/munin";
327 };
328
329 users.groups.munin = {
330 gid = config.ids.gids.munin;
331 };
332
333 }) (mkIf nodeCfg.enable {
334
335 systemd.services.munin-node = {
336 description = "Munin Node";
337 after = [ "network.target" ];
338 wantedBy = [ "multi-user.target" ];
339 path = with pkgs; [ munin smartmontools "/run/current-system/sw" "/run/wrappers" ];
340 environment.MUNIN_LIBDIR = "${pkgs.munin}/lib";
341 environment.MUNIN_PLUGSTATE = "/run/munin";
342 environment.MUNIN_LOGDIR = "/var/log/munin";
343 preStart = ''
344 echo "Updating munin plugins..."
345
346 mkdir -p /etc/munin/plugins
347 rm -rf /etc/munin/plugins/*
348
349 # Autoconfigure builtin plugins
350 ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${pkgs.munin}/lib/plugins --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
351
352 # Autoconfigure extra plugins
353 ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${extraAutoPluginDir} --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
354
355 ${lib.optionalString (nodeCfg.extraPlugins != {}) ''
356 # Link in manually enabled plugins
357 ln -f -s -t /etc/munin/plugins ${extraPluginDir}/*
358 ''}
359
360 ${lib.optionalString (nodeCfg.disabledPlugins != []) ''
361 # Disable plugins
362 cd /etc/munin/plugins
363 rm -f ${toString nodeCfg.disabledPlugins}
364 ''}
365 '';
366 serviceConfig = {
367 ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/ --sconfdir=${pluginConfDir}";
368 };
369 };
370
371 # munin_stats plugin breaks as of 2.0.33 when this doesn't exist
372 systemd.tmpfiles.rules = [ "d /run/munin 0755 munin munin -" ];
373
374 }) (mkIf cronCfg.enable {
375
376 # Munin is hardcoded to use DejaVu Mono and the graphs come out wrong if
377 # it's not available.
378 fonts.fonts = [ pkgs.dejavu_fonts ];
379
380 systemd.timers.munin-cron = {
381 description = "batch Munin master programs";
382 wantedBy = [ "timers.target" ];
383 timerConfig.OnCalendar = "*:0/5";
384 };
385
386 systemd.services.munin-cron = {
387 description = "batch Munin master programs";
388 unitConfig.Documentation = "man:munin-cron(8)";
389
390 serviceConfig = {
391 Type = "oneshot";
392 User = "munin";
393 ExecStart = "${pkgs.munin}/bin/munin-cron --config ${muninConf}";
394 };
395 };
396
397 systemd.tmpfiles.rules = [
398 "d /run/munin 0755 munin munin -"
399 "d /var/log/munin 0755 munin munin -"
400 "d /var/www/munin 0755 munin munin -"
401 "d /var/lib/munin 0755 munin munin -"
402 ];
403 })];
404}