freedzhost.cloud

Failures that call you: every service reports, every deploy can roll back

Monitoring suites are heavy and mostly look at what is up. What you need first is simpler: when a service fails, you hear about it, once, with its name. systemd already knows when a unit fails; you only have to give it somewhere to say so.

What you get. Every failed service sends a signed event with its name; every machine says once a day that it is alive; a delivery that breaks a service puts the previous version back and tells you. The rollback and the skip were proven here by forcing them on purpose.

1. One template for every failure

# ~/.config/systemd/user/notify-failure@.service
[Unit]
Description=Report the failure of %i

[Service]
Type=oneshot
ExecStart=%h/.local/bin/notify-failure %i

Then attach it to any unit with a drop-in, without editing the unit itself:

# ~/.config/systemd/user/my-tunnel.service.d/notify.conf
[Unit]
OnFailure=notify-failure@%n.service

notify-failure is a few lines that call a sender with a source, a type, a severity and a small JSON detail. The sender signs the body with HMAC and posts it to an automation, which writes the event and pushes a notification to the phone. Its exit codes say what happened: accepted, refused (bad signature), or collector unreachable.

2. A daily "I am alive", with patience

Each machine sends a health event once a day. The watcher alerts when one goes silent too long, with a tolerance that fits the machine: a laptop may stay closed for three days, a server much less. An "info" event never rings.

3. Skip, don't fail, when the target is away

A service that reads the server every ten minutes failed 86 times per boot of the laptop, 65 of them because the server was asleep, and each failure triggered a failure report that failed in turn. A condition skips the run instead:

[Service]
ExecCondition=bash -c 'ssh -o BatchMode=yes -o ConnectTimeout=3 server true || exit 1'

The || exit 1 is not decoration. For ExecCondition, exit codes 1 to 254 mean "skip", but 255 means "fail", and 255 is exactly what ssh returns when it cannot connect.

4. Deliveries that roll back

Our delivery script does, for each component: back up, install, restart, check health, and if the check fails, put the previous version back and restart again. It remembers the commit it delivered and sends deploy.delivered or deploy.rolled_back. We proved the rollback by forcing a failure with a --simulate-failure flag.

Traps

A user service does not have ~/.local/bin in its PATH: write full paths, or the failure report itself fails.

A notification storm teaches you to ignore notifications. Report a failure once, and the recovery once.

Found this useful? Tell us what to measure next.