diff --git a/scripts/service_watchdog.py b/scripts/service_watchdog.py new file mode 100644 index 0000000..847cc87 --- /dev/null +++ b/scripts/service_watchdog.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +""" +Service Health Watchdog — Monitors systemd services and sends email alerts +when any service goes DOWN or comes back UP. + +Runs via systemd timer every 60 seconds. Only sends email on STATUS CHANGE. +State is tracked in /tmp/service_watchdog_state.json +""" + +import subprocess +import json +import os +import sys +import smtplib +from datetime import datetime +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +if hasattr(sys.stdout, 'reconfigure'): + sys.stdout.reconfigure(encoding='utf-8') + +# ─── CONFIGURATION ─── +SERVICES = [ + {"name": "springboot", "label": "Spring Boot API", "port": "8085"}, + {"name": "chatbot", "label": "Go AI Chatbot Engine", "port": "8081"}, + {"name": "telegram-bot", "label": "Telegram Bot Service", "port": "8082"}, + {"name": "nginx", "label": "Nginx Web Server", "port": "80/443"}, + {"name": "webhook", "label": "Webhook Receiver", "port": "9000"}, + {"name": "postgresql", "label": "PostgreSQL Database", "port": "5432"}, +] + +STATE_FILE = "/tmp/service_watchdog_state.json" +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) + +# Email config (reuses same Gmail credentials as deploy emails) +SMTP_SERVER = os.environ.get("SMTP_SERVER", "smtp.gmail.com") +SMTP_PORT = int(os.environ.get("SMTP_PORT", 587)) +SENDER_EMAIL = os.environ.get("SENDER_EMAIL", "krishnaowoxd@gmail.com") +SENDER_PASSWORD = os.environ.get("SENDER_PASSWORD", "qkwylwtcsnlrpree").replace(" ", "") + +# Load recipients from shared recipients.txt +recipients_file = os.path.join(SCRIPT_DIR, "recipients.txt") +recipients = [] +if os.path.exists(recipients_file): + with open(recipients_file, "r", encoding="utf-8") as f: + recipients = [line.strip() for line in f if line.strip() and not line.startswith("#")] + +if not recipients: + recipients = [os.environ.get("RECIPIENT_EMAIL", "dorutoslayer@gmail.com")] + + +def is_service_active(service_name): + """Check if a systemd service is currently active (running).""" + try: + result = subprocess.run( + ["systemctl", "is-active", service_name], + capture_output=True, text=True, timeout=5 + ) + return result.stdout.strip() == "active" + except Exception: + return False + + +def load_previous_state(): + """Load the last known state of all services.""" + if os.path.exists(STATE_FILE): + try: + with open(STATE_FILE, "r") as f: + return json.load(f) + except Exception: + pass + return {} + + +def save_current_state(state): + """Save current state to disk.""" + with open(STATE_FILE, "w") as f: + json.dump(state, f, indent=2) + + +def send_alert_email(changes, current_state): + """Send an HTML email alert for service status changes.""" + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Determine if any service went DOWN + has_down = any(c["status"] == "DOWN" for c in changes) + has_up = any(c["status"] == "UP" for c in changes) + + if has_down and has_up: + subject = f"[RIT VPS] ⚠️ Service Status Change — {now}" + banner_bg = "#fef3c7" + banner_border = "#f59e0b" + banner_text = "#92400e" + banner_icon = "⚠️" + banner_msg = "Some services changed status" + elif has_down: + subject = f"[RIT VPS] 🔴 SERVICE DOWN ALERT — {now}" + banner_bg = "#fef2f2" + banner_border = "#ef4444" + banner_text = "#b91c1c" + banner_icon = "🚨" + banner_msg = "One or more services went DOWN" + else: + subject = f"[RIT VPS] 🟢 SERVICE RECOVERED — {now}" + banner_bg = "#ecfdf5" + banner_border = "#10b981" + banner_text = "#047857" + banner_icon = "✅" + banner_msg = "Services have recovered and are back online" + + # Build change rows + change_rows = "" + for c in changes: + if c["status"] == "DOWN": + icon = "🔴" + status_label = "WENT DOWN" + row_bg = "#fef2f2" + row_color = "#b91c1c" + else: + icon = "🟢" + status_label = "RECOVERED" + row_bg = "#ecfdf5" + row_color = "#047857" + + change_rows += f""" +