edwh-restic-discord
Discord webhook notifications for edwh-restic-plugin: a failed backup reaches your Discord server 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 discord, and does nothing at all until [restic.notify] channels names
it.
Installation
(uv) pip install edwh-restic-discord
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):
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/1234567890/token_value
.toml (or default.toml, the committed template):
[restic.notify]
channels = ["discord"]
Then check it works without waiting for a real failure:
edwh restic.notify-test
edwh restic.notify-test --all-events
A webhook 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 webhook, or one per anything
The .env url above is the default for every event. The general form is a table whose keys say
which events go where:
[restic.notify.discord]
[restic.notify.discord.urls]
"*" = "https://discord.com/api/webhooks/1/all" # everything
"backup" = "https://discord.com/api/webhooks/2/backups" # one operation
"warning" = "https://discord.com/api/webhooks/3/noisy" # one level
"failed" = "https://discord.com/api/webhooks/4/oncall" # one phase
"check.failed" = "https://discord.com/api/webhooks/5/damage"
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.
The most specific key wins, and exactly one webhook set is chosen per event. A list value is how you deliberately fan out:
[restic.notify.discord.urls]
"failed" = [
"https://discord.com/api/webhooks/4/oncall",
"https://discord.com/api/webhooks/6/audit",
]
webhook and webhooks are accepted aliases for url and urls. Values must be complete
https:// webhook urls; there is no Discord equivalent of a bare-topic plus server form.
Mentions by friendly name
Discord pings by numeric id, so [restic.notify.discord.users] and
[restic.notify.discord.roles] map names you choose to the ids from Discord:
[restic.notify.discord]
webhook = "https://discord.com/api/webhooks/1/all"
[restic.notify.discord.roles]
oncall = "123456789012345678"
[restic.notify.discord.users]
robin = "234567890123456789"
[restic.notify.discord.mentions]
"failed" = ["oncall"]
"backup.failed" = ["oncall", "robin"]
The mention table uses the same keys and precedence as webhooks. Values can be mapped names, or raw
Discord mentions such as <@id>, <@&id>, @everyone and @here. A matched route adds one short
content line to the message and allows those mention types to ping.
What a notification looks like
Each event becomes a Discord embed:
- Title:
⏳ backup.failed - acme-prod@db-01(phase picks the emoji) - Description: a one-line headline, plus restic logs on failures
- Fields: repository, host, project, target, failed scripts, and other event details
- Color: info is blue, warning is orange, error is red
All options
Under [restic.notify.discord]:
| Key | Default | Meaning |
|---|---|---|
url, webhook |
— | shorthand for the "*" route |
urls, webhooks |
— | the route table (or a bare value, same as url) |
logs |
true |
include restic's stdout/stderr on failures |
timeout |
4.0 |
seconds per request |
max_chars |
3900 |
character budget for the embed description |
username |
— | webhook display-name override |
avatar_url |
— | webhook avatar override |
users |
— | map of friendly name to Discord user id |
roles |
— | map of friendly name to Discord role id |
mention, mentions |
— | mention route table |
Environment:
| Key | Meaning |
|---|---|
DISCORD_WEBHOOK_URL |
default webhook when TOML has no url route |
events and min_level are read by core, not by this plugin, and still apply:
[restic.notify.discord]
webhook = "https://discord.com/api/webhooks/1/all"
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 = ["discord"]
project = "acme-prod-db01"
[restic.notify.discord]
logs = true
username = "restic"
[restic.notify.discord.urls]
"*" = "https://discord.com/api/webhooks/1/timeline"
"failed" = "https://discord.com/api/webhooks/2/oncall"
"check.failed" = "https://discord.com/api/webhooks/3/urgent"
"backup.slow" = "https://discord.com/api/webhooks/1/timeline"
[restic.notify.discord.roles]
oncall = "123456789012345678"
[restic.notify.discord.mentions]
"check.failed" = ["oncall"]
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. A Discord webhook url is also a secret: anyone who
has it can post to that channel. Keep it in .env, point failure routes only at channels you trust,
and set logs = false when the full terminal output should stay off Discord.
Development
uv pip install -e .[dev]
edwh test.run
edwh plugin.release # to publish a new version with vommit
License
edwh-restic-discord is distributed under the terms of the
MIT license.
Metadata
Release files for edwh-restic-discord 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| edwh_restic_discord-0.1.1.tar.gz | 11.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| edwh_restic_discord-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 24.0 kB
Release files / edwh_restic_discord-0.1.1.tar.gz
| Download URL | edwh_restic_discord-0.1.1.tar.gz |
|---|---|
| Size | 11.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f57902c7091f29a1d81bc1a1e4962f403d63ddfe16fb18b5e2bb317fe7a7db8b
|
|
BLAKE2b-256 checksum How to use checksums |
93bb7dadc2bafbadb38d2f84924023ccb0849aea175f84c61d157eec48a0cbe1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / edwh_restic_discord-0.1.1-py3-none-any.whl
| Download URL | edwh_restic_discord-0.1.1-py3-none-any.whl |
|---|---|
| Size | 12.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8577dab98bed3c5f2f6ca5d514e2aa7e47cbf7eae3b01f7b49f2d3a40ac52f8c
|
|
BLAKE2b-256 checksum How to use checksums |
9912f3b9353711a7bceb627fbff59fda2851976a5da258b49729355450ab432a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|