#!/bin/bash # ═══════════════════════════════════════════════════════════════════════════════ # export_adsb_bundle.sh — Empaquette le stack ADS-B fonctionnel de Linux-25 # Version 2.0 — JCA 2026 # # À LANCER SUR LINUX-25 (la machine source, celle qui fonctionne déjà). # Produit une archive .tar.gz portable, prête à copier sur la machine neuve # et à installer avec install_adsb_stack.sh. # # v2.0 (05/07/2026) : inclut les correctifs découverts lors des incidents # du 03-05/07/2026 : # - adsb2pg.py : clean_altitude() (bug "ground") + rollback() sur exception # - watchdog_readsb.sh v2 : seuil adaptatif jour/nuit (évite le flapping # nocturne sur les creux de trafic réels) # - schema_adsb_base.sql : schéma générique SANS les partitions datées # spécifiques à Linux-25 (w25-w28, 2026/2027) — la nouvelle machine # amorce ses propres partitions à la date réelle d'installation # - check_adsb.sh v3.4 : watchdog crontab, continuité partitions, RestartCount # - cron d'automatisation des partitions (dimanche 20h, +14 jours) # # Usage : ./export_adsb_bundle.sh [chemin_sortie.tar.gz] # ═══════════════════════════════════════════════════════════════════════════════ set -euo pipefail ADSB_DIR="/data/adsb" OUT_FILE="${1:-/tmp/adsb_bundle_$(date +%Y%m%d_%H%M%S).tar.gz}" WORK_DIR=$(mktemp -d) BUNDLE_DIR="$WORK_DIR/adsb_bundle" GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' ok() { echo -e " ${GREEN}✓${NC} $1"; } fail() { echo -e " ${RED}✗${NC} $1"; } warn() { echo -e " ${YELLOW}⚠${NC} $1"; } info() { echo -e " ${CYAN}→${NC} $1"; } step() { echo -e "\n${BOLD}▸ $1${NC}"; } trap 'rm -rf "$WORK_DIR"' EXIT echo -e "${BOLD}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}║ Export bundle ADS-B — source : $(hostname) ║${NC}" echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}" mkdir -p "$BUNDLE_DIR"/{adsb2pg,webapp,scripts,sql,docs} # ── 1. docker-compose.yml (nettoyé des secrets) ─────────────────────────────── step "1. docker-compose.yml" if [ -f "$ADSB_DIR/docker-compose.yml" ]; then # Remplace le mot de passe en dur par un placeholder — install_adsb_stack.sh # en génère un nouveau et l'injecte à l'installation. sed -E 's/(PG_PASSWORD=)[^[:space:]]+/\1__PG_PASSWORD_PLACEHOLDER__/' \ "$ADSB_DIR/docker-compose.yml" > "$BUNDLE_DIR/docker-compose.yml" ok "docker-compose.yml exporté (mot de passe neutralisé)" else fail "docker-compose.yml introuvable dans $ADSB_DIR" fi # ── 2. adsb2pg (code source complet) ────────────────────────────────────────── step "2. adsb2pg" if [ -d "$ADSB_DIR/adsb2pg" ]; then # --exclude .git : tar1090-db est un dépôt Git cloné en sous-répertoire — # son historique n'a aucune utilité dans l'image Docker et alourdit # inutilement le bundle (souvent plusieurs dizaines de Mo). if command -v rsync &>/dev/null; then rsync -a --exclude='.git' "$ADSB_DIR/adsb2pg/" "$BUNDLE_DIR/adsb2pg/" else cp -r "$ADSB_DIR/adsb2pg/." "$BUNDLE_DIR/adsb2pg/" find "$BUNDLE_DIR/adsb2pg" -type d -name ".git" -exec rm -rf {} + 2>/dev/null fi GIT_LEFT=$(find "$BUNDLE_DIR/adsb2pg" -type d -name ".git" | wc -l) if [ "$GIT_LEFT" -eq 0 ]; then ok "Code adsb2pg copié ($(find "$BUNDLE_DIR/adsb2pg" -name '*.py' | wc -l) fichier(s) .py), .git exclu" else warn ".git non exclu proprement — vérifier manuellement la taille du bundle" fi if grep -q "clean_altitude" "$BUNDLE_DIR/adsb2pg/adsb2pg.py" 2>/dev/null; then ok "Correctif clean_altitude() présent" else warn "Correctif clean_altitude() ABSENT — bundle basé sur une version non corrigée !" fi if grep -q "conn.rollback()" "$BUNDLE_DIR/adsb2pg/adsb2pg.py" 2>/dev/null; then ok "Correctif rollback() présent" else warn "Correctif rollback() ABSENT — bundle basé sur une version non corrigée !" fi else fail "Répertoire adsb2pg introuvable dans $ADSB_DIR" fi # ── 3. webapp (code source complet) ─────────────────────────────────────────── step "3. webapp" if [ -d "$ADSB_DIR/webapp" ]; then cp -r "$ADSB_DIR/webapp/." "$BUNDLE_DIR/webapp/" ok "Code webapp copié" else fail "Répertoire webapp introuvable dans $ADSB_DIR" fi # ── 4. Schéma SQL générique (SANS les partitions datées) ────────────────────── step "4. Schéma PostgreSQL" if [ -f "$ADSB_DIR/schema_adsb_base.sql" ]; then cp "$ADSB_DIR/schema_adsb_base.sql" "$BUNDLE_DIR/sql/schema_adsb_base.sql" ok "schema_adsb_base.sql copié depuis $ADSB_DIR" else warn "schema_adsb_base.sql absent de $ADSB_DIR — utilisation de la copie du bundle courant" info "Pense à le régénérer si le schéma a changé depuis :" info " docker exec wordpress-postgres pg_dump -U pgz_admin -d wpz_postgres --schema-only -n adsb > schema_full.sql" info " puis en retirer les partitions datées (w25-w28, aircraft_history_2026/2027)" fi # ── 5. Scripts opérationnels (watchdog v2, check_adsb v3.4) ─────────────────── step "5. Scripts opérationnels" for f in scripts/watchdog_readsb.sh check_adsb.sh; do SRC="$ADSB_DIR/$f" DEST="$BUNDLE_DIR/scripts/$(basename "$f")" if [ -f "$SRC" ]; then cp "$SRC" "$DEST" chmod +x "$DEST" ok "$(basename "$f") copié" else warn "$SRC introuvable — à ajouter manuellement au bundle avant install" fi done # Vérifier que le watchdog copié est bien la v2 (seuil adaptatif) if grep -q "THRESHOLD" "$BUNDLE_DIR/scripts/watchdog_readsb.sh" 2>/dev/null; then ok "watchdog_readsb.sh : v2 (seuil adaptatif jour/nuit) confirmée" else warn "watchdog_readsb.sh : semble être la v1 (pas de seuil adaptatif) — flapping nocturne possible sur la nouvelle machine" fi # ── 6. udev rule (RTL-SDR) ──────────────────────────────────────────────────── step "6. Règle udev RTL-SDR" if [ -f /etc/udev/rules.d/99-adsb-docker.rules ]; then cp /etc/udev/rules.d/99-adsb-docker.rules "$BUNDLE_DIR/scripts/99-adsb-docker.rules" ok "Règle udev copiée" else warn "Règle udev absente sur cette machine — la nouvelle machine n'aura pas de relance auto USB" fi # ── 7. Documentation / notes de version ─────────────────────────────────────── step "7. Documentation" cat > "$BUNDLE_DIR/docs/CHANGELOG_BUNDLE.md" << 'EOF' # Bundle ADS-B — historique des correctifs inclus ## v2.0 (05/07/2026) - **adsb2pg.py** : fix `clean_altitude()` — readsb/tar1090 renvoient la chaîne littérale `"ground"` pour l'altitude d'un avion au sol, ce qui plantait l'INSERT PostgreSQL (colonne integer). Sans `conn.rollback()` derrière, cette unique erreur bloquait tout le collecteur pendant 13h (incident du 03/07/2026 sur Linux-25). - **watchdog_readsb.sh v2** : seuil adaptatif jour (1 cycle/5min) / nuit (3 cycles/15min) — la v1 redémarrait le dongle sur de simples creux de trafic nocturne, confondus avec un vrai gel matériel. - **schema_adsb_base.sql** : schéma généré à partir d'un pg_dump --schema-only de Linux-25, DÉLIBÉRÉMENT expurgé des partitions datées (semaines/années spécifiques à Linux-25). L'installeur amorce les partitions à la date réelle d'installation via `create_week_partition()`/`create_year_partition()`. - **check_adsb.sh v3.4** : ajout des vérifications watchdog (crontab, fraîcheur, flapping), continuité des partitions, RestartCount Docker, sync NTP. - Automatisation cron de création des partitions (dimanche 20h, +14 jours). ## Notes de déploiement - Le récepteur RTL-SDR de la nouvelle machine aura un numéro de série USB différent — install_adsb_stack.sh le détecte automatiquement et met à jour RECEIVER_SERIAL dans docker-compose.yml + une ligne dans adsb.receivers. - Le mot de passe PostgreSQL est régénéré à l'installation, jamais réutilisé tel quel entre machines. EOF ok "CHANGELOG_BUNDLE.md généré" # ── 8. Archivage ─────────────────────────────────────────────────────────────── step "8. Création de l'archive" tar czf "$OUT_FILE" -C "$WORK_DIR" adsb_bundle SIZE=$(du -sh "$OUT_FILE" | cut -f1) ok "Archive créée : $OUT_FILE ($SIZE)" echo -e "\n${BOLD}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}║ Bundle prêt : $OUT_FILE" echo -e "${BOLD}║ Copie sur la nouvelle machine puis lance : ║${NC}" echo -e "${BOLD}║ tar xzf $(basename "$OUT_FILE") ║${NC}" echo -e "${BOLD}║ cd adsb_bundle && sudo ./install_adsb_stack.sh ║${NC}" echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}"