#!/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"""