Phase 3 of the fresh-install spec: the parts of a run that depend on what the machine actually is. NVIDIA, Secure Boot, Fedora's preinstalled extras, firmware. Two of these looked like they would force a compromise, and neither did. sunhat opened an editor in the middle of its run so grub could be hand-corrected, and that single step is why walking away from an install did not work. The step existed to delete duplicated kernel arguments -- and grubby replaces an argument that already exists rather than appending a second copy, so the duplicates cannot accumulate and there is nothing to correct. The editor was load-bearing for a problem that a different tool does not have. MOK enrolment needs a password now and the same password at the next boot's blue screen, which reads like a prompt that has to happen mid-run. mokutil has --generate-hash and --hash-file for exactly this: the interview asks, hashes it on the spot, and records only the hash. The plaintext never reaches the answers file, the environment, or a command line, and the stage runs without asking. The stage runs last rather than fourth as the spec's table had it. The constraint was always "late" and fourth of eight is not late: enrolment arms a prompt for the next boot and firmware may want a reboot, so a machine that reboots out of this stage should already be completely configured. Every question names what was found -- the card, the packages actually installed -- and is not asked at all on a machine it would do nothing to. sunhat's debloat list no longer describes Fedora 44: totem became showtime and LibreOffice is not preinstalled, so the list is curated and a package that is not installed is never passed to dnf, which is what lets it outlive a release. This stage cannot be verified by running it. It installs a proprietary driver and queues a Secure Boot enrolment, and this machine is an AMD desktop. So every privileged command is stood in on PATH and the contract asserts which answer led to which call: that no answers means no commands, that a failed driver install is not followed by arguments and services for a driver that is not there, that the hash reaches mokutil through a file and never a command line, and that removal is offered only for packages that are installed. The contract was checked by breaking the stage three ways and confirming it caught each. It does not verify that akmod-nvidia builds, and says so where a reader would otherwise assume it did. The README's stage table listed three of seven stages; the interview and identity work never reached it. Corrected rather than extended, since a table that lists three of seven is worse than one that lists none. The Desktops section still describes a GNOME session nothing installs -- that is phase 5. Claude-Session: https://claude.ai/code/session_01NvgBuSWB5sE43yWmg21ozj
127 lines
6.0 KiB
Bash
Executable File
127 lines
6.0 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 INT 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 enrolment 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
|
|
|
|
# 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
|
|
|
|
# ── 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
|
|
|
|
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
|