diff --git a/playbooks/ns.yml b/playbooks/ns.yml
new file mode 100644
index 0000000..e29c441
--- /dev/null
+++ b/playbooks/ns.yml
@@ -0,0 +1,6 @@
+- name: Configure bind9 name server
+ hosts: all
+ tasks:
+ - name: Configure services to install in the server
+ import_role:
+ name: ns
\ No newline at end of file
diff --git a/playbooks/vpn-ipsec.yml b/playbooks/vpn-ipsec.yml
new file mode 100644
index 0000000..aa9b29a
--- /dev/null
+++ b/playbooks/vpn-ipsec.yml
@@ -0,0 +1,6 @@
+- name: Configure IPsec StrongSwan
+ hosts: all
+ tasks:
+ - name: Configure services to install in the server
+ import_role:
+ name: ipsec
\ No newline at end of file
diff --git a/roles/ceph/files/nrpe.cfg b/roles/ceph/files/nrpe.d/95-ceph.cfg
similarity index 95%
rename from roles/ceph/files/nrpe.cfg
rename to roles/ceph/files/nrpe.d/95-ceph.cfg
index 76d252f..afc0035 100644
--- a/roles/ceph/files/nrpe.cfg
+++ b/roles/ceph/files/nrpe.d/95-ceph.cfg
@@ -1,4 +1,4 @@
-command[check_total_procs]=/usr/lib/nagios/plugins/check_procs -w 700 -c 1000
+command[check_total_procs]=/usr/lib/nagios/plugins/check_procs -w 1300 -c 1500
command[check_chrony]=/usr/lib/nagios/plugins/check_chrony 1 2
command[check_smartdisk]=/etc/nagios/plugins/check_smartdisk.sh /dev/sda /dev/sdb
command[check_raid]=/usr/lib/nagios/plugins/check_raid
diff --git a/roles/ceph/files/plugins/check_ceph_mon b/roles/ceph/files/plugins/check_ceph_mon
new file mode 100755
index 0000000..f8decea
--- /dev/null
+++ b/roles/ceph/files/plugins/check_ceph_mon
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013 Catalyst IT http://www.catalyst.net.nz
+# Copyright (c) 2015 SWITCH http://www.switch.ch
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from __future__ import print_function
+import argparse
+import socket
+import os
+import re
+import subprocess
+import sys
+import json
+
+__version__ = '1.5.0'
+
+# default ceph values
+CEPH_EXEC = '/usr/bin/ceph'
+CEPH_COMMAND = 'quorum_status'
+
+# nagios exit code
+STATUS_OK = 0
+STATUS_WARNING = 1
+STATUS_ERROR = 2
+STATUS_UNKNOWN = 3
+
+##
+# ceph quorum_status output example
+##
+ceph_quorum_status_output_example = '''{
+ "quorum_leader_name" : "s0001",
+ "monmap" : {
+ "mons" : [
+ {
+ "name" : "s0001",
+ "addr" : "[2001:620:5ca1:8000::1001]:6789/0",
+ "rank" : 0
+ },
+ {
+ "name" : "s0003",
+ "addr" : "[2001:620:5ca1:8000::1003]:6789/0",
+ "rank" : 1
+ }
+ ],
+ "created" : "2014-12-15 08:28:35.153650",
+ "epoch" : 2,
+ "modified" : "2014-12-15 08:28:40.371878",
+ "fsid" : "22348d2b-b69d-46cc-9a79-ca93cd6bae84"
+ },
+ "quorum_names" : [
+ "s0001",
+ "s0003"
+ ],
+ "quorum" : [
+ 0,
+ 1
+ ],
+ "election_epoch" : 24
+}'''
+
+def main():
+
+ # parse args
+ parser = argparse.ArgumentParser(description="'ceph quorum_status' nagios plugin.")
+ parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_EXEC)
+ parser.add_argument('-c','--conf', help='alternative ceph conf file')
+ parser.add_argument('-m','--monaddress', help='ceph monitor to use for queries (address[:port])')
+ parser.add_argument('-i','--id', help='ceph client id')
+ parser.add_argument('-k','--keyring', help='ceph client keyring file')
+ parser.add_argument('-V','--version', help='show version and exit', action='store_true')
+ parser.add_argument('-I','--monid', help='mon ID to be checked for availability')
+ args = parser.parse_args()
+
+ if args.version:
+ print('version %s' % __version__)
+ return STATUS_OK
+
+ # validate args
+ ceph_exec = args.exe if args.exe else CEPH_EXEC
+ if not os.path.exists(ceph_exec):
+ print("MON ERROR: ceph executable '%s' doesn't exist" % ceph_exec)
+ return STATUS_UNKNOWN
+
+ if args.conf and not os.path.exists(args.conf):
+ print("MON ERROR: ceph conf file '%s' doesn't exist" % args.conf)
+ return STATUS_UNKNOWN
+
+ if args.keyring and not os.path.exists(args.keyring):
+ print("MON ERROR: keyring file '%s' doesn't exist" % args.keyring)
+ return STATUS_UNKNOWN
+
+ if not args.monid:
+ print("MON ERROR: no MON ID given, use -I/--monid parameter")
+ return STATUS_UNKNOWN
+
+ # build command
+ ceph_cmd = [ceph_exec]
+ if args.monaddress:
+ ceph_cmd.append('-m')
+ ceph_cmd.append(args.monaddress)
+ if args.conf:
+ ceph_cmd.append('-c')
+ ceph_cmd.append(args.conf)
+ if args.id:
+ ceph_cmd.append('--id')
+ ceph_cmd.append(args.id)
+ if args.keyring:
+ ceph_cmd.append('--keyring')
+ ceph_cmd.append(args.keyring)
+ ceph_cmd.append(CEPH_COMMAND)
+
+ # exec command
+ p = subprocess.Popen(ceph_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
+ output, err = p.communicate()
+
+ if p.returncode != 0 or not output:
+ print("MON ERROR: %s" % err)
+ return STATUS_ERROR
+
+ # load json output and parse
+ quorum_status = False
+ try:
+ quorum_status = json.loads(output)
+ except Exception as e:
+ print("MON ERROR: could not parse '%s' output: %s: %s" % (CEPH_COMMAND,output,e))
+ return STATUS_UNKNOWN
+
+ #print "XXX: quorum_status['quorum_names']:", quorum_status['quorum_names']
+
+ # do our checks
+ is_monitor = False
+ for mon in quorum_status['monmap']['mons']:
+ if mon['name'] == args.monid:
+ is_monitor = True
+ if not is_monitor:
+ print("MON WARN: mon '%s' is not in monmap: %s" % (args.monid,quorum_status['monmap']['mons']))
+ return STATUS_WARNING
+
+ in_quorum = args.monid in quorum_status['quorum_names']
+ if in_quorum:
+ print("MON OK")
+ return STATUS_OK
+ else:
+ print("MON WARN: no MON '%s' found in quorum" % args.monid)
+ return STATUS_WARNING
+
+# main
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/roles/ceph/files/plugins/check_ceph_osd b/roles/ceph/files/plugins/check_ceph_osd
new file mode 100755
index 0000000..2ee9de6
--- /dev/null
+++ b/roles/ceph/files/plugins/check_ceph_osd
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013 Catalyst IT http://www.catalyst.net.nz
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# 1.5.2 (2019-06-16) Martin Seener: fixed regex to work with Ceph Nautilus (14.2.x)
+
+from __future__ import print_function
+import argparse
+import os
+import re
+import subprocess
+import sys
+import socket
+
+__version__ = '1.5.2'
+
+# default ceph values
+CEPH_COMMAND = '/usr/bin/ceph'
+
+# nagios exit code
+STATUS_OK = 0
+STATUS_WARNING = 1
+STATUS_ERROR = 2
+STATUS_UNKNOWN = 3
+
+def main():
+
+ # parse args
+ parser = argparse.ArgumentParser(description="'ceph osd' nagios plugin.")
+ parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_COMMAND)
+ parser.add_argument('-c','--conf', help='alternative ceph conf file')
+ parser.add_argument('-m','--monaddress', help='ceph monitor address[:port]')
+ parser.add_argument('-i','--id', help='ceph client id')
+ parser.add_argument('-k','--keyring', help='ceph client keyring file')
+ parser.add_argument('-V','--version', help='show version and exit', action='store_true')
+ parser.add_argument('-H','--host', help='osd host', required=True)
+ parser.add_argument('-I','--osdid', help='osd id', required=False)
+ parser.add_argument('-C','--crit', help='Number of failed OSDs to trigger critical (default=2)',type=int,default=2, required=False)
+ parser.add_argument('-o','--out', help='check osds that are set OUT', default=False, action='store_true', required=False)
+ args = parser.parse_args()
+
+ # validate args
+ ceph_exec = args.exe if args.exe else CEPH_COMMAND
+ if not os.path.exists(ceph_exec):
+ print("OSD ERROR: ceph executable '%s' doesn't exist" % ceph_exec)
+ return STATUS_UNKNOWN
+
+ if args.version:
+ print('version %s' % __version__)
+ return STATUS_OK
+
+ if args.conf and not os.path.exists(args.conf):
+ print("OSD ERROR: ceph conf file '%s' doesn't exist" % args.conf)
+ return STATUS_UNKNOWN
+
+ if args.keyring and not os.path.exists(args.keyring):
+ print("OSD ERROR: keyring file '%s' doesn't exist" % args.keyring)
+ return STATUS_UNKNOWN
+
+ if not args.osdid:
+ args.osdid = '[^ ]*'
+
+ if not args.host:
+ print("OSD ERROR: no OSD hostname given")
+ return STATUS_UNKNOWN
+
+ try:
+ addrinfo = socket.getaddrinfo(args.host, None, 0, socket.SOCK_STREAM)
+ args.host = addrinfo[0][-1][0]
+ if addrinfo[0][0] == socket.AF_INET6:
+ args.host = "[%s]" % args.host
+ except:
+ print('OSD ERROR: could not resolve %s' % args.host)
+ return STATUS_UNKNOWN
+
+
+ # build command
+ ceph_cmd = [ceph_exec]
+ if args.monaddress:
+ ceph_cmd.append('-m')
+ ceph_cmd.append(args.monaddress)
+ if args.conf:
+ ceph_cmd.append('-c')
+ ceph_cmd.append(args.conf)
+ if args.id:
+ ceph_cmd.append('--id')
+ ceph_cmd.append(args.id)
+ if args.keyring:
+ ceph_cmd.append('--keyring')
+ ceph_cmd.append(args.keyring)
+ ceph_cmd.append('osd')
+ ceph_cmd.append('dump')
+
+ # exec command
+ p = subprocess.Popen(ceph_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
+ output, err = p.communicate()
+ output = output.decode('utf8')
+
+ if err or not output:
+ print("OSD ERROR: %s" % err)
+ return STATUS_ERROR
+
+ # escape IPv4 host address
+ osd_host = args.host.replace('.', '\.')
+ # escape IPv6 host address
+ osd_host = osd_host.replace('[', '\[')
+ osd_host = osd_host.replace(']', '\]')
+ up = re.findall(r"^(osd\.%s) up.*%s:" % (args.osdid, osd_host), output, re.MULTILINE)
+ if args.out:
+ down = re.findall(r"^(osd\.%s) down.*%s:" % (args.osdid, osd_host), output, re.MULTILINE)
+ down_in = re.findall(r"^(osd\.%s) down[ ]+in.*%s:" % (args.osdid, osd_host), output, re.MULTILINE)
+ down_out = re.findall(r"^(osd\.%s) down[ ]+out.*%s:" % (args.osdid, osd_host), output, re.MULTILINE)
+ else:
+ down = re.findall(r"^(osd\.%s) down[ ]+in.*%s:" % (args.osdid, osd_host), output, re.MULTILINE)
+ down_in = down
+ down_out = re.findall(r"^(osd\.%s) down[ ]+out.*%s:" % (args.osdid, osd_host), output, re.MULTILINE)
+
+ if down:
+ print("OSD %s: Down OSD%s on %s: %s" % ('CRITICAL' if len(down)>=args.crit else 'WARNING' ,'s' if len(down)>1 else '', args.host, " ".join(down)))
+ print("Up OSDs: " + " ".join(up))
+ print("Down+In OSDs: " + " ".join(down_in))
+ print("Down+Out OSDs: " + " ".join(down_out))
+ print("| 'osd_up'=%d 'osd_down_in'=%d;;%d 'osd_down_out'=%d;;%d" % (len(up), len(down_in), args.crit, len(down_out), args.crit))
+ if len(down)>=args.crit:
+ return STATUS_ERROR
+ else:
+ return STATUS_WARNING
+
+ if up:
+ print("OSD OK")
+ print("Up OSDs: " + " ".join(up))
+ print("Down+In OSDs: " + " ".join(down_in))
+ print("Down+Out OSDs: " + " ".join(down_out))
+ print("| 'osd_up'=%d 'osd_down_in'=%d;;%d 'osd_down_out'=%d;;%d" % (len(up), len(down_in), args.crit, len(down_out), args.crit))
+ return STATUS_OK
+
+ print("OSD WARN: no OSD.%s found on host %s" % (args.osdid, args.host))
+ return STATUS_WARNING
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/roles/ceph/files/plugins/check_chrony b/roles/ceph/files/plugins/check_chrony
new file mode 100755
index 0000000..1d220f4
--- /dev/null
+++ b/roles/ceph/files/plugins/check_chrony
@@ -0,0 +1,128 @@
+#!/usr/bin/env perl
+#===============================================================================
+# DESCRIPTION: Icinga2 / Nagios Check for chrony time sync status and offset
+#
+# OPTIONS: -h : Help
+# -w [warning threshold in seconds]
+# -c [critical threshold in seconds]
+#
+# REQUIREMENTS: Chrony, perl version 5.10.1+
+#
+# AUTHOR: Dennis Ullrich (request@decstasy.de)
+#
+# BUGS ETC: https://github.com/Decstasy/check_chrony
+#
+# LICENSE: GPL v3 (GNU General Public License, Version 3)
+# see https://www.gnu.org/licenses/gpl-3.0.txt
+#===============================================================================
+
+use 5.10.1;
+use strict;
+use warnings;
+use utf8;
+use Getopt::Std;
+
+#
+# Variables
+#
+my $chronyDaemonName = "chronyd";
+my $leapOk = "Normal";
+
+my $rc = 3;
+my $msg= "";
+my $perfdata = "";
+
+#
+# Subroutines
+#
+
+sub help {
+ print "check_chrony [options]
+ -w [warning threshold in seconds]
+ -c [critical threshold in seconds]
+ e.g.: check_chrony -w 0.6 -c 2\n";
+ exit(3);
+}
+
+# Script exit with Nagios / Icinga typical output
+sub _exit {
+ my ( $return, $line ) = @_;
+ my @state = ( "OK", "WARNING", "CRITICAL", "UNKNOWN" );
+ print "$state[$return]: $line\n";
+ exit( $return );
+}
+
+# Checks if a process with $_[0] as name exists
+sub proc_exists {
+ my $PID = `ps -C $_[0] -o pid=`;
+ if ( ${^CHILD_ERROR_NATIVE} == 0 ){
+ return 1;
+ }
+ return 0;
+}
+
+#
+# Options
+#
+
+my %options=();
+getopts( "hw:c:", \%options );
+
+# Check input
+if ( keys %options == 0 || defined $options{h} ){
+ &help;
+}
+
+for my $key ( keys %options ){
+ if ( $options{$key} !~ /^[\d\.]+$/ ){
+ &_exit( 3, "Value of option -$key is not a valid number!" );
+ }
+}
+
+#
+# Check chrony process
+#
+
+&_exit( 2, "$chronyDaemonName is not running!" ) if not &proc_exists( $chronyDaemonName );
+
+#
+# Get tracking data
+#
+
+my $chronyOutput = `chronyc tracking`;
+&_exit( 3, "Chronyc tracking command failed!" ) if ${^CHILD_ERROR_NATIVE} != 0;
+
+my ( $offset, $dir ) = $chronyOutput =~ /(?:System\stime)[^\d]+([\d\.]+)(?:.*?)(fast|slow)/;
+my ( $leap ) = $chronyOutput =~ /(?:Leap)[^\:]+(?::\s+)([\w\h]+)/;
+
+#
+# Check stuff
+#
+
+# Check offset
+if ( $offset >= $options{"c"} ){
+ $rc = 2; # Critical
+}
+elsif ( $offset >= $options{"w"} ){
+ $rc = 1; # Warning
+}
+else {
+ $rc = 0; # Ok
+}
+
+# Prepare offset performace data
+$offset = $dir =~ "slow" ? "-$offset" : "+$offset";
+$msg = sprintf( "Time offset of %+.9f seconds to reference.", $offset);
+$perfdata = sprintf( "|offset=%.9fs;%.9f;%.9f", ${offset}, $options{'w'}, $options{'c'});
+
+# Check leap
+if( $leap !~ $leapOk ){
+ &_exit( 2, "Chrony leap status \"$leap\" is not equal to \"$leapOk\"! $msg $perfdata" );
+}
+
+#
+# Return stuff
+#
+
+&_exit($rc, "$msg $perfdata");
+
diff --git a/roles/pve/files/nrpe/check_smartdisk.sh b/roles/ceph/files/plugins/check_smartdisk.sh
similarity index 100%
rename from roles/pve/files/nrpe/check_smartdisk.sh
rename to roles/ceph/files/plugins/check_smartdisk.sh
diff --git a/roles/ceph/files/sudoers b/roles/ceph/files/sudoers
new file mode 100644
index 0000000..5e2f0cd
--- /dev/null
+++ b/roles/ceph/files/sudoers
@@ -0,0 +1 @@
+nagios ALL=(root) NOPASSWD: /usr/sbin/smartctl,/sbin/dmsetup
\ No newline at end of file
diff --git a/roles/ceph/tasks/main.yml b/roles/ceph/tasks/main.yml
index c1af77e..f334f87 100644
--- a/roles/ceph/tasks/main.yml
+++ b/roles/ceph/tasks/main.yml
@@ -1,8 +1,2 @@
-- name: Set NRPE Ceph configuration
- copy:
- src: nrpe.cfg
- dest: /etc/nagios/nrpe.d/95-ceph.cfg
- owner: root
- group: root
- mode: u=rw,g=r,o=r
- notify: restart-nrpe
+- import_tasks: nrpe.yml
+ tags: nrpe
diff --git a/roles/ceph/tasks/nrpe.yml b/roles/ceph/tasks/nrpe.yml
new file mode 100644
index 0000000..2b87bbe
--- /dev/null
+++ b/roles/ceph/tasks/nrpe.yml
@@ -0,0 +1,24 @@
+- name: Set NRPE Ceph configuration
+ copy:
+ src: nrpe.d/95-ceph.cfg
+ dest: /etc/nagios/nrpe.d/
+ owner: root
+ group: root
+ mode: u=rw,g=r,o=r
+ notify: restart-nrpe
+- name: Copy Ceph NRPE plugins
+ copy:
+ src: plugins/
+ dest: /etc/nagios/plugins/
+ owner: root
+ group: root
+ mode: u=rwx,g=rx,o=rx
+ notify: restart-nrpe
+- name: Add nagios to sudoers
+ copy:
+ src: sudoers
+ dest: /etc/sudoers.d/nagios
+ mode: u=rw,g=r,o=
+ owner: root
+ group: root
+ notify: restart-nrpe
diff --git a/roles/db/defaults/main.yaml b/roles/db/defaults/main.yaml
index b1818b7..6820f53 100644
--- a/roles/db/defaults/main.yaml
+++ b/roles/db/defaults/main.yaml
@@ -22,17 +22,20 @@ required_directories:
- { path: /etc/systemd/system/mariadb.service.d, owner: root, group: root, mode: 'u=rwx,g=rx,o=rx' }
required_mariabackup_files_and_scripts:
- { src: mysql-flush.sh, dest: /etc/qemu/fsfreeze-hook.d/mysql-flush.sh, mode: u=rwx,g=rx,o=rx }
- - { src: mariabackup/bacula-before.sh, dest: /root/mariabackup/bacula-before.sh, mode: u=rwx,g=rx,o=rx }
+ - { src: mariabackup/make-backup.sh, dest: /root/mariabackup/make-backup.sh, mode: u=rwx,g=rx,o=rx }
- { src: mariabackup/config.sh, dest: /root/mariabackup/config.sh, mode: u=rwx,g=rx,o=x }
- - { src: mariabackup/inc-backup.sh, dest: /root/mariabackup/inc-backup.sh, mode: u=rwx,g=rx,o=rx }
- { src: mariabackup/restore-backup.sh, dest: /root/mariabackup/restore-backup.sh, mode: u=rwx,g=rx,o=rx }
- { src: scripts/check-memory.sh, dest: /root/scripts/check-memory.sh, mode: u=rwx,g=rx,o=rx }
- { src: scripts/export-privs.sh, dest: /root/scripts/export-privs.sh, mode: u=rwx,g=rx,o=rx }
- { src: scripts/mysqltuner.pl, dest: /root/scripts/mysqltuner.pl, mode: u=rwx,g=rx,o=rx }
- - { src: scripts/events-promote.sh, dest: /root/scripts/events-promote.sh, mode: u=rwx,g=rx,o=rx }
- - { src: scripts/events-demote.sh, dest: /root/scripts/events-demote.sh, mode: u=rwx,g=rx,o=rx }
+ - { src: scripts/role-change.sh, dest: /root/scripts/role-change.sh, mode: u=rwx,g=rx,o=rx }
- { src: scripts/README.md, dest: /root/scripts/README.md, mode: u=rw,g=r,o=r }
- { src: scripts/scheduler-log.sh, dest: /root/scripts/scheduler-log.sh, mode: u=rwx,g=rx,o=rx }
+configuration_files:
+ - { src: "conf/z99-local.cnf", dest: "/etc/mysql/mariadb.conf.d/z99-local.cnf", owner: "root", group: "root", mode: "u=rw,g=r,o=r" }
+ - { src: "mariabackup/my.cnf", dest: "/root/mariabackup/my.cnf", owner: "root", group: "root", mode: "u=rw,g=,o=" }
+ - { src: "pmm-agent.yaml", dest: "/usr/local/percona/pmm2/config/pmm-agent.yaml", owner: "pmm-agent", group: "pmm-agent", mode: "u=rw,g=rw,o=" }
+ - { src: "check-memory.cron", dest: "/etc/cron.d/vn-check-memory", owner: "root", group: "root", mode: "u=rw,g=r,o=r" }
downloads:
- url: https://r.mariadb.com/downloads/mariadb_repo_setup
dest: /tmp/mariadb_repo_setup
@@ -40,3 +43,10 @@ downloads:
- url: https://repo.percona.com/apt/percona-release_latest.generic_all.deb
dest: /tmp/percona-release_latest.generic_all.deb
mode: u=rw,g=r,o=r
+clean_config_and_scripts:
+ - /root/scripts/events-promote.sh
+ - /root/scripts/events-demote.sh
+ - /root/scripts/promote-master.sh
+ - /root/scripts/promote-slave.sh
+ - /root/scripts/inc-backup.sh
+ - /root/scripts/bacula-before.sh
\ No newline at end of file
diff --git a/roles/db/files/conf/z90-vn.cnf b/roles/db/files/conf/z90-vn.cnf
index 1911135..c7db964 100644
--- a/roles/db/files/conf/z90-vn.cnf
+++ b/roles/db/files/conf/z90-vn.cnf
@@ -30,7 +30,7 @@ interactive_timeout = 1800
wait_timeout = 1800
open_files_limit = 20000
low_priority_updates = 1
-table_open_cache = 40000
+table_open_cache = 50000
table_definition_cache = 10000
table_open_cache_instances = 1
key_buffer_size = 256K
diff --git a/roles/db/files/mariabackup/bacula-after.sh b/roles/db/files/mariabackup/bacula-after.sh
index 6857ed7..32bb6b9 100755
--- a/roles/db/files/mariabackup/bacula-after.sh
+++ b/roles/db/files/mariabackup/bacula-after.sh
@@ -5,12 +5,12 @@ myDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$myDir/config.sh"
. "$myDir/apply.config.sh"
-pattern="$baculaDir/mnt/local-backup/*_full.gz"
+pattern="$backupDir/*_full.gz"
files=($pattern)
backupFile="${files[0]}"
"$myDir/restore-backup.sh" "$backupFile"
-rm -r "$baculaDir"
+rm -r "$backupDir/"*
if [[ "${#dbClusterSiblings[@]}" -gt "0" ]]; then
for node in "${dbClusterSiblings[@]}"; do
@@ -46,7 +46,7 @@ fi
echo "Promoting Events."
-"/root/scripts/events-promote.sh"
+/root/scripts/role-change.sh --promote
for node in "${dbClusterSiblings[@]}"; do
ssh root@$node service mysql start
diff --git a/roles/db/files/mariabackup/bacula-before.sh b/roles/db/files/mariabackup/bacula-before.sh
deleted file mode 100644
index f4723a3..0000000
--- a/roles/db/files/mariabackup/bacula-before.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-# https://mariadb.com/kb/en/mariabackup/
-set -e
-
-myDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-. "$myDir/config.sh"
-
-todayDir="$(date +%Y-%m-%d)"
-backupName="${todayDir}_$(date +"%H-%M")_full"
-backupFile="$backupDir/$backupName.gz"
-
-if [ -d "$backupDir" ]; then
- rm -rf "$backupDir/"*
-fi
-
-ulimit -n 8192
-mariabackup \
- --defaults-extra-file="$myDir/my.cnf" \
- --backup \
- --extra-lsndir="$backupDir/$backupName" \
- --history="$todayDir" \
- --stream=xbstream \
- --parallel=4 \
- 2>> "$logFile" \
- | pigz -p 12 \
- > "$backupFile"
-
-if [ $? != "0" ]; then
- echo "An error ocurred during backup, please take a look at log file: $logFile"
- exit 1
-fi
diff --git a/roles/db/files/mariabackup/config.sh b/roles/db/files/mariabackup/config.sh
index bb61fd8..3aacc00 100644
--- a/roles/db/files/mariabackup/config.sh
+++ b/roles/db/files/mariabackup/config.sh
@@ -18,3 +18,5 @@ restoreDir=/mnt/mysqldata/mysql-restore
# Directory of MySQL data
dataDir=/mnt/mysqldata/mysql
+# Number of procs created by pigz
+pigzProcs=12
diff --git a/roles/db/files/mariabackup/inc-backup.sh b/roles/db/files/mariabackup/inc-backup.sh
deleted file mode 100644
index c6d6e91..0000000
--- a/roles/db/files/mariabackup/inc-backup.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-# https://mariadb.com/kb/en/incremental-backup-and-restore-with-mariabackup/
-set -e
-
-myDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-. "$myDir/config.sh"
-
-todayDir="$(date +%Y-%m-%d)"
-todayPath="$historyDir/$todayDir"
-
-pattern="$todayPath/${todayDir}_??-??_full.xb.gz.enc"
-files=($pattern)
-backupFile="${files[0]}"
-backupBase=$(basename -- "$backupFile")
-backupName="${backupBase%%.*}"
-
-incrementalName="${todayDir}_$(date +"%H-%M")_incremental"
-incrementalFile="$backupDir/${incrementalName}.xb.gz.enc"
-
-ulimit -n 24098
-mariabackup \
- --defaults-extra-file="$myDir/my.cnf" \
- --backup \
- --incremental-basedir="$backupDir/$backupName" \
- --extra-lsndir="$backupDir/$incrementalName" \
- --incremental-history-name="$todayDir" \
- 2>> "$logFile" \
- | gzip \
- | openssl enc -aes-256-cbc -pbkdf2 -kfile "$myDir/xbcrypt.key" \
- > "$incrementalFile"
-
-if [ $? != "0" ]; then
- echo "An error ocurred during backup, please take a look at log file: $logFile"
- exit 1
-fi
-
-cp "$incrementalFile" "$todayPath"
-cp -r "$backupDir/$incrementalName" "$todayPath"
diff --git a/roles/db/files/mariabackup/make-backup.sh b/roles/db/files/mariabackup/make-backup.sh
new file mode 100644
index 0000000..41d4c37
--- /dev/null
+++ b/roles/db/files/mariabackup/make-backup.sh
@@ -0,0 +1,120 @@
+#!/bin/bash
+#
+# Author: Juan Ferrer
+# Copyright (c) 2025 Verdnatura S.L. All rights reserved.
+# Version: 0.0.2
+#
+# 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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, see .
+#
+# Backups the MariaDB database, it supports three modes:
+# - Full: Full backup of all DB data, deletes all previous backups.
+# - Differential: Backup changes since the last full backup, deletes all
+# previous differential backups.
+# - Incremental: Backup changes since the last full backup, same as
+# differential, but does not erase any previous backups.
+#
+# References:
+# - https://mariadb.com/kb/en/mariabackup/
+# - https://mariadb.com/kb/en/mariabackup-options/
+# - https://mariadb.com/kb/en/incremental-backup-and-restore-with-mariabackup/
+# - https://mariadb.com/kb/en/setting-up-a-replica-with-mariabackup/
+#
+set -e
+
+myDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+. "$myDir/config.sh"
+
+level=$1
+historyName=$2
+curDate="$(date +%Y-%m-%d)"
+
+usage() {
+ echo "Usage: $0 [historyName]"
+ exit 1
+}
+backup_error() {
+ echo "An error ocurred during backup, please take a look at log file: $logFile"
+ exit 2
+}
+
+if [ -z "$level" ]; then
+ usage
+fi
+
+case "$level" in
+ Full)
+ backupName="${curDate}_$(date +"%H-%M")_full"
+ backupFile="$backupDir/$backupName.gz"
+
+ if [ -d "$backupDir" ]; then
+ rm -rf "$backupDir/"*
+ fi
+ if [ -z "$historyName" ]; then
+ historyName=$backupName
+ fi
+
+ ulimit -n 8192
+ mariabackup \
+ --defaults-extra-file="$myDir/my.cnf" \
+ --backup \
+ --history="$historyName" \
+ --extra-lsndir="$backupDir/$backupName" \
+ --slave-info \
+ --safe-slave-backup \
+ 2>> "$logFile" \
+ | pigz -p "$pigzProcs" \
+ > "$backupFile"
+
+ if [ $? != "0" ]; then
+ backup_error
+ fi
+ ;;
+ Differential|Incremental)
+ pattern="$backupDir/${curDate}_??-??_full.gz"
+ files=($pattern)
+ backupFile="${files[0]}"
+ backupBase=$(basename -- "$backupFile")
+ backupName="${backupBase%%.*}"
+
+ incrementalName="${backupName%%_full}_diff_$(date +"%H-%M")"
+ incrementalFile="$backupDir/${incrementalName}.gz"
+
+ if [ "$level" = "Differential" ]; then
+ rm -rf "$backupDir/${curDate}_"??-??_diff_??-??{,.gz}
+ fi
+ if [ -z "$historyName" ]; then
+ historyName=$incrementalName
+ fi
+
+ ulimit -n 24098
+ mariabackup \
+ --defaults-extra-file="$myDir/my.cnf" \
+ --backup \
+ --history="$historyName" \
+ --extra-lsndir="$backupDir/$incrementalName" \
+ --incremental-basedir="$backupDir/$backupName" \
+ --slave-info \
+ --safe-slave-backup \
+ 2>> "$logFile" \
+ | pigz -p "$pigzProcs" \
+ > "$incrementalFile"
+
+ if [ $? != "0" ]; then
+ backup_error
+ fi
+ ;;
+ *)
+ usage
+ ;;
+esac
diff --git a/roles/db/files/mariabackup/restore-backup.sh b/roles/db/files/mariabackup/restore-backup.sh
index 73d06e2..ecf2fd7 100644
--- a/roles/db/files/mariabackup/restore-backup.sh
+++ b/roles/db/files/mariabackup/restore-backup.sh
@@ -28,7 +28,7 @@ mkdir -p "$restoreDir"
echo "$(formatted_date)"
echo "Decompresing backup."
pigz --decompress --processes 4 --stdout "$backupFile" \
- | mbstream --extract --parallel=4 --directory="$restoreDir"
+ | mbstream --extract --parallel=6 --directory="$restoreDir"
echo "Preparing backup."
mariabackup \
diff --git a/roles/db/files/nrpe/95-mariadb.cfg b/roles/db/files/nrpe/95-mariadb.cfg
index a918213..5e28bf2 100644
--- a/roles/db/files/nrpe/95-mariadb.cfg
+++ b/roles/db/files/nrpe/95-mariadb.cfg
@@ -3,4 +3,3 @@ command[check_disk_mysqlbin]=/usr/lib/nagios/plugins/check_disk -w 10% -c 5% -p
command[check_disk_backup]=/usr/lib/nagios/plugins/check_disk -w 10% -c 5% -p /mnt/local-backup
command[check_mysql_scheduler]=/etc/nagios/plugins/check_mysql_scheduler
command[check_total_procs]=/usr/lib/nagios/plugins/check_procs -w 600 -c 700
-
diff --git a/roles/db/files/scripts/events-demote.sh b/roles/db/files/scripts/events-demote.sh
deleted file mode 100755
index 4856f4f..0000000
--- a/roles/db/files/scripts/events-demote.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-mysql -e "SET GLOBAL event_scheduler = OFF"
-
-echo "SELECT db, name FROM mysql.event WHERE status = 'ENABLED'" | mysql --raw --silent | \
-awk '{
- gsub("`", "``", $1);
- gsub("`", "``", $2);
- print "`"$1"`.`"$2"`";
-}' | \
-while read event; do
- mysql -e "ALTER EVENT $event DISABLE ON SLAVE"
-done
-
-mysql -e "SET GLOBAL event_scheduler = ON"
-
diff --git a/roles/db/files/scripts/events-promote.sh b/roles/db/files/scripts/events-promote.sh
deleted file mode 100755
index d4b5b9f..0000000
--- a/roles/db/files/scripts/events-promote.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-
-mysql -e "SET GLOBAL event_scheduler = OFF"
-
-echo "SELECT db, name FROM mysql.event WHERE status = 'SLAVESIDE_DISABLED'" | mysql --raw --silent | \
-awk '{
- gsub("`", "``", $1);
- gsub("`", "``", $2);
- print "`"$1"`.`"$2"`";
-}' | \
-while read event; do
- mysql -e "ALTER EVENT $event ENABLE"
-done
-
-mysql -e "SET GLOBAL event_scheduler = ON"
diff --git a/roles/db/files/scripts/role-change.sh b/roles/db/files/scripts/role-change.sh
new file mode 100644
index 0000000..1ba3e0b
--- /dev/null
+++ b/roles/db/files/scripts/role-change.sh
@@ -0,0 +1,123 @@
+#!/bin/bash
+# Script to manage MariaDB events during replication role changes.
+#
+# Author: Xavi Lleó & GhatGPT ®
+# Copyright (c) 2025 Verdnatura S.L. All rights reserved.
+# Version: 1.4.0
+#
+# 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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, see .
+#
+# Description:
+# This script toggles MariaDB events during replication role changes.
+# It supports three modes:
+# --promote : Re-enable events previously marked SLAVESIDE_DISABLED
+# --demote : Disable currently ENABLED events using DISABLE ON SLAVE
+# --status : Display current status of all events
+# All actions are logged to /var/log/mariadb_admin.log.
+
+set -euo pipefail
+
+LOG_FILE="/var/log/mariadb_admin.log"
+TIMESTAMP="$(date '+%F %T')"
+
+log() {
+ echo "$TIMESTAMP - $1" | tee -a "$LOG_FILE"
+}
+
+disable_scheduler() {
+ log "Disabling event_scheduler..."
+ if mysql -e "SET GLOBAL event_scheduler = OFF"; then
+ log "Successfully disabled event_scheduler"
+ else
+ log "ERROR: Failed to disable event_scheduler"
+ exit 1
+ fi
+}
+
+enable_scheduler() {
+ log "Re-enabling event_scheduler..."
+ if mysql -e "SET GLOBAL event_scheduler = ON"; then
+ log "Successfully re-enabled event_scheduler"
+ else
+ log "ERROR: Failed to re-enable event_scheduler"
+ exit 1
+ fi
+}
+
+final_status() {
+ log "Final event_scheduler status:"
+ mysql --table -e "SHOW VARIABLES LIKE 'event_scheduler';" | tee -a "$LOG_FILE"
+
+ log "Final state of all events:"
+ mysql --table -e "SELECT db, name, status FROM mysql.event;" | tee -a "$LOG_FILE"
+}
+
+if [[ $# -ne 1 ]]; then
+ echo "Usage: $0 --promote | --demote | --status"
+ exit 1
+fi
+
+MODE="$1"
+
+case "$MODE" in
+ --status)
+ log "Fetching current event_scheduler status:"
+ mysql --table -e "SHOW VARIABLES LIKE 'event_scheduler';" | tee -a "$LOG_FILE"
+
+ log "Fetching current event status:"
+ mysql --table -e "SELECT db, name, status FROM mysql.event;" | tee -a "$LOG_FILE"
+ ;;
+
+ --promote)
+ disable_scheduler
+
+ log "Promoting: Enabling events with SLAVESIDE_DISABLED status..."
+ mysql --raw --silent -e "SELECT db, name FROM mysql.event WHERE status = 'SLAVESIDE_DISABLED'" | \
+ awk '{ gsub("`", "``", $1); gsub("`", "``", $2); print "`"$1"`.`"$2"`" }' | \
+ while read -r event; do
+ if mysql -e "ALTER EVENT $event ENABLE"; then
+ log "Enabled event: $event"
+ else
+ log "ERROR: Failed to enable event: $event"
+ fi
+ done
+
+ enable_scheduler
+ final_status
+ ;;
+
+ --demote)
+ disable_scheduler
+
+ log "Demoting: Disabling events currently ENABLED..."
+ mysql --raw --silent -e "SELECT db, name FROM mysql.event WHERE status = 'ENABLED'" | \
+ awk '{ gsub("`", "``", $1); gsub("`", "``", $2); print "`"$1"`.`"$2"`" }' | \
+ while read -r event; do
+ if mysql -e "ALTER EVENT $event DISABLE ON SLAVE"; then
+ log "Disabled event: $event"
+ else
+ log "ERROR: Failed to disable event: $event"
+ fi
+ done
+
+ enable_scheduler
+ final_status
+ ;;
+
+ *)
+ echo "Invalid mode: $MODE"
+ echo "Usage: $0 --promote | --demote | --status"
+ exit 1
+ ;;
+esac
diff --git a/roles/db/handlers/main.yml b/roles/db/handlers/main.yml
index 320b475..11143ee 100644
--- a/roles/db/handlers/main.yml
+++ b/roles/db/handlers/main.yml
@@ -9,3 +9,7 @@
service:
name: nagios-nrpe-server
state: restarted
+- name: restart-percona
+ service:
+ name: pmm-agent.service
+ state: restarted
diff --git a/roles/db/tasks/mariadb.yml b/roles/db/tasks/mariadb.yml
index 21d4a57..b7fe120 100644
--- a/roles/db/tasks/mariadb.yml
+++ b/roles/db/tasks/mariadb.yml
@@ -3,35 +3,29 @@
name: "{{ mariadb_requeriments }}"
state: present
install_recommends: no
-
- name: Download required setup files
get_url:
url: "{{ item.url }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
loop: "{{ downloads }}"
-
- name: Run MariaDB repository setup script
command:
cmd: "/bin/bash /tmp/mariadb_repo_setup --mariadb-server-version={{ db.version | default('10.11.10') }}"
creates: "/etc/apt/sources.list.d/mariadb.list"
-
- name: Install Percona repository package
apt:
deb: "/tmp/percona-release_latest.generic_all.deb"
state: present
install_recommends: no
-
- name: Update apt cache
apt:
update_cache: yes
-
- name: Install MariaDB packages
apt:
name: "{{ mariadb_base_packages }}"
state: present
install_recommends: no
-
- name: Add tmpfs in /etc/fstab
blockinfile:
path: /etc/fstab
@@ -39,15 +33,6 @@
block: |
tmpfs /mnt/mysqltmp tmpfs rw,size={{ mysqltmpsize }} 0 0
register: fstab
-
-- name: Configure MariaDB memory check CRON
- template:
- src: check-memory.cron
- dest: /etc/cron.d/vn-check-memory
- owner: root
- group: root
- mode: u=rw,g=r,o=r
-
- name: Configure MariaDB scheduler log CRON
copy:
src: scheduler-log.cron
@@ -55,7 +40,6 @@
owner: root
group: root
mode: u=rw,g=r,o=r
-
- name: Insert MySQL certificates
no_log: true
copy:
@@ -66,7 +50,6 @@
mode: "{{ item.mode }}"
loop: "{{ certificates }}"
notify: restart-mariadb
-
- name: Ensure required directories exist
file:
path: "{{ item.path }}"
@@ -75,7 +58,6 @@
group: "{{ item.group }}"
mode: "{{ item.mode }}"
loop: "{{ required_directories }}"
-
- name: Copy required MariaBackup files and scripts
copy:
src: "{{ item.src }}"
@@ -84,7 +66,15 @@
group: root
mode: "{{ item.mode }}"
loop: "{{ required_mariabackup_files_and_scripts }}"
-
+- name: Deploy configuration files
+ template:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ owner: "{{ item.owner }}"
+ group: "{{ item.group }}"
+ mode: "{{ item.mode }}"
+ loop: "{{ configuration_files }}"
+ notify: restart-percona
- name: Set MariaDB common configuration
copy:
src: conf/z90-vn.cnf
@@ -93,23 +83,6 @@
group: root
mode: u=rw,g=r,o=r
notify: restart-mariadb
-
-- name: Set MariaDB local configuration template
- template:
- src: conf/z99-local.cnf
- dest: /etc/mysql/mariadb.conf.d/
- owner: root
- group: root
- mode: u=rw,g=r,o=r
-
-- name: Set MariaBackup connection configuration
- template:
- src: mariabackup/my.cnf
- dest: /root/mariabackup/
- owner: root
- group: root
- mode: u=rw,g=,o=
-
- name: Override MariaDB systemd service configuration
copy:
src: mariadb_override.conf
@@ -118,7 +91,6 @@
group: root
mode: u=rw,g=r,o=r
notify: reload-systemd
-
- name: Set MariaDB NRPE configuration
copy:
src: nrpe/95-mariadb.cfg
@@ -127,12 +99,10 @@
group: root
mode: u=rw,g=r,o=r
notify: restart-nrpe
-
- name: Check if /var/lib/mysql/ exists
stat:
path: /var/lib/mysql/
register: mysql_dir
-
- when: mysql_dir.stat.exists
block:
@@ -149,7 +119,6 @@
file:
path: /var/lib/mysql/
state: absent
-
- name: Mount all filesystems from /etc/fstab
command: mount -a
when: fstab.changed
@@ -178,3 +147,9 @@
name: process_monitor.timer
enabled: true
state: stopped
+
+- name: Clean old configs or scripts
+ file:
+ path: "{{ item }}"
+ state: absent
+ loop: "{{ clean_config_and_scripts }}"
diff --git a/roles/db/templates/conf/z99-local.cnf b/roles/db/templates/conf/z99-local.cnf
index a434496..390df19 100644
--- a/roles/db/templates/conf/z99-local.cnf
+++ b/roles/db/templates/conf/z99-local.cnf
@@ -1,6 +1,8 @@
[mysqld]
server-id = {{ serverid }}
+#log_slave_updates = ON
+#read_only = ON
#bind-address = 127.0.0.1
#event-scheduler = OFF
#skip-log-bin
diff --git a/roles/db/templates/mariabackup/apply.config.sh b/roles/db/templates/mariabackup/apply.config.sh
index a175dd8..ca604ae 100755
--- a/roles/db/templates/mariabackup/apply.config.sh
+++ b/roles/db/templates/mariabackup/apply.config.sh
@@ -1,8 +1,5 @@
#!/bin/bash
-# Bacula directory for restore
-baculaDir=/mnt/mysqldata/bacula-restore
-
# Database branch name
dbBranch={{ db.branch }}
@@ -15,7 +12,7 @@ dbClusterSiblings=()
# Jenkins authentication string
jenkinsAuth=jenkins:{{ lookup(passbolt, 'jenkinsAuth', folder_parent_id=passbolt_folder).password }}
-{% if db.jenkinsUrl is defined %}
+{% if db.jenkinsJob is defined %}
# Jenkins job URL
-jenkinsUrl={{ db.jenkinsUrl }}
+jenkinsUrl={{ jenkinsUrl }}{{ db.jenkinsJob }}
{% endif %}
diff --git a/roles/db/templates/mariabackup/my.cnf b/roles/db/templates/mariabackup/my.cnf
index 7037bef..47207c2 100644
--- a/roles/db/templates/mariabackup/my.cnf
+++ b/roles/db/templates/mariabackup/my.cnf
@@ -2,6 +2,6 @@
host = localhost
user = mariabackup
password = {{ lookup(passbolt, 'mariabackup', folder_parent_id=passbolt_folder).password }}
-use-memory = 1G
-parallel = 4
+use-memory = 4G
+parallel = 8
stream = xbstream
diff --git a/roles/db/templates/pmm-agent.yaml b/roles/db/templates/pmm-agent.yaml
new file mode 100644
index 0000000..04e2b6a
--- /dev/null
+++ b/roles/db/templates/pmm-agent.yaml
@@ -0,0 +1,34 @@
+# Updated by `pmm-agent setup`.
+---
+id: /agent_id/{{ pmm.id }}
+listen-address: 127.0.0.1
+listen-port: 7777
+server:
+ address: {{ pmm.address }}
+ username: {{ pmm.username }}
+ password: {{ pmm.password }}
+ insecure-tls: {{ pmm.insecure }}
+paths:
+ paths_base: /usr/local/percona/pmm2
+ exporters_base: /usr/local/percona/pmm2/exporters
+ node_exporter: /usr/local/percona/pmm2/exporters/node_exporter
+ mysqld_exporter: /usr/local/percona/pmm2/exporters/mysqld_exporter
+ mongodb_exporter: /usr/local/percona/pmm2/exporters/mongodb_exporter
+ postgres_exporter: /usr/local/percona/pmm2/exporters/postgres_exporter
+ proxysql_exporter: /usr/local/percona/pmm2/exporters/proxysql_exporter
+ rds_exporter: /usr/local/percona/pmm2/exporters/rds_exporter
+ azure_exporter: /usr/local/percona/pmm2/exporters/azure_exporter
+ vmagent: /usr/local/percona/pmm2/exporters/vmagent
+ tempdir: {{ pmm.tempdir }}
+ pt_summary: /usr/local/percona/pmm2/tools/pt-summary
+ pt_pg_summary: /usr/local/percona/pmm2/tools/pt-pg-summary
+ pt_mysql_summary: /usr/local/percona/pmm2/tools/pt-mysql-summary
+ pt_mongodb_summary: /usr/local/percona/pmm2/tools/pt-mongodb-summary
+ports:
+ min: 42000
+ max: 51999
+log-level: warn
+debug: false
+trace: false
+loglinescount: 1024
+window-connected-time: 1h0m0s
diff --git a/roles/dhcp/templates/dhcpd.conf b/roles/dhcp/templates/dhcpd.conf
index 47f5af2..7059f42 100644
--- a/roles/dhcp/templates/dhcpd.conf
+++ b/roles/dhcp/templates/dhcpd.conf
@@ -14,7 +14,7 @@ max-lease-time 86400;
option domain-name "{{ domain_name.name }}";
option domain-name-servers {{ domain_name.servers }};
-option ntp-servers {{ ntp_servers }};
+option ntp-servers {{ domain_name.ntp_servers }};
# DHCP daemon uses default time zone UTC
db-time-format local;
diff --git a/roles/ipsec/defaults/main.yml b/roles/ipsec/defaults/main.yml
new file mode 100644
index 0000000..a7d3b9d
--- /dev/null
+++ b/roles/ipsec/defaults/main.yml
@@ -0,0 +1,36 @@
+strongswan_requeriments:
+ - strongswan
+ - libstrongswan-standard-plugins
+ - strongswan-pki
+ - tcpdump
+ - iperf
+ - conntrack
+ - iptables-persistent
+certificates:
+ - { content: '{{ cert_ipsec }}', dest: '/etc/ipsec.d/certs/cert.pem', mode: 'u=rw,g=r,o=r' }
+ - { content: '{{ ca }}', dest: '/etc/ipsec.d/cacerts/ca.pem', mode: 'u=rw,g=r,o=r' }
+config_ipsec_files:
+ - { src: 'ipsec.conf', dest: '/etc/ipsec.conf', mode: 'u=rw,g=r,o=r' }
+ - { src: 'vn-attr.conf', dest: '/etc/strongswan.d/charon/vn-attr.conf', mode: 'u=rw,g=r,o=r' }
+ - { src: 'vn-eap-radius.conf', dest: '/etc/strongswan.d/charon/vn-eap-radius.conf', mode: 'u=r,g=,o=' }
+ - { src: 'ipsec.secrets', dest: '/etc/ipsec.secrets', mode: 'u=r,g=,o=' }
+mangle_block: |
+ *mangle
+ :PREROUTING ACCEPT [0:0]
+ :INPUT ACCEPT [0:0]
+ :FORWARD ACCEPT [0:0]
+ :OUTPUT ACCEPT [0:0]
+ :POSTROUTING ACCEPT [0:0]
+ -A PREROUTING -p tcp -m policy --dir in --pol ipsec -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
+ -A POSTROUTING -p tcp -m policy --dir out --pol ipsec -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
+ COMMIT
+ *filter
+ :INPUT ACCEPT [0:0]
+ :FORWARD ACCEPT [0:0]
+ :OUTPUT ACCEPT [0:0]
+ -A FORWARD -m conntrack --ctstate NEW,RELATED,ESTABLISHED -j ACCEPT
+ -A FORWARD -m conntrack --ctstate INVALID -j LOG --log-prefix "CT INVALID: "
+ COMMIT
+config_and_logrotate:
+ - { src: vn.conf, dest: '/etc/strongswan.d/vn.conf' }
+ - { src: charon, dest: '/etc/logrotate.d/charon' }
diff --git a/roles/ipsec/files/charon b/roles/ipsec/files/charon
new file mode 100644
index 0000000..9a05de0
--- /dev/null
+++ b/roles/ipsec/files/charon
@@ -0,0 +1,11 @@
+/var/log/strongswan/charon.log
+{
+ copytruncate
+ create 644 root root
+ rotate 10
+ weekly
+ missingok
+ notifempty
+ compress
+ delaycompress
+}
diff --git a/roles/ipsec/files/vn.conf b/roles/ipsec/files/vn.conf
new file mode 100644
index 0000000..0b26373
--- /dev/null
+++ b/roles/ipsec/files/vn.conf
@@ -0,0 +1,19 @@
+charon {
+ cisco_unity = yes
+
+ filelog {
+ log {
+ path = /var/log/strongswan/charon.log
+ append = yes
+ default = 1
+ flush_line = yes
+ ike_name = yes
+ time_format = %Y-%m-%d %H:%M:%S
+ }
+ }
+ syslog {
+ identifier = charon
+ daemon {
+ }
+ }
+}
diff --git a/roles/ipsec/handlers/main.yml b/roles/ipsec/handlers/main.yml
new file mode 100644
index 0000000..79978af
--- /dev/null
+++ b/roles/ipsec/handlers/main.yml
@@ -0,0 +1,4 @@
+- name: restart-ipsec
+ systemd:
+ name: strongswan-starter.service
+ state: restarted
diff --git a/roles/ipsec/tasks/ipsec.yml b/roles/ipsec/tasks/ipsec.yml
new file mode 100644
index 0000000..37f63ce
--- /dev/null
+++ b/roles/ipsec/tasks/ipsec.yml
@@ -0,0 +1,85 @@
+- name: Update apt cache
+ apt:
+ update_cache: yes
+- name: Install VPN package requirements
+ apt:
+ name: "{{ strongswan_requeriments }}"
+ state: present
+ install_recommends: no
+- name: Create directory /var/log/strongswan
+ file:
+ path: /var/log/strongswan
+ state: directory
+ owner: root
+ group: root
+ mode: '0755'
+- name: Insert certificates
+ no_log: true
+ copy:
+ content: "{{ item.content }}"
+ dest: "{{ item.dest }}"
+ owner: root
+ group: root
+ mode: "{{ item.mode }}"
+ loop: "{{ certificates }}"
+- name: Add private key
+ copy:
+ content: "{{ lookup(passbolt, 'ipsec_private_key', folder_parent_id=passbolt_folder).description }}"
+ dest: /etc/ipsec.d/private/key.pem
+ owner: root
+ group: root
+ mode: u=r,g=r,o=
+- name: Configure ipsec and charon
+ template:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ owner: root
+ group: root
+ mode: "{{ item.mode }}"
+ loop: "{{ config_ipsec_files }}"
+ notify: restart-ipsec
+- name: Copy Configure file and logrotate Charon
+ copy:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ owner: root
+ group: root
+ mode: u=rw,g=r,o=r
+ loop: "{{ config_and_logrotate }}"
+ notify: restart-ipsec
+- name: IP forward as a router
+ sysctl:
+ name: net.ipv4.ip_forward
+ value: "1"
+ state: present
+ sysctl_set: yes
+ reload: yes
+- name: Add iptables rules in rules.v4 file
+ blockinfile:
+ path: /etc/iptables/rules.v4
+ marker: "# {mark} ANSIBLE-MANAGED MANGLE CHAIN"
+ block: "{{ mangle_block }}"
+ register: iptables
+- name: Reload iptables rules
+ command: netfilter-persistent reload
+ when: iptables.changed
+- name: Get default IPv4 interface
+ command: ip -o -4 route show default
+ register: default_route
+- name: Extract interface default name
+ set_fact:
+ active_interface: "{{ default_route.stdout.split()[-1] }}"
+- name: Routing table for VPN
+ lineinfile:
+ path: /etc/iproute2/rt_tables
+ line: "10 vpn"
+ state: present
+ regexp: "vpn"
+- name: Static routing rules to send VPN traffic directly to the firewall
+ lineinfile:
+ path: /etc/network/interfaces
+ insertafter: "dhcp"
+ line: "{{ item }}"
+ state: present
+ loop: "{{ static_routes }}"
+
\ No newline at end of file
diff --git a/roles/ipsec/tasks/main.yml b/roles/ipsec/tasks/main.yml
new file mode 100644
index 0000000..d3dd860
--- /dev/null
+++ b/roles/ipsec/tasks/main.yml
@@ -0,0 +1,3 @@
+- import_tasks: ipsec.yml
+ tags: ipsec
+
diff --git a/roles/ipsec/templates/ipsec.conf b/roles/ipsec/templates/ipsec.conf
new file mode 100644
index 0000000..76d3627
--- /dev/null
+++ b/roles/ipsec/templates/ipsec.conf
@@ -0,0 +1,32 @@
+
+config setup
+ charondebug="ike 1, knl 1, cfg 0"
+ uniqueids=no
+
+conn %default
+ auto=add
+ compress=no
+ type=tunnel
+ keyexchange=ikev2
+ fragmentation=yes
+ forceencaps=yes
+ eap_identity=%identity
+
+ dpdaction=clear
+ dpddelay=300s
+ rekey=no
+
+ left=%any
+ leftid=@{{ leftid }}
+ leftcert=cert.pem
+ leftsendcert=always
+ leftsubnet={{ leftsubnet }}
+
+ right=%any
+ rightid=%any
+ rightauth=eap-radius
+ rightdns={{ rightdns }}
+ rightsendcert=never
+
+{{ ipsec_groups }}
+
diff --git a/roles/ipsec/templates/ipsec.secrets b/roles/ipsec/templates/ipsec.secrets
new file mode 100644
index 0000000..9956a00
--- /dev/null
+++ b/roles/ipsec/templates/ipsec.secrets
@@ -0,0 +1,2 @@
+{{ leftid }} : RSA "key.pem"
+admin %any% : EAP "{{ lookup(passbolt, 'eap', folder_parent_id=passbolt_folder).password }}"
diff --git a/roles/ipsec/templates/vn-attr.conf b/roles/ipsec/templates/vn-attr.conf
new file mode 100644
index 0000000..94b2b2f
--- /dev/null
+++ b/roles/ipsec/templates/vn-attr.conf
@@ -0,0 +1,8 @@
+attr {
+ load = yes
+ dns = {{ rightdns }}
+ split-include = {{ leftsubnet }}
+ split-exclude = 0.0.0.0/0
+ 28674 = {{ leftid }}
+ 25 = {{ leftid }}
+}
diff --git a/roles/ipsec/templates/vn-eap-radius.conf b/roles/ipsec/templates/vn-eap-radius.conf
new file mode 100644
index 0000000..de69c64
--- /dev/null
+++ b/roles/ipsec/templates/vn-eap-radius.conf
@@ -0,0 +1,21 @@
+eap-radius {
+ load = yes
+ accounting = yes
+ class_group = yes
+ servers {
+ primary {
+ #address = radius1.verdnatura.es
+ address = {{ address_radiusA }}
+ auth_port = {{ auth_port }}
+ acct_port = {{ acct_port }}
+ secret = {{ lookup(passbolt, 'eap-radius', folder_parent_id=passbolt_folder).password }}
+ }
+ secondary {
+ #address = radius2.verdnatura.es
+ address = {{ address_radiusB }}
+ auth_port = {{ auth_port }}
+ acct_port = {{ acct_port }}
+ secret = {{ lookup(passbolt, 'eap-radius', folder_parent_id=passbolt_folder).password }}
+ }
+ }
+}
\ No newline at end of file
diff --git a/roles/ns/defaults/main.yml b/roles/ns/defaults/main.yml
new file mode 100644
index 0000000..4b1213f
--- /dev/null
+++ b/roles/ns/defaults/main.yml
@@ -0,0 +1,29 @@
+bind_packages:
+ - bind9
+ - bind9-dnsutils
+ - bind9-host
+ - bind9-libs
+ - bind9-utils
+ - dnsutils
+ - python3-pycurl
+bind_config_templates:
+ - { src: 'named.conf.j2', dest: '/etc/bind/named.conf', mode: 'u=rw,g=r,o=r' }
+ - { src: 'named.conf.master.j2', dest: '/etc/bind/named.conf.master', mode: 'u=rw,g=r,o=r' }
+ - { src: 'named.conf.local.j2', dest: '/etc/bind/named.conf.local', mode: 'u=rw,g=r,o=r' }
+ - { src: 'named.conf.slave.j2', dest: '/etc/bind/named.conf.slave', mode: 'u=rw,g=r,o=r' }
+ - { src: 'certbot.key', dest: '/etc/bind/keys/certbot.key', mode: 'u=rw,g=r,o=' }
+ - { src: 'lan.key', dest: '/etc/bind/keys/lan.key', mode: 'u=rw,g=r,o=' }
+ - { src: 'wan.key', dest: '/etc/bind/keys/wan.key', mode: 'u=rw,g=r,o=' }
+ - { src: 'rndc.key', dest: '/etc/bind/rndc.key', mode: 'u=rw,g=r,o=' }
+ - { src: 'dhcp.key', dest: '/etc/bind/keys/dhcp.key', mode: 'u=rw,g=r,o=' }
+ - { src: 'isp1.ns', dest: '/root/scripts/switch-isp', mode: 'u=rw,g=rw,o=r' }
+ - { src: 'isp2.ns', dest: '/root/scripts/switch-isp', mode: 'u=rw,g=rw,o=r' }
+ - { src: 'delete.ns', dest: '/root/scripts/switch-isp', mode: 'u=rw,g=rw,o=r' }
+directory:
+ - { path: '/root/scripts', owner: 'root', group: 'root', mode: 'u=rwx,g=rx,o=rx' }
+ - { path: '/etc/bind/keys', owner: 'root', group: 'bind', mode: 'u=rwx,g=rxs,o=rx' }
+ - { path: '/root/scripts/switch-isp', owner: 'root', group: 'bind', mode: 'u=rwx,g=rxs,o=rx' }
+required_files:
+ - { src: 'switch-isp.sh', dest: '/root/scripts', owner: 'root', group: 'root', mode: 'u=rwx,g=rx,o=rx' }
+ - { src: 'sync-conf', dest: '/root/scripts', owner: 'root', group: 'root', mode: 'u=rwx,g=rx,o=rx' }
+ - { src: 'gen-key.sh', dest: '/root/scripts', owner: 'root', group: 'bind', mode: 'u=rwx,g=rx,o=rx' }
diff --git a/roles/ns/files/gen-key.sh b/roles/ns/files/gen-key.sh
new file mode 100644
index 0000000..4c062a2
--- /dev/null
+++ b/roles/ns/files/gen-key.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+KEYNAME=$1
+
+if [ -z "$KEYNAME" ]; then
+ echo "Usage: $0 "
+ exit 1
+fi
+
+tsig-keygen -a hmac-sha512 "$KEYNAME"
diff --git a/roles/ns/files/switch-isp.sh b/roles/ns/files/switch-isp.sh
new file mode 100644
index 0000000..5444192
--- /dev/null
+++ b/roles/ns/files/switch-isp.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+ISP=$1
+
+if [ -z "$ISP" ]; then
+ echo "Usage: $0 "
+ exit 1
+fi
+
+KEY_FILE="/etc/bind/keys/wan.key"
+NS_DIR="/root/scripts/switch-isp"
+ISP_FILE="$NS_DIR/$ISP.ns"
+
+if [ ! -f "$ISP_FILE" ]; then
+ echo "ISP file for nsupdate not found: $ISP_FILE"
+ exit 2
+fi
+
+echo "Deleting ISP dependent DNS records."
+nsupdate -k "$KEY_FILE" "$NS_DIR/delete.ns"
+
+echo "Registering $ISP DNS records."
+nsupdate -k "$KEY_FILE" "$ISP_FILE"
diff --git a/roles/ns/files/sync-conf b/roles/ns/files/sync-conf
new file mode 100644
index 0000000..a3b947b
--- /dev/null
+++ b/roles/ns/files/sync-conf
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+if [ "$(hostname)" = "ns1" ]; then
+ partner=root@ns2.servers.dc.verdnatura.es
+else
+ partner=root@ns1.servers.dc.verdnatura.es
+fi
+
+confDir=/etc/bind
+
+echo "Restarting service."
+service bind9 restart
+
+if [ $? -eq "0" ]; then
+ echo "Synchronizing partner configuration."
+
+ scp "$confDir/named.conf.local" $partner:$confDir
+ scp "$confDir/named.conf.master" $partner:$confDir
+ scp "$confDir/named.conf.slave" $partner:$confDir
+
+ ssh "$partner" rm -rf "$confDir/keys"
+ ssh "$partner" mkdir "$confDir/keys"
+ scp -r "$confDir/keys/"* $partner:"$confDir/keys"
+
+ echo "Restarting partner service."
+ ssh $partner service bind9 restart
+fi
diff --git a/roles/ns/handlers/main.yml b/roles/ns/handlers/main.yml
new file mode 100644
index 0000000..840d041
--- /dev/null
+++ b/roles/ns/handlers/main.yml
@@ -0,0 +1,4 @@
+- name: restart-dns
+ systemd:
+ name: bind9
+ state: restarted
\ No newline at end of file
diff --git a/roles/ns/tasks/main.yml b/roles/ns/tasks/main.yml
new file mode 100644
index 0000000..0948ab4
--- /dev/null
+++ b/roles/ns/tasks/main.yml
@@ -0,0 +1,2 @@
+- import_tasks: ns.yml
+ tags: ns
diff --git a/roles/ns/tasks/ns.yml b/roles/ns/tasks/ns.yml
new file mode 100644
index 0000000..beb5d3a
--- /dev/null
+++ b/roles/ns/tasks/ns.yml
@@ -0,0 +1,41 @@
+- name: Update apt cache
+ apt:
+ update_cache: yes
+- name: Install bind package requirements
+ apt:
+ name: "{{ bind_packages }}"
+ state: present
+ install_recommends: no
+- name: Ensure BIND9 starts with IPv4 only (-4)
+ lineinfile:
+ path: /etc/default/named
+ regexp: '^OPTIONS='
+ line: 'OPTIONS="-u bind -4"'
+ backrefs: yes
+ notify: restart-dns
+- name: Create directory
+ file:
+ path: "{{ item.path }}"
+ state: directory
+ owner: "{{ item.group }}"
+ group: "{{ item.group }}"
+ mode: "{{ item.mode }}"
+ loop: "{{ directory }}"
+- name: Copy required files and scripts
+ copy:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ owner: "{{ item.group }}"
+ group: "{{ item.group }}"
+ mode: "{{ item.mode }}"
+ loop: "{{ required_files }}"
+- name: Deploy BIND config templates
+ template:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ owner: root
+ group: bind
+ mode: "{{ item.mode }}"
+ loop: "{{ bind_config_templates }}"
+ notify: restart-dns
+
diff --git a/roles/ns/templates/certbot.key b/roles/ns/templates/certbot.key
new file mode 100644
index 0000000..6e9f1b9
--- /dev/null
+++ b/roles/ns/templates/certbot.key
@@ -0,0 +1,4 @@
+key "certbot" {
+ algorithm hmac-sha512;
+ secret "{{ lookup(passbolt, 'certbot', folder_parent_id=passbolt_folder).password }}";
+};
diff --git a/roles/ns/templates/delete.ns b/roles/ns/templates/delete.ns
new file mode 100644
index 0000000..899f5cc
--- /dev/null
+++ b/roles/ns/templates/delete.ns
@@ -0,0 +1,4 @@
+{% for record in dns_records_delete %}
+update delete {{ record.name.ljust(30) }} A
+{% endfor %}
+send
diff --git a/roles/ns/templates/dhcp.key b/roles/ns/templates/dhcp.key
new file mode 100644
index 0000000..a732d1e
--- /dev/null
+++ b/roles/ns/templates/dhcp.key
@@ -0,0 +1,4 @@
+key "dhcp" {
+ algorithm hmac-sha512;
+ secret "{{ lookup(passbolt, 'dhcpkey', folder_parent_id=passbolt_folder).password }}";
+};
diff --git a/roles/ns/templates/isp1.ns b/roles/ns/templates/isp1.ns
new file mode 100644
index 0000000..57eba65
--- /dev/null
+++ b/roles/ns/templates/isp1.ns
@@ -0,0 +1,4 @@
+{% for record in dns_records_main %}
+update add {{ record.name.ljust(30) }} {{ record.ttl }} A {{ record.ip }}
+{% endfor %}
+send
diff --git a/roles/ns/templates/isp2.ns b/roles/ns/templates/isp2.ns
new file mode 100644
index 0000000..e89b38e
--- /dev/null
+++ b/roles/ns/templates/isp2.ns
@@ -0,0 +1,4 @@
+{% for record in dns_records_secondary %}
+update add {{ record.name.ljust(30) }} {{ record.ttl }} A {{ record.ip }}
+{% endfor %}
+send
diff --git a/roles/ns/templates/lan.key b/roles/ns/templates/lan.key
new file mode 100644
index 0000000..c99ad33
--- /dev/null
+++ b/roles/ns/templates/lan.key
@@ -0,0 +1,4 @@
+key "lan" {
+ algorithm hmac-sha512;
+ secret "{{ lookup(passbolt, 'lankey', folder_parent_id=passbolt_folder).password }}";
+};
diff --git a/roles/ns/templates/named.conf.j2 b/roles/ns/templates/named.conf.j2
new file mode 100644
index 0000000..32e13f7
--- /dev/null
+++ b/roles/ns/templates/named.conf.j2
@@ -0,0 +1,16 @@
+// This is the primary configuration file for the BIND DNS server named.
+//
+// Please read /usr/share/doc/bind9/README.Debian.gz for information on the
+// structure of BIND configuration files in Debian, *BEFORE* you customize
+// this configuration file.
+//
+// If you are just adding zones, please do that in /etc/bind/named.conf.local
+
+#include "/etc/bind/named.conf.options";
+include "/etc/bind/named.conf.local";
+{% if bind_is_master %}
+include "/etc/bind/named.conf.master";
+{% else %}
+include "/etc/bind/named.conf.slave";
+{% endif %}
+#include "/etc/bind/named.conf.default-zones";
diff --git a/roles/ns/templates/named.conf.local.j2 b/roles/ns/templates/named.conf.local.j2
new file mode 100644
index 0000000..09a8935
--- /dev/null
+++ b/roles/ns/templates/named.conf.local.j2
@@ -0,0 +1,30 @@
+include "/etc/bind/rndc.key";
+include "/etc/bind/keys/wan.key";
+include "/etc/bind/keys/lan.key";
+include "/etc/bind/keys/certbot.key";
+include "/etc/bind/keys/dhcp.key";
+
+server fe80::/16 { bogus yes; };
+
+acl lan {
+ 10.0.0.0/8;
+ 172.16.0.0/12;
+ 192.168.0.0/16;
+ };
+
+acl rfc5735 {
+ 0.0.0.0/8;
+ 169.254.0.0/16;
+ 192.0.0.0/24;
+ 192.0.2.0/24;
+ 192.88.99.0/24;
+ 198.18.0.0/15;
+ 198.51.100.0/24;
+ 203.0.113.0/24;
+ 224.0.0.0/4;
+ 240.0.0.0/4;
+ };
+
+controls {
+ inet * allow { localhost; lan; } keys { "rndc-key"; };
+};
diff --git a/roles/ns/templates/named.conf.master.j2 b/roles/ns/templates/named.conf.master.j2
new file mode 100644
index 0000000..3d0a2e0
--- /dev/null
+++ b/roles/ns/templates/named.conf.master.j2
@@ -0,0 +1,85 @@
+options {
+ directory "/var/cache/bind";
+ max-cache-size 500m;
+ auth-nxdomain no;
+ listen-on-v6 { none; };
+ version "DNS";
+ allow-update { none; };
+ blackhole { rfc5735; };
+
+ allow-transfer {
+ {% for ip in bind_allow_transfer %}
+ {{ ip }};
+ {% endfor %}
+ };
+};
+
+view "lan" {
+ match-clients {
+{% for item in key_match_clients_lan_master if item.startswith("!key") %}
+ {{ item }};
+{% endfor %}
+{% for item in acl_match_clients %}
+ {{ item }};
+{% endfor %}
+{% for item in key_match_clients_lan_master if not item.startswith("!key") %}
+ {{ item }};
+{% endfor %}
+ };
+
+ plugin query "filter-aaaa.so" {
+ filter-aaaa-on-v4 yes;
+ filter-aaaa-on-v6 yes;
+ filter-aaaa { any; };
+ };
+
+ recursion yes;
+ allow-recursion { any; };
+ empty-zones-enable yes;
+ notify yes;
+
+ include "/etc/bind/named.conf.default-zones";
+
+{% for zone in bind_zones.lan %}
+ zone "{{ zone.name }}" {
+ type master;
+ forwarders {};
+ allow-update { key {{ zone.key }}; };
+ file "{{ zone.file }}";
+ };
+{% endfor %}
+};
+
+view "wan" {
+ match-clients { any; };
+
+ recursion no;
+ allow-query-cache { none; };
+ empty-zones-enable no;
+
+ notify explicit;
+
+ also-notify {
+{% for entry in bind_also_notify %}
+ {{ entry.ip }} key {{ entry.key }};
+{% endfor %}
+ };
+
+{% for zone in bind_zones.wan %}
+{% if zone.in_view is defined %}
+{% for z in zone.in_view %}
+ zone "{{ z }}" {
+ in-view "lan";
+ };
+{% endfor %}
+{% else %}
+ zone "{{ zone.name }}" {
+ type master;
+ forwarders {};
+ allow-update { key {{ zone.key }}; };
+ file "{{ zone.file }}";
+ };
+{% endif %}
+{% endfor %}
+};
+
diff --git a/roles/ns/templates/named.conf.slave.j2 b/roles/ns/templates/named.conf.slave.j2
new file mode 100644
index 0000000..cfe4ade
--- /dev/null
+++ b/roles/ns/templates/named.conf.slave.j2
@@ -0,0 +1,78 @@
+options {
+ directory "/var/cache/bind";
+ max-cache-size 500m;
+ auth-nxdomain no;
+ listen-on-v6 { none; };
+ version "DNS";
+ allow-update { none; };
+ blackhole { rfc5735; };
+
+ notify no;
+ allow-transfer { none; };
+ masterfile-format text;
+};
+
+masters master-ips {
+{% for ip in bind_slave_masters %}
+ {{ ip }};
+{% endfor %}
+};
+
+view "lan" {
+ match-clients {
+ {%- for item in key_match_clients_lan_master if item.startswith("!key") -%}
+ {{ item }};
+ {% endfor %}
+{%- for item in acl_match_clients -%}
+ {{ item }};
+ {% endfor %}
+{%- for item in key_match_clients_lan_slave if not item.startswith("!key") -%}
+ {{ item }};
+ {% endfor %}
+};
+
+ plugin query "filter-aaaa.so" {
+ filter-aaaa-on-v4 yes;
+ filter-aaaa-on-v6 yes;
+ filter-aaaa { any; };
+ };
+ recursion yes;
+ allow-recursion { any; };
+ empty-zones-enable yes;
+
+ include "/etc/bind/named.conf.default-zones";
+
+ {% for zone in bind_zones.lan %}
+ zone "{{ zone.name }}" {
+ type slave;
+ masters { master-ips; };
+ forwarders {};
+ file "{{ zone.file }}";
+ };
+ {% endfor %}
+};
+
+view "wan" {
+ match-clients { any; };
+
+ recursion no;
+ allow-query-cache { none; };
+ empty-zones-enable no;
+
+ {% for zone in bind_zones.wan %}
+ {% if zone.in_view is defined %}
+ {% for z in zone.in_view %}
+ zone "{{ z }}" {
+ in-view "lan";
+ };
+ {% endfor %}
+ {% else %}
+ zone "{{ zone.name }}" {
+ type slave;
+ masters { {{ bind_slave_masters | join('; ') }} key {{ zone.key }}; };
+ forwarders {};
+ file "{{ zone.file }}";
+ };
+ {% endif %}
+ {% endfor %}
+};
diff --git a/roles/ns/templates/rndc.key b/roles/ns/templates/rndc.key
new file mode 100644
index 0000000..40d9600
--- /dev/null
+++ b/roles/ns/templates/rndc.key
@@ -0,0 +1,4 @@
+key "rndc-key" {
+ algorithm hmac-md5;
+ secret "{{ lookup(passbolt, 'rndc-key', folder_parent_id=passbolt_folder).password }}";
+};
diff --git a/roles/ns/templates/wan.key b/roles/ns/templates/wan.key
new file mode 100644
index 0000000..daa655c
--- /dev/null
+++ b/roles/ns/templates/wan.key
@@ -0,0 +1,4 @@
+key "wan-key" {
+ algorithm hmac-md5;
+ secret "{{ lookup(passbolt, 'wan-key', folder_parent_id=passbolt_folder).password }}";
+};
diff --git a/roles/pve/files/nrpe.cfg b/roles/pve/files/nrpe.d/95-pve.cfg
similarity index 100%
rename from roles/pve/files/nrpe.cfg
rename to roles/pve/files/nrpe.d/95-pve.cfg
diff --git a/roles/pve/files/nrpe/check_chrony b/roles/pve/files/plugins/check_chrony
similarity index 100%
rename from roles/pve/files/nrpe/check_chrony
rename to roles/pve/files/plugins/check_chrony
diff --git a/roles/pve/files/plugins/check_ilo2_health.pl b/roles/pve/files/plugins/check_ilo2_health.pl
new file mode 100755
index 0000000..4f5dba4
--- /dev/null
+++ b/roles/pve/files/plugins/check_ilo2_health.pl
@@ -0,0 +1,1234 @@
+#!/usr/bin/perl
+# icinga: -epn
+
+# check_ilo2_health.pl
+# based on check_stuff.pl and locfg.pl
+#
+# Nagios plugin using the Nagios::Plugin or Monitoring::Plugin module and the
+# HP Lights-Out XML PERL Scripting Sample from
+# ftp://ftp.hp.com/pub/softlib2/software1/pubsw-linux/p391992567/v60711/linux-LOsamplescripts3.00.0-2.tgz
+# checks if all sensors are ok, returns warning on high temperatures and
+# fan failures and critical on overall health failure
+#
+# Alexander Greiner-Baer 2007 - 2021
+# Matthew Stier 2011
+# Claudio Kuenzler 2021
+#
+# 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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, see .
+#
+#
+# Changelog:
+# 1.66 Wed, 21 Apr 2021 13:00:00 +0200
+# support both Nagios::Plugin and Monitoring::Plugin modules
+# add option "-W" for checking server's current power usage (in Watt)
+# 1.65 Sat, 06 Feb 2021 13:00:03 +0100
+# new option "--ignorecacheother|-O"
+# ignores cache status "Other"
+# 1.64 Tue, 02 Jun 2020 18:56:30 +0200
+# fix memory status (was Unknown on an otherwise healthy G8)
+# fix drive check for ilo2, was broken for 5 years...
+# new option "-L"
+# retrieve event log from RIB_INFO section
+# fix help message for "-l"
+# new option "-S"
+# check self_test value from https:///json/login_session
+# 1.63 Tue, 13 Nov 2018 18:41:48 +0100
+# support iLO5 firmware infos
+# applied patch from Rene Koch
+# ignore link unknown (option "-U")
+# 1.62 Mon, 14 May 2018 19:05:22 +0200
+# retrieve firmware infos only when using --getinfos
+# 1.61 Thu, 01 Jun 2017 20:05:04 +0200
+# fix for iLO4 2.50 link state when using --ignorelinkdown
+# 1.60 Wed, 12 Aug 2015 18:20:13 +0200
+# provide --sslopts to override defaults settings
+# fix }; for GET_EVENT_LOG
+# applied patch from Rene Koch :
+# handle missing values when using "-g"
+# CONTROLLER_STATUS not present on iLO4 anymore, use STATUS instead
+# put SSL_VERIFY_NONE in ''
+# 1.59 Wed, 28 Jan 2015 18:56:26 +0100
+# fix chunk size handling
+# corrected HTTP/1.1 HOST Header
+# applied patch from Max Winterstein :
+# sslv3 support
+# add retries option
+# catch XMLin() errors
+# applied patch from Rene Koch :
+# ignore battery not installed status (option "-x")
+# display server name (option "-g")
+# added warning for logical drive status "Degraded (Recovering)"
+# display system details (hardware model, serial number, SystemROM, iLO version)
+# display memory size and part number in case of memory failure
+# display hard disk model number in case of hard disk failure
+# display power supply part number in case of power supply failure
+# 1.58 Thu, 08 Aug 2013 18:17:02 +0200
+# ignore network link down status (option "-i")
+# added ENCLOSURE_ADDR to drive bay label (bay numbering was inconsistent)
+# ignore spare drives
+# 1.57 Fri, 17 May 2013 19:30:48 +0200
+# SSL_verify_mode SSL_VERIFY_NONE (IO::Socket::SSL changed default)
+# event log support for ilo2
+# disable embedded perl in icinga
+# 1.56 Fri, 15 Mar 2013 20:47:13 +0100
+# applied patch from Niklas Edmundsson :
+# check processor and memory details
+# applied patches from Dragan Sekerovic :
+# add location label to temperature (option "-b")
+# support for checking event log (option "-l")
+# add iLO version to output
+# add 2 new values for power supply status
+# --
+# 1.55 Sun, 05 Aug 2012 20:18:46 +0200
+# faulty drive (option "-c") exits now with CRITICAL instead of WARNING
+# applied patches from Niklas Edmundsson :
+# iLO4 RAID Controller Status
+# nodriveexit
+# add g6 drive status
+# overall health probes every element now
+# fixed bug with drive bay index
+# supports iLO3 with multiple backplanes
+# supports iLO4 disk check
+# Note: overall health may show drive/storage status, even without "-c"
+# --
+# 1.54 Thu, 14 Jun 2012 21:36:40 +0200
+# applied fix for iLO4 from Niklas Edmundsson
+# --
+# 1.53 Tue, 14 Feb 2012 19:47:40 +0100
+# added new disk bay variant
+# added power supply NOT APPLICABLE
+# --
+# 1.52 Wed, 27 Jul 2011 20:46:14 +0200
+# fixed again
+# --
+# 1.51 Mon, 25 Jul 2011 19:36:53 +0200
+# fixed bug with chunked replies by Matthew Stier
+# --
+# 1.5 Sat, 16 Jul 2011 10:02:10 +0200
+# optimized by Matthew Stier
+# --
+# 1.47 Thu, 14 Jul 2011 12:02:01 +0200
+# also print perfdata when temperature output is disabled
+# --
+# 1.46 Wed, 06 Jul 2011 08:46:51 +0200
+# fixed bug with nagios embedded perl interpreter
+# --
+# 1.45 Wed, 13 Oct 2010 22:17:01 +0200
+# new option "--ilo3"
+#
+# "--checkdrives" enhancements
+#
+# shows always "Failed" even when the power
+# supplies are redundant
+#
+# improved "--fanredundancy" and "--powerredundancy"
+# --
+# 1.44 Mon, 14 Dec 2009 20:11:37 +0100
+# new option "--checkdrives"
+# --
+# 1.43 Mon, 17 Aug 2009 20:50:13 +0200
+# new option "--fanredundancy"
+#
+# new option "--powerredundancy"
+# --
+# 1.42 Mon, 17 Aug 2009 12:52:23 +0100
+# check power supply and fans redundancy
+# gcivitella@enter.it
+# --
+# 1.41 Thu, 26 Jul 2007 17:42:36 +0200
+# perfdata label ist now quoted
+# --
+# 1.4 Mon, 25 Jun 2007 09:45:52 +0200
+# check vrm and power supply
+#
+# new option "--notemperatures"
+#
+# new option "--perfdata"
+#
+# some minor changes
+# --
+# 1.3beta Wed, 20 Jun 2007 09:57:46 +0200
+# do some error checking
+#
+# new option "--inputfile"
+# read bmc output from file
+# --
+# 1.2 Mon, 18 Jun 2007 09:33:17 +0200
+# new option "--skipsyntaxerrors"
+# ignores syntax errors in the xml output, maybe required by older firmwares
+#
+# introduce a date to the changelog ;)
+# --
+# 1.1 do not return warning if temperature status is n/a
+#
+# add "" to get rid of the
+# "Scripting utility should be updated to the latest version."
+# message
+# --
+# 1 initial release
+
+use strict;
+use warnings;
+use strict 'refs';
+
+use Sys::Hostname;
+use IO::Socket::SSL;
+use XML::Simple;
+
+$Net::SSLeay::slowly = 5;
+
+use vars qw($VERSION $PROGNAME $verbose $warn $critical $timeout $result);
+$VERSION = 1.66;
+
+$PROGNAME = "check_ilo2_health";
+
+# instantiate Nagios::Plugin or Monitoring::Plugin
+sub load_module {
+ my @names = @_;
+ my $module;
+ for my $name (@names) {
+ my $file = $name;
+ # requires need either a bare word or a file name
+ $file =~ s{::}{/}gsxm;
+ $file .= '.pm';
+ eval {
+ require $file;
+ $name->import();
+ $module = $name;
+ };
+ last if $module;
+ }
+ return $module;
+}
+
+my $plugin_module;
+BEGIN {
+ $plugin_module = load_module( 'Monitoring::Plugin', 'Nagios::Plugin' );
+}
+
+
+our $p = $plugin_module->new(
+ usage => "Usage: %s [-H ] [ -u|--user= ]
+ [ -p|--password= ] [ -f|--inputfile= ]
+ [ -a|--fanredundancy ] [ -c|--checkdrives ] [ -d|--perfdata ]
+ [ -e|--skipsyntaxerrors ] [ -n|--notemperatures ] [ -3|--ilo3 ]
+ [ -o|--powerredundancy ] [ -b|--locationlabel ] [ -l|--eventlogcheck]
+ [ -i|--ignorelinkdown ] [ -x|--ignorebatterymissing ] [ -s|--sslv3 ]
+ [ -t ] [ -r ] [ -g|--getinfos ] [ --sslopts ]
+ [ -U|--ignorelinkunknown ] [ -L|--eventlogiLO ] [ -S|--iLOselftest ]
+ [ -O|--ignorecacheother ] [ -W|--powerusage ]
+ [ -v|--verbose ] ",
+ version => $VERSION,
+ blurb => 'This plugin checks the health status on a remote iLO2|3|4|5 device
+and will return OK, WARNING or CRITICAL. iLO (integrated Lights-Out)
+can be found on HP Proliant servers.'
+);
+
+$p->add_arg(
+ spec => 'host|H=s',
+ help =>
+ qq{-H, --host=STRING
+ Specify the host on the command line.},
+);
+
+# add all arguments
+$p->add_arg(
+ spec => 'user|u=s',
+ help =>
+ qq{-u, --user=STRING
+ Specify the username on the command line.},
+);
+
+$p->add_arg(
+ spec => 'password|p=s',
+ help =>
+ qq{-p, --password=STRING
+ Specify the password on the command line.},
+);
+
+$p->add_arg(
+ spec => 'inputfile|f=s',
+ help =>
+ qq{-f, --inputfile=STRING
+ Read input from file.},
+);
+
+$p->add_arg(
+ spec => 'fanredundancy|a',
+ help =>
+ qq{-a, --fanredundancy
+ Check fan redundancy},
+);
+
+$p->add_arg(
+ spec => 'checkdrives|c',
+ help =>
+ qq{-c, --checkdrives
+ Check drive bays.},
+);
+
+$p->add_arg(
+ spec => 'perfdata|d',
+ help =>
+ qq{-d, --perfdata
+ Enable perfdata on output.},
+);
+
+$p->add_arg(
+ spec => 'locationlabel|b',
+ help =>
+ qq{-b, --locationlabel
+ Show temperature with location.},
+);
+
+$p->add_arg(
+ spec => 'eventlogcheck|l',
+ help =>
+ qq{-l, --eventlogcheck
+ Parse iLO event log "Integrated Management Log" (SERVER_INFO) for interesting events (f.e. broken memory).},
+);
+
+$p->add_arg(
+ spec => 'eventlogiLO|L',
+ help =>
+ qq{-L, --eventlogiLO
+ Parse iLO event log "iLO Event Log" (RIB_INFO) for interesting events (f.e. Embedded media manager failed media attach).},
+);
+
+$p->add_arg(
+ spec => 'skipsyntaxerrors|e',
+ help =>
+ qq{-e, --skipsyntaxerrors
+ Skip syntax errors on older firmwares.},
+);
+
+$p->add_arg(
+ spec => 'ignorebatterymissing|x',
+ help =>
+ qq{-x, --ignorebatterymissing
+ Ignore Battery missing status.},
+);
+
+$p->add_arg(
+ spec => 'ignorelinkdown|i',
+ help =>
+ qq{-i, --ignorelinkdown
+ Ignore NIC Link Down status (iLO4).},
+);
+
+$p->add_arg(
+ spec => 'ignorelinkunknown|U',
+ help =>
+ qq{-U, --ignorelinkunknown
+ Ignore NIC Link Unknown status (iLO5).},
+);
+
+$p->add_arg(
+ spec => 'notemperatures|n',
+ help =>
+ qq{-n, --notemperatures
+ Disable temperature listing.},
+);
+
+$p->add_arg(
+ spec => 'powerredundancy|o',
+ help =>
+ qq{-o, --powerredundancy
+ Check power redundancy.},
+);
+
+$p->add_arg(
+ spec => 'getinfos|g',
+ help =>
+ qq{-g, --getinfos
+ Display additional infos like firmware version and servername. May need increased timeout.},
+);
+
+$p->add_arg(
+ spec => 'ilo3|3',
+ help =>
+ qq{-3, --ilo3
+ Check iLO version >= 3 device.},
+);
+
+$p->add_arg(
+ spec => 'retries|r=i',
+ help =>
+ qq{-r, --retries=INTEGER
+ Number of retries.},
+);
+
+$p->add_arg(
+ spec => 'sslv3|s',
+ help =>
+ qq{-s, --sslv3
+ Use sslv3 for connection.},
+);
+
+$p->add_arg(
+ spec => 'sslopts=s',
+ help =>
+ qq{--sslopts
+ Sets IO::Socket:SSL Options, defaults to 'SSL_verify_mode => SSL_VERIFY_NONE'.
+ Some firmware may need --sslopts 'SSL_verify_mode => SSL_VERIFY_NONE, SSL_version => "TLSv1"'.},
+);
+
+$p->add_arg(
+ spec => 'selftest|S',
+ help =>
+ qq{-S, --selftest
+ Make additional check for iLO self test from https:///json/login_session. May only work on iLO5.},
+);
+
+$p->add_arg(
+ spec => 'ignorecacheother|O',
+ help =>
+ qq{-O, --ignorecacheother
+ Ignore cache status "Other".},
+);
+
+$p->add_arg(
+ spec => 'powerusage|W',
+ help =>
+ qq{-W, --powerusage
+ Check servers current power usage (in Watt)},
+);
+
+# parse arguments
+$p->getopts;
+
+my $return = "OK";
+my $message = "";
+our $xmlinput = "";
+our $isinput = 0;
+our $drive_input = "";
+our $is_drive_input = 0;
+our $drive_xml_broken = 0;
+our $client;
+our $is_event_input = 0;
+our $event_severity = "";
+our $event_class = "";
+our $event_description = "";
+our %event_status;
+my $host = $p->opts->host;
+my $hostname = $p->opts->host;
+my $username = $p->opts->user;
+my $password = $p->opts->password;
+my $inputfile = $p->opts->inputfile;
+our $skipsyntaxerrors = defined($p->opts->skipsyntaxerrors) ? 1 : 0;
+my $optfanredundancy = defined($p->opts->fanredundancy) ? 1 : 0;
+my $optpowerredundancy = defined($p->opts->powerredundancy) ? 1 : 0;
+my $notemperatures = defined($p->opts->notemperatures) ? 1 : 0;
+our $optcheckdrives = defined($p->opts->checkdrives) ? 1 : 0;
+my $optilo3 = defined($p->opts->ilo3) ? 1 : 0;
+my $iloboardversion = defined($p->opts->ilo3) ? "ILO>=3" : "ILO2";
+my $perfdata = defined($p->opts->perfdata) ? 1 : 0;
+my $locationlabel = defined($p->opts->locationlabel) ? 1 : 0;
+my $eventlogcheck = defined($p->opts->eventlogcheck) ? 1 : 0;
+my $eventlogiLO = defined($p->opts->eventlogiLO) ? 1 : 0;
+my $ignorelinkdown = defined($p->opts->ignorelinkdown) ? 1 : 0;
+my $ignorelinkunknown = defined($p->opts->ignorelinkunknown) ? 1 : 0;
+my $ignorebatterymissing = defined($p->opts->ignorebatterymissing) ? 1 : 0;
+my $optpowerusage = defined($p->opts->powerusage) ? 1 : 0;
+my $getinfos = defined($p->opts->getinfos) ? 1 : 0;
+our %drives;
+our $drive;
+our $drivestatus;
+our $product_name = "";
+our $serial_number = "";
+our $server_name = "";
+my $retries=0;
+my $xml;
+my $sslv3 = defined($p->opts->sslv3) ? 1 : 0;
+my $sslopts = 'SSL_verify_mode => SSL_VERIFY_NONE';
+our @product;
+our @serial;
+our @sname;
+my $optselftest = defined($p->opts->selftest) ? 1 : 0;
+my $ignorecacheother = defined($p->opts->ignorecacheother) ? 1 : 0;
+
+$message = "(Board-Version: $iloboardversion) ";
+
+unless ( ( defined($inputfile) ) ||
+ ( defined($host) && defined($username) && defined($password) ) ) {
+ $p->nagios_die("ERROR: Missing host, password and user.");
+}
+
+if ( defined ( $p->opts->retries ) ) {
+ $retries = $p->opts->retries;
+}
+
+if ( defined ( $p->opts->sslopts ) ) {
+ $sslopts = $p->opts->sslopts;
+}
+
+alarm $p->opts->timeout;
+
+my $boundary;
+our $sendsize;
+my $localhost = hostname() || 'localhost';
+print "hostname is $localhost\n" if ( $p->opts->verbose );
+
+for (my $i=0;$i<=$retries;$i++) {
+ print "retry: $i\n" if ( $p->opts->verbose );
+ unless ( defined($inputfile) ) {
+ # query code from locfg.pl
+ # Set the default SSL port number if no port is specified
+ $host .= ":443" unless ($host =~ m/:/);
+ #
+ # Open the SSL connection and the input file
+ $client = new IO::Socket::SSL->new(PeerAddr => $host, eval $sslopts, $sslv3 ?
+ ( SSL_version => 'SSLv3' ) : () );
+ unless ( $client ) {
+ $p->nagios_exit(
+ return_code => "UNKNOWN",
+ message => "ERROR: Failed to establish SSL connection with $host $! $SSL_ERROR."
+ );
+ }
+
+ if ( $optilo3 ) {
+ print "sending ilo3\n" if ( $p->opts->verbose );
+ my $cmd = '';
+ $cmd .= '';
+ $cmd .= '';
+ $cmd .= '';
+ $cmd .= '';
+ $cmd .= '';
+ if ( $eventlogcheck ) {
+ $cmd .= '';
+ }
+ if ( $getinfos ) {
+ $cmd .= '';
+ $cmd .= '';
+ $cmd .= '';
+ }
+ $cmd .= '';
+ if ( $eventlogiLO ) {
+ $cmd .= '';
+ $cmd .= '';
+ $cmd .= '';
+ }
+ $cmd .= '';
+ $cmd .= '';
+ $cmd .= "\r\n";
+ send_or_calculate(0,$cmd);
+
+ send_to_client(0, "POST /ribcl HTTP/1.1\r\n");
+ send_to_client(0, "HOST: $hostname\r\n"); # Mandatory for http 1.1
+ send_to_client(0, "TE: chunked\r\n");
+ send_to_client(0, "Connection: Close\r\n"); # Required
+ send_to_client(0, "Content-length: $sendsize\r\n"); # Mandatory for http 1.1
+ send_to_client(0, "\r\n");
+ send_or_calculate(1,$cmd); #Send it to iLO
+ }
+ else {
+ # send xml to BMC
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ if ( $eventlogcheck ) {
+ print $client '' . "\r\n";
+ }
+ if ( $getinfos ) {
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ }
+ print $client '' . "\r\n";
+ if ( $eventlogiLO ) {
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ }
+ print $client '' . "\r\n";
+ print $client '' . "\r\n";
+ }
+ }
+ else {
+ open($client,$inputfile) or $p->nagios_die("ERROR: $inputfile not found");
+ }
+
+# retrieve data
+ if ( $optilo3 && !$inputfile ) {
+ read_chunked_reply();
+ }
+ else {
+ while (my $ln = <$client>) {
+ parse_reply($ln);
+ }
+ close $client;
+ }
+
+# parse with XML::Simple
+ if ( $xmlinput && $isinput == 0 ) {
+ $xml = eval { XMLin($xmlinput, ForceArray => 1) };
+ if ( $@ ) {
+ if ( $i < $retries ) {
+ next;
+ }
+ $p->nagios_exit(
+ return_code => "UNKNOWN",
+ message => "ERROR: $@"
+ );
+ }
+ else {
+ last;
+ }
+ }
+ else {
+ $p->nagios_exit(
+ return_code => "UNKNOWN",
+ message => "ERROR: No parseable output."
+ );
+ }
+}
+
+if ( $getinfos ) {
+ $serial_number = "";
+ if ( defined $serial[3] ) {
+ $serial[3] =~ tr/ //ds ;
+ $serial_number = "Serial: $serial[3]";
+ }
+ $server_name = "";
+ $server_name = " - Servername: $sname[1]" if defined $sname[1];
+ my $system_rom = undef;
+ my $firmware_name = undef;
+ my $firmware_version = undef;
+
+ # loop through firmware hash
+ foreach my $index (keys %{ $xml->{'FIRMWARE_INFORMATION'}[0] }) {
+ if (defined $xml->{'FIRMWARE_INFORMATION'}[0]->{$index}[0]->{'FIRMWARE_NAME'}[0]->{'VALUE'}) {
+ if ($xml->{'FIRMWARE_INFORMATION'}[0]->{$index}[0]->{'FIRMWARE_NAME'}[0]->{'VALUE'} =~ m/^iLO/) {
+ $firmware_name = $xml->{'FIRMWARE_INFORMATION'}[0]->{$index}[0]->{'FIRMWARE_NAME'}[0]->{'VALUE'};
+ $firmware_version = $xml->{'FIRMWARE_INFORMATION'}[0]->{$index}[0]->{'FIRMWARE_VERSION'}[0]->{'VALUE'};
+ } elsif ($xml->{'FIRMWARE_INFORMATION'}[0]->{$index}[0]->{'FIRMWARE_NAME'}[0]->{'VALUE'} eq "HP ProLiant System ROM" || $xml->{'FIRMWARE_INFORMATION'}[0]->{$index}[0]->{'FIRMWARE_NAME'}[0]->{'VALUE'} eq "System ROM") {
+ $system_rom = $xml->{'FIRMWARE_INFORMATION'}[0]->{$index}[0]->{'FIRMWARE_VERSION'}[0]->{'VALUE'};
+ }
+ }
+ }
+
+ my $product_name = undef;
+ if (! defined $product[1]) {
+ $product_name = "Unknown product";
+ } else {
+ $product_name = $product[1]
+ }
+ $serial_number = "Unknown serial" if ! defined $serial_number;
+ $firmware_name = "iLO" if ! defined $firmware_name;
+ $firmware_version = "Unknown firmware version" if ! defined $firmware_version;
+ $server_name = "Unknown server name" if ! defined $server_name;
+
+ if (defined $system_rom) {
+ $message = "($product_name - SystemROM: $system_rom - $serial_number - $firmware_name FW $firmware_version" . "$server_name) ";
+ } else {
+ $message = "($product_name - $serial_number - $firmware_name FW $firmware_version" . "$server_name) ";
+ }
+}
+
+my $drive_xml;
+if ( $optcheckdrives && !$drive_xml_broken ) {
+ if ( $drive_input && $is_drive_input == 0 ) {
+ $drive_xml = eval { XMLin($drive_input, ForceArray => 1) };
+ if ( $@ ) {
+ $p->nagios_exit(
+ return_code => "UNKNOWN",
+ message => "ERROR: $@"
+ );
+ }
+ }
+ elsif ( ref $xml->{'STORAGE'}[0]->{'CONTROLLER'} ) {
+ # iLO4 specific, no need for $drive_input
+ }
+ else {
+ # No need to error out if host uncapable of checking drive status
+ warn "No drive_input found" if ( $p->opts->verbose );
+ }
+}
+
+my $temperatures = $xml->{'TEMPERATURE'}[0]->{'TEMP'};
+my $backplanes = $drive_xml->{'BACKPLANE'};
+my $raidcontroller = $xml->{'STORAGE'}[0]->{'CONTROLLER'};
+my @checks;
+push(@checks,$xml->{'FANS'}[0]->{'FAN'});
+push(@checks,$xml->{'VRM'}[0]->{'MODULE'});
+push(@checks,$xml->{'POWER_SUPPLIES'}[0]->{'SUPPLY'});
+if($xml->{'PROCESSORS'}) {
+ push(@checks,$xml->{'PROCESSORS'}[0]->{'PROCESSOR'});
+}
+my $memdetails;
+if($xml->{'MEMORY'}) {
+ $memdetails = $xml->{'MEMORY'}[0]->{'MEMORY_DETAILS'}[0];
+}
+my $health = $xml->{'HEALTH_AT_A_GLANCE'}[0];
+my $label;
+my $status;
+my $temperature;
+my $cautiontemp;
+my $criticaltemp;
+
+## check overall health status
+
+my $componentstate;
+foreach (keys %{$health}) {
+ $componentstate = $health->{$_}[0]->{'STATUS'};
+ if ( defined($componentstate) && ( $componentstate !~ m/^Ok$|^OTHER$|^NOT APPLICABLE$/i ) ) {
+ if ($_ eq 'STORAGE') {
+ if ( ref($raidcontroller) ) {
+ # For iLO4 we can look at the raid controller to get a more detailed
+ # status, so just log a WARNING unless we find something CRITICAL
+ # later on.
+ $return = "WARNING" unless ( $return eq "CRITICAL" );
+ }
+ else {
+ $return = "CRITICAL";
+ }
+ }
+ elsif ( ( $_ eq 'BATTERY' ) && $ignorebatterymissing && ( $componentstate =~ m/^Not Installed$/i ) ) {
+ next;
+ }
+ elsif ( ( $_ eq 'NETWORK' ) && $ignorelinkdown && ( $componentstate =~ m/^Link Down$/i || $componentstate =~ m/^Degraded$/i ) ) {
+ next;
+ }
+ elsif ( ( $_ eq 'NETWORK' ) && $ignorelinkunknown && ( $componentstate =~ m/^Unknown$/i ) ) {
+ next;
+ }
+ else {
+ $return = "CRITICAL";
+ }
+ $message .= "$_ $componentstate, ";
+ }
+}
+
+if ( $optpowerredundancy ) {
+ my $powerredundancy = $health->{'POWER_SUPPLIES'}[1]->{'REDUNDANCY'};
+ if ( defined($powerredundancy) &&
+ ( $powerredundancy !~ m/^Fully Redundant$|^REDUNDANT$|^NOT APPLICABLE$/i ) ) {
+ $return = "CRITICAL";
+ $message .= "Power supply $powerredundancy, ";
+ }
+}
+
+if ( $optpowerusage ) {
+ my $powerusage=$xml->{'POWER_SUPPLIES'}[0]->{'POWER_SUPPLY_SUMMARY'}[0]->{'PRESENT_POWER_READING'}[0]->{'VALUE'};
+ my ($powerperf) = $powerusage =~ m/(\d+) [Ww]atts/i;
+ $message .= "Power Usage: $powerusage, ";
+ if ( $perfdata ) {
+ $p->add_perfdata(
+ label => "power",
+ value => $powerperf,
+ uom => ""
+ );
+ }
+}
+
+if ( $optfanredundancy ) {
+ my $fanredundancy = $health->{'FANS'}[1]->{'REDUNDANCY'};
+ if ( defined($fanredundancy) &&
+ ( $fanredundancy !~ m/^Fully Redundant$|^REDUNDANT$|^NOT APPLICABLE$/i ) ) {
+ $return = "CRITICAL";
+ $message .= "Fans $fanredundancy, ";
+ }
+}
+
+# check fans, vrm and power supplies
+foreach my $check ( @checks ) {
+ if ( ref($check) ) {
+ foreach my $item ( @$check ) {
+ $label=$item->{'LABEL'}[0]->{'VALUE'};
+ $status=$item->{'STATUS'}[0]->{'VALUE'};
+ if ( defined($label) && defined($status) ) {
+ # misleading output on some iLO3 shows always failed, skip it
+ if ($label =~ m/^Power Supplies$/) {
+ next;
+ }
+ if ($label =~ m/^Power Supply/) {
+ # get details for power supplies
+ $label =~ s/ /_/g;
+ if ( ( $status !~ m"^Ok$|^Good|^n/a$|^Not Installed$|^Unknown$"i ) ) {
+ $return = "WARNING" unless ( $return eq "CRITICAL" );
+ if ( defined($item->{'MODEL'}[0]->{'VALUE'}) ) {
+ $message .= "$label is $status (ModelNumber: $item->{'MODEL'}[0]->{'VALUE'}) ";
+ }
+ else {
+ $message .= "$label is $status ";
+ }
+ }
+ next;
+ }
+ $label =~ s/ /_/g;
+ if ( ( $status !~ m"^Ok$|^Good|^n/a$|^Not Installed$|^Unknown$"i ) ) {
+ $return = "WARNING" unless ( $return eq "CRITICAL" );
+ $message .= "$label: $status, ";
+ }
+ }
+ }
+ }
+}
+
+# check memory status (iLO4 only?)
+if ( ref($memdetails) ) {
+ foreach my $loc ( sort keys %{$memdetails} ) {
+ foreach ( @{$memdetails->{$loc}} ) {
+ $status = $_->{'STATUS'}[0]->{'VALUE'};
+ if ( ( $status !~ m"^Ok$|^Good|^n/a$|^Not Present$|^Unknown$"i ) ) {
+ $return = "WARNING" unless ( $return eq "CRITICAL" );
+ my $socket = $_->{'SOCKET'}[0]->{'VALUE'};
+ my $size = $_->{'SIZE'}[0]->{'VALUE'};
+ my $part = $_->{'PART'}[0]->{'NUMBER'};
+ if ( defined $part ) {
+ # works only with new iLO4 firmware
+ $message .= "Mem $loc $socket: $status (Size: $size, PartNumber: $part), ";
+ }
+ else {
+ $message .= "Mem $loc $socket: $status (Size: $size), ";
+ }
+ }
+ }
+ }
+}
+
+# check newer drive bays (iLO3)
+if ( ref($backplanes) ) {
+ my $backplane = 0;
+ foreach ( @{$backplanes} ) {
+ if ( defined($_->{'ENCLOSURE_ADDR'}[0]->{'VALUE'} ) ) {
+ $backplane = $_->{'ENCLOSURE_ADDR'}[0]->{'VALUE'};
+ }
+ else {
+ $backplane++;
+ }
+ if ( $_->{'DRIVE_BAY'} ) {
+ for ( my $i=0; $i<= $#{$_->{'DRIVE_BAY'}}; $i++ ) {
+ $label=$backplane." ".$_->{'DRIVE_BAY'}[$i]->{'VALUE'};
+ $status=$_->{'STATUS'}[$i]->{'VALUE'};
+ $drives{$label}{'status'} = $status;
+ }
+ }
+ if ( $_->{'DRIVE'} ) {
+ for ( my $i=0; $i<= $#{$_->{'DRIVE'}}; $i++ ) {
+ $label=$backplane." ".$_->{'DRIVE'}[$i]->{'BAY'};
+ $status=$_->{'DRIVE_STATUS'}[$i]->{'VALUE'};
+ $drives{$label}{'status'} = $status;
+ }
+ }
+ }
+}
+
+# seems that iLO4 reads the state from the RAID controller, nice
+if ( ref($raidcontroller) ) {
+ foreach ( @{$raidcontroller} ) {
+ my $ctrllabel = $_->{'LABEL'}[0]->{'VALUE'};
+ my $ctrlstatus = $_->{'CONTROLLER_STATUS'}[0]->{'VALUE'} || $_->{'STATUS'}[0]->{'VALUE'};
+ if($ctrlstatus ne 'OK') {
+ $return = "CRITICAL";
+ $message .= "SmartArray $ctrllabel Status: $ctrlstatus, ";
+ }
+ my $cachestatus = $_->{'CACHE_MODULE_STATUS'}[0]->{'VALUE'};
+ if($cachestatus && $cachestatus ne 'OK') {
+ # FIXME: There are probably other valid cache module states that
+ # needs to be excluded.
+ if($ignorecacheother && $cachestatus eq 'Other') {
+ next;
+ }
+ $return = "CRITICAL";
+ $message .= "SmartArray $ctrllabel Cache Status: $cachestatus, ";
+ }
+ foreach ( @{$_->{'DRIVE_ENCLOSURE'}} ) {
+ my $enclabel = $_->{'LABEL'}[0]->{'VALUE'};
+ my $encstatus = $_->{'STATUS'}[0]->{'VALUE'};
+ my $encmodel = $_->{'MODEL_NUMBER'}[0]->{'VALUE'};
+ if($encstatus ne 'OK') {
+ $message .= "SmartArray $ctrllabel Enclosure $enclabel: $encstatus (ModelNumber: $encmodel) - check hardware status in OS, ";
+ $return = "CRITICAL";
+ }
+ }
+ foreach ( @{$_->{'LOGICAL_DRIVE'}} ) {
+ my $ldlabel = $_->{'LABEL'}[0]->{'VALUE'};
+ my $ldstatus = $_->{'STATUS'}[0]->{'VALUE'};
+ if($ldstatus ne 'OK') {
+ $message .= "SmartArray $ctrllabel LD $ldlabel: $ldstatus, ";
+ if($ldstatus eq 'Degraded (Rebuilding)' || $ldstatus eq 'Degraded (Recovering)') {
+ $return = "WARNING" unless ( $return eq "CRITICAL" );
+ }
+ else {
+ $return = "CRITICAL";
+ }
+ }
+ foreach ( @{$_->{'PHYSICAL_DRIVE'}} ) {
+ $label = "$ctrllabel $_->{'LABEL'}[0]->{'VALUE'}";
+ $status = $_->{'STATUS'}[0]->{'VALUE'};
+ my $model = $_->{'MODEL'}[0]->{'VALUE'};
+ $drives{$label}{'status'} = $status;
+ $drives{$label}{'model'} = $model;
+ }
+ }
+ }
+}
+
+# check drive bays
+if ( $optcheckdrives ) {
+ foreach ( sort keys(%drives) ) {
+ if ( ( $drives{$_}{'status'} !~ m"^(Ok)$|^(n/a)$|^(Spare)$|^(Not Installed)|^(Not Present/Not Installed)$|^(spun down)$"i ) ) {
+ $return = "CRITICAL";
+ $message .= "$_: ".$drives{$_}{'status'};
+ if (defined $drives{$_}{'model'}){
+ $message .= " (Drive ModelNumber: " . $drives{$_}{'model'} ."), ";
+ }
+ }
+ }
+}
+
+# check event logs
+if ( $eventlogcheck || $eventlogiLO ) {
+ foreach ( keys %event_status ) {
+ next if ( $event_status{$_} =~ m/Repaired/ );
+ $message .= " $_:$event_status{$_} ";
+ $return = "WARNING" unless ( $return eq "CRITICAL" );
+ }
+}
+
+# check self test
+if ( $optselftest ) {
+ my %ret = getselfteststatus();
+ if ($ret{'status'}) {
+ $return = "CRITICAL";
+ $message .= "iLO self test is $ret{'selftest'}, ";
+ if ( defined($ret{'faults'}) ) {
+ $message .= "self test fault is $ret{'faults'}, ";
+ }
+ }
+}
+
+
+unless ( $message ) {
+ $message .= "No faults detected, ";
+}
+
+# check temperatures
+if ( ref($temperatures) ) {
+ unless ( $notemperatures ) {
+ $message .= "Temperatures: ";
+ }
+ foreach my $temp ( @$temperatures ) {
+ $label=$temp->{'LABEL'}[0]->{'VALUE'};
+ if ( $locationlabel && defined($temp->{'LOCATION'}[0]->{'VALUE'}) ) {
+ $label .= " (" . $temp->{'LOCATION'}[0]->{'VALUE'} . ")";
+ }
+ $status=$temp->{'STATUS'}[0]->{'VALUE'};
+ $temperature=$temp->{'CURRENTREADING'}[0]->{'VALUE'};
+ if ( defined($label) && defined($status) && defined($temperature) ) {
+ $label =~ s/ /_/g;
+ unless ( ( $status =~ m"^Ok$|^n/a$|^Not Installed$"i ) ) {
+ $return = "WARNING" unless ( $return eq "CRITICAL" );
+ $message .= "$label ($status): $temperature, "
+ if ( $notemperatures );
+ }
+ unless ( ( $status =~ m"^n/a$|^Not Installed$"i ) ) {
+ $message .= "$label ($status): $temperature, "
+ unless ( $notemperatures );
+ if ( $perfdata ) {
+ $cautiontemp=$temp->{'CAUTION'}[0]->{'VALUE'};
+ $criticaltemp=$temp->{'CRITICAL'}[0]->{'VALUE'};
+ # Returned value can be 'N/A', enforce this being a number
+ if($cautiontemp && $cautiontemp !~ /^[0-9]+/) {
+ $cautiontemp=undef;
+ }
+ if($criticaltemp && $criticaltemp !~ /^[0-9]+/) {
+ $criticaltemp=undef;
+ }
+ if ( defined($cautiontemp) && defined($criticaltemp) ) {
+ $p->set_thresholds(
+ warning => $cautiontemp,
+ critical => $criticaltemp,
+ );
+ my $threshold = $p->threshold;
+ # add perfdata
+ $p->add_perfdata(
+ label => $label,
+ value => $temperature,
+ uom => "",
+ threshold => $threshold,
+ );
+ }
+ }
+ }
+ }
+ else {
+ $message .= "no reading, ";
+ }
+ }
+}
+
+
+# strip trailing ","
+$message =~ s/, $//;
+
+$p->nagios_exit(
+ return_code => $return,
+ message => $message
+);
+
+
+# send_to_client, send_or_calculate and read_chunked_reply
+# are adapted from locfg.pl
+
+sub send_to_client
+{
+ my ($send, $cmd) = @_;
+ print $cmd if ( $p->opts->verbose && length($cmd) < 1024 );
+ print $client $cmd;
+ $sendsize -= length($cmd) if ( $send );
+}
+
+sub send_or_calculate # used for iLO 3 only
+{
+ $sendsize = 0;
+ my ($send, $cmd) = @_;
+ if ($send) {
+ print $client $cmd;
+ }
+ $sendsize += length($cmd);
+ print "size $sendsize\n" if ( $p->opts->verbose );
+}
+
+
+sub read_chunked_reply # used for iLO 3 only
+{
+ my $ln = "";
+ my $lp = "";
+ my $hide = 1;
+ my $chunk = 1;
+ my $chunkSize;
+
+ while( 1 ) {
+ # Read a line
+ $ln = <$client>;
+ # Get length of line
+ my $length = length($ln);
+ # Exit loop if zero
+ if ( $length == 0 ) {
+ if ( $p->opts->verbose ) {
+ print "read_chunked_reply: read a zero-length line. Continue...\n";
+ }
+ last;
+ }
+ # Skip HTTP headers and first line of chunked responses
+ if ( $hide ) {
+ $hide = 0 if ( $ln =~ m/^\r\n$/ );
+ print "Head: " . $ln if ( $p->opts->verbose );
+ next;
+ }
+ # Get size of chunk
+ if ( $chunk ) {
+ print "chunk: " . $ln if ( $p->opts->verbose );
+ $ln =~ s/\r|\n//g;
+ $chunkSize = hex($ln);
+ $chunk = 0;
+ print "chunk size: $chunkSize\n" if ( $p->opts->verbose );
+ next;
+ }
+ # Last Chunk
+ if ( $chunkSize == 0 ) {
+ print "read_chunked_reply: reach end of responses.\n" if ($p->opts->verbose);
+ last;
+ }
+ # End of chunk, process incomplete line
+ if ( $chunkSize < $length ) {
+ $chunk = 1; # Next line, new chunk
+ $hide = 0; # Skip hide
+ $lp .= substr($ln, 0, $chunkSize); # Truncate and append
+ }
+ # End of chunk, process complete line
+ elsif ( $chunkSize == $length ) {
+ $chunk = 1; # Next line, new chunk
+ $hide = 1; # Hide new chunk's first line
+ $lp .= $ln; # Append line as-is
+ }
+ # Process line
+ else {
+ $chunkSize -= $length; # Decrement chunk size
+ $lp .= $ln; # Append line as-is
+ }
+ # Skip incomplete line
+ next unless ( $lp =~ m/\n$/ );
+ # Parse complete line
+ parse_reply($lp);
+ # Line parsed, clear line
+ $lp = "";
+ }
+ if ($client->error()) {
+ print "Error: connection error " . $client->error() . "\n";
+ }
+}
+
+sub parse_reply
+{
+ my ($line) = @_;
+ $line =~ s/\r\n$/\n/;
+ print $line if ( $p->opts->verbose );
+
+ if ( $getinfos ) {
+ # Prune all unnecessary lines
+ $isinput = 1 if ( $line =~ m"|" );
+ $xmlinput .= $line if ( $isinput );
+ $isinput = 0 if ( $line =~ m"|" );
+ $product_name = $line if ( $line =~ m"||" );
+ $xmlinput .= $line if ( $isinput );
+ $isinput = 0 if ( $line =~ m"||" );
+ }
+
+ # drive check needs special handling
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+
+ $is_drive_input = 1 if ( $line =~ m"" );
+ $drive_input .= $line if ( $is_drive_input );
+ $is_drive_input = 0 if ( $line =~ m"" );
+
+ # because on many (older?) iLOs drive status is not XML
+ if ($optcheckdrives) {
+ if ( $line =~ m/
+ ( $drive, $drivestatus ) = ( $line =~
+ m/Drive Bay: "(.*)"; status: "(.*)"; uid led: ".*"/ );
+ if ( defined($drive) && defined($drivestatus) ) {
+ $drives{$drive}{'status'} = $drivestatus;
+ }
+ }
+ if ( $line =~ m/
+ ( $drive, $drivestatus ) = ( $line =~
+ m/DRIVE BAY="(.*)" PRODUCT_ID=".*"STATUS="(.*)" UID_LED=".*"/ );
+ if ( defined($drive) && defined($drivestatus) ) {
+ $drives{$drive}{'status'} = $drivestatus;
+ }
+ }
+ }
+
+ if ( $eventlogcheck || $eventlogiLO ) {
+ $is_event_input = 1 if ( $line =~ m"" );
+ if ( $is_event_input == 0 && $event_class ) {
+ if ( ($event_class !~ m/POST|Maintenance/) && ( $event_severity !~ m/Informational/) ) {
+ $event_status{$event_description} = $event_severity;
+ $event_class = "";
+ }
+ }
+ }
+
+ if ( $line =~ m/MESSAGE='(.*)'/ ) {
+ my $msg = $1;
+
+ if ( $msg =~ m/No error/i ) {
+ # Skip
+ }
+ elsif ( $msg =~ m/Syntax error/i && $skipsyntaxerrors ) {
+ # Skip
+ }
+ else {
+ close $client;
+ $p->nagios_exit(
+ return_code => "UNKNOWN",
+ message => "ERROR: $msg."
+ );
+ }
+ }
+}
+
+sub getselfteststatus {
+ my ($json, $selftest, $selftestfaults, $selfteststatus);
+ # Set the default SSL port number if no port is specified
+ $host .= ":443" unless ($host =~ m/:/);
+ # Open the SSL connection and the input file
+ $client = new IO::Socket::SSL->new(PeerAddr => $host, eval $sslopts, $sslv3 ?
+ ( SSL_version => 'SSLv3' ) : () );
+ unless ( $client ) {
+ $p->nagios_exit(
+ return_code => "UNKNOWN",
+ message => "ERROR: Failed to establish SSL connection with $host $! $SSL_ERROR."
+ );
+ }
+
+ print "sending self test status request\n" if ( $p->opts->verbose );
+
+ send_to_client(0, "GET /json/login_session HTTP/1.1\r\n");
+ send_to_client(0, "HOST: $hostname\r\n"); # Mandatory for http 1.1
+ send_to_client(0, "Connection: Close\r\n"); # Required
+ send_to_client(0, "\r\n");
+
+ # retrieve data
+ while (my $ln = <$client>) {
+ $json .= $ln;
+ }
+ close $client;
+
+ if ( $json ) {
+ print "$json\n" if ( $p->opts->verbose );
+ ( $selftest ) = ( $json =~ m/"self_test":"([^"]+)"/ );
+ ( $selftestfaults ) = ( $json =~ m/"self_test_faults":"([^"]+)"/ );
+ $selfteststatus = 0;
+ if ( defined($selftest) ) {
+ unless ( $selftest =~ m/^OP_STATUS_OK$/ ) {
+ $selfteststatus = 1;
+ }
+ }
+ return ( 'selftest' => $selftest, 'faults' => $selftestfaults, 'status' => $selfteststatus);
+ }
+ else {
+ $p->nagios_exit(
+ return_code => "UNKNOWN",
+ message => "ERROR: No parseable output from self test."
+ );
+ }
+}
diff --git a/roles/pve/files/plugins/check_smartdisk.sh b/roles/pve/files/plugins/check_smartdisk.sh
new file mode 100755
index 0000000..605ea12
--- /dev/null
+++ b/roles/pve/files/plugins/check_smartdisk.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Checks status of disks SMART
+
+STATUS_LABEL="SMART Health Status:"
+STATUS_OK="$STATUS_LABEL OK"
+
+if [[ "$#" == "0" ]]; then
+ echo "Usage: $0 [ ... ]"
+ exit
+fi
+
+for DISK in "$@"
+do
+ STATUS=$(sudo /usr/sbin/smartctl -H -d scsi "$DISK" | grep "$STATUS_LABEL")
+
+ if [ "$STATUS" != "$STATUS_OK" ]; then
+ echo "CRITICAL: $DISK: $STATUS"
+ exit 2
+ fi
+done
+
+echo "OK: $STATUS_OK"
diff --git a/roles/pve/files/nrpe/check_zfs.pl b/roles/pve/files/plugins/check_zfs.pl
similarity index 100%
rename from roles/pve/files/nrpe/check_zfs.pl
rename to roles/pve/files/plugins/check_zfs.pl
diff --git a/roles/pve/tasks/nrpe.yml b/roles/pve/tasks/nrpe.yml
index e280c13..4d70576 100644
--- a/roles/pve/tasks/nrpe.yml
+++ b/roles/pve/tasks/nrpe.yml
@@ -1,14 +1,20 @@
+- name: Install PVE NRPE base packages
+ apt:
+ name:
+ - libxml-simple-perl
+ - libmonitoring-plugin-perl
+ state: present
- name: Set NRPE PVE configuration
copy:
- src: nrpe.cfg
- dest: /etc/nagios/nrpe.d/95-pve.cfg
+ src: nrpe.d/95-pve.cfg
+ dest: /etc/nagios/nrpe.d/
owner: root
group: root
mode: u=rw,g=r,o=r
notify: restart-nrpe
- name: Copy PVE NRPE plugins
copy:
- src: nrpe/
+ src: plugins/
dest: /etc/nagios/plugins/
owner: root
group: root
diff --git a/roles/pve/tasks/vhost.yml b/roles/pve/tasks/vhost.yml
index 77c25b5..3287de5 100644
--- a/roles/pve/tasks/vhost.yml
+++ b/roles/pve/tasks/vhost.yml
@@ -7,8 +7,8 @@
group: root
register: copy_result
-- name: Reboot the system if file was copied
- reboot:
- reboot_timeout: 600
- become: true
- when: copy_result.changed
\ No newline at end of file
+#- name: Reboot the system if file was copied
+# reboot:
+# reboot_timeout: 600
+# become: true
+# when: copy_result.changed
\ No newline at end of file