Two of Section F. Hooks are the pressure valve. "Can Panama also do X when the theme changes" is now a five-line file in ~/.config/panama/hooks rather than a fork, a feature request, or a patch somebody rebases forever. Each name takes a single file and a .d directory so several things can react without fighting over one, and a broken hook is reported and stepped over: somebody's script must never cost a theme change, an upgrade or a login. Wired at theme-set, post-upgrade and post-migrate. This is the thirty-line version of the plugin host the upstream ledger defers, and it has no API to keep stable beyond "we will run your script and tell you what happened". Testing it caught a real bug the reading would not have: run_one captured the script path but never shifted it off, so every hook got its own filename as $1 and the real arguments arrived one place late. A hook reading $1 as the colour scheme got a path. The crash watcher notices when a program dumps core and says so. Under GNOME, ABRT does this; here nothing did, and applications died silently, which is most of how "Linux is flaky" gets earned. Once per program per session is the entire design, not a nicety. This machine's portal backend crashes between eleven and sixty times a day, and 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. The health page keeps the running count. It waits for the notification server before reporting, because the crash most worth hearing about is the one that took the shell with it, and it names the executable rather than the kernel's comm field, which truncates at fifteen characters. Verified against real segfaults.
69 lines
2.4 KiB
Bash
Executable File
69 lines
2.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# The pressure valve.
|
|
#
|
|
# panama-hook theme-set dark orchid
|
|
#
|
|
# Runs ~/.config/panama/hooks/<name> and everything executable in
|
|
# ~/.config/panama/hooks/<name>.d/, in sorted order, with the hook's arguments.
|
|
#
|
|
# This exists so "can Panama also do X when the theme changes" is a five-line
|
|
# file somebody drops in a directory rather than a fork, a feature request, or
|
|
# a patch that has to be rebased forever. docs/UPSTREAM-INSPIRATION.md defers a
|
|
# plugin host as premature and still should: this is the thirty-line version
|
|
# that covers most of what people actually want from one, and it has no API to
|
|
# keep stable beyond "we will run your script and tell you what happened".
|
|
#
|
|
# A failing hook is reported and stepped over. Somebody's broken script must
|
|
# never break a theme change, an upgrade, or a login -- which is exactly what
|
|
# would happen if this used `set -e` and the caller did too.
|
|
#
|
|
# Hooks run synchronously, so a slow one delays whatever called it. That is
|
|
# deliberate: the alternative is a hook whose output arrives after the thing it
|
|
# was reacting to has already finished, which is harder to reason about than a
|
|
# pause.
|
|
|
|
set -uo pipefail
|
|
|
|
HOOK_DIR="${PANAMA_HOOK_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/panama/hooks}"
|
|
|
|
name="${1:-}"
|
|
if [[ -z "$name" ]]; then
|
|
echo 'usage: panama-hook <name> [args...]' >&2
|
|
exit 2
|
|
fi
|
|
shift
|
|
|
|
# A hook name reaches the filesystem, so it cannot be allowed to leave the
|
|
# directory. Callers are all in-repo today, which is exactly when this is
|
|
# cheap to add and easy to forget.
|
|
if [[ ! "$name" =~ ^[a-z][a-z0-9-]*$ ]]; then
|
|
echo "panama-hook: refusing hook name: $name" >&2
|
|
exit 2
|
|
fi
|
|
|
|
run_one() {
|
|
local script="$1"
|
|
# Shifted off before the arguments are forwarded, or every hook receives
|
|
# its own path as $1 and the real arguments arrive one place late.
|
|
shift
|
|
[[ -f "$script" && -x "$script" ]] || return 0
|
|
if ! "$script" "$@"; then
|
|
printf 'panama-hook: %s failed (%s); continuing\n' \
|
|
"$(basename "$script")" "$name" >&2
|
|
fi
|
|
}
|
|
|
|
# The single file first, then the .d directory in sorted order. Both are
|
|
# optional and having neither is the normal case.
|
|
run_one "$HOOK_DIR/$name" "$@"
|
|
|
|
if [[ -d "$HOOK_DIR/$name.d" ]]; then
|
|
while IFS= read -r script; do
|
|
[[ -n "$script" ]] || continue
|
|
run_one "$script" "$@"
|
|
done < <(find "$HOOK_DIR/$name.d" -maxdepth 1 -type f | sort)
|
|
fi
|
|
|
|
exit 0
|