Bezig met laden…

📘 Handleiding: Dead Man’s Switch

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 gehuurde 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:

nano ~/deadmanswitch/.env

Plak de volgende inhoud en vul je eigen gegevens in:

TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
TELEGRAM_CHAT_ID=123456789
EMAIL_USER=jouweigenemail@gmail.com
EMAIL_PASS=xxxxxxxxxxxxxxxx
MY_EMAIL=jouweigenemail@gmail.com  # mail waarschuwing naar jezelf
EMAIL_NAME=Dead Man's Switch   # weergavenaam, optioneel

Opslaan en afsluiten:

  • CTRL+O → Enter (opslaan)
  • CTRL+X → afsluiten

🔹 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

Nu maak je de berichten die automatisch verstuurd moeten worden.
Deze bestanden plaats je in de map messages. Je kunt er meerdere maken, bijvoorbeeld bericht1.txt, bericht2.txt, enz.

ℹ️ Let op: de bestandsnaam mag je zelf kiezen (bijv. bericht1.txt, geheim.txt, test123.txt), zolang deze maar eindigt op .txt. Het script leest automatisch alle .txt bestanden in de map messages.

1. Maak de map aan (als die nog niet bestaat):

mkdir -p ~/deadmanswitch/messages

2. Maak een nieuw berichtbestand aan met nano, bijvoorbeeld:

nano ~/deadmanswitch/messages/bericht1.txt

3. Zet de inhoud in dit bestand in dit formaat:

ontvanger1@example.com, ontvanger2@example.com
Onderwerp: Mijn laatste bericht
Dit is de tekst van mijn bericht.
Je mag meerdere regels gebruiken.
  • Eerste regel = ontvangers (meerdere adressen scheiden met komma’s)
  • Tweede regel = onderwerp (begin met Onderwerp: of Subject:)
  • Rest = het bericht zelf, zoveel regels als je wilt

4. Opslaan en afsluiten:

  • CTRL+O → Enter (opslaan)
  • CTRL+X → afsluiten

5. Herhaal dit voor extra berichten (bericht2.txt, geheim.txt, enz.).

👉 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 ~/deadmanswitch.
Helemaal onderaan deze handleiding vind je de volledige Python-code. Kopieer die code en plak hem in het bestand:

nano ~/deadmanswitch/deadmanswitch.py

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 ~/deadmanswitch
sudo chmod 600 ~/deadmanswitch/.env

Vervang USERNAME door de naam van jouw Linux-account. Voer deze commando’s uit terwijl je bent ingelogd als de juiste gebruiker (USERNAME).

Activeer de virtuele omgeving en start het script:

cd ~/deadmanswitch
source venv/bin/activate
python3 deadmansswitch.py

📲 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=on-failure
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.

  • CTRL+O → Enter (opslaan)
  • CTRL+X → 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

Om deadmanswitch.log leeg te maken zonder het bestand zelf te verwijderen:

> ~/deadmanswitch/deadmanswitch.log

🔹 Stap 11. Finale e-mails

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.


🐍 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 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")
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")
EMAIL_NAME = os.getenv("EMAIL_NAME")
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:
    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
    msg["From"] = f"{EMAIL_NAME} <{EMAIL_USER}>" if EMAIL_NAME else 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():
    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

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

async def send_final_notice(total_recipients):
    try:
        await bot.send_message(
            chat_id=TELEGRAM_CHAT_ID,
            text=f"📩 Alle finale mails zijn verzonden ({total_recipients} ontvangers).\n"
                 "Het Dead Man’s Switch script stopt nu definitief."
        )
    except Exception as e:
        logging.error(f"Fout bij laatste Telegram-bericht: {e}")

def send_final_mail():
    global final_sent
    if os.path.exists(FINAL_MAIL_FLAG):
        logging.info("Final flag bestaat al; stoppen zonder opnieuw te verzenden.")
        os._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"Totaal {total_recipients} ontvangers gemaild. Flag aangemaakt en script sluit af.")

    # 👉 Synchroon slotbericht versturen vóór afsluiten
    asyncio.run(send_final_notice(total_recipients))

    os._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
                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.")

                if elapsed_time >= warning_interval and not warning_sent:
                    send_warning_mail()
                    warning_sent = True

                if elapsed_time >= final_interval and not final_sent:
                    send_final_mail()

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

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

# ----------------------------- Main -----------------------------
if os.path.exists(FINAL_MAIL_FLAG):
    logging.info("Final flag bestaat al bij start → script stopt direct.")
    os._exit(0)

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