Four installer bugs, all in the space between exit codes and intent:
- A flatpak-only extras category -- most of them -- died at the grep
that filters out its dnf half, because grep exits 1 on zero matches
and set -e read that as failure. sed deletes lines without editorial
comment. The extras contract now runs a flatpak-only category under
the installer's own strict options so this stays fixed.
- A rate-limited GitHub API call aborted the whole package stage while
resolving the RustDesk URL, even though the empty-result fallback was
sitting right below it. The pipeline is now guarded so the fallback
is reachable.
- Ctrl-C did not stop the install: the INT trap ran cleanup and bash
carried on with the remaining stages, MOK enrollment and firmware
included. INT and TERM now exit explicitly; cleanup rides EXIT.
- change-settings and link-dotfiles ran without set -e, so a failed
copy over / or a failed symlink fell through to guarded no-ops and
the stage reported success. Turning strictness on immediately caught
what it had been hiding: link-dotfiles never created ~/.config, so on
a truly fresh HOME every symlink was failing silently.
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
sunhat's failure mode was a question twenty minutes into a run. Walking away
from an install meant coming back to a prompt that had been waiting an hour.
So the questions move to the front. A new interview stage asks what Panama needs
to be told -- hostname, git identity, whether to sign in to GitHub, whether to
make an SSH key -- shows the answers back, and asks once to proceed. After that
nothing asks again. gum is bootstrapped before it runs, because the interview is
built on gum and gum arrives with a stage that has not run yet.
Answers reach the stages through a mktemp file that install sources and the
existing trap deletes, since a child process cannot export into its siblings.
They are not remembered between runs: there is no state file to go stale, and
one of the answers is an email address.
The interview asks only what a stage in this repository actually consumes.
Extras, hardware and debloat questions arrive with the stages that act on them
-- a prompt whose answer nothing reads is a control that lies. The new contract
pins that in both directions, and four deliberate mutations confirmed it catches
a question nobody reads, a stage reading something nobody asks, an answers file
left on disk, and a declined interview that fails to stop the run.
The run now ends with panama-doctor, because a failed-stage count says nothing
about a service that did not start. It never changes the exit code: on a fresh
machine, unconfigured is the honest answer, not a failure.
espanso and oh-my-posh stop being exceptions -- Terra packages espanso-wayland
and Fedora packages oh-my-posh, so the curl installer is gone. bun is now the
only remaining one.
Claude-Session: https://claude.ai/code/session_01Q84axqUE5inJhf5Jz9CFy1