Give an installed machine a way to catch up
./install only ever adds. It copies over /, links dotfiles, installs packages -- and has no way to say "remove that file", "disable that unit", "that symlink points nowhere now". So a machine set up months ago keeps whatever this repository has since decided was wrong, and the only thing that ever fixes it is somebody reading a commit message. With a curl installer in the README, that stopped being hypothetical. A migration is one script that performs one repair, exactly once, on the machines that need it. Named by the commit timestamp that authored it, so glob order is chronological without a sequence number two branches could both pick. Marked in ~/.local/state on success and only on success, so a repair that failed stays pending rather than being recorded as done and hidden forever. Ordered, and stopped at the first failure, because a later repair may assume an earlier one landed. A fresh install marks everything without running it, the way Migrations.qml stamps a pre-versioning settings file at its baseline. The first real one removes the dangling ~/.config/forge symlink left behind when the GNOME session was cut: link-dotfiles could link it but never unlink it. Verified both ways -- a no-op on a machine that never had it, an actual repair on one that did. Root work goes through panama-sudo --reason so the password prompt names the repair, and the contract fails any migration reaching for bare sudo.
This commit is contained in:
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# "Panama has repairs waiting for this machine."
|
||||
#
|
||||
# A migration that nobody knows about is a migration that never runs. This
|
||||
# checks once per session and, when work is pending, sends one notification
|
||||
# whose action opens a terminal running `panama migrate` -- so the repair is
|
||||
# always something the user chose, never something that happened to them.
|
||||
#
|
||||
# Waiting for the notification server first is not politeness. Quickshell owns
|
||||
# org.freedesktop.Notifications, and a shell that has not started yet would
|
||||
# swallow this silently -- which is precisely the session where a pending
|
||||
# migration is most likely to matter.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
PANAMA_PATH="${PANAMA_PATH:-$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)}"
|
||||
TERMINAL="${PANAMA_TERMINAL:-kitty}"
|
||||
|
||||
command -v notify-send >/dev/null 2>&1 || exit 1
|
||||
|
||||
count="$("$PANAMA_PATH/bin/panama-migrate" --pending)" || exit 1
|
||||
[[ -n "$count" ]] || exit 1
|
||||
|
||||
# Up to ~15s for the shell to claim the bus name. Longer than a healthy start
|
||||
# needs, short enough that a session without a shell gives up rather than
|
||||
# lingering.
|
||||
for _ in $(seq 1 30); do
|
||||
if busctl --user status org.freedesktop.Notifications >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
if (( count == 1 )); then
|
||||
body="One repair is waiting for this machine."
|
||||
else
|
||||
body="$count repairs are waiting for this machine."
|
||||
fi
|
||||
|
||||
# Critical so it waits to be read: a notification that expires while the user
|
||||
# is elsewhere has told nobody anything. The action is the whole point -- there
|
||||
# is no instruction to remember and nothing to type.
|
||||
action="$(notify-send --urgency=critical --icon=system-software-update \
|
||||
--app-name=Panama \
|
||||
--action=migrate="Apply now" --action=later="Later" \
|
||||
"Panama updates" "$body" 2>/dev/null)" || exit 0
|
||||
|
||||
[[ "$action" == "migrate" ]] || exit 0
|
||||
exec "$TERMINAL" --hold "$PANAMA_PATH/bin/panama-migrate" run
|
||||
Reference in New Issue
Block a user