diff --git a/install b/install index 3c108ea..c1d3469 100755 --- a/install +++ b/install @@ -38,7 +38,13 @@ cleanup() { # design, and one of them is an email address. [[ -n "${PANAMA_ANSWERS:-}" ]] && rm -f "$PANAMA_ANSWERS" } -trap cleanup EXIT INT TERM +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 diff --git a/setup/scripts/install-packages b/setup/scripts/install-packages index 58b1a7a..395d92f 100755 --- a/setup/scripts/install-packages +++ b/setup/scripts/install-packages @@ -214,9 +214,12 @@ if rpm -q rustdesk >/dev/null 2>&1; then log "RustDesk already installed" else log "Resolving the latest RustDesk release..." + # `|| true` because a failed curl -- unauthenticated GitHub API calls get + # rate-limited -- would otherwise trip set -e and kill the stage before the + # empty-result fallback below could do its job. rustdesk_url="$(curl -fsSL https://api.github.com/repos/rustdesk/rustdesk/releases/latest 2>/dev/null \ | jq -r '.assets[].browser_download_url | select(test("x86_64\\.rpm$")) | select(test("suse") | not)' \ - | head -1)" + | head -1 || true)" if [[ -n "$rustdesk_url" ]]; then log "Installing RustDesk from $rustdesk_url" # The RPM ships rustdesk.service already enabled, which is what provides @@ -271,7 +274,9 @@ install_extra_category() { name="$(basename "$file")" local dnf_packages flatpak_ids - dnf_packages=$(catalog_all_targets "$file" | grep -v '^flatpak:' | tr "\n" " ") + # sed rather than grep -v: most categories are flatpak-only, and grep exits 1 + # when it selects nothing, which set -e above turns into a dead stage. + dnf_packages=$(catalog_all_targets "$file" | sed '/^flatpak:/d' | tr "\n" " ") flatpak_ids=$(catalog_all_targets "$file" | sed -n 's/^flatpak://p' | tr "\n" " ") if [[ -n "${dnf_packages// /}" ]]; then diff --git a/setup/scripts/link-dotfiles b/setup/scripts/link-dotfiles index 5cc0d69..2722fec 100755 --- a/setup/scripts/link-dotfiles +++ b/setup/scripts/link-dotfiles @@ -1,5 +1,10 @@ #!/usr/bin/env bash +# Strict: a failed mv or ln here used to fall through and exit 0, so a machine +# could end up half-linked while the installer's summary reported the stage as +# fine. Anything genuinely optional below carries its own `|| true`. +set -euo pipefail + # --- Helper functions --- log() { echo -e "\033[1;34m[INFO]\033[0m $*"; } @@ -10,8 +15,10 @@ PANAMA_DOT="$PANAMA_PATH/config/dot" PANAMA_OLD="$PANAMA_PATH/config/old" CONFIG="$HOME/.config" -# Make backup folder if it doesn't exist -mkdir -p "$PANAMA_OLD" +# Make backup folder if it doesn't exist -- and ~/.config itself, which a +# truly fresh HOME does not have yet. Before set -e above, its absence made +# every symlink below fail silently while the stage still reported success. +mkdir -p "$PANAMA_OLD" "$CONFIG" # --- Bashrc --- echo -e "\n--- Replacing .bashrc ---" diff --git a/tests/setup/extras-contract b/tests/setup/extras-contract index 5715e90..f071b62 100755 --- a/tests/setup/extras-contract +++ b/tests/setup/extras-contract @@ -115,6 +115,31 @@ if grep 'flatpak install' <<<"$recorded" | grep -q 'from-dnf'; then note 'a dnf package is passed to flatpak' fi +# A flatpak-only category -- which most of the real ones are -- must survive +# the installer's own strict options. Filtering the dnf half with `grep -v` +# once left an exit 1 for zero matches, and set -e killed the stage before its +# flatpak half ran. Run under those options, not the contract's laxer ones; +# the status is captured rather than `||`-guarded because a condition context +# would switch errexit off inside the subshell and hide the very failure this +# pins. +: >"$calls" +flatpak_only="$work/flatpak-only" +printf 'flatpak:org.example.OnlyFlatpak\n' >"$flatpak_only" +( + set -euo pipefail + PATH="$stub_dir:$PATH" + log() { :; } + source "$catalog" + eval "$filter" + eval "$loop" + install_extra_category "$flatpak_only" +) +flatpak_only_status=$? +(( flatpak_only_status == 0 )) \ + || note 'a flatpak-only category aborts the installer under set -euo pipefail' +grep -q 'flatpak install -y flathub org.example.OnlyFlatpak' <<<"$(cat "$calls" 2>/dev/null)" \ + || note 'a flatpak-only category installs nothing' + # Choosing nothing installs nothing. : >"$calls" ( diff --git a/tests/setup/interview-contract b/tests/setup/interview-contract index 5f20d11..4f01925 100755 --- a/tests/setup/interview-contract +++ b/tests/setup/interview-contract @@ -52,8 +52,15 @@ done <<<"$consumed" # ── 3. Nothing is left behind ──────────────────────────────────────────────── -grep -q 'trap cleanup EXIT INT TERM' "$install_script" \ - || note 'install does not arm a cleanup trap on EXIT INT TERM' +# Cleanup rides the EXIT trap; INT and TERM must exit explicitly, because a +# trap handler that merely cleans up lets bash carry on with the remaining +# stages after a Ctrl-C -- MOK enrollment and firmware included. +grep -q 'trap cleanup EXIT' "$install_script" \ + || note 'install does not arm a cleanup trap on EXIT' +grep -qE "trap 'exit [0-9]+' INT" "$install_script" \ + || note 'install does not exit on SIGINT, so Ctrl-C would keep installing' +grep -qE "trap 'exit [0-9]+' TERM" "$install_script" \ + || note 'install does not exit on SIGTERM, so a kill would keep installing' grep -q 'rm -f "$PANAMA_ANSWERS"' "$install_script" \ || note 'the cleanup trap does not delete the answers file' grep -qE 'mktemp' "$install_script" \