From 5a4ad1f6ce7bb56fbdbe738ad0a662e0289a1b90 Mon Sep 17 00:00:00 2001 From: daniele Date: Sat, 29 Nov 2025 23:37:41 +0100 Subject: [PATCH] Riorganizzazione struttura repository: separazione servizi e script --- configs/keepalived_pi1.conf | 14 +++ configs/keepalived_pi2.conf | 14 +++ scripts/pi1-master/dhcp-alert.sh | 57 +++++++++++ scripts/pi1-master/sync-npm-full.sh | 57 +++++++++++ scripts/pi1-master/watchdog_pi2.sh | 47 ++++++++++ scripts/pi2-backup/daily_report.py | 125 +++++++++++++++++++++++++ scripts/pi2-backup/dhcp-watchdog.sh | 56 +++++++++++ bot.py => services/telegram-bot/bot.py | 0 8 files changed, 370 insertions(+) create mode 100644 configs/keepalived_pi1.conf create mode 100644 configs/keepalived_pi2.conf create mode 100644 scripts/pi1-master/dhcp-alert.sh create mode 100755 scripts/pi1-master/sync-npm-full.sh create mode 100755 scripts/pi1-master/watchdog_pi2.sh create mode 100644 scripts/pi2-backup/daily_report.py create mode 100755 scripts/pi2-backup/dhcp-watchdog.sh rename bot.py => services/telegram-bot/bot.py (100%) diff --git a/configs/keepalived_pi1.conf b/configs/keepalived_pi1.conf new file mode 100644 index 0000000..e8f156c --- /dev/null +++ b/configs/keepalived_pi1.conf @@ -0,0 +1,14 @@ +vrrp_instance VI_1 { + state MASTER + interface eth0 + virtual_router_id 51 + priority 101 # 101 = Priorità più alta (Master) + advert_int 1 + authentication { + auth_type PASS + auth_pass @Dedelove1 + } + virtual_ipaddress { + 192.168.128.85 # Il nostro VIP + } +} diff --git a/configs/keepalived_pi2.conf b/configs/keepalived_pi2.conf new file mode 100644 index 0000000..52ca13b --- /dev/null +++ b/configs/keepalived_pi2.conf @@ -0,0 +1,14 @@ +vrrp_instance VI_1 { + state BACKUP + interface eth0 + virtual_router_id 51 + priority 100 # 100 = Priorità più bassa (Backup) + advert_int 1 + authentication { + auth_type PASS + auth_pass @Dedelove1 + } + virtual_ipaddress { + 192.168.128.85 + } +} diff --git a/scripts/pi1-master/dhcp-alert.sh b/scripts/pi1-master/dhcp-alert.sh new file mode 100644 index 0000000..41f0994 --- /dev/null +++ b/scripts/pi1-master/dhcp-alert.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# --- CONFIGURAZIONE TELEGRAM --- +# 👇👇 INSERISCI I TUOI DATI VERI QUI 👇👇 +BOT_TOKEN="8155587974:AAF9OekvBpixtk8ZH6KoIc0L8edbhdXt7A4" +CHAT_ID="64463169" +# 👆👆 FINE MODIFICHE 👆👆 + +# --- FILE DI STATO --- +KNOWN_MACS_FILE="/etc/pihole/known_macs.txt" + +# Dati ricevuti da dnsmasq +ACTION=$1 +MAC=$2 +IP=$3 +HOSTNAME=$4 + +# Se l'hostname è vuoto, mettiamo un placeholder +if [ -z "$HOSTNAME" ]; then HOSTNAME="[Nessun Hostname]"; fi + +# Ci interessano solo le azioni di assegnazione IP ('add' o 'old') +if [[ "$ACTION" == "add" || "$ACTION" == "old" ]]; then + + # Assicura che il file dei MAC esista e sia scrivibile + if [ ! -f "$KNOWN_MACS_FILE" ]; then + touch "$KNOWN_MACS_FILE" && chmod 666 "$KNOWN_MACS_FILE" + fi + + # Cerca il MAC address nel file dei conosciuti + if ! grep -q "$MAC" "$KNOWN_MACS_FILE"; then + # === NUOVO DISPOSITIVO RILEVATO! === + + # --- NOVITÀ: Lookup Vendor --- + # Usiamo un'API esterna gratuita per trovare il produttore. + # Impostiamo un timeout stretto (3s connessione, 5s totale) per non bloccare il DHCP se l'API è lenta. + VENDOR=$(curl -s --connect-timeout 3 --max-time 5 "https://api.macvendors.com/$MAC") + + # Se l'API fallisce, è vuota o restituisce errore, mettiamo un default. + # (Nota: alcune API restituiscono "Not Found" come testo puro se non trovano il vendor) + if [ -z "$VENDOR" ] || [[ "${VENDOR,,}" == *"not found"* ]] || [[ "${VENDOR,,}" == *"error"* ]]; then + VENDOR="[Sconosciuto / API Timeout]" + fi + + # 1. Invia notifica Telegram (silenziosa) + # Aggiunta la riga 🏭 **Vendor:** + MSG="🚨 **NUOVO DISPOSITIVO IN RETE!**%0A%0A💻 **Host:** $HOSTNAME%0A📟 **MAC:** \`$MAC\`%0A🏭 **Vendor:** $VENDOR%0A🌐 **IP:** $IP" + curl -s -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" -d chat_id="$CHAT_ID" -d text="$MSG" -d parse_mode="Markdown" > /dev/null 2>&1 + + # 2. Aggiungi il MAC alla lista dei conosciuti + # Salviamo anche il vendor nel file di testo per comodità futura + { + flock -x 200 + echo "$MAC # Aggiunto il $(date) - Vendor: $VENDOR - Host: $HOSTNAME" >> "$KNOWN_MACS_FILE" + } 200>"$KNOWN_MACS_FILE.lock" + + fi +fi diff --git a/scripts/pi1-master/sync-npm-full.sh b/scripts/pi1-master/sync-npm-full.sh new file mode 100755 index 0000000..df93e7a --- /dev/null +++ b/scripts/pi1-master/sync-npm-full.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# --- CONFIGURAZIONE --- +REMOTE_IP="192.168.128.81" +REMOTE_USER="daniely" +# Cartella che contiene 'data' e 'letsencrypt' +SOURCE_BASE="/home/daniely/docker/npm" +TEMP_FILE="/tmp/npm_full_clone.tar.gz" + +echo "[$(date)] Avvio clonazione totale NPM (Master -> Backup)..." + +# 1. Crea un archivio compresso di TUTTO (Database SQL + Certificati) +# Usiamo sudo per leggere i file di root senza permessi negati +# Escludiamo i log per risparmiare spazio +sudo tar --exclude='*.log' -czf "$TEMP_FILE" -C "$SOURCE_BASE" data letsencrypt + +# 2. Assegna l'archivio all'utente corrente (per poterlo spedire via SCP) +sudo chown $USER:$USER "$TEMP_FILE" + +# 3. Spedisce l'archivio al Pi-2 +echo "Invio archivio al Pi-2 ($REMOTE_IP)..." +scp -o ConnectTimeout=10 "$TEMP_FILE" "$REMOTE_USER@$REMOTE_IP:/tmp/" + +if [ $? -eq 0 ]; then + echo "Trasferimento riuscito. Applicazione sul Backup..." + + # 4. Comanda al Pi-2 di: + # a) Arrestare NPM (per sbloccare il database) + # b) Scompattare sovrascrivendo tutto + # c) Ripristinare i permessi di root + # d) Riavviare NPM + ssh "$REMOTE_USER@$REMOTE_IP" " + echo ' ...Stop NPM...'; + docker stop npm; + + echo ' ...Estrazione dati...'; + # Pulisce la cartella di destinazione prima di estrarre per evitare residui + sudo rm -rf /home/daniely/docker/npm/data/* + sudo rm -rf /home/daniely/docker/npm/letsencrypt/* + sudo tar -xzf /tmp/npm_full_clone.tar.gz -C /home/daniely/docker/npm/; + + echo ' ...Fix Permessi...'; + sudo chown -R root:root /home/daniely/docker/npm/; + + echo ' ...Start NPM...'; + docker start npm; + + # Pulizia remota + rm /tmp/npm_full_clone.tar.gz + " + echo "[$(date)] Sincronizzazione Completata con Successo." +else + echo "[$(date)] ERRORE CRITICO: Trasferimento fallito." +fi + +# 5. Pulizia locale +rm "$TEMP_FILE" diff --git a/scripts/pi1-master/watchdog_pi2.sh b/scripts/pi1-master/watchdog_pi2.sh new file mode 100755 index 0000000..64e3cd0 --- /dev/null +++ b/scripts/pi1-master/watchdog_pi2.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# ================================================ +# SENTINELLA DI BACKUP (Gira su Pi-1 Master) +# Controlla solo che il Pi-2 sia vivo. +# ================================================ + +# --- CONFIGURAZIONE --- +# 👇👇 INSERISCI I TUOI DATI VERI QUI 👇👇 +BOT_TOKEN="8155587974:AAF9OekvBpixtk8ZH6KoIc0L8edbhdXt7A4" +CHAT_ID="64463169" +# 👆👆 FINE MODIFICHE 👆👆 + +# Il bersaglio da controllare (Pi-2 Backup) +TARGET_IP="192.168.128.81" +TARGET_NAME="🍓 Pi-2 (Backup & Monitor)" + +# File di stato specifico per questo controllo +STATE_FILE="/tmp/pi2_watchdog.state" + +# Funzione per inviare messaggi Telegram con timeout breve +send_telegram() { + MSG="$1" + curl -s --max-time 5 -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" -d chat_id="$CHAT_ID" -d text="$MSG" -d parse_mode="Markdown" > /dev/null 2>&1 +} + +# Inizializza stato se non esiste +if [ ! -f "$STATE_FILE" ]; then echo "UP" > "$STATE_FILE"; fi +LAST_STATE=$(cat "$STATE_FILE") + +# --- IL PING --- +# 3 tentativi, 2 secondi di timeout l'uno. +# Basta che uno risponda per considerare il bersaglio UP. +if ping -c 3 -W 2 "$TARGET_IP" > /dev/null 2>&1; then + # === È ONLINE === + if [ "$LAST_STATE" == "DOWN" ]; then + send_telegram "✅ **RISOLTO: $TARGET_NAME è tornato ONLINE!**%0AIl monitoraggio di rete è di nuovo attivo." + echo "UP" > "$STATE_FILE" + fi +else + # === È OFFLINE === + if [ "$LAST_STATE" == "UP" ]; then + # Usiamo un'icona diversa per distinguerlo subito + send_telegram "🛡️ **ALLARME SICUREZZA: $TARGET_NAME è OFFLINE!**%0A%0AAttenzione: Il sistema di monitoraggio principale (Super Watchdog) non sta funzionando." + echo "DOWN" > "$STATE_FILE" + fi +fi diff --git a/scripts/pi2-backup/daily_report.py b/scripts/pi2-backup/daily_report.py new file mode 100644 index 0000000..c427578 --- /dev/null +++ b/scripts/pi2-backup/daily_report.py @@ -0,0 +1,125 @@ +import sqlite3 +import os +import datetime +import urllib.request +import urllib.parse +import json + +# --- CONFIGURAZIONE --- +BOT_TOKEN = os.environ.get('BOT_TOKEN') +CHAT_ID = os.environ.get('ALLOWED_USER_ID') +DB_PATH = "/data/speedtest.sqlite" + +# SOGLIE DI ALLARME (Mbps) +WARN_DOWN = 400 +WARN_UP = 100 + +def send_telegram_message(message): + if not message: return + + url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage" + payload = { + "chat_id": CHAT_ID, + "text": message, + "parse_mode": "Markdown" + } + + try: + data = urllib.parse.urlencode(payload).encode('utf-8') + req = urllib.request.Request(url, data=data) + with urllib.request.urlopen(req) as response: + if response.status == 200: + print("✅ Report inviato.") + except Exception as e: + print(f"❌ Errore invio Telegram: {e}") + +def generate_report(): + if not os.path.exists(DB_PATH): + print(f"❌ Database non trovato: {DB_PATH}") + return None + + try: + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + # Calcola l'orario UTC di 24h fa (perché il DB è in UTC) + yesterday = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(hours=24) + + query = "SELECT download, upload, ping, created_at FROM results WHERE created_at > ? AND status = 'completed' ORDER BY created_at ASC" + cursor.execute(query, (yesterday,)) + rows = cursor.fetchall() + except Exception as e: + print(f"❌ Errore DB: {str(e)}") + return None + finally: + if 'conn' in locals(): conn.close() + + if not rows: + print("ℹ️ Nessun test trovato.") + return None + + # Variabili per statistiche + total_down = 0 + total_up = 0 + count = 0 + issues = 0 + + # Intestazione Messaggio + # Usiamo l'ora locale del sistema per l'intestazione + now_local = datetime.datetime.now() + msg = f"📊 **REPORT VELOCITÀ 24H**\n📅 {now_local.strftime('%d/%m/%Y')}\n\n" + + msg += "```text\n" + msg += "ORA | DOWN | UP \n" + msg += "------+------+-----\n" + + for row in rows: + # Conversione Bit -> Mbps + d_val = (int(row[0]) * 8) / 1000000 + u_val = (int(row[1]) * 8) / 1000000 + + total_down += d_val + total_up += u_val + count += 1 + + marker = "" + if d_val < WARN_DOWN or u_val < WARN_UP: + issues += 1 + marker = "!" + + # Parsing Data e CONVERSIONE TIMEZONE + try: + d_str = row[3].split(".")[0].replace("T", " ") + # 1. Creiamo l'oggetto datetime e gli diciamo "Tu sei UTC" + dt_utc = datetime.datetime.strptime(d_str, '%Y-%m-%d %H:%M:%S').replace(tzinfo=datetime.timezone.utc) + + # 2. Convertiamo nell'orario locale del sistema (definito da TZ nel docker-compose) + dt_local = dt_utc.astimezone() + + time_str = dt_local.strftime('%H:%M') + except: + time_str = "--:--" + + # Formattazione tabella + row_str = f"{time_str} | {d_val:>4.0f} | {u_val:>3.0f} {marker}" + msg += f"{row_str}\n" + + msg += "```\n" + + if count > 0: + avg_d = total_down / count + avg_u = total_up / count + + icon_d = "✅" if avg_d >= WARN_DOWN else "⚠️" + icon_u = "✅" if avg_u >= WARN_UP else "⚠️" + + msg += f"📈 **MEDIA:**\n⬇️ {icon_d} `{avg_d:.0f}` Mbps\n⬆️ {icon_u} `{avg_u:.0f}` Mbps" + + if issues > 0: + msg += f"\n\n⚠️ **{issues}** test sotto soglia (!)" + + return msg + +if __name__ == "__main__": + report = generate_report() + if report: + send_telegram_message(report) diff --git a/scripts/pi2-backup/dhcp-watchdog.sh b/scripts/pi2-backup/dhcp-watchdog.sh new file mode 100755 index 0000000..afbf37a --- /dev/null +++ b/scripts/pi2-backup/dhcp-watchdog.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# --- CONFIGURAZIONE --- +MASTER_IP="192.168.128.80" +CONFIG_FILE="/etc/pihole/pihole.toml" +LOG_FILE="/var/log/dhcp-watchdog.log" + +# Funzione per il logging +log_message() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" + echo "$1" +} + +# Funzione ROBUSTA v2 per leggere lo stato +# Usa i campi ($1, $2) per ignorare gli spazi di indentazione +is_dhcp_active() { + val=$(awk '/^\[dhcp\]/{flag=1} flag && $1=="active" && $2=="=" {print $3; exit}' "$CONFIG_FILE") + echo "$val" +} + +# Funzione per ATTIVARE (sed robusto) +enable_dhcp() { + # Cerca nel blocco [dhcp] e sostituisce mantenendo l'indentazione (\1) + sudo sed -i '/^\[dhcp\]/,/^\[/ s/^\(\s*\)active\s*=\s*false/\1active = true/' "$CONFIG_FILE" + sudo systemctl restart pihole-FTL +} + +# Funzione per DISATTIVARE (sed robusto) +disable_dhcp() { + sudo sed -i '/^\[dhcp\]/,/^\[/ s/^\(\s*\)active\s*=\s*true/\1active = false/' "$CONFIG_FILE" + sudo systemctl restart pihole-FTL +} + +# --- LOGICA DI CONTROLLO --- + +if ping -c 3 -W 5 "$MASTER_IP" &> /dev/null; then + # MASTER VIVO + CURRENT_STATUS=$(is_dhcp_active) + if [ "$CURRENT_STATUS" == "true" ]; then + log_message "RECOVERY: Master tornato online. Disattivo DHCP Backup." + disable_dhcp + log_message "STATO: DHCP disattivato." + fi +else + # MASTER FORSE MORTO + sleep 30 + if ! ping -c 3 -W 5 "$MASTER_IP" &> /dev/null; then + # CONFERMATO MORTO + CURRENT_STATUS=$(is_dhcp_active) + if [ "$CURRENT_STATUS" == "false" ]; then + log_message "EMERGENZA: Master irraggiungibile. Attivo DHCP Backup." + enable_dhcp + log_message "STATO: DHCP ATTIVO." + fi + fi +fi diff --git a/bot.py b/services/telegram-bot/bot.py similarity index 100% rename from bot.py rename to services/telegram-bot/bot.py