Bezig met laden…

📘 Handleiding: Dead Man’s Switch met Python, Gmail & Telegram

Met deze handleiding zet je een Dead Man’s Switch op: een systeem dat automatisch e-mails verstuurt als je een bepaalde tijd niet meer reageert. Dit werkt met een Python-script, Gmail SMTP en een Telegram-bot.

Hoe werkt het?

  • Het script draait op een Linux-server.
  • Via een Telegram-bot moet ik regelmatig bevestigen dat ik nog actief ben.
  • Doe ik dit niet → eerst een waarschuwing via Telegram daarna via e-mail.
  • Reageer ik nog steeds niet → dan worden mijn vooraf ingestelde berichten automatisch gemaild naar de ontvangers die ik heb ingesteld.

Waarom handig?

  • Je kunt hiermee belangrijke informatie, berichten of instructies automatisch doorgeven als jou iets overkomt.
  • Denk bijvoorbeeld aan: persoonlijke boodschappen, digitale accounts of toegangsinformatie die veilig bij iemand terecht moet komen.

🔹 Stap 1. Server voorbereiden

Log in op je Linux server (bijv. Contabo, Ubuntu/Debian):

ssh user@jouwserverip

Werk je systeem bij:

sudo apt update && sudo apt upgrade -y

Installeer Python en pip:

sudo apt install -y python3 python3-pip python3-venv git

🔹 Stap 2. Telegram Bot aanmaken

  1. Open Telegram en zoek naar @BotFather.
  2. Typ /newbot en volg de instructies.
    • Kies een naam → bv. MijnDeadmanBot
    • Kies een gebruikersnaam die eindigt op bot → bv. mijndeadman_bot
  3. Je krijgt een API Token, bijvoorbeeld:
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
Chat ID achterhalen
  • Stuur een bericht naar je bot (bijv. Hallo).
  • Ga naar je browser en open:
https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/getUpdates
  • In de JSON-output zie je "chat":{"id":123456789} → dat is je CHAT ID.

🔹 Stap 3. Gmail App Password aanmaken

  1. Ga naar https://myaccount.google.com/
  2. Zet 2-stapsverificatie aan (Beveiliging → Inloggen bij Google).
  3. Klik op App-wachtwoorden.
  4. Kies “Mail” + “Ander apparaat” → geef bv. de naam DeadmanSwitch.
  5. Je krijgt een 16-cijferig wachtwoord.

👉 Dit gebruik je als EMAIL_PASS.


🔹 Stap 4. Scriptmap maken

Maak een map aan en ga erin:

mkdir ~/deadmanswitch
cd ~/deadmanswitch

Plaats hier:

  • deadmanswitch.py (zie onderaan deze handleiding)
  • .env bestand
  • map messages/

🔹 Stap 5. .env bestand

Maak een .env bestand met je gegevens:

TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_CHAT_ID=123456789
EMAIL_USER=jouweigenemail@gmail.com
EMAIL_PASS=xxxxxxxxxxxxxxxx
MY_EMAIL=jouweigenemail@gmail.com

🔹 Stap 6. Python omgeving + pakketten

Maak een virtuele omgeving en installeer de benodigde pakketten:

python3 -m venv venv
source venv/bin/activate
pip install python-telegram-bot python-dotenv

🔹 Stap 7. Berichten aanmaken

Maak een map:

mkdir messages
📂 Structuur van een berichtbestand
  1. Eerste regel = ontvangers (meerdere mogelijk, gescheiden door komma’s).
  2. Tweede regel = onderwerp van de mail (Onderwerp: of Subject:).
  3. Derde regel en verder = het bericht (meerdere regels toegestaan).

Voorbeeld 1 – één ontvanger

messages/bericht1.txt

mijnadres@gmail.com
Onderwerp: TEST – Dead Man’s Switch
Hallo,

Dit is een testbericht.

Voorbeeld 2 – meerdere ontvangers

messages/bericht2.txt

vriend1@example.com, vriend2@example.com, vriend3@example.com
Onderwerp: Belangrijk bericht
Hallo allemaal,

Dit bericht gaat naar meerdere ontvangers tegelijk.

Voorbeeld 3 – meerdere bestanden

messages/
 ├─ bericht1.txt
 ├─ bericht2.txt
 ├─ bericht3.txt

