A small shell script — qnt-alert.sh — fans out to
whichever of Slack and Discord you've configured. Set the webhook
URLs through a systemd drop-in, declare it as an
OnFailure= handler, and a server that bounces sends
you a message instead of dying quietly.
Alerting is a single shell script, deploy/qnt-alert.sh,
that pushes a message out to webhooks. There's a companion systemd
template unit, deploy/qnt-alert@.service, you can wire
as a failure handler. No daemon, no extra moving parts.
The script reads webhook URLs from the environment and fans out to whichever are set. Each destination is guarded — if its variable is empty, that destination is simply skipped:
SLACK_WEBHOOK_URL — a Slack incoming-webhook
URL.DISCORD_WEBHOOK_URL — a Discord webhook URL.Set one, set both, or set neither. With neither, the script runs but sends nothing — there's no hard dependency on either provider.
OnFailure= on the unit you want watched.Both providers hand you a single URL that accepts an HTTP POST. Create whichever you want, then keep the URLs for the next step. The exact dashboard click-path moves around, so follow each provider's own docs rather than a screenshot here.
Create an Incoming Webhook in your Slack
workspace — under Apps → Incoming Webhooks. Pick
the channel the alert should land in; Slack gives you a URL of the
form https://hooks.slack.com/services/....
See Slack's own Incoming Webhooks documentation for the current setup flow.
Create a webhook in the target channel's
Integrations settings. Discord gives you a URL of
the form https://discord.com/api/webhooks/....
See Discord's own webhook documentation for the current setup flow.
The script reads the URLs from the environment, so qnt-server (and
the alert unit) need to see them. The runbook way is a systemd
drop-in .conf — that keeps the URLs out of the main
unit file and survives package upgrades.
Make the drop-in directory and create a .conf with
the Slack URL:
mkdir -p /etc/systemd/system/qnt-server.service.d
Then put this in
/etc/systemd/system/qnt-server.service.d/slack.conf:
[Service]
Environment=SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
Same pattern for Discord — add it to the same .conf
or a separate one:
[Service]
Environment=DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... Pick up the drop-in and restart so qnt-server sees the new environment:
systemctl daemon-reload
systemctl restart qnt-server
Setting the env vars doesn't send anything on its own — you still
need something to invoke the alert. The mechanism that turns
"server bounced" into a notification is systemd's
OnFailure=.
deploy/qnt-alert@.service is a template unit. The
watched unit declares it as an OnFailure= handler in
its [Unit] section — the %n passes the
failing unit's name through to the template:
[Unit]
OnFailure=qnt-alert@%n.service
When the watched unit enters a failed state, systemd starts
qnt-alert@<unit>.service, which runs the script
and fans out to whichever webhooks you wired in step 3.
Don't wait for a real crash to find out a URL was pasted wrong. The script has a test mode that sends an alert immediately.
qnt-alert.sh test-unit
You should see the message land in every channel you configured.
If a destination stays silent, re-check that its
Environment= line is present in the drop-in and that
you ran systemctl daemon-reload.
This is webhook-push alerting — good enough for a single-operator setup that mostly wants to know when a unit fell over. It does not do metric-threshold alerting (an error-rate spike, say). For that you'd pair it with Prometheus Alertmanager, which is not pre-wired here. The monitoring page covers the metrics surface to build that on.
Alerts wired and tested. Round out the operational guides.