đ„ CWWK - Debian 13 - Alertes services systemd en erreur via ntfy
debian linux, comment ĂȘtre alertĂ© lorsquâun service systemd est en Ă©chec
Alerte service défaillant
Script envoi (/usr/local/bin/notify-ntfy.sh)
/usr/local/bin/notify-ntfy.sh (exĂ©cutable) â envoie la notification avec token, collecte journalctl (200 lignes), throttle 60s par unitĂ©, supprime fichier temporaire.
Voici la version mise Ă jour et plus robuste de /usr/local/bin/notify-ntfy.sh. Elle :
- dĂ©tecte lâunitĂ© dĂ©clenchante via plusieurs heuristiques (extraction de journal, systemctl âfailed),
- nettoie le nom dâunitĂ© pour Ă©viter caractĂšres non imprimables,
- Ă©vite les autoâdĂ©clenchements (notify-ntfy.service),
- conserve throttling et enâtĂȘte Email/token ntfy.
Remplacez lâancien script par celuiâci, chmod 755 /usr/local/bin/notify-ntfy.sh, puis testez.
Fichier /usr/local/bin/notify-ntfy.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/bin/sh
# notify-ntfy.sh
# Usage: notify-ntfy.sh <unit> <instance> <exitstatus>
# Envoie une notification Ă ntfy.rnmkcy.eu/yan_infos avec token et header Email.
# Robustification : heuristiques pour détecter l'unité déclenchante et nettoyage du nom.
NTFY_URL="https://ntfy.rnmkcy.eu/yan_infos"
TOKEN="tk_9h2bfxjs0pkbuwsnavc6w0wua5t5x"
EMAIL_HEADER="ntfy@cinay.eu"
HOST="$(hostname -f)"
# Params fournis par systemd
UNIT_PARAM="$1"
INSTANCE="$2"
EXITSTATUS="$3"
# Nettoyage simple d'une chaĂźne (supprime caractĂšres non imprimables)
clean() {
printf '%s' "$1" | tr -cd '[:print:]' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
# Détecter l'unité la plus récemment en erreur dans le journal
detect_unit_from_journal() {
# Cherche d'abord des occurrences explicites d'unités dans messages d'erreur récents
journalctl -b -n 1000 --no-pager -p err,crit,alert,emerg 2>/dev/null \
| tac \
| sed -n '1,500p' \
| grep -m1 -Eo '[A-Za-z0-9_.@-]+\.service' \
|| true
}
# Détecter unité via messages systemd (Unit=... or Failed)
detect_unit_from_journal_alt() {
journalctl -b -n 1000 --no-pager -o short-precise 2>/dev/null \
| tac \
| sed -n '1,500p' \
| grep -m1 -Eo 'Unit=[A-Za-z0-9_.@-]+\.service' \
| sed 's/^Unit=//g' \
|| true
}
# Détecter via systemctl --failed
detect_unit_from_systemctl_failed() {
systemctl --failed --no-legend --no-pager 2>/dev/null | awk '{print $1}' | head -n1 || true
}
# Resolve UNIT
UNIT="$(clean "$UNIT_PARAM")"
# If parameter absent or looks wrong, try heuristics
if [ -z "$UNIT" ] || [ "$UNIT" = "notify-ntfy.service" ] || [ -z "$EXITSTATUS" ] || printf '%s' "$EXITSTATUS" | grep -qE '/bin/(sh|bash|dash)'; then
DETECTED="$(detect_unit_from_journal)"
if [ -z "$DETECTED" ]; then
DETECTED="$(detect_unit_from_journal_alt)"
fi
if [ -z "$DETECTED" ]; then
DETECTED="$(detect_unit_from_systemctl_failed)"
fi
DETECTED="$(clean "$DETECTED")"
if [ -n "$DETECTED" ] && [ "$DETECTED" != "notify-ntfy.service" ]; then
UNIT="$DETECTED"
fi
fi
# Final fallback
[ -z "$UNIT" ] && UNIT="unknown"
# Prepare tmp log
TMPLOG="$(mktemp /tmp/journal-${UNIT}.XXXXXX 2>/dev/null || echo /tmp/journal-${UNIT}.$$)"
# Collect unit journal (if unit known)
journalctl -u "$UNIT" -n 200 --no-pager > "$TMPLOG" 2>/dev/null || true
TIMESTAMP="$(date --iso-8601=seconds)"
UNIT_CLEAN="$(printf '%s' "$UNIT" | tr -cd '[:alnum:].@_-')"
[ -z "$UNIT_CLEAN" ] && UNIT_CLEAN="unknown"
TITLE="${UNIT_CLEAN} failed on ${HOST}"
BODY="${TIMESTAMP} - ${HOST} - ${UNIT}
detected_unit: ${UNIT}
instance: ${INSTANCE}
exitstatus: ${EXITSTATUS}
Last journal lines:
$(tail -n 60 "$TMPLOG" 2>/dev/null)
"
# Throttle: one alert per unit per 60s
STATE_DIR="/var/lib/notify-ntfy"
mkdir -p "$STATE_DIR"
LASTFILE="$STATE_DIR/last-${UNIT_CLEAN}"
NOW=$(date +%s)
if [ -f "$LASTFILE" ]; then
LAST=$(cat "$LASTFILE" 2>/dev/null || echo 0)
else
LAST=0
fi
THROTTLE=60
if [ $((NOW - LAST)) -lt $THROTTLE ]; then
rm -f "$TMPLOG"
exit 0
fi
printf '%s' "$NOW" > "$LASTFILE"
echo "$TITLE script notify-ntfy.sh" | systemd-cat -t notify -p info
# -H "Email: $EMAIL_HEADER" \
# -H "Content-Type: text/plain; charset=utf-8" \
RESUL=`journalctl -u notify-ntfy@$UNIT_PARAM -n 10 --no-pager`
# Send to ntfy
curl -sS -X POST "$NTFY_URL" \
-H "Title: $TITLE" \
-H "Priority: high" \
-H "Authorization: Bearer $TOKEN" \
-d "$RESUL"
rm -f "$TMPLOG"
exit 0
Rendre exécutable
1
chmod 755 /usr/local/bin/notify-ntfy.sh
Unité systemd (/etc/systemd/system/notify-ntfy@.service)
Voici la version template prĂȘte Ă installer â crĂ©ez /etc/systemd/system/notify-ntfy@.service avec ce contenu, puis sudo systemctl daemon-reload.
1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Notify ntfy for failed unit %i
# Ne pas lier au service fautif pour éviter cycles
RefuseManualStart=no
[Service]
Type=oneshot
# %i = instance (nom de l'unité fournie par OnFailure=notify-ntfy@%n.service)
ExecStart=/usr/local/bin/notify-ntfy.sh %i "" "1"
Restart=no
Services Ă inclure dans les alertes (/root/gen-and-confirm-authorize.sh)
Voici la version renommĂ©e et adaptĂ©e en /root/gen-and-confirm-authorize.sh â interactive par dĂ©faut, avec option --all pour ajouter toutes les unitĂ©s dans /root/AUTHORIZED_LIST. ExĂ©cution en root.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/bin/sh
# gen-and-confirm-authorize.sh
# Usage:
# sudo /root/gen-and-confirm-authorize.sh # interactif
# sudo /root/gen-and-confirm-authorize.sh --all # ajoute tous les services Ă AUTHORIZED_LIST
#
SERVICES_OUT="/root/services-list.txt"
AUTHORIZED_FILE="/root/AUTHORIZED_LIST"
set -eu
# Générer la liste complÚte des services
systemctl list-units --type=service --all --no-legend --no-pager | awk '{print $1}' > "$SERVICES_OUT"
echo "Wrote $(wc -l < "$SERVICES_OUT") services to $SERVICES_OUT"
# Créer AUTHORIZED_FILE s'il n'existe pas
touch "$AUTHORIZED_FILE"
if [ "${1-}" = "--all" ]; then
# Ajouter tous les services (unique, trié)
cat "$SERVICES_OUT" "$AUTHORIZED_FILE" | sort -u > "${AUTHORIZED_FILE}.tmp"
mv "${AUTHORIZED_FILE}.tmp" "$AUTHORIZED_FILE"
echo "Added all services to $AUTHORIZED_FILE"
exit 0
fi
echo "Pour chaque service, tapez y pour l'ajouter dans $AUTHORIZED_FILE, n pour ignorer, q pour quitter."
while IFS= read -r svc; do
[ -z "$svc" ] && continue
# skip if already present
if grep -qxF "$svc" "$AUTHORIZED_FILE" 2>/dev/null; then
continue
fi
printf 'Ajouter "%s" Ă %s ? [y/N/q]: ' "$svc" "$AUTHORIZED_FILE"
read -r ans || ans="n"
case "$ans" in
y|Y)
echo "$svc" >> "$AUTHORIZED_FILE"
echo "Ajouté."
;;
q|Q)
echo "Abandon."
break
;;
*)
;;
esac
done < "$SERVICES_OUT"
echo "Fini. Editez $AUTHORIZED_FILE si nécessaire."
exit 0
Instructions :
- Copier en
/root/gen-and-confirm-authorize.sh chmod 700 /root/gen-and-confirm-authorize.sh- Exécuter interactif :
sudo /root/gen-and-confirm-authorize.sh - Ou ajouter tous en une fois :
sudo /root/gen-and-confirm-authorize.sh --all
Ensuite éditez /root/EXCLUDE_LIST si besoin et lancez apply-onfailure.sh
Drop-in (/root/apply-onfailure.sh)
version mise Ă jour du script /root/apply-onfailure.sh qui ajoute une option --force pour réécrire les dropâins existants (par dĂ©faut ils sont laissĂ©s intacts) et conserve --dry-run et --help puis chmod +x.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/bin/sh
# apply-onfailure.sh
# Usage:
# sudo /root/apply-onfailure.sh # applique les drop-ins pour les services listés dans AUTHORIZED_LIST
# sudo /root/apply-onfailure.sh --dry-run # n'écrit rien, affiche ce qui serait fait
# sudo /root/apply-onfailure.sh --force # réécrit les drop-ins existants
# sudo /root/apply-onfailure.sh --help # affiche cette aide
AUTHORIZED_FILE="/root/AUTHORIZED_LIST"
DRY_RUN=0
FORCE=0
set -eu
usage() {
cat <<EOF
Usage: $0 [--dry-run] [--force] [--help]
--dry-run : show what would be done, do not write files
--force : overwrite existing drop-ins
--help : show this help
EOF
}
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=1 ;;
--force) FORCE=1 ;;
--help) usage; exit 0 ;;
*) printf 'Unknown argument: %s\n' "$arg" >&2; usage >&2; exit 2 ;;
esac
done
if [ ! -f "$AUTHORIZED_FILE" ]; then
printf 'Fichier %s introuvable. Créez-le avec une unité .service par ligne.\n' "$AUTHORIZED_FILE" >&2
exit 1
fi
# Read authorized list
AUTHORIZED=""
while IFS= read -r line || [ -n "$line" ]; do
line="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
[ -z "$line" ] && continue
case "$line" in \#*) continue ;; esac
AUTHORIZED="$AUTHORIZED $line"
done < "$AUTHORIZED_FILE"
is_authorized() {
svc="$1"
for a in $AUTHORIZED; do
[ "$svc" = "$a" ] && return 0
done
return 1
}
TO_CREATE=0
TO_OVERWRITE=0
printf 'Scanning services...\n'
systemctl list-units --type=service --all --no-legend --no-pager | awk '{print $1}' | while IFS= read -r u; do
[ -z "$u" ] && continue
if ! is_authorized "$u"; then
printf 'SKIP (not authorized): %s\n' "$u"
continue
fi
d="/etc/systemd/system/${u}.d"
target="$d/10-onfailure.conf"
if [ "$DRY_RUN" -eq 1 ]; then
if [ -f "$target" ]; then
if [ "$FORCE" -eq 1 ]; then
printf 'WILL OVERWRITE: %s\n' "$target"
TO_OVERWRITE=$((TO_OVERWRITE+1))
else
printf 'EXISTS: %s (will not overwrite)\n' "$target"
fi
else
printf 'WILL CREATE: %s\n' "$target"
TO_CREATE=$((TO_CREATE+1))
fi
else
mkdir -p "$d"
if [ -f "$target" ]; then
if [ "$FORCE" -eq 1 ]; then
cat > "$target" <<'EOF'
[Unit]
OnFailure=notify-ntfy@%n.service
EOF
printf 'OVERWRITTEN: %s\n' "$target"
TO_OVERWRITE=$((TO_OVERWRITE+1))
else
printf 'SKIP (already exists): %s\n' "$target"
fi
else
cat > "$target" <<'EOF'
[Unit]
OnFailure=notify-ntfy@%n.service
EOF
printf 'CREATED: %s\n' "$target"
TO_CREATE=$((TO_CREATE+1))
fi
fi
done
if [ "$DRY_RUN" -eq 1 ]; then
printf 'Dry run complete. %d drop-ins would be created, %d overwritten.\n' "$TO_CREATE" "$TO_OVERWRITE"
exit 0
else
systemctl daemon-reload
printf 'Done. %d drop-ins created, %d overwritten. systemctl daemon-reload executed.\n' "$TO_CREATE" "$TO_OVERWRITE"
exit 0
fi
Instructions :
- Sauvegardez, rendez exécutable :
chmod 700 /root/apply-onfailure.sh - Assurez-vous que
/root/EXCLUDE_LISTexiste (une unité .service par ligne, # pour commentaires). - Exécutez en root :
sudo /root/apply-onfailure.sh
Lancement service
Sâassurer que le service notify existe et le script est exĂ©cutable, puis reload
1
2
3
#chmod 755 /usr/local/bin/notify-ntfy.sh
sudo systemctl daemon-reload
sudo systemctl restart notify-ntfy
Tests
Tests rapides pour vérifier notify-ntfy@.service et le mécanisme OnFailure.
1) VĂ©rifier lâunitĂ© template et le script
Afficher le contenu de lâunitĂ© template :
1
sudo systemctl cat notify-ntfy@.service
Vérifier que le script existe et est exécutable :
1
2
sudo stat -c '%a %U:%G %n' /usr/local/bin/notify-ntfy.sh
sudo head -n 50 /usr/local/bin/notify-ntfy.sh
2) Tester lâunitĂ© template directement (dry-run dâexĂ©cution systemd)
Exécution simulée sans implicite dépendances :
1
sudo systemd-run --unit=notify-test --on-active=0 --description="test notify" --property=RemainAfterExit=no /usr/local/bin/notify-ntfy.sh test.service "" "1"
Puis :
1
sudo journalctl -u systemd-run\@notify-test.service -n 200
3) Lancer lâinstance template directement
DĂ©marrer lâinstance pour voir quâelle sâexĂ©cute :
1
2
3
sudo systemctl start notify-ntfy@test.service
sudo systemctl status notify-ntfy@test.service
sudo journalctl -u notify-ntfy@test.service -n 200
Remarques : %i dans lâunitĂ© devient test.service.
4) Simuler un OnFailure réel depuis une unité de test
Créez une unité éphémÚre qui échoue (test-fail.service) :
1
2
3
4
5
6
7
8
9
10
11
12
13
sudo tee /etc/systemd/system/test-fail.service > /dev/null <<'EOF'
[Unit]
Description=Test fail unit
OnFailure=notify-ntfy@%n.service
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo FAIL >&2; exit 1'
EOF
sudo systemctl daemon-reload
sudo systemctl start test-fail.service
sudo systemctl status test-fail.service
sudo journalctl -u notify-ntfy@test-fail.service -n 200
VĂ©rifiez que notify-ntfy@test-fail.service sâest lancĂ© et que votre script a Ă©tĂ© appelĂ©.
5) Tester intĂ©gration sur un service existant (sans modifier lâunitĂ© principale) Si apply-onfailure a créé le drop-in OnFailure=notify-ntfy@%n.service, forcez lâĂ©chec de la cible :
1
2
3
4
sudo systemctl kill -s SIGABRT gonic.service
sleep 1
sudo systemctl status gonic.service
sudo journalctl -u notify-ntfy@gonic.service -n 200
Si gonic est configuré Restart=on-failure et ne devient pas failed, utilisez le wrapper ou simulez avec une unité de test.
6) DĂ©pannage si notify ne sâexĂ©cute pas
Vérifiez journaux unitaires :
1
2
sudo journalctl -u notify-ntfy@<unit>.service -n 200
sudo journalctl -u <unit> -n 200
Vérifiez code retour et permissions du script :
1
sudo -u <user> /usr/local/bin/notify-ntfy.sh <unit> "" "1"
Vérifiez dépendances circulaires :
1
systemctl show notify-ntfy@<unit>.service --property=Wants,Requires,BindsTo
7) Nettoyage test
1
2
3
4
sudo systemctl stop notify-ntfy@test-fail.service || true
sudo systemctl disable --now test-fail.service || true
sudo rm -f /etc/systemd/system/test-fail.service
sudo systemctl daemon-reload