👉 Elk bestand wordt als apart bericht verstuurd bij de finale trigger.

⚠️ Belangrijk: eerst testen

💡 Test altijd eerst met je eigen e-mailadres.

  • Maak een bestand in messages/ met alleen jouw adres.
  • Controleer of je het bericht ontvangt.
  • Pas daarna vul je de echte ontvangers in.

🔹 Stap 8. Script starten

Activeer de virtuele omgeving en start het script:

source venv/bin/activate
python3 deadmanswitch.py

Telegram commando’s

  • /status → toont volgende controle
  • /reset → reset timer
  • /setcheck 1d → check-interval instellen (bv. 1 dag)
  • /setwarning 3d → waarschuwing via mail na 3 dagen
  • /setfinal 7d → finale e-mail na 7 dagen

🔹 Stap 9. Automatisch starten na reboot

Maak een systemd service:

sudo nano /etc/systemd/system/deadmanswitch.service

Inhoud:

[Unit]
Description=Dead Man's Switch
After=network.target

[Service]
User=USERNAME
WorkingDirectory=/home/USERNAME/deadmanswitch
ExecStart=/home/USERNAME/deadmanswitch/venv/bin/python3 /home/USERNAME/deadmanswitch/deadmanswitch.py
Restart=always
StandardOutput=append:/home/USERNAME/deadmanswitch/deadmanswitch.log
StandardError=append:/home/USERNAME/deadmanswitch/deadmanswitch.log

[Install]
WantedBy=multi-user.target

➡️ Vervang USERNAME door je servergebruikersnaam.

Opslaan en afsluiten.

Service activeren:

sudo systemctl daemon-reload
sudo systemctl enable deadmanswitch
sudo systemctl start deadmanswitch

Status bekijken:

sudo systemctl status deadmanswitch

🔹 Stap 10. Logs bekijken

Het script logt naar:

deadmanswitch.log

Bekijk live:

tail -f ~/deadmanswitch/deadmanswitch.log

🐍 Laatste versie deadmanswitch.py

import os
import time
import smtplib
import logging
import telegram
from dotenv import load_dotenv
from email.mime.text import MIMEText
from telegram import Update
from telegram.ext import Application, CommandHandler, CallbackContext
import threading
import sys
import asyncio
import glob

