#!/usr/bin/env bash # ============================================================================== # frogg_freebox_check.sh # Script Zabbix - Métriques Freebox via API V10 # Compatible : Zabbix 7.0 / ExternalCheck # Auteur : frogg (généré) # Répertoire : /usr/lib/zabbix/externalscripts/ # Usage : frogg_freebox_check.sh # ============================================================================== set -uo pipefail # Note: -e retiré volontairement pour gérer les erreurs curl manuellement # ------------------------------------------------------------------------------ # Dépendances requises : curl, jq, openssl # ------------------------------------------------------------------------------ for cmd in curl jq openssl bc; do if ! command -v "$cmd" &>/dev/null; then echo "ERREUR: '$cmd' est requis mais introuvable." >&2 exit 1 fi done # ------------------------------------------------------------------------------ # Paramètres (fournis par Zabbix via macros du template) # ------------------------------------------------------------------------------ APP_ID="${1:-}" APP_TOKEN="${2:-}" FREEBOX_HOST="${3:-mafreebox.freebox.fr}" METRIC="${4:-}" # Mode debug : passer DEBUG=1 en variable d'environnement pour voir les erreurs curl DEBUG="${DEBUG:-0}" if [[ -z "$APP_ID" || -z "$APP_TOKEN" || -z "$FREEBOX_HOST" || -z "$METRIC" ]]; then echo "Usage: $0 " >&2 echo "Métriques disponibles:" >&2 echo " cpu_percent - Utilisation CPU (%)" >&2 echo " ram_percent - Utilisation RAM (%)" >&2 echo " ram_used - RAM utilisée (bytes)" >&2 echo " ram_total - RAM totale (bytes)" >&2 echo " hdd_percent - Utilisation HDD (%)" >&2 echo " hdd_used - HDD utilisé (bytes)" >&2 echo " hdd_total - HDD total (bytes)" >&2 echo " net_rate_down - Débit descendant actuel (bit/s)" >&2 echo " net_rate_up - Débit montant actuel (bit/s)" >&2 echo " net_bytes_down - Total octets reçus" >&2 echo " net_bytes_up - Total octets envoyés" >&2 echo " net_bw_down - Bande passante max descendante (bit/s)" >&2 echo " net_bw_up - Bande passante max montante (bit/s)" >&2 echo " temp_cpum - Température CPU mère (°C)" >&2 echo " temp_cpub - Température CPU box (°C)" >&2 echo " temp_sw - Température switch (°C)" >&2 echo " temp_hdd - Température HDD (°C)" >&2 echo " fan_rpm - Vitesse ventilateur (RPM)" >&2 echo " uptime - Uptime système (secondes)" >&2 echo " disk_status - Statut disque (ok=1 / problem=0)" >&2 exit 1 fi BASE_URL="https://${FREEBOX_HOST}/api/v10" SESSION_FILE="/tmp/.freebox_session_${APP_ID}.cache" SESSION_MAX_AGE=1800 # 30 min en secondes # ------------------------------------------------------------------------------ # Options curl communes # --insecure : La Freebox utilise un certificat Free (CA non reconnu par défaut) # Si tu veux valider le certificat, télécharge le CA Free : # curl -O https://raw.githubusercontent.com/freebox/ssl/main/freebox_root_ca.pem # puis remplace --insecure par --cacert /etc/ssl/freebox_root_ca.pem # ------------------------------------------------------------------------------ CURL_OPTS=( --insecure # Certificat SSL Free (CA non standard) --silent # Pas de barre de progression --max-time 15 # Timeout global --connect-timeout 5 # Timeout connexion --retry 2 # 2 tentatives en cas d'échec réseau --retry-delay 1 ) # En mode debug, on affiche les erreurs curl sur stderr if [[ "$DEBUG" == "1" ]]; then CURL_OPTS+=(--show-error) fi # ------------------------------------------------------------------------------ # Fonctions utilitaires # ------------------------------------------------------------------------------ log_error() { echo "ERREUR: $*" >&2 } log_debug() { if [[ "$DEBUG" == "1" ]]; then echo "DEBUG: $*" >&2 fi } # Requête API Freebox (sans session, pour login) api_request_noauth() { local method="$1" local endpoint="$2" local data="${3:-}" local response local http_code local tmp_body tmp_body=$(mktemp) if [[ "$method" == "POST" && -n "$data" ]]; then http_code=$(curl "${CURL_OPTS[@]}" \ -o "$tmp_body" \ -w "%{http_code}" \ -X POST \ -H "Content-Type: application/json" \ -d "$data" \ "${BASE_URL}${endpoint}" 2>&1) else http_code=$(curl "${CURL_OPTS[@]}" \ -o "$tmp_body" \ -w "%{http_code}" \ "${BASE_URL}${endpoint}" 2>&1) fi local curl_exit=$? response=$(cat "$tmp_body") rm -f "$tmp_body" if [[ $curl_exit -ne 0 ]]; then log_error "curl a échoué (exit=$curl_exit) sur ${BASE_URL}${endpoint}" log_error "Conseil : vérifiez la connectivité et le certificat SSL (mode DEBUG=1 pour détails)" exit 2 fi if [[ "$http_code" != "200" && "$http_code" != "201" ]]; then log_error "Réponse HTTP inattendue : $http_code sur ${endpoint}" log_debug "Corps de la réponse : $response" exit 2 fi log_debug "Réponse ${endpoint} [HTTP $http_code] : $response" echo "$response" } # Requête API Freebox avec session token api_request() { local method="$1" local endpoint="$2" local session_token="$3" local data="${4:-}" local response local http_code local tmp_body tmp_body=$(mktemp) if [[ "$method" == "POST" && -n "$data" ]]; then http_code=$(curl "${CURL_OPTS[@]}" \ -o "$tmp_body" \ -w "%{http_code}" \ -X POST \ -H "Content-Type: application/json" \ -H "X-Fbx-App-Auth: ${session_token}" \ -d "$data" \ "${BASE_URL}${endpoint}" 2>&1) else http_code=$(curl "${CURL_OPTS[@]}" \ -o "$tmp_body" \ -w "%{http_code}" \ -H "X-Fbx-App-Auth: ${session_token}" \ "${BASE_URL}${endpoint}" 2>&1) fi local curl_exit=$? response=$(cat "$tmp_body") rm -f "$tmp_body" if [[ $curl_exit -ne 0 ]]; then log_error "curl a échoué (exit=$curl_exit) sur ${endpoint}" # Session peut être expirée : supprimer le cache pour forcer un nouveau login rm -f "$SESSION_FILE" exit 2 fi if [[ "$http_code" != "200" && "$http_code" != "201" ]]; then log_error "Réponse HTTP inattendue : $http_code sur ${endpoint}" log_debug "Corps de la réponse : $response" # Si 403, la session est invalide, on purge le cache if [[ "$http_code" == "403" || "$http_code" == "401" ]]; then log_error "Session invalide, suppression du cache. Relancez la commande." rm -f "$SESSION_FILE" fi exit 2 fi log_debug "Réponse ${endpoint} [HTTP $http_code] : $response" echo "$response" } # Génère le mot de passe de session via HMAC-SHA1 generate_password() { local token="$1" local challenge="$2" echo -n "$challenge" | openssl dgst -sha1 -hmac "$token" | awk '{print $NF}' } # ------------------------------------------------------------------------------ # Gestion de la session (cache pour éviter trop de logins) # ------------------------------------------------------------------------------ get_session_token() { # Vérification du cache if [[ -f "$SESSION_FILE" ]]; then local file_age file_age=$(( $(date +%s) - $(stat -c %Y "$SESSION_FILE" 2>/dev/null || echo 0) )) if [[ $file_age -lt $SESSION_MAX_AGE ]]; then cat "$SESSION_FILE" return 0 fi fi # Récupération du challenge local login_resp login_resp=$(api_request_noauth "GET" "/login") local success success=$(echo "$login_resp" | jq -r '.success // false') if [[ "$success" != "true" ]]; then log_error "Impossible de récupérer le challenge de login" exit 3 fi local challenge challenge=$(echo "$login_resp" | jq -r '.result.challenge') if [[ -z "$challenge" || "$challenge" == "null" ]]; then log_error "Challenge vide reçu de la Freebox" exit 3 fi # Génération du mot de passe local password password=$(generate_password "$APP_TOKEN" "$challenge") # Ouverture de session local session_payload session_payload=$(jq -nc \ --arg app_id "$APP_ID" \ --arg password "$password" \ '{"app_id": $app_id, "password": $password}') local session_resp session_resp=$(api_request_noauth "POST" "/login/session" "$session_payload") local session_success session_success=$(echo "$session_resp" | jq -r '.success // false') if [[ "$session_success" != "true" ]]; then local err_msg err_msg=$(echo "$session_resp" | jq -r '.msg // "Erreur inconnue"') log_error "Échec d'ouverture de session : $err_msg" exit 3 fi local session_token session_token=$(echo "$session_resp" | jq -r '.result.session_token') if [[ -z "$session_token" || "$session_token" == "null" ]]; then log_error "Session token vide" exit 3 fi # Mise en cache echo "$session_token" > "$SESSION_FILE" chmod 600 "$SESSION_FILE" echo "$session_token" } # ------------------------------------------------------------------------------ # Récupération des métriques système (CPU, RAM, Temp, Fan) # ------------------------------------------------------------------------------ get_system_metrics() { local session_token="$1" api_request "GET" "/system/" "$session_token" } # ------------------------------------------------------------------------------ # Récupération des métriques réseau (WAN stats) # ------------------------------------------------------------------------------ get_connection_metrics() { local session_token="$1" api_request "GET" "/connection/" "$session_token" } # ------------------------------------------------------------------------------ # Récupération des métriques stockage # ------------------------------------------------------------------------------ get_storage_metrics() { local session_token="$1" api_request "GET" "/storage/disk/" "$session_token" } # ------------------------------------------------------------------------------ # Calcul du pourcentage HDD # Parcourt tous les disques et agrège used/total # ------------------------------------------------------------------------------ compute_hdd_stats() { local storage_json="$1" local metric="$2" local total used percent total=$(echo "$storage_json" | jq '[.result[]? | .partitions[]? | .total_bytes // 0] | add // 0') used=$(echo "$storage_json" | jq '[.result[]? | .partitions[]? | .used_bytes // 0] | add // 0') if [[ "$total" -gt 0 ]]; then percent=$(echo "scale=2; ($used * 100) / $total" | bc) else percent=0 fi case "$metric" in hdd_percent) echo "$percent" ;; hdd_used) echo "$used" ;; hdd_total) echo "$total" ;; esac } # ------------------------------------------------------------------------------ # MAIN - Sélection de la métrique # ------------------------------------------------------------------------------ SESSION_TOKEN=$(get_session_token) case "$METRIC" in # ---- CPU ------------------------------------------------------------------ cpu_percent) ######## NON DISPONIBLE ATM SYS=$(get_system_metrics "$SESSION_TOKEN") echo "$SYS" | jq -r '.result.cpu_usage // 0' ;; # ---- RAM ------------------------------------------------------------------ ram_percent) ######## NON DISPONIBLE ATM SYS=$(get_system_metrics "$SESSION_TOKEN") TOTAL=$(echo "$SYS" | jq -r '.result.total_mem // 1') USED=$(echo "$SYS" | jq -r '.result.used_mem // 0') if [[ "$TOTAL" -gt 0 ]]; then echo "scale=2; ($USED * 100) / $TOTAL" | bc else echo "0" fi ;; ram_used) ######## NON DISPONIBLE ATM SYS=$(get_system_metrics "$SESSION_TOKEN") echo "$SYS" | jq -r '.result.used_mem // 0' ;; ram_total) ######## NON DISPONIBLE ATM SYS=$(get_system_metrics "$SESSION_TOKEN") echo "$SYS" | jq -r '.result.total_mem // 0' ;; # ---- HDD ------------------------------------------------------------------ hdd_percent|hdd_used|hdd_total) STORAGE=$(get_storage_metrics "$SESSION_TOKEN") compute_hdd_stats "$STORAGE" "$METRIC" ;; # ---- RÉSEAU --------------------------------------------------------------- net_rate_down) CONN=$(get_connection_metrics "$SESSION_TOKEN") echo "$CONN" | jq -r '.result.rate_down // 0' ;; net_rate_up) CONN=$(get_connection_metrics "$SESSION_TOKEN") echo "$CONN" | jq -r '.result.rate_up // 0' ;; net_bytes_down) CONN=$(get_connection_metrics "$SESSION_TOKEN") echo "$CONN" | jq -r '.result.bytes_down // 0' ;; net_bytes_up) CONN=$(get_connection_metrics "$SESSION_TOKEN") echo "$CONN" | jq -r '.result.bytes_up // 0' ;; net_bw_down) CONN=$(get_connection_metrics "$SESSION_TOKEN") echo "$CONN" | jq -r '.result.bandwidth_down // 0' ;; net_bw_up) CONN=$(get_connection_metrics "$SESSION_TOKEN") echo "$CONN" | jq -r '.result.bandwidth_up // 0' ;; # ---- TEMPÉRATURES --------------------------------------------------------- temp_cpum) # Note : Sur la Pop (r1), il n'y a pas de 'temp_cpum', c'est 'temp_t1' et 'temp_t2' # Je te mets 'temp_t1' par défaut, ou adapte selon ce que tu veux remonter SYS=$(get_system_metrics "$SESSION_TOKEN") echo "$SYS" | jq -r '.result.sensors[]? | select(.id=="temp_t1") | .value // 0' ;; temp_cpub) SYS=$(get_system_metrics "$SESSION_TOKEN") echo "$SYS" | jq -r '.result.sensors[]? | select(.id=="temp_cpub") | .value // 0' ;; temp_sw) # Note : Pas de 'temp_sw' (Switch) dans ton JSON Pop, on se rabat sur 'temp_t2' SYS=$(get_system_metrics "$SESSION_TOKEN") echo "$SYS" | jq -r '.result.sensors[]? | select(.id=="temp_t2") | .value // 0' ;; temp_hdd) # Ton JSON confirme 'customer_hdd_slots': 0 et internal_hdd_size: 0 (Pas de HDD) # On laisse la sécurité à 0 au cas où tu branches un disque USB SMART plus tard STORAGE=$(get_storage_metrics "$SESSION_TOKEN") echo "$STORAGE" | jq -r '[.result[]? | .smart_data.temperature // 0] | first // 0' ;; # ---- VENTILATEUR ---------------------------------------------------------- fan_rpm) SYS=$(get_system_metrics "$SESSION_TOKEN") # L'ID dans ton JSON est 'fan0_speed' au lieu de 'fan_rpm' echo "$SYS" | jq -r '.result.fans[]? | select(.id=="fan0_speed") | .value // 0' ;; # ---- UPTIME --------------------------------------------------------------- uptime) SYS=$(get_system_metrics "$SESSION_TOKEN") echo "$SYS" | jq -r '.result.uptime_val // 0' ;; # ---- STATUT DISQUE -------------------------------------------------------- disk_status) STORAGE=$(get_storage_metrics "$SESSION_TOKEN") # Retourne 1 si tous les disques sont ok, 0 sinon ALL_OK=$(echo "$STORAGE" | jq '[.result[]? | select(.state != "ok" and .state != "enabled")] | length == 0') if [[ "$ALL_OK" == "true" ]]; then echo "1" else echo "0" fi ;; *) log_error "Métrique inconnue : $METRIC" exit 1 ;; esac exit 0