adsb-1090/scripts/watchdog_readsb.sh

199 lines
9.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# watchdog_readsb.sh — surveille le(s) récepteur(s) ADS-B et redémarre le
# maillon réellement en cause quand le flux de messages se fige.
#
# v4 (31/07/2026) — refonte après l'incident du 31/07 (36 redémarrages en 24h
# sur un récepteur qui n'était pas en panne). Quatre défauts corrigés :
#
# 1. RECEIVERS était statique et listait un dongle inexistant (00000012).
# Le watchdog redémarrait donc en boucle un conteneur sans matériel.
# → RECEIVERS est maintenant dérivé des dongles réellement énumérés,
# via detect_dongles.sh (même source que check_adsb.sh).
#
# 2. THRESHOLD=1 le jour : redémarrage après 5 min sans progression. Or le
# service autogain a besoin de plusieurs heures de trafic pour converger,
# et chaque redémarrage le relançait de zéro. Le watchdog empêchait donc
# la seule chose qui aurait corrigé le gain (bloqué à 7.7 dB).
# → seuil minimum 3 cycles, + délai de grâce après démarrage.
#
# 3. Aucun plafond : un redémarrage qui ne corrige rien était répété
# indéfiniment, noyant le vrai diagnostic sous le bruit.
# → budget de MAX_RESTARTS_24H, au-delà duquel on alerte sans agir.
#
# 4. Le diagnostic reposait uniquement sur aircraft.json (servi par tar1090).
# Un tar1090 déconnecté de readsb produisait donc le même symptôme qu'un
# dongle mort — c'est exactement ce qui s'est produit le 31/07 après une
# perte d'alias réseau : readsb décodait, tar1090 servait 0 message, et
# le watchdog redémarrait readsb, le maillon sain.
# → sonde directe du port SBS : si readsb émet, c'est tar1090 qu'on
# redémarre, pas readsb.
#
# Cron : */5 * * * * /data/adsb/scripts/watchdog_readsb.sh
# =============================================================================
BASE_DIR="/data/adsb"
LOG_FILE="/var/log/readsb_watchdog.log"
DETECT_LIB="${BASE_DIR}/scripts/detect_dongles.sh"
# ── Paramètres de prudence ────────────────────────────────────────────────────
MIN_UPTIME=1800 # s — ne jamais redémarrer un conteneur démarré depuis
# moins de 30 min (laisse converger l'autogain)
MAX_RESTARTS_24H=6 # au-delà, on alerte sans redémarrer : si 6 redémarrages
# n'ont pas corrigé le problème, le 7e ne le fera pas
SBS_PROBE_TIMEOUT=6 # s — durée de la sonde sur le port SBS
SBS_MIN_BYTES=50 # octets reçus au-delà desquels readsb est jugé actif
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" >> "$LOG_FILE"; }
# ── Seuil adaptatif jour/nuit ─────────────────────────────────────────────────
# Le creux de trafic nocturne reste réel : on tolère plus longtemps la nuit.
# Mais le plancher est désormais à 3 cycles (15 min) même en journée.
HOUR=$(date +%H)
if [ "$((10#$HOUR))" -ge 6 ] && [ "$((10#$HOUR))" -lt 22 ]; then
THRESHOLD=3; PERIOD="jour"
else
THRESHOLD=5; PERIOD="nuit"
fi
# ── Construction dynamique des récepteurs ─────────────────────────────────────
if [ ! -r "$DETECT_LIB" ]; then
log "[FATAL] $DETECT_LIB introuvable — watchdog inopérant"
exit 1
fi
# shellcheck source=/dev/null
source "$DETECT_LIB"
mapfile -t PRESENT_SERIALS < <(detect_serials)
if [ "${#PRESENT_SERIALS[@]}" -eq 0 ]; then
log "[ALERTE] Aucun dongle RTL-SDR détecté sur le bus USB — rien à surveiller. Vérifier le branchement physique."
exit 0
fi
# ── Budget de redémarrages ────────────────────────────────────────────────────
# Historique des redémarrages par conteneur, élagué à 24h glissantes.
restart_count_24h() {
local ctn="$1" f="/tmp/readsb_restart_hist_${ctn}" now cutoff
now=$(date +%s); cutoff=$((now - 86400))
[ -f "$f" ] || { echo 0; return; }
awk -v c="$cutoff" '$1 >= c' "$f" > "${f}.tmp" && mv "${f}.tmp" "$f"
wc -l < "$f" | tr -d ' '
}
record_restart() {
echo "$(date +%s)" >> "/tmp/readsb_restart_hist_$1"
}
# ── Uptime d'un conteneur, en secondes ────────────────────────────────────────
container_uptime() {
local started
started=$(docker inspect --format '{{.State.StartedAt}}' "$1" 2>/dev/null) || return 1
[ -z "$started" ] && return 1
echo $(( $(date +%s) - $(date -d "$started" +%s 2>/dev/null || echo 0) ))
}
container_running() {
[ "$(docker inspect --format '{{.State.Status}}' "$1" 2>/dev/null)" = "running" ]
}
# ── Sonde SBS : readsb émet-il réellement des messages décodés ? ───────────────
# Retourne 0 si du trafic ADS-B sort du récepteur lui-même.
sbs_has_traffic() {
local port="$1" bytes
if command -v nc >/dev/null 2>&1; then
bytes=$(timeout "$SBS_PROBE_TIMEOUT" nc 127.0.0.1 "$port" 2>/dev/null \
| head -c 400 | wc -c)
else
bytes=$(timeout "$SBS_PROBE_TIMEOUT" bash -c \
"exec 3<>/dev/tcp/127.0.0.1/$port && head -c 400 <&3" 2>/dev/null | wc -c)
fi
[ "${bytes:-0}" -ge "$SBS_MIN_BYTES" ]
}
# ── Redémarrage encadré ───────────────────────────────────────────────────────
guarded_restart() {
local ctn="$1" reason="$2" up n
if ! container_running "$ctn"; then
log "[$ctn] non démarré — pas de redémarrage watchdog (relève de docker compose / adsb-stack.service)"
return 1
fi
up=$(container_uptime "$ctn")
if [ -n "$up" ] && [ "$up" -lt "$MIN_UPTIME" ]; then
log "[$ctn] $reason — mais uptime ${up}s < ${MIN_UPTIME}s, on laisse converger (autogain / initialisation)"
return 1
fi
n=$(restart_count_24h "$ctn")
if [ "$n" -ge "$MAX_RESTARTS_24H" ]; then
log "[ALERTE] [$ctn] $reason$n redémarrages sur 24h, plafond atteint : AUCUNE action. Le redémarrage ne corrige pas ce problème, diagnostic manuel requis (gain, antenne, câble, alias réseau)."
return 1
fi
log "[$ctn] $reason — redémarrage ($((n + 1))/${MAX_RESTARTS_24H} sur 24h)"
docker restart "$ctn" >/dev/null 2>&1 && record_restart "$ctn"
return 0
}
# ── Boucle principale ─────────────────────────────────────────────────────────
for i in "${!PRESENT_SERIALS[@]}"; do
n=$((i + 1))
[ "$n" -gt "$MAX_RECEIVERS" ] && break
IFS=':' read -r SLOT PORT CTN_RS CTN_T1090 CTN_PG PROFILE <<< "${SLOTS[$i]}"
RX_SERIAL="${PRESENT_SERIALS[$i]}"
read -r BEAST_PORT RAW_PORT SBS_PORT <<< "$(slot_ports "$SLOT")"
STATE_FILE="/tmp/readsb_last_msg_count_${RX_SERIAL}"
STREAK_FILE="/tmp/readsb_stall_streak_${RX_SERIAL}"
# Le conteneur readsb doit exister ; sinon rien à surveiller sur ce slot.
if ! docker inspect "$CTN_RS" >/dev/null 2>&1; then
log "[$CTN_RS] conteneur absent alors que le dongle $RX_SERIAL est présent — le stack n'est pas déployé pour ce slot (docker compose up -d --remove-orphans)"
continue
fi
CURRENT=$(curl -s --max-time 10 "http://127.0.0.1:${PORT}/data/aircraft.json" \
| grep -oP '"messages"\s*:\s*\K[0-9]+')
if [ -z "$CURRENT" ]; then
log "[$CTN_RS] aircraft.json illisible (port ${PORT}) — tar1090 muet ; check ignoré ce cycle"
continue
fi
LAST=$(cat "$STATE_FILE" 2>/dev/null || echo -1)
STREAK=$(cat "$STREAK_FILE" 2>/dev/null || echo 0)
if [ "$CURRENT" == "$LAST" ]; then
STREAK=$((STREAK + 1))
if [ "$STREAK" -ge "$THRESHOLD" ]; then
# Compteur figé confirmé. Avant d'accuser readsb, on demande
# directement au récepteur s'il émet : c'est ce test qui distingue
# un dongle sourd d'un tar1090 déconnecté.
if sbs_has_traffic "$SBS_PORT"; then
log "[$CTN_RS] compteur tar1090 figé à $CURRENT depuis ${STREAK} cycle(s) MAIS le port SBS ${SBS_PORT} émet — readsb est sain, le maillon en cause est tar1090"
guarded_restart "$CTN_T1090" "aircraft.json figé alors que readsb émet (alias réseau BEASTHOST perdu ?)"
else
guarded_restart "$CTN_RS" "compteur messages figé à $CURRENT depuis ${STREAK} cycle(s) [$PERIOD, seuil=$THRESHOLD] et port SBS ${SBS_PORT} silencieux"
fi
STREAK=0
else
log "[$CTN_RS] compteur inchangé à $CURRENT (${STREAK}/${THRESHOLD} cycles) [$PERIOD] - en observation"
fi
else
# Progression : on efface aussi l'historique de redémarrages, le
# récepteur est manifestement revenu à la normale.
if [ "$STREAK" -gt 0 ]; then
log "[$CTN_RS] flux rétabli ($LAST$CURRENT) - compteur de blocage remis à zéro"
fi
STREAK=0
rm -f "/tmp/readsb_restart_hist_${CTN_RS}" "/tmp/readsb_restart_hist_${CTN_T1090}"
fi
echo "$CURRENT" > "$STATE_FILE"
echo "$STREAK" > "$STREAK_FILE"
done