#!/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