commit c669ef03b6ba06d2b51568a50d8c22a219650c03 Author: daniele Date: Sat Nov 29 23:22:55 2025 +0100 Primo salvataggio script Loogle diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..3d0e0c1 --- /dev/null +++ b/bot.py @@ -0,0 +1,221 @@ +import logging +import subprocess +import os +from functools import wraps +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update +from telegram.ext import ( + Application, + CommandHandler, + CallbackQueryHandler, + ContextTypes, +) + +# --- CONFIGURAZIONE --- +BOT_TOKEN = os.environ.get('BOT_TOKEN') +OWNER_ID = int(os.environ.get('ALLOWED_USER_ID')) +SSH_USER = "daniely" +NAS_USER = "daniely" +MASTER_IP = "192.168.128.80" + +# --- LISTE DISPOSITIVI --- +# Core: Dispositivi intelligenti (SSH, CPU, RAM) +CORE_DEVICES = [ + {"name": "πŸ“ Pi-1 (Master)", "ip": MASTER_IP, "type": "pi", "user": SSH_USER}, + {"name": "πŸ“ Pi-2 (Backup)", "ip": "127.0.0.1", "type": "local", "user": ""}, + {"name": "πŸ—„οΈ NAS 920+", "ip": "192.168.128.100", "type": "nas", "user": NAS_USER}, + {"name": "πŸ—„οΈ NAS 214", "ip": "192.168.128.90", "type": "nas", "user": NAS_USER} +] + +# Infra: Solo Ping e Riavvio (Switch, WiFi) +INFRA_DEVICES = [ + {"name": "πŸ“‘ Router", "ip": "192.168.128.1"}, + {"name": "πŸ“Ά WiFi Sala", "ip": "192.168.128.101"}, + {"name": "πŸ“Ά WiFi Luca", "ip": "192.168.128.102"}, + {"name": "πŸ“Ά WiFi Taverna", "ip": "192.168.128.103"}, + {"name": "πŸ“Ά WiFi Dado", "ip": "192.168.128.104"}, + {"name": "πŸ”Œ Sw Sala", "ip": "192.168.128.105"}, + {"name": "πŸ”Œ Sw Main", "ip": "192.168.128.106"}, + {"name": "πŸ”Œ Sw Lav", "ip": "192.168.128.107"}, + {"name": "πŸ”Œ Sw Tav", "ip": "192.168.128.108"} +] + +logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO) +logger = logging.getLogger(__name__) + +# --- FUNZIONI --- + +def run_cmd(command, ip=None, user=None): + try: + if ip == "127.0.0.1" or ip is None: + return subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip() + else: + safe_cmd = command.replace("'", "'\\''") + full_cmd = f"ssh -o LogLevel=ERROR -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=4 {user}@{ip} '{safe_cmd}'" + return subprocess.check_output(full_cmd, shell=True, stderr=subprocess.STDOUT).decode('utf-8').strip() + except Exception: return "Err" + +def get_ping_icon(ip): + try: + # Ping ultra-rapido (Timeout 1s) + subprocess.check_call(["ping", "-c", "1", "-W", "1", ip], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + return "βœ…" + except: return "πŸ”΄" + +def get_device_stats(device): + ip, user, dtype = device['ip'], device['user'], device['type'] + uptime_raw = run_cmd("uptime -p 2>/dev/null || uptime", ip, user) + if not uptime_raw or "Err" in uptime_raw: return "πŸ”΄ **OFFLINE**" + uptime = uptime_raw.replace("up ", "").split(", load")[0].split(", ")[0] + + temp = "N/A" + if dtype in ["pi", "local"]: + t = run_cmd("cat /sys/class/thermal/thermal_zone0/temp", ip, user) + if t.isdigit(): temp = f"{int(t)/1000:.1f}Β°C" + elif dtype == "nas": + t = run_cmd("cat /sys/class/hwmon/hwmon0/temp1_input 2>/dev/null || cat /sys/class/thermal/thermal_zone0/temp", ip, user) + if t.isdigit(): + v = int(t); temp = f"{v/1000:.1f}Β°C" if v > 1000 else f"{v}Β°C" + + if dtype == "nas": ram_cmd = "free | grep Mem | awk '{printf \"%.0f%%\", $3*100/$2}'" + else: ram_cmd = "free -m | awk 'NR==2{if ($2>0) printf \"%.0f%%\", $3*100/$2; else print \"0%\"}'" + disk_path = "/" if dtype != "nas" else "/volume1" + disk_cmd = f"df -h {disk_path} | awk 'NR==2{{print $5}}'" + + return f"βœ… **ONLINE**\n⏱️ Up: {uptime}\n🌑️ Temp: {temp} | 🧠 RAM: {run_cmd(ram_cmd, ip, user)} | πŸ’Ύ Disk: {run_cmd(disk_cmd, ip, user)}" + +def read_log_file(filepath, lines=15): + if not os.path.exists(filepath): return "⚠️ File non trovato." + try: return subprocess.check_output(['tail', '-n', str(lines), filepath], stderr=subprocess.STDOUT).decode('utf-8') + except Exception as e: return f"Errore: {str(e)}" + +def run_speedtest(): + try: return subprocess.check_output("speedtest-cli --simple", shell=True, timeout=50).decode('utf-8') + except: return "Errore Speedtest" + +# --- GESTIONE MENU --- +def restricted(func): + @wraps(func) + async def wrapped(update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs): + if update.effective_user.id != OWNER_ID: return + return await func(update, context, *args, **kwargs) + return wrapped + +@restricted +async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + # MENU PRINCIPALE + keyboard = [ + [InlineKeyboardButton("πŸ–₯️ Core Server", callback_data="menu_core"), InlineKeyboardButton("πŸ” Scan LAN", callback_data="menu_lan")], + [InlineKeyboardButton("πŸ›‘οΈ Pi-hole", callback_data="menu_pihole"), InlineKeyboardButton("🌐 Rete", callback_data="menu_net")], + [InlineKeyboardButton("πŸ“œ Logs", callback_data="menu_logs")] + ] + text = "πŸŽ› **Loogle Control Center**\nSeleziona un pannello:" + + if update.message: + await update.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown") + else: + # Se chiamato da un pulsante "Indietro", modifica il messaggio esistente + await update.callback_query.edit_message_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown") + +@restricted +async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + query = update.callback_query + await query.answer() + data = query.data + + if data == "main_menu": await start(update, context) + + # --- MENU CORE --- + elif data == "menu_core": + keyboard = [] + for i, dev in enumerate(CORE_DEVICES): keyboard.append([InlineKeyboardButton(dev['name'], callback_data=f"stat_{i}")]) + keyboard.append([InlineKeyboardButton("πŸ“Š Report Completo", callback_data="stat_all")]) + keyboard.append([InlineKeyboardButton("⬅️ Indietro", callback_data="main_menu")]) + await query.edit_message_text("πŸ–₯️ **Core Servers**\nDettagli CPU/RAM/Temp:", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown") + + elif data == "stat_all": + await query.edit_message_text("⏳ **Analisi Core Servers...**", parse_mode="Markdown") + report = "πŸ“Š **REPORT CORE**\n" + for dev in CORE_DEVICES: report += f"\nπŸ”Ή **{dev['name']}**\n{get_device_stats(dev)}\n" + await query.edit_message_text(report, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Indietro", callback_data="menu_core")]]), parse_mode="Markdown") + + elif data.startswith("stat_"): + dev = CORE_DEVICES[int(data.split("_")[1])] + await query.edit_message_text(f"⏳ Controllo {dev['name']}...", parse_mode="Markdown") + await query.edit_message_text(f"πŸ”Ή **{dev['name']}**\n\n{get_device_stats(dev)}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Indietro", callback_data="menu_core")]]), parse_mode="Markdown") + + # --- MENU LAN (DIAGNOSTICA) --- + elif data == "menu_lan": + await query.edit_message_text("⏳ **Ping test in corso...**", parse_mode="Markdown") + + report = "πŸ” **DIAGNOSTICA LAN**\n\n" + + # Genera tabella compatta + for dev in INFRA_DEVICES: + status = get_ping_icon(dev['ip']) + # Aggiunge riga: Icona Nome (IP) + report += f"{status} **{dev['name']}**\n`{dev['ip']}`\n" + + keyboard = [ + [InlineKeyboardButton("⚑ Menu Riavvio Dispositivi", callback_data="menu_reboot")], + [InlineKeyboardButton("πŸ”„ Aggiorna", callback_data="menu_lan")], + [InlineKeyboardButton("⬅️ Indietro", callback_data="main_menu")] + ] + await query.edit_message_text(report, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown") + + # --- MENU RIAVVIO --- + elif data == "menu_reboot": + keyboard = [] + for i, dev in enumerate(INFRA_DEVICES): + if "Router" not in dev['name']: # Protezione router + keyboard.append([InlineKeyboardButton(f"⚑ {dev['name']}", callback_data=f"reboot_{i}")]) + keyboard.append([InlineKeyboardButton("⬅️ Indietro", callback_data="menu_lan")]) + await query.edit_message_text("⚠️ **RIAVVIO REMOTO**\nRichiede SSH configurato sul dispositivo target (admin).", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown") + + elif data.startswith("reboot_"): + dev = INFRA_DEVICES[int(data.split("_")[1])] + # Tenta riavvio con utente 'admin' (standard per Zyxel/AP) + res = run_cmd("reboot", dev['ip'], "admin") + + await query.edit_message_text(f"⚑ Comando inviato a {dev['name']}...\n\nRisposta:\n`{res}`", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Indietro", callback_data="menu_reboot")]]), parse_mode="Markdown") + + # --- ALTRI MENU (Pi-hole, Net, Logs) --- + elif data == "menu_pihole": + status_raw = run_cmd("sudo pihole status", MASTER_IP, SSH_USER) + icon = "βœ…" if "Enabled" in status_raw or "enabled" in status_raw else "πŸ”΄" + text = f"πŸ›‘οΈ **Pi-hole Master**\nStato: {icon}\n\n`{status_raw}`" + keyboard = [[InlineKeyboardButton("⏸️ 5m", callback_data="ph_disable_300"), InlineKeyboardButton("⏸️ 30m", callback_data="ph_disable_1800")], [InlineKeyboardButton("▢️ Attiva", callback_data="ph_enable"), InlineKeyboardButton("πŸ”„ Restart", callback_data="ph_restart")], [InlineKeyboardButton("⬅️ Indietro", callback_data="main_menu")]] + await query.edit_message_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown") + + elif data.startswith("ph_"): + if "disable" in data: run_cmd(f"sudo pihole disable {data.split('_')[2]}s", MASTER_IP, SSH_USER) + elif "enable" in data: run_cmd("sudo pihole enable", MASTER_IP, SSH_USER) + elif "restart" in data: run_cmd("sudo systemctl restart pihole-FTL", MASTER_IP, SSH_USER) + await query.edit_message_text("βœ… Comando inviato.", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Indietro", callback_data="menu_pihole")]]), parse_mode="Markdown") + + elif data == "menu_net": + ip = run_cmd("curl -s ifconfig.me") + keyboard = [[InlineKeyboardButton("πŸš€ Speedtest", callback_data="net_speedtest")], [InlineKeyboardButton("⬅️ Indietro", callback_data="main_menu")]] + await query.edit_message_text(f"🌐 **Rete**\n🌍 IP: `{ip}`", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown") + + elif data == "net_speedtest": + await query.edit_message_text("πŸš€ **Speedtest...**", parse_mode="Markdown") + res = run_speedtest() + await query.edit_message_text(f"πŸš€ **Risultato:**\n\n`{res}`", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Indietro", callback_data="menu_net")]]), parse_mode="Markdown") + + elif data == "menu_logs": + keyboard = [[InlineKeyboardButton("🐢 Watchdog", callback_data="log_wd"), InlineKeyboardButton("πŸ’Ύ Backup", callback_data="log_bk")], [InlineKeyboardButton("πŸ”„ NPM Sync", callback_data="log_npm"), InlineKeyboardButton("⬅️ Indietro", callback_data="main_menu")]] + await query.edit_message_text("πŸ“œ **Logs**", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown") + + elif data.startswith("log_"): + paths = {"log_wd": "/logs/dhcp-watchdog.log", "log_bk": "/logs/raspiBackup.log", "log_npm": "/logs/sync-npm.log"} + log_c = read_log_file(paths[data]) + await query.edit_message_text(f"πŸ“œ **Log:**\n\n`{log_c}`", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Indietro", callback_data="menu_logs")]]), parse_mode="Markdown") + +def main(): + application = Application.builder().token(BOT_TOKEN).build() + application.add_handler(CommandHandler("start", start)) + application.add_handler(CallbackQueryHandler(button_handler)) + application.run_polling() + +if __name__ == "__main__": + main() diff --git a/daily_report.py b/daily_report.py new file mode 100644 index 0000000..c427578 --- /dev/null +++ b/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)