From c4bd1ca39c39e3187db79ce1a07401e0044da7f8 Mon Sep 17 00:00:00 2001 From: Rick Peters Date: Fri, 25 Sep 2026 07:58:02 +0200 Subject: [PATCH] feat: Pin the kernel to 7.1.6-1 on a Steam Machine Sub-option of Steam Machine support, ticked along with it: newer CachyOS kernels make the Steam Machine reboot instead of shutting down. Packages are kept in /var/cache/steamify/kernel and reinstalled from there; else pacman's cache, the CachyOS archive or mirror, or PINNED_KERNEL_URL. --- CHANGELOG.md | 14 ++++ README.md | 22 ++++++ lib/menu.sh | 20 ++++-- lib/steam-machine.sh | 167 ++++++++++++++++++++++++++++++++----------- steamify.sh | 7 +- 5 files changed, 180 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5b7054..a16092f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes, per version and per commit. Versions follow `steamify.sh`. Versions before 0.7.0 were numbered afterwards, one per merged pull request. +## 0.11.0 - 2026-09-25 + +- **feat: Pin the kernel to 7.1.6-1 on a Steam Machine** + - New sub-option under Steam Machine support, ticked along with it (opt + out): newer CachyOS kernels make the Steam Machine reboot instead of + shutting down. Installs `linux-cachyos` and `-headers` 7.1.6-1 and adds + them to `IgnorePkg`; unticking removes the pin and updates the system. + - The packages are kept in `/var/cache/steamify/kernel` and reinstalled + from there without downloading; else from pacman's cache, the CachyOS + archive, the CachyOS mirror, or `PINNED_KERNEL_URL`. + - Installed kernel headers are no longer updated by the wizard, so the + pinned kernel keeps matching headers. + - The BIOS item moves down one (9 on a Steam Machine). + ## 0.10.0 - 2026-09-24 - `0f503dd` **feat: Boot into gamescope or the desktop** diff --git a/README.md b/README.md index 3e15de8..db694dd 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ Everything you turn off is put back the way it was. 7. **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). + - **Pin the kernel to 7.1.6-1** - on by default with Steam Machine + support (untick to opt out): newer CachyOS kernels make the Steam + Machine reboot instead of shutting down (see + [Kernel pin](#kernel-pin-steam-machine)). 8. **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)). @@ -291,6 +295,24 @@ or upgraded. A newly added kernel doesn't come with its headers, so `ensure-kernel-headers.service` checks at every boot and installs any missing `-headers` package, which makes DKMS build the driver for it. +### Kernel pin (Steam Machine) + +With CachyOS kernels newer than 7.1.6 a Steam Machine reboots instead of +shutting down. The **Pin the kernel** sub-option (ticked along with Steam +Machine support) installs `linux-cachyos` and `linux-cachyos-headers` +7.1.6-1 and adds them to `IgnorePkg` in `/etc/pacman.conf`, so updates skip +them. DKMS builds the LED driver for it; restart to boot it. + +The packages (and their signatures, which pacman checks) are kept in +`/var/cache/steamify/kernel`, so re-applying needs no download. Missing +files are taken from pacman's cache, else downloaded from +`archive.cachyos.org`, then `mirror.cachyos.org` (which only has the current +kernel). Set `PINNED_KERNEL_URL` to a directory URL with the files to try +another source first, or drop them into the kernel directory yourself. + +Unticking it removes the pin and runs `sudo pacman -Syu`, which brings the +kernel back to CachyOS's current version; the files stay for next time. + Troubleshooting: ```bash diff --git a/lib/menu.sh b/lib/menu.sh index c859488..372495b 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -5,10 +5,12 @@ # Menu order. Components are turned on in this order and off in reverse; # gaming must come first (single user builds on it). -COMPONENTS=(gaming boot theme glyphs single launcher machine bios) +COMPONENTS=(gaming boot theme glyphs single launcher machine kpin bios) # One-off actions rather than on/off components: never preselected, never # re-applied, not listed as on or off. ACTIONS=(bios) +# Sub-options, shown indented under their parent and only while it's ticked. +declare -A PARENT=([boot]=gaming [kpin]=machine) # Never preselected on a first run: booting into the desktop is a choice, # gamescope is the default. NO_PRESELECT=(boot) @@ -21,13 +23,14 @@ declare -A LABEL=( [single]="Single user mode: no password, lock screen or log out (SDDM)" [launcher]="Steamify shortcut: desktop icon to run this again" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" + [kpin]="Pin the kernel to $PINNED_KERNEL_VER (fixes rebooting after shutdown)" [bios]="Update BIOS" ) declare -A CURRENT WANTED component_available() { case "$1" in - machine) machine_available ;; + machine|kpin) machine_available ;; bios) bios_available ;; esac } @@ -41,10 +44,10 @@ boot_mode() { } menu_visible() { - # Shown in the menu. "Boot into" is a sub-option of the conversion: only - # while the conversion is ticked. + # Shown in the menu. A sub-option ("Boot into", the kernel pin) only + # while its parent is ticked. component_available "$1" || return 1 - [[ "$1" != boot || "${WANTED[gaming]:-0}" == 1 ]] + [[ -z "${PARENT[$1]:-}" || "${WANTED[${PARENT[$1]}]:-0}" == 1 ]] } component_selectable() { @@ -81,6 +84,9 @@ toggle_component() { if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; WANTED[boot]=0; fi # Where to boot to is part of the conversion, too. if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi + # The kernel pin is opt-out: ticked along with Steam Machine support. + if [[ "$c" == machine ]]; then WANTED[kpin]=${WANTED[machine]}; fi + if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi } show_menu() { @@ -103,6 +109,8 @@ show_menu() { [[ "${CURRENT[gaming]}" == 1 ]] || now="-" want="gaming"; [[ "${WANTED[boot]}" == 1 ]] && want="desk" printf " %-3s %-6s %-6s └ %s\n" "$i" "$now" "$want" "$(boot_choice "${WANTED[boot]}")" + elif [[ -n "${PARENT[$c]:-}" ]]; then + printf " %-3s %b %-6s └ %s\n" "$i" "$now" "$want" "${LABEL[$c]}" elif component_selectable "$c"; then printf " %-3s %b %-6s %s\n" "$i" "$now" "$want" "${LABEL[$c]}" else @@ -149,6 +157,8 @@ draw_menu_tui() { local mode=gamescope; [[ "${CURRENT[boot]}" == 1 ]] && mode=desktop state=" (${c_bold}←/→${c_reset} choose)" [[ "${CURRENT[gaming]}" == 1 ]] && state=" (now: $mode; ${c_bold}←/→${c_reset} choose)" + elif [[ -n "${PARENT[$c]:-}" ]]; then + line=" └ $line" fi if ! component_selectable "$c"; then # Greyed out: nothing to do (e.g. BIOS already up to date). diff --git a/lib/steam-machine.sh b/lib/steam-machine.sh index 1154185..7dca52f 100644 --- a/lib/steam-machine.sh +++ b/lib/steam-machine.sh @@ -14,58 +14,145 @@ detect_valve_fremont() { [[ "$vendor" == "OEM" && "$product" == "F7F" ]] } +# Kernel pinned on a Steam Machine: with newer linux-cachyos releases it +# reboots instead of shutting down. The packages are kept in +# PINNED_KERNEL_DIR, so re-applying (or reinstalling after an update slipped +# through) needs no download. +PINNED_KERNEL_VER="7.1.6-1" +PINNED_KERNEL_KVER="7.1.6-1-cachyos" +PINNED_KERNEL_PKGS=(linux-cachyos linux-cachyos-headers) +PINNED_KERNEL_DIR="${PINNED_KERNEL_DIR:-/var/cache/steamify/kernel}" +# Tried in order, after the kernel dir and pacman's cache. The archive keeps +# every release; the mirror only the current one. PINNED_KERNEL_URL puts +# another source (a directory holding the files) in front. +PINNED_KERNEL_SOURCES=( + ${PINNED_KERNEL_URL:+"$PINNED_KERNEL_URL"} + "https://archive.cachyos.org/archive/cachyos" + "https://mirror.cachyos.org/repo/x86_64/cachyos" +) + +pinned_kernel_installed() { + local p + for p in "${PINNED_KERNEL_PKGS[@]}"; do + [[ "$(pacman -Q "$p" 2>/dev/null)" == "$p $PINNED_KERNEL_VER" ]] || return 1 + done +} + +fetch_pinned_kernel_file() { + # $1 = file name. Local kernel dir first, then pacman's cache, then the + # download sources. Downloads go to a .part file so a broken one is never + # mistaken for a finished one. + local f="$1" dest="$PINNED_KERNEL_DIR/$1" src + [[ -s "$dest" ]] && return 0 + if [[ -s "/var/cache/pacman/pkg/$f" ]]; then + sudo cp "/var/cache/pacman/pkg/$f" "$dest" && return 0 + fi + for src in "${PINNED_KERNEL_SOURCES[@]}"; do + if sudo curl -fL --retry 2 --connect-timeout 15 -o "$dest.part" "$src/$f" 2>/dev/null; then + sudo mv "$dest.part" "$dest" + return 0 + fi + warn "Couldn't get $f from $src, trying the next source..." + done + sudo rm -f "$dest.part" + return 1 +} + +pin_kernel_in_pacman_conf() { + # Adds our packages to IgnorePkg in [options], keeping what's there. + local p + for p in "${PINNED_KERNEL_PKGS[@]}"; do + grep -Eq "^IgnorePkg\s*=.*(\s|=)$p(\s|$)" /etc/pacman.conf && continue + if grep -Eq '^IgnorePkg\s*=' /etc/pacman.conf; then + sudo sed -i -E "0,/^IgnorePkg\s*=/s/^(IgnorePkg\s*=.*)$/\1 $p/" /etc/pacman.conf + else + sudo sed -i -E "0,/^\[options\]/s//[options]\nIgnorePkg = $p/" /etc/pacman.conf + fi + done +} + +unpin_kernel_in_pacman_conf() { + local p + for p in "${PINNED_KERNEL_PKGS[@]}"; do + sudo sed -i -E "/^IgnorePkg\s*=/s/\s$p(\s|$)/\1/" /etc/pacman.conf + done + # Drop the line if nothing is left on it. + sudo sed -i -E '/^IgnorePkg\s*=\s*$/d' /etc/pacman.conf +} + install_pinned_kernel() { - # Define urls for downloading the kernel and it's headers - local KERNEL_URL="https://archive.cachyos.org/archive/cachyos/linux-cachyos-7.1.6-1-x86_64.pkg.tar.zst" - local KERNEL_SIG_URL="https://archive.cachyos.org/archive/cachyos/linux-cachyos-7.1.6-1-x86_64.pkg.tar.zst.sig" - local HEADERS_URL="https://archive.cachyos.org/archive/cachyos/linux-cachyos-headers-7.1.6-1-x86_64.pkg.tar.zst" - local HEADERS_SIG_URL="https://archive.cachyos.org/archive/cachyos/linux-cachyos-headers-7.1.6-1-x86_64.pkg.tar.zst.sig" - - # Resolve filenames - local KERNEL_PKG=$(basename "$KERNEL_URL") - local HEADERS_PKG=$(basename "$HEADERS_URL") - - info "Downloading CachyOS kernel packages and headers..." - wget -c "$KERNEL_URL" "$KERNEL_SIG_URL" "$HEADERS_URL" "$HEADERS_SIG_URL" || { err "Failed to download kernel packages."; return 1; } - - info "Installing packages and verifying signatures via pacman..." - sudo pacman -U --noconfirm "$KERNEL_PKG" "$HEADERS_PKG" || { err "Failed to install packages."; return 1; } - - info "Locking the kernel packages to prevent updates..." - sudo sed -i '/^IgnorePkg/s/ linux-cachyos linux-cachyos-headers//' /etc/pacman.conf - sudo sed -i '/^#IgnorePkg/s/^#//' /etc/pacman.conf - sudo sed -i '/^IgnorePkg/ s/$/ linux-cachyos linux-cachyos-headers/' /etc/pacman.conf - - # FIX: Recover symlinks directly to accompany led install - local CURRENT_KVER="7.1.6-1-cachyos" - if [ -d "/usr/src/linux-${CURRENT_KVER}" ]; then - info "Koppelen van kernel-headers symlink..." - sudo ln -snf "/usr/src/linux-${CURRENT_KVER}" "/usr/lib/modules/${CURRENT_KVER}/build" + sudo mkdir -p "$PINNED_KERNEL_DIR" + pin_kernel_in_pacman_conf + if pinned_kernel_installed; then + ok "Kernel $PINNED_KERNEL_VER already installed and pinned." + return 0 fi - rm -f "$KERNEL_PKG" "${KERNEL_PKG}.sig" "$HEADERS_PKG" "${HEADERS_PKG}.sig" - ok "Custom kernel setup finished." + local p f files=() + info "Getting kernel $PINNED_KERNEL_VER (kept in $PINNED_KERNEL_DIR)..." + for p in "${PINNED_KERNEL_PKGS[@]}"; do + f="$p-$PINNED_KERNEL_VER-x86_64.pkg.tar.zst" + fetch_pinned_kernel_file "$f" && fetch_pinned_kernel_file "$f.sig" || + { err "Couldn't download $f from any source."; return 1; } + files+=("$PINNED_KERNEL_DIR/$f") + done + + # pacman checks each package against the .sig next to it. + info "Installing kernel $PINNED_KERNEL_VER..." + if ! sudo pacman -U --noconfirm "${files[@]}"; then + err "Installing kernel $PINNED_KERNEL_VER failed. If a file is damaged, delete it from $PINNED_KERNEL_DIR and try again." + return 1 + fi + [[ "$(uname -r)" != "$PINNED_KERNEL_KVER" ]] && RESTART_FOR_LOGIN=true + ok "Kernel $PINNED_KERNEL_VER installed and pinned (restart to use it)." } +remove_kernel_pin() { + # Back to CachyOS's current kernel. The packages stay in + # PINNED_KERNEL_DIR, so turning support on again doesn't download. + unpin_kernel_in_pacman_conf + pinned_kernel_installed || return 0 + info "Updating the kernel back to CachyOS's current version..." + sudo pacman -Syu --noconfirm || + { warn "Updating the kernel failed; run: sudo pacman -Syu"; return 0; } + RESTART_FOR_LOGIN=true +} + +# "Pin the kernel" menu item, a sub-option of Steam Machine support. +kpin_status() { + pinned_kernel_installed && grep -Eq '^IgnorePkg\s*=.*\slinux-cachyos(\s|$)' /etc/pacman.conf +} + +kpin_enable() { + install_pinned_kernel || return 1 + # The DKMS pacman hook built leds-valve for the new headers; make sure. + if pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && + ! dkms status -k "$PINNED_KERNEL_KVER" leds-valve-dkms 2>/dev/null | grep -q installed; then + sudo dkms install leds-valve-dkms/0.1 -k "$PINNED_KERNEL_KVER" || + warn "Building the LED driver for $PINNED_KERNEL_KVER failed." + fi +} + +kpin_disable() { remove_kernel_pin; } + install_kernel_headers() { # DKMS can only build leds-valve against kernels whose headers are # installed. CachyOS ships several kernels (linux-cachyos, -lts, -bore, # ...); install the matching -headers package for every one present. local -a kernels headers=() local k - local target_kver="7.1.6-1-cachyos" - - # FIX: Als de specifieke 7.1.6 headers al lokaal op de schijf staan, hoeft pacman niks te doen - if [ -d "/usr/src/linux-${target_kver}" ] || [ -d "/usr/lib/modules/${target_kver}/build" ]; then - info "Geverifieerd: Gevolgde CachyOS 7.1.6 headers zijn reeds lokaal aanwezig. Pacman-update overgeslagen." - return 0 - fi - - # Originele fallback-loop voor het geval er andere kernels actief zijn mapfile -t kernels < <(pacman -Qqo /usr/lib/modules/*/pkgbase 2>/dev/null | sort -u) + # Installed headers are left alone: -S would update them past a pinned + # kernel (linux-cachyos-headers on a Steam Machine). + local have=0 for k in "${kernels[@]}"; do + pacman -Q "${k}-headers" >/dev/null 2>&1 && { have=$((have + 1)); continue; } pacman -Si "${k}-headers" >/dev/null 2>&1 && headers+=("${k}-headers") done + if [[ ${#headers[@]} -eq 0 && $have -gt 0 ]]; then + ok "Kernel headers already installed." + return 0 + fi if [[ ${#headers[@]} -eq 0 ]]; then warn "Couldn't work out which kernel headers package you need." warn "Install the -headers package for your kernel (e.g. linux-cachyos-headers) yourself." @@ -243,9 +330,6 @@ machine_status() { } machine_enable() { - # Install and pin the specific CachyOS kernel first - install_pinned_kernel || { err "Custom kernel setup failed."; return 1; } - ensure_aur_helper || { warn "Couldn't set up an AUR helper automatically. Install yay or paru, then run the wizard again."; return 1; } install_valve_led_driver || { warn "LED driver setup ran into a problem - see errors above."; return 1; } install_headers_boot_check @@ -282,9 +366,6 @@ EOF machine_disable() { info "Removing Steam Machine support..." - # Disables services, removes packages, and unpins the kernel - sudo sed -i '/^IgnorePkg/s/ linux-cachyos linux-cachyos-headers//' /etc/pacman.conf - sudo pacman -Syu --noconfirm linux-cachyos linux-cachyos-headers || { warn "Kernel upgrade issues."; } systemctl --user disable --now steamos-manager.service 2>/dev/null sudo systemctl disable --now steamos-manager.service 2>/dev/null sudo systemctl disable --now inputplumber.service 2>/dev/null diff --git a/steamify.sh b/steamify.sh index 313bb2b..2c50f3d 100755 --- a/steamify.sh +++ b/steamify.sh @@ -15,7 +15,7 @@ set -uo pipefail # Release version, see CHANGELOG.md. -VERSION=0.10.0 +VERSION=0.11.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -139,7 +139,7 @@ while true; do apply_changes # Login manager changes only take effect after a restart. - [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot)\ ]] && + [[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot|kpin)\ ]] && RESTART_FOR_LOGIN=true echo @@ -149,6 +149,9 @@ while true; do component_available "$c" && ! is_action "$c" || continue if [[ "$c" == boot ]]; then [[ "${CURRENT[gaming]}" == 1 ]] && echo " └ boots into: $(boot_mode "${CURRENT[boot]}")" + elif [[ -n "${PARENT[$c]:-}" ]]; then + [[ "${CURRENT[${PARENT[$c]}]}" == 1 ]] || continue + if [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " └ ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " └ off ${LABEL[$c]}"; fi elif [[ "${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