Skip to main content

edwh-restic-ntfy

ntfy notifications for edwh-restic-plugin: a failed backup reaches your phone instead of dying in a cron log.

Core emits an event per restic operation and ships no notifiers itself. This package is one — it registers a channel called ntfy, and does nothing at all until [restic.notify] channels names it.

Installation

(uv) pip install edwh-restic-ntfy

Installed beside edwh-restic-plugin, it is discovered through its entry point; no import or module list is needed.

Quick start

.env (gitignored, secrets only):

NTFY_TOKEN=tk_your_token_here

.toml (or default.toml, the committed template):

[restic.notify]
channels = ["ntfy"]

[restic.notify.ntfy]
url = "https://ntfy.sh/acme-backups"

Then check it works without waiting for a real failure:

edwh restic.notify-test
edwh restic.notify-test --all-events

A public ntfy topic needs no credentials, so NTFY_TOKEN is optional. A url is what activates the channel: named in channels but without one, it is skipped with a note and your backups run on.

Routing: one url, or one per anything

url above is shorthand. The general form is a table whose keys say which events go where:

[restic.notify.ntfy]
server = "https://ntfy.example.com"        # optional, default https://ntfy.sh

[restic.notify.ntfy.urls]
"*" = "acme-all"               # everything, unless a key below claims it
"backup" = "acme-backups"           # one operation  (also spelled "backup.*")
"warning" = "acme-noisy"             # one level      (info | warning | error)
"failed" = "acme-oncall"            # one phase      (also spelled "*.failed")
"check.failed" = "https://ntfy.sh/acme-damage"   # one combination

Five kinds of key, freely mixed:

Key Means Matches
"*" everything any event
"backup", "backup.*" one operation backup.started, backup.failed, …
"failed", "*.failed" one phase, across operations backup.failed, check.failed, …
"error", "warning", "info" one level whatever core gives that level
"check.failed" one exact event only that one

Operations are backup, restore, check, forget, wipe; phases are started, succeeded, failed, slow. A key that could never match anything — "faild", "nonsense.failed" — is reported and skipped when the channel is configured, so a typo tells you at once instead of quietly never firing.

Which url wins

The most specific key wins, and exactly one url set is chosen per event:

"check.failed"  (exact)      >  "failed"  (phase)  >  "error"  (level)
                             >  "backup"  (operation)          >  "*"

Overlapping keys never fan out, so a single failure cannot wake someone twice, and "*" behaves as the fallback rather than as an extra copy of everything.

Phase and level deliberately outrank operation. Routing by severity is the whole reason to have a second url, and a "backup" key that swallowed backup.failed away from your oncall topic would defeat it. Between the two, phase beats level, because it is the narrower statement.

When you do want one event in two places, say so with a list:

[restic.notify.ntfy.urls]
"failed" = ["acme-oncall", "https://ntfy.sh/acme-audit"]

Both are attempted; one dead host does not cost the other its notification.

Topics and urls

A value containing :// is used as it is. Anything else is a bare topic, resolved against server (default https://ntfy.sh, overridable with NTFY_SERVER in .env). Mixing is fine — that is how one channel reaches both a self-hosted server and ntfy.sh.

Credentials

.env key Purpose
NTFY_TOKEN access token, sent as Authorization: Bearer to every host
NTFY_TOKEN_<HOST> overrides NTFY_TOKEN for one host
NTFY_USER, NTFY_PASSWORD basic auth, used when no token applies
NTFY_SERVER default server for bare topics

<HOST> is the hostname uppercased with everything non-alphanumeric replaced by _: ntfy.shNTFY_TOKEN_NTFY_SH, ntfy.example.com:8080NTFY_TOKEN_NTFY_EXAMPLE_COM_8080.

Core hands this plugin only the NTFY_* keys from .env, so restic's password and other channels' tokens are never in scope.

What a notification looks like

Title, then body:

backup.failed - acme-prod@db-01

backup failed with exit 2 after 4m12s

repository: s3:acme
host: db-01
project: acme-prod
target: files
failed scripts: backup_files.sh (exit 1)

Fatal: unable to open repository at s3:...

The phase picks the icon (⏳ started, ✅ succeeded, 🚨 failed, 🐌 slow) and the level picks the ntfy priority: info → 3, warning → 4, error → 5, so only bad news pushes past a normal notification.

Override priorities with the same keys and precedence as urls:

[restic.notify.ntfy]
priority = 4                       # one value for every event

[restic.notify.ntfy.priorities]    # or per route
"*" = 2
"backup.failed" = 5

All options

Under [restic.notify.ntfy]:

Key Default Meaning
url, topic shorthand for the "*" route
urls, topics the route table (or a bare value, same as url)
server https://ntfy.sh base for bare topics
priority / priorities per level 1–5, single value or route table
logs true include restic's stdout/stderr on failures
timeout 4.0 seconds per request
max_bytes 3800 message budget; longer bodies are cut with [truncated]

events and min_level are read by core, not by this plugin, and still apply:

[restic.notify.ntfy]
url = "https://ntfy.sh/acme-backups"
min_level = "warning"      # core drops info events before routing ever sees them

You rarely need events: this plugin narrows core's subscription to what its own table can deliver, so restic.notify-test reports an event with no matching route as filtered rather than as a silent success. An event that reaches us with no route sends nothing and says so once per event name.

A worked setup

[restic.notify]
channels = ["ntfy"]
project = "acme-prod-db01"

[restic.notify.ntfy]
server = "https://ntfy.example.com"
logs = true

[restic.notify.ntfy.urls]
"*" = "backups-log"      # the timeline: started, succeeded, forget, …
"failed" = "backups-oncall"   # anything broken, whichever operation
"check.failed" = "backups-urgent"   # repository damage gets its own topic
"backup.slow" = "backups-log"      # a slow backup is not worth a phone call

[restic.notify.ntfy.priorities]
"check.failed" = 5
"*" = 2                  # the timeline should not buzz

Security note

A notifier runs in-process and is trusted like any other dependency. Events carry an allowlist of fields and never the repository URI — but logs on a failure event is restic's full stdout/stderr, which can include repository paths and hostnames. Point failure routes at a topic you would be comfortable pasting a terminal session into, protect it with a token, or set logs = false for that channel.

Development

uv pip install -e .[dev]
edwh test.run
edwh plugin.release # to publish a new version with vommit

License

edwh-restic-ntfy is distributed under the terms of the MIT license.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

edwh_restic_ntfy-0.1.0.tar.gz (12.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

edwh_restic_ntfy-0.1.0-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

File details

Details for the file edwh_restic_ntfy-0.1.0.tar.gz.

File metadata

  • Download URL: edwh_restic_ntfy-0.1.0.tar.gz
  • Upload date:
  • Size: 12.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22.3","id":"zena","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for edwh_restic_ntfy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ce0c2e1d300d27bffbba2a87241a4ce3d9df1ea8fa3a0f80e5f646370d7cac4a
MD5 1885f2283fcc3c4b5d52e9fe4e3277b4
BLAKE2b-256 da9221247dac9e5ec9200bfdeb7d2f5d77a7c61dc5844801792aa0015887f36e

See more details on using hashes here.

File details

Details for the file edwh_restic_ntfy-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: edwh_restic_ntfy-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22.3","id":"zena","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for edwh_restic_ntfy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 33659accd3f727d40f2f6d3acd1ccbcaf57e181ee1f918ff7e3070d110bf4e9c
MD5 54c8318c8ce1103ee980384268da9bc4
BLAKE2b-256 cf2259142de3e3bbd18b14a9a54ecb4287b2fccfc703b1919b5e2e521db629eb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page