[roles/base/munin-node] munin node + async proxy setup
authorSomeone <someone@somenet.org>
Tue, 25 Mar 2025 21:35:34 +0000 (22:35 +0100)
committerSomeone <someone@somenet.org>
Tue, 25 Mar 2025 21:35:34 +0000 (22:35 +0100)
roles/base/munin-node/defaults/main.yml [new file with mode: 0644]
roles/base/munin-node/files/default/authorized_keys [new file with mode: 0644]
roles/base/munin-node/files/default/munin-async.service [new file with mode: 0644]
roles/base/munin-node/files/default/munin-node.service [new file with mode: 0644]
roles/base/munin-node/files/default/plugin-confd.munin-node.conf [new file with mode: 0644]
roles/base/munin-node/files/default/plugins.somesible/df [new file with mode: 0644]
roles/base/munin-node/files/default/plugins.somesible/fw_conntrackntp [new file with mode: 0644]
roles/base/munin-node/files/default/plugins.somesible/smart_rw [new file with mode: 0644]
roles/base/munin-node/files/default/plugins.somesible/threads [new file with mode: 0644]
roles/base/munin-node/handlers/main.yml [new file with mode: 0644]
roles/base/munin-node/tasks/main.yml [new file with mode: 0644]

diff --git a/roles/base/munin-node/defaults/main.yml b/roles/base/munin-node/defaults/main.yml
new file mode 100644 (file)
index 0000000..2a52764
--- /dev/null
@@ -0,0 +1,25 @@
+#####################################
+### someone's ansible provisioner ###
+#####################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+# If not overridden in inventory or as a parameter, this is the value that will be used
+#
+---
+munin_node_setup: True
+
+munin_node_plugins:
+  cpu: "/usr/share/munin/plugins/cpu"
+  df: "/opt/somesible/munin-plugins/df"
+  fail2ban: "/usr/share/munin/plugins/fail2ban"
+  fw_conntrack: "/usr/share/munin/plugins/fw_conntrack"
+  hddtemp_smartctl: "/usr/share/munin/plugins/hddtemp_smartctl"
+  iostat: "/usr/share/munin/plugins/iostat"
+  memory: "/usr/share/munin/plugins/memory"
+  sensors_temp: "/usr/share/munin/plugins/sensors_"
+  smart_rw: "/opt/somesible/munin-plugins/smart_rw"
+  threads: "/opt/somesible/munin-plugins/threads"
+  uptime: "/usr/share/munin/plugins/uptime"
+
+munin_node_plugins_extra: {}
diff --git a/roles/base/munin-node/files/default/authorized_keys b/roles/base/munin-node/files/default/authorized_keys
new file mode 100644 (file)
index 0000000..bb9f542
--- /dev/null
@@ -0,0 +1,11 @@
+#
+################################################
+### Managed by someone's ansible provisioner ###
+################################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+
+command="/usr/share/munin/munin-async --spoolfetch",restrict OVERRIDE_KEY_HERE
+
+# keep empty line at EOF. (sshd quirk)
diff --git a/roles/base/munin-node/files/default/munin-async.service b/roles/base/munin-node/files/default/munin-async.service
new file mode 100644 (file)
index 0000000..4621904
--- /dev/null
@@ -0,0 +1,29 @@
+#
+################################################
+### Managed by someone's ansible provisioner ###
+################################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+
+[Unit]
+Description=Munin Node - asynchronous proxy
+Documentation=man:munin-asyncd http://guide.munin-monitoring.org/en/stable-2.0/reference/munin-asyncd.html
+Wants=munin-node.service
+After=munin-node.service
+
+[Service]
+Type=notify
+Restart=always
+User=munin-async
+ExecStart=/usr/share/munin/munin-asyncd --retain 14
+ProtectSystem=full
+ProtectHome=true
+PrivateTmp=true
+PrivateDevices=true
+ProtectKernelTunables=true
+ProtectKernelModules=true
+ProtectControlGroups=true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/base/munin-node/files/default/munin-node.service b/roles/base/munin-node/files/default/munin-node.service
new file mode 100644 (file)
index 0000000..97c9b62
--- /dev/null
@@ -0,0 +1,30 @@
+#
+################################################
+### Managed by someone's ansible provisioner ###
+################################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+
+[Unit]
+Description=Munin Node
+Documentation=man:munin-node(1) http://guide.munin-monitoring.org/en/stable-2.0/reference/munin-node.html
+After=network-online.target
+
+[Service]
+EnvironmentFile=-/etc/default/munin-node
+Type=notify
+Restart=always
+ExecStart=/usr/sbin/munin-node --foreground $DAEMON_ARGS
+PIDFile=/run/munin/munin-node.pid
+# Plugins like "smart_" require access to devices
+# someone: breaks iostat
+#PrivateDevices=false
+PrivateTmp=true
+#ProtectHome=true
+# "full" (instead of "strict") still allows write access to the state files
+# someone: breaks iostat
+#ProtectSystem=full
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/base/munin-node/files/default/plugin-confd.munin-node.conf b/roles/base/munin-node/files/default/plugin-confd.munin-node.conf
new file mode 100644 (file)
index 0000000..c5298bf
--- /dev/null
@@ -0,0 +1,99 @@
+#
+################################################
+### Managed by someone's ansible provisioner ###
+################################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+# This file is used to configure how the plugins are invoked.
+# Place in /etc/munin/plugin-conf.d/ or corresponding directory.
+#
+# PLEASE NOTE: Changes in the plugin-conf.d directory are only
+# read at munin-node startup, so restart at any changes.
+#
+# user <user>         # Set the user to run the plugin as.
+# group <group>       # Set the group to run the plugin as.
+# command <command>   # Run <command> instead of the plugin. %c expands to
+#                       what would normally be run.
+# env.<variable> <value> # Sets <variable> in the plugin's environment, see the
+#                       individual plugins to find out which variables they
+#                       care about.
+
+
+[amavis]
+group adm
+env.MUNIN_MKTEMP /bin/mktemp -p /tmp/ $1
+env.amavislog /var/log/mail.info
+
+[apt]
+user root
+
+[df*]
+env.exclude_re ^/btrfs ^/dev/shm  ^/run ^/sys
+
+[fail2ban]
+user root
+
+[fw_conntrack*]
+user root
+
+[fw_forwarded_local]
+user root
+
+[hddtemp_smartctl]
+user root
+
+[hddtemp2]
+user root
+
+[if_*]
+user root
+
+[if_err_*]
+user nobody
+
+[iostat]
+user root
+
+[ip_*]
+user root
+
+[munin_stats]
+user munin
+group munin
+
+[mysql*]
+user root
+env.mysqlopts --defaults-file=/etc/mysql/debian.cnf
+env.mysqluser root
+env.mysqlconnection DBI:mysql:mysql;mysql_read_default_file=/etc/mysql/debian.cnf
+
+[postfix_mailqueue]
+user postfix
+
+[postfix_mailstats]
+group adm
+
+[postfix_mailvolume]
+group adm
+env.logfile mail.log
+
+[postgres_*]
+user postgres
+env.PGUSER postgres
+env.PGPORT 5432
+
+[sendmail_*]
+user smmta
+
+[smart_*]
+user root
+
+[vlan*]
+user root
+
+[samba]
+user root
+
+[threads]
+user root
diff --git a/roles/base/munin-node/files/default/plugins.somesible/df b/roles/base/munin-node/files/default/plugins.somesible/df
new file mode 100644 (file)
index 0000000..0e595f8
--- /dev/null
@@ -0,0 +1,167 @@
+#!/usr/bin/perl -w
+# -*-  perl -*-
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+df - Munin plugin to monitor disk usage
+
+=head1 APPLICABLE SYSTEMS
+
+Every Linux system with df installed.
+
+=head1 CONFIGURATION
+
+The plugin excludes per default the following special, read-only or
+dynamically allocating file systems from graphing:
+
+  none unknown rootfs iso9660 squashfs udf romfs ramfs debugfs cgroup_root
+
+To change this set the environment variable "exclude" with a list of
+space separated fs types.  The environment variables "warning" and
+"critical" sets the percentage from which Munin starts to warn about
+the disk usage.
+
+This configuration snipplet is an example with the defaults:
+
+  [df]
+    env.exclude none unknown rootfs iso9660 squashfs udf romfs ramfs debugfs cgroup_root devtmpfs
+    env.warning 92
+    env.critical 98
+
+Put it in a file in /etc/munin/plugin-conf.d/ and restart the munin-node.
+
+You may specify filesystem specific warning and critical levels:
+
+    env._dev_sda2_warning 98
+    env._dev_sda2_critical 99
+
+Devices can be explicitly included or excluded based on their mountpoint or
+device name using the include_re and exclude_re environment variables.  These
+environment variables are parsed as whitespace separated regular expressions.
+For example, if you wish to ignore the filesystem on /dev/sda2 and all
+filesystems mounted under /var except /var/tmp, these rules would achieve this:
+
+    env.include_re ^/var/tmp$
+    env.exclude_re /dev/sda2 ^/var/
+
+Please note that these expressions are tried against both mountpoints and
+device names, therefore broad matches could potentially filter out desired
+devices.  Anchoring is also useful for avoiding false positives (as seen in the
+example), but not strictly necessary.  Testing with munin-run is always a good
+idea.
+
+Also note that a mountpoint that is excluded by filesystem type but included by
+RE will not be included.
+
+=head1 USAGE
+
+Link this plugin to /etc/munin/plugins/ and restart the munin-node.
+
+=head1 MAGIC MARKERS
+
+  #%# family=auto
+  #%# capabilities=autoconf
+
+=head1 BUGS
+
+Uses device names instead of mount points to identify mounted
+filesystems.
+
+=head1 AUTHOR
+
+Ingvar Hagelund
+
+=head1 LICENSE
+
+GPLv2
+
+=cut
+
+use Munin::Plugin;
+
+# For these devices use the mount point, the device is useless
+my %usemntpt = ( tmpfs => 1, none => 1, udev => 1, simfs => 1 );
+
+my $exclude = $ENV{'exclude'} || 'none unknown rootfs iso9660 squashfs udf romfs ramfs debugfs cgroup_root devtmpfs';
+my $dfopts  = "-P -l ".join(' -x ',('',split('\s+',$exclude)));
+
+my $mode = ($ARGV[0] or "print");
+
+# Compile REs from env
+my @include_re;
+if (defined $ENV{include_re}) {
+    foreach my $re (split m{\s+}, $ENV{include_re}) {
+        push @include_re, qr/$re/;
+    }
+}
+my @exclude_re;
+if (defined $ENV{exclude_re}) {
+    foreach my $re (split m{\s+}, $ENV{exclude_re}) {
+        push @exclude_re, qr/$re/;
+    }
+}
+
+sub skip {
+    my $name = shift;
+    my $mountpt = shift;
+
+    foreach my $re (@include_re) {
+        return 0 if ($name =~ $re or $mountpt =~ $re);
+    }
+
+    foreach my $re (@exclude_re) {
+        return 1 if ($name =~ $re or $mountpt =~ $re);
+    }
+
+    return 0;
+}
+
+if ($mode eq 'autoconf' ) {
+    if (`/usr/bin/perl $0` eq '' ) {
+        print "no (no devices to monitor)\n";
+    } else {
+        print "yes\n";
+    }
+    exit 0;
+}
+
+if ($mode eq 'config' ) {
+    # The headers
+    print "graph_title Disk usage in percent\n";
+    print "graph_args --upper-limit 100 -l 0\n";
+    print "graph_vlabel %\n";
+    print "graph_scale no\n";
+    print "graph_category disk\n";
+}
+
+# Read from df
+open (DF,"df $dfopts 2>/dev/null | tail -n+2 | sort -k6 |") or die "Unable to open pipe from df: $!";
+#<DF>; # Skip the header
+while (<DF>) {
+    next if m{//};
+
+    # Parse the output
+    my ($name, undef, $used, $avail, undef, $mountpt, undef) = split(/\s+/, $_, 7);
+
+    next if skip($name, $mountpt);
+
+    # Calculate percentage used
+    my $ps = 0;
+    $ps = ($used / ($used+$avail)) * 100 if $used;
+
+    $name = $mountpt; # if defined($usemntpt{$name}) && $usemntpt{$name};
+    $name = clean_fieldname($name);
+
+    if($mode eq 'config') {
+        print $name, ".label ", $mountpt, "\n";
+        print_thresholds($name,undef,undef,92,98);
+    } else {
+        print $name, ".value ", $ps, "\n";
+    }
+}
+close DF;
+
+# vim: ft=perl : sw=4 : ts=4 : et
diff --git a/roles/base/munin-node/files/default/plugins.somesible/fw_conntrackntp b/roles/base/munin-node/files/default/plugins.somesible/fw_conntrackntp
new file mode 100644 (file)
index 0000000..be56f12
--- /dev/null
@@ -0,0 +1,168 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+fw_conntrack - Plugin to monitor the number of tracked connections
+through a Linux 2.4/2.6 firewall
+
+=head1 CONFIGURATION
+
+This plugin must run with root privileges
+
+=head2 CONFIGURATION EXAMPLE
+
+/etc/munin/plugin-conf.d/global or other file in that dir must contain:
+
+ [fw_*]
+  user root
+
+=head1 NOTES
+
+ESTABLISHED+FIN_WAIT+TIME_WAIT+SYN_SENT+UDP are the most interesting
+connections.
+
+The total list also includes SYN_RECV, CLOSE, CLOSE_WAIT, LAST_ACK and
+LISTEN, but these were not (often) observed on my firewall.
+
+TOTAL is the total number of tracked connections.
+
+ASSURED and UNREPLIED connections are complementary subsets of
+ESTABLISHED.
+
+ASSURED is after ACK is seen after SYN_RECV.  Therefore ASSURED is
+plotted but not UNREPLIED.
+
+Note that the plugin depends on the netfilter "conntrack" userspace tool.
+It comes from http://conntrack-tools.netfilter.org/
+
+=head1 AUTHORS
+
+=over
+
+=item 2004.05.05: Initial version by Nicolai Langfeldt, Linpro AS, Oslo, Norway
+
+=item 2004.05.06: Enhanced to count NATed connections after input from Xavier on munin-users list
+
+=item 2011.09.23: Perl version by Alex Tomlins
+
+=back
+
+=head1 LICENSE
+
+GPL
+
+=head1 MAGIC MARKERS
+
+ #%# family=auto
+ #%# capabilities=autoconf
+
+=cut
+
+use strict;
+use Munin::Plugin;
+
+my $conntrack = '/usr/sbin/conntrack';
+my $nf_conntrack_file = '/proc/net/nf_conntrack';
+my $ip_conntrack_file = '/proc/net/ip_conntrack';
+my @conntrack_max_files = qw(
+        /proc/sys/net/nf_conntrack_max
+        /proc/sys/net/netfilter/nf_conntrack_max
+        /proc/sys/net/ipv4/ip_conntrack_max
+        /proc/sys/net/ipv4/netfilter/ip_conntrack_max
+);
+
+if ( defined($ARGV[0]) and $ARGV[0] eq "autoconf" ) {
+    if ( -x $conntrack or -r $nf_conntrack_file or -r $ip_conntrack_file) {
+        print "yes\n";
+    } else {
+        print "no (command $conntrack or file $nf_conntrack_file or file $ip_conntrack_file not found)\n";
+    }
+    exit 0;
+}
+
+if ( defined($ARGV[0]) and $ARGV[0] eq "config" ) {
+    print <<EOF;
+graph_title Connections through firewall
+graph_vlabel Connections
+graph_category network
+graph_args -l 0
+established.label Established
+established.type GAUGE
+established.draw AREA
+fin_wait.label FIN_WAIT
+fin_wait.type GAUGE
+fin_wait.draw STACK
+time_wait.label TIME_WAIT
+time_wait.type GAUGE
+time_wait.draw STACK
+syn_sent.label SYN_SENT
+syn_sent.type GAUGE
+syn_sent.draw STACK
+udp.label UDP connections
+udp.type GAUGE
+udp.draw STACK
+assured.label Assured
+assured.type GAUGE
+assured.draw LINE2
+nated.label NATed
+nated.type GAUGE
+nated.draw LINE1
+total.label Total
+total.type GAUGE
+total.graph no
+EOF
+    my $max;
+    foreach (@conntrack_max_files) {
+        if ( -r $_) {
+            chomp($max = `cat $_`);
+            last;
+        }
+    }
+    if ($max) {
+        print "total.warning ", $max * 8 / 10, "\n";
+        print "total.critical ", $max * 9 / 10, "\n";
+    }
+    exit 0;
+}
+
+my $command;
+if ( -x $conntrack) {
+    $command = "$conntrack -L -o extended -f ipv4 2>/dev/null | grep -e 'dport=123 ' -e 'src=185.144.161.170 '; $conntrack -L -o extended -f ipv6 2>/dev/null | grep -e 'dport=123 '";
+} elsif ( -r $nf_conntrack_file ) {
+    $command = "cat $nf_conntrack_file";
+} else {
+    $command = "cat $ip_conntrack_file";
+}
+
+my %state = (
+    'ESTABLISHED' => 0,
+    'FIN_WAIT' => 0,
+    'TIME_WAIT' => 0,
+    'SYN_SENT' => 0,
+    'UDP' => 0,
+    'ASSURED' => 0,
+    'NATTED' => 0,
+    'TOTAL' => 0
+);
+open CMD, "$command|";
+while (<CMD>) {
+    $state{'TOTAL'} ++;
+    $state{'UDP'} ++ if /udp /;
+    $state{'ASSURED'} ++ if /ASSURED/;
+    if (/tcp \s*\d+\s+\d+\s+(\S+)/) {
+         $state{$1} ++;
+    }
+    if (/src=(\S+)\s+dst=(\S+)\s+sport.*src=(\S+)\s+dst=(\S+)/) {
+        $state{'NATTED'} ++ if $1 ne $4 or $2 ne $3;
+    }
+}
+close CMD;
+
+print "established.value $state{'ESTABLISHED'}\n";
+print "fin_wait.value $state{'FIN_WAIT'}\n";
+print "time_wait.value $state{'TIME_WAIT'}\n";
+print "syn_sent.value $state{'SYN_SENT'}\n";
+print "udp.value $state{'UDP'}\n";
+print "assured.value $state{'ASSURED'}\n";
+print "nated.value $state{'NATTED'}\n";
+print "total.value $state{'TOTAL'}\n";
diff --git a/roles/base/munin-node/files/default/plugins.somesible/smart_rw b/roles/base/munin-node/files/default/plugins.somesible/smart_rw
new file mode 100644 (file)
index 0000000..bf0678b
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/bash
+################################################
+### Managed by someone's ansible provisioner ###
+################################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+
+
+if [ "$1" = "autoconf" ]; then
+    echo "yes"
+    exit 0
+fi
+
+
+if [ "$1" = "config" ]; then
+    echo "graph_title Lifetime bytes read and written"
+    echo "graph_info This graph shows the bytes read and written per disk as reported by SMART"
+    echo "graph_category disk"
+    echo "graph_vlabel Lifetime bytes read (-) / written (+)"
+    cd /sys/block/
+    for f in *; do
+        HD_NAME=$(lsblk -S "/dev/$f" -o MODEL,SERIAL | tail -q -n-1 | sed -Ee 's#-#_#g' -e's# #-#g')
+        if [ "-${HD_NAME}-" != "--" ]; then
+            echo "${HD_NAME}_r.draw LINE2"
+            echo "${HD_NAME}_r.min 0"
+            echo "${HD_NAME}_r.label ${HD_NAME} not displayed"
+            echo "${HD_NAME}_r.graph no"
+            echo "${HD_NAME}_w.draw LINE2"
+            echo "${HD_NAME}_w.min 0"
+            echo "${HD_NAME}_w.label /dev/${f}"
+            echo "${HD_NAME}_w.negative ${HD_NAME}_r"
+        fi
+    done
+#    echo "graph_printf %.0lf"
+#    echo "graph_scale no"
+    echo "graph_args -l 0"
+    exit 0
+fi
+
+
+cd /sys/block/
+for f in *; do
+    HD_NAME=$(lsblk -S "/dev/$f" -o MODEL,SERIAL | tail -q -n-1 | sed -Ee 's#-#_#g' -e's# #-#g')
+    if [ "-${HD_NAME}-" != "--" ]; then
+        echo "${HD_NAME}_w.value $(tail -q -n-1 "/var/lib/smartmontools/attrlog.${HD_NAME}.ata.csv" 2>/dev/null | sed -Ee 's/^.*\t241;[0-9]+;([0-9]+);.*$/\1*512/' | bc)"
+        echo "${HD_NAME}_r.value $(tail -q -n-1 "/var/lib/smartmontools/attrlog.${HD_NAME}.ata.csv" 2>/dev/null | sed -Ee 's/^.*\t242;[0-9]+;([0-9]+);.*$/\1*512/' | bc)"
+    fi
+done
+exit 0
diff --git a/roles/base/munin-node/files/default/plugins.somesible/threads b/roles/base/munin-node/files/default/plugins.somesible/threads
new file mode 100644 (file)
index 0000000..36a3080
--- /dev/null
@@ -0,0 +1,441 @@
+#!/bin/sh
+# -*- sh -*-
+#
+################################################
+### Managed by someone's ansible provisioner ###
+################################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+
+set -e
+
+: << =cut
+
+=head1 NAME
+
+threads - Plugin to monitor threads and thread states. - copy of threads, with another ps option. commented out all but linux.
+
+=head1 ABOUT
+
+This plugin requires munin-server version 1.2.5 or 1.3.3 (or higher).
+
+This plugin is backwards compatible with the old threads-plugins found on
+SunOS, Linux and *BSD (i.e. the history is preserved).
+
+All fields have colours associated with them which reflect the type of process
+(sleeping/idle = blue, running = green, stopped/zombie/dead = red, etc.)
+
+=head1 CONFIGURATION
+
+No configuration for this plugin.
+
+=head1 AUTHOR
+
+Copyright (C) 2006 Lars Strand
+
+=head1 LICENSE
+
+GNU General Public License, version 2
+
+=begin comment
+
+This file is part of Munin.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; version 2 dated June, 1991.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+=end comment
+
+=head1 MAGIC MARKERS
+
+=begin comment
+
+These magic markers are used by munin-node-configure when installing
+munin-node.
+
+=end comment
+
+ #%# family=auto
+ #%# capabilities=autoconf
+
+=cut
+
+# Search for program in $PATH unless predefined.
+awk=${awk:-awk}
+ps=${ps:-ps}
+
+# Find operating system
+OPERSYS=${OPERSYS:-$(uname | cut -f 1 -d _)}
+[ -z "$OPERSYS" ] && echo >&2 "Failed to detect environment via uname" && exit 1
+
+if [ "$1" = "autoconf" ]; then
+    case "$OPERSYS" in
+        Linux) #|SunOS|FreeBSD|OpenBSD|NetBSD|Darwin|CYGWIN) - some edit: disable all but linux.
+            if ! "$ps" >/dev/null 2>/dev/null; then
+                echo "no (ps=$ps failed)"
+            elif ! echo | "$awk" '{ print "Hei" }' >/dev/null 2>/dev/null; then
+                echo "no (awk=$awk failed)"
+            else
+                echo yes
+            fi
+            exit 0
+            ;;
+        *)
+            echo "no (unknown OS)"
+            exit 0
+            ;;
+    esac
+fi
+
+. "$MUNIN_LIBDIR/plugins/plugin.sh"
+
+# Define colours
+RUNNABLE='22ff22'         # Green
+SLEEPING='0022ff'         # Blue
+STOPPED='cc0000'          # Darker red
+ZOMBIE='990000'           # Darkest red
+UNINTERRUPTIBLE='ffa500'  # Orange
+IDLE='4169e1'             # Royal blue
+PAGING='00aaaa'           # Darker turquoise
+INTERRUPT='ff00ff'        # Fuchsia
+LOCK='ff3333'             # Lighter red
+RUNNING='00ff7f'          # Spring green
+DEAD='ff0000'             # Red
+SUSPENDED='ff1493'        # Deep pink
+TOTAL='c0c0c0'            # Silver
+
+# Taken from ps(1)
+# R - Linux, SunOS, FreeBSD, OpenBSD, NetBSD, OSX, HP-UX      (runable)
+# S - Linux, SunOS, FreeBSD*, OpenBSD*, NetBSD*, OSX*, HP-UX  (sleeping)
+# T - Linux, SunOS, FreeBSD, OpenBSD, NetBSD, OSX, HP-UX      (stopped)
+# Z - Linux, SunOS, FreeBSD, OpenBSD, NetBSD, OSX, HP-UX      (zombie/terminated)
+# D - Linux, FreeBSD, OpenBSD, NetBSD                         (uninterruptible)
+# I - FreeBSD, OpenBSD, NetBSD, OSX, HP-UX                    (idle/intermediate)
+# W - Linux*, FreeBSD*, HP-UX                                 (paging/interrupt/waiting)
+# L - FreeBSD                                                 (lock)
+# O - SunOS                                                   (running)
+# X - Linux, HP-UX*                                           (dead)
+# U - OSX, NetBSD*                                            (uninterruptible/suspended)
+# 0 - HP-UX                                                   (nonexistent)
+# *) Differ meaning
+
+if [ "$1" = "config" ]; then
+    echo "graph_title Threads"
+    echo "graph_info This graph shows the number of threads"
+    echo "graph_category system"
+    echo "graph_args --base 1000 -l 0"
+    echo "graph_vlabel Number of threads"
+
+    # OS specific flags
+    if [ "$OPERSYS" = "Linux" ]; then
+        echo "graph_order sleeping idle stopped zombie dead paging uninterruptible runnable threads"
+        echo "dead.label dead"
+        echo "dead.draw STACK"
+        echo "dead.colour $DEAD"
+        echo "dead.info The number of dead threads."
+        print_warning dead
+        print_critical dead
+        echo "paging.label paging"
+        echo "paging.draw STACK"
+        echo "paging.colour $PAGING"
+        echo "paging.info The number of paging threads (<2.6 kernels only)."
+        print_warning paging
+        print_critical paging
+
+    elif [ "$OPERSYS" = "SunOS" ]; then
+        echo "graph_order sleeping stopped zombie runnable running total"
+        echo "running.label running"
+        echo "running.draw STACK"
+        echo "running.colour $RUNNING"
+        echo "running.info The number of threads that are running on a processor."
+        print_warning running
+        print_critical running
+        # Be backwards compatible.
+        echo "total.label total"
+        echo "total.draw LINE1"
+        echo "total.colour $TOTAL"
+        echo "total.info The total number of threads."
+        print_warning total
+        print_critical total
+
+    elif [ "$OPERSYS" = "FreeBSD" ]; then
+        echo "graph_order sleeping idle stopped zombie lock uninterruptible interrupt runnable threads"
+        echo "lock.label lock"
+        echo "lock.draw STACK"
+        echo "lock.colour $LOCK"
+        echo "lock.info The number of threads that are waiting to acquire a lock."
+        print_warning lock
+        print_critical lock
+        echo "interrupt.label interrupt"
+        echo "interrupt.draw STACK"
+        echo "interrupt.colour $INTERRUPT"
+        echo "interrupt.info The number of idle interrupt threads."
+        print_warning interrupt
+        print_critical interrupt
+
+    elif [ "$OPERSYS" = "OpenBSD" ]; then
+        echo "graph_order sleeping idle stopped zombie uninterruptible runnable threads"
+
+    elif [ "$OPERSYS" = "NetBSD" ]; then
+        echo "graph_order sleeping idle stopped zombie uninterruptible suspended runnable threads"
+        echo "suspended.label suspended"
+        echo "suspended.draw STACK"
+        echo "suspended.colour $SUSPENDED"
+        echo "suspended.info The number of threads that are suspended."
+        print_warning suspended
+        print_critical suspended
+
+    elif [ "$OPERSYS" = "Darwin" ]; then
+        echo "graph_order sleeping idle stopped zombie uninterruptible running threads"
+        echo "uninterruptible.label uninterruptible"
+        echo "uninterruptible.draw STACK"
+        echo "uninterruptible.colour $UNINTERRUPTIBLE"
+        echo "uninterruptible.info The number of uninterruptible threads (usually IO)."
+        print_warning uninterruptible
+        print_critical uninterruptible
+    elif [ "$OPERSYS" = "HP-UX" ]; then
+        echo "graph_order sleeping intermediate stopped terminated waiting growing nonexistent runnable threads"
+        echo "waiting.label waiting"
+        echo "waiting.draw STACK"
+        echo "waiting.colour $INTERRUPT"
+        echo "waiting.info The number of waiting threads."
+        print_warning waiting
+        print_critical waiting
+        echo "terminated.label terminated"
+        echo "terminated.draw STACK"
+        echo "terminated.colour $ZOMBIE"
+        echo "terminated.info The number of threads that are terminated."
+        print_warning terminated
+        print_critical terminated
+        echo "growing.label growing"
+        echo "growing.draw STACK"
+        echo "growing.colour $RUNNING"
+        echo "growing.info The number of growing threads."
+        print_warning growing
+        print_critical growing
+        echo "intermediate.label intermediate"
+        echo "intermediate.draw STACK"
+        echo "intermediate.colour $IDLE"
+        echo "intermediate.info The number of intermediate threads."
+        print_warning intermediate
+        print_critical intermediate
+        echo "nonexistent.label nonexistent"
+        echo "nonexistent.draw STACK"
+        echo "nonexistent.colour $LOCK"
+        echo "nonexistent.info The number of nonexistent threads."
+        print_warning nonexistent
+        print_critical nonexistent
+    fi
+
+    # Common flags for some OS
+    if [ "$OPERSYS" = "FreeBSD" ] || [ "$OPERSYS" = "OpenBSD" ] ||
+    [ "$OPERSYS" = "NetBSD" ] || [ "$OPERSYS" = "Darwin" ]; then
+        echo "idle.label idle"
+        echo "idle.draw STACK"
+        echo "idle.colour $IDLE"
+        echo "idle.info The number of threads that are idle (sleeping for longer than about 20 seconds)."
+        print_warning idle
+        print_critical idle
+        echo "sleeping.label sleeping"
+        echo "sleeping.draw AREA"
+        echo "sleeping.colour $SLEEPING"
+        echo "sleeping.info The number of threads that are sleeping for less than about 20 seconds."
+        print_warning sleeping
+        print_critical sleeping
+    elif [ "$OPERSYS" = "Linux" ]; then
+        echo "idle.label idle"
+        echo "idle.draw STACK"
+        echo "idle.colour $IDLE"
+        echo "idle.info The number of idle kernel threads (>= 4.2 kernels only)."
+        print_warning idle
+        print_critical idle
+        echo "sleeping.label sleeping"
+        echo "sleeping.draw AREA"
+        echo "sleeping.colour $SLEEPING"
+        echo "sleeping.info The number of sleeping threads."
+        print_warning sleeping
+        print_critical sleeping
+    elif [ "$OPERSYS" = "SunOS" ] || [ "$OPERSYS" = "HP-UX" ]; then
+        echo "sleeping.label sleeping"
+        echo "sleeping.draw AREA"
+        echo "sleeping.colour $SLEEPING"
+        echo "sleeping.info The number of sleeping threads."
+        print_warning sleeping
+        print_critical sleeping
+    fi
+
+    if [ "$OPERSYS" = "Linux" ] || [ "$OPERSYS" = "FreeBSD" ] ||
+    [ "$OPERSYS" = "OpenBSD" ] || [ "$OPERSYS" = "NetBSD" ]; then
+        echo "uninterruptible.label uninterruptible"
+        echo "uninterruptible.draw STACK"
+        echo "uninterruptible.colour $UNINTERRUPTIBLE"
+        echo "uninterruptible.info The number of uninterruptible threads (usually IO)."
+        print_warning uninterruptible
+        print_critical uninterruptible
+    fi
+
+    # Common (non-cygwin) flags
+    if [ "$OPERSYS" != "CYGWIN" ]; then
+        echo "stopped.label stopped"
+        echo "stopped.draw STACK"
+        echo "stopped.colour $STOPPED"
+        echo "stopped.info The number of stopped or traced threads."
+        print_warning stopped
+        print_critical stopped
+
+        echo "runnable.label runnable"
+        echo "runnable.draw STACK"
+        echo "runnable.colour $RUNNABLE"
+        echo "runnable.info The number of runnable threads (on the run queue)."
+        print_warning runnable
+        print_critical runnable
+    fi
+
+    if [ "$OPERSYS" != "CYGWIN" ] && [ "$OPERSYS" != "HP-UX" ]; then
+        echo "zombie.label zombie"
+        echo "zombie.draw STACK"
+        echo "zombie.colour $ZOMBIE"
+        echo "zombie.info The number of defunct ('zombie') threads (process terminated and parent not waiting)."
+        print_warning zombie
+        print_critical zombie
+    fi
+
+    if [ "$OPERSYS" != "SunOS" ]; then
+    # Not using 'graph_total' due to backwards compability. SunOS uses 'total'.
+        #echo 'graph_total total'
+        echo "threads.label total"
+        echo "threads.draw LINE1"
+        echo "threads.colour $TOTAL"
+        echo "threads.info The total number of threads."
+    print_warning threads
+    print_critical threads
+    fi
+
+    exit 0
+fi
+
+if [ "$OPERSYS" = "Linux" ]; then
+    # shellcheck disable=SC2016
+    "$ps" --no-header -eTo s | "$awk" '
+{ threads++; stat[$1]++ }
+END {
+print "threads.value "        0+threads;
+print "uninterruptible.value "  0+stat["D"];
+print "runnable.value "         0+stat["R"];
+print "sleeping.value "         0+stat["S"];
+print "idle.value "             0+stat["I"];
+print "stopped.value "          0+stat["T"];
+print "paging.value "           0+stat["W"];
+print "dead.value "             0+stat["X"];
+print "zombie.value "           0+stat["Z"];
+}'
+
+#elif [ "$OPERSYS" = "SunOS" ]; then
+#    # shellcheck disable=SC2016
+#    "$ps" -e -o s | "$awk" '
+#{ total++; stat[$1]++ }
+#END {
+#print "total.value "    0+total;
+#print "running.value "  0+stat["O"];
+#print "sleeping.value " 0+stat["S"];
+#print "runnable.value " 0+stat["R"];
+#print "stopped.value "  0+stat["T"];
+#print "zombie.value "   0+stat["Z"];
+#}'
+#elif [ "$OPERSYS" = "FreeBSD" ]; then
+#    # shellcheck disable=SC2016
+#    "$ps" -axo state= | sed -e 's/^\(.\).*/\1/' | "$awk" '
+#{ threads++; stat[$1]++ }
+#END {
+#print "threads.value "        0+threads;
+#print "uninterruptible.value "  0+stat["D"];
+#print "idle.value "             0+stat["I"];
+#print "lock.value "             0+stat["G"];
+#print "runnable.value "         0+stat["R"];
+#print "sleeping.value "         0+stat["S"];
+#print "stopped.value "          0+stat["T"];
+#print "interrupt.value "        0+stat["W"];
+#print "zombie.value "           0+stat["Z"];
+#}'
+#elif [ "$OPERSYS" = "OpenBSD" ]; then
+#    # First line is header. Remove it.
+#    # shellcheck disable=SC2016
+#    "$ps" -axo state= | sed '1d' | sed -e 's/^\(.\).*/\1/' | "$awk" '
+#{ threads++; stat[$1]++ }
+#END {
+#print "threads.value "        0+threads;
+#print "uninterruptible.value "  0+stat["D"];
+#print "idle.value "             0+stat["I"];
+#print "runnable.value "         0+stat["R"];
+#print "sleeping.value "         0+stat["S"];
+#print "stopped.value "          0+stat["T"];
+#print "zombie.value "           0+stat["Z"];
+#}'
+#elif [ "$OPERSYS" = "NetBSD" ]; then
+#    # First line is header. Remove it.
+#    # shellcheck disable=SC2016
+#    "$ps" -axo state= | sed '1d' | sed -e 's/^\(.\).*/\1/' | "$awk" '
+#{ threads++; stat[$1]++ }
+#END {
+#print "threads.value "        0+threads;
+#print "uninterruptible.value "  0+stat["D"];
+#print "idle.value "             0+stat["I"];
+#print "suspended.value "        0+stat["U"];
+#print "runnable.value "         0+stat["R"];
+#print "sleeping.value "         0+stat["S"];
+#print "stopped.value "          0+stat["T"];
+#print "zombie.value "           0+stat["Z"];
+#}'
+#
+#elif [ "$OPERSYS" = "Darwin" ]; then
+#    # First line is header. Remove it.
+#    # shellcheck disable=SC2016
+#    "$ps" -axo state= | sed '1d' | sed -e 's/^\(.\).*/\1/' | "$awk" '
+#{ threads++; stat[$1]++ }
+#END {
+#print "threads.value "        0+threads;
+#print "uninterruptible.value "  0+stat["U"];
+#print "idle.value "             0+stat["I"];
+#print "runnable.value "         0+stat["R"];
+#print "sleeping.value "         0+stat["S"];
+#print "stopped.value "          0+stat["T"];
+#print "zombie.value "           0+stat["Z"];
+#}'
+#
+#elif [ "$OPERSYS" = "CYGWIN" ]; then
+#    # First line is header. Remove it. Also remove WINPID duplicates.
+#    # shellcheck disable=SC2016
+#    "$ps" -aW | sed '1d' | cut -c 30-36 | sort -u | "$awk" '
+#{ threads++; }
+#END {
+#print "threads.value "        0+threads;
+#}'
+#
+#elif [ "$OPERSYS" = "HP-UX" ]; then
+#    # First line is header. Remove it.
+#    # shellcheck disable=SC2016
+#    "$ps" -el | sed '1d' | "$awk" '{print $2}' | "$awk" '
+#{ threads++; stat[$1]++ }
+#END {
+#print "threads.value "        0+threads;
+#print "nonexistent.value "      0+stat["0"];
+#print "sleeping.value "         0+stat["S"];
+#print "waiting.value "          0+stat["W"];
+#print "runnable.value "         0+stat["R"];
+#print "intermediate.value "     0+stat["I"];
+#print "terminated.value "       0+stat["Z"];
+#print "stopped.value "          0+stat["T"];
+#print "growing.value "          0+stat["X"];
+#}'
+
+fi
diff --git a/roles/base/munin-node/handlers/main.yml b/roles/base/munin-node/handlers/main.yml
new file mode 100644 (file)
index 0000000..75c7f3c
--- /dev/null
@@ -0,0 +1,23 @@
+#####################################
+### someone's ansible provisioner ###
+#####################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+---
+- name: restart munin-node.service
+  systemd:
+    name: munin-node.service
+    daemon_reload: yes
+    state: restarted
+  listen: restart munin-node.service and munin-async.service
+  ignore_errors: yes
+
+
+- name: restart munin-async.service
+  systemd:
+    name: munin-async.service
+    daemon_reload: yes
+    state: restarted
+  listen: restart munin-node.service and munin-async.service
+  ignore_errors: yes
diff --git a/roles/base/munin-node/tasks/main.yml b/roles/base/munin-node/tasks/main.yml
new file mode 100644 (file)
index 0000000..b9dc5ed
--- /dev/null
@@ -0,0 +1,171 @@
+#####################################
+### someone"s ansible provisioner ###
+#####################################
+# Part of: https://git.somenet.org/root/pub/somesible.git
+# 2017-2025 by someone <someone@somenet.org>
+#
+# Install munin-node and munin-async.
+#
+---
+- name: install munin
+  apt:
+    pkg:
+    - munin-async
+    - munin-node
+    - munin-plugins-extra
+    state: present
+    policy_rc_d: 101
+  when: munin_node_setup | bool
+  tags: "online"
+
+
+- name: create munin-plugins dir
+  file:
+    path: "/opt/somesible/munin-plugins"
+    state: directory
+    mode: 0755
+    owner: "root"
+    group: "root"
+  when: munin_node_setup | bool
+
+
+- name: copy custom plugins
+  copy:
+    src:  "{{item.src}}"
+    dest: "/opt/somesible/munin-plugins/{{item.path}}"
+    mode: 0755
+    owner: "root"
+    group: "root"
+  with_filetree:
+    - "{{lookup('env','PWD')}}/host_files/{{inventory_hostname}}/{{role_name}}/plugins.somesible/"
+    - "{{lookup('env','PWD')}}/group_files/{{group_files_group}}/{{role_name}}/plugins.somesible/"
+    - "{{lookup('env','PWD')}}/group_files/all/{{role_name}}/plugins.somesible/"
+    - "default/plugins.somesible/"
+  when: munin_node_setup | bool and item.state == "file"
+  notify: restart munin-node.service and munin-async.service
+
+
+- name: list files in /etc/munin/plugins
+  shell: "ls -1 /etc/munin/plugins"
+  register: contents
+  when: munin_node_setup | bool
+  changed_when: False
+
+
+- name: remove unmanaged files in /etc/munin/plugins
+  file:
+    path: "/etc/munin/plugins/{{item}}"
+    state: absent
+  with_items: "{{contents.stdout_lines}}"
+  when: munin_node_setup | bool and item not in munin_node_plugins.keys() | list and item not in munin_node_plugins_extra.keys() | list
+  notify: restart munin-node.service and munin-async.service
+
+
+- name: symlink new files into /etc/munin/plugins
+  file:
+    dest: "/etc/munin/plugins/{{item.key}}"
+    src: "{{item.value}}"
+    force: yes
+    owner: "root"
+    group: "root"
+    state: link
+  loop: "{{ lookup('dict', munin_node_plugins|combine(munin_node_plugins_extra)) }}"
+  when: munin_node_setup | bool
+  notify: restart munin-node.service and munin-async.service
+
+
+- name: copy plugin-conf.d/munin-node
+  copy:
+    src:  "{{item}}"
+    dest: "/etc/munin/plugin-conf.d/munin-node"
+    mode: 0644
+    owner: "root"
+    group: "root"
+  with_first_found:
+    - "{{lookup('env','PWD')}}/host_files/{{inventory_hostname}}/{{role_name}}/plugin-confd.munin-node.conf"
+    - "{{lookup('env','PWD')}}/group_files/{{group_files_group}}/{{role_name}}/plugin-confd.munin-node.conf"
+    - "{{lookup('env','PWD')}}/group_files/all/{{role_name}}/plugin-confd.munin-node.conf"
+    - "default/plugin-confd.munin-node.conf"
+  when: munin_node_setup | bool
+  notify: restart munin-node.service and munin-async.service
+
+
+- name: copy munin-node.service to /etc/systemd/system/
+  copy:
+    src: "{{item}}"
+    dest: "/etc/systemd/system/munin-node.service"
+    mode: 0644
+    owner: "root"
+    group: "root"
+  with_first_found:
+    - "{{lookup('env','PWD')}}/host_files/{{inventory_hostname}}/{{role_name}}/munin-node.service"
+    - "{{lookup('env','PWD')}}/group_files/{{group_files_group}}/{{role_name}}/munin-node.service"
+    - "{{lookup('env','PWD')}}/group_files/all/{{role_name}}/munin-node.service"
+    - "default/munin-node.service"
+  when: munin_node_setup | bool
+  notify: restart munin-node.service and munin-async.service
+
+
+- name: create munin-async home and fix ownership
+  file:
+    path: "/var/lib/munin-async/"
+    state: directory
+    recurse: yes
+    owner: "munin-async"
+    group: "munin-async"
+  when: munin_node_setup | bool
+
+
+- name: create .ssh-dir
+  file:
+    path: "/var/lib/munin-async/.ssh"
+    state: directory
+    mode: 0700
+    owner: "munin-async"
+    group: "munin-async"
+  when: munin_node_setup | bool
+
+
+- name: copy authorized_keys to /var/lib/munin-async/.ssh/authorized_keys
+  copy:
+    src: "{{item}}"
+    dest: "/var/lib/munin-async/.ssh/authorized_keys"
+    mode: 0600
+    owner: "munin-async"
+    group: "munin-async"
+  with_first_found:
+    - "{{lookup('env','PWD')}}/host_files/{{inventory_hostname}}/{{role_name}}/authorized_keys"
+    - "{{lookup('env','PWD')}}/group_files/{{group_files_group}}/{{role_name}}/authorized_keys"
+    - "{{lookup('env','PWD')}}/group_files/all/{{role_name}}/authorized_keys"
+    - "default/authorized_keys"
+  when: munin_node_setup | bool
+
+
+- name: copy munin-async.service to /etc/systemd/system/
+  copy:
+    src: "{{item}}"
+    dest: "/etc/systemd/system/munin-async.service"
+    mode: 0644
+    owner: "root"
+    group: "root"
+  with_first_found:
+    - "{{lookup('env','PWD')}}/host_files/{{inventory_hostname}}/{{role_name}}/munin-async.service"
+    - "{{lookup('env','PWD')}}/group_files/{{group_files_group}}/{{role_name}}/munin-async.service"
+    - "{{lookup('env','PWD')}}/group_files/all/{{role_name}}/munin-async.service"
+    - "default/munin-async.service"
+  when: munin_node_setup | bool
+  notify: restart munin-node.service and munin-async.service
+
+
+- name: enable and start munin-node.service
+  include_role: name="base/systemd/enable-and-start"
+  vars:
+    service_name: munin-node.service
+  when: munin_node_setup | bool
+
+
+- name: enable and start munin-async.service
+  include_role: name="base/systemd/enable-and-start"
+  vars:
+    service_name: munin-async.service
+  when: munin_node_setup | bool