Bezig met laden…

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

Met deze handleiding maak je je eigen Dead Man’s Switch: een systeem dat automatisch e-mails verstuurt als je een bepaalde tijd niet meer reageert. Je hebt hiervoor wel een beetje Linux-kennis nodig. Het script gebruikt Gmail SMTP en een Telegram-bot om alles te regelen.

🔹 Hoe werkt het?

  • Het script draait op een Linux-server (ik gebruik zelf een Contabo VPS met Linux).
  • Via een Telegram-bot bevestig je regelmatig dat je nog actief bent.
  • Doe je dit niet → eerst krijg je een waarschuwing in Telegram, daarna per e-mail.
  • Blijf je inactief → dan worden je vooraf ingestelde berichten automatisch verstuurd naar de ontvangers die jij hebt ingesteld.

🔹 Waarom handig?

Met een Dead Man’s Switch kun je ervoor zorgen dat belangrijke informatie of persoonlijke boodschappen alsnog terechtkomen bij de juiste mensen, ook als jij zelf niet meer in staat bent om dit te doen. Denk bijvoorbeeld aan:

  • Persoonlijke berichten voor familie of vrienden
  • Toegangsinformatie voor digitale accounts
  • Belangrijke instructies die niet verloren mogen gaan

🔹 Stap 1. Server voorbereiden

Log in op je Linux server (bijv. via een gehuurde Contabo VPS met Ubuntu/Debian):

ssh user@jouwserverip

⚠️ Deze handleiding gaat ervan uit dat je een eigen Linux-gebruiker hebt aangemaakt en niet direct als root werkt. Root inloggen via SSH is onveilig en wordt daarom afgeraden.

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
EMAIL_NAME=Dead Man's Switch   # <- weergavenaam, optioneel

🔹 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

Script aanmaken
Maak het bestand deadmanswitch.py in de map /home/USERNAME/deadmanswitch.
Helemaal onderaan deze handleiding vind je de volledige Python-code. Kopieer die code en plak hem in het bestand:

nano /home/USERNAME/deadmanswitch/deadmanswitch.py

Vervang USERNAME door de naam van jouw Linux-account.
Plak de code, sla op met CTRL+O, ENTER, en sluit af met CTRL+X.

Bestandsrechten instellen
Zorg dat de map en bestanden eigendom zijn van jouw Linux-gebruiker (de gebruiker waarmee je het script draait). Gebruik hiervoor:

sudo chown -R USERNAME:USERNAME /home/USERNAME/deadmanswitch
sudo chmod 600 /home/USERNAME/deadmanswitch/.env

Vervang USERNAME door de naam van jouw Linux-account.

Activeer de virtuele omgeving en start het script:

cd /home/USERNAME/deadmanswitch
source venv/bin/activate
python3 deadmansswitch.py

Vervang USERNAME door de naam van jouw Linux-account.

📲 Telegram commando’s

Met de Telegram-bot beheer je de Dead Man’s Switch. Tijden kun je instellen in minuten (m), uren (h) of dagen (d). Zonder eenheid geldt dagen.

Standaardwaarden:

  • Check (Telegram): 7d
  • Warning (Mail): 14d
  • Final (Mail): 21d

Commando’s:

  • /status → toont volgende controle
  • /reset → reset timer
  • /setcheck <tijd>Telegram-waarschuwing (bv. 30m, 2h, 1d)
  • /setwarning <tijd>mail-waarschuwing (bv. 1h, 3d)
  • /setfinal <tijd>finale mail (bv. 2h, 7d)
  • /help → toont overzicht van alle commando’s

🔹 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 -----------------------------
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)

# ----------------------------- Config ------------------------------
load_dotenv()

TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
TELEGRAM_CHAT_ID   = os.getenv("TELEGRAM_CHAT_ID")  # enkel deze chat mag commando's
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")   # waarschuwing naar jezelf
EMAIL_NAME = os.getenv("EMAIL_NAME") # optioneel: weergavenaam voor From
FINAL_MAIL_FLAG = "final_mail_sent.txt"
MESSAGES_DIR    = "messages"

bot = telegram.Bot(token=TELEGRAM_BOT_TOKEN)
last_response_time = time.time()

# Standaard intervals
check_interval   = 7  * 86400  # 7 dagen
warning_interval = 14 * 86400  # 14 dagen
final_interval   = 21 * 86400  # 21 dagen

lock = threading.Lock()

# Flags om dubbele meldingen te voorkomen
check_sent   = False
warning_sent = False
final_sent   = False

# Asyncio event loop voor Telegram-sends vanuit background thread
loop = asyncio.new_event_loop()

logging.info("Dead Man’s Switch gestart.")

# ----------------------------- Helpers -----------------------------
def is_authorized(update: Update) -> bool:
    """Alleen commando's van dit chat-id accepteren."""
    return str(update.effective_chat.id) == TELEGRAM_CHAT_ID

def send_telegram_message(message: str):
    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: str, body: str, recipients: list[str]):
    msg = MIMEText(body, "plain")
    msg["Subject"] = subject

    # From: met weergavenaam als EMAIL_NAME is gezet, anders alleen adres
    if EMAIL_NAME:
        msg["From"] = f"{EMAIL_NAME} <{EMAIL_USER}>"
    else:
        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())
        logging.info(f"E-mail verzonden: '{subject}' naar {', '.join(recipients)}")
    except Exception as e:
        logging.error(f"Fout bij verzenden e-mail: {e}")

