From 2f9b2a54e1366289e343b4722dd02f51c5c2bc30 Mon Sep 17 00:00:00 2001 From: Rick Peters Date: Thu, 24 Sep 2026 10:22:15 +0200 Subject: [PATCH 1/9] feat: Opt-in BIOS update for the Steam Machine - Menu item (Fremont only, never preselected) with the current and newest BIOS version. - Two warnings and two confirmations (y/N, then typing UPDATE); checksum-verified download of Valve's fremont-hw-support; installed with fwupd, written at the next restart. - Menu actions: one-off items that are never preselected, re-applied or listed as on/off. --- AGENTS.md | 7 +++ CHANGELOG.md | 8 ++- README.md | 23 ++++---- lib/bios.sh | 121 ++++++++++++++++++++++++++++++++++++++++ lib/menu.sh | 31 +++++++--- setup-gamescope-boot.sh | 19 ++++++- 6 files changed, 187 insertions(+), 22 deletions(-) create mode 100644 lib/bios.sh diff --git a/AGENTS.md b/AGENTS.md index 80da0f5..be4a747 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -132,6 +132,13 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine and `ensure-kernel-headers.service` installs missing ones at boot (a pacman hook can't run pacman), which triggers DKMS's install hook. The module creates `/sys/class/leds/valve-leds*`. +- `bios` is an *action* (`ACTIONS` in `lib/menu.sh`), not an on/off + component: never preselected (not even on a first run), never re-applied + by `a`, not listed in the state overview, and `bios_status` is always off. + Keep both confirmations (y/N, then typing `UPDATE`) and the warnings; the + firmware comes from the newest `holo-X.Y` repo (`.files` db names the + `.cab`, `.db` gives the SHA-256), and fwupd itself refuses non-Fremont + hardware. It can only be tested up to fwupd's refusal in the VM. - `Relogin=true` means a gamescope that fails to start is relaunched in a tight loop; keep that in mind when changing session handling. diff --git a/CHANGELOG.md b/CHANGELOG.md index 39bb60e..b159f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,10 +52,16 @@ Machine LED driver works on every installed kernel and survives kernel updates. - Install with `.../releases/latest/download/setup-gamescope-boot.sh`, which always points to the newest release. - `1604ed1` **docs: Versioning and release rules in AGENTS.md** -- **ci: The latest tag follows the newest version tag** +- `6afef78` **ci: The latest tag follows the newest version tag** - When a new version is released, the `latest` tag and release move to it (bundle replaced), so the older `.../releases/download/latest/...` URL also always gives the newest version. +- **feat: Opt-in BIOS update for the Steam Machine** + - New menu item (Steam Machine only, never ticked by default) showing the + current BIOS version and the newest from Valve's `fremont-hw-support`. + - Two large warnings and two confirmations (y/N, then typing `UPDATE`), + checksum-verified download, installed with fwupd; the wizard then offers + the restart that writes it, with a warning to keep the power on. ## 0.6.2 - 2026-09-23 diff --git a/README.md b/README.md index 215d969..eb1b65c 100644 --- a/README.md +++ b/README.md @@ -283,17 +283,19 @@ cat /var/lib/dkms/leds-valve-dkms/0.1/build/make.log journalctl --user -b | grep -i led ``` -**BIOS updates** are not part of the wizard. Valve ships the Steam Machine -BIOS as `F7F0108.cab` in its `fremont-hw-support` package for fwupd. To -install it by hand (keep the machine on mains power and don't interrupt it): +**BIOS updates** (opt-in, never ticked by default). On a Steam Machine the menu +has an **Update BIOS** item that shows the current BIOS version and the newest +one Valve ships (`F7F0108.cab` in its `fremont-hw-support` package, looked up +on Valve's SteamOS mirror). Ticking it shows a large warning, asks for +confirmation, shows the warning again and only continues when you type +`UPDATE`. It then downloads the package (checksum verified) and hands the +firmware to fwupd; the BIOS is written during the next restart. -```bash -sudo dmidecode -s bios-version # current version -sudo pacman -S fwupd -curl -LO https://steamdeck-packages.steamos.cloud/archlinux-mirror/holo-3.9/os/x86_64/fremont-hw-support-20260807.1-1-any.pkg.tar.zst -mkdir fhw && tar -I zstd -xf fremont-hw-support-*.pkg.tar.zst -C fhw -sudo fwupdmgr install fhw/usr/share/fwupd/remotes.d/fremont/firmware/F7F0108.cab -``` +**At your own risk:** a failed or interrupted BIOS update can leave the machine +unable to start. Keep it on mains power, and never turn off the power, unplug +it or press the power button while it updates, including during the restart +afterwards; the screen can stay black for several minutes. fwupd refuses the +file on anything that isn't a Steam Machine. ### Manual session control @@ -328,6 +330,7 @@ gamescope-session, ...) stay installed. | `lib/vapor-theme.sh` | SteamOS theme: installs and switches to `cachyos-vapor` | | `lib/steamos-extras.sh` | SteamOS desktop extras from Valve's package (Add to Steam, Nested Desktop, icon, keyboard rule, KWallet) | | `lib/single-user.sh` | Single user mode: no lock screen, user switching or log out | +| `lib/bios.sh` | Update BIOS (Steam Machine, opt-in): current/newest version, double confirmation, fwupd | | `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager | | `.github/tools/bundle.sh` | Builds the single-file version (`dist/setup-gamescope-boot.sh`) | | `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` | diff --git a/lib/bios.sh b/lib/bios.sh new file mode 100644 index 0000000..44e25d5 --- /dev/null +++ b/lib/bios.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# "Update BIOS" menu item, only on a Steam Machine and always opt-in: flashes +# the newest Steam Machine BIOS from Valve's fremont-hw-support package with +# fwupd. It's an action, not an on/off component: never preselected, never +# re-applied, and there is nothing to turn off afterwards. +# Sourced by setup-gamescope-boot.sh; not meant to be run on its own. + +BIOS_REPO_PREFIX=holo + +bios_available() { detect_valve_fremont; } + +bios_status() { return 1; } + +bios_disable() { return 0; } + +bios_current() { + cat /sys/class/dmi/id/bios_version 2>/dev/null || echo unknown +} + +bios_lookup_newest() { + # Sets BIOS_REPO, BIOS_PKG, BIOS_SHA256 and BIOS_NEWEST (e.g. F7F0108) + # from the newest holo-X.Y repository on Valve's mirror: its .files + # database names the firmware file, its .db gives the package checksum. + # Only once per run, and with short timeouts so an offline machine + # doesn't hold up the menu. + [[ -n "${BIOS_LOOKED_UP:-}" ]] && return 0 + BIOS_LOOKED_UP=1; BIOS_NEWEST="" + local tmp desc + BIOS_REPO="$(curl -fsL --max-time 10 "$VALVE_MIRROR/" | grep -oE "$BIOS_REPO_PREFIX-[0-9]+\.[0-9]+/" | tr -d / | sort -uV | tail -n 1)" + [[ -n "$BIOS_REPO" ]] || return 1 + tmp="$(mktemp -d)" + if curl -fsL --max-time 30 "$VALVE_MIRROR/$BIOS_REPO/os/x86_64/$BIOS_REPO.files" -o "$tmp/files" && + curl -fsL --max-time 30 "$VALVE_MIRROR/$BIOS_REPO/os/x86_64/$BIOS_REPO.db" -o "$tmp/db"; then + desc="$(tar -tf "$tmp/files" 2>/dev/null | grep -E '^fremont-hw-support-[0-9][^/]*/files$' | head -n 1)" + [[ -n "$desc" ]] && BIOS_CAB="$(tar -xOf "$tmp/files" "$desc" | grep -E '^usr/share/fwupd/.*\.cab$' | head -n 1)" + desc="${desc%/files}/desc" + BIOS_PKG="$(tar -xOf "$tmp/db" "$desc" 2>/dev/null | awk '/^%FILENAME%$/ { getline; print }')" + BIOS_SHA256="$(tar -xOf "$tmp/db" "$desc" 2>/dev/null | awk '/^%SHA256SUM%$/ { getline; print }')" + [[ -n "${BIOS_CAB:-}" && -n "$BIOS_PKG" && -n "$BIOS_SHA256" ]] && BIOS_NEWEST="$(basename "$BIOS_CAB" .cab)" + fi + rm -rf "$tmp" + [[ -n "$BIOS_NEWEST" ]] +} + +bios_label() { + # Menu label with the current and the newest version. + local newest="newest unknown (offline?)" + bios_lookup_newest && newest="newest $BIOS_NEWEST" + [[ "$BIOS_NEWEST" == "$(bios_current)" ]] && newest="up to date" + echo "Update BIOS (at your own risk): now $(bios_current), $newest" +} + +bios_disclaimer() { + local r="$c_red$c_bold" n="$c_reset" + echo + echo -e "${r} ###################################################################${n}" + echo -e "${r} ## ##${n}" + echo -e "${r} ## WARNING: BIOS UPDATE - ENTIRELY AT YOUR OWN RISK ##${n}" + echo -e "${r} ## ##${n}" + echo -e "${r} ###################################################################${n}" + echo -e "${r} ##${n} $1" + echo -e "${r} ##${n}" + echo -e "${r} ##${n} - A failed or interrupted BIOS update can leave the machine" + echo -e "${r} ##${n} unable to start (bricked). This wizard, CachyOS and Valve" + echo -e "${r} ##${n} take no responsibility for that." + echo -e "${r} ##${n} - ${c_bold}NEVER turn off the power, unplug the machine or press the${n}" + echo -e "${r} ##${n} ${c_bold}power button while the update runs${n}, including during the" + echo -e "${r} ##${n} restart(s) afterwards, when the firmware is actually written." + echo -e "${r} ##${n} - The screen can stay black for several minutes. Wait." + echo -e "${r} ##${n} - Use this only on a Valve Steam Machine, on mains power, and" + echo -e "${r} ##${n} close all other programs first." + echo -e "${r} ###################################################################${n}" + echo +} + +bios_enable() { + if ! bios_lookup_newest; then + err "Couldn't find the newest Steam Machine BIOS on Valve's mirror ($VALVE_MIRROR)." + return 1 + fi + local current + current="$(bios_current)" + if [[ "$current" == "$BIOS_NEWEST" ]]; then + ok "The BIOS is already the newest version ($current); nothing to do." + return 0 + fi + + bios_disclaimer "Current BIOS: ${c_bold}$current${c_reset} -> new BIOS: ${c_bold}$BIOS_NEWEST${c_reset} ($BIOS_PKG)" + ask_yn "Do you understand the risks and want to continue?" n || + { info "BIOS update cancelled; nothing was changed."; return 0; } + bios_disclaimer "LAST CHANCE: this flashes BIOS $BIOS_NEWEST onto this machine." + local reply + read -rp "$(echo -e "${c_red}${c_bold}Type UPDATE (in capitals) to flash the BIOS, anything else cancels:${c_reset} ")" reply + [[ "$reply" == UPDATE ]] || { info "BIOS update cancelled; nothing was changed."; return 0; } + + pacman -Q fwupd >/dev/null 2>&1 || sudo pacman -S --needed --noconfirm fwupd || + { err "Installing fwupd failed."; return 1; } + local tmp + tmp="$(mktemp -d)" + info "Downloading $BIOS_PKG ($BIOS_REPO)..." + if ! curl -fL "$VALVE_MIRROR/$BIOS_REPO/os/x86_64/$BIOS_PKG" -o "$tmp/pkg.tar.zst" || + ! echo "$BIOS_SHA256 $tmp/pkg.tar.zst" | sha256sum -c --quiet - || + ! tar -I unzstd -xf "$tmp/pkg.tar.zst" -C "$tmp" "$BIOS_CAB"; then + err "Downloading or verifying $BIOS_PKG failed; the BIOS was not touched." + rm -rf "$tmp" + return 1 + fi + + info "Handing BIOS $BIOS_NEWEST to fwupd. Do NOT turn off the power from now on." + # -y: we already asked twice; --no-reboot-check: the wizard's own + # restart question comes at the end. + if ! sudo fwupdmgr install -y --no-reboot-check "$tmp/$BIOS_CAB"; then + err "fwupd could not install the BIOS update (see above); the BIOS was not changed." + rm -rf "$tmp" + return 1 + fi + rm -rf "$tmp" + BIOS_NEEDS_RESTART=1 + ok "BIOS $BIOS_NEWEST is staged. It is written during the next restart:" + warn "keep the power on and don't touch the machine until it has fully started again." +} diff --git a/lib/menu.sh b/lib/menu.sh index eb2d5e0..c5854df 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -5,7 +5,10 @@ # Menu order. Components are turned on in this order and off in reverse; # gaming must come first (single user builds on it). -COMPONENTS=(gaming theme glyphs single machine) +COMPONENTS=(gaming theme glyphs single machine bios) +# One-off actions rather than on/off components: never preselected, never +# re-applied, not listed as on or off. +ACTIONS=(bios) declare -A LABEL=( [gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop" @@ -13,13 +16,19 @@ declare -A LABEL=( [glyphs]="Install Steam Deck/Machine icons: Deck button icons in gaming mode" [single]="Single user mode: no password, lock screen or log out (SDDM)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" + [bios]="Update BIOS" ) declare -A CURRENT WANTED component_available() { - [[ "$1" != machine ]] || machine_available + case "$1" in + machine) machine_available ;; + bios) bios_available ;; + esac } +is_action() { [[ " ${ACTIONS[*]} " == *" $1 "* ]]; } + detect_components() { local c any=false for c in "${COMPONENTS[@]}"; do @@ -27,10 +36,12 @@ detect_components() { if "${c}_status"; then CURRENT[$c]=1; any=true; else CURRENT[$c]=0; fi WANTED[$c]=${CURRENT[$c]} done - # First run: preselect the full SteamOS experience. + # Shows the current and newest BIOS version. + bios_available && { bios_lookup_newest; LABEL[bios]="$(bios_label)"; } + # First run: preselect the full SteamOS experience (never an action). if [[ "$any" == false ]]; then for c in "${COMPONENTS[@]}"; do - component_available "$c" && WANTED[$c]=1 + component_available "$c" && ! is_action "$c" && WANTED[$c]=1 done fi } @@ -52,6 +63,7 @@ show_menu() { component_available "$c" || continue i=$((i + 1)); MENU_ITEMS[$i]=$c now="off"; [[ "${CURRENT[$c]}" == 1 ]] && now="${c_green}on${c_reset} " + is_action "$c" && now="-" want="[ ]"; [[ "${WANTED[$c]}" == 1 ]] && want="[x]" printf " %-3s %-6b %-6s %s\n" "$i" "$now " "$want" "${LABEL[$c]}" done @@ -84,13 +96,14 @@ draw_menu_tui() { component_available "$c" || continue MENU_ITEMS[$i]=$c box="[ ]"; [[ "${WANTED[$c]}" == 1 ]] && box="[${c_green}x${c_reset}]" - state="off"; [[ "${CURRENT[$c]}" == 1 ]] && state="${c_green}on${c_reset}" + state=" (now: off)"; [[ "${CURRENT[$c]}" == 1 ]] && state=" (now: ${c_green}on${c_reset})" + is_action "$c" && state=" (opt-in, runs once)" line="$box ${LABEL[$c]}" if (( i == cursor )); then # The green x's reset would end the bold too: re-enable it after. - echo -e " ${c_cyan}>${c_reset} ${c_bold}${line//"$c_reset"/"$c_reset$c_bold"}${c_reset} (now: ${state})" + echo -e " ${c_cyan}>${c_reset} ${c_bold}${line//"$c_reset"/"$c_reset$c_bold"}${c_reset}${state}" else - echo -e " ${line} (now: ${state})" + echo -e " ${line}${state}" fi i=$((i + 1)) done @@ -160,6 +173,7 @@ plan_changes() { for c in "${COMPONENTS[@]}"; do component_available "$c" || continue [[ "${WANTED[$c]}" == 1 ]] || continue + if is_action "$c"; then TO_ENABLE+=("$c"); continue; fi if [[ "${CURRENT[$c]}" == 0 || "$REAPPLY" == true ]] || [[ "$c" == gaming && "${CURRENT[single]}" != "${WANTED[single]}" ]]; then TO_ENABLE+=("$c") @@ -177,7 +191,8 @@ apply_changes() { "${c}_disable" || failed+=("$c") done for c in "${TO_ENABLE[@]}"; do - echo; echo -e "${c_bold}Turning on: ${LABEL[$c]}${c_reset}" + if is_action "$c"; then echo; echo -e "${c_bold}Running: ${LABEL[$c]}${c_reset}" + else echo; echo -e "${c_bold}Turning on: ${LABEL[$c]}${c_reset}"; fi "${c}_enable" || failed+=("$c") done FAILED=("${failed[@]}") diff --git a/setup-gamescope-boot.sh b/setup-gamescope-boot.sh index 549ca1b..b9f65db 100755 --- a/setup-gamescope-boot.sh +++ b/setup-gamescope-boot.sh @@ -19,7 +19,7 @@ VERSION=0.7.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for lib in common state packages login-manager single-user steam-desktop steam-machine vapor-theme steamos-extras desktop-shortcut menu; do +for lib in common state packages login-manager single-user steam-desktop steam-machine vapor-theme steamos-extras bios desktop-shortcut menu; do # shellcheck source=/dev/null source "$SCRIPT_DIR/lib/$lib.sh" done @@ -52,7 +52,8 @@ echo echo -e "${c_bold}This will:${c_reset}" for c in "${TO_DISABLE[@]}"; do echo " - turn off: ${LABEL[$c]}"; done for c in "${TO_ENABLE[@]}"; do - if [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi + if is_action "$c"; then echo " - run: ${LABEL[$c]} (asks two more confirmations)" + elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi done ask_yn "Go ahead?" y || { info "Nothing changed."; exit 0; } @@ -67,7 +68,7 @@ echo detect_components echo -e "${c_bold}Done. Current state:${c_reset}" for c in "${COMPONENTS[@]}"; do - component_available "$c" || continue + component_available "$c" && ! is_action "$c" || continue if [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " off ${LABEL[$c]}"; fi done if [[ ${#FAILED[@]} -gt 0 ]]; then @@ -75,6 +76,18 @@ if [[ ${#FAILED[@]} -gt 0 ]]; then fi echo +# A staged BIOS update is written during the restart. +if [[ -n "${BIOS_NEEDS_RESTART:-}" ]]; then + warn "The BIOS update is written during the next restart. Keep the power on and" + warn "don't touch the machine until it has fully started again, even if the screen stays black." + if ask_yn "Restart now to install the BIOS update?" n; then + sudo reboot + else + info "The BIOS update installs at your next restart." + fi + exit 0 +fi + # Login manager changes only take effect after a restart. if [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" gaming "* || " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" single "* ]]; then if ask_yn "Restart now so the changes take effect?" n; then From 40c9ee638b35e5053cb51c3acf099014e26f404b Mon Sep 17 00:00:00 2001 From: Rick Peters Date: Thu, 24 Sep 2026 10:29:46 +0200 Subject: [PATCH 2/9] fix: BIOS update only when newer, device check first, aligned warnings - Greyed out and not tickable unless Valve has a newer BIOS (component_selectable). - Download, SHA-256 and a fwupd device check (get-details) before the warnings. - Warning box with a solid block frame (a coloured row of # renders narrower in Konsole); menu Now column aligned. --- lib/bios.sh | 117 +++++++++++++++++++++++++++++----------- lib/common.sh | 2 +- lib/menu.sh | 30 +++++++++-- setup-gamescope-boot.sh | 2 +- 4 files changed, 113 insertions(+), 38 deletions(-) diff --git a/lib/bios.sh b/lib/bios.sh index 44e25d5..99b6fec 100644 --- a/lib/bios.sh +++ b/lib/bios.sh @@ -42,69 +42,124 @@ bios_lookup_newest() { [[ -n "$BIOS_NEWEST" ]] } +bios_selectable() { + # Only when Valve has a newer BIOS than the one installed; the menu + # greys the item out otherwise (up to date, or newest unknown/offline). + [[ -n "${BIOS_NEWEST:-}" && "$BIOS_NEWEST" != "$(bios_current)" ]] +} + bios_label() { # Menu label with the current and the newest version. local newest="newest unknown (offline?)" - bios_lookup_newest && newest="newest $BIOS_NEWEST" - [[ "$BIOS_NEWEST" == "$(bios_current)" ]] && newest="up to date" + [[ -n "${BIOS_NEWEST:-}" ]] && newest="newest $BIOS_NEWEST" + [[ "${BIOS_NEWEST:-}" == "$(bios_current)" ]] && newest="up to date" echo "Update BIOS (at your own risk): now $(bios_current), $newest" } +BIOS_BOX_WIDTH=68 + +bios_box_line() { + # bios_box_line [text]: one line of the warning box, padded to the same + # visible width (colour codes don't count) so the right edge lines up. + local text="${1:-}" plain pad + plain="$(printf '%b' "$text" | sed 's/\x1b\[[0-9;]*m//g')" + pad=$(( BIOS_BOX_WIDTH - 6 - ${#plain} )) + (( pad < 0 )) && pad=0 + printf ' %b██%b %b%*s%b██%b\n' "$c_red" "$c_reset" "$text" "$pad" "" "$c_red" "$c_reset" +} + +bios_box_border() { + # Block characters: Konsole draws a long coloured row of # narrower + # than the box's other lines, so its right edge wouldn't line up. + printf ' %b%s%b\n' "$c_red" "$(printf '█%.0s' $(seq "$BIOS_BOX_WIDTH"))" "$c_reset" +} + bios_disclaimer() { - local r="$c_red$c_bold" n="$c_reset" + # bios_disclaimer [line...]: the warning box; extra lines are + # shown above the fixed risks. + local title="$1" line b="$c_bold" n="$c_reset" + shift echo - echo -e "${r} ###################################################################${n}" - echo -e "${r} ## ##${n}" - echo -e "${r} ## WARNING: BIOS UPDATE - ENTIRELY AT YOUR OWN RISK ##${n}" - echo -e "${r} ## ##${n}" - echo -e "${r} ###################################################################${n}" - echo -e "${r} ##${n} $1" - echo -e "${r} ##${n}" - echo -e "${r} ##${n} - A failed or interrupted BIOS update can leave the machine" - echo -e "${r} ##${n} unable to start (bricked). This wizard, CachyOS and Valve" - echo -e "${r} ##${n} take no responsibility for that." - echo -e "${r} ##${n} - ${c_bold}NEVER turn off the power, unplug the machine or press the${n}" - echo -e "${r} ##${n} ${c_bold}power button while the update runs${n}, including during the" - echo -e "${r} ##${n} restart(s) afterwards, when the firmware is actually written." - echo -e "${r} ##${n} - The screen can stay black for several minutes. Wait." - echo -e "${r} ##${n} - Use this only on a Valve Steam Machine, on mains power, and" - echo -e "${r} ##${n} close all other programs first." - echo -e "${r} ###################################################################${n}" + bios_box_border + bios_box_line + bios_box_line "$c_red$b$title$n" + bios_box_line + bios_box_border + for line in "$@"; do bios_box_line "$line"; done + bios_box_line + bios_box_line "- A failed or interrupted BIOS update can leave the machine" + bios_box_line " unable to start (bricked). This wizard, CachyOS and Valve" + bios_box_line " take no responsibility for that." + bios_box_line "- ${b}NEVER turn off the power, unplug the machine or press${n}" + bios_box_line " ${b}the power button while the update runs${n}, including the" + bios_box_line " restart(s) afterwards, when the firmware is written." + bios_box_line "- The screen can stay black for several minutes. Wait." + bios_box_line "- Only on mains power, with all other programs closed." + bios_box_line + bios_box_border echo } +bios_fits_device() { + # bios_fits_device <cab>: does fwupd match this firmware to a device in + # this machine? It compares the file's hardware IDs (GUIDs) with the + # hardware; for a file that doesn't fit it reports an UpdateError. + local details + details="$(sudo fwupdmgr get-details --json "$1" 2>/dev/null)" || return 1 + grep -q '"Guid"' <<< "$details" && ! grep -q '"UpdateError"' <<< "$details" +} + bios_enable() { if ! bios_lookup_newest; then err "Couldn't find the newest Steam Machine BIOS on Valve's mirror ($VALVE_MIRROR)." return 1 fi - local current + local current tmp current="$(bios_current)" if [[ "$current" == "$BIOS_NEWEST" ]]; then ok "The BIOS is already the newest version ($current); nothing to do." return 0 fi - bios_disclaimer "Current BIOS: ${c_bold}$current${c_reset} -> new BIOS: ${c_bold}$BIOS_NEWEST${c_reset} ($BIOS_PKG)" - ask_yn "Do you understand the risks and want to continue?" n || - { info "BIOS update cancelled; nothing was changed."; return 0; } - bios_disclaimer "LAST CHANCE: this flashes BIOS $BIOS_NEWEST onto this machine." - local reply - read -rp "$(echo -e "${c_red}${c_bold}Type UPDATE (in capitals) to flash the BIOS, anything else cancels:${c_reset} ")" reply - [[ "$reply" == UPDATE ]] || { info "BIOS update cancelled; nothing was changed."; return 0; } - + # Download and check everything before asking: the warnings only come + # when the file is Valve's (SHA-256 from Valve's repo database) and fwupd + # confirms it's firmware for this very machine. pacman -Q fwupd >/dev/null 2>&1 || sudo pacman -S --needed --noconfirm fwupd || { err "Installing fwupd failed."; return 1; } - local tmp tmp="$(mktemp -d)" info "Downloading $BIOS_PKG ($BIOS_REPO)..." - if ! curl -fL "$VALVE_MIRROR/$BIOS_REPO/os/x86_64/$BIOS_PKG" -o "$tmp/pkg.tar.zst" || + if ! curl -fsSL "$VALVE_MIRROR/$BIOS_REPO/os/x86_64/$BIOS_PKG" -o "$tmp/pkg.tar.zst" || ! echo "$BIOS_SHA256 $tmp/pkg.tar.zst" | sha256sum -c --quiet - || ! tar -I unzstd -xf "$tmp/pkg.tar.zst" -C "$tmp" "$BIOS_CAB"; then err "Downloading or verifying $BIOS_PKG failed; the BIOS was not touched." rm -rf "$tmp" return 1 fi + ok "Checksum OK: this is Valve's $BIOS_PKG." + if ! bios_fits_device "$tmp/$BIOS_CAB"; then + err "fwupd says BIOS $BIOS_NEWEST is not for this machine's hardware; not installing it." + rm -rf "$tmp" + return 1 + fi + ok "fwupd confirms BIOS $BIOS_NEWEST is firmware for this machine." + + local g="$c_green$c_bold" b="$c_bold" n="$c_reset" + bios_disclaimer "WARNING: BIOS UPDATE - ENTIRELY AT YOUR OWN RISK" \ + "Current BIOS: ${b}$current${n}" \ + "New BIOS: ${b}$BIOS_NEWEST${n}" \ + "Checksum: ${g}OK${n} (Valve's package)" \ + "Compatible: ${g}yes${n} (checked by fwupd)" + if ! ask_yn "Do you understand the risks and want to continue?" n; then + info "BIOS update cancelled; nothing was changed."; rm -rf "$tmp"; return 0 + fi + bios_disclaimer "LAST CHANCE: THIS FLASHES BIOS $BIOS_NEWEST" \ + "After this, keep the power on until the machine has fully" \ + "started again. Don't touch it, even if the screen is black." + local reply + read -rp "$(echo -e "${c_red}${c_bold}Type UPDATE (in capitals) to flash the BIOS, anything else cancels:${c_reset} ")" reply + if [[ "$reply" != UPDATE ]]; then + info "BIOS update cancelled; nothing was changed."; rm -rf "$tmp"; return 0 + fi info "Handing BIOS $BIOS_NEWEST to fwupd. Do NOT turn off the power from now on." # -y: we already asked twice; --no-reboot-check: the wizard's own diff --git a/lib/common.sh b/lib/common.sh index 781c387..c5701ec 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -2,7 +2,7 @@ # Output helpers, prompts and small shared utilities. # Sourced by setup-gamescope-boot.sh; not meant to be run on its own. -c_reset="\033[0m"; c_bold="\033[1m"; c_green="\033[32m"; c_yellow="\033[33m"; c_red="\033[31m"; c_cyan="\033[36m" +c_reset="\033[0m"; c_bold="\033[1m"; c_green="\033[32m"; c_yellow="\033[33m"; c_red="\033[31m"; c_cyan="\033[36m"; c_dim="\033[2m" info() { echo -e "${c_cyan}[INFO]${c_reset} $*"; } ok() { echo -e "${c_green}[OK]${c_reset} $*"; } diff --git a/lib/menu.sh b/lib/menu.sh index c5854df..5c4b842 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -29,6 +29,13 @@ component_available() { is_action() { [[ " ${ACTIONS[*]} " == *" $1 "* ]]; } +component_selectable() { + # Greyed out and not tickable when it has nothing to do. + case "$1" in + bios) bios_selectable ;; + esac +} + detect_components() { local c any=false for c in "${COMPONENTS[@]}"; do @@ -48,6 +55,7 @@ detect_components() { toggle_component() { local c="$1" + component_selectable "$c" || return 1 WANTED[$c]=$(( 1 - WANTED[$c] )) # Single user mode only makes sense on top of the SteamOS conversion. if [[ "$c" == single && "${WANTED[single]}" == 1 ]]; then WANTED[gaming]=1; fi @@ -62,10 +70,17 @@ show_menu() { for c in "${COMPONENTS[@]}"; do component_available "$c" || continue i=$((i + 1)); MENU_ITEMS[$i]=$c - now="off"; [[ "${CURRENT[$c]}" == 1 ]] && now="${c_green}on${c_reset} " + # Pad the plain word, then colour it: colour codes would count as width. + now="off"; [[ "${CURRENT[$c]}" == 1 ]] && now="on" is_action "$c" && now="-" + now="$(printf '%-6s' "$now")" + [[ "${CURRENT[$c]}" == 1 ]] && now="${now/on/${c_green}on${c_reset}}" want="[ ]"; [[ "${WANTED[$c]}" == 1 ]] && want="[x]" - printf " %-3s %-6b %-6s %s\n" "$i" "$now " "$want" "${LABEL[$c]}" + if component_selectable "$c"; then + printf " %-3s %b %-6s %s\n" "$i" "$now" "$want" "${LABEL[$c]}" + else + printf " %b%-3s %-6s %-6s %s (not available)%b\n" "$c_dim" "$i" "$now" "$want" "${LABEL[$c]}" "$c_reset" + fi done echo echo -e "$KERNEL_OVERVIEW" @@ -99,7 +114,11 @@ draw_menu_tui() { state=" (now: off)"; [[ "${CURRENT[$c]}" == 1 ]] && state=" (now: ${c_green}on${c_reset})" is_action "$c" && state=" (opt-in, runs once)" line="$box ${LABEL[$c]}" - if (( i == cursor )); then + if ! component_selectable "$c"; then + # Greyed out: nothing to do (e.g. BIOS already up to date). + local mark=" "; (( i == cursor )) && mark=" ${c_cyan}>${c_reset} " + echo -e "${mark}${c_dim}[ ] ${LABEL[$c]} (not available)${c_reset}" + elif (( i == cursor )); then # The green x's reset would end the bold too: re-enable it after. echo -e " ${c_cyan}>${c_reset} ${c_bold}${line//"$c_reset"/"$c_reset$c_bold"}${c_reset}${state}" else @@ -150,7 +169,8 @@ run_menu_lines() { q|Q) return 1 ;; *) if [[ "$reply" =~ ^[0-9]+$ && -n "${MENU_ITEMS[$reply]:-}" ]]; then - toggle_component "${MENU_ITEMS[$reply]}" + toggle_component "${MENU_ITEMS[$reply]}" || + warn "Not available: ${LABEL[${MENU_ITEMS[$reply]}]}" else warn "Unknown choice: $reply" fi @@ -191,7 +211,7 @@ apply_changes() { "${c}_disable" || failed+=("$c") done for c in "${TO_ENABLE[@]}"; do - if is_action "$c"; then echo; echo -e "${c_bold}Running: ${LABEL[$c]}${c_reset}" + if is_action "$c"; then echo; echo -e "${c_bold}Running: ${LABEL[$c]%%:*}${c_reset}" else echo; echo -e "${c_bold}Turning on: ${LABEL[$c]}${c_reset}"; fi "${c}_enable" || failed+=("$c") done diff --git a/setup-gamescope-boot.sh b/setup-gamescope-boot.sh index b9f65db..3c3c202 100755 --- a/setup-gamescope-boot.sh +++ b/setup-gamescope-boot.sh @@ -52,7 +52,7 @@ echo echo -e "${c_bold}This will:${c_reset}" for c in "${TO_DISABLE[@]}"; do echo " - turn off: ${LABEL[$c]}"; done for c in "${TO_ENABLE[@]}"; do - if is_action "$c"; then echo " - run: ${LABEL[$c]} (asks two more confirmations)" + if is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} (checks, then asks twice more)" elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi done ask_yn "Go ahead?" y || { info "Nothing changed."; exit 0; } From b70c9d614f131e5b8637a402764fbdfac927943a Mon Sep 17 00:00:00 2001 From: Rick Peters <rickpeters@upriser.nl> Date: Thu, 24 Sep 2026 10:30:24 +0200 Subject: [PATCH 3/9] fix: shellcheck in the bundle, and the docs for the BIOS checks - Rename a variable in lib/bios.sh that clashed with lib/state.sh's array in the bundle. - README, AGENTS.md and CHANGELOG.md for the greyed-out item, the device check and the aligned warning box. --- AGENTS.md | 10 ++++++++++ CHANGELOG.md | 11 ++++++++++- README.md | 11 +++++++---- lib/bios.sh | 6 +++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index be4a747..6676adb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -135,6 +135,11 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine - `bios` is an *action* (`ACTIONS` in `lib/menu.sh`), not an on/off component: never preselected (not even on a first run), never re-applied by `a`, not listed in the state overview, and `bios_status` is always off. + Only selectable when Valve's version differs from the installed one + (`component_selectable`, greyed out otherwise). Download, SHA-256 and the + fwupd device check (`get-details --json`: no `UpdateError`) come before the + warnings. The warning box uses `█` for its frame: Konsole draws a long + coloured row of `#` narrower, so the right edge wouldn't line up. Keep both confirmations (y/N, then typing `UPDATE`) and the warnings; the firmware comes from the newest `holo-X.Y` repo (`.files` db names the `.cab`, `.db` gives the SHA-256), and fwupd itself refuses non-Fremont @@ -144,6 +149,11 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine ## Checking changes +The bundle puts every module in one file, so shellcheck sees all their +`local` variables together: don't reuse a name another module uses as an +array (e.g. `g` in `lib/state.sh`), or CI's shellcheck on the bundle fails. + + There is no test suite. At minimum: ```bash diff --git a/CHANGELOG.md b/CHANGELOG.md index b159f61..4731128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,12 +56,21 @@ Machine LED driver works on every installed kernel and survives kernel updates. - When a new version is released, the `latest` tag and release move to it (bundle replaced), so the older `.../releases/download/latest/...` URL also always gives the newest version. -- **feat: Opt-in BIOS update for the Steam Machine** +- `2f9b2a5` **feat: Opt-in BIOS update for the Steam Machine** - New menu item (Steam Machine only, never ticked by default) showing the current BIOS version and the newest from Valve's `fremont-hw-support`. - Two large warnings and two confirmations (y/N, then typing `UPDATE`), checksum-verified download, installed with fwupd; the wizard then offers the restart that writes it, with a warning to keep the power on. +- `40c9ee6` **fix: BIOS update only when newer, device check first, aligned warnings** + - The item is greyed out and can't be ticked unless Valve has a newer BIOS. + - Before any warning: SHA-256 of Valve's package, then fwupd confirms the + firmware fits this machine's hardware; otherwise it stops. + - Warning box drawn with a solid red frame whose edges line up, and the + menu's "Now" column aligned. +- **fix: shellcheck in the bundle, and the docs for the BIOS checks** + - A variable name in `lib/bios.sh` clashed with `lib/state.sh` in the bundle + (CI's shellcheck would fail); README, AGENTS.md and this changelog updated. ## 0.6.2 - 2026-09-23 diff --git a/README.md b/README.md index eb1b65c..f444c32 100644 --- a/README.md +++ b/README.md @@ -286,10 +286,13 @@ journalctl --user -b | grep -i led **BIOS updates** (opt-in, never ticked by default). On a Steam Machine the menu has an **Update BIOS** item that shows the current BIOS version and the newest one Valve ships (`F7F0108.cab` in its `fremont-hw-support` package, looked up -on Valve's SteamOS mirror). Ticking it shows a large warning, asks for -confirmation, shows the warning again and only continues when you type -`UPDATE`. It then downloads the package (checksum verified) and hands the -firmware to fwupd; the BIOS is written during the next restart. +on Valve's SteamOS mirror). It can only be ticked when Valve has a newer BIOS +than the one installed; otherwise it's greyed out. Before asking anything it +downloads the package and checks it: the SHA-256 from Valve's repository +proves it's Valve's file, and fwupd confirms the firmware is for this very +machine (it compares the firmware's hardware IDs with the device). Only then +it shows a large warning and asks for confirmation, shows the warning again +and continues when you type `UPDATE`; the BIOS is written during the next restart. **At your own risk:** a failed or interrupted BIOS update can leave the machine unable to start. Keep it on mains power, and never turn off the power, unplug diff --git a/lib/bios.sh b/lib/bios.sh index 99b6fec..0fd8075 100644 --- a/lib/bios.sh +++ b/lib/bios.sh @@ -143,12 +143,12 @@ bios_enable() { fi ok "fwupd confirms BIOS $BIOS_NEWEST is firmware for this machine." - local g="$c_green$c_bold" b="$c_bold" n="$c_reset" + local yes="$c_green$c_bold" b="$c_bold" n="$c_reset" bios_disclaimer "WARNING: BIOS UPDATE - ENTIRELY AT YOUR OWN RISK" \ "Current BIOS: ${b}$current${n}" \ "New BIOS: ${b}$BIOS_NEWEST${n}" \ - "Checksum: ${g}OK${n} (Valve's package)" \ - "Compatible: ${g}yes${n} (checked by fwupd)" + "Checksum: ${yes}OK${n} (Valve's package)" \ + "Compatible: ${yes}yes${n} (checked by fwupd)" if ! ask_yn "Do you understand the risks and want to continue?" n; then info "BIOS update cancelled; nothing was changed."; rm -rf "$tmp"; return 0 fi From 54fc9e362bb3319539f9e4295efbd72db2ea5e55 Mon Sep 17 00:00:00 2001 From: Rick Peters <rickpeters@upriser.nl> Date: Thu, 24 Sep 2026 10:40:16 +0200 Subject: [PATCH 4/9] feat: Menu comes back after each run, BIOS dry run, version 0.8.0 - After a run the menu returns; [m] back to the menu or [r] restart now when a restart is needed; q quits. - A staged BIOS update greys the item out until the restart. - WIZARD_BIOS_DRY_RUN=1: whole BIOS flow without the device check, never flashes. - VERSION 0.8.0; CHANGELOG: BIOS work moved to 0.8.0; README: BIOS update step by step. --- AGENTS.md | 7 ++- CHANGELOG.md | 44 ++++++++++----- README.md | 52 ++++++++++++----- lib/bios.sh | 29 ++++++++-- setup-gamescope-boot.sh | 122 ++++++++++++++++++++++++++++------------ 5 files changed, 182 insertions(+), 72 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6676adb..3094a4a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -143,7 +143,12 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine Keep both confirmations (y/N, then typing `UPDATE`) and the warnings; the firmware comes from the newest `holo-X.Y` repo (`.files` db names the `.cab`, `.db` gives the SHA-256), and fwupd itself refuses non-Fremont - hardware. It can only be tested up to fwupd's refusal in the VM. + hardware. In the VM, test it with `WIZARD_BIOS_DRY_RUN=1` (skips only the + device check, never flashes) and a faked version (dev-env + `BIOS_VERSION=F7F0107 ./run.sh --fremont`). +- The entry point loops: menu, run, "back to the menu" (or `[m]`/`[r]` when a + restart is needed), until `q`; the restart question is asked once at the + end. Scripted input that runs out ends the loop like `q`. - `Relogin=true` means a gamescope that fails to start is relaunched in a tight loop; keep that in mind when changing session handling. diff --git a/CHANGELOG.md b/CHANGELOG.md index 4731128..8fb73f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ All notable changes, per version and per commit. Versions follow `setup-gamescope-boot.sh`. Versions before 0.7.0 were numbered afterwards, one per merged pull request. +## 0.8.0 - 2026-09-24 + +An opt-in BIOS update for the Steam Machine, and a menu that comes back after +every run. Merged together with 0.7.0, so v0.8.0 is the release that has both. + +- `2f9b2a5` **feat: Opt-in BIOS update for the Steam Machine** + - New menu item (Steam Machine only, never ticked by default) showing the + current BIOS version and the newest from Valve's `fremont-hw-support`. + - Two large warnings and two confirmations (y/N, then typing `UPDATE`), + checksum-verified download, installed with fwupd; the wizard then offers + the restart that writes it, with a warning to keep the power on. +- `40c9ee6` **fix: BIOS update only when newer, device check first, aligned warnings** + - The item is greyed out and can't be ticked unless Valve has a newer BIOS. + - Before any warning: SHA-256 of Valve's package, then fwupd confirms the + firmware fits this machine's hardware; otherwise it stops. + - Warning box drawn with a solid red frame whose edges line up, and the + menu's "Now" column aligned. +- `b70c9d6` **fix: shellcheck in the bundle, and the docs for the BIOS checks** + - A variable name in `lib/bios.sh` clashed with `lib/state.sh` in the bundle + (CI's shellcheck would fail); README, AGENTS.md and this changelog updated. +- **feat: Menu comes back after each run, BIOS dry run, version 0.8.0** + - After a run the menu returns with the new state; quit with `q`. When + something needs a restart, choose between back to the menu (`m`) and + restart now (`r`); quitting asks once more. + - A staged BIOS update greys the item out until the restart. + - `WIZARD_BIOS_DRY_RUN=1` walks through the whole BIOS update (download, + checksum, both warnings) but only prints the install and never flashes. + - README: the menu loop and the BIOS update step by step. + ## 0.7.0 - 2026-09-24 The SteamOS theme comes from CachyOS's `cachyos-vapor` package, applied with @@ -56,21 +85,6 @@ Machine LED driver works on every installed kernel and survives kernel updates. - When a new version is released, the `latest` tag and release move to it (bundle replaced), so the older `.../releases/download/latest/...` URL also always gives the newest version. -- `2f9b2a5` **feat: Opt-in BIOS update for the Steam Machine** - - New menu item (Steam Machine only, never ticked by default) showing the - current BIOS version and the newest from Valve's `fremont-hw-support`. - - Two large warnings and two confirmations (y/N, then typing `UPDATE`), - checksum-verified download, installed with fwupd; the wizard then offers - the restart that writes it, with a warning to keep the power on. -- `40c9ee6` **fix: BIOS update only when newer, device check first, aligned warnings** - - The item is greyed out and can't be ticked unless Valve has a newer BIOS. - - Before any warning: SHA-256 of Valve's package, then fwupd confirms the - firmware fits this machine's hardware; otherwise it stops. - - Warning box drawn with a solid red frame whose edges line up, and the - menu's "Now" column aligned. -- **fix: shellcheck in the bundle, and the docs for the BIOS checks** - - A variable name in `lib/bios.sh` clashed with `lib/state.sh` in the bundle - (CI's shellcheck would fail); README, AGENTS.md and this changelog updated. ## 0.6.2 - 2026-09-23 diff --git a/README.md b/README.md index f444c32..aa413e6 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ Everything you turn off is put back the way it was. 5. **Steam Machine support** - only shown on a Valve Steam Machine: the driver for the front LED bar, and the hardware settings in Steam (fan, TV control over HDMI-CEC). +6. **Update BIOS** - only on a Steam Machine, opt-in and at your own risk: + installs the newest Steam Machine BIOS from Valve (see + [BIOS updates](#bios-updates-steam-machine)). ## Requirements @@ -74,6 +77,10 @@ it's loaded right now, so a kernel update is easy to check. The wizard then shows what it will change, asks your password once, and at the end offers to restart (needed for changes to how the PC starts). +After each run the menu comes back with the new state, so you can change more +in one go; quit with **q**. When something needs a restart, you choose +between going back to the menu and restarting now; quitting asks once more. + Run it again whenever you like - to change your choices, to turn things off again, or after a CachyOS update (press `a` in the menu to re-apply everything that's on). @@ -283,22 +290,41 @@ cat /var/lib/dkms/leds-valve-dkms/0.1/build/make.log journalctl --user -b | grep -i led ``` -**BIOS updates** (opt-in, never ticked by default). On a Steam Machine the menu -has an **Update BIOS** item that shows the current BIOS version and the newest -one Valve ships (`F7F0108.cab` in its `fremont-hw-support` package, looked up -on Valve's SteamOS mirror). It can only be ticked when Valve has a newer BIOS -than the one installed; otherwise it's greyed out. Before asking anything it -downloads the package and checks it: the SHA-256 from Valve's repository -proves it's Valve's file, and fwupd confirms the firmware is for this very -machine (it compares the firmware's hardware IDs with the device). Only then -it shows a large warning and asks for confirmation, shows the warning again -and continues when you type `UPDATE`; the BIOS is written during the next restart. +### BIOS updates (Steam Machine) + +The **Update BIOS** item is only shown on a Steam Machine and is never ticked +by default. It shows the BIOS version you have now and the newest one Valve +ships (the `.cab` file in its `fremont-hw-support` package, looked up on +Valve's SteamOS mirror): + +``` + [ ] Update BIOS (at your own risk): now F7F0107, newest F7F0108 (opt-in, runs once) +``` + +It can only be ticked when Valve has a newer BIOS than yours; when you're up +to date, when the newest version can't be looked up (offline), or when an +update is already waiting for a restart, it's greyed out. + +When you run it, the wizard: + +1. downloads Valve's package and checks its **SHA-256** against Valve's + repository, so it's exactly Valve's file; +2. asks **fwupd** whether the firmware is for this very machine (fwupd compares + the firmware's hardware IDs with the device) and stops if it isn't; +3. shows a large red **warning** with the current and the new version, and asks + whether you understand the risks (default: no); +4. shows the warning **again** and only continues when you type `UPDATE`; +5. hands the firmware to fwupd, which writes it during the **next restart**: + choose "restart now" or restart later yourself. **At your own risk:** a failed or interrupted BIOS update can leave the machine unable to start. Keep it on mains power, and never turn off the power, unplug -it or press the power button while it updates, including during the restart -afterwards; the screen can stay black for several minutes. fwupd refuses the -file on anything that isn't a Steam Machine. +it or press the power button while it updates, including during the restart; +the screen can stay black for several minutes. + +To walk through it without flashing anything, run the wizard with +`WIZARD_BIOS_DRY_RUN=1`: it downloads and checks the package and shows both +warnings, skips fwupd's device check, and only prints the install command. ### Manual session control diff --git a/lib/bios.sh b/lib/bios.sh index 0fd8075..00c5e4b 100644 --- a/lib/bios.sh +++ b/lib/bios.sh @@ -6,6 +6,10 @@ # Sourced by setup-gamescope-boot.sh; not meant to be run on its own. BIOS_REPO_PREFIX=holo +# WIZARD_BIOS_DRY_RUN=1 walks through the whole BIOS update (download, +# checksum, both warnings) but never flashes: fwupd's device check is +# skipped and the install is only printed. For testing, e.g. in a VM. +BIOS_DRY_RUN="${WIZARD_BIOS_DRY_RUN:-}" bios_available() { detect_valve_fremont; } @@ -45,7 +49,7 @@ bios_lookup_newest() { bios_selectable() { # Only when Valve has a newer BIOS than the one installed; the menu # greys the item out otherwise (up to date, or newest unknown/offline). - [[ -n "${BIOS_NEWEST:-}" && "$BIOS_NEWEST" != "$(bios_current)" ]] + [[ -z "${BIOS_NEEDS_RESTART:-}" && -n "${BIOS_NEWEST:-}" && "$BIOS_NEWEST" != "$(bios_current)" ]] } bios_label() { @@ -53,6 +57,7 @@ bios_label() { local newest="newest unknown (offline?)" [[ -n "${BIOS_NEWEST:-}" ]] && newest="newest $BIOS_NEWEST" [[ "${BIOS_NEWEST:-}" == "$(bios_current)" ]] && newest="up to date" + [[ -n "${BIOS_NEEDS_RESTART:-}" ]] && newest="$BIOS_NEWEST staged, restart to install" echo "Update BIOS (at your own risk): now $(bios_current), $newest" } @@ -114,8 +119,9 @@ bios_enable() { err "Couldn't find the newest Steam Machine BIOS on Valve's mirror ($VALVE_MIRROR)." return 1 fi - local current tmp + local current tmp compatible current="$(bios_current)" + [[ -n "$BIOS_DRY_RUN" ]] && warn "DRY RUN (WIZARD_BIOS_DRY_RUN): nothing will be flashed." if [[ "$current" == "$BIOS_NEWEST" ]]; then ok "The BIOS is already the newest version ($current); nothing to do." return 0 @@ -136,19 +142,24 @@ bios_enable() { return 1 fi ok "Checksum OK: this is Valve's $BIOS_PKG." - if ! bios_fits_device "$tmp/$BIOS_CAB"; then + local yes="$c_green$c_bold" b="$c_bold" n="$c_reset" + if [[ -n "$BIOS_DRY_RUN" ]]; then + warn "Dry run: skipping fwupd's check that the firmware fits this machine." + compatible="${c_yellow}${b}not checked${n} (dry run)" + elif bios_fits_device "$tmp/$BIOS_CAB"; then + ok "fwupd confirms BIOS $BIOS_NEWEST is firmware for this machine." + compatible="${yes}yes${n} (checked by fwupd)" + else err "fwupd says BIOS $BIOS_NEWEST is not for this machine's hardware; not installing it." rm -rf "$tmp" return 1 fi - ok "fwupd confirms BIOS $BIOS_NEWEST is firmware for this machine." - local yes="$c_green$c_bold" b="$c_bold" n="$c_reset" bios_disclaimer "WARNING: BIOS UPDATE - ENTIRELY AT YOUR OWN RISK" \ "Current BIOS: ${b}$current${n}" \ "New BIOS: ${b}$BIOS_NEWEST${n}" \ "Checksum: ${yes}OK${n} (Valve's package)" \ - "Compatible: ${yes}yes${n} (checked by fwupd)" + "Compatible: $compatible" if ! ask_yn "Do you understand the risks and want to continue?" n; then info "BIOS update cancelled; nothing was changed."; rm -rf "$tmp"; return 0 fi @@ -161,6 +172,12 @@ bios_enable() { info "BIOS update cancelled; nothing was changed."; rm -rf "$tmp"; return 0 fi + if [[ -n "$BIOS_DRY_RUN" ]]; then + ok "Dry run: would now run: sudo fwupdmgr install -y --no-reboot-check $BIOS_CAB" + ok "Dry run finished; nothing was flashed and no restart is needed." + rm -rf "$tmp" + return 0 + fi info "Handing BIOS $BIOS_NEWEST to fwupd. Do NOT turn off the power from now on." # -y: we already asked twice; --no-reboot-check: the wizard's own # restart question comes at the end. diff --git a/setup-gamescope-boot.sh b/setup-gamescope-boot.sh index 3c3c202..c12e97a 100755 --- a/setup-gamescope-boot.sh +++ b/setup-gamescope-boot.sh @@ -15,7 +15,7 @@ set -uo pipefail # Release version, see CHANGELOG.md. -VERSION=0.7.0 +VERSION=0.8.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -39,41 +39,91 @@ fi # user running the script. TARGET_USER="$(id -un)" -detect_components -run_menu || { info "Nothing changed."; exit 0; } -plan_changes +restart_needed() { [[ -n "${BIOS_NEEDS_RESTART:-}" || "$RESTART_FOR_LOGIN" == true ]]; } -if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then - ok "Everything is already the way you want it." +restart_now() { + if [[ -n "${BIOS_NEEDS_RESTART:-}" ]]; then + warn "The BIOS update is written during this restart. Keep the power on and don't" + warn "touch the machine until it has fully started again, even if the screen stays black." + fi + info "Restarting..." + sudo reboot exit 0 -fi +} -echo -echo -e "${c_bold}This will:${c_reset}" -for c in "${TO_DISABLE[@]}"; do echo " - turn off: ${LABEL[$c]}"; done -for c in "${TO_ENABLE[@]}"; do - if is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} (checks, then asks twice more)" - elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi +after_run() { + # After a run: back to the menu, or restart right away when something + # that just ran needs it. Returns 1 when input has ended (scripted runs). + local reply + if ! restart_needed; then + read -rp "Press Enter to go back to the menu... " _ || return 1 + return 0 + fi + if [[ -n "${BIOS_NEEDS_RESTART:-}" ]]; then + warn "The BIOS update is installed at the next restart." + else + info "The login changes take effect after a restart." + fi + while true; do + read -rp "$(echo -e "${c_bold}[m]${c_reset} back to the menu ${c_bold}[r]${c_reset} restart now: ")" reply || return 1 + case "$reply" in + m|M|"") return 0 ;; + r|R) restart_now ;; + *) warn "Type m or r." ;; + esac + done +} + +# Menu loop: after each run the menu comes back with the new state, until +# the user quits; the restart question comes then, once, for everything. +RESTART_FOR_LOGIN=false +SUDO_KEEPALIVE=false +while true; do + detect_components + run_menu || break + plan_changes + + if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then + ok "Everything is already the way you want it." + read -rp "Press Enter to go back to the menu... " _ || break + continue + fi + + echo + echo -e "${c_bold}This will:${c_reset}" + for c in "${TO_DISABLE[@]}"; do echo " - turn off: ${LABEL[$c]}"; done + for c in "${TO_ENABLE[@]}"; do + if is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} (checks, then asks twice more)" + elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi + done + ask_yn "Go ahead?" y || { info "Nothing changed."; continue; } + + # Ask for the sudo password once and keep it fresh, instead of prompting + # at random points during the run. + sudo -n true 2>/dev/null || sudo -v || exit 1 + if [[ "$SUDO_KEEPALIVE" == false ]]; then + SUDO_KEEPALIVE=true + while true; do sudo -n true; sleep 50; kill -0 "$$" 2>/dev/null || exit; done 2>/dev/null & + fi + + apply_changes + # Login manager changes only take effect after a restart. + [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" gaming "* || " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" single "* ]] && + RESTART_FOR_LOGIN=true + + echo + detect_components + echo -e "${c_bold}Done. Current state:${c_reset}" + for c in "${COMPONENTS[@]}"; do + component_available "$c" && ! is_action "$c" || continue + if [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " off ${LABEL[$c]}"; fi + done + if [[ ${#FAILED[@]} -gt 0 ]]; then + warn "These had problems (see above): ${FAILED[*]}" + fi + echo + after_run || break done -ask_yn "Go ahead?" y || { info "Nothing changed."; exit 0; } - -# Ask for the sudo password once and keep it fresh, instead of prompting at -# random points during the run. -sudo -n true 2>/dev/null || sudo -v || exit 1 -while true; do sudo -n true; sleep 50; kill -0 "$$" 2>/dev/null || exit; done 2>/dev/null & - -apply_changes - -echo -detect_components -echo -e "${c_bold}Done. Current state:${c_reset}" -for c in "${COMPONENTS[@]}"; do - component_available "$c" && ! is_action "$c" || continue - if [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " off ${LABEL[$c]}"; fi -done -if [[ ${#FAILED[@]} -gt 0 ]]; then - warn "These had problems (see above): ${FAILED[*]}" -fi echo # A staged BIOS update is written during the restart. @@ -85,14 +135,12 @@ if [[ -n "${BIOS_NEEDS_RESTART:-}" ]]; then else info "The BIOS update installs at your next restart." fi - exit 0 -fi - -# Login manager changes only take effect after a restart. -if [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" gaming "* || " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" single "* ]]; then +elif [[ "$RESTART_FOR_LOGIN" == true ]]; then if ask_yn "Restart now so the changes take effect?" n; then sudo reboot else info "Restart whenever you're ready." fi +else + info "Bye." fi From 45a8757f58e880c1a8fb552117f4fae65d8cf48f Mon Sep 17 00:00:00 2001 From: Rick Peters <rickpeters@upriser.nl> Date: Thu, 24 Sep 2026 10:48:39 +0200 Subject: [PATCH 5/9] fix: BIOS dry run also walks through the restart choices A dry run counts as staged, so [m]/[r] and the restart question on q show up; restarting only prints in dry-run mode. --- CHANGELOG.md | 5 ++++- lib/bios.sh | 5 ++++- setup-gamescope-boot.sh | 8 ++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb73f1..c7c692f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ every run. Merged together with 0.7.0, so v0.8.0 is the release that has both. - `b70c9d6` **fix: shellcheck in the bundle, and the docs for the BIOS checks** - A variable name in `lib/bios.sh` clashed with `lib/state.sh` in the bundle (CI's shellcheck would fail); README, AGENTS.md and this changelog updated. -- **feat: Menu comes back after each run, BIOS dry run, version 0.8.0** +- `54fc9e3` **feat: Menu comes back after each run, BIOS dry run, version 0.8.0** - After a run the menu returns with the new state; quit with `q`. When something needs a restart, choose between back to the menu (`m`) and restart now (`r`); quitting asks once more. @@ -33,6 +33,9 @@ every run. Merged together with 0.7.0, so v0.8.0 is the release that has both. - `WIZARD_BIOS_DRY_RUN=1` walks through the whole BIOS update (download, checksum, both warnings) but only prints the install and never flashes. - README: the menu loop and the BIOS update step by step. +- **fix: BIOS dry run also walks through the restart choices** + - A dry run counts as staged, so `[m]`/`[r]` and the restart question on `q` + show up; in dry-run mode restarting only prints what it would do. ## 0.7.0 - 2026-09-24 diff --git a/lib/bios.sh b/lib/bios.sh index 00c5e4b..64da99b 100644 --- a/lib/bios.sh +++ b/lib/bios.sh @@ -174,7 +174,10 @@ bios_enable() { if [[ -n "$BIOS_DRY_RUN" ]]; then ok "Dry run: would now run: sudo fwupdmgr install -y --no-reboot-check $BIOS_CAB" - ok "Dry run finished; nothing was flashed and no restart is needed." + ok "Dry run finished; nothing was flashed." + # Treated as staged, so the restart choices that follow a real + # update show up too; restarting only prints (see restart_now). + BIOS_NEEDS_RESTART=1 rm -rf "$tmp" return 0 fi diff --git a/setup-gamescope-boot.sh b/setup-gamescope-boot.sh index c12e97a..8955d95 100755 --- a/setup-gamescope-boot.sh +++ b/setup-gamescope-boot.sh @@ -46,6 +46,10 @@ restart_now() { warn "The BIOS update is written during this restart. Keep the power on and don't" warn "touch the machine until it has fully started again, even if the screen stays black." fi + if [[ -n "${BIOS_DRY_RUN:-}" ]]; then + ok "Dry run: would restart now (sudo reboot); not restarting." + exit 0 + fi info "Restarting..." sudo reboot exit 0 @@ -131,13 +135,13 @@ if [[ -n "${BIOS_NEEDS_RESTART:-}" ]]; then warn "The BIOS update is written during the next restart. Keep the power on and" warn "don't touch the machine until it has fully started again, even if the screen stays black." if ask_yn "Restart now to install the BIOS update?" n; then - sudo reboot + restart_now else info "The BIOS update installs at your next restart." fi elif [[ "$RESTART_FOR_LOGIN" == true ]]; then if ask_yn "Restart now so the changes take effect?" n; then - sudo reboot + restart_now else info "Restart whenever you're ready." fi From 429e840a9f7e6a62cb10a996c20664306b781032 Mon Sep 17 00:00:00 2001 From: Rick Peters <rickpeters@upriser.nl> Date: Thu, 24 Sep 2026 10:51:53 +0200 Subject: [PATCH 6/9] fix: Shorter BIOS labels so the menu fits 80 columns --- CHANGELOG.md | 5 ++++- README.md | 2 +- lib/bios.sh | 21 ++++++++++++++------- setup-gamescope-boot.sh | 2 +- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c692f..1a8ffe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,9 +33,12 @@ every run. Merged together with 0.7.0, so v0.8.0 is the release that has both. - `WIZARD_BIOS_DRY_RUN=1` walks through the whole BIOS update (download, checksum, both warnings) but only prints the install and never flashes. - README: the menu loop and the BIOS update step by step. -- **fix: BIOS dry run also walks through the restart choices** +- `45a8757` **fix: BIOS dry run also walks through the restart choices** - A dry run counts as staged, so `[m]`/`[r]` and the restart question on `q` show up; in dry-run mode restarting only prints what it would do. +- **fix: Shorter BIOS labels so the menu fits 80 columns** + - "now F7F0107, newest F7F0108 (own risk)", "F7F0108 waits for a restart", + "F7F0107 is up to date"; shorter dry-run line. ## 0.7.0 - 2026-09-24 diff --git a/README.md b/README.md index aa413e6..87e49f7 100644 --- a/README.md +++ b/README.md @@ -298,7 +298,7 @@ ships (the `.cab` file in its `fremont-hw-support` package, looked up on Valve's SteamOS mirror): ``` - [ ] Update BIOS (at your own risk): now F7F0107, newest F7F0108 (opt-in, runs once) + [ ] Update BIOS: now F7F0107, newest F7F0108 (own risk) (opt-in, runs once) ``` It can only be ticked when Valve has a newer BIOS than yours; when you're up diff --git a/lib/bios.sh b/lib/bios.sh index 64da99b..6889553 100644 --- a/lib/bios.sh +++ b/lib/bios.sh @@ -53,12 +53,19 @@ bios_selectable() { } bios_label() { - # Menu label with the current and the newest version. - local newest="newest unknown (offline?)" - [[ -n "${BIOS_NEWEST:-}" ]] && newest="newest $BIOS_NEWEST" - [[ "${BIOS_NEWEST:-}" == "$(bios_current)" ]] && newest="up to date" - [[ -n "${BIOS_NEEDS_RESTART:-}" ]] && newest="$BIOS_NEWEST staged, restart to install" - echo "Update BIOS (at your own risk): now $(bios_current), $newest" + # Menu label with the current and the newest version; kept short so the + # row (with "(not available)" when greyed out) fits 80 columns. + local current + current="$(bios_current)" + if [[ -n "${BIOS_NEEDS_RESTART:-}" ]]; then + echo "Update BIOS: $BIOS_NEWEST waits for a restart" + elif [[ -z "${BIOS_NEWEST:-}" ]]; then + echo "Update BIOS: now $current, newest unknown (offline?)" + elif [[ "$BIOS_NEWEST" == "$current" ]]; then + echo "Update BIOS: $current is up to date" + else + echo "Update BIOS: now $current, newest $BIOS_NEWEST (own risk)" + fi } BIOS_BOX_WIDTH=68 @@ -173,7 +180,7 @@ bios_enable() { fi if [[ -n "$BIOS_DRY_RUN" ]]; then - ok "Dry run: would now run: sudo fwupdmgr install -y --no-reboot-check $BIOS_CAB" + ok "Dry run: would run: fwupdmgr install -y --no-reboot-check $(basename "$BIOS_CAB")" ok "Dry run finished; nothing was flashed." # Treated as staged, so the restart choices that follow a real # update show up too; restarting only prints (see restart_now). diff --git a/setup-gamescope-boot.sh b/setup-gamescope-boot.sh index 8955d95..5da934b 100755 --- a/setup-gamescope-boot.sh +++ b/setup-gamescope-boot.sh @@ -97,7 +97,7 @@ while true; do echo -e "${c_bold}This will:${c_reset}" for c in "${TO_DISABLE[@]}"; do echo " - turn off: ${LABEL[$c]}"; done for c in "${TO_ENABLE[@]}"; do - if is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} (checks, then asks twice more)" + if is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} at your own risk (checks, then asks twice more)" elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi done ask_yn "Go ahead?" y || { info "Nothing changed."; continue; } From 99a95abf1fd28d453d760144b850a989d2eca074 Mon Sep 17 00:00:00 2001 From: Rick Peters <rickpeters@upriser.nl> Date: Thu, 24 Sep 2026 10:54:33 +0200 Subject: [PATCH 7/9] fix: Kernel legend only mentions the LEDs on a Steam Machine --- CHANGELOG.md | 3 ++- lib/steam-machine.sh | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a8ffe1..8d0a174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,9 +36,10 @@ every run. Merged together with 0.7.0, so v0.8.0 is the release that has both. - `45a8757` **fix: BIOS dry run also walks through the restart choices** - A dry run counts as staged, so `[m]`/`[r]` and the restart question on `q` show up; in dry-run mode restarting only prints what it would do. -- **fix: Shorter BIOS labels so the menu fits 80 columns** +- `429e840` **fix: Shorter BIOS labels so the menu fits 80 columns** - "now F7F0107, newest F7F0108 (own risk)", "F7F0108 waits for a restart", "F7F0107 is up to date"; shorter dry-run line. +- **fix: Kernel legend only mentions the LEDs on a Steam Machine** ## 0.7.0 - 2026-09-24 diff --git a/lib/steam-machine.sh b/lib/steam-machine.sh index 3f7ce93..d82c56d 100644 --- a/lib/steam-machine.sh +++ b/lib/steam-machine.sh @@ -163,7 +163,9 @@ kernel_overview() { local mark="${c_green}yes${c_reset}" miss="${c_red}no ${c_reset}" leds=false detect_valve_fremont && command -v dkms >/dev/null 2>&1 && leds=true local kdir k pkg headers hid led running line - echo -e " ${c_bold}Kernels${c_reset} (> = running; controller = Steam controller driver, LEDs = LED bar driver built)" + local legend="> = running; controller = Steam controller driver" + [[ "$leds" == true ]] && legend+=", LEDs = LED bar driver built" + echo -e " ${c_bold}Kernels${c_reset} ($legend)" for kdir in /usr/lib/modules/*/; do k="$(basename "$kdir")" [[ -f "$kdir/pkgbase" ]] || continue From d39731a8b9589274cdd986c5dc7e2ee667ee3b4f Mon Sep 17 00:00:00 2001 From: Rick Peters <rickpeters@upriser.nl> Date: Thu, 24 Sep 2026 10:55:27 +0200 Subject: [PATCH 8/9] docs: Last commit hash in the changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d0a174..ecaf2d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,8 @@ every run. Merged together with 0.7.0, so v0.8.0 is the release that has both. - `429e840` **fix: Shorter BIOS labels so the menu fits 80 columns** - "now F7F0107, newest F7F0108 (own risk)", "F7F0108 waits for a restart", "F7F0107 is up to date"; shorter dry-run line. -- **fix: Kernel legend only mentions the LEDs on a Steam Machine** +- `99a95ab` **fix: Kernel legend only mentions the LEDs on a Steam Machine** +- **docs: Last commit hash in the changelog** ## 0.7.0 - 2026-09-24 From c0014ee62347840cca569805535350b0dde18983 Mon Sep 17 00:00:00 2001 From: Rick Peters <rickpeters@upriser.nl> Date: Thu, 24 Sep 2026 10:55:55 +0200 Subject: [PATCH 9/9] docs: 0.7.0 was released on its own (#6, #7) --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecaf2d3..68ba3b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ one per merged pull request. ## 0.8.0 - 2026-09-24 An opt-in BIOS update for the Steam Machine, and a menu that comes back after -every run. Merged together with 0.7.0, so v0.8.0 is the release that has both. +every run. - `2f9b2a5` **feat: Opt-in BIOS update for the Steam Machine** - New menu item (Steam Machine only, never ticked by default) showing the @@ -40,9 +40,9 @@ every run. Merged together with 0.7.0, so v0.8.0 is the release that has both. - "now F7F0107, newest F7F0108 (own risk)", "F7F0108 waits for a restart", "F7F0107 is up to date"; shorter dry-run line. - `99a95ab` **fix: Kernel legend only mentions the LEDs on a Steam Machine** -- **docs: Last commit hash in the changelog** +- **docs: Last commit hash in the changelog; 0.7.0 was released on its own (#6, #7)** -## 0.7.0 - 2026-09-24 +## 0.7.0 - 2026-09-24 (#6, #7) The SteamOS theme comes from CachyOS's `cachyos-vapor` package, applied with Vapor's own desktop layout, and gains the SteamOS desktop extras. The Steam