#!/usr/bin/env python3 import sys import os import time import subprocess import json import syslog import socket from datetime import datetime, timedelta CONFIG_PATH = "/etc/ssh/ssh_alerts_config.json" STATE_FILE = "/tmp/system_resources_state.json" ALERT_THRESHOLD = 85.0 COOLDOWN_SECONDS = 3600 # Remind every 1 hour if resource stays above threshold def log_to_syslog(message, is_error=False): level = syslog.LOG_ERR if is_error else syslog.LOG_INFO syslog.openlog(ident="system_resources_monitor", logoption=syslog.LOG_PID, facility=syslog.LOG_DAEMON) syslog.syslog(level, message) syslog.closelog() def get_vps_identity(): # Attempt to auto-detect WireGuard IP try: output = subprocess.check_output( ['ip', '-o', '-4', 'addr', 'show', 'dev', 'wg0'], stderr=subprocess.DEVNULL ).decode('utf-8') for line in output.split('\n'): parts = line.split() if len(parts) >= 4: return f"VPS 3 ({parts[3].split('/')[0]})" except Exception: pass # Fallback to general IP/hostname try: return f"VPS ({socket.gethostname()})" except Exception: return "VPS (10.99.0.3)" def get_cpu_usage(): def read_cpu_times(): with open('/proc/stat', 'r') as f: first_line = f.readline() parts = first_line.split() times = [float(x) for x in parts[1:9]] idle = times[3] + times[4] # idle + iowait total = sum(times) return idle, total try: idle1, total1 = read_cpu_times() time.sleep(1.0) idle2, total2 = read_cpu_times() idle_delta = idle2 - idle1 total_delta = total2 - total1 if total_delta > 0: return (1.0 - idle_delta / total_delta) * 100.0 except Exception as e: log_to_syslog(f"Erro ao obter uso de CPU: {e}", is_error=True) return 0.0 def get_memory_usage(): try: mem_info = {} with open('/proc/meminfo', 'r') as f: for line in f: parts = line.split(':') if len(parts) == 2: key = parts[0].strip() val = parts[1].strip().split()[0] mem_info[key] = int(val) total = mem_info.get('MemTotal', 0) available = mem_info.get('MemAvailable', 0) if total > 0: used = total - available return (used / total) * 100.0 except Exception as e: log_to_syslog(f"Erro ao obter uso de memoria: {e}", is_error=True) return 0.0 def get_disk_usage(): try: output = subprocess.check_output(['df', '-Pl', '/']).decode('utf-8') lines = output.strip().split('\n') if len(lines) >= 2: parts = lines[-1].split() if len(parts) >= 5: pct_str = parts[4] if pct_str.endswith('%'): return float(pct_str[:-1]) except Exception as e: log_to_syslog(f"Erro ao obter uso de disco: {e}", is_error=True) return 0.0 def get_top_processes(): try: ps_output = subprocess.check_output( ['ps', '-eo', 'pid,%mem,%cpu,cmd', '--sort=-%mem'], stderr=subprocess.DEVNULL ).decode('utf-8', errors='ignore').strip().split('\n') return ps_output[:6] # Header + top 5 processes except Exception as e: log_to_syslog(f"Erro ao obter processos ofensores: {e}", is_error=True) return [] def load_state(): if os.path.exists(STATE_FILE): try: with open(STATE_FILE, "r") as f: return json.load(f) except Exception: pass return {} def save_state(state): try: with open(STATE_FILE, "w") as f: json.dump(state, f) except Exception as e: log_to_syslog(f"Erro ao salvar estado de alertas: {e}", is_error=True) def send_telegram_alert(token, chat_id, vps_identity, alerts_active, top_processes): try: url = f"https://api.telegram.org/bot{token}/sendMessage" alerts_text = "" for resource, val in alerts_active.items(): alerts_text += f"• *{resource}:* `{val:.1f}%` (Limite: {ALERT_THRESHOLD:.1f}%)\n" proc_text = "" if top_processes: proc_text = "\n🔥 *Top 5 Processos Consumindo mais RAM:*\n```\n" proc_text += "\n".join(top_processes) proc_text += "\n```" text = ( f"🚨 *ALERTA DE USO DE RECURSOS CRÍTICOS*\n\n" f"• *Servidor:* `{vps_identity}`\n" f"{alerts_text}" f"{proc_text}\n" f"📅 _Registrado automaticamente pelo monitor do sistema._" ) payload = json.dumps({ "chat_id": chat_id, "text": text, "parse_mode": "Markdown" }).encode("utf-8") req = urllib.request.Request( url, data=payload, headers={ 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0' } ) import urllib.request with urllib.request.urlopen(req, timeout=5) as r: res = json.loads(r.read().decode()) return res.get("ok", False) except Exception as e: log_to_syslog(f"Erro ao enviar alerta para o Telegram: {e}", is_error=True) return False def send_telegram_resolved(token, chat_id, vps_identity, resolved_resources): try: url = f"https://api.telegram.org/bot{token}/sendMessage" resolved_text = "" for resource, val in resolved_resources.items(): resolved_text += f"• *{resource}:* `{val:.1f}%` (Normalizado)\n" text = ( f"✅ *RECURSOS NORMALIZADOS*\n\n" f"• *Servidor:* `{vps_identity}`\n" f"{resolved_text}\n" f"📅 _O servidor voltou a operar dentro dos limites de segurança._" ) payload = json.dumps({ "chat_id": chat_id, "text": text, "parse_mode": "Markdown" }).encode("utf-8") req = urllib.request.Request( url, data=payload, headers={ 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0' } ) import urllib.request with urllib.request.urlopen(req, timeout=5) as r: res = json.loads(r.read().decode()) return res.get("ok", False) except Exception as e: log_to_syslog(f"Erro ao enviar resolucao para o Telegram: {e}", is_error=True) return False def create_calendar_event(vps_identity, alerts_active, top_processes, calendar_id): try: from google.oauth2 import service_account from googleapiclient.discovery import build creds = service_account.Credentials.from_service_account_file( '/etc/ssh/gcal_key.json', scopes=['https://www.googleapis.com/auth/calendar'] ) service = build('calendar', 'v3', credentials=creds) now = datetime.utcnow() end_time = now + timedelta(minutes=30) start_iso = now.isoformat() + 'Z' end_iso = end_time.isoformat() + 'Z' resources_str = ", ".join(alerts_active.keys()) summary = f"{vps_identity}: 🚨 Estresse de Sistema - {resources_str}" description = ( f"=== 🖥️ ALERTA DE RECURSOS DO SISTEMA ===\n\n" f"• Servidor: {vps_identity}\n" f"• Recursos acima do limite (>={ALERT_THRESHOLD}%):\n" ) for res, val in alerts_active.items(): description += f" - {res}: {val:.1f}%\n" if top_processes: description += f"\n--- TOP PROCESSOS POR MEMÓRIA ---\n" description += "\n".join(top_processes) description += f"\n\nRegistrado automaticamente em {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}." event_body = { 'summary': summary, 'description': description, 'start': {'dateTime': start_iso, 'timeZone': 'UTC'}, 'end': {'dateTime': end_iso, 'timeZone': 'UTC'}, 'colorId': "11", # Vermelho Bold 'reminders': { 'useDefault': False, 'overrides': [{'method': 'popup', 'minutes': 0}] } } event = service.events().insert( calendarId=calendar_id, body=event_body ).execute() return True except Exception as e: log_to_syslog(f"Erro ao criar evento de estresse no Google Calendar: {e}", is_error=True) return False def main(): # Load config if not os.path.exists(CONFIG_PATH): log_to_syslog(f"Arquivo de configuracao nao encontrado: {CONFIG_PATH}", is_error=True) return try: with open(CONFIG_PATH, "r") as f: config = json.load(f) except Exception as e: log_to_syslog(f"Erro ao carregar configuracoes: {e}", is_error=True) return telegram_token = config.get("telegram_token") telegram_chat_id = config.get("telegram_chat_id") calendar_id = config.get("calendar_id") if not telegram_token or not telegram_chat_id: log_to_syslog("Telegram credentials missing in config.", is_error=True) return # Check metrics cpu = get_cpu_usage() mem = get_memory_usage() disk = get_disk_usage() metrics = { "CPU": cpu, "Memoria": mem, "Disco (/)": disk } vps_identity = get_vps_identity() state = load_state() now = time.time() alerts_to_send = {} resolved_to_send = {} state_changed = False for resource, val in metrics.items(): is_above = val >= ALERT_THRESHOLD prev_alert_time = state.get(resource, 0) if is_above: # Check if we should notify: either first time or after cooldown if prev_alert_time == 0 or (now - prev_alert_time >= COOLDOWN_SECONDS): alerts_to_send[resource] = val state[resource] = now state_changed = True else: # If it was previously in alert state, send resolution message if prev_alert_time > 0: resolved_to_send[resource] = val state[resource] = 0 state_changed = True if state_changed: save_state(state) # Process resolutions if resolved_to_send: log_to_syslog(f"Métricas normalizadas para: {', '.join(resolved_to_send.keys())}") send_telegram_resolved(telegram_token, telegram_chat_id, vps_identity, resolved_to_send) # Process alerts if alerts_to_send: log_to_syslog(f"ALERTA: Métricas acima de {ALERT_THRESHOLD}%: " + ", ".join([f"{k} ({v:.1f}%)" for k, v in alerts_to_send.items()])) top_processes = get_top_processes() # Send to Telegram send_telegram_alert(telegram_token, telegram_chat_id, vps_identity, alerts_to_send, top_processes) # Log to Calendar if calendar_id: create_calendar_event(vps_identity, alerts_to_send, top_processes, calendar_id) if __name__ == "__main__": main()