Merge branch 'unstable' into stable
This commit is contained in:
commit
f4ab4a5dc7
15 changed files with 1762 additions and 145 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -21,14 +21,27 @@ The **patch** part is incremented if multiple releases happen the same month
|
|||
|
||||
### Security
|
||||
|
||||
## [25.01] 2025-01-03
|
||||
|
||||
### Changed
|
||||
|
||||
* check_free_space: don't store outgoing emails (they can be quite big)
|
||||
* lxc: replace `lxc_template_mirror` with `lxc_template_options`
|
||||
* nagios-nrpe: extend check_nfsclient to accept cifs and smb2 stat type
|
||||
* nagios-nrpe: use locally stored upstream files for monitoringctl
|
||||
|
||||
### Fixed
|
||||
|
||||
* lxc: install of Jessie and Stretch LXC container
|
||||
|
||||
## [24.12.2] 2024-12-31
|
||||
|
||||
### Changed
|
||||
|
||||
* monitoringctl: follow v0.1 branch by default
|
||||
* munin: return 444 for our « bad bots » list
|
||||
* nagios-nrpe: change NRPE check_https definition
|
||||
* nagios-nrpe: check_nfsclient takes and needs now paths arguments
|
||||
* munin: return 444 for our « bad bots » list
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -198,6 +211,7 @@ The **patch** part is incremented if multiple releases happen the same month
|
|||
* evolinux-users: improve SSH configuration
|
||||
* evomaintenance: upstream release 24.05
|
||||
* evomaintenance: move upstream files into upstream folder
|
||||
* webapps/evoadmin-mail: package installed via public.evolix.org/evolix repo starting with Buster
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ then
|
|||
-e "s/__URGENCYFROM__/$URGENCYFROM/" \
|
||||
-e "s/__URGENCYTEL__/$URGENCYTEL/" \
|
||||
$email_template | \
|
||||
/usr/bin/mutt -H - $graph_list
|
||||
/usr/bin/mutt -e 'unset record' -H - $graph_list
|
||||
else
|
||||
sed -e "s/__TO__/$EVOMAINTMAIL/" \
|
||||
-e "s/__HOSTNAME__/$HOSTNAME/" \
|
||||
|
@ -160,7 +160,7 @@ else
|
|||
-e "s/__URGENCYFROM__/$URGENCYFROM/" \
|
||||
-e "s/__URGENCYTEL__/$URGENCYTEL/" \
|
||||
$email_template | \
|
||||
/usr/bin/mutt -H - $graph_list
|
||||
/usr/bin/mutt -e 'unset record' -H - $graph_list
|
||||
fi
|
||||
|
||||
rm -f $PID_FILE
|
||||
|
|
|
@ -8,9 +8,8 @@ lxc_network_type: "none"
|
|||
# Partition to bind mount into containers.
|
||||
lxc_mount_part: "/home"
|
||||
|
||||
# Mirror URL (optionnal).
|
||||
# For old Debian, use https://archive.debian.org/debian/
|
||||
lxc_template_mirror: ""
|
||||
# It's "template_options" used directly by community.general.lxc_container
|
||||
lxc_template_options: ""
|
||||
|
||||
# List of LXC containers to create.
|
||||
# Eg.:
|
||||
|
|
|
@ -7,7 +7,26 @@
|
|||
register: container_exists
|
||||
|
||||
- ansible.builtin.set_fact:
|
||||
lxc_template_mirror_option: "{{ '--mirror ' + lxc_template_mirror if lxc_template_mirror != '' else '' }}"
|
||||
lxc_template_options: >-
|
||||
--keyring=/etc/apt/trusted.gpg.d/freexian-archive-extended-lts.gpg
|
||||
--mirror=https://elts.evolix.org/extended-lts
|
||||
--security-mirror=http://archive.debian.org/debian-security
|
||||
{{ lxc_template_options }}
|
||||
when: release == "jessie"
|
||||
|
||||
- ansible.builtin.set_fact:
|
||||
lxc_template_options: >-
|
||||
--keyring=/usr/share/keyrings/debian-archive-removed-keys.gpg
|
||||
--mirror=http://archive.debian.org/debian
|
||||
--security-mirror=http://archive.debian.org/debian-security
|
||||
{{ lxc_template_options }}
|
||||
when: release == "stretch"
|
||||
|
||||
- name: "Install keyring for eLTS"
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- freexian-archive-keyring
|
||||
when: release == 'jessie' or release == 'stretch' or release == 'buster'
|
||||
|
||||
- name: "Create container {{ name }}"
|
||||
community.general.lxc_container:
|
||||
|
@ -15,7 +34,7 @@
|
|||
container_log: true
|
||||
template: debian
|
||||
state: stopped
|
||||
template_options: "--arch amd64 --release {{ release }} {{ lxc_template_mirror_option }}"
|
||||
template_options: "--arch amd64 --release {{ release }} {{ lxc_template_options }}"
|
||||
when: container_exists.stdout_lines | length == 0
|
||||
|
||||
- name: "Disable network configuration inside container {{ name }}"
|
||||
|
|
|
@ -30,3 +30,7 @@ nagios_plugins_directory: "/usr/local/lib/nagios/plugins"
|
|||
|
||||
monitoringctl_wrapper_path: /usr/local/lib/monitoringctl/alerts_wrapper
|
||||
monitoringctl_branch: v0.1
|
||||
|
||||
monitoringctl_lib_dir: /usr/local/lib/monitoringctl
|
||||
monitoringctl_bin_dir: /usr/local/bin
|
||||
monitoringctl_var_dir: /var/lib/monitoringctl
|
||||
|
|
133
nagios-nrpe/files/monitoringctl/upstream/alerts_wrapper.py
Executable file
133
nagios-nrpe/files/monitoringctl/upstream/alerts_wrapper.py
Executable file
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# alerts_wrapper wraps an NRPE command and overrides its return code if alert is disabled by monitoringctl.
|
||||
#
|
||||
# Source:
|
||||
# https://gitea.evolix.org/evolix/ansible-roles/src/branch/stable/nagios-nrpe/
|
||||
#
|
||||
|
||||
lib_dir = '/usr/local/lib/monitoringctl'
|
||||
prog_version = '0.1.2'
|
||||
prog_name = 'monitoringctl'
|
||||
|
||||
import sys, os
|
||||
import argparse
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
# Load common lib
|
||||
if os.path.isdir(lib_dir) and os.path.isfile(lib_dir + '/common.py'):
|
||||
sys.path.append(lib_dir)
|
||||
import common as lib
|
||||
else:
|
||||
print('ERROR: missing {}/monitoringctl_common module.'.format(lib_dir), file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
|
||||
# Check /var directory
|
||||
if not os.path.isdir(lib.var_dir):
|
||||
lib.error('ERROR: missing ${var_dir} directory.'.format(lib.var_dir))
|
||||
|
||||
|
||||
def show_help():
|
||||
help_str = '''
|
||||
alerts_wrapper wraps an NRPE command and overrides the return code.
|
||||
|
||||
Usage: alerts_wrapper --name <WRAPPER_NAME> -- <CHECK_COMMAND>
|
||||
Usage: alerts_wrapper <WRAPPER_NAME> <CHECK_COMMAND> (deprecated)
|
||||
|
||||
Options
|
||||
--name Wrapper name, it is very recommended to use the check name (like load, disk1…).
|
||||
Special name: 'all' is already hard-coded.
|
||||
-h, --help Print this message and exit.
|
||||
-V, --version Print version and exit.
|
||||
'''
|
||||
print(help_str)
|
||||
|
||||
|
||||
def enable_wrapper(wrapper_name):
|
||||
enable_command = '/usr/local/bin/monitoringctl enable {} --message \'Disable time expired.\''.format(wrapper_name)
|
||||
if os.getuid() != 0:
|
||||
enable_command = 'sudo ' + enable_command
|
||||
subprocess.run(enable_command, shell=True)
|
||||
|
||||
|
||||
def main(wrapper_name, check_command):
|
||||
disable_file = lib.get_disable_file_path(wrapper_name)
|
||||
is_disabled = lib.is_disabled_wrapper(wrapper_name)
|
||||
|
||||
if os.path.exists(disable_file) and not is_disabled:
|
||||
enable_wrapper(wrapper_name)
|
||||
|
||||
if is_disabled:
|
||||
check_command = 'timeout 8 ' + check_command
|
||||
|
||||
check_rc = 0
|
||||
try:
|
||||
stdout = subprocess.check_output(check_command, shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
check_rc = e.returncode
|
||||
stdout = e.output
|
||||
check_stdout = stdout.decode('utf8').strip() # strip() removes trailing \n
|
||||
|
||||
if is_disabled and check_rc == 124 and not check_stdout:
|
||||
check_stdout = 'Check timeout (> 8 sec)'
|
||||
|
||||
if is_disabled:
|
||||
# TODO: Pythonize these lib functions
|
||||
enable_time = lib.get_enable_time(wrapper_name)
|
||||
enable_delay = lib.calc_enable_delay(enable_time)
|
||||
delay_str = lib.delay_to_string(enable_delay)
|
||||
|
||||
enable_delay_delta = timedelta(seconds=enable_delay)
|
||||
enable_date = datetime.strftime(datetime.now() + enable_delay_delta, '%d %h %Y at %H:%M:%S')
|
||||
|
||||
disable_msg = lib.get_disable_message(wrapper_name)
|
||||
if disable_msg:
|
||||
disable_msg = '- {} '.format(disable_msg)
|
||||
|
||||
print('ALERT DISABLED until {} ({} left) {}- Check output: {}'.format(enable_date, delay_str, disable_msg, check_stdout))
|
||||
|
||||
else:
|
||||
print(check_stdout)
|
||||
|
||||
if is_disabled:
|
||||
if check_rc == 0:
|
||||
exit(0) # Nagios OK
|
||||
else:
|
||||
exit(1) # Nagios WARNING
|
||||
else:
|
||||
exit(check_rc)
|
||||
|
||||
|
||||
# Parse arguments
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='alerts_wrapper',
|
||||
description='alerts_wrapper wraps an NRPE command and overrides its return code if alert is disabled by monitoringctl.')
|
||||
|
||||
parser.add_argument('-V', '--version', action='store_true')
|
||||
parser.add_argument('-n', '--name', required=True, help='Wrapper name. Using the check name (like "load", "disk1"…) is strongly advised. "all" is a special name already hard-coded.')
|
||||
parser.add_argument('check_command', help='NRPE check command that will be run by the wrapper.')
|
||||
|
||||
# unknown_args is a workaround to get args starting with '--'
|
||||
args, unknown_args = parser.parse_known_args()
|
||||
|
||||
if args.version:
|
||||
lib.show_version(prog_name, prog_version)
|
||||
exit(0)
|
||||
|
||||
wrapper_name = args.name
|
||||
for i, arg in enumerate(unknown_args):
|
||||
if '"' in arg:
|
||||
unknown_args[i] = '\'{}\''.format(arg)
|
||||
else:
|
||||
unknown_args[i] = '"{}"'.format(arg)
|
||||
check_command = [args.check_command] + unknown_args
|
||||
check_command = ' '.join(check_command)
|
||||
|
||||
|
||||
# Run program
|
||||
main(wrapper_name, check_command)
|
||||
|
6
nagios-nrpe/files/monitoringctl/upstream/check-local
Normal file
6
nagios-nrpe/files/monitoringctl/upstream/check-local
Normal file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
# Replaced by monitoringctl.
|
||||
# check-local executable is kept for retrocompatibility purposes.
|
||||
|
||||
monitoringctl check "${1}"
|
||||
|
16
nagios-nrpe/files/monitoringctl/upstream/check-local_completion
Executable file
16
nagios-nrpe/files/monitoringctl/upstream/check-local_completion
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
function _get_checks_names() {
|
||||
grep --extended-regexp --no-filename --no-messages -R "command\[check_.*\]=" /etc/nagios/ | grep --invert-match --extended-regexp "^\s*#" | awk -F"[\\\[\\\]=]" '{sub("check_", "", $2); print $2}' | sort | uniq
|
||||
}
|
||||
|
||||
# List of available checks
|
||||
_check_local_dynamic_completion() {
|
||||
local cur=${COMP_WORDS[COMP_CWORD]};
|
||||
|
||||
COMPREPLY=( $( compgen -W '$(_get_checks_names)' -- "${cur}" ) );
|
||||
}
|
||||
|
||||
complete -F _check_local_dynamic_completion check-local
|
||||
|
||||
|
746
nagios-nrpe/files/monitoringctl/upstream/monitoringctl
Executable file
746
nagios-nrpe/files/monitoringctl/upstream/monitoringctl
Executable file
|
@ -0,0 +1,746 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
#set -x
|
||||
|
||||
readonly VERSION="0.1.2"
|
||||
|
||||
function show_help() {
|
||||
cat <<EOF
|
||||
${bold}monitoringctl${no_bold} version ${VERSION}.
|
||||
|
||||
${bold}monitoringctl${no_bold} gives some control over NRPE checks and alerts.
|
||||
|
||||
Usage: ${bold}monitoringctl${no_bold} [OPTIONS] ACTION ARGUMENTS
|
||||
|
||||
${bold}ACTIONS${no_bold}
|
||||
|
||||
${bold}help${no_bold}
|
||||
|
||||
Print this message and exit.
|
||||
|
||||
${bold}version${no_bold}
|
||||
|
||||
Print version number and exit.
|
||||
|
||||
${bold}list${no_bold}
|
||||
|
||||
List the checks defined in NRPE configuration.
|
||||
|
||||
${bold}status [CHECK_NAME]${no_bold}
|
||||
|
||||
Print whether alerts are enabled or not (silenced).
|
||||
If alerts are disabled (silenced), show disable message and time left before automatic re-enabling.
|
||||
|
||||
${bold}check [-b|--bypass-nrpe] [CHECK_NAME]${no_bold}
|
||||
|
||||
Ask CHECK_NAME to NRPE with an HTTP request.
|
||||
If CHECK_NAME is not provided, verify all checks.
|
||||
Also, print which command NRPE has supposedly run (from its configuration).
|
||||
|
||||
-b, --bypass-nrpe Execute directly command from NRPE configuration,
|
||||
as user nagios, without passing the request to NRPE.
|
||||
|
||||
${bold}disable [CHECK_NAME] [-d|--during DURATION] [-m|--message 'DISABLE MESSAGE']${no_bold}
|
||||
|
||||
Disable (silence) CHECK_NAME alerts for DURATION and write DISABLE MESSAGE into the log.
|
||||
Checks output is still printed, so alerts history won't be lost.
|
||||
If CHECK_NAME is not provided, disable all alerts.
|
||||
|
||||
-d, --during DURATION See section DURATION.
|
||||
-m, --message 'DISABLE MESSAGE' See section MESSAGE.
|
||||
-n, --non-interactive Do not ask confirmation (--message is mandatory then).
|
||||
|
||||
${bold}enable [CHECK_NAME] [-m|--message 'ENABLE MESSAGE']${no_bold}
|
||||
|
||||
Re-enable CHECK_NAME.
|
||||
If CHECK_NAME is not provided, re-enable all alerts.
|
||||
|
||||
-m, --message 'ENABLE MESSAGE' See section MESSAGE.
|
||||
|
||||
${bold}show CHECK_NAME${no_bold}
|
||||
|
||||
Show NPRE command(s) configured for CHECK_NAME
|
||||
|
||||
${bold}MESSAGE${no_bold}
|
||||
|
||||
Message that will be written in log and in check output when disabled.
|
||||
It is mandatory, but in interactive shells it can be omitted. In this case it is asked interactively.
|
||||
|
||||
Warning: In non-interactive shells (scripts, crons…) or with option --non-interactive,
|
||||
this option is mandatory.
|
||||
|
||||
${bold}DURATION${no_bold}
|
||||
|
||||
Time (string) during which alerts will be disabled (optional, default: "1h").
|
||||
|
||||
${bold}Format${no_bold}
|
||||
You can use 'd' (day), 'h' (hour) and 'm' (minute) , or a combination of them, to specify a duration.
|
||||
Examples: '2d', '1h', '10m', '1h10' ('m' is guessed).
|
||||
|
||||
${bold}OTHER NOTES${no_bold}
|
||||
|
||||
For actions disable, enable and status, CHECK_NAME is actually the --name option passed to alerts_wrapper, and not the NRPE check name. Both check name and alerts_wrapper --name option should be equal in NRPE configuration to avoid confusion.
|
||||
|
||||
Log path: ${log_file}
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
function list_checks() {
|
||||
checks="$(get_checks_names)"
|
||||
for check in $checks; do
|
||||
echo "${check}"
|
||||
done
|
||||
}
|
||||
|
||||
function check() {
|
||||
# $1: check name or empty
|
||||
readonly check_nrpe_bin="/usr/lib/nagios/plugins/check_nrpe"
|
||||
if [ ! -f "${check_nrpe_bin}" ]; then
|
||||
>&2 echo "${check_nrpe_bin} is missing, please install nagios-nrpe-plugin package."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
conf_lines="$(get_nrpe_conf "${nrpe_conf_path}")"
|
||||
|
||||
server_address=$(echo "$conf_lines" | grep "server_address" | tail -n1 | cut -d'=' -f2)
|
||||
if [ -z "${server_address}" ]; then server_address="127.0.0.1"; fi
|
||||
|
||||
server_port=$(echo "$conf_lines" | grep "server_port" | tail -n1 | cut -d'=' -f2)
|
||||
if [ -z "${server_port}" ]; then server_port="5666"; fi
|
||||
|
||||
if [ -z "${1}" ]; then
|
||||
# Array header for multi-checks
|
||||
checks="$(get_checks_names)"
|
||||
header="Check\tStatus\tOutput (truncated)"
|
||||
underline="-----\t------\t------------------"
|
||||
str_out="\n${header}\n${underline}\n"
|
||||
else
|
||||
checks="${1}"
|
||||
fi
|
||||
|
||||
warn_options_not_ended_properly "${checks}"
|
||||
|
||||
for check in $checks; do
|
||||
printf "\033[KChecking %s…\r" "${check}"
|
||||
err_msg=""
|
||||
|
||||
check_commands="$(get_check_commands "${check}")"
|
||||
if [ -n "${check_commands}" ]; then
|
||||
check_command="$(echo "${check_commands}" | tail -n1)"
|
||||
if [ -n "${1}" ]; then
|
||||
printf "\033[K" # erase tmp line "Checking check_toto…"
|
||||
echo "Command played by NRPE:"
|
||||
echo -e " ${orange}${check_command}${nocolor}"
|
||||
|
||||
if is_wrapped "${check}"; then
|
||||
if [[ "${check_command}" == *" -- "* ]]; then
|
||||
check_command_no_wrapper="$(echo "${check_command}" | awk -F' -- ' '{print $2}')"
|
||||
echo "Command without 'alerts_wrapper':"
|
||||
echo -e " ${orange}${check_command_no_wrapper}${nocolor}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${bypass_nrpe}" = "False" ]; then
|
||||
request_command="${check_nrpe_bin} -H ${server_address} -p ${server_port} -b 127.0.0.1 -c check_${check} 2&>1"
|
||||
else
|
||||
if [ -n "${check_command}" ]; then
|
||||
request_command="sudo -u nagios -- ${check_command}"
|
||||
else
|
||||
if [ -z "${1}" ]; then
|
||||
err_msg="Check command not found in NRPE configuration."
|
||||
else
|
||||
err_msg="Error: no command found in NRPE configuration for check '${check}'. Aborted."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${err_msg}" ]; then
|
||||
check_output="$(${request_command})"
|
||||
rc="$?"
|
||||
if [ -z "${1}" ]; then
|
||||
check_output="$(echo "${check_output}" | sed 's/|/\n/g' | head -n1)"
|
||||
if [ "${#check_output}" -gt 60 ]; then
|
||||
check_output="$(echo "${check_output}" | cut -c-80) [...]"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
check_output="${err_msg}"
|
||||
rc="3"
|
||||
fi
|
||||
|
||||
case "${rc}" in
|
||||
0)
|
||||
rc_str="OK"
|
||||
color="${green}"
|
||||
;;
|
||||
1)
|
||||
rc_str="Warning"
|
||||
color="${orange}"
|
||||
;;
|
||||
2)
|
||||
rc_str="Critical"
|
||||
color="${red}"
|
||||
;;
|
||||
3)
|
||||
rc_str="Unknown"
|
||||
color="${purple}"
|
||||
;;
|
||||
*)
|
||||
rc_str="Unknown"
|
||||
color="${purple}"
|
||||
esac
|
||||
|
||||
if [ -z "${1}" ]; then
|
||||
str_out="${str_out}${color}${check}\t${rc_str}${nocolor}\t${check_output}\n"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "${1}" ]; then
|
||||
echo -e "${str_out}" | column -t -s $'\t'
|
||||
else
|
||||
printf "\033[K\n" # erase tmp line « Checking check_toto…»
|
||||
if [ "${bypass_nrpe}" = "False" ]; then
|
||||
echo -e "NRPE service output (on ${server_address}:${server_port}):\n"
|
||||
else
|
||||
echo -e "Direct check output (bypassing NRPE):\n"
|
||||
fi
|
||||
echo -e "${color}${check_output}${nocolor}\n" | sed 's/|/\n/g'
|
||||
exit "${rc}"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
function disable_alert() {
|
||||
# $1: wrapper name, $2: duration_sec, $3: disable message
|
||||
now_secs=$(date +"%s")
|
||||
disable_until_secs=$(( now_secs + ${2} ))
|
||||
disable_file_path="$(get_disable_file_path "${1}")"
|
||||
echo "${disable_until_secs}" > "${disable_file_path}"
|
||||
echo "$(get_real_user): \"${3}\"" >> "${disable_file_path}"
|
||||
chmod 0644 "${disable_file_path}"
|
||||
log "${1} alerts disabled by user $(get_real_user)"
|
||||
log "Disable message: ${3}"
|
||||
}
|
||||
|
||||
|
||||
function get_real_user() {
|
||||
real_user="$(logname)"
|
||||
if [ -z "${real_user}" ]; then
|
||||
if [ -n "${SUDO_USER}" ]; then
|
||||
real_user="${SUDO_USER}"
|
||||
else
|
||||
real_user="unknown"
|
||||
fi
|
||||
fi
|
||||
echo "${real_user}"
|
||||
}
|
||||
|
||||
|
||||
function enable_alert() {
|
||||
# $1: wrapper name, $2: enable message
|
||||
disable_file_path="$(get_disable_file_path "${1}")"
|
||||
if [ -e "${disable_file_path}" ]; then
|
||||
rm "${disable_file_path}"
|
||||
fi
|
||||
log "${1} alerts enabled by user $(get_real_user)"
|
||||
log "Enable message: ${2}"
|
||||
}
|
||||
|
||||
|
||||
function disable_alerts() {
|
||||
# $1: check name
|
||||
# $2: disable message
|
||||
if [ -z "${1}" ]; then
|
||||
checks="$(get_checks_names)"
|
||||
else
|
||||
checks="${1}"
|
||||
fi
|
||||
|
||||
warn_not_wrapped "${checks}"
|
||||
warn_options_not_ended_properly "${checks}"
|
||||
warn_wrapper_names "${checks}"
|
||||
|
||||
if [ -z "${2}" ]; then
|
||||
if [ "${is_interactive}" = "False" ]; then
|
||||
error "Error: disable message option is mandatory in non-interactive shell."
|
||||
fi
|
||||
echo -n "> Please provide a disable message (for logging and check output): "
|
||||
read -r message
|
||||
echo ''
|
||||
if [ -z "${message}" ]; then
|
||||
error "${red}Error:${nocolor} disable message is mandatory."
|
||||
fi
|
||||
else
|
||||
message="${2}"
|
||||
fi
|
||||
|
||||
default_msg=""
|
||||
if [ "${default_duration}" = "True" ]; then
|
||||
default_msg=" (use --during to change default time)"
|
||||
fi
|
||||
|
||||
if [ -z "${1}" ]; then
|
||||
check_txt="All checks"
|
||||
else
|
||||
check_txt="Check ${1}"
|
||||
fi
|
||||
|
||||
echo_box "${check_txt} will be disabled for ${duration}${default_msg}."
|
||||
cat <<EOF
|
||||
|
||||
Additional information:
|
||||
* Alerts history is kept in our monitoring system.
|
||||
* To see when the will be re-enabled, execute 'monitoringctl status ${1}'.
|
||||
* To re-enable alert(s) before ${duration}, execute as root or with sudo: 'monitoringctl enable ${1}'.
|
||||
|
||||
EOF
|
||||
|
||||
wrapper=""
|
||||
if [ -n "${1}" ]; then
|
||||
if is_check "${1}"; then
|
||||
wrapper="$(get_check_wrapper_name "${1}")"
|
||||
else
|
||||
wrapper="${1}"
|
||||
fi
|
||||
checks="$(get_wrapper_checks "${wrapper}")"
|
||||
n_checks="$(echo "${checks}" | wc -w)"
|
||||
if [ "${n_checks}" -gt 1 ]; then
|
||||
>&2 echo -e "${orange}Warning:${nocolor} because they have the same configuration, disabling ${1} will disable: ${checks}.\n"
|
||||
log "Warning: disabling ${1} will disable ${checks} (which have the same wrapper name)."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${is_interactive}" = "True" ]; then
|
||||
echo -n "> Confirm (y/N)? "
|
||||
read -r answer
|
||||
if [ "${answer}" != "Y" ] && [ "${answer}" != "y" ]; then
|
||||
echo -e "${orange}Canceled.${nocolor}" && exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
duration_sec=$(time_to_seconds "${duration}")
|
||||
if [ -z "${wrapper}" ]; then
|
||||
for wrapper_name in $(get_wrappers_names); do
|
||||
disable_alert "${wrapper_name}" "${duration_sec}" "${message}"
|
||||
done
|
||||
else
|
||||
disable_alert "${wrapper}" "${duration_sec}" "${message}"
|
||||
fi
|
||||
|
||||
if [ -n "${1}" ]; then
|
||||
if [ "${n_checks}" -eq 1 ]; then
|
||||
echo -e "${orange}Check ${1} alerts are now disabled for ${duration}.${nocolor}"
|
||||
else
|
||||
echo -e "${orange}Alerts are now disabled for ${duration} for checks: ${checks}.${nocolor}"
|
||||
fi
|
||||
else
|
||||
echo -e "${orange}All alerts are now disabled for ${duration}.${nocolor}"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
function enable_alerts() {
|
||||
# $1: check name, $2: enable message
|
||||
if [ -z "${2}" ]; then
|
||||
if [ "${is_interactive}" = "False" ]; then
|
||||
error "Error: disable message option is mandatory in non-interactive shell."
|
||||
fi
|
||||
echo -n "> Please provide an enable message (for logging): "
|
||||
read -r message
|
||||
echo ''
|
||||
if [ -z "${message}" ]; then
|
||||
error "${red}Error:${nocolor} disable message is mandatory."
|
||||
fi
|
||||
else
|
||||
message="${2}"
|
||||
fi
|
||||
|
||||
wrapper=""
|
||||
if [ -n "${1}" ]; then
|
||||
if is_check "${1}"; then
|
||||
wrapper="$(get_check_wrapper_name "${1}")"
|
||||
else
|
||||
wrapper="${1}"
|
||||
fi
|
||||
checks="$(get_wrapper_checks "${wrapper}")"
|
||||
n_checks="$(echo "${checks}" | wc -w)"
|
||||
if [ "${n_checks}" -gt 1 ]; then
|
||||
>&2 echo -e "${orange}Warning:${nocolor} because they have the same configuration, enabling ${1} will enable: ${checks}.\n"
|
||||
log "Warning: check ${1} will enable ${checks} (which have the same wrapper name)."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${wrapper}" ]; then
|
||||
for wrapper_name in $(get_wrappers_names); do
|
||||
enable_alert "${wrapper_name}" "${message}"
|
||||
done
|
||||
else
|
||||
enable_alert "${wrapper}" "${message}"
|
||||
fi
|
||||
|
||||
if [ -n "${1}" ]; then
|
||||
if [ "${n_checks}" -eq 1 ]; then
|
||||
echo -e "${green}Check ${1} alerts are now enabled.${nocolor}"
|
||||
else
|
||||
echo -e "${green}Alerts are now enabled for checks: ${checks}.${nocolor}"
|
||||
fi
|
||||
else
|
||||
echo -e "${green}All alerts are now enabled.${nocolor}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Show NRPE command(s) configured for a check
|
||||
function show_check_commands() {
|
||||
# $1: check name
|
||||
check_commands=$(get_check_commands "${1}")
|
||||
|
||||
if [ -z "${check_commands}" ]; then
|
||||
usage_error "Error: no command found in NRPE configuration for check '${1}."
|
||||
fi
|
||||
|
||||
warn_options_not_ended_properly "${1}"
|
||||
|
||||
n_commands="$(echo "${check_commands}" | wc -l)"
|
||||
if [ "${n_commands}" -ne 1 ]; then
|
||||
echo "Available commands (in config order, the last one overwrites the others):"
|
||||
echo " $check_commands"
|
||||
fi
|
||||
|
||||
check_command=$(echo "${check_commands}" | tail -n1)
|
||||
echo "Command used by NRPE:"
|
||||
echo " ${check_command}"
|
||||
|
||||
if [[ "${check_command}" == *" -- "* ]]; then
|
||||
check_command_no_wrapper="$(echo "${check_command}" | awk -F' -- ' '{print $2}')"
|
||||
echo
|
||||
echo "Command without 'alerts_wrapper':"
|
||||
echo -e " ${check_command_no_wrapper}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Print a warning if some wrappers have the same name
|
||||
# or if a name is different from the check.
|
||||
function warn_wrapper_names() {
|
||||
#$1: checks to verify
|
||||
warned="False"
|
||||
for check in ${1}; do
|
||||
wrapper_name="$(get_check_wrapper_name "${check}")"
|
||||
if [ -n "${wrapper_name}" ] && [ "${wrapper_name}" != "${check}" ]; then
|
||||
>&2 echo -e "${orange}Warning:${nocolor} ${check} check has wrapper name ${wrapper_name}."
|
||||
warned="True"
|
||||
fi
|
||||
done
|
||||
if [ "${warned}" = "True" ]; then
|
||||
>&2 echo -e "${orange}It is recommanded to name the wrappers the same as the checks.${nocolor}\n"
|
||||
fi
|
||||
}
|
||||
|
||||
# Print a warning if the wrapper does not end its options with -- (to not interpret the wrapped NRPE command options)
|
||||
function warn_options_not_ended_properly() {
|
||||
#$1: checks to verify
|
||||
wrappers_without_ddash='' # double dash --
|
||||
for check in ${1}; do
|
||||
if is_wrapped "${check}"; then
|
||||
check_command=$(get_check_commands "${check}" | tail -n1)
|
||||
if [[ ! ${check_command} == *" -- "* ]]; then
|
||||
wrappers_without_ddash="${wrappers_without_ddash} ${check}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ -n "${wrappers_without_ddash}" ]; then
|
||||
>&2 echo -e "${orange}Some wrappers do not end their options with '--' before the NRPE command:${wrappers_without_ddash}"
|
||||
>&2 echo -e "Use of '--' is strongly recommended to separate 'alerts_wrapper' and the wrapped command in NRPE configuration.${nocolor}\n"
|
||||
fi
|
||||
}
|
||||
|
||||
# Print a warning if some checks are not wrapped
|
||||
function warn_not_wrapped() {
|
||||
#$1: checks to verify
|
||||
unwrappeds="$(not_wrapped_checks)"
|
||||
unwrapped_checks="$(comm -12 <(echo "${1}") <(echo "${unwrappeds}"))"
|
||||
if [ -n "${unwrapped_checks}" ]; then
|
||||
n_checks="$(echo "${1}" | wc -w)"
|
||||
n_unwrapped="$(echo "${unwrapped_checks}" | wc -w)"
|
||||
if [ "${n_unwrapped}" == "${n_checks}" ]; then
|
||||
if [ "${n_unwrapped}" -eq 1 ]; then
|
||||
error "${red}Error:${nocolor} ${1} check is not wrapped, it cannot be disabled."
|
||||
else
|
||||
error "${red}Error:${nocolor} these checks are not wrapped, they cannot be disabled: $(echo "${unwrapped_checks}" | xargs)"
|
||||
fi
|
||||
else
|
||||
if [ "${n_unwrapped}" -eq 1 ]; then
|
||||
>&2 echo -e "${orange}Warning:${nocolor} ${unwrapped_checks} check is not wrapped, it will not be disabled."
|
||||
else
|
||||
>&2 echo -e -n "${orange}Warning:${nocolor} some checks are not configured, they will not be disabled: $(echo "${unwrapped_checks}" | xargs)\n\n"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Warning: some checks have no alerts_wrapper, they will not be disabled: $(echo "${unwrapped_checks}" | xargs)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Echo a message in a box
|
||||
function echo_box() {
|
||||
# $1: message
|
||||
msg_len="${#1}"
|
||||
line="$(printf '─%.0s' $(eval "echo {1.."${msg_len}"}"))"
|
||||
cat <<EOF
|
||||
┌${line}┐
|
||||
│${1}│
|
||||
└${line}┘
|
||||
EOF
|
||||
}
|
||||
|
||||
# Echo which checks are enabled or disabled and time left
|
||||
function alerts_status() {
|
||||
# $1: check name or empty
|
||||
if [ -z "${1}" ]; then
|
||||
checks="$(get_checks_names)"
|
||||
else
|
||||
checks="${1}"
|
||||
fi
|
||||
|
||||
warn_options_not_ended_properly "${checks}"
|
||||
warn_wrapper_names "${checks}"
|
||||
|
||||
header="Check\tStatus\tRe-enable time\tDisable message"
|
||||
underline="-----\t------\t--------------\t---------------"
|
||||
str_out="${header}\n${underline}\n"
|
||||
|
||||
for check in $checks; do
|
||||
enable_str=""
|
||||
status_str="Enabled"
|
||||
disable_msg=""
|
||||
if ! is_wrapped "${check}"; then
|
||||
status_str="Not configured"
|
||||
else
|
||||
is_disabled="$(is_disabled_check "${check}")"
|
||||
wrapper_name="$(get_check_wrapper_name "${check}")"
|
||||
if [ "${is_disabled}" = "True" ]; then
|
||||
status_str="Disabled"
|
||||
enable_time="$(get_enable_time "${wrapper_name}")"
|
||||
enable_delay="$(enable_delay "${enable_time}")"
|
||||
delay_str="$(delay_to_string "${enable_delay}")"
|
||||
enable_date="$(date --date "+${enable_delay} seconds" "+%d %h %Y at %H:%M:%S")"
|
||||
enable_str="${enable_date} (${delay_str} left)"
|
||||
disable_msg="$(get_disable_message "${wrapper_name}")"
|
||||
fi
|
||||
fi
|
||||
case "${status_str}" in
|
||||
"Enabled")
|
||||
color="${green}"
|
||||
;;
|
||||
"Disabled")
|
||||
color="${orange}"
|
||||
;;
|
||||
*)
|
||||
color="${red}"
|
||||
esac
|
||||
str_out="${str_out}${color}${check}\t${status_str}${nocolor}\t${enable_str}\t${disable_msg}\n"
|
||||
done
|
||||
|
||||
echo -e "${str_out}" | column -t -s $'\t'
|
||||
}
|
||||
|
||||
|
||||
### MAIN #########################################
|
||||
|
||||
red=''
|
||||
green=''
|
||||
orange=''
|
||||
purple=''
|
||||
nocolor=''
|
||||
bold=''
|
||||
no_bold=''
|
||||
|
||||
# Is interactive shell ?
|
||||
if [ -t 0 ] && [ -t 1 ]; then
|
||||
is_interactive="True"
|
||||
red="\e[0;31m"
|
||||
green="\e[0;32m"
|
||||
orange="\e[0;33m"
|
||||
purple="\e[0;35m"
|
||||
nocolor="\e[0m"
|
||||
bold="$(tput bold)"
|
||||
no_bold="$(tput sgr0)"
|
||||
else
|
||||
is_interactive="False"
|
||||
fi
|
||||
|
||||
# Load common functions and vars
|
||||
readonly lib_dir="/usr/local/lib/monitoringctl"
|
||||
if [ -r "${lib_dir}/common" ]; then
|
||||
# shellcheck source=monitoringctl_common
|
||||
source "${lib_dir}/common"
|
||||
else
|
||||
>&2 echo "Error: missing ${lib_dir}/common file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "${PATH}" =~ /usr/local/bin ]]; then
|
||||
PATH="/usr/local/bin:${PATH}"
|
||||
fi
|
||||
|
||||
# Must be root
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
>&2 echo "You need to be root (or use sudo) to run ${0}!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# No argument
|
||||
if [ "$#" = "0" ]; then
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Default arguments and options
|
||||
action=""
|
||||
message=""
|
||||
duration="${default_disabled_time}"
|
||||
bypass_nrpe="False"
|
||||
default_duration="True"
|
||||
|
||||
# Parse arguments and options
|
||||
while :; do
|
||||
case "${1}" in
|
||||
-h|-\?|--help)
|
||||
show_help
|
||||
exit 0;;
|
||||
-V|--version)
|
||||
show_version
|
||||
exit 0;;
|
||||
-b|--bypass-nrpe)
|
||||
bypass_nrpe="True"
|
||||
shift;;
|
||||
-n|--non-interactive)
|
||||
is_interactive="False"
|
||||
shift;;
|
||||
-d|--during)
|
||||
if [ "${default_duration}" = "False" ]; then
|
||||
usage_error "Option --during: defined multiple times."
|
||||
fi
|
||||
if [ "$#" -lt 2 ]; then
|
||||
usage_error "Option --during: missing value."
|
||||
fi
|
||||
if filter_duration "${2}"; then
|
||||
duration="${2}"
|
||||
else
|
||||
usage_error "Option --during: \"${2}\" is not a valid duration."
|
||||
fi
|
||||
default_duration="False"
|
||||
shift; shift;;
|
||||
-m|--message)
|
||||
if [ "$#" -lt 2 ]; then
|
||||
usage_error "Option --message: missing message string."
|
||||
fi
|
||||
message="${2}"
|
||||
shift; shift;;
|
||||
status|check|enable|disable|show|list|help|version)
|
||||
action="${1}"
|
||||
shift;;
|
||||
*)
|
||||
if [ -z "${1}" ]; then
|
||||
break
|
||||
else
|
||||
arg="${1}"
|
||||
fi
|
||||
|
||||
# Add some flexibility with - and _ in check name
|
||||
if ! is_check "${arg}"; then
|
||||
if [[ "${1}" == *"-"* ]]; then
|
||||
arg_underscore="$(echo "${arg}" | tr '-' '_')"
|
||||
if is_check "${arg_underscore}"; then
|
||||
>&2 echo -e "${orange}Warning: '${arg}' real name is '${arg_underscore}'!${nocolor}\n"
|
||||
arg="${arg_underscore}"
|
||||
fi
|
||||
fi
|
||||
if [[ "${1}" == *"_"* ]]; then
|
||||
arg_dash="$(echo "${arg}" | tr '_' '-')"
|
||||
if is_check "${arg_dash}"; then
|
||||
>&2 echo -e "${orange}Warning: '${arg}' real name is '${arg_dash}'!${nocolor}\n"
|
||||
arg="${arg_dash}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
case "${action}" in
|
||||
status|check)
|
||||
if is_check "${arg}"; then
|
||||
check_name="${arg}"
|
||||
else
|
||||
usage_error "Action ${action}: unknown check '${arg}'."
|
||||
fi
|
||||
;;
|
||||
show)
|
||||
if is_check "${arg}"; then
|
||||
check_name="${arg}"
|
||||
else
|
||||
usage_error "Action ${action}: unknown check '${arg}'."
|
||||
fi
|
||||
;;
|
||||
enable|disable)
|
||||
if is_wrapper "${arg}" || is_check "${arg}"; then
|
||||
check_name="${arg}"
|
||||
else
|
||||
# We use the word "check" for the end user,
|
||||
# but this is actually "unknown wrapper"
|
||||
usage_error "Action ${action}: unknown check '${arg}'."
|
||||
fi
|
||||
;;
|
||||
help)
|
||||
show_help
|
||||
exit 0;;
|
||||
version)
|
||||
show_version
|
||||
exit 0;;
|
||||
*)
|
||||
usage_error "Missing or invalid ACTION argument."
|
||||
;;
|
||||
esac
|
||||
shift;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
usage_error "Too many arguments."
|
||||
fi
|
||||
|
||||
case "${action}" in
|
||||
show)
|
||||
if [ -z "${check_name}" ]; then
|
||||
usage_error "Action ${action}: missing CHECK_NAME argument."
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! "${action}" = "disable" ]; then
|
||||
if [ "${default_duration}" = "False" ]; then
|
||||
usage_error "Action ${action}: there is no --during option."
|
||||
fi
|
||||
fi
|
||||
|
||||
case "${action}" in
|
||||
list)
|
||||
list_checks
|
||||
;;
|
||||
status)
|
||||
alerts_status "${check_name}"
|
||||
;;
|
||||
check)
|
||||
check "${check_name}"
|
||||
;;
|
||||
show)
|
||||
show_check_commands "${check_name}"
|
||||
;;
|
||||
enable)
|
||||
enable_alerts "${check_name}" "${message}"
|
||||
;;
|
||||
disable)
|
||||
disable_alerts "${check_name}" "${message}"
|
||||
;;
|
||||
esac
|
||||
|
294
nagios-nrpe/files/monitoringctl/upstream/monitoringctl_common
Normal file
294
nagios-nrpe/files/monitoringctl/upstream/monitoringctl_common
Normal file
|
@ -0,0 +1,294 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Location of disable files
|
||||
readonly var_dir="/var/lib/monitoringctl"
|
||||
|
||||
readonly log_file="/var/log/monitoringctl.log"
|
||||
|
||||
readonly nrpe_conf_path="/etc/nagios/nrpe.cfg"
|
||||
|
||||
debian_major_version="$(cut -d "." -f 1 < /etc/debian_version)"
|
||||
readonly debian_major_version
|
||||
|
||||
# If no time limit is provided in CLI or found in file, this value is used
|
||||
readonly default_disabled_time="1h"
|
||||
|
||||
_nrpe_conf_lines='' # populated at the end of the file
|
||||
|
||||
|
||||
function error() {
|
||||
# $1: error message
|
||||
>&2 echo -e "${1}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function usage_error() {
|
||||
# $1: error message
|
||||
>&2 echo "${1}"
|
||||
>&2 echo "Execute \"${PROGNAME} --help\" for information on usage."
|
||||
exit 1
|
||||
}
|
||||
|
||||
function log() {
|
||||
# $1: message
|
||||
echo "$(now_iso) - ${PROGNAME}: ${1}" >> "${log_file}"
|
||||
}
|
||||
|
||||
function show_version() {
|
||||
cat <<END
|
||||
${PROGNAME} version ${VERSION}.
|
||||
|
||||
Copyright 2018-2024 Evolix <info@evolix.fr>,
|
||||
Jérémy Lecour <jlecour@evolix.fr>
|
||||
and others.
|
||||
|
||||
${PROGNAME} comes with ABSOLUTELY NO WARRANTY.This is free software,
|
||||
and you are welcome to redistribute it under certain conditions.
|
||||
See the GNU General Public License v3.0 for details.
|
||||
END
|
||||
}
|
||||
|
||||
# Fail if argument does not respect format: XwXdXhXmXs, XhX, XmX
|
||||
function filter_duration() {
|
||||
# $1: duration in format specified above
|
||||
_time_regex="^([0-9]+d)?(([0-9]+h(([0-9]+m?)|([0-9]+m([0-9]+s?)?))?)|(([0-9]+m([0-9]+s?)?)?))?$"
|
||||
if [[ "${1}" =~ ${_time_regex} ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Convert human writable duration into seconds
|
||||
function time_to_seconds() {
|
||||
# $1: formated time string
|
||||
if echo "${1}" | grep -E -q '^([0-9]+[wdhms])+$'; then
|
||||
echo "${1}" | sed 's/w/ * 604800 + /g; s/d/ * 86400 + /g; s/h/ * 3600 + /g; s/m/ * 60 + /g; s/s/ + /g; s/+ $//' | xargs expr
|
||||
elif echo "${1}" | grep -E -q '^([0-9]+h[0-9]+$)'; then
|
||||
echo "${1}" | sed 's/h/ * 3600 + /g; s/$/ * 60/' | xargs expr
|
||||
elif echo "${1}" | grep -E -q '^([0-9]+m[0-9]+$)'; then
|
||||
echo "${1}" | sed 's/m/ * 60 + /g' | xargs expr
|
||||
else
|
||||
error "Invalid duration: '${1}'."
|
||||
fi
|
||||
}
|
||||
|
||||
# Print re-enable time in secs
|
||||
function get_enable_time() {
|
||||
# $1: wrapper name
|
||||
_disable_file_path="$(get_disable_file_path "${1}")"
|
||||
if [ ! -e "${_disable_file_path}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
_enable_secs="$(grep -v -E "^\s*#" "${_disable_file_path}" | sed '/^$/d' | head -n1 | awk '/^[0-9]+$/ {print $1}')"
|
||||
# If file is empty, use file last change date plus default disabled time
|
||||
if [ -z "${_enable_secs}" ]; then
|
||||
_file_last_change_secs="$(stat -c %Z "${_disable_file_path}")"
|
||||
_default_disabled_time_secs="$(time_to_seconds "${default_disabled_time}")"
|
||||
_enable_secs="$(( _file_last_change_secs + _default_disabled_time_secs ))"
|
||||
fi
|
||||
echo "${_enable_secs}"
|
||||
}
|
||||
|
||||
# Print disable message
|
||||
function get_disable_message() {
|
||||
# $1: wrapper name
|
||||
_disable_file_path="$(get_disable_file_path "${1}")"
|
||||
if [ ! -e "${_disable_file_path}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
_disable_msg="$(sed '/^$/d' "${_disable_file_path}" | tail -n+2 | tr '\n' ' ' | awk '{$1=$1;print}')"
|
||||
echo "${_disable_msg}"
|
||||
}
|
||||
|
||||
function now_secs() {
|
||||
date +"%s"
|
||||
}
|
||||
|
||||
function now_iso() {
|
||||
date --iso-8601=seconds
|
||||
}
|
||||
|
||||
# Print delay before re-enable in secs
|
||||
function enable_delay() {
|
||||
# $1: re-enable time in secs
|
||||
echo $(( ${1} - $(now_secs) ))
|
||||
}
|
||||
|
||||
# Converts delay (in seconds) into human readable duration
|
||||
function delay_to_string() {
|
||||
# $1: delay in secs
|
||||
_delay_days="$(( ${1} /86400 ))"
|
||||
if [ "${_delay_days}" -eq 0 ]; then _delay_days=""
|
||||
else _delay_days="${_delay_days}d"; fi
|
||||
|
||||
_delay_hours="$(( (${1} %86400) /3600 ))"
|
||||
if [ "${_delay_hours}" -eq 0 ]; then _delay_hours=""
|
||||
else _delay_hours="${_delay_hours}h"; fi
|
||||
|
||||
_delay_minutes="$(( ((${1} %86400) %3600) /60 ))"
|
||||
if [ "${_delay_minutes}" -eq 0 ]; then _delay_minutes=""
|
||||
else _delay_minutes="${_delay_minutes}m"; fi
|
||||
|
||||
_delay_seconds="$(( ((${1} %86400) %3600) %60 ))"
|
||||
if [ "${_delay_seconds}" -eq 0 ]; then _delay_seconds=""
|
||||
else _delay_seconds="${_delay_seconds}s"; fi
|
||||
|
||||
echo "${_delay_days}${_delay_hours}${_delay_minutes}${_delay_seconds}"
|
||||
}
|
||||
|
||||
function is_disabled_check() {
|
||||
# $1: check name
|
||||
_wrapper="$(get_check_wrapper_name "${1}")"
|
||||
is_disabled_wrapper "${_wrapper}"
|
||||
}
|
||||
|
||||
function is_disabled_wrapper() {
|
||||
# $1: wrapper name
|
||||
_wrapper="${1}"
|
||||
_disable_file_path="$(get_disable_file_path "${_wrapper}")"
|
||||
if [ -e "${_disable_file_path}" ]; then
|
||||
_enable_time="$(get_enable_time "${_wrapper}")"
|
||||
_enable_delay="$(enable_delay "${_enable_time}")"
|
||||
if [ "${_enable_delay}" -le "0" ]; then
|
||||
echo "False"
|
||||
else
|
||||
echo "True"
|
||||
fi
|
||||
else
|
||||
echo False
|
||||
fi
|
||||
}
|
||||
|
||||
function get_disable_file_path() {
|
||||
# $1: wrapper name
|
||||
echo "${var_dir}/${1}_alerts_disabled"
|
||||
}
|
||||
|
||||
|
||||
|
||||
### Nagios configuration functions ####################
|
||||
|
||||
# Print NRPE configuration, with includes, without comments
|
||||
# and in the same order than NRPE does (taking account that
|
||||
# order changes from Deb10)
|
||||
function get_nrpe_conf() {
|
||||
echo "${_nrpe_conf_lines}"
|
||||
}
|
||||
|
||||
# Private function to recursively get NRPE conf from file
|
||||
function _get_conf_from_file() {
|
||||
# $1: NRPE conf file (.cfg)
|
||||
if [ ! -f "${1}" ]; then return; fi
|
||||
|
||||
_conf_lines=$(grep -E -R -v --no-filename "^\s*(#.*|)$" "${1}")
|
||||
while read -r _line; do
|
||||
if [[ "${_line}" =~ .*'include='.* ]]; then
|
||||
_conf_file=$(echo "${_line}" | cut -d= -f2)
|
||||
_get_conf_from_file "${_conf_file}"
|
||||
elif [[ "${_line}" =~ .*'include_dir='.* ]]; then
|
||||
_conf_dir=$(echo "${_line}" | cut -d= -f2)
|
||||
_get_conf_from_dir "${_conf_dir}"
|
||||
elif [[ "${_line}" =~ .*'check_hda1'.* ]]; then
|
||||
continue # Ludo dirty hack to avoid modifying /etc/nrpe/nrpe.cfg
|
||||
else
|
||||
echo "${_line}"
|
||||
fi
|
||||
done <<< "${_conf_lines}"
|
||||
}
|
||||
|
||||
# Private function to recursively get NRPE conf from directory
|
||||
function _get_conf_from_dir() {
|
||||
# $1: NRPE conf dir
|
||||
if [ ! -d "${1}" ]; then return; fi
|
||||
|
||||
if [ "${debian_major_version}" -ge 10 ]; then
|
||||
# From Deb10, NRPE use scandir() with alphasort() function
|
||||
_sort_command="sort"
|
||||
else
|
||||
# Before Deb10, NRPE use loaddir(), like find utility
|
||||
_sort_command="cat -"
|
||||
fi
|
||||
|
||||
# Add conf files in dir to be processed recursively
|
||||
for _file in $(find "${1}" -maxdepth 1 -name "*.cfg" 2> /dev/null | ${_sort_command}); do
|
||||
if [ -f "${_file}" ]; then
|
||||
_get_conf_from_file "${_file}"
|
||||
elif [ -d "${_file}" ]; then
|
||||
_get_conf_from_dir "${_file}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Print the checks that are configured in NRPE
|
||||
function get_checks_names() {
|
||||
echo "${_nrpe_conf_lines}" | grep -E "command\[check_.*\]=" | awk -F"[\\\[\\\]=]" '{sub("check_", "", $2); print $2}' | sort | uniq
|
||||
}
|
||||
|
||||
# Print the commands defined for check $1 in NRPE configuration
|
||||
function get_check_commands() {
|
||||
# $1: check name
|
||||
echo "${_nrpe_conf_lines}" | grep -E "command\[check_${1}\]" | cut -d'=' -f2-
|
||||
}
|
||||
|
||||
# Print the checks that have no alerts_wrapper in NRPE configuration
|
||||
function not_wrapped_checks() {
|
||||
for _check in $(get_checks_names); do
|
||||
if ! is_wrapped "${_check}"; then
|
||||
echo "${_check}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Fail if check is not wrapped
|
||||
function is_wrapped() {
|
||||
# $1: check name
|
||||
_cmd=$(get_check_commands "${1}" | tail -n1)
|
||||
if echo "${_cmd}" | grep --quiet --no-messages alerts_wrapper; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print the names that are defined in the wrappers of the checks
|
||||
function get_wrappers_names() {
|
||||
echo "${_nrpe_conf_lines}" | grep -s "alerts_wrapper" | awk '{ for (i=1 ; i<=NF; i++) { if ($i ~ /^(-n|--name)$/) { print $(i+1); break } } }' | tr ',' '\n' | sort | uniq
|
||||
}
|
||||
|
||||
# Print the wrapper name of the check
|
||||
function get_check_wrapper_name() {
|
||||
# $1: check name
|
||||
_cmd=$(get_check_commands "${1}" | tail -n1)
|
||||
if echo "${_cmd}" | grep --quiet --no-messages alerts_wrapper; then
|
||||
echo "${_cmd}" | awk '/--name/ {match($0, /--name\s*([a-zA-Z0-9_\-]*)\s*/, m); print m[1]}'
|
||||
fi
|
||||
}
|
||||
|
||||
function is_check() {
|
||||
# $1: check name
|
||||
_checks="$(get_checks_names)"
|
||||
if echo "${_checks}" | grep --quiet -E "^${1}$"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_wrapper() {
|
||||
# $1: wrapper name
|
||||
_wrappers="$(get_wrappers_names)"
|
||||
if echo "${_wrappers}" | grep --quiet -E "^${1}$"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print the checks that name this wrapper
|
||||
function get_wrapper_checks() {
|
||||
# $1: wrapper name
|
||||
echo "${_nrpe_conf_lines}" | grep -E "command\[check_.*\]=" | grep -E "\-\-name\s+${1}\s+" | awk -F"[\\\[\\\]=]" '{sub("check_", "", $2); print $2}' | sort | uniq | xargs
|
||||
}
|
||||
|
||||
|
||||
# Load NRPE configuration
|
||||
_nrpe_conf_lines="$(_get_conf_from_file "${nrpe_conf_path}")"
|
350
nagios-nrpe/files/monitoringctl/upstream/monitoringctl_common.py
Normal file
350
nagios-nrpe/files/monitoringctl/upstream/monitoringctl_common.py
Normal file
|
@ -0,0 +1,350 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Common functions for monitoringctl and alerts_wrapper
|
||||
#
|
||||
# Source:
|
||||
# https://gitea.evolix.org/evolix/ansible-roles/src/branch/stable/nagios-nrpe/
|
||||
|
||||
import sys, os
|
||||
from datetime import datetime, timezone
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
|
||||
# Location of disable files
|
||||
var_dir = '/var/lib/monitoringctl'
|
||||
|
||||
log_file = '/var/log/monitoringctl.log'
|
||||
|
||||
nrpe_conf_path = '/etc/nagios/nrpe.cfg'
|
||||
|
||||
# If no time limit is provided in CLI or found in file, this value is used
|
||||
default_disabled_time = '1h'
|
||||
|
||||
_nrpe_conf_lines = '' # populated at the end of the file
|
||||
|
||||
|
||||
def error(err_msg):
|
||||
print(err_msg, file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
|
||||
def usage_error(err_msg):
|
||||
print(err_msg, file=sys.stderr)
|
||||
usage_msg='Execute "{} --help" for information on usage.'.format(prog_name)
|
||||
print(usage_msg, file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
|
||||
def log(log_msg):
|
||||
now = now_iso()
|
||||
with open(log_file, 'a', encoding='utf-8') as file:
|
||||
line = '{} - {}: {}'.format(now, prog_name, log_msg)
|
||||
file.write(line)
|
||||
|
||||
|
||||
def show_version(prog_name, prog_version):
|
||||
msg = '''
|
||||
{1} version {2}.
|
||||
|
||||
Copyright 2018-2024 Evolix <info@evolix.fr>,
|
||||
Jérémy Lecour <jlecour@evolix.fr>
|
||||
and others.
|
||||
|
||||
{1} comes with ABSOLUTELY NO WARRANTY.This is free software,
|
||||
and you are welcome to redistribute it under certain conditions.
|
||||
See the GNU General Public License v3.0 for details.
|
||||
'''.format(prog_name, prog_version)
|
||||
print(msg)
|
||||
|
||||
|
||||
# Return Debian major version number : 10, 11, 12…
|
||||
def debian_major_version():
|
||||
if not os.path.exists('/etc/debian_version'):
|
||||
error('OS is not Debian (/etc/debian_version missing).')
|
||||
version = open('/etc/debian_version', 'r', encoding='utf-8').read().strip() # -> major.minor
|
||||
return int(version.split('.')[0])
|
||||
|
||||
debian_major_version = debian_major_version()
|
||||
|
||||
|
||||
# Return False if duration does not follow format: XwXdXhXmXs, XhX, XmX
|
||||
def filter_duration(duration):
|
||||
time_regex='^([0-9]+w)?([0-9]+d)?(([0-9]+h(([0-9]+m?)|([0-9]+m([0-9]+s?)?))?)|(([0-9]+m([0-9]+s?)?)?))?$'
|
||||
pattern = re.compile(time_regex)
|
||||
match = pattern.fullmatch(duration)
|
||||
return match != None
|
||||
|
||||
|
||||
# Convert human writable duration into seconds
|
||||
def time_to_seconds(duration):
|
||||
regex=(r'((?P<weeks>\d+)w)?'
|
||||
r'((?P<days>\d+)d)?'
|
||||
r'((?P<hours>\d+)h)?'
|
||||
r'((?P<minutes>\d+)(m?$|m))?'
|
||||
r'((?P<seconds>\d+)(s?$|s))?'
|
||||
)
|
||||
pattern = re.compile(regex, re.IGNORECASE)
|
||||
m = pattern.match(duration)
|
||||
if not m:
|
||||
error('Invalid duration: "{}".'.format(duration))
|
||||
|
||||
duration_dict = {}
|
||||
for s in ['weeks', 'days', 'hours', 'minutes', 'seconds']:
|
||||
if not m[s]:
|
||||
duration_dict[s] = 0
|
||||
else:
|
||||
duration_dict[s] = int(m[s])
|
||||
duration_secs = duration_dict['weeks'] * 604800 + duration_dict['days'] * 86400 + duration_dict['hours'] * 3600 + duration_dict['minutes'] * 60 + duration_dict['seconds']
|
||||
return duration_secs
|
||||
|
||||
|
||||
# Print re-enable time in secs
|
||||
def get_enable_time(wrapper_name):
|
||||
enable_secs = now_secs()
|
||||
|
||||
disable_file_path = get_disable_file_path(wrapper_name)
|
||||
if not os.path.exists(disable_file_path):
|
||||
return enable_secs
|
||||
|
||||
with open(disable_file_path, 'r', encoding='utf-8') as file:
|
||||
pattern = re.compile('^[0-9]+$')
|
||||
for line in file:
|
||||
match = pattern.fullmatch(line.strip())
|
||||
if match:
|
||||
enable_secs = int(line.strip())
|
||||
break
|
||||
|
||||
# If disable_file_path is empty, use last change date plus default disabled time
|
||||
if not enable_secs:
|
||||
file_last_change_secs = int(os.stat(disable_file_path).st_ctime) # stat -c %Z
|
||||
default_disabled_time_secs = time_to_seconds(default_disabled_time)
|
||||
enable_secs = file_last_change_secs + default_disabled_time_secs
|
||||
|
||||
return enable_secs
|
||||
|
||||
|
||||
# Return disable message
|
||||
def get_disable_message(wrapper_name):
|
||||
disable_file_path = get_disable_file_path(wrapper_name)
|
||||
|
||||
if not os.path.exists(disable_file_path):
|
||||
return
|
||||
|
||||
lines = ''
|
||||
with open(disable_file_path, 'r', encoding='utf-8') as file:
|
||||
lines = file.readlines()
|
||||
if len(lines) > 1:
|
||||
# Remove first line which contains re-enable time
|
||||
# The next lines as the disable message
|
||||
lines = lines[1:]
|
||||
return ' '.join(lines).replace('\n', '')
|
||||
|
||||
|
||||
def now_secs():
|
||||
now_secs = round(datetime.now().timestamp())
|
||||
return now_secs
|
||||
|
||||
|
||||
def now_iso():
|
||||
dt = datetime.now(timezone.utc).astimezone()
|
||||
dt = dt.replace(microsecond=0)
|
||||
return dt.isoformat()
|
||||
|
||||
|
||||
# Return delay before re-enable in secs
|
||||
def calc_enable_delay(reenable_time): # in secs
|
||||
return int(reenable_time - now_secs())
|
||||
|
||||
|
||||
# Convert delay (in seconds) into human readable duration
|
||||
def delay_to_string(delay):
|
||||
delay_days = '{}d'.format(delay // 86400)
|
||||
if delay_days == '0d': delay_days = ''
|
||||
|
||||
delay_hours = '{}h'.format((delay % 86400) // 3600)
|
||||
if delay_hours == '0h' and not delay_days: delay_hours = ''
|
||||
|
||||
delay_minutes = '{}m'.format(((delay % 86400) % 3600) // 60)
|
||||
if delay_minutes == '0m' and not delay_hours: delay_minutes = ''
|
||||
|
||||
delay_seconds = '{}s'.format(((delay % 86400) % 3600) % 60)
|
||||
if delay_seconds == '0s' and not delay_minutes: delay_seconds = ''
|
||||
|
||||
return '{}{}{}{}'.format(delay_days, delay_hours, delay_minutes, delay_seconds)
|
||||
|
||||
|
||||
def is_disabled_check(check_name):
|
||||
wrapper = get_check_wrapper_name(check_name)
|
||||
return is_disabled_wrapper(wrapper)
|
||||
|
||||
|
||||
def is_disabled_wrapper(wrapper_name):
|
||||
disable_file_path = get_disable_file_path(wrapper_name)
|
||||
if os.path.exists(disable_file_path):
|
||||
enable_time = get_enable_time(wrapper_name)
|
||||
enable_delay = calc_enable_delay(enable_time)
|
||||
is_disabled = enable_delay > 0
|
||||
return is_disabled
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_disable_file_path(wrapper_name):
|
||||
return '{}/{}_alerts_disabled'.format(var_dir, wrapper_name)
|
||||
|
||||
|
||||
|
||||
|
||||
### Nagios configuration functions ####################
|
||||
|
||||
# Return NRPE configuration, with includes, without comments
|
||||
# and in the same order than NRPE does (taking account that
|
||||
# order changes from Deb10)
|
||||
def get_nrpe_conf():
|
||||
return _nrpe_conf_lines
|
||||
|
||||
|
||||
# Private function to recursively get NRPE conf from file
|
||||
def _get_conf_from_file(file_path):
|
||||
if not os.path.exists(file_path) or not os.path.isfile(file_path):
|
||||
return
|
||||
|
||||
conf_lines = []
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
for line in file:
|
||||
line = line.split('#')[0].strip()
|
||||
if line:
|
||||
if 'include=' in line:
|
||||
conf_file = line.split('=')[1]
|
||||
include = _get_conf_from_file(conf_file)
|
||||
conf_lines.extend(include)
|
||||
elif 'include_dir=' in line:
|
||||
conf_dir = line.split('=')[1]
|
||||
include = _get_conf_from_dir(conf_dir)
|
||||
conf_lines.extend(include)
|
||||
elif 'check_hda1' in line:
|
||||
continue # Ludo dirty hack to avoid modifying /etc/nrpe/nrpe.cfg
|
||||
else:
|
||||
conf_lines.append(line)
|
||||
return conf_lines
|
||||
|
||||
|
||||
# Private function to recursively get NRPE conf from directory
|
||||
def _get_conf_from_dir(dir_path):
|
||||
if not os.path.exists(dir_path) or not os.path.isdir(dir_path):
|
||||
return
|
||||
|
||||
# Get dir content in the right order (depending on debian_major_version).
|
||||
# From Deb10, NRPE uses scandir() with alphasort() function, so we use 'sort' to reproduce it.
|
||||
# Before Deb10, NRPE used loaddir(), so we keep 'find' output order because it also uses loaddir().
|
||||
command = 'find "{}" -maxdepth 1 -name "*.cfg" 2> /dev/null'.format(dir_path)
|
||||
if debian_major_version >= 10:
|
||||
command += ' | sort'
|
||||
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
stdout, stderr = proc.communicate()
|
||||
dir_content = stdout.decode('utf8').split('\n')
|
||||
|
||||
# Process recursively dir_path content
|
||||
conf_lines = []
|
||||
for path in dir_content:
|
||||
if os.path.isfile(path):
|
||||
include = _get_conf_from_file(path)
|
||||
conf_lines.extend(include)
|
||||
elif os.path.isdir(path):
|
||||
include = _get_conf_from_dir(path)
|
||||
conf_lines.extend(include)
|
||||
return conf_lines
|
||||
|
||||
|
||||
# Return the checks that are configured in NRPE
|
||||
def get_checks_names():
|
||||
pattern = re.compile('command\[check_([0-9a-zA-Z_\-]*)\]=')
|
||||
check_names = []
|
||||
for line in _nrpe_conf_lines:
|
||||
match = re.search(pattern, line)
|
||||
if match and len(match.groups()) == 1 and match.group(1) not in check_names:
|
||||
check_names.append(match.group(1))
|
||||
return check_names
|
||||
|
||||
|
||||
# Return all the commands defined for check_name in NRPE configuration
|
||||
def get_check_commands(check_name):
|
||||
pattern = re.compile('command\[check_{}\]=(.*)'.format(check_name))
|
||||
commands = []
|
||||
for line in _nrpe_conf_lines:
|
||||
match = re.search(pattern, line)
|
||||
if match and len(match.groups()) == 1:
|
||||
commands.append(match.group(1))
|
||||
return commands
|
||||
|
||||
|
||||
# Return the checks that have no alerts_wrapper in NRPE configuration
|
||||
def not_wrapped_checks():
|
||||
not_wrapped = []
|
||||
for check in get_checks_names():
|
||||
if not is_wrapped(check) and check not in not_wrapped:
|
||||
not_wrapped.append(check)
|
||||
return not_wrapped
|
||||
|
||||
|
||||
# Return True if check is wrapped
|
||||
def is_wrapped(check_name):
|
||||
commands = get_check_commands(check_name)
|
||||
if not commands:
|
||||
return False
|
||||
command = commands[-1]
|
||||
return 'alerts_wrapper' in command
|
||||
|
||||
|
||||
# Private function to extract the name of the wrapper from a NRPE config line
|
||||
def _get_wrapper_name_from_line(line):
|
||||
if 'alerts_wrapper' in line:
|
||||
words = line.split(' ')
|
||||
while '' in words:
|
||||
words.remove('')
|
||||
for i in range(len(words)):
|
||||
if words[i] in ['--name', '-n']:
|
||||
return words[i+1]
|
||||
|
||||
|
||||
# Return the names that are defined in the wrappers of the checks
|
||||
def get_wrappers_names():
|
||||
wrapper_names = []
|
||||
for line in _nrpe_conf_lines:
|
||||
wrapper_name = _get_wrapper_name_from_line(line)
|
||||
if wrapper_name:
|
||||
wrapper_names.append(wrapper_name)
|
||||
return wrapper_names
|
||||
|
||||
|
||||
# Return the wrapper name of the check
|
||||
def get_check_wrapper_name(check_name):
|
||||
commands = get_check_commands(check_name)
|
||||
if not commands:
|
||||
return
|
||||
return _get_wrapper_name_from_line(commands[-1])
|
||||
|
||||
|
||||
def is_check(check_name):
|
||||
checks = get_checks_names()
|
||||
return check_name in checks
|
||||
|
||||
|
||||
def is_wrapper(wrapper_name):
|
||||
wrappers |