#!/bin/bash # ═══════════════════════════════════════════════════════════════════════════════ # update-webapp.sh — Rebuild et redéploiement de la webapp ADS-B # Usage : ./update-webapp.sh [--no-cache] [--check-only] # ═══════════════════════════════════════════════════════════════════════════════ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$SCRIPT_DIR" # ── Fichiers d'environnement (v4.2) ────────────────────────────────────────── # Depuis les profiles Compose, un « docker compose » nu affiche # « The "RX1_SERIAL" variable is not set » à chaque appel. Inoffensif pour la # webapp (aucun profile), mais le bruit masque les vraies erreurs — et si # PG_PASSWORD n'est pas injecté, le conteneur démarre sans accès à la base. COMPOSE_ARGS=() [[ -f "$SCRIPT_DIR/.env" ]] && COMPOSE_ARGS+=(--env-file "$SCRIPT_DIR/.env") [[ -f "$SCRIPT_DIR/.env.receivers" ]] && COMPOSE_ARGS+=(--env-file "$SCRIPT_DIR/.env.receivers") dc() { docker compose "${COMPOSE_ARGS[@]}" "$@"; } # ── Couleurs ────────────────────────────────────────────────────────────────── 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"; } hdr() { echo -e "\n${BOLD}$1${NC}"; } NO_CACHE="" CHECK_ONLY=0 for arg in "$@"; do case "$arg" in --no-cache) NO_CACHE="--no-cache" ;; --check-only) CHECK_ONLY=1 ;; esac done echo -e "${BOLD}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}║ Déploiement webapp ADS-B Linux-25 ║${NC}" echo -e "${BOLD}║ $(date '+%a %d/%m/%Y %H:%M:%S') ║${NC}" echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}" # ── 1. Prérequis ────────────────────────────────────────────────────────────── hdr "1. Vérification des prérequis" # Docker disponible ? if ! docker info &>/dev/null; then fail "Docker non disponible — arrêt" exit 1 fi ok "Docker disponible ($(docker version --format '{{.Server.Version}}' 2>/dev/null))" # Dockerfile présent ? if [ ! -f "$SCRIPT_DIR/webapp/Dockerfile" ]; then fail "webapp/Dockerfile introuvable dans $SCRIPT_DIR" exit 1 fi ok "Dockerfile présent : $SCRIPT_DIR/webapp/Dockerfile" # Sources présentes ? SRC_COUNT=$(find "$SCRIPT_DIR/webapp/src" -type f 2>/dev/null | wc -l) if [ "$SRC_COUNT" -eq 0 ]; then fail "Aucun fichier source dans webapp/src/" exit 1 fi ok "$SRC_COUNT fichiers sources dans webapp/src/" # ── 2. Accès internet ───────────────────────────────────────────────────────── hdr "2. Vérification accès internet" INTERNET=0 URLS_TO_CHECK=( "https://cdn.jsdelivr.net/" "https://unpkg.com/" ) for url in "${URLS_TO_CHECK[@]}"; do CODE=$(curl -s --max-time 5 -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000") if [ "$CODE" != "000" ] && [ "$CODE" != "599" ]; then ok "Accès internet OK ($url → HTTP $CODE)" INTERNET=1 break else warn "Pas de réponse : $url" fi done if [ "$INTERNET" -eq 0 ]; then warn "Aucun accès internet détecté" # Vérifier si les assets sont déjà dans l'image existante EXISTING_ASSETS=$(docker exec adsb-webapp ls /var/www/html/assets/ 2>/dev/null | wc -l || echo 0) if [ "$EXISTING_ASSETS" -gt 0 ]; then warn "Assets déjà présents dans le conteneur ($EXISTING_ASSETS fichiers)" warn "Le build va échouer si l'image doit être recréée depuis zéro" warn "Option : copier uniquement les sources sans rebuild" echo "" echo -e " ${YELLOW}Que voulez-vous faire ?${NC}" echo " [1] Tenter le rebuild quand même (peut échouer sans internet)" echo " [2] Copier uniquement les sources dans le conteneur existant" echo " [3] Annuler" read -rp " Choix [1/2/3] : " CHOIX case "$CHOIX" in 2) hdr "Copie des sources dans le conteneur existant" for f in "$SCRIPT_DIR/webapp/src/"*; do fname=$(basename "$f") if [ -d "$f" ]; then docker cp "$f" "adsb-webapp:/var/www/html/$fname" && \ ok "Copié : $fname/" || warn "Échec : $fname/" else docker cp "$f" "adsb-webapp:/var/www/html/$fname" && \ ok "Copié : $fname" || warn "Échec : $fname" fi done docker exec adsb-webapp chown -R www-data:www-data /var/www/html/ 2>/dev/null || true ok "Sources mises à jour sans rebuild" exit 0 ;; 3) info "Annulé" exit 0 ;; *) warn "Tentative de rebuild sans garantie de succès" ;; esac else fail "Pas d'internet et aucun conteneur existant — impossible de builder" exit 1 fi fi [ "$CHECK_ONLY" -eq 1 ] && { info "Mode --check-only : arrêt ici"; exit 0; } # ── 3. Build de l'image ─────────────────────────────────────────────────────── hdr "3. Build de l'image webapp" info "Lancement du build${NO_CACHE:+ (--no-cache)}..." BUILD_START=$(date +%s) if dc build $NO_CACHE webapp 2>&1 | tee /tmp/webapp-build.log | \ grep -E "^(Step|#[0-9]|Successfully|ERROR|error)" ; then BUILD_END=$(date +%s) ok "Build réussi en $((BUILD_END - BUILD_START))s" else BUILD_END=$(date +%s) fail "Build échoué après $((BUILD_END - BUILD_START))s" echo "" warn "Dernières lignes du log :" tail -20 /tmp/webapp-build.log exit 1 fi # ── 4. Redémarrage du conteneur ─────────────────────────────────────────────── hdr "4. Redémarrage du conteneur" info "Arrêt de adsb-webapp..." dc stop webapp dc rm -f webapp info "Démarrage avec la nouvelle image..." dc up -d webapp sleep 8 # ── 5. Vérification post-déploiement ───────────────────────────────────────── hdr "5. Vérification post-déploiement" # Conteneur running ? STATUS=$(docker inspect --format '{{.State.Status}}' adsb-webapp 2>/dev/null || echo "absent") [ "$STATUS" = "running" ] && ok "Conteneur adsb-webapp : running" \ || fail "Conteneur adsb-webapp : $STATUS" # Port 8080 en écoute ? ss -tlnp 2>/dev/null | grep -q ":8080" \ && ok "Port 8080 en écoute" \ || fail "Port 8080 absent" # HTTP 200 ? HTTP=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://127.0.0.1:8080/" 2>/dev/null) [ "$HTTP" = "200" ] && ok "HTTP 200 sur http://127.0.0.1:8080/" \ || fail "HTTP $HTTP sur http://127.0.0.1:8080/" # Nombre d'onglets ? TABS=$(docker exec adsb-webapp grep -c 'href="#tab' /var/www/html/index.php 2>/dev/null || echo 0) [ "$TABS" -ge 5 ] && ok "$TABS onglets détectés dans index.php" \ || warn "$TABS onglets détectés (< 5 attendus)" # Taille app.js ? 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" # ── Contrôles fonctionnels de l'API (v4.2) ─────────────────────────────────── # Le fuseau doit être ancré : sans date_default_timezone_set, PHP retombe sur # UTC et la carte historique interroge une fenêtre décalée de 2 h — elle # revient alors systématiquement vide, sans la moindre erreur HTTP. if docker exec adsb-webapp grep -q "date_default_timezone_set" /var/www/html/api.php 2>/dev/null; then ok "api.php : fuseau horaire ancré (date_default_timezone_set)" else warn "api.php : date_default_timezone_set absent — carte historique probablement vide (décalage UTC/CEST)" fi for act in live kpi trajectories; do BODY=$(curl -s --max-time 10 "http://127.0.0.1:8080/api.php?action=${act}" 2>/dev/null) if echo "$BODY" | head -c 1 | grep -q '{'; then ok "api.php?action=${act} : JSON valide" else warn "api.php?action=${act} : réponse inattendue" fi done # La carte historique est le seul endpoint sensible au fuseau : on l'interroge # sur une fenêtre récente, qui doit contenir des données si le stack reçoit. HIST_FROM=$(date -d '-40 minutes' '+%Y-%m-%d %H:%M:%S') HIST=$(curl -s --max-time 20 --get \ --data-urlencode "action=trajectories_history" \ --data-urlencode "from=${HIST_FROM}" \ --data-urlencode "window=30" \ "http://127.0.0.1:8080/api.php" 2>/dev/null) HIST_COUNT=$(echo "$HIST" | grep -oP '"count"\s*:\s*\K[0-9]+' | head -1) if [ "${HIST_COUNT:-0}" -gt 0 ]; then ok "Carte historique : ${HIST_COUNT} avion(s) sur les 30 dernières minutes" else warn "Carte historique : 0 avion sur une fenêtre récente — vérifier le fuseau (api.php) ou l'ingestion adsb2pg" fi # Assets présents ? ASSETS=$(docker exec adsb-webapp ls /var/www/html/assets/ 2>/dev/null | wc -l) ok "$ASSETS fichiers assets dans /var/www/html/assets/" # Drapeaux : sans eux, tous les pays s'affichent avec le pictogramme « ? ». # Le Dockerfile les télécharge, mais un build sans réseau les laisse absents. FLAG_N=$(docker exec adsb-webapp sh -c 'ls /var/www/html/assets/flags/*.png 2>/dev/null | wc -l' 2>/dev/null) if [ "${FLAG_N:-0}" -gt 50 ]; then ok "$FLAG_N drapeau(x) disponible(s)" else warn "Seulement ${FLAG_N:-0} drapeau(x) — les pays afficheront un pictogramme générique" warn "Correctif : /data/adsb/scripts/fetch_flags.sh puis relancer ce script" fi # Endpoints d'analyse des départs for act in airports departures_offfield_zones; do BODY=$(curl -s --max-time 25 "http://127.0.0.1:8080/api.php?action=${act}&window=1440" 2>/dev/null) if echo "$BODY" | grep -q '"error"'; then warn "api.php?action=${act} : $(echo "$BODY" | head -c 110)" elif echo "$BODY" | head -c 1 | grep -q '{'; then ok "api.php?action=${act} : JSON valide" else warn "api.php?action=${act} : réponse inattendue" fi done # IP pour le lien final IP=$(ip -4 addr show 2>/dev/null | grep -oP '(?<=inet )192\.[0-9.]+' | head -1) [ -z "$IP" ] && IP=$(hostname -I | awk '{print $1}') echo "" echo -e "${BOLD}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}║ ✓ Déploiement terminé ║${NC}" echo -e "${BOLD}║ Dashboard : http://${IP}:8080${NC}${BOLD} ║${NC}" echo -e "${BOLD}╚══════════════════════════════════════════════════════╝${NC}"