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