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.
160 lines
7.7 KiB
Bash
Executable File
160 lines
7.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Panama's installer. Safe to re-run: every stage is idempotent, and this is
|
|
# also the upgrade path.
|
|
|
|
set -uo pipefail
|
|
|
|
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
|
|
source "$PANAMA_PATH/bin/ascii"
|
|
|
|
# ── The interview ────────────────────────────────────────────────────────────
|
|
#
|
|
# Everything Panama needs to be told is asked here, before a single package is
|
|
# installed, and nothing asks again afterwards. That is the whole bargain: the
|
|
# rest of the run takes twenty minutes and needs nobody watching it.
|
|
#
|
|
# gum is bootstrapped first because the interview is built on it and it cannot
|
|
# install itself -- it is declared in initial-packages, which install-packages
|
|
# installs, which runs after this. One small dnf call buys a real interface for
|
|
# the only part of the install a person actually interacts with.
|
|
if ! command -v gum >/dev/null 2>&1; then
|
|
echo "Installing gum, which the setup questions are built on"
|
|
sudo dnf install -y gum >/dev/null || {
|
|
echo "Could not install gum, so the setup questions cannot be asked." >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
# ── Keep the machine awake for the duration ──────────────────────────────────
|
|
# Package installation takes long enough to hit an idle lock, and being locked
|
|
# out mid-transaction is unpleasant. Restored on every exit path, including
|
|
# failure and Ctrl-C, so an interrupted install does not leave the screen
|
|
# permanently awake.
|
|
cleanup() {
|
|
gsettings set org.gnome.desktop.screensaver lock-enabled true 2>/dev/null || true
|
|
gsettings set org.gnome.desktop.session idle-delay 300 2>/dev/null || true
|
|
# Deleted on every exit path, including Ctrl-C. The answers are transient by
|
|
# design, and one of them is an email address.
|
|
[[ -n "${PANAMA_ANSWERS:-}" ]] && rm -f "$PANAMA_ANSWERS"
|
|
}
|
|
trap cleanup EXIT
|
|
# A bare `trap cleanup INT` is not an abort: bash runs the handler and then
|
|
# carries on with the script, so Ctrl-C would kill only the current stage and
|
|
# the remaining ones -- MOK enrollment, firmware -- would still run. Exit
|
|
# explicitly instead; the EXIT trap above does the actual cleanup.
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
|
|
gsettings set org.gnome.desktop.screensaver lock-enabled false 2>/dev/null || true
|
|
gsettings set org.gnome.desktop.session idle-delay 0 2>/dev/null || true
|
|
|
|
# ── Stages ───────────────────────────────────────────────────────────────────
|
|
# Each runs in its own process so strict-shell options and helper variables stay
|
|
# local to the script that owns them. A failing stage is reported and the rest
|
|
# still run: a missing optional package should not stop the dotfiles being
|
|
# linked. The summary at the end is what decides whether the install worked,
|
|
# because a failure scrolled past twenty minutes ago is a failure nobody saw.
|
|
#
|
|
# Explicit order, not glob order: change-settings runs `vicinae theme set`,
|
|
# which needs both vicinae itself (installed by install-packages) and the
|
|
# theme files it selects among (symlinked into place by link-dotfiles);
|
|
# setup-identity needs the gh and git-all that install-packages provides; and
|
|
# install-hardware is last because MOK enrollment arms a prompt consumed at the
|
|
# next boot and a firmware update may ask for a reboot -- a machine that reboots
|
|
# out of the final stage has already been completely configured. New scripts
|
|
# must be added here explicitly, or they will not run at all.
|
|
|
|
# The interview is not in that list, because it is the one stage whose output the
|
|
# installer reads back -- and because declining it must stop everything rather
|
|
# than be recorded as one failure among several.
|
|
#
|
|
# The answers live for exactly one run. There is no state file to go stale and
|
|
# nothing personal reaches a durable path, which is what keeps this repository
|
|
# something somebody else could clone. Created here rather than earlier so the
|
|
# trap that deletes it is already armed before the file exists.
|
|
PANAMA_ANSWERS="$(mktemp -t panama-answers.XXXXXX)"
|
|
export PANAMA_ANSWERS
|
|
|
|
if ! "$PANAMA_PATH/setup/scripts/interview"; then
|
|
exit 1
|
|
fi
|
|
# shellcheck source=/dev/null
|
|
source "$PANAMA_ANSWERS"
|
|
export PANAMA_HOSTNAME PANAMA_GIT_NAME PANAMA_GIT_EMAIL PANAMA_GIT_EDITOR \
|
|
PANAMA_GH_LOGIN PANAMA_SSH_KEY PANAMA_NVIDIA PANAMA_MOK_HASH \
|
|
PANAMA_DEBLOAT PANAMA_FIRMWARE PANAMA_EXTRAS
|
|
|
|
# Applied here rather than in a stage, and applied early: it needs sudo, and
|
|
# sudo is warm right now. At the end of a long unattended run the timestamp has
|
|
# expired, and a password prompt then is exactly the interruption the interview
|
|
# exists to prevent.
|
|
if [[ -n "${PANAMA_HOSTNAME:-}" ]]; then
|
|
sudo hostnamectl set-hostname "$PANAMA_HOSTNAME"
|
|
echo "Hostname set to: $(hostname)"
|
|
fi
|
|
|
|
STAGES=(install-packages link-dotfiles change-settings link-vicinae-scripts setup-identity install-hardware)
|
|
failed=()
|
|
for stage in "${STAGES[@]}"; do
|
|
script="$PANAMA_PATH/setup/scripts/$stage"
|
|
[[ -x "$script" ]] || continue
|
|
printf '\n=== %s ===\n' "$stage"
|
|
if ! "$script"; then
|
|
failed+=("$stage")
|
|
printf '!!! %s failed\n' "$stage" >&2
|
|
fi
|
|
done
|
|
|
|
# ── Migrations ───────────────────────────────────────────────────────────────
|
|
#
|
|
# Repairs for machines that installed an older Panama: removing a file this
|
|
# repository stopped shipping, disabling a unit it stopped wanting. The
|
|
# installer itself cannot do any of that, because it only ever adds.
|
|
#
|
|
# A machine that has never seen migrations before is one of two things, and
|
|
# the difference matters. If it has no marker directory at all it was just
|
|
# built from THIS checkout, so every repair those migrations describe is
|
|
# already true of it -- they are marked applied without running, exactly as
|
|
# Migrations.qml stamps a pre-versioning settings file at its baseline rather
|
|
# than replaying upgrades it never needed. Otherwise the pending ones run.
|
|
migrate="$PANAMA_PATH/bin/panama-migrate"
|
|
if [[ -x "$migrate" ]]; then
|
|
printf '\n=== migrations ===\n'
|
|
if [[ -d "${XDG_STATE_HOME:-$HOME/.local/state}/panama/migrations" ]]; then
|
|
"$migrate" run || failed+=(migrations)
|
|
else
|
|
"$migrate" --baseline || true
|
|
fi
|
|
fi
|
|
|
|
# ── Did it actually work? ────────────────────────────────────────────────────
|
|
#
|
|
# A failed-stage count only reports what exited non-zero. It says nothing about a
|
|
# service that did not start or a font that did not land, and those are the
|
|
# failures that survive an install unnoticed. Doctor answers the question the
|
|
# stage list cannot.
|
|
#
|
|
# It never changes the exit code. On a fresh machine it legitimately reports
|
|
# things as unconfigured -- no Home Assistant token yet, Nextcloud not signed in
|
|
# -- and failing an install over those would be crying wolf.
|
|
doctor="$PANAMA_PATH/config/dot/quickshell/scripts/panama-doctor"
|
|
if [[ -x "$doctor" ]]; then
|
|
printf '\n=== health ===\n'
|
|
"$doctor" --summary || true
|
|
fi
|
|
|
|
# Whatever this particular machine wants doing that Panama should not carry for
|
|
# everyone. Runs last, after every stage, migrations and the health summary.
|
|
hook="$PANAMA_PATH/bin/panama-hook"
|
|
[[ -x "$hook" ]] && "$hook" post-upgrade || true
|
|
|
|
printf '\n'
|
|
if (( ${#failed[@]} == 0 )); then
|
|
echo "Panama installed. Log out and choose the Hyprland session to start it."
|
|
else
|
|
printf 'Panama installed with %d failed stage(s): %s\n' "${#failed[@]}" "${failed[*]}" >&2
|
|
printf 'Re-running ./install is safe and will retry them.\n' >&2
|
|
exit 1
|
|
fi
|