Files
Panama/setup/scripts/install-hardware
T
Gabriel Brown 153554b5df Make ./install something you could hand a stranger
The audit's third tier: everything between this installer and a fresh machine
it has never met.

The one path that could cost a person their display: the interview probes
Secure Boot with mokutil, which install-packages had not installed yet, so on
a minimal base the MOK question silently never fired -- and install-hardware
still installed akmod-nvidia and blacklisted nouveau, arming a reboot into an
unloadable driver with its fallback disabled. The probe tools (pciutils,
mokutil, fwupd) now bootstrap beside gum, and install-hardware re-checks
Secure Boot for itself and refuses the driver rather than the display.

Secrets leave the checkout: the personal environment moves to
~/.config/panama/env at mode 600 by migration, and .bashrc sources it with a
permission check that quietly re-tightens drift. change-settings no longer
overwrites /etc/dnf/dnf.conf -- two performance keys are set additively, the
defaultyes=True that made every `dnf remove` treat Enter as yes is gone, and
a migration strips it from machines that already received it.

Package installation survives the world changing: the initial and desktop
lists run with --skip-unavailable and a report_missing pass that names what
was skipped (resolved through --whatprovides, so capability names like awk
do not cry wolf); the openh264, appstream and core-group extras go through
soft; RustDesk resolves its RPM for the machine's own architecture; and the
Claude Desktop repository script is fetched to a kept file and run, never
piped from the network into root.

The hardware predicates stop guessing: a wireless mouse's scope=Device
battery no longer turns a tower into a laptop, USB-PD-only machines read
their power state from the battery's own status instead of being permanently
"on AC", the lid falls back to logind's LidClosed where ACPI is silent, and
charge limits reach every pack of a two-battery machine in one authorization
-- with the reported percentage summed across packs.

And the parsers stop assuming this machine: snapper is read through
--machine-readable csv with named columns instead of a localized box-drawing
table, and reports whether snapshots are even possible so ext4 and
unconfigured-btrfs stop looking identical; fprintd is parsed under LC_ALL=C;
the hypridle drop-in resolves the binary it points at; the recorder's render
node became an "auto" token resolved at record time; update-grub writes the
config its firmware actually boots; the nvm prompt hook and the SSH tmux
takeover are guarded; hipblas and rocm-opencl move to an opt-in gpu-compute
category; and the two interactive python tools' libraries are declared.

Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
2026-08-23 12:10:03 -04:00

