#!/usr/bin/env bash # Install the failure notifier and hook every fleet user unit on this box. # services/althing-notify-failure/install.sh [--dry-run] # # Idempotent. Re-running adds hooks for units that appeared since last time. set -euo pipefail cd "$(dirname "$0")" DRY="${1:-}" UD="${HOME}/.config/systemd/user" BIN="${HOME}/.local/bin" # dbus.service is systemd's own plumbing, not a fleet service -- excluded. # althing-notify-failure@ is excluded because a notifier must never report # itself (see the template's comment). # althing-notify-failure@ covers both the bare template and every instance # (althing-notify-failure@booth.service.service) -- a notifier must never # report itself. dbus is systemd's own plumbing, not a fleet service. # EXCLUDED, and why each: # dbus / gpg-agent / dirmngr -- OS plumbing, not fleet services; they cycle # routinely and benignly. # althing-notify-failure@ -- both the template and every instance. A # notifier must never report itself. # svos-failed-alarm -- an alarm; hooking an alarm with an alarm is # a loop with extra steps. (Being retired.) # DELIBERATELY INCLUDED: timer-driven oneshots (dev-backup, ha-backup, # fleet-tls-cert-check, headscale-ddns, seat-inventory-drift, # brokkr-landscape-scan, soong-ci-relay). A backup or a cert check that fails # silently is the same "nobody knows" class this tool exists for, and all seven # were clean at install time so they are not a known noise source. SKIP_RE='^(dbus|gpg-agent|dirmngr|althing-notify-failure@|svos-failed-alarm)' run() { if [ "$DRY" = "--dry-run" ]; then echo " would: $*"; else "$@"; fi; } echo "== installing the notifier ==" run install -Dm755 althing-notify-failure "${BIN}/althing-notify-failure" run install -Dm644 althing-notify-failure@.service "${UD}/althing-notify-failure@.service" echo "== hooking fleet user units ==" # ⚠ ENABLED, not running. The first cut selected --state=running, which skips # exactly the units that most need the alarm: one that is already down at # install time never gets hooked. Found on 2026-09-22 with talk.service sitting # in failed state (exit 143) while its containers kept serving on :8092 -- the # precise failure this tool exists for, missed because the installer would not # look at anything that was not already healthy. # # Union of enabled unit files and currently-loaded units, so a unit that is # enabled-but-down and a unit that is running-but-not-enabled both get hooked. # # ⚠ --plain IS LOAD-BEARING. systemd decorates a FAILED unit with a leading # "●", so `awk '{print $1}'` returns the bullet and not the unit name -- and a # sanitiser then reduces it to an empty string and drops the row. Measured on # 2026-09-22: talk.service sat in failed state and was silently skipped by this # very installer, i.e. the parser lost precisely the unit the tool exists for. # --plain suppresses the decoration so every row has the name in field 1. mapfile -t UNITS < <( { systemctl --user list-unit-files --type=service --state=enabled --plain --no-pager \ | awk '{print $1}' systemctl --user list-units --type=service --all --plain --no-pager \ | awk '{print $1}' } | grep '\.service$' | sort -u \ | grep -Ev "$SKIP_RE" ) for u in "${UNITS[@]}"; do d="${UD}/${u}.d" run mkdir -p "$d" run install -m644 onfailure-drop-in.conf "${d}/10-onfailure-althing.conf" echo " hooked ${u}" done run systemctl --user daemon-reload echo "== ${#UNITS[@]} unit(s) hooked =="