#!/usr/bin/env bash # Telling somebody when a program crashes. # # On GNOME, ABRT says so. Under a hand-assembled Hyprland desktop nothing did, # and applications died silently, which is most of how "Linux is flaky" gets # earned. Fedora ships systemd-coredump by default, so the information was # already there and nobody was reading it. # # What must hold: # # 1. Once per program per session. This machine's portal backend crashes # between eleven and sixty times a day; a notification per crash would be # one every few minutes for something nobody can act on. The first is # news, the fortieth is why people turn notifications off. # 2. Another user's crash is not reported. It is not this session's business # and it would leak what they are running. # 3. The program is named by its executable, not by the kernel's comm field, # which is truncated to fifteen characters -- "panama-test-cra" for a # program called panama-test-crasher. # 4. It waits for the notification server. The crash most worth reporting is # the one that took the shell down with it. # 5. It follows from now rather than replaying the boot, so a session that # starts after a crash does not open with a notification about something # already lived through. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" watcher="$repo_dir/bin/panama-crash-watch" unit="$repo_dir/config/local/share/systemd/user/panama-crash-watch.service" autostart="$repo_dir/config/dot/hypr/autostart.lua" findings=() note() { findings+=("$1"); } [[ -x "$watcher" ]] || { printf 'crash watch contract: %s is not executable\n' "$watcher" >&2; exit 1; } work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT calls="$work/calls" stub="$work/bin" mkdir -p "$stub" # Two crashes of one program, one of another, and one belonging to somebody # else. journalctl is replaced by a stub that emits them and exits, so the # watcher's follow loop terminates instead of hanging the test. uid="$(id -u)" cat >"$stub/journalctl" <"$stub/notify-send" <>"$calls" STUB chmod +x "$stub/notify-send" # The bus is already up, so the wait loop falls straight through. cat >"$stub/busctl" <<'STUB' #!/usr/bin/env bash exit 0 STUB chmod +x "$stub/busctl" : >"$calls" PATH="$stub:$PATH" timeout 20 "$watcher" >/dev/null 2>&1 # ── 1. Once per program ───────────────────────────────────────────────────── crasher_notices="$(grep -c 'panama-test-crasher' "$calls" || true)" (( crasher_notices == 1 )) \ || note "a program that crashed twice produced $crasher_notices notifications; it must produce one per session" # A different program is still news. grep -q 'other-program' "$calls" \ || note 'a second, different program crashing was not reported' # ── 2. Somebody else's crash is not ours ──────────────────────────────────── grep -q 'someone-elses' "$calls" \ && note "another user's crash was reported, which leaks what they are running" # ── 3. The name is not the truncated one ──────────────────────────────────── grep -q 'panama-test-cra ' "$calls" \ && note 'the notification uses the truncated kernel comm field rather than the executable name' # ── 4 & 5. How it listens ─────────────────────────────────────────────────── grep -q 'org.freedesktop.Notifications' "$watcher" \ || note 'the watcher does not wait for the notification server, so a shell crash would report to nobody' grep -q -- '-f -n 0' "$watcher" \ || note 'the watcher replays the journal rather than following from now, so a session would open with old crashes' grep -q 'MESSAGE_ID=' "$watcher" \ || note 'the watcher matches on log text rather than the coredump message id' # ── Installed and started ─────────────────────────────────────────────────── [[ -r "$unit" ]] || note 'there is no user unit for the crash watcher' grep -q 'panama-crash-watch' "$autostart" \ || note 'nothing starts the crash watcher at login' grep -q 'PANAMA_PATH' "$unit" \ || note 'the unit hardcodes the repository path, so a clone elsewhere would not start' if (( ${#findings[@]} > 0 )); then printf 'crash watch contract: %d finding(s)\n' "${#findings[@]}" >&2 printf ' - %s\n' "${findings[@]}" >&2 exit 1 fi printf 'crash watch contract: PASS\n'