150 lines
7.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# The parts of an install that depend on what the machine actually is: the
# NVIDIA driver, the machine owner key that lets it load under Secure Boot,
# Fedora's preinstalled extras, and firmware.
#
# Runs last. MOK enrollment arms a prompt consumed at the next boot and firmware
# updates can ask for a reboot, so neither belongs in front of the package work
# or the dotfiles -- a machine that reboots out of this stage has already been
# fully configured.
#
# Nothing here decides anything. Every branch is an answer the interview
# collected before the run began, and an absent answer means no, which is what
# makes this safe to re-run by hand while repairing one piece of a machine.
#
# sunhat's version of this opened an editor in the middle of the run so grub
# could be hand-corrected. That is the exact failure this repository exists to
# avoid, and it is unnecessary: grubby replaces an argument that already exists
# rather than appending a second copy, so the duplicates that had to be cleaned
# up by hand cannot accumulate in the first place.
set -uo pipefail
log() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*" >&2; }
# Fedora ships these and Panama uses none of them. Named here rather than in the
# interview so there is one list: the interview asks for it with --debloat-list
# to name what it is about to remove, and this stage removes it. A package that
# is not installed is skipped rather than passed to dnf, so the list can outlive
# a Fedora release -- totem left in Fedora 43 and the list should not start
# failing because of it.
DEBLOAT=(gnome-contacts gnome-tour gnome-maps showtime)
if [[ "${1:-}" == "--debloat-list" ]]; then
printf '%s\n' "${DEBLOAT[@]}"
exit 0
fi
# ── NVIDIA ───────────────────────────────────────────────────────────────────
if [[ "${PANAMA_NVIDIA:-no}" == yes ]]; then
# The interview asks about MOK enrollment only when mokutil was present to
# see Secure Boot at all. Re-check here rather than trusting that the
# question was ever asked: installing akmod-nvidia and blacklisting
# nouveau under Secure Boot with no key to enroll produces a machine that
# reboots into an unloadable driver with its fallback disabled -- the one
# failure in this installer that costs a person their display.
if mokutil --sb-state 2>/dev/null | grep -qi 'secureboot enabled' \
&& [[ -z "${PANAMA_MOK_HASH:-}" ]]; then
warn "Secure Boot is on and no MOK enrollment was prepared; refusing to install"
warn "the NVIDIA driver, which could not load. Re-run ./install and answer the"
warn "Secure Boot question, or disable Secure Boot first."
else
log "Installing the NVIDIA driver"
if sudo dnf install -y akmod-nvidia xorg-x11-drv-nvidia-cuda; then
# nouveau has to be out of the way before the kernel would otherwise
# bind it, which is why these are kernel arguments and not a modprobe
# drop-in. modeset=1 is what makes the Wayland session work at all.
if command -v grubby >/dev/null 2>&1; then
sudo grubby --update-kernel=ALL \
--args="rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvidia-drm.modeset=1"
log "Kernel arguments set for every installed kernel"
else
warn "grubby is not installed; nouveau was not blacklisted"
fi
# Suspend and resume are where a proprietary driver most visibly fails.
# These units save and restore VRAM across it.
sudo systemctl enable nvidia-hibernate.service nvidia-suspend.service \
nvidia-resume.service nvidia-powerd.service
log "NVIDIA power management services enabled"
else
warn "The NVIDIA driver did not install; skipping its kernel arguments and services"
fi
fi
fi
# ── Secure Boot ──────────────────────────────────────────────────────────────
#
# akmods signs the modules it builds with a key it generates on installation.
# Under Secure Boot that key means nothing until it is enrolled, and enrollment
# is deliberately a thing only somebody at the physical machine can complete:
# the request is queued here, and the next boot shows a blue screen asking for
# the password before it will trust the key.
#
# The password itself never reaches this stage. The interview hashed it and
# recorded the hash, so nothing readable is passed on a command line or left in
# this process's environment -- which is the whole reason mokutil has
# --generate-hash and --hash-file.
mok_hash="${PANAMA_MOK_HASH:-}"
if [[ -n "$mok_hash" ]]; then
# Overridable so the contract can exercise this against a certificate it is
# allowed to create. Nothing else sets it.
cert="${PANAMA_MOK_CERT:-/etc/pki/akmods/certs/public_key.der}"
if [[ ! -r "$cert" ]]; then
warn "No akmods certificate at $cert, so there is no key to enroll"
elif mokutil --test-key "$cert" 2>/dev/null | grep -q 'already enrolled'; then
log "The akmods key is already enrolled"
else
hash_file="$(mktemp -t panama-mok.XXXXXX)"
chmod 600 "$hash_file"
printf '%s\n' "$mok_hash" >"$hash_file"
if sudo mokutil --import "$cert" --hash-file "$hash_file"; then
log "Key enrollment requested"
log "At the next boot, choose 'Enroll MOK' and enter the password you gave the installer"
else
warn "Key enrollment failed; the NVIDIA module will not load until it is enrolled"
fi
rm -f "$hash_file"
fi
fi
# ── Fedora's preinstalled extras ─────────────────────────────────────────────
if [[ "${PANAMA_DEBLOAT:-no}" == yes ]]; then
present=()
for package in "${DEBLOAT[@]}"; do
rpm -q "$package" >/dev/null 2>&1 && present+=("$package")
done
if (( ${#present[@]} > 0 )); then
log "Removing ${present[*]}"
sudo dnf remove -y "${present[@]}" >/dev/null \
|| warn "Some packages could not be removed"
else
log "None of Fedora's extras are installed"
fi
fi
# ── Firmware ─────────────────────────────────────────────────────────────────
#
# Panama's Updates page covers this from then on; the installer covers the
# first run. --no-reboot-check because deciding to reboot is not this stage's
# call to make in the middle of an unattended install.
if [[ "${PANAMA_FIRMWARE:-no}" == yes ]]; then
if command -v fwupdmgr >/dev/null 2>&1; then
log "Refreshing firmware metadata"
sudo fwupdmgr refresh --force >/dev/null 2>&1
log "Applying firmware updates"
sudo fwupdmgr update -y --no-reboot-check \
|| log "No firmware updates were applied"
else
warn "fwupdmgr is not installed; skipping firmware"
fi
fi