#!/bin/bash # ═══════════════════════════════════════════════════════════════════════════════ # check_adsb.sh — Vérification complète du stack ADS-B Linux-25 # Version 2.0 — JCA 2026 # ═══════════════════════════════════════════════════════════════════════════════ GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; BLUE='\033[1;34m'; BOLD='\033[1m'; 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"; } hdr() { echo -e "\n${BLUE}${BOLD}═══ $1 ═══${NC}"; } ERRORS=0; WARNS=0 IP=$(ip -4 addr show wlp58s0 2>/dev/null | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' | head -1) [ -z "$IP" ] && IP=$(hostname -I | awk '{print $1}') echo -e "${BOLD}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}║ Stack ADS-B Linux-25 — Vérification complète ║${NC}" echo -e "${BOLD}║ $(date '+%a %d/%m/%Y %H:%M:%S') ║${NC}" echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}" info "IP détectée : ${IP:-inconnue}" # ── 1. CONTENEURS ───────────────────────────────────────────────────────────── hdr "1. Conteneurs Docker" CONTAINERS="adsb-readsb adsb-tar1090 adsb-adsb2pg adsb-webapp wordpress-postgres" for ctn in $CONTAINERS; do STATUS=$(docker inspect --format '{{.State.Status}}' "$ctn" 2>/dev/null) HEALTH=$(docker inspect --format '{{.State.Health.Status}}' "$ctn" 2>/dev/null) if [ "$STATUS" = "running" ]; then UPTIME=$(docker inspect --format '{{.State.StartedAt}}' "$ctn" | xargs -I{} date -d {} '+%d/%m %H:%M' 2>/dev/null) LABEL="${ctn}" [ -n "$HEALTH" ] && [ "$HEALTH" != "" ] && LABEL="$LABEL [$HEALTH]" if [ "$HEALTH" = "unhealthy" ]; then warn "$LABEL — démarré $UPTIME" else ok "$LABEL — démarré $UPTIME" fi else fail "$ctn — STATUS=$STATUS" fi done # ── 2. DONGLE RTL-SDR ───────────────────────────────────────────────────────── hdr "2. Dongle RTL-SDR" if docker logs adsb-readsb 2>&1 | grep -q "SN 00000010"; then ok "AirNav FlightStick SN:00000010 détecté" GAIN=$(docker logs adsb-readsb 2>&1 | grep "tuner gain set to" | tail -1 | grep -oP '[\d.]+(?= dB)') [ -n "$GAIN" ] && info "Gain actuel : ${GAIN} dB (autogain)" else fail "Dongle SN:00000010 non détecté dans les logs" fi # Vérifier le device USB sur l'hôte if ls /dev/adsb_dongle 2>/dev/null | grep -q adsb_dongle; then ok "Symlink /dev/adsb_dongle présent" elif lsusb 2>/dev/null | grep -q "RTL2838\|2838:0001\|0bda:2838"; then ok "Dongle détecté via lsusb (symlink absent)" else warn "Symlink /dev/adsb_dongle absent" fi # ── 3. DONNÉES ADS-B ────────────────────────────────────────────────────────── hdr "3. Données ADS-B" # Vérifier aircraft.json via tar1090 AC_JSON=$(curl -s --max-time 5 "http://127.0.0.1:8090/data/aircraft.json" 2>/dev/null) if [ -n "$AC_JSON" ]; then MSGS=$(echo "$AC_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('messages',0))" 2>/dev/null) AVIONS=$(echo "$AC_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('aircraft',[])))" 2>/dev/null) WITH_POS=$(echo "$AC_JSON" | python3 -c " import json,sys d=json.load(sys.stdin) n=sum(1 for a in d.get('aircraft',[]) if a.get('lat') and a.get('lon')) print(n)" 2>/dev/null) if [ "${MSGS:-0}" -gt 0 ] 2>/dev/null; then ok "tar1090 actif — $MSGS messages décodés" ok "$AVIONS avion(s) en vue dont $WITH_POS avec position GPS" else warn "tar1090 répond mais 0 message (démarrage récent ou aucun avion ?)" fi else fail "tar1090 ne répond pas sur :8090/data/aircraft.json" fi # Volume partagé readsb-run VOL_PATH=$(docker inspect adsb_readsb-run 2>/dev/null | \ python3 -c "import json,sys; d=json.load(sys.stdin); print(d[0]['Mountpoint'])" 2>/dev/null) if [ -n "$VOL_PATH" ] && ls "$VOL_PATH/aircraft.pb" 2>/dev/null | grep -q pb; then AGE=$(( $(date +%s) - $(stat -c %Y "$VOL_PATH/aircraft.pb" 2>/dev/null || echo 0) )) if [ "$AGE" -lt 10 ]; then ok "Volume readsb-run : aircraft.pb mis à jour il y a ${AGE}s" else warn "Volume readsb-run : aircraft.pb âgé de ${AGE}s" fi else warn "Volume readsb-run : aircraft.pb introuvable" fi # ── 4. PORTS RÉSEAU ─────────────────────────────────────────────────────────── hdr "4. Ports réseau" declare -A PORTS=( [":8080"]="webapp PHP" [":8090"]="tar1090 nginx" [":30005"]="Beast TCP readsb" [":5432"]="PostgreSQL" [":30002"]="RAW TCP" [":30003"]="SBS TCP" ) for port_str in ":8080" ":8090" ":30005" ":5432" ":30002" ":30003"; do port="${port_str:1}" label="${PORTS[$port_str]}" if ss -tlnp 2>/dev/null | grep -q ":${port}[[:space:]]"; then ok "Port ${port} en écoute — ${label}" else fail "Port ${port} absent — ${label}" fi done # Connexion Beast tar1090 → readsb BEAST_ERRS=$(docker logs adsb-tar1090 2>&1 | grep "Beast TCP input.*failed" | tail -1) if [ -n "$BEAST_ERRS" ]; then FAIL_CNT=$(echo "$BEAST_ERRS" | grep -oP '\d+(?= times)') if [ "${FAIL_CNT:-0}" -gt 50 ] 2>/dev/null; then fail "Connexion Beast tar1090→readsb : $FAIL_CNT échecs (vérifier réseau adsb-net)" else warn "Quelques échecs Beast TCP au démarrage (normal): $FAIL_CNT" fi else ok "Connexion Beast tar1090→readsb : OK" fi # Réseau adsb-net READSB_IP=$(docker inspect adsb-readsb 2>/dev/null | \ python3 -c " import json,sys d=json.load(sys.stdin)[0] nets=d['NetworkSettings']['Networks'] for n,v in nets.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" else fail "readsb non attaché au réseau adsb-net (Beast TCP impossible)" fi # ── 5. POSTGRESQL ───────────────────────────────────────────────────────────── hdr "5. PostgreSQL — schéma adsb" PG_CMD="docker exec wordpress-postgres psql -U pgz_admin -d wpz_postgres -t -c" # Connexion PG_VER=$($PG_CMD "SELECT version();" 2>/dev/null | head -1 | xargs) if [ -n "$PG_VER" ]; then ok "PostgreSQL accessible" info "${PG_VER:0:60}" else fail "PostgreSQL inaccessible" fi # Partitions positions PART_POS=$($PG_CMD " SELECT COUNT(*) FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE n.nspname='adsb' AND c.relname LIKE 'positions_2%' AND c.relkind='r';" 2>/dev/null | xargs) ok "${PART_POS:-0} sous-partitions adsb.positions" # Partitions aircraft_history PART_HIS=$($PG_CMD " SELECT COUNT(*) FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE n.nspname='adsb' AND c.relname LIKE 'aircraft_history_%' AND c.relkind='r';" 2>/dev/null | xargs) ok "${PART_HIS:-0} sous-partitions adsb.aircraft_history" # Insertions récentes positions POS_COUNT=$($PG_CMD " SELECT COUNT(*) FROM adsb.positions WHERE ts >= NOW() - INTERVAL '5 minutes';" 2>/dev/null | xargs) if [ "${POS_COUNT:-0}" -gt 0 ] 2>/dev/null; then ok "${POS_COUNT} positions insérées dans les 5 dernières minutes" else warn "Aucune position insérée dans les 5 dernières minutes" fi # Pourcentage aircraft_desc rempli PCT_DESC=$($PG_CMD " 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) if [ -n "$PCT_DESC" ] && [ "$PCT_DESC" != "NULL" ]; then if python3 -c "exit(0 if float('${PCT_DESC:-0}') >= 80 else 1)" 2>/dev/null; then ok "aircraft_desc rempli à ${PCT_DESC}% (30 dernières min)" else warn "aircraft_desc rempli à ${PCT_DESC}% seulement (< 80%)" fi fi # Stats générales STATS=$($PG_CMD " SELECT COUNT(DISTINCT icao) AS avions_aujourd_hui, COUNT(*) AS sessions_aujourd_hui FROM adsb.aircraft_history WHERE session_start >= CURRENT_DATE;" 2>/dev/null | xargs) info "Aujourd'hui : $STATS" # Taille de la base adsb DB_SIZE=$($PG_CMD " 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 ──────────────────────────────────────────────────────────────── hdr "6. adsb2pg — collecteur" LAST_LOG=$(docker logs adsb-adsb2pg 2>&1 | grep "positions insérées" | tail -1) if [ -n "$LAST_LOG" ]; then ok "adsb2pg actif : $LAST_LOG" # Vérifier la fraîcheur (dernière log < 30s ?) LOG_TIME=$(docker logs adsb-adsb2pg 2>&1 | grep "positions insérées" | tail -1 | \ grep -oP '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}') if [ -n "$LOG_TIME" ]; then AGE_LOG=$(( $(date +%s) - $(date -d "$LOG_TIME" +%s 2>/dev/null || echo 0) )) if [ "$AGE_LOG" -lt 30 ]; then ok "Dernière insertion il y a ${AGE_LOG}s (cycle 5s actif)" else warn "Dernière insertion il y a ${AGE_LOG}s (cycle 5s peut être bloqué)" fi fi else warn "Aucun log d'insertion trouvé dans adsb2pg" fi # ── 7. WEBAPP ───────────────────────────────────────────────────────────────── hdr "7. Webapp — interface utilisateur" # Test API endpoints 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) if echo "$RESP" | python3 -c "import json,sys; json.load(sys.stdin)" 2>/dev/null; then # Vérifier pas d'erreur PHP if echo "$RESP" | grep -q '"error"'; then ERR=$(echo "$RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('error','?'))" 2>/dev/null) warn "api.php?action=${action} : erreur — $ERR" else ok "api.php?action=${action} : OK" fi else fail "api.php?action=${action} : réponse non-JSON" fi done # Vérifier les onglets dans index.php TAB_COUNT=$(docker exec adsb-webapp grep -c 'href="#tab' /var/www/html/index.php 2>/dev/null) ok "${TAB_COUNT:-?} onglets dans index.php" [ "${TAB_COUNT:-0}" -lt 5 ] && warn "Moins de 5 onglets — index.php peut être obsolète" # Taille du JS JS_SIZE=$(docker exec adsb-webapp wc -c /var/www/html/js/app.js 2>/dev/null | awk '{print $1}') info "app.js : ${JS_SIZE:-?} octets" # Accès depuis le LAN if [ -n "$IP" ]; then HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://${IP}:8080/" 2>/dev/null) [ "$HTTP_CODE" = "200" ] && ok "Accès LAN http://${IP}:8080 : HTTP $HTTP_CODE" || \ warn "Accès LAN http://${IP}:8080 : HTTP $HTTP_CODE" fi # ── 8. AUTOGAIN ─────────────────────────────────────────────────────────────── hdr "8. Autogain" GAIN_LOG=$(docker logs adsb-readsb 2>&1 | grep -E "gain|autogain" | tail -5) CURRENT_GAIN=$(docker logs adsb-readsb 2>&1 | grep "tuner gain set to" | tail -1 | \ grep -oP '[\d.]+(?= dB)') STATE=$(docker logs adsb-readsb 2>&1 | grep "autogain" | grep -oP "(?<=state ')[\w]+" | tail -1) if [ -n "$CURRENT_GAIN" ]; then ok "Gain courant : ${CURRENT_GAIN} dB (état: ${STATE:-inconnu})" else warn "Gain courant inconnu" fi INSUF=$(docker logs adsb-readsb 2>&1 | grep "Insufficient messages" | wc -l) [ "$INSUF" -gt 3 ] && warn "$INSUF cycles avec messages insuffisants (antenne/portée ?)" || \ [ "$INSUF" -gt 0 ] && info "$INSUF cycle(s) avec messages insuffisants" # ── RÉSUMÉ ──────────────────────────────────────────────────────────────────── echo "" echo -e "${BOLD}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}║ RÉSUMÉ ║${NC}" echo -e "${BOLD}╠══════════════════════════════════════════════════════╣${NC}" if [ "$ERRORS" -eq 0 ] && [ "$WARNS" -eq 0 ]; then echo -e "${BOLD}║ ${GREEN}✓ Stack opérationnel — aucune anomalie détectée${NC}${BOLD} ║${NC}" elif [ "$ERRORS" -eq 0 ]; then echo -e "${BOLD}║ ${YELLOW}⚠ Stack opérationnel — $WARNS avertissement(s)${NC}${BOLD} ║${NC}" else echo -e "${BOLD}║ ${RED}✗ $ERRORS erreur(s), $WARNS avertissement(s) détectés${NC}${BOLD} ║${NC}" fi echo -e "${BOLD}║ Dashboard : http://${IP}:8080${NC}${BOLD} ║${NC}" echo -e "${BOLD}║ tar1090 : http://${IP}:8090${NC}${BOLD} ║${NC}" echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}" exit $ERRORS