menage et adaptation dans check_disk
This commit is contained in:
352
servers/linux/monitoring/old-bin/alert-engine.sh
Executable file
352
servers/linux/monitoring/old-bin/alert-engine.sh
Executable file
@@ -0,0 +1,352 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2026 Cédric Abonnel
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
set -u
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_PATH="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
|
||||
|
||||
# shellcheck source=/opt/monitoring/lib/monitoring-lib.sh
|
||||
. /opt/monitoring/lib/monitoring-lib.sh || exit 3
|
||||
|
||||
load_conf_if_exists "/opt/monitoring/conf/alert-engine.conf"
|
||||
load_conf_if_exists "/opt/monitoring/conf/alert-engine.local.conf"
|
||||
|
||||
lock_or_exit "alert-engine"
|
||||
require_cmd awk sed grep date tail stat cut tr
|
||||
|
||||
LOG_SOURCE="${LOG_FILE:-/var/log/monitoring/events.jsonl}"
|
||||
STATE_FILE="${ALERT_STATE_FILE:-/var/lib/monitoring/alert-engine.offset}"
|
||||
DEDUP_FILE="${ALERT_DEDUP_FILE:-/var/lib/monitoring/alert-engine.dedup}"
|
||||
|
||||
mkdir -p "$(dirname "$STATE_FILE")" "$(dirname "$DEDUP_FILE")" || fail_internal "Impossible de créer les répertoires d'état"
|
||||
touch "$STATE_FILE" "$DEDUP_FILE" || fail_internal "Impossible d'initialiser les fichiers d'état"
|
||||
|
||||
json_get() {
|
||||
local key="$1"
|
||||
local line="$2"
|
||||
|
||||
printf '%s\n' "$line" \
|
||||
| sed -n "s/.*\"${key}\":\"\([^\"]*\)\".*/\1/p" \
|
||||
| head -n1
|
||||
}
|
||||
|
||||
json_get_number() {
|
||||
local key="$1"
|
||||
local line="$2"
|
||||
|
||||
printf '%s\n' "$line" \
|
||||
| sed -n "s/.*\"${key}\":\([0-9][0-9]*\).*/\1/p" \
|
||||
| head -n1
|
||||
}
|
||||
|
||||
get_last_offset() {
|
||||
local offset
|
||||
offset="$(cat "$STATE_FILE" 2>/dev/null || true)"
|
||||
if [[ "$offset" =~ ^[0-9]+$ ]]; then
|
||||
printf '%s\n' "$offset"
|
||||
else
|
||||
printf '0\n'
|
||||
fi
|
||||
}
|
||||
|
||||
set_last_offset() {
|
||||
printf '%s\n' "$1" > "$STATE_FILE"
|
||||
}
|
||||
|
||||
current_log_size() {
|
||||
stat -c '%s' "$LOG_SOURCE" 2>/dev/null || printf '0\n'
|
||||
}
|
||||
|
||||
cleanup_dedup_file() {
|
||||
local now window tmp
|
||||
now="$(date +%s)"
|
||||
window="${ALERT_DEDUP_WINDOW:-3600}"
|
||||
tmp="$(mktemp "${MONITORING_STATE_DIR}/alert-engine.dedup.XXXXXX")" || return 0
|
||||
|
||||
awk -F'|' -v now="$now" -v window="$window" '
|
||||
NF >= 2 {
|
||||
if ((now - $2) <= window) print $0
|
||||
}
|
||||
' "$DEDUP_FILE" > "$tmp" 2>/dev/null || true
|
||||
|
||||
mv -f "$tmp" "$DEDUP_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
dedup_key() {
|
||||
local host="$1"
|
||||
local app="$2"
|
||||
local level="$3"
|
||||
local event="$4"
|
||||
printf '%s|%s|%s|%s\n' "$host" "$app" "$level" "$event"
|
||||
}
|
||||
|
||||
should_notify_dedup() {
|
||||
local key="$1"
|
||||
local now window found_ts
|
||||
now="$(date +%s)"
|
||||
window="${ALERT_DEDUP_WINDOW:-3600}"
|
||||
|
||||
found_ts="$(awk -F'|' -v k="$key" '
|
||||
$1 "|" $3 "|" $4 "|" $5 == k {print $2}
|
||||
' "$DEDUP_FILE" | tail -n1)"
|
||||
|
||||
if [[ "$found_ts" =~ ^[0-9]+$ ]]; then
|
||||
if [ $((now - found_ts)) -lt "$window" ]; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
save_dedup_entry() {
|
||||
local host="$1"
|
||||
local app="$2"
|
||||
local level="$3"
|
||||
local event="$4"
|
||||
local now
|
||||
now="$(date +%s)"
|
||||
printf '%s|%s|%s|%s|%s\n' "$host" "$now" "$app" "$level" "$event" >> "$DEDUP_FILE"
|
||||
}
|
||||
|
||||
event_is_ignored() {
|
||||
local event="$1" ignored
|
||||
for ignored in ${ALERT_IGNORE_EVENTS:-}; do
|
||||
[ "$ignored" = "$event" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
channels_for_event() {
|
||||
local level="$1"
|
||||
local event="$2"
|
||||
local varname value
|
||||
|
||||
varname="ALERT_RULE_${event}"
|
||||
value="${!varname:-}"
|
||||
|
||||
if [ -n "$value" ]; then
|
||||
printf '%s\n' "$value"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$level" in
|
||||
WARNING)
|
||||
printf '%s\n' "${ALERT_DEFAULT_CHANNELS_WARNING:-ntfy}"
|
||||
;;
|
||||
ERROR)
|
||||
printf '%s\n' "${ALERT_DEFAULT_CHANNELS_ERROR:-ntfy,mail}"
|
||||
;;
|
||||
CRITICAL)
|
||||
printf '%s\n' "${ALERT_DEFAULT_CHANNELS_CRITICAL:-ntfy,mail}"
|
||||
;;
|
||||
*)
|
||||
printf '\n'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
tags_for_level() {
|
||||
case "$1" in
|
||||
WARNING) printf '%s\n' "${NTFY_TAGS_WARNING:-warning}" ;;
|
||||
ERROR) printf '%s\n' "${NTFY_TAGS_ERROR:-warning,rotating_light}" ;;
|
||||
CRITICAL) printf '%s\n' "${NTFY_TAGS_CRITICAL:-skull,warning}" ;;
|
||||
*) printf '\n' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
send_ntfy() {
|
||||
|
||||
local title="$1"
|
||||
local body="$2"
|
||||
local priority="$3"
|
||||
|
||||
[ "${ALERT_NTFY_ENABLED:-true}" = "true" ] || return 0
|
||||
[ -n "${NTFY_SERVER:-}" ] || return 1
|
||||
[ -n "${NTFY_TOPIC:-}" ] || return 1
|
||||
|
||||
local url="${NTFY_SERVER%/}/${NTFY_TOPIC}"
|
||||
|
||||
local curl_args=(
|
||||
-fsS
|
||||
-X POST
|
||||
-H "Title: ${title}"
|
||||
-H "Priority: ${priority}"
|
||||
-H "Tags: warning"
|
||||
-d "$body"
|
||||
)
|
||||
|
||||
# topic protégé
|
||||
if [ -n "${NTFY_TOKEN:-}" ]; then
|
||||
curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
|
||||
fi
|
||||
|
||||
curl "${curl_args[@]}" "$url" >/dev/null
|
||||
}
|
||||
|
||||
send_mail() {
|
||||
local subject="$1"
|
||||
local body="$2"
|
||||
|
||||
[ "${ALERT_MAIL_ENABLED:-true}" = "true" ] || return 0
|
||||
[ -n "${DEST:-}" ] || return 1
|
||||
[ -x "${ALERT_MAIL_BIN:-/usr/sbin/sendmail}" ] || return 1
|
||||
|
||||
{
|
||||
printf 'To: %s\n' "${DEST}"
|
||||
printf 'Subject: %s %s\n' "${ALERT_MAIL_SUBJECT_PREFIX:-[monitoring]}" "$subject"
|
||||
printf 'Content-Type: text/plain; charset=UTF-8\n'
|
||||
printf '\n'
|
||||
printf '%s\n' "$body"
|
||||
} | "${ALERT_MAIL_BIN:-/usr/sbin/sendmail}" -t
|
||||
}
|
||||
|
||||
priority_for_level() {
|
||||
case "$1" in
|
||||
CRITICAL) printf 'urgent\n' ;;
|
||||
ERROR) printf 'high\n' ;;
|
||||
WARNING) printf 'default\n' ;;
|
||||
*) printf 'default\n' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
build_title() {
|
||||
local host="$1"
|
||||
local app="$2"
|
||||
local level="$3"
|
||||
local event="$4"
|
||||
printf '%s [%s] %s %s\n' "$host" "$app" "$level" "$event"
|
||||
}
|
||||
|
||||
build_body() {
|
||||
local ts="$1"
|
||||
local host="$2"
|
||||
local app="$3"
|
||||
local level="$4"
|
||||
local event="$5"
|
||||
local message="$6"
|
||||
local line="$7"
|
||||
|
||||
cat <<EOF
|
||||
Date: $ts
|
||||
Hôte: $host
|
||||
Script: $app
|
||||
Niveau: $level
|
||||
Événement: $event
|
||||
|
||||
Message:
|
||||
$message
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
process_line() {
|
||||
|
||||
local line="$1"
|
||||
local ts host app level event message channels title body prio ch key
|
||||
|
||||
ts="$(json_get "ts" "$line")"
|
||||
host="$(json_get "host" "$line")"
|
||||
app="$(json_get "app" "$line")"
|
||||
level="$(json_get "level" "$line")"
|
||||
event="$(json_get "event" "$line")"
|
||||
message="$(json_get "message" "$line")"
|
||||
|
||||
local tags
|
||||
tags="$(tags_for_level "$level")"
|
||||
|
||||
[ -n "$level" ] || return 0
|
||||
[ -n "$event" ] || return 0
|
||||
|
||||
case "$level" in
|
||||
DEBUG|INFO|NOTICE)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if event_is_ignored "$event"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
key="$(dedup_key "$host" "$app" "$level" "$event")"
|
||||
if ! should_notify_dedup "$key"; then
|
||||
log_debug "alert_suppressed_dedup" "Alerte supprimée par déduplication" \
|
||||
"event=$event" "level=$level" "host=$host" "app=$app"
|
||||
return 0
|
||||
fi
|
||||
|
||||
channels="$(channels_for_event "$level" "$event")"
|
||||
[ -n "$channels" ] || return 0
|
||||
|
||||
title="$(build_title "$host" "$app" "$level" "$event")"
|
||||
body="$(build_body "$ts" "$host" "$app" "$level" "$event" "$message" "$line")"
|
||||
prio="$(priority_for_level "$level")"
|
||||
|
||||
IFS=',' read -r -a channel_array <<< "$channels"
|
||||
for ch in "${channel_array[@]}"; do
|
||||
case "$ch" in
|
||||
ntfy)
|
||||
if send_ntfy "$title" "$body" "$prio" "$tags"; then
|
||||
log_info "alert_sent_ntfy" "Notification ntfy envoyée" \
|
||||
"event=$event" "level=$level" "host=$host" "app=$app"
|
||||
else
|
||||
log_error "alert_ntfy_failed" "Échec d'envoi ntfy" \
|
||||
"event=$event" "level=$level" "host=$host" "app=$app"
|
||||
fi
|
||||
;;
|
||||
mail)
|
||||
if send_mail "$title" "$body"; then
|
||||
log_info "alert_sent_mail" "Mail d'alerte envoyé" \
|
||||
"event=$event" "level=$level" "host=$host" "app=$app"
|
||||
else
|
||||
log_error "alert_mail_failed" "Échec d'envoi mail" \
|
||||
"event=$event" "level=$level" "host=$host" "app=$app"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
save_dedup_entry "$host" "$app" "$level" "$event"
|
||||
}
|
||||
|
||||
main() {
|
||||
local last_offset log_size
|
||||
last_offset="$(get_last_offset)"
|
||||
log_size="$(current_log_size)"
|
||||
|
||||
if [ ! -f "$LOG_SOURCE" ]; then
|
||||
log_notice "alert_log_missing" "Fichier de log absent, rien à traiter" "file=$LOG_SOURCE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$last_offset" -gt "$log_size" ]; then
|
||||
log_notice "alert_offset_reset" "Offset réinitialisé après rotation ou troncature du log" \
|
||||
"old_offset=$last_offset" "new_offset=0"
|
||||
last_offset=0
|
||||
fi
|
||||
|
||||
cleanup_dedup_file
|
||||
|
||||
tail -c +$((last_offset + 1)) "$LOG_SOURCE" | while IFS= read -r line; do
|
||||
[ -n "$line" ] || continue
|
||||
process_line "$line"
|
||||
done
|
||||
|
||||
set_last_offset "$log_size"
|
||||
}
|
||||
|
||||
main
|
||||
exit_with_status
|
||||
147
servers/linux/monitoring/old-bin/monitoring-lib.sh
Normal file
147
servers/linux/monitoring/old-bin/monitoring-lib.sh
Normal file
@@ -0,0 +1,147 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2026 Cédric Abonnel
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
MONITORING_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
MONITORING_BASE_DIR="$(cd "${MONITORING_LIB_DIR}/.." && pwd)"
|
||||
MONITORING_CONF_DIR="${MONITORING_BASE_DIR}/conf"
|
||||
|
||||
# Chargement config globale
|
||||
if [ -f "${MONITORING_CONF_DIR}/monitoring.conf" ]; then
|
||||
# shellcheck source=/dev/null
|
||||
. "${MONITORING_CONF_DIR}/monitoring.conf"
|
||||
fi
|
||||
|
||||
SCRIPT_NAME="${SCRIPT_NAME:-$(basename "$0")}"
|
||||
SCRIPT_PATH="${SCRIPT_PATH:-$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")}"
|
||||
|
||||
STATUS_OK=0
|
||||
STATUS_WARNING=1
|
||||
STATUS_ERROR=2
|
||||
STATUS_INTERNAL=3
|
||||
|
||||
CURRENT_STATUS=$STATUS_OK
|
||||
|
||||
LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
|
||||
json_escape() {
|
||||
local s="${1:-}"
|
||||
s="${s//\\/\\\\}"
|
||||
s="${s//\"/\\\"}"
|
||||
s="${s//$'\n'/\\n}"
|
||||
s="${s//$'\r'/\\r}"
|
||||
s="${s//$'\t'/\\t}"
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
log_event() {
|
||||
local level="$1"
|
||||
local event="$2"
|
||||
local message="$3"
|
||||
shift 3
|
||||
|
||||
local ts extra key value kv host
|
||||
ts="$(date --iso-8601=seconds)"
|
||||
|
||||
# Détection dynamique du hostname si HOSTNAME_FQDN n'est pas défini
|
||||
# On utilise 'hostname -f' pour le nom complet ou 'hostname' en secours
|
||||
host="${HOSTNAME_FQDN:-$(hostname -f 2>/dev/null || hostname)}"
|
||||
|
||||
extra=""
|
||||
|
||||
for kv in "$@"; do
|
||||
key="${kv%%=*}"
|
||||
value="${kv#*=}"
|
||||
extra="${extra},\"$(json_escape "$key")\":\"$(json_escape "$value")\""
|
||||
done
|
||||
|
||||
# Utilisation de la variable 'host' détectée ci-dessus
|
||||
printf '{"ts":"%s","host":"%s","app":"%s","level":"%s","event":"%s","message":"%s"%s}\n' \
|
||||
"$(json_escape "$ts")" \
|
||||
"$(json_escape "$host")" \
|
||||
"$(json_escape "$SCRIPT_NAME")" \
|
||||
"$(json_escape "$level")" \
|
||||
"$(json_escape "$event")" \
|
||||
"$(json_escape "$message")" \
|
||||
"$extra" >> "${LOG_FILE:-/var/log/monitoring/events.jsonl}"
|
||||
}
|
||||
|
||||
set_status() {
|
||||
local new_status="$1"
|
||||
if [ "$new_status" -gt "$CURRENT_STATUS" ]; then
|
||||
CURRENT_STATUS="$new_status"
|
||||
fi
|
||||
}
|
||||
|
||||
log_debug() { log_event "DEBUG" "$@"; }
|
||||
log_info() { log_event "INFO" "$@"; }
|
||||
log_notice() { log_event "NOTICE" "$@"; }
|
||||
log_warning() { log_event "WARNING" "$@"; set_status "$STATUS_WARNING"; }
|
||||
log_error() { log_event "ERROR" "$@"; set_status "$STATUS_ERROR"; }
|
||||
log_critical() { log_event "CRITICAL" "$@"; set_status "$STATUS_ERROR"; }
|
||||
|
||||
fail_internal() {
|
||||
log_event "ERROR" "internal_error" "$1"
|
||||
exit "$STATUS_INTERNAL"
|
||||
}
|
||||
|
||||
exit_with_status() {
|
||||
exit "$CURRENT_STATUS"
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
local cmd
|
||||
for cmd in "$@"; do
|
||||
command -v "$cmd" >/dev/null 2>&1 || fail_internal "Commande requise absente: $cmd"
|
||||
done
|
||||
}
|
||||
|
||||
load_conf_if_exists() {
|
||||
local conf="$1"
|
||||
[ -f "$conf" ] && . "$conf"
|
||||
}
|
||||
|
||||
lock_or_exit() {
|
||||
local lock_name="${1:-$SCRIPT_NAME}"
|
||||
local lock_file="${MONITORING_LOCK_DIR:-/var/lock/monitoring}/${lock_name}.lock"
|
||||
|
||||
exec 9>"$lock_file" || fail_internal "Impossible d'ouvrir le lock $lock_file"
|
||||
flock -n 9 || {
|
||||
log_notice "already_running" "Une autre instance est déjà en cours" "lock=$lock_file"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
threshold_level() {
|
||||
local value="$1"
|
||||
local warning="$2"
|
||||
local critical="$3"
|
||||
|
||||
if [ "$value" -ge "$critical" ]; then
|
||||
printf 'CRITICAL'
|
||||
elif [ "$value" -ge "$warning" ]; then
|
||||
printf 'WARNING'
|
||||
else
|
||||
printf 'INFO'
|
||||
fi
|
||||
}
|
||||
|
||||
safe_mv() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
mv -f "$src" "$dst" || fail_internal "Échec du déplacement de $src vers $dst"
|
||||
}
|
||||
|
||||
ensure_parent_dir() {
|
||||
local file="$1"
|
||||
mkdir -p "$(dirname "$file")" || fail_internal "Impossible de créer le répertoire parent de $file"
|
||||
}
|
||||
112
servers/linux/monitoring/old-bin/monitoring-update-config.sh
Executable file
112
servers/linux/monitoring/old-bin/monitoring-update-config.sh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2026 Cédric Abonnel
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
set -u
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
. /opt/monitoring/lib/monitoring-lib.sh || exit 3
|
||||
|
||||
# On s'assure d'avoir les permissions root
|
||||
if [ "${EUID}" -ne 0 ]; then
|
||||
echo "Ce script doit être exécuté en root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
extract_keys() {
|
||||
local file="$1"
|
||||
grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$file" | cut -d'=' -f1 | sort -u
|
||||
}
|
||||
|
||||
check_config_drift() {
|
||||
local conf_dir="/opt/monitoring/conf"
|
||||
local base_conf local_conf
|
||||
local found_issue=false
|
||||
local reviewed_files=0
|
||||
local files_requiring_action=0
|
||||
|
||||
log_info "audit_start" "Début de l'audit des configurations locales"
|
||||
|
||||
while IFS= read -r base_conf; do
|
||||
reviewed_files=$((reviewed_files + 1))
|
||||
|
||||
local_conf="${base_conf%.conf}.local.conf"
|
||||
local file_name local_file_name
|
||||
file_name="$(basename "$base_conf")"
|
||||
local_file_name="$(basename "$local_conf")"
|
||||
|
||||
if [ ! -f "$local_conf" ]; then
|
||||
cp "$base_conf" "$local_conf" || {
|
||||
log_error "audit_create_local_failed" \
|
||||
"Impossible de créer ${local_file_name} à partir de ${file_name}"
|
||||
found_issue=true
|
||||
files_requiring_action=$((files_requiring_action + 1))
|
||||
continue
|
||||
}
|
||||
chmod 600 "$local_conf" 2>/dev/null || true
|
||||
|
||||
log_notice "audit_missing_local" \
|
||||
"Le fichier ${local_file_name} n'existait pas ; il a été créé par copie de ${file_name}"
|
||||
continue
|
||||
fi
|
||||
|
||||
local tmp_base tmp_local
|
||||
tmp_base="$(mktemp)" || fail_internal "mktemp a échoué"
|
||||
tmp_local="$(mktemp)" || fail_internal "mktemp a échoué"
|
||||
|
||||
extract_keys "$base_conf" > "$tmp_base"
|
||||
extract_keys "$local_conf" > "$tmp_local"
|
||||
|
||||
local missing obsolete
|
||||
missing="$(comm -23 "$tmp_base" "$tmp_local" | xargs)"
|
||||
obsolete="$(comm -13 "$tmp_base" "$tmp_local" | xargs)"
|
||||
|
||||
if [ -n "$missing" ] || [ -n "$obsolete" ]; then
|
||||
found_issue=true
|
||||
files_requiring_action=$((files_requiring_action + 1))
|
||||
|
||||
log_warning "audit_file_requires_action" \
|
||||
"Le fichier ${local_file_name} nécessite une vérification"
|
||||
|
||||
if [ -n "$missing" ]; then
|
||||
log_warning "audit_keys_missing" \
|
||||
"Dans ${local_file_name}, options disponibles dans ${file_name} mais absentes du local : ${missing}"
|
||||
fi
|
||||
|
||||
if [ -n "$obsolete" ]; then
|
||||
log_info "audit_keys_obsolete" \
|
||||
"Dans ${local_file_name}, options présentes uniquement dans le local et à vérifier ou supprimer : ${obsolete}"
|
||||
fi
|
||||
else
|
||||
log_info "audit_file_ok" \
|
||||
"Le fichier ${local_file_name} contient les mêmes options que ${file_name}"
|
||||
fi
|
||||
|
||||
rm -f "$tmp_base" "$tmp_local"
|
||||
done < <(find "$conf_dir" -maxdepth 1 -type f -name "*.conf" ! -name "*.local.conf" | sort)
|
||||
|
||||
if [ "$found_issue" = false ]; then
|
||||
log_info "audit_success" \
|
||||
"Toutes les configurations locales sont à jour (${reviewed_files} fichier(s) vérifié(s))"
|
||||
else
|
||||
log_warning "audit_requires_action" \
|
||||
"Certaines configurations locales doivent être mises à jour (${files_requiring_action} fichier(s) à vérifier sur ${reviewed_files})"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
lock_or_exit "monitoring-audit"
|
||||
check_config_drift
|
||||
}
|
||||
|
||||
main
|
||||
exit_with_status
|
||||
247
servers/linux/monitoring/old-bin/monitoring-update.sh
Executable file
247
servers/linux/monitoring/old-bin/monitoring-update.sh
Executable file
@@ -0,0 +1,247 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2026 Cédric Abonnel
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
#
|
||||
# Moteur de mise à jour des programmes et fichiers connexes
|
||||
|
||||
set -u
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_PATH="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
|
||||
|
||||
# shellcheck source=/opt/monitoring/lib/monitoring-lib.sh
|
||||
. /opt/monitoring/lib/monitoring-lib.sh || exit 3
|
||||
|
||||
load_conf_if_exists "/opt/monitoring/conf/autoupdate.conf"
|
||||
load_conf_if_exists "/opt/monitoring/conf/autoupdate.local.conf"
|
||||
|
||||
# Définir les variables par défaut si elles ne sont pas dans les fichiers .conf
|
||||
UPDATE_TMP_DIR="${UPDATE_TMP_DIR:-/tmp/monitoring-update}"
|
||||
UPDATE_TIMEOUT_CONNECT="${UPDATE_TIMEOUT_CONNECT:-3}"
|
||||
UPDATE_TIMEOUT_TOTAL="${UPDATE_TIMEOUT_TOTAL:-15}"
|
||||
UPDATE_MANIFEST_URL="${UPDATE_MANIFEST_URL:-}"
|
||||
UPDATE_BASE_URL="${UPDATE_BASE_URL:-}"
|
||||
|
||||
lock_or_exit "monitoring-update"
|
||||
require_cmd curl sha256sum awk mktemp chmod dirname mv rm grep sed sort comm cut tr find
|
||||
|
||||
[ "${UPDATE_ENABLED:-true}" = "true" ] || {
|
||||
log_notice "update_disabled" "Mise à jour désactivée par configuration"
|
||||
exit 0
|
||||
}
|
||||
|
||||
mkdir -p "${UPDATE_TMP_DIR:-/tmp/monitoring-update}" || fail_internal "Impossible de créer le répertoire temporaire"
|
||||
|
||||
TMP_MANIFEST="$(mktemp "${UPDATE_TMP_DIR}/manifest.XXXXXX")" || fail_internal "mktemp a échoué"
|
||||
TMP_LOCAL_LIST="$(mktemp "${UPDATE_TMP_DIR}/local.XXXXXX")" || fail_internal "mktemp a échoué"
|
||||
TMP_REMOTE_LIST="$(mktemp "${UPDATE_TMP_DIR}/remote.XXXXXX")" || fail_internal "mktemp a échoué"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$TMP_MANIFEST" "$TMP_LOCAL_LIST" "$TMP_REMOTE_LIST"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fetch_manifest() {
|
||||
if ! curl -fsS \
|
||||
--connect-timeout "${UPDATE_TIMEOUT_CONNECT:-3}" \
|
||||
--max-time "${UPDATE_TIMEOUT_TOTAL:-15}" \
|
||||
"${UPDATE_MANIFEST_URL}" \
|
||||
-o "$TMP_MANIFEST"; then
|
||||
log_error "manifest_download_failed" \
|
||||
"Impossible de télécharger le manifeste" \
|
||||
"url=${UPDATE_MANIFEST_URL}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! awk '
|
||||
NF == 3 &&
|
||||
$1 ~ /^[0-9a-fA-F]{64}$/ &&
|
||||
$2 ~ /^(644|755)$/ &&
|
||||
$3 ~ /^(bin|lib|conf)\/[A-Za-z0-9._\/-]+$/ &&
|
||||
$3 !~ /\.\./
|
||||
' "$TMP_MANIFEST" >/dev/null; then
|
||||
log_error "manifest_invalid" \
|
||||
"Le manifeste distant est invalide" \
|
||||
"url=${UPDATE_MANIFEST_URL}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "manifest_downloaded" "Manifeste téléchargé" "url=${UPDATE_MANIFEST_URL}"
|
||||
return 0
|
||||
}
|
||||
|
||||
list_remote_files() {
|
||||
awk '{print $3}' "$TMP_MANIFEST" | sort -u > "$TMP_REMOTE_LIST"
|
||||
}
|
||||
|
||||
list_local_files() {
|
||||
find "${MONITORING_BASE_DIR}/bin" "${MONITORING_BASE_DIR}/lib" "${MONITORING_BASE_DIR}/conf" \
|
||||
-type f 2>/dev/null \
|
||||
| sed "s#^${MONITORING_BASE_DIR}/##" \
|
||||
| sort -u > "$TMP_LOCAL_LIST"
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
local mode="$1"
|
||||
local file="$2"
|
||||
|
||||
case "$mode" in
|
||||
755) chmod 755 "$file" ;;
|
||||
644) chmod 644 "$file" ;;
|
||||
*) fail_internal "Mode non supporté: $mode" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
update_one_file() {
|
||||
local expected_hash="$1"
|
||||
local mode="$2"
|
||||
local rel_path="$3"
|
||||
|
||||
local local_file="${MONITORING_BASE_DIR}/${rel_path}"
|
||||
local remote_file="${UPDATE_BASE_URL}/${rel_path}"
|
||||
local tmp_file local_hash downloaded_hash
|
||||
|
||||
tmp_file="$(mktemp "${UPDATE_TMP_DIR}/$(basename "$rel_path").XXXXXX")" || fail_internal "mktemp a échoué"
|
||||
|
||||
if [ -f "$local_file" ]; then
|
||||
local_hash="$(sha256sum "$local_file" | awk '{print $1}')"
|
||||
else
|
||||
local_hash=""
|
||||
fi
|
||||
|
||||
if [ "$local_hash" = "$expected_hash" ]; then
|
||||
log_debug "update_not_needed" "Fichier déjà à jour" "file=$rel_path"
|
||||
rm -f "$tmp_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! curl -fsS \
|
||||
--connect-timeout "${UPDATE_TIMEOUT_CONNECT:-3}" \
|
||||
--max-time "${UPDATE_TIMEOUT_TOTAL:-15}" \
|
||||
"$remote_file" \
|
||||
-o "$tmp_file"; then
|
||||
log_error "update_download_failed" \
|
||||
"Téléchargement impossible" \
|
||||
"file=$rel_path" "url=$remote_file"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
downloaded_hash="$(sha256sum "$tmp_file" | awk '{print $1}')"
|
||||
if [ "$downloaded_hash" != "$expected_hash" ]; then
|
||||
log_error "update_hash_mismatch" \
|
||||
"Hash téléchargé invalide" \
|
||||
"file=$rel_path" "expected=$expected_hash" "got=$downloaded_hash"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ensure_parent_dir "$local_file"
|
||||
apply_mode "$mode" "$tmp_file"
|
||||
safe_mv "$tmp_file" "$local_file"
|
||||
|
||||
if [ -z "$local_hash" ]; then
|
||||
log_notice "file_created" \
|
||||
"Fichier créé depuis le manifeste" \
|
||||
"file=$rel_path" "mode=$mode" "hash=$expected_hash"
|
||||
else
|
||||
log_notice "update_applied" \
|
||||
"Mise à jour appliquée" \
|
||||
"file=$rel_path" "mode=$mode" "old_hash=$local_hash" "new_hash=$expected_hash"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
delete_extra_local_files() {
|
||||
[ "${UPDATE_ALLOW_DELETE:-false}" = "true" ] || return 0
|
||||
|
||||
comm -23 "$TMP_LOCAL_LIST" "$TMP_REMOTE_LIST" | while IFS= read -r rel_path; do
|
||||
[ -n "$rel_path" ] || continue
|
||||
|
||||
# Protection globale de TOUS les fichiers .local.conf
|
||||
if [[ "$rel_path" == *.local.conf ]]; then
|
||||
log_notice "delete_skipped" \
|
||||
"Fichier local protégé (ignoré)" \
|
||||
"file=$rel_path"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Sécurité supplémentaire pour ne pas supprimer les répertoires vitaux
|
||||
rm -f "${MONITORING_BASE_DIR}/${rel_path}" \
|
||||
&& log_notice "file_deleted" \
|
||||
"Fichier obsolète supprimé" \
|
||||
"file=$rel_path" \
|
||||
|| log_error "delete_failed" \
|
||||
"Échec suppression" \
|
||||
"file=$rel_path"
|
||||
done
|
||||
}
|
||||
|
||||
run_local_conf_sync() {
|
||||
|
||||
local sync_script="${MONITORING_BASE_DIR}/bin/sync-local-confs.sh"
|
||||
|
||||
if [ -x "$sync_script" ]; then
|
||||
log_info "local_conf_sync_start" \
|
||||
"Synchronisation des fichiers .local.conf"
|
||||
|
||||
if "$sync_script"; then
|
||||
log_info "local_conf_sync_done" \
|
||||
"Synchronisation terminée"
|
||||
else
|
||||
log_warning "local_conf_sync_failed" \
|
||||
"La synchronisation des .local.conf a échoué"
|
||||
fi
|
||||
else
|
||||
log_notice "local_conf_sync_missing" \
|
||||
"Script de synchronisation absent" \
|
||||
"script=$sync_script"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local total=0 updated_or_checked=0 failed=0
|
||||
local hash mode path
|
||||
|
||||
fetch_manifest || exit 2
|
||||
list_remote_files
|
||||
list_local_files
|
||||
|
||||
while read -r hash mode path; do
|
||||
[ -n "${hash:-}" ] || continue
|
||||
total=$((total + 1))
|
||||
|
||||
if update_one_file "$hash" "$mode" "$path"; then
|
||||
updated_or_checked=$((updated_or_checked + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
done < "$TMP_MANIFEST"
|
||||
|
||||
delete_extra_local_files
|
||||
|
||||
run_local_conf_sync
|
||||
|
||||
if [ "$failed" -gt 0 ]; then
|
||||
log_warning "update_finished_with_errors" \
|
||||
"Mise à jour terminée avec erreurs" \
|
||||
"total=$total" "updated_or_checked=$updated_or_checked" "failed=$failed"
|
||||
else
|
||||
log_info "update_finished" \
|
||||
"Mise à jour terminée" \
|
||||
"total=$total" "updated_or_checked=$updated_or_checked" "failed=0"
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
exit_with_status
|
||||
208
servers/linux/monitoring/old-bin/monitoring.sh
Executable file
208
servers/linux/monitoring/old-bin/monitoring.sh
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2026 Cédric Abonnel
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
#
|
||||
# lit le fichier log
|
||||
|
||||
set -u
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_PATH="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
|
||||
|
||||
# shellcheck source=/opt/monitoring/lib/monitoring-lib.sh
|
||||
. /opt/monitoring/lib/monitoring-lib.sh || exit 3
|
||||
|
||||
load_conf_if_exists "/opt/monitoring/conf/autoupdate.conf"
|
||||
|
||||
lock_or_exit "monitoring-update"
|
||||
require_cmd curl sha256sum awk mktemp chmod dirname mv rm grep sed sort comm cut tr
|
||||
|
||||
[ "${UPDATE_ENABLED:-true}" = "true" ] || {
|
||||
log_notice "update_disabled" "Mise à jour désactivée par configuration"
|
||||
exit 0
|
||||
}
|
||||
|
||||
mkdir -p "${UPDATE_TMP_DIR:-/tmp/monitoring-update}" || fail_internal "Impossible de créer le répertoire temporaire"
|
||||
|
||||
TMP_MANIFEST="$(mktemp "${UPDATE_TMP_DIR}/manifest.XXXXXX")" || fail_internal "mktemp a échoué"
|
||||
TMP_LOCAL_LIST="$(mktemp "${UPDATE_TMP_DIR}/local.XXXXXX")" || fail_internal "mktemp a échoué"
|
||||
TMP_REMOTE_LIST="$(mktemp "${UPDATE_TMP_DIR}/remote.XXXXXX")" || fail_internal "mktemp a échoué"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$TMP_MANIFEST" "$TMP_LOCAL_LIST" "$TMP_REMOTE_LIST"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fetch_manifest() {
|
||||
if ! curl -fsS \
|
||||
--connect-timeout "${UPDATE_TIMEOUT_CONNECT:-3}" \
|
||||
--max-time "${UPDATE_TIMEOUT_TOTAL:-15}" \
|
||||
"${UPDATE_MANIFEST_URL}" \
|
||||
-o "$TMP_MANIFEST"; then
|
||||
log_error "manifest_download_failed" "Impossible de télécharger le manifeste" "url=${UPDATE_MANIFEST_URL}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! awk '
|
||||
NF == 3 &&
|
||||
$1 ~ /^[0-9a-fA-F]{64}$/ &&
|
||||
$2 ~ /^(644|755)$/ &&
|
||||
$3 ~ /^(bin|lib|conf)\/[A-Za-z0-9._\/-]+$/ &&
|
||||
$3 !~ /\.\./
|
||||
' "$TMP_MANIFEST" >/dev/null; then
|
||||
log_error "manifest_invalid" "Le manifeste distant est invalide" "url=${UPDATE_MANIFEST_URL}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "manifest_downloaded" "Manifeste téléchargé" "url=${UPDATE_MANIFEST_URL}"
|
||||
return 0
|
||||
}
|
||||
|
||||
list_remote_files() {
|
||||
awk '{print $3}' "$TMP_MANIFEST" | sort -u > "$TMP_REMOTE_LIST"
|
||||
}
|
||||
|
||||
list_local_files() {
|
||||
find "${MONITORING_BASE_DIR}/bin" "${MONITORING_BASE_DIR}/lib" "${MONITORING_BASE_DIR}/conf" \
|
||||
-type f 2>/dev/null \
|
||||
| sed "s#^${MONITORING_BASE_DIR}/##" \
|
||||
| sort -u > "$TMP_LOCAL_LIST"
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
local mode="$1"
|
||||
local file="$2"
|
||||
|
||||
case "$mode" in
|
||||
755) chmod 755 "$file" ;;
|
||||
644) chmod 644 "$file" ;;
|
||||
*) fail_internal "Mode non supporté: $mode" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
update_one_file() {
|
||||
local expected_hash="$1"
|
||||
local mode="$2"
|
||||
local rel_path="$3"
|
||||
|
||||
local local_file="${MONITORING_BASE_DIR}/${rel_path}"
|
||||
local remote_file="${UPDATE_BASE_URL}/${rel_path}"
|
||||
local tmp_file local_hash downloaded_hash
|
||||
|
||||
tmp_file="$(mktemp "${UPDATE_TMP_DIR}/$(basename "$rel_path").XXXXXX")" || fail_internal "mktemp a échoué"
|
||||
|
||||
if [ -f "$local_file" ]; then
|
||||
local_hash="$(sha256sum "$local_file" | awk '{print $1}')"
|
||||
else
|
||||
local_hash=""
|
||||
fi
|
||||
|
||||
if [ "$local_hash" = "$expected_hash" ]; then
|
||||
log_debug "update_not_needed" "Fichier déjà à jour" "file=$rel_path"
|
||||
rm -f "$tmp_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! curl -fsS \
|
||||
--connect-timeout "${UPDATE_TIMEOUT_CONNECT:-3}" \
|
||||
--max-time "${UPDATE_TIMEOUT_TOTAL:-15}" \
|
||||
"$remote_file" \
|
||||
-o "$tmp_file"; then
|
||||
log_error "update_download_failed" "Téléchargement impossible" "file=$rel_path" "url=$remote_file"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
downloaded_hash="$(sha256sum "$tmp_file" | awk '{print $1}')"
|
||||
if [ "$downloaded_hash" != "$expected_hash" ]; then
|
||||
log_error "update_hash_mismatch" "Hash téléchargé invalide" \
|
||||
"file=$rel_path" "expected=$expected_hash" "got=$downloaded_hash"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ensure_parent_dir "$local_file"
|
||||
apply_mode "$mode" "$tmp_file"
|
||||
safe_mv "$tmp_file" "$local_file"
|
||||
|
||||
if [ -z "$local_hash" ]; then
|
||||
log_notice "file_created" "Fichier créé depuis le manifeste" \
|
||||
"file=$rel_path" "mode=$mode" "hash=$expected_hash"
|
||||
else
|
||||
log_notice "update_applied" "Mise à jour appliquée" \
|
||||
"file=$rel_path" "mode=$mode" "old_hash=$local_hash" "new_hash=$expected_hash"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
delete_extra_local_files() {
|
||||
|
||||
[ "${UPDATE_ALLOW_DELETE:-false}" = "true" ] || return 0
|
||||
|
||||
comm -23 "$TMP_LOCAL_LIST" "$TMP_REMOTE_LIST" | while IFS= read -r rel_path; do
|
||||
|
||||
[ -n "$rel_path" ] || continue
|
||||
|
||||
case "$rel_path" in
|
||||
conf/autoupdate.conf|conf/alert-engine.local.conf)
|
||||
log_notice "delete_skipped" \
|
||||
"Suppression ignorée pour fichier local protégé" \
|
||||
"file=$rel_path"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -f "${MONITORING_BASE_DIR}/${rel_path}" \
|
||||
&& log_notice "file_deleted" \
|
||||
"Fichier supprimé car absent du manifeste" \
|
||||
"file=$rel_path" \
|
||||
|| log_error "delete_failed" \
|
||||
"Impossible de supprimer fichier local" \
|
||||
"file=$rel_path"
|
||||
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
local total=0 updated=0 failed=0 hash mode path
|
||||
|
||||
fetch_manifest || exit 2
|
||||
list_remote_files
|
||||
list_local_files
|
||||
|
||||
while read -r hash mode path; do
|
||||
[ -n "${hash:-}" ] || continue
|
||||
total=$((total + 1))
|
||||
|
||||
if update_one_file "$hash" "$mode" "$path"; then
|
||||
updated=$((updated + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
done < "$TMP_MANIFEST"
|
||||
|
||||
delete_extra_local_files
|
||||
|
||||
if [ "$failed" -gt 0 ]; then
|
||||
log_warning "update_finished_with_errors" \
|
||||
"Mise à jour terminée avec erreurs" \
|
||||
"total=$total" "updated_or_checked=$updated" "failed=$failed"
|
||||
else
|
||||
log_info "update_finished" \
|
||||
"Mise à jour terminée" \
|
||||
"total=$total" "updated_or_checked=$updated" "failed=0"
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
exit_with_status
|
||||
Reference in New Issue
Block a user