Dead man switch as vibe-coded by Claude
  • Python 96%
  • Dockerfile 4%
Find a file
greg 3d417ab6ce Update docker-compose.yaml
Make the 'local' netrc non-hidden
2026-01-05 10:27:55 +11:00
.forgejo/workflows Change from 'development' tag back to 'latest' and add 'stable' tag for non-pre-release 2025-11-26 12:59:08 +11:00
config Update example config.yaml to reflect additional functionality in current version 2025-11-29 14:46:12 +11:00
static Initial commit 2025-11-23 10:37:08 +11:00
.gitignore Add .netrc to .gitignore 2025-11-25 15:35:12 +11:00
dead_mans_switch.py Add file variables to email action 2025-11-29 13:02:39 +11:00
docker-compose.yaml Update docker-compose.yaml 2026-01-05 10:27:55 +11:00
Dockerfile Add pyyaml to Dockerfile 2025-11-27 09:03:52 +11:00
LICENSE Initial commit 2025-11-23 10:28:23 +11:00
README.md Update README.md 2025-11-29 14:58:48 +11:00

dead-man-switch

Dead man switch as vibe-coded by Claude

A service to detect when notifications sent by it are not acknowledged, for a configurable number of notifications at a configurable interval.

  1. Read config file at /config/config.yaml
  2. At startup, send a notification to
    1. gotify instance
    2. in the case that gotify is unreachable or otherwise fails, by SMTP (email)
  3. If the configured number of notifications remain unacknowledged, execute an arbitrary list of failsafe_commands from the config.yaml file. Types supported are "shell" and "email". Refer to config/config.yaml (reproduced below) for examples. A limited number of tools are available to shell scripts, but they include curl for tasks such as deleting or creating Nextcloud files.
# Dead Man's Switch Configuration - EXAMPLE

# dms
# instant_failsafe          - set to 'true' to instantly execute failsafe commands at service startup. U
#                             Used to debug the failsafe section of this file
instant_failsafe: false
# check_interval_hours      - hours between gotify notifications
check_interval_hours: 24
# notification_expiry_hours - how long the reset link in the notification remains active, in hours. 
#                             Two minutes are subtracted from this by the service.
notification_expiry_hours: 24
# max_missed_notifications  - how many times the reset link can be NOT clicked before the failsafe actions execute
max_missed_notifications: 7
# reset_link_base_url       - The publicly reachable (if you know what's good for you) base URL of the service, so that
#                             the reset links are accessible from anywhere with internet access
reset_link_base_url: https://dms.example.com

# gotify_url                - URL of your gotify service
gotify_url: https://gotify.example.com
# gotify_app_key            - The key to send notifications to the 'DMS' app in Gotify
gotify_app_key: your_key_here

# SMTP Settings
smtp_server: smtp.gmail.com
smtp_port: 587
smtp_use_tls: true
smtp_username: your_email@gmail.com
smtp_password: your_password
smtp_from: your_email@gmail.com
# notification_recipient    - The recipient of notifications, used when gotify is unreachable or fails for another reason
notification_recipient: recipient@example.com

# Failsafe Commands
failsafe_commands:
  # Delete a file from a nextcloud instance
  # note the use of /root/.netrc to provide credentials to the Nextcloud API call
  # To do this you must manually create .netrc and bind mount it (in your docker-compose.yaml file)
  - type: shell
    command: |
      curl -v --netrc-file /root/.netrc -X DELETE 
      https://nextcloud.example.org/remote.php/dav/files/username/Documents/test.md
  # Create a passworded public share link to Nextcloud 'alargefile.ext'
  - type: shell
    command: |
      # Generate password
      export PASSWORD=$(openssl rand -base64 16)
      export FETCHFILE="alargefile.ext";
      export FETCHPATH="Documents/Folder/";
      # Capture the response and extract the URL and STATUSCODE
      RESPONSE=$(curl --netrc-file /root/.netrc -X POST \
      'https://nextcloud.example.com/ocs/v2.php/apps/files_sharing/api/v1/shares' \
      -H 'OCS-APIRequest: true' \
      -d "path=${FETCHPATH}${FETCHFILE}" \
      -d 'shareType=3' \
      -d "password=$PASSWORD")
      SHARE_LINK=$(echo "$RESPONSE" | grep -oP '(?<=<url>)[^<]+')
      STATUSCODE=$(echo "$RESPONSE" | grep -oP '(?<=<statuscode>)[^<]+')
      echo -n "$(date +'%Y-%m-%d %H:%M:%S') generate sharing link to ${FETCHFILE} returned code: ${STATUSCODE}" >> /logs/curl_cmds.log;
      echo "Share link: $SHARE_LINK"
      echo "" >> /logs/curl_cmds.log 
      echo -n "$PASSWORD" > /tmp/share.pwd
      echo -n "$SHARE_LINK" > /tmp/share.lnk
  
  - type: email
    subject: Dead Man's Switch Activated
    to: "someone@example.com, someoneelse@another.org"
    file_vars:
      share_link_password: /tmp/share.pwd
      share_link: /tmp/share.lnk
    body_text: |
      This is a multi-line email body.
      You can write it naturally without escape characters.

      You may insert variables defined in file_vars (above in config file) and also
      the variable {max_missed_notifications}.

      Much easier to read and maintain!
    body_html: |
      <html>
        <body>
          <h1>Failsafe Activated</h1>
            <p>You may also use variables here. The file <a href="{share_link}">a file</a>
            requires password <b>{share_link_password}</b></p>
          <p>Easy to format HTML too!</p>
        </body>
      </html>
    attachments:
      - /tmp/downloadedfile.ext