diff --git a/AGENTS.md b/AGENTS.md
index 80da0f5..3094a4a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -132,11 +132,33 @@ 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.
+ 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
+ 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.
## 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 39bb60e..68ba3b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,44 @@ 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.7.0 - 2026-09-24
+## 0.8.0 - 2026-09-24
+
+An opt-in BIOS update for the Steam Machine, and a menu that comes back after
+every run.
+
+- `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.
+- `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.
+ - 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.
+- `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.
+- `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.
+- `99a95ab` **fix: Kernel legend only mentions the LEDs on a Steam Machine**
+- **docs: Last commit hash in the changelog; 0.7.0 was released on its own (#6, #7)**
+
+## 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
@@ -52,7 +89,7 @@ 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.
diff --git a/README.md b/README.md
index 215d969..87e49f7 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,17 +290,41 @@ 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 (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):
-```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
```
+ [ ] 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
+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;
+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
@@ -328,6 +359,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..6889553
--- /dev/null
+++ b/lib/bios.sh
@@ -0,0 +1,203 @@
+#!/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
+# 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; }
+
+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_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).
+ [[ -z "${BIOS_NEEDS_RESTART:-}" && -n "${BIOS_NEWEST:-}" && "$BIOS_NEWEST" != "$(bios_current)" ]]
+}
+
+bios_label() {
+ # 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
+
+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() {
+ # 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
+ 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 : 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 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
+ fi
+
+ # 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; }
+ tmp="$(mktemp -d)"
+ info "Downloading $BIOS_PKG ($BIOS_REPO)..."
+ 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."
+ 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
+
+ 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: $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
+ 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
+
+ if [[ -n "$BIOS_DRY_RUN" ]]; then
+ 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).
+ BIOS_NEEDS_RESTART=1
+ 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.
+ 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/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 eb2d5e0..5c4b842 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,11 +16,24 @@ 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 "* ]]; }
+
+component_selectable() {
+ # Greyed out and not tickable when it has nothing to do.
+ case "$1" in
+ bios) bios_selectable ;;
+ esac
}
detect_components() {
@@ -27,16 +43,19 @@ 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
}
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
@@ -51,9 +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"
@@ -84,13 +111,18 @@ 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
+ 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} (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
@@ -137,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
@@ -160,6 +193,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 +211,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/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
diff --git a/setup-gamescope-boot.sh b/setup-gamescope-boot.sh
index 549ca1b..5da934b 100755
--- a/setup-gamescope-boot.sh
+++ b/setup-gamescope-boot.sh
@@ -15,11 +15,11 @@
set -uo pipefail
# Release version, see CHANGELOG.md.
-VERSION=0.7.0
+VERSION=0.8.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
@@ -39,47 +39,112 @@ 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
+ 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
-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 [[ "${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]%%:*} 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; }
+
+ # 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" || 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
-# Login manager changes only take effect after a restart.
-if [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" gaming "* || " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" single "* ]]; then
+# 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
+ 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
+else
+ info "Bye."
fi