282 lines
15 KiB
Bash
Executable File
282 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
# =============================================
|
|
# Script de vérification complète de la stack ADS-B
|
|
# Version : 9.0 (Corrigé par Claude)
|
|
# Date : 26/06/2026
|
|
# Auteur : JC Abiven
|
|
# =============================================
|
|
|
|
if ! command -v jq &> /dev/null; then
|
|
echo -e "\033[0;31m✗ jq n'est pas installé : sudo apt install jq\033[0m"
|
|
exit 1
|
|
fi
|
|
|
|
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'; MAGENTA='\033[0;35m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
|
|
ok() { echo -e "${GREEN} ✓${NC} $1"; }
|
|
fail() { echo -e "${RED} ✗${NC} $1"; ERRORS=$((ERRORS+1)); }
|
|
warn() { echo -e "${YELLOW} ⚠${NC} $1"; WARNS=$((WARNS+1)); }
|
|
info() { echo -e "${CYAN} →${NC} $1"; }
|
|
sec() { echo -e "\n${CYAN}═══ $1 ═══${NC}"; }
|
|
|
|
ERRORS=0; WARNS=0
|
|
HOST_IP=$(ip -4 addr show wlp58s0 2>/dev/null | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' | head -1)
|
|
[ -z "$HOST_IP" ] && HOST_IP=$(hostname -I | awk '{print $1}')
|
|
|
|
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════╗"
|
|
echo -e "║ Stack ADS-B Linux-25 — Vérification complète ║"
|
|
echo -e "║ $(date '+%a %d/%m/%Y %H:%M:%S') ║"
|
|
echo -e "╚══════════════════════════════════════════════════════╝${NC}"
|
|
info "IP détectée : $HOST_IP"
|
|
|
|
# ── 1. Conteneurs ─────────────────────────────────────────────────────────────
|
|
sec "1. Conteneurs Docker"
|
|
for ctn in adsb-readsb adsb-tar1090 adsb-adsb2pg adsb-webapp wordpress-postgres; do
|
|
STATUS=$(docker inspect --format '{{.State.Status}}' "$ctn" 2>/dev/null)
|
|
HEALTH=$(docker inspect --format '{{.State.Health.Status}}' "$ctn" 2>/dev/null)
|
|
UPTIME=$(docker inspect --format '{{.State.StartedAt}}' "$ctn" 2>/dev/null | \
|
|
xargs -I{} date -d {} '+%d/%m %H:%M' 2>/dev/null)
|
|
if [ "$STATUS" = "running" ]; then
|
|
LABEL="$ctn"
|
|
[ "$HEALTH" = "healthy" ] && LABEL="$ctn [healthy]"
|
|
[ "$HEALTH" = "unhealthy" ] && LABEL="$ctn [unhealthy]" && WARNS=$((WARNS+1))
|
|
[ "$HEALTH" = "unhealthy" ] \
|
|
&& warn "$LABEL — démarré $UPTIME" \
|
|
|| ok "$LABEL — démarré $UPTIME"
|
|
else
|
|
fail "$ctn — STATUS=${STATUS:-absent}"
|
|
fi
|
|
done
|
|
|
|
# ── 2. Dongle RTL-SDR ─────────────────────────────────────────────────────────
|
|
sec "2. Dongle RTL-SDR"
|
|
if docker logs adsb-readsb 2>&1 | grep -q "SN 00000010"; then
|
|
# BUG CORRIGÉ : grep -oP avec lookbehind correct
|
|
GAIN=$(docker logs adsb-readsb 2>&1 | grep "tuner gain set to" | tail -1 \
|
|
| grep -oP '[\d.]+(?= dB)')
|
|
ok "AirNav FlightStick SN:00000010 détecté — gain ${GAIN:-?} dB"
|
|
else
|
|
fail "Dongle SN:00000010 non détecté"
|
|
fi
|
|
[ -L "/dev/adsb_dongle" ] && ok "Symlink /dev/adsb_dongle présent" \
|
|
|| warn "Symlink /dev/adsb_dongle introuvable"
|
|
|
|
# ── 3. Données ADS-B ──────────────────────────────────────────────────────────
|
|
sec "3. Données ADS-B"
|
|
AC_JSON=$(curl -s --max-time 5 "http://127.0.0.1:8090/data/aircraft.json" 2>/dev/null)
|
|
if [ -n "$AC_JSON" ]; then
|
|
# BUG CORRIGÉ : utiliser .aircraft[] et pas .[] (qui itère aussi sur messages/now)
|
|
MSGS=$(echo "$AC_JSON" | jq -r '.messages // 0')
|
|
AVIONS=$(echo "$AC_JSON" | jq '[.aircraft // []] | length')
|
|
WITH_POS=$(echo "$AC_JSON" | jq '[.aircraft[]? | select(.lat != null and .lon != null)] | length')
|
|
[ "${MSGS:-0}" -gt 0 ] \
|
|
&& ok "tar1090 actif — $(printf '%d' "$MSGS") messages décodés" \
|
|
|| warn "tar1090 répond — 0 message (démarrage récent ?)"
|
|
ok "$AVIONS avion(s) en vue dont $WITH_POS avec position GPS"
|
|
else
|
|
fail "tar1090 ne répond pas sur :8090/data/aircraft.json"
|
|
AVIONS=0; WITH_POS=0; MSGS=0
|
|
fi
|
|
|
|
# BUG CORRIGÉ : vérifier via docker exec (le volume tmpfs n'est pas accessible hôte)
|
|
if docker exec adsb-readsb test -f /run/readsb/aircraft.pb 2>/dev/null; then
|
|
ok "Volume readsb-run : aircraft.pb présent dans le conteneur readsb"
|
|
elif docker exec adsb-tar1090 test -f /run/readsb/aircraft.pb 2>/dev/null; then
|
|
ok "Volume readsb-run : aircraft.pb présent dans le conteneur tar1090"
|
|
else
|
|
warn "aircraft.pb introuvable dans les conteneurs"
|
|
fi
|
|
|
|
# ── 4. Ports réseau ───────────────────────────────────────────────────────────
|
|
sec "4. Ports réseau"
|
|
declare -A PORT_LABELS=(
|
|
["8080"]="webapp PHP" ["8090"]="tar1090 nginx"
|
|
["30005"]="Beast TCP" ["5432"]="PostgreSQL"
|
|
["30002"]="RAW TCP" ["30003"]="SBS TCP"
|
|
)
|
|
for port in 8080 8090 30005 5432 30002 30003; do
|
|
LISTEN=$(ss -tlnp 2>/dev/null | grep ":${port}[[:space:]]" | awk '{print $4}' | head -1)
|
|
if [ -n "$LISTEN" ]; then
|
|
ok "Port $port en écoute sur $LISTEN — ${PORT_LABELS[$port]}"
|
|
else
|
|
fail "Port $port absent — ${PORT_LABELS[$port]}"
|
|
fi
|
|
done
|
|
|
|
# Connexion Beast via gateway adsb-net
|
|
READSB_IP=$(docker inspect adsb-readsb 2>/dev/null | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)[0]
|
|
for n,v in d['NetworkSettings']['Networks'].items():
|
|
if 'adsb' in n: print(v['IPAddress'])" 2>/dev/null)
|
|
if [ -n "$READSB_IP" ] && [ "$READSB_IP" != "None" ]; then
|
|
ok "readsb attaché à adsb-net : $READSB_IP (gateway 172.20.0.1)"
|
|
else
|
|
fail "readsb non attaché à adsb-net — Beast TCP impossible"
|
|
fi
|
|
|
|
# ── 5. PostgreSQL ─────────────────────────────────────────────────────────────
|
|
sec "5. PostgreSQL — schéma adsb"
|
|
PG="docker exec wordpress-postgres psql -U pgz_admin -d wpz_postgres -t -c"
|
|
|
|
PG_VER=$($PG "SELECT version();" 2>/dev/null | head -1 | sed 's/ */ /g' | xargs)
|
|
if [ -n "$PG_VER" ]; then
|
|
ok "PostgreSQL accessible"
|
|
info "${PG_VER:0:70}"
|
|
else
|
|
fail "PostgreSQL inaccessible"
|
|
fi
|
|
|
|
# BUG CORRIGÉ : pg_tables plutôt que pg_class pour les partitions feuilles
|
|
PART_POS=$($PG "SELECT COUNT(*) FROM pg_tables
|
|
WHERE schemaname='adsb' AND tablename LIKE 'positions_%';" 2>/dev/null | xargs)
|
|
ok "${PART_POS:-0} sous-partitions adsb.positions"
|
|
|
|
PART_HIS=$($PG "SELECT COUNT(*) FROM pg_tables
|
|
WHERE schemaname='adsb' AND tablename LIKE 'aircraft_history_%';" 2>/dev/null | xargs)
|
|
ok "${PART_HIS:-0} sous-partitions adsb.aircraft_history"
|
|
|
|
POS_5MIN=$($PG "SELECT COUNT(*) FROM adsb.positions
|
|
WHERE ts >= NOW() - INTERVAL '5 minutes';" 2>/dev/null | xargs)
|
|
[ "${POS_5MIN:-0}" -gt 0 ] 2>/dev/null \
|
|
&& ok "$POS_5MIN positions insérées dans les 5 dernières minutes" \
|
|
|| warn "Aucune position dans les 5 dernières minutes"
|
|
|
|
PCT_DESC=$($PG "SELECT ROUND(COUNT(aircraft_desc)*100.0/NULLIF(COUNT(*),0),1)
|
|
FROM adsb.positions WHERE ts >= NOW() - INTERVAL '30 minutes'
|
|
AND lat IS NOT NULL;" 2>/dev/null | xargs)
|
|
python3 -c "exit(0 if float('${PCT_DESC:-0}') >= 80 else 1)" 2>/dev/null \
|
|
&& ok "aircraft_desc rempli à ${PCT_DESC}% (30 dernières min)" \
|
|
|| warn "aircraft_desc rempli à ${PCT_DESC:-0}% (< 80%)"
|
|
|
|
AV_TODAY=$($PG "SELECT COUNT(DISTINCT icao) FROM adsb.aircraft_history
|
|
WHERE session_start >= CURRENT_DATE;" 2>/dev/null | xargs)
|
|
SESS_TODAY=$($PG "SELECT COUNT(*) FROM adsb.aircraft_history
|
|
WHERE session_start >= CURRENT_DATE;" 2>/dev/null | xargs)
|
|
info "Aujourd'hui : ${AV_TODAY:-0} avions distincts, ${SESS_TODAY:-0} sessions"
|
|
|
|
# BUG CORRIGÉ : pg_size_pretty sur le schéma via pg_class
|
|
DB_SIZE=$($PG "SELECT pg_size_pretty(SUM(pg_total_relation_size(c.oid)))
|
|
FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
|
|
WHERE n.nspname='adsb';" 2>/dev/null | xargs)
|
|
info "Taille totale schéma adsb : ${DB_SIZE:-?}"
|
|
|
|
# ── 6. adsb2pg ────────────────────────────────────────────────────────────────
|
|
sec "6. adsb2pg — collecteur"
|
|
LAST_INS=$(docker logs adsb-adsb2pg 2>&1 | grep "positions insérées" | tail -1)
|
|
if [ -n "$LAST_INS" ]; then
|
|
ok "adsb2pg actif"
|
|
info "$LAST_INS"
|
|
# BUG CORRIGÉ : calculer l'âge depuis PostgreSQL (plus fiable que les logs)
|
|
LAST_SEC=$($PG "SELECT EXTRACT(EPOCH FROM (NOW() - MAX(inserted_at)))::INT
|
|
FROM adsb.positions WHERE inserted_at >= NOW() - INTERVAL '10 minutes';" \
|
|
2>/dev/null | xargs)
|
|
LAST_SEC_INT=${LAST_SEC%.*}
|
|
if [ -n "$LAST_SEC_INT" ] && [ "$LAST_SEC_INT" -le 15 ] 2>/dev/null; then
|
|
ok "Dernière insertion il y a ${LAST_SEC_INT}s (cycle 5s actif)"
|
|
elif [ -n "$LAST_SEC_INT" ]; then
|
|
warn "Dernière insertion il y a ${LAST_SEC_INT}s (> 15s)"
|
|
fi
|
|
else
|
|
warn "Aucun log d'insertion trouvé dans adsb2pg"
|
|
fi
|
|
|
|
# ── 7. Webapp ─────────────────────────────────────────────────────────────────
|
|
sec "7. Webapp — interface utilisateur"
|
|
for action in live kpi trajectories; do
|
|
RESP=$(curl -s --max-time 5 \
|
|
"http://127.0.0.1:8080/api.php?action=${action}&period=24&granularity=60" 2>/dev/null)
|
|
ERR=$(echo "$RESP" | jq -r '.error // empty' 2>/dev/null)
|
|
if echo "$RESP" | jq . >/dev/null 2>&1; then
|
|
[ -n "$ERR" ] && warn "api.php?action=${action} : $ERR" \
|
|
|| ok "api.php?action=${action} : JSON valide"
|
|
else
|
|
fail "api.php?action=${action} : réponse non-JSON"
|
|
fi
|
|
done
|
|
|
|
# BUG CORRIGÉ : compter les onglets directement dans le conteneur
|
|
TAB_CNT=$(docker exec adsb-webapp grep -c 'href="#tab' /var/www/html/index.php 2>/dev/null)
|
|
ok "${TAB_CNT:-?} onglets dans index.php"
|
|
[ "${TAB_CNT:-0}" -lt 5 ] 2>/dev/null && warn "Moins de 5 onglets — index.php peut être obsolète"
|
|
|
|
# BUG CORRIGÉ : taille app.js directement dans le conteneur
|
|
JS_SZ=$(docker exec adsb-webapp wc -c /var/www/html/js/app.js 2>/dev/null | awk '{print $1}')
|
|
info "app.js : ${JS_SZ:-?} octets"
|
|
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://${HOST_IP}:8080/" 2>/dev/null)
|
|
[ "$HTTP_CODE" = "200" ] \
|
|
&& ok "Accès LAN http://${HOST_IP}:8080 : HTTP $HTTP_CODE" \
|
|
|| warn "Accès LAN http://${HOST_IP}:8080 : HTTP ${HTTP_CODE:-?}"
|
|
|
|
# ── 8. Autogain ───────────────────────────────────────────────────────────────
|
|
sec "8. Autogain"
|
|
# BUG CORRIGÉ : pattern grep correct pour le gain
|
|
CUR_GAIN=$(docker logs adsb-readsb 2>&1 | grep "tuner gain set to" | tail -1 \
|
|
| grep -oP '[\d.]+(?= dB)')
|
|
AG_STATE=$(docker logs adsb-readsb 2>&1 | grep -oP "(?<=state ')[\w]+" | tail -1)
|
|
[ -n "$CUR_GAIN" ] \
|
|
&& ok "Gain courant : ${CUR_GAIN} dB — état : ${AG_STATE:-inconnu}" \
|
|
|| warn "Gain courant inconnu"
|
|
|
|
# BUG CORRIGÉ : recherche en anglais (les logs readsb sont en anglais)
|
|
INSUF=$(docker logs adsb-readsb 2>&1 | grep -c "Insufficient messages")
|
|
[ "$INSUF" -gt 3 ] \
|
|
&& warn "$INSUF cycles avec messages insuffisants (antenne/portée ?)" \
|
|
|| info "$INSUF cycle(s) avec messages insuffisants"
|
|
|
|
# ── 9. Statistiques readsb ────────────────────────────────────────────────────
|
|
sec "9. Statistiques readsb"
|
|
# BUG CORRIGÉ : les stats sont sur lighttpd port 8080 interne, pas 30003
|
|
# Le bon endpoint est /data/stats.json via tar1090
|
|
STATS_JSON=$(curl -s --max-time 5 "http://127.0.0.1:8090/data/stats.json" 2>/dev/null)
|
|
if [ -n "$STATS_JSON" ] && echo "$STATS_JSON" | jq . >/dev/null 2>&1; then
|
|
# Stats last 1 min
|
|
MSGS_1MIN=$(echo "$STATS_JSON" | jq -r '.last1min.local.messages_strong_signal // .last1min.local.decoded // 0' 2>/dev/null)
|
|
MSGS_TOTAL=$(echo "$STATS_JSON" | jq -r '.total.local.messages // 0' 2>/dev/null)
|
|
RANGE=$(echo "$STATS_JSON" | jq -r '.last1min.local.max_distance_meters // 0' 2>/dev/null)
|
|
RANGE_KM=$(python3 -c "print(round(${RANGE:-0}/1000,1))" 2>/dev/null)
|
|
TRACKS=$(echo "$STATS_JSON" | jq -r '.last1min.local.tracks_with_pos // 0' 2>/dev/null)
|
|
ok "Statistiques readsb disponibles (via tar1090 :8090)"
|
|
info "Messages totaux : ${MSGS_TOTAL:-0}"
|
|
info "Messages dernière minute : ${MSGS_1MIN:-0}"
|
|
info "Pistes avec position (1min) : ${TRACKS:-0}"
|
|
info "Portée max (1min) : ${RANGE_KM:-0} km"
|
|
else
|
|
# Fallback : stats depuis lighttpd interne readsb
|
|
STATS2=$(docker exec adsb-readsb wget -qO- "http://localhost:8080/data/stats.json" 2>/dev/null)
|
|
if [ -n "$STATS2" ] && echo "$STATS2" | jq . >/dev/null 2>&1; then
|
|
MSGS_TOTAL=$(echo "$STATS2" | jq -r '.total.local.messages // 0')
|
|
ok "Statistiques readsb (via lighttpd interne) — Messages totaux : $MSGS_TOTAL"
|
|
else
|
|
warn "Statistiques readsb non disponibles"
|
|
info "Utiliser : docker exec adsb-readsb cat /run/readsb/stats.pb"
|
|
fi
|
|
fi
|
|
|
|
# Fichiers stats protobuf (toujours disponibles)
|
|
STATS_AGE=$(docker exec adsb-readsb stat -c %Y /run/readsb/stats.pb 2>/dev/null)
|
|
if [ -n "$STATS_AGE" ]; then
|
|
AGE_S=$(( $(date +%s) - STATS_AGE ))
|
|
ok "stats.pb dans readsb-run, mis à jour il y a ${AGE_S}s"
|
|
fi
|
|
|
|
# ── RÉSUMÉ ────────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${MAGENTA}╔══════════════════════════════════════════════════════╗"
|
|
echo -e "║ RÉSUMÉ ║"
|
|
echo -e "╠══════════════════════════════════════════════════════╣${NC}"
|
|
if [ "$ERRORS" -eq 0 ] && [ "$WARNS" -eq 0 ]; then
|
|
echo -e "${MAGENTA}║${NC} ${GREEN}✓ Stack opérationnel — aucune anomalie détectée${NC} ${MAGENTA}║${NC}"
|
|
elif [ "$ERRORS" -eq 0 ]; then
|
|
echo -e "${MAGENTA}║${NC} ${YELLOW}⚠ Stack opérationnel — $WARNS avertissement(s)${NC} ${MAGENTA}║${NC}"
|
|
else
|
|
echo -e "${MAGENTA}║${NC} ${RED}✗ $ERRORS erreur(s), $WARNS avertissement(s)${NC} ${MAGENTA}║${NC}"
|
|
fi
|
|
echo -e "${MAGENTA}║${NC} ${BLUE}Dashboard : http://${HOST_IP}:8080${NC} ${MAGENTA}║${NC}"
|
|
echo -e "${MAGENTA}║${NC} ${BLUE}tar1090 : http://${HOST_IP}:8090${NC} ${MAGENTA}║${NC}"
|
|
echo -e "${MAGENTA}╚══════════════════════════════════════════════════════╝${NC}"
|
|
|
|
exit $ERRORS
|