at 24.11-pre 14 kB view raw
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# 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 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. 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 . '{}' '+'"; 96 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 {} '' 109 mkdir -p "$out" 110 cd "$out" 111 ${lib.concatStringsSep "\n" (map intern-fn paths)} 112 chmod -R u+w . 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 115 " '{}' '+' 116 ''; 117 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" 122 internOnePlugin 123 (lib.attrsets.mapAttrsToList (k: v: { name = k; path = v; }) nodeCfg.extraPlugins); 124 125 extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d" 126 internManyPlugins nodeCfg.extraAutoPlugins; 127 128 customStaticDir = pkgs.runCommand "munin-custom-static-data" {} '' 129 cp -a "${pkgs.munin}/etc/opt/munin/static" "$out" 130 cd "$out" 131 chmod -R u+w . 132 echo "${cronCfg.extraCSS}" >> style.css 133 echo "${cronCfg.extraCSS}" >> style-new.css 134 ''; 135in 136 137{ 138 139 options = { 140 141 services.munin-node = { 142 143 enable = mkOption { 144 default = false; 145 type = types.bool; 146 description = '' 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. 149 150 See <https://guide.munin-monitoring.org/en/latest/architecture/index.html>. 151 ''; 152 }; 153 154 extraConfig = mkOption { 155 default = ""; 156 type = types.lines; 157 description = '' 158 {file}`munin-node.conf` extra configuration. See 159 <https://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html> 160 ''; 161 }; 162 163 extraPluginConfig = mkOption { 164 default = ""; 165 type = types.lines; 166 description = '' 167 {file}`plugin-conf.d` extra plugin configuration. See 168 <https://guide.munin-monitoring.org/en/latest/plugin/use.html> 169 ''; 170 example = '' 171 [fail2ban_*] 172 user root 173 ''; 174 }; 175 176 extraPlugins = mkOption { 177 default = {}; 178 type = with types; attrsOf path; 179 description = '' 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 183 plugins). 184 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`. 188 189 Plugins enabled in this manner take precedence over autoconfigured 190 plugins. 191 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 194 `/bin`, `/usr/bin`, 195 `/sbin`, and `/usr/sbin`. 196 ''; 197 example = literalExpression '' 198 { 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; 202 }; 203 ''; 204 }; 205 206 extraAutoPlugins = mkOption { 207 default = []; 208 type = with types; listOf path; 209 description = '' 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. 214 215 If you want to manually enable individual plugins instead, use 216 {option}`services.munin-node.extraPlugins`. 217 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`. 222 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 225 `/bin`, `/usr/bin`, 226 `/sbin`, and `/usr/sbin`. 227 ''; 228 example = literalExpression '' 229 [ 230 /src/munin-contrib/plugins/zfs 231 /src/munin-contrib/plugins/ssh 232 ]; 233 ''; 234 }; 235 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; 242 description = '' 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 246 the example. 247 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. 251 ''; 252 example = [ "diskstats" "zfs_usage_*" ]; 253 }; 254 }; 255 256 services.munin-cron = { 257 258 enable = mkOption { 259 default = false; 260 type = types.bool; 261 description = '' 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. 265 266 HTML output is in {file}`/var/www/munin/`, configure your 267 favourite webserver to serve static files. 268 ''; 269 }; 270 271 extraGlobalConfig = mkOption { 272 default = ""; 273 type = types.lines; 274 description = '' 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> 279 ''; 280 example = '' 281 contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com 282 ''; 283 }; 284 285 hosts = mkOption { 286 default = ""; 287 type = types.lines; 288 description = '' 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> 292 ''; 293 example = literalExpression '' 294 ''' 295 [''${config.networking.hostName}] 296 address localhost 297 ''' 298 ''; 299 }; 300 301 extraCSS = mkOption { 302 default = ""; 303 type = types.lines; 304 description = '' 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. 308 ''; 309 example = '' 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); 315 } 316 ''; 317 }; 318 319 }; 320 321 }; 322 323 config = mkMerge [ (mkIf (nodeCfg.enable || cronCfg.enable) { 324 325 environment.systemPackages = [ pkgs.munin ]; 326 327 users.users.munin = { 328 description = "Munin monitoring user"; 329 group = "munin"; 330 uid = config.ids.uids.munin; 331 home = "/var/lib/munin"; 332 }; 333 334 users.groups.munin = { 335 gid = config.ids.gids.munin; 336 }; 337 338 }) (mkIf nodeCfg.enable { 339 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"; 348 preStart = '' 349 echo "Updating munin plugins..." 350 351 mkdir -p /etc/munin/plugins 352 rm -rf /etc/munin/plugins/* 353 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 356 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 359 360 ${lib.optionalString (nodeCfg.extraPlugins != {}) '' 361 # Link in manually enabled plugins 362 ln -f -s -t /etc/munin/plugins ${extraPluginDir}/* 363 ''} 364 365 ${lib.optionalString (nodeCfg.disabledPlugins != []) '' 366 # Disable plugins 367 cd /etc/munin/plugins 368 rm -f ${toString nodeCfg.disabledPlugins} 369 ''} 370 ''; 371 serviceConfig = { 372 ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/ --sconfdir=${pluginConfDir}"; 373 }; 374 }; 375 376 # munin_stats plugin breaks as of 2.0.33 when this doesn't exist 377 systemd.tmpfiles.settings."10-munin"."/run/munin".d = { 378 mode = "0755"; 379 user = "munin"; 380 group = "munin"; 381 }; 382 383 }) (mkIf cronCfg.enable { 384 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 ]; 388 389 systemd.timers.munin-cron = { 390 description = "batch Munin master programs"; 391 wantedBy = [ "timers.target" ]; 392 timerConfig.OnCalendar = "*:0/5"; 393 }; 394 395 systemd.services.munin-cron = { 396 description = "batch Munin master programs"; 397 unitConfig.Documentation = "man:munin-cron(8)"; 398 399 serviceConfig = { 400 Type = "oneshot"; 401 User = "munin"; 402 ExecStart = "${pkgs.munin}/bin/munin-cron --config ${muninConf}"; 403 }; 404 }; 405 406 systemd.tmpfiles.settings."20-munin" = let 407 defaultConfig = { 408 mode = "0755"; 409 user = "munin"; 410 group = "munin"; 411 }; 412 in { 413 "/run/munin".d = defaultConfig; 414 "/var/log/munin".d = defaultConfig; 415 "/var/www/munin".d = defaultConfig; 416 "/var/lib/munin".d = defaultConfig; 417 }; 418 })]; 419}