Files
Panama/tests/setup/desktop-first-contract
T
Gabriel Brown 669a228286 Install the desktop before anything allowed to fail, and let Terra be installed twice
Two bugs, one story: ./install on a machine that had been half set up before
produced no Hyprland at all, and said so in one line among twenty minutes of
scrollback.

Terra bootstraps itself with --repofrompath, which defines a throwaway repo id
just long enough to install terra-release. Run it again on a machine that
already has terra-release and dnf5 refuses the whole transaction -- the
throwaway id collides with the real one. That step sits above everything, so
set -e ended the stage before a single package was considered. It is skipped
now when terra-release is already installed.

The rest is the reason one failed repo cost the desktop. Hyprland was installed
near the bottom of the stage, below a codec swap, two group updates and a
GStreamer glob, any one of which can fail for reasons outside this repository.
It now installs directly after the packages it needs and before anything
optional, and everything fragile below it runs through a soft helper that logs
and continues rather than ending the run. What was stepped over is listed at
the end, because tolerating a failure only beats aborting on it if somebody is
told.

A missing Hyprland is still fatal, and now says so in words.

Also removes the leftover disabled solopasha/hyprland COPR, which would mix
with lionheartp's the moment anyone enabled it while debugging.

Fixes the usage widget reading 1500%: the endpoint reports percentages, not
0..1 fractions. Clamped as well, and the widget answers a click now -- it set
interactive:false, which disables the mouse area its own handler needed.
2026-08-22 09:36:54 -04:00

111 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# The desktop installs before anything that is allowed to fail.
#
# install-packages runs under `set -euo pipefail`, and the Hyprland block used
# to sit near the bottom of it, below a codec swap, two group updates and a
# GStreamer glob. Any one of those exiting non-zero ended the stage where it
# stood. On a machine whose spin never shipped ffmpeg-free, the swap failed, the
# stage died, and the install finished reporting one red line among twenty
# minutes of scrollback -- with no Hyprland on the disk at all.
#
# The rule that prevents it: anything that can fail for a reason outside this
# repository goes BELOW the desktop, and goes through `soft`. What is left above
# the desktop is only what the desktop needs.
#
# This pins the order, not the individual commands, because the next fragile
# thing somebody adds will not be a codec swap.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
installer="$repo_dir/setup/scripts/install-packages"
findings=()
note() { findings+=("$1"); }
[[ -x "$installer" ]] || { printf 'desktop first contract: %s is not executable\n' "$installer" >&2; exit 1; }
line_of() { grep -n "$1" "$installer" | head -1 | cut -d: -f1; }
hyprland_at="$(line_of '^HYPR_FILE=')"
[[ -n "$hyprland_at" ]] || { printf 'desktop first contract: no Hyprland block found\n' >&2; exit 1; }
# ── Nothing fragile above the desktop ───────────────────────────────────────
#
# Named individually rather than by pattern: each is a command whose failure is
# survivable, and each one above the Hyprland block is a machine that boots to
# nothing.
for fragile in 'dnf swap' 'groupupdate' 'group upgrade' 'gstreamer1-plugins' \
'flatpak install' 'nvm install' 'curl -fsSL'; do
while read -r at; do
(( at < hyprland_at )) \
&& note "'$fragile' runs at line $at, above the desktop at line $hyprland_at"
done < <(grep -n -- "$fragile" "$installer" | grep -v '^\s*#' | cut -d: -f1)
done
# ── Everything fragile is actually tolerated ────────────────────────────────
#
# Being below the desktop is only half of it. A bare `dnf swap` below the
# Hyprland block still kills every step after it -- the flatpaks, the extras
# somebody explicitly chose.
# Comments dropped and backslash continuations joined, so a `soft` invocation
# wrapped across three lines reads as the one command it is.
uncommented() { grep -vE '^\s*#' "$installer" | sed -e :a -e '/\\$/N; s/\\\n\s*/ /; ta'; }
while read -r command; do
uncommented | grep -q "soft .*$command" \
|| note "'$command' runs without soft, so its failure still ends the stage"
done <<'FRAGILE'
dnf swap -y 'ffmpeg-free'
dnf swap -y mesa-va-drivers
groupupdate
group upgrade
gstreamer1-plugins
flatpak install -y flathub $FLATPAK_PACKAGES
FRAGILE
grep -q '^soft()' "$installer" \
|| note 'install-packages defines no soft helper, so nothing can be tolerated deliberately'
# ── The desktop failing is still fatal, and still said out loud ─────────────
#
# The inverse mistake: making everything survivable turns a machine with no
# desktop into a run that reports success.
python3 - "$installer" "$hyprland_at" <<'PY' || note 'a missing Hyprland does not stop the stage'
import sys
lines = open(sys.argv[1], encoding="utf-8").read().splitlines()
start = int(sys.argv[2])
after = "\n".join(lines[start:start + 40])
if "rpm -q hyprland" not in after or "exit 1" not in after:
raise SystemExit(1)
PY
uncommented | grep -q 'soft .*HYPR_PACKAGES' \
&& note 'the Hyprland install is tolerated, so a machine with no desktop reports success'
# ── Soft failures are reported ──────────────────────────────────────────────
grep -q 'softly_failed' "$installer" \
|| note 'nothing collects what was stepped over, so a tolerated failure is a silent one'
python3 - "$installer" <<'PY' || note 'the list of stepped-over steps is never printed'
import sys
text = open(sys.argv[1], encoding="utf-8").read()
tail = text[text.rindex("softly_failed"):]
if "printf" not in tail and "log" not in tail:
raise SystemExit(1)
PY
if (( ${#findings[@]} > 0 )); then
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
printf 'desktop first contract: %d finding(s)\n' "${#findings[@]}" >&2
printf ' - %s\n' "${findings[@]}" >&2
exit 1
fi
printf 'desktop first contract: PASS (desktop at line %s, everything fragile below it)\n' "$hyprland_at"