diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bca6f02 --- /dev/null +++ b/.env.example @@ -0,0 +1,22 @@ +# E-Mail Empfang (IMAP) - für Videos von der Kamera +IMAP_SERVER=secureimap.t-online.de +IMAP_PORT=993 +EMAIL_USER=deine-email@t-online.de +EMAIL_PASS=dein-passwort + +# Speicherort für Videos +SAVE_DIR=/app/videospeicher + +# E-Mail Versand (SMTP) - für Monitoring-Benachrichtigungen +SMTP_SERVER=securesmtp.t-online.de +SMTP_PORT=587 +SMTP_USER=deine-email@t-online.de +SMTP_PASS=dein-passwort + +# Alert-E-Mail (an wen sollen Benachrichtigungen gehen?) +ALERT_EMAIL=deine-email@t-online.de + +# Monitoring-Einstellungen (optional) +# CHECK_INTERVAL=600 # Monitoring-Prüfung alle 10 Minuten +# MAX_AGE=900 # Alarm wenn kein Heartbeat seit 15 Minuten +# ALERT_COOLDOWN=3600 # Mindestens 1 Stunde zwischen Alarm-E-Mails diff --git a/Dockerfile b/Dockerfile index ff10dfe..e5c24cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,7 @@ RUN pip install --no-cache-dir -r requirements.txt # Kopiere den Anwendungscode COPY main.py . +COPY monitor.py . # Erstelle Verzeichnis für Videospeicher RUN mkdir -p /app/videospeicher diff --git a/docker-compose.yml b/docker-compose.yml index e8f0e90..89d0df0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: restart: unless-stopped volumes: - ./videospeicher:/app/videospeicher + - heartbeat:/app env_file: - .env environment: @@ -14,8 +15,32 @@ services: - EMAIL_USER=${EMAIL_USER} - EMAIL_PASS=${EMAIL_PASS} - SAVE_DIR=${SAVE_DIR} + - HEARTBEAT_FILE=/app/heartbeat.txt # Führe das Skript alle 5 Minuten aus command: > sh -c "while true; do python main.py && echo \"CameraSave ausgeführt um \$$(date)\" && sleep 300; - done" \ No newline at end of file + done" + + monitor: + build: . + container_name: camerasave-monitor + restart: unless-stopped + volumes: + - heartbeat:/app + env_file: + - .env + environment: + - HEARTBEAT_FILE=/app/heartbeat.txt + - CHECK_INTERVAL=600 # Prüfung alle 10 Minuten + - MAX_AGE=900 # Alarm nach 15 Minuten ohne Heartbeat + - ALERT_COOLDOWN=3600 # 1 Stunde zwischen Alarm-E-Mails + - SMTP_SERVER=${SMTP_SERVER:-securesmtp.t-online.de} + - SMTP_PORT=${SMTP_PORT:-587} + - SMTP_USER=${SMTP_USER:-${EMAIL_USER}} + - SMTP_PASS=${SMTP_PASS:-${EMAIL_PASS}} + - ALERT_EMAIL=${ALERT_EMAIL:-${EMAIL_USER}} + command: python monitor.py + +volumes: + heartbeat: \ No newline at end of file diff --git a/main.py b/main.py index c4a29f3..dbab3cd 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,17 @@ IMAP_PORT = int(os.getenv("IMAP_PORT", "993")) EMAIL_USER = os.getenv("EMAIL_USER", "dk2ge@t-online.de") EMAIL_PASS = os.getenv("EMAIL_PASS", "ETBjw65tf2") SAVE_DIR = os.getenv("SAVE_DIR", "./videospeicher") +HEARTBEAT_FILE = os.getenv("HEARTBEAT_FILE", "/app/heartbeat.txt") + +# === HEARTBEAT === +def update_heartbeat(): + """Schreibt einen Heartbeat für das Monitoring.""" + try: + with open(HEARTBEAT_FILE, "w") as f: + f.write(datetime.now().isoformat()) + print(f"💓 Heartbeat aktualisiert: {HEARTBEAT_FILE}") + except Exception as e: + print(f"⚠️ Fehler beim Heartbeat-Update: {e}") # === ALTE DATEIEN LÖSCHEN (älter als 1 Jahr) === def cleanup_old_files(base_dir, days=365): @@ -84,5 +95,7 @@ def process_mails(): if __name__ == "__main__": + update_heartbeat() # Heartbeat zu Beginn cleanup_old_files(SAVE_DIR, days=365) - process_mails() \ No newline at end of file + process_mails() + update_heartbeat() # Heartbeat nach erfolgreicher Ausführung diff --git a/monitor.py b/monitor.py new file mode 100644 index 0000000..d5b8ecd --- /dev/null +++ b/monitor.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +""" +Monitoring-Script für CameraSave +Prüft, ob das Programm regelmäßig läuft und sendet E-Mail-Benachrichtigungen bei Problemen. +""" + +import os +import smtplib +import time +from datetime import datetime, timedelta +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +# === KONFIGURATION === +HEARTBEAT_FILE = os.getenv("HEARTBEAT_FILE", "/app/heartbeat.txt") +CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", "600")) # 10 Minuten +MAX_AGE = int(os.getenv("MAX_AGE", "900")) # 15 Minuten - danach Alarm + +# E-Mail Konfiguration für Benachrichtigungen +SMTP_SERVER = os.getenv("SMTP_SERVER", "securesmtp.t-online.de") +SMTP_PORT = int(os.getenv("SMTP_PORT", "587")) +SMTP_USER = os.getenv("SMTP_USER", os.getenv("EMAIL_USER", "")) +SMTP_PASS = os.getenv("SMTP_PASS", os.getenv("EMAIL_PASS", "")) +ALERT_EMAIL = os.getenv("ALERT_EMAIL", SMTP_USER) # Standard: an sich selbst + +last_alert_time = None +ALERT_COOLDOWN = int(os.getenv("ALERT_COOLDOWN", "3600")) # 1 Stunde zwischen Alarmen + + +def send_alert_email(subject, message): + """Sendet eine E-Mail-Benachrichtigung.""" + global last_alert_time + + # Cooldown prüfen - nicht zu oft E-Mails senden + if last_alert_time: + time_since_last = (datetime.now() - last_alert_time).total_seconds() + if time_since_last < ALERT_COOLDOWN: + print(f"⏳ Cooldown aktiv. Nächste E-Mail möglich in {int(ALERT_COOLDOWN - time_since_last)}s") + return + + try: + msg = MIMEMultipart() + msg['From'] = SMTP_USER + msg['To'] = ALERT_EMAIL + msg['Subject'] = subject + + body = f""" +CameraSave Monitoring Alert +========================== + +{message} + +Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} +Server: {os.uname().nodename if hasattr(os, 'uname') else 'Unknown'} + +Diese Nachricht wurde automatisch vom Monitoring-System generiert. +""" + msg.attach(MIMEText(body, 'plain')) + + server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) + server.starttls() + server.login(SMTP_USER, SMTP_PASS) + server.send_message(msg) + server.quit() + + last_alert_time = datetime.now() + print(f"✅ Alert-E-Mail gesendet an {ALERT_EMAIL}") + + except Exception as e: + print(f"❌ Fehler beim E-Mail-Versand: {e}") + + +def check_heartbeat(): + """Prüft, ob die Heartbeat-Datei aktuell ist.""" + if not os.path.exists(HEARTBEAT_FILE): + return False, "Heartbeat-Datei existiert nicht" + + try: + mtime = os.path.getmtime(HEARTBEAT_FILE) + age = time.time() - mtime + + if age > MAX_AGE: + return False, f"Heartbeat zu alt: {int(age)}s (max {MAX_AGE}s)" + + return True, f"OK - Heartbeat vor {int(age)}s" + + except Exception as e: + return False, f"Fehler beim Lesen der Heartbeat-Datei: {e}" + + +def send_recovery_email(): + """Sendet eine E-Mail, wenn das System sich erholt hat.""" + send_alert_email( + "✅ CameraSave: System wiederhergestellt", + "Das CameraSave-Programm läuft wieder normal." + ) + + +def main(): + """Hauptschleife des Monitoring.""" + print("🔍 CameraSave Monitor gestartet") + print(f" Heartbeat-Datei: {HEARTBEAT_FILE}") + print(f" Prüfintervall: {CHECK_INTERVAL}s") + print(f" Max Alter: {MAX_AGE}s") + print(f" Alert E-Mail: {ALERT_EMAIL}") + print() + + was_down = False + + while True: + try: + is_healthy, status = check_heartbeat() + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + if is_healthy: + print(f"[{timestamp}] ✅ {status}") + if was_down: + # System ist wieder online + send_recovery_email() + was_down = False + else: + print(f"[{timestamp}] ❌ {status}") + if not was_down: + # Erster Fehler - Alarm senden + send_alert_email( + "⚠️ CameraSave: Programm läuft nicht mehr!", + f"Das CameraSave-Programm antwortet nicht mehr.\n\nStatus: {status}" + ) + was_down = True + + time.sleep(CHECK_INTERVAL) + + except KeyboardInterrupt: + print("\n🛑 Monitor gestoppt") + break + except Exception as e: + print(f"❌ Unerwarteter Fehler: {e}") + time.sleep(CHECK_INTERVAL) + + +if __name__ == "__main__": + main()