# Logging instellen
logging.basicConfig(
    filename="deadmanswitch.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logging.getLogger("telegram").setLevel(logging.WARNING)

load_dotenv()

TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_USER = os.getenv("EMAIL_USER")
EMAIL_PASS = os.getenv("EMAIL_PASS")  
MY_EMAIL = os.getenv("MY_EMAIL")
FINAL_MAIL_FLAG = "final_mail_sent.txt"
MESSAGES_DIR = "messages"

bot = telegram.Bot(token=TELEGRAM_BOT_TOKEN)
last_response_time = time.time()
check_interval = 7 * 86400
warning_interval = 14 * 86400
final_interval = 21 * 86400

lock = threading.Lock()

check_sent = False
warning_sent = False
final_sent = False

loop = asyncio.new_event_loop()

def send_telegram_message(message):
    async def async_send_message():
        try:
            await bot.send_message(chat_id=TELEGRAM_CHAT_ID, text=message)
        except Exception as e:
            logging.error(f"Fout bij verzenden Telegram bericht: {e}")

    asyncio.run_coroutine_threadsafe(async_send_message(), loop)

def send_email(subject, body, recipients):
    msg = MIMEText(body, "plain")
    msg["Subject"] = subject
    msg["From"] = EMAIL_USER
    msg["To"] = ", ".join(recipients)

    try:
        with smtplib.SMTP(EMAIL_HOST, EMAIL_PORT) as server:
            server.starttls()
            server.login(EMAIL_USER, EMAIL_PASS)
            server.sendmail(EMAIL_USER, recipients, msg.as_string())
    except Exception as e:
        logging.error(f"Fout bij verzenden e-mail: {e}")

def load_messages_from_files():
    messages = []
    try:
        for file_path in glob.glob(f"{MESSAGES_DIR}/*.txt"):
            with open(file_path, "r", encoding="utf-8") as f:
                lines = f.read().splitlines()
                if len(lines) < 3:
                    continue
                recipients = [r.strip() for r in lines[0].split(",") if r.strip()]
                subject_line = lines[1].strip()
                if subject_line.lower().startswith("onderwerp:"):
                    subject = subject_line[len("onderwerp:"):].strip()
                elif subject_line.lower().startswith("subject:"):
                    subject = subject_line[len("subject:"):].strip()
                else:
                    subject = "Een laatste brief"
                message = "\n".join(lines[2:]).strip()
                if recipients and message:
                    messages.append((recipients, subject, message))
        return messages
    except Exception as e:
        logging.error(f"Fout bij inlezen berichten: {e}")
        return []

def send_warning_mail():
    send_email("⚠️ Waarschuwing: Controle vereist", 
               "Er is al een tijd geen activiteit geweest. Reageer om de dead man's switch te resetten.", 
               [MY_EMAIL])

def send_final_mail():
    global final_sent
    if os.path.exists(FINAL_MAIL_FLAG):
        sys.exit(0)

    messages = load_messages_from_files()
    if not messages:
        return

    for recipients, subject, message in messages:
        send_email(subject, message, recipients)

    open(FINAL_MAIL_FLAG, "w").write("sent")
    final_sent = True
    sys.exit(0)

async def set_interval(update: Update, context: CallbackContext, interval_type):
    global check_interval, warning_interval, final_interval
    try:
        input_value = context.args[0]
        if input_value.endswith("d"):
            new_interval = float(input_value[:-1]) * 86400
        elif input_value.endswith("h"):
            new_interval = float(input_value[:-1]) * 3600
        elif input_value.endswith("m"):
            new_interval = float(input_value[:-1]) * 60
        else:
            new_interval = float(input_value) * 86400
        with lock:
            if interval_type == "check":
                check_interval = new_interval
            elif interval_type == "warning":
                warning_interval = new_interval
            elif interval_type == "final":
                final_interval = new_interval
        await update.message.reply_text(f"{interval_type.capitalize()} interval ingesteld op {input_value}.")
    except:
        await update.message.reply_text(f"Gebruik: /set{interval_type} <tijd> (bijv. '1d', '2h', '30m')")

async def reset_timer(update: Update, context: CallbackContext):
    global last_response_time, check_sent, warning_sent, final_sent
    with lock:
        last_response_time = time.time()
        check_sent = False
        warning_sent = False
        final_sent = False
    await update.message.reply_text("✅ Timer gereset.")

async def show_status(update: Update, context: CallbackContext):
    with lock:
        next_check = time.strftime('%d-%m-%Y %H:%M:%S', time.localtime(last_response_time + check_interval))
    await update.message.reply_text(f"Volgende controle: {next_check}")

async def show_help(update: Update, context: CallbackContext):
    help_text = """
📌 Commando's:
/status - Volgende controle
/setcheck <tijd> - Check-interval
/setwarning <tijd> - Waarschuwingstijd
/setfinal <tijd> - Finale e-mail tijd
/reset - Reset timer
/help - Toon deze lijst
    """
    await update.message.reply_text(help_text)

def start_background_task():
    def timer_checker():
        global check_sent, warning_sent, final_sent
        while True:
            time.sleep(60)
            with lock:
                elapsed_time = time.time() - last_response_time
                if elapsed_time >= final_interval and not final_sent:
                    send_final_mail()
                elif elapsed_time >= warning_interval and not warning_sent:
                    send_warning_mail()
                    warning_sent = True
                elif elapsed_time >= check_interval and not check_sent:
                    send_telegram_message("⚠️ Controle vereist: Er is al een tijd geen activiteit geweest. Reageer om de dead man's switch te resetten.")
                    check_sent = True
    threading.Thread(target=timer_checker, daemon=True).start()

def start_event_loop():
    asyncio.set_event_loop(loop)
    loop.run_forever()

threading.Thread(target=start_event_loop, daemon=True).start()
start_background_task()

application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
application.add_handler(CommandHandler("status", show_status))
application.add_handler(CommandHandler("setcheck", lambda u, c: set_interval(u, c, "check")))
application.add_handler(CommandHandler("setwarning", lambda u, c: set_interval(u, c, "warning")))
application.add_handler(CommandHandler("setfinal", lambda u, c: set_interval(u, c, "final")))
application.add_handler(CommandHandler("reset", reset_timer))
application.add_handler(CommandHandler("help", show_help))
application.run_polling()

Geef een reactie