def load_messages_from_files():
    """Lees alle .txt berichten uit de map messages/ en parseer ontvangers/onderwerp/tekst."""
    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:
                    logging.error(f"Berichtbestand onvoldoende regels: {file_path}")
                    continue

                # 1e regel: ontvangers (komma-gescheiden)
                recipients = [r.strip() for r in lines[0].split(",") if r.strip()]

                # 2e regel: onderwerp
                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"

                # Rest: berichttekst (meerdere regels toegestaan)
                message = "\n".join(lines[2:]).strip()

                if recipients and message:
                    messages.append((recipients, subject, message))
                else:
                    logging.error(f"Lege ontvangers of bericht in: {file_path}")
        return messages
    except Exception as e:
        logging.error(f"Fout bij inlezen berichten: {e}")
        return []

def send_warning_mail():
    body = (
        "⚠️ Dead Man’s Switch actief!\n\n"
        "Er is al een tijd geen activiteit gedetecteerd.\n\n"
        "➡️ Gebruik het commando /reset in Telegram om de timer opnieuw te starten.\n"
        "Als je dit niet doet, worden na het ingestelde interval je berichten automatisch verstuurd."
    )
    send_email("⚠️ Waarschuwing: Dead Man’s Switch actief", body, [MY_EMAIL])
    logging.info(f"Waarschuwingsmail verzonden naar {MY_EMAIL}.")

def send_final_mail():
    """Verstuur alle berichten; maak daarna het flag-bestand aan en stop."""
    global final_sent
    if os.path.exists(FINAL_MAIL_FLAG):
        logging.info("Final flag bestaat al; stoppen zonder opnieuw te verzenden.")
        sys.exit(0)

    messages = load_messages_from_files()
    if not messages:
        logging.error("Geen berichten gevonden in 'messages/'.")
        return

    total_recipients = 0
    for recipients, subject, message in messages:
        send_email(subject, message, recipients)
        total_recipients += len(recipients)

    open(FINAL_MAIL_FLAG, "w").write("sent")
    final_sent = True
    logging.info(f"Finale mails verzonden naar totaal {total_recipients} ontvangers. Flag aangemaakt en script sluit af.")
    sys.exit(0)

# ----------------------------- Telegram Commands -----------------------------
async def set_interval(update: Update, context: CallbackContext, interval_type: str):
    if not is_authorized(update):
        await update.message.reply_text("🚫 Je bent niet geautoriseerd om dit commando te gebruiken.")
        return

    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

        logging.info(f"/set{interval_type} ingesteld op {input_value}.")
        await update.message.reply_text(f"{interval_type.capitalize()} interval ingesteld op {input_value}.")
    except Exception:
        await update.message.reply_text(f"Gebruik: /set{interval_type} <tijd> (bijv. '1d', '2h', '30m')")

async def reset_timer(update: Update, context: CallbackContext):
    if not is_authorized(update):
        await update.message.reply_text("🚫 Je bent niet geautoriseerd om dit commando te gebruiken.")
        return

    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
    logging.info("/reset ontvangen → timer gereset.")
    await update.message.reply_text("✅ Timer gereset.")

async def show_status(update: Update, context: CallbackContext):
    if not is_authorized(update):
        await update.message.reply_text("🚫 Je bent niet geautoriseerd om dit commando te gebruiken.")
        return

    with lock:
        next_check = time.strftime('%d-%m-%Y %H:%M:%S', time.localtime(last_response_time + check_interval))
    logging.info("/status opgevraagd.")
    await update.message.reply_text(f"Volgende controle: {next_check}")

async def show_help(update: Update, context: CallbackContext):
    if not is_authorized(update):
        await update.message.reply_text("🚫 Je bent niet geautoriseerd om dit commando te gebruiken.")
        return

    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)

# ----------------------------- Background Task -----------------------------
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

                # 1) Telegram check
                if elapsed_time >= check_interval and not check_sent:
                    send_telegram_message(
                        "⚠️ Dead Man’s Switch actief!\n"
                        "Er is al een tijd geen activiteit gedetecteerd.\n\n"
                        "➡️ Gebruik het commando /reset in Telegram om de timer opnieuw te starten.\n"
                        "Als je dit niet doet, worden na het ingestelde interval je berichten automatisch verstuurd."
                    )
                    check_sent = True
                    logging.info("Check-bericht via Telegram verstuurd.")

                # 2) Waarschuwing per mail
                if elapsed_time >= warning_interval and not warning_sent:
                    send_warning_mail()
                    warning_sent = True
                    # (send_warning_mail logt ook)

                # 3) Finale mails
                if elapsed_time >= final_interval and not final_sent:
                    send_final_mail()
                    # (send_final_mail logt & sluit af)

    threading.Thread(target=timer_checker, daemon=True).start()

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

# ----------------------------- Main -----------------------------
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()

Wanneer de finale e-mails worden verstuurd, maakt het script automatisch een flagbestand aan in de scriptmap:

final_mail_sent.txt
  • Dit bestand voorkomt dat er meerdere keren dezelfde finale mails worden gestuurd.
  • Zodra het script ziet dat dit bestand bestaat, stopt het zichzelf.

👉 Belangrijk:
Als je het script opnieuw wilt gebruiken of opnieuw wilt testen, moet je dit bestand handmatig verwijderen:

rm ~/deadmanswitch/final_mail_sent.txt

Daarna kun je het script weer starten en werkt het opnieuw vanaf nul.

Geef een reactie