1 { config, lib, pkgs, ... }:
3 # TODO: support munin-async
4 # TODO: LWP/Pg perl libs aren't recognized
6 # TODO: support fastcgi
7 # https://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
17 nodeCfg = config.services.munin-node;
18 cronCfg = config.services.munin-cron;
20 muninConf = pkgs.writeText "munin.conf"
23 htmldir /var/www/munin
27 ${lib.optionalString (cronCfg.extraCSS != "") "staticdir ${customStaticDir}"}
29 ${cronCfg.extraGlobalConfig}
34 nodeConf = pkgs.writeText "munin-node.conf"
43 host_name ${config.networking.hostName}
46 # wrapped plugins by makeWrapper being with dots
52 ${nodeCfg.extraConfig}
55 pluginConf = pkgs.writeText "munin-plugin-conf"
70 env.UPDATE_STATSFILE /var/lib/munin/munin-update.stats
72 ${nodeCfg.extraPluginConfig}
75 pluginConfDir = pkgs.stdenv.mkDerivation {
76 name = "munin-plugin-conf.d";
79 ln -s ${pluginConf} $out/nixos-config
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}'";
89 # Copy an entire tree of Munin plugins into a single directory in the Nix
90 # store, with no renaming. The output is suitable for use with
91 # munin-node-configure --suggest, i.e. munin.extraAutoPlugins.
92 # Note that this flattens the input; this is intentional, as
93 # munin-node-configure won't recurse into subdirectories.
94 internManyPlugins = path:
95 "find '${path}' -type f -perm /a+x -exec cp -a -t . '{}' '+'";
97 # Use the appropriate intern-fn to copy the plugins into the store and patch
98 # them afterwards in an attempt to get them to run on NixOS.
99 # This is a bit hairy because we can't just fix shebangs; lots of munin plugins
100 # hardcode paths like /sbin/mount rather than trusting $PATH, so we have to
101 # look for and update those throughout the script. At the same time, if the
102 # plugin comes from a package that is already nixified, we don't want to
103 # rewrite paths like /nix/store/foo/sbin/mount.
104 # For now we make the simplifying assumption that no file will contain lines
105 # which mix store paths and FHS paths, and thus run our substitution only on
106 # lines which do not contain store paths.
107 internAndFixPlugins = name: intern-fn: paths:
108 pkgs.runCommand name {} ''
111 ${lib.concatStringsSep "\n" (map intern-fn paths)}
113 ${pkgs.findutils}/bin/find . -type f -exec ${pkgs.gnused}/bin/sed -E -i "
114 \%''${NIX_STORE}/%! s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
118 # TODO: write a derivation for munin-contrib, so that for contrib plugins
119 # you can just refer to them by name rather than needing to include a copy
120 # of munin-contrib in your nixos configuration.
121 extraPluginDir = internAndFixPlugins "munin-extra-plugins.d"
123 (lib.attrsets.mapAttrsToList (k: v: { name = k; path = v; }) nodeCfg.extraPlugins);
125 extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d"
126 internManyPlugins nodeCfg.extraAutoPlugins;
128 customStaticDir = pkgs.runCommand "munin-custom-static-data" {} ''
129 cp -a "${pkgs.munin}/etc/opt/munin/static" "$out"
132 echo "${cronCfg.extraCSS}" >> style.css
133 echo "${cronCfg.extraCSS}" >> style-new.css
141 services.munin-node = {
147 Enable Munin Node agent. Munin node listens on 0.0.0.0 and
148 by default accepts connections only from 127.0.0.1 for security reasons.
150 See <https://guide.munin-monitoring.org/en/latest/architecture/index.html>.
154 extraConfig = mkOption {
158 {file}`munin-node.conf` extra configuration. See
159 <https://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
163 extraPluginConfig = mkOption {
167 {file}`plugin-conf.d` extra plugin configuration. See
168 <https://guide.munin-monitoring.org/en/latest/plugin/use.html>
176 extraPlugins = mkOption {
178 type = with types; attrsOf path;
180 Additional Munin plugins to activate. Keys are the name of the plugin
181 symlink, values are the path to the underlying plugin script. You
182 can use the same plugin script multiple times (e.g. for wildcard
185 Note that these plugins do not participate in autoconfiguration. If
186 you want to autoconfigure additional plugins, use
187 {option}`services.munin-node.extraAutoPlugins`.
189 Plugins enabled in this manner take precedence over autoconfigured
192 Plugins will be copied into the Nix store, and it will attempt to
193 modify them to run properly by fixing hardcoded references to
195 `/sbin`, and `/usr/sbin`.
197 example = literalExpression ''
199 zfs_usage_bigpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
200 zfs_usage_smallpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
201 zfs_list = /src/munin-contrib/plugins/zfs/zfs_list;
206 extraAutoPlugins = mkOption {
208 type = with types; listOf path;
210 Additional Munin plugins to autoconfigure, using
211 `munin-node-configure --suggest`. These should be
212 the actual paths to the plugin files (or directories containing them),
213 not just their names.
215 If you want to manually enable individual plugins instead, use
216 {option}`services.munin-node.extraPlugins`.
218 Note that only plugins that have the 'autoconfig' capability will do
219 anything if listed here, since plugins that cannot autoconfigure
220 won't be automatically enabled by
221 `munin-node-configure`.
223 Plugins will be copied into the Nix store, and it will attempt to
224 modify them to run properly by fixing hardcoded references to
226 `/sbin`, and `/usr/sbin`.
228 example = literalExpression ''
230 /src/munin-contrib/plugins/zfs
231 /src/munin-contrib/plugins/ssh
236 disabledPlugins = mkOption {
237 # TODO: figure out why Munin isn't writing the log file and fix it.
238 # In the meantime this at least suppresses a useless graph full of
239 # NaNs in the output.
240 default = [ "munin_stats" ];
241 type = with types; listOf str;
243 Munin plugins to disable, even if
244 `munin-node-configure --suggest` tries to enable
245 them. To disable a wildcard plugin, use an actual wildcard, as in
248 munin_stats is disabled by default as it tries to read
249 `/var/log/munin/munin-update.log` for timing
250 information, and the NixOS build of Munin does not write this file.
252 example = [ "diskstats" "zfs_usage_*" ];
256 services.munin-cron = {
262 Enable munin-cron. Takes care of all heavy lifting to collect data from
263 nodes and draws graphs to html. Runs munin-update, munin-limits,
264 munin-graphs and munin-html in that order.
266 HTML output is in {file}`/var/www/munin/`, configure your
267 favourite webserver to serve static files.
271 extraGlobalConfig = mkOption {
275 {file}`munin.conf` extra global configuration.
276 See <https://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
277 Useful to setup notifications, see
278 <https://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
281 contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
289 Definitions of hosts of nodes to collect data from. Needs at least one
290 host for cron to succeed. See
291 <https://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
293 example = literalExpression ''
295 [''${config.networking.hostName}]
301 extraCSS = mkOption {
305 Custom styling for the HTML that munin-cron generates. This will be
306 appended to the CSS files used by munin-cron and will thus take
307 precedence over the builtin styles.
310 /* A simple dark theme. */
311 html, body { background: #222222; }
312 #header, #footer { background: #333333; }
313 img.i, img.iwarn, img.icrit, img.iunkn {
314 filter: invert(100%) hue-rotate(-30deg);
323 config = mkMerge [ (mkIf (nodeCfg.enable || cronCfg.enable) {
325 environment.systemPackages = [ pkgs.munin ];
327 users.users.munin = {
328 description = "Munin monitoring user";
330 uid = config.ids.uids.munin;
331 home = "/var/lib/munin";
334 users.groups.munin = {
335 gid = config.ids.gids.munin;
338 }) (mkIf nodeCfg.enable {
340 systemd.services.munin-node = {
341 description = "Munin Node";
342 after = [ "network.target" ];
343 wantedBy = [ "multi-user.target" ];
344 path = with pkgs; [ munin smartmontools "/run/current-system/sw" "/run/wrappers" ];
345 environment.MUNIN_LIBDIR = "${pkgs.munin}/lib";
346 environment.MUNIN_PLUGSTATE = "/run/munin";
347 environment.MUNIN_LOGDIR = "/var/log/munin";
349 echo "Updating munin plugins..."
351 mkdir -p /etc/munin/plugins
352 rm -rf /etc/munin/plugins/*
354 # Autoconfigure builtin plugins
355 ${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
357 # Autoconfigure extra plugins
358 ${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
360 ${lib.optionalString (nodeCfg.extraPlugins != {}) ''
361 # Link in manually enabled plugins
362 ln -f -s -t /etc/munin/plugins ${extraPluginDir}/*
365 ${lib.optionalString (nodeCfg.disabledPlugins != []) ''
367 cd /etc/munin/plugins
368 rm -f ${toString nodeCfg.disabledPlugins}
372 ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/ --sconfdir=${pluginConfDir}";
376 # munin_stats plugin breaks as of 2.0.33 when this doesn't exist
377 systemd.tmpfiles.settings."10-munin"."/run/munin".d = {
383 }) (mkIf cronCfg.enable {
385 # Munin is hardcoded to use DejaVu Mono and the graphs come out wrong if
386 # it's not available.
387 fonts.packages = [ pkgs.dejavu_fonts ];
389 systemd.timers.munin-cron = {
390 description = "batch Munin master programs";
391 wantedBy = [ "timers.target" ];
392 timerConfig.OnCalendar = "*:0/5";
395 systemd.services.munin-cron = {
396 description = "batch Munin master programs";
397 unitConfig.Documentation = "man:munin-cron(8)";
402 ExecStart = "${pkgs.munin}/bin/munin-cron --config ${muninConf}";
406 systemd.tmpfiles.settings."20-munin" = let
413 "/run/munin".d = defaultConfig;
414 "/var/log/munin".d = defaultConfig;
415 "/var/www/munin".d = defaultConfig;
416 "/var/lib/munin".d = defaultConfig;