diff --git a/AGENTS.md b/AGENTS.md index dfed8b4..8ae9fd8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -269,6 +269,16 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine latter only starts with `kcgroupsrc [Foreground Booster] autostart=true` (set with `kset`). Disable removes only the packages it installed (`state_get vram installed_pkgs`). +- Install-time mode (`steamify.sh --defaults`, 2.6.0): applies what the menu + would preselect, no menu or prompts (needs passwordless sudo). The Steam + Machine ISO ([steammachine-cachyos-live-iso](https://github.com/theupriser/steammachine-cachyos-live-iso)) + runs it in the installer for a user who has never logged in: no session + bus, user systemd or plasmashell. So every `systemctl --user` goes through + `user_systemctl` (`lib/common.sh`; without a session only unit files change, + via `--root=/`), and the theme, when the layout is still `/etc/skel`'s, + removes it so Plasma builds Vapor's layout at the first login. What needs + that layout (single user's launcher) runs then, from a one-time autostart + (`lib/first-login.sh`, `--first-login`) that also opens the app. - `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 48f8719..2befe61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,13 @@ 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. +## 2.6.0 - 2026-09-27 + +- **feat: --defaults applies the default setup without the menu, also from an installer (no session): the base for a Steam Machine ISO** + ## 2.5.2 - 2026-09-27 -- **docs: README screenshot at 2.5.2: everything on, nothing to update** +- `0c96bed` **docs: README screenshot at 2.5.2: everything on, nothing to update** ## 2.5.1 - 2026-09-27 diff --git a/TECHNICAL.md b/TECHNICAL.md index 657ee09..b245b58 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -445,6 +445,7 @@ immediately, which can turn into a loop - see | `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) | | `lib/steam-game.sh` | Add as non-Steam game: Steamify in the Steam library (`patches/steam-shortcuts.py`) | | `lib/update-notifier.sh` | Update notifications: the notifier from `patches/` and its user timer | +| `lib/first-login.sh` | `--defaults` without a session (the Steam Machine ISO's installer): the one-time first-login step that sets up single user's launcher on Plasma's new layout and opens the app | | `lib/vram-booster.sh` | VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) | | `services/` | The systemd units the scripts install (`service_file`, `@KEY@` placeholders); see its README | | `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README | diff --git a/lib/cec.sh b/lib/cec.sh index ffe570a..a075e5d 100644 --- a/lib/cec.sh +++ b/lib/cec.sh @@ -34,11 +34,11 @@ cec_link_steamos_manager() { # called by Steam Machine support, which installs steamos-manager after # this item ran. pacman -Q steamos-manager >/dev/null 2>&1 || return 0 - systemctl --user daemon-reload - systemctl --user enable steamos-manager-configure-cecd.service 2>/dev/null - systemctl --user start steamos-manager-configure-cecd.service 2>/dev/null - systemctl --user restart cecd.service 2>/dev/null - systemctl --user restart steamos-manager.service 2>/dev/null + user_systemctl daemon-reload + user_systemctl enable steamos-manager-configure-cecd.service 2>/dev/null + user_systemctl start steamos-manager-configure-cecd.service 2>/dev/null + user_systemctl restart cecd.service 2>/dev/null + user_systemctl restart steamos-manager.service 2>/dev/null return 0 } @@ -51,10 +51,10 @@ cec_status() { cec_installed && [[ -f "$CEC_STEAM_DROPIN" ]] && cec_driver_ok; } cec_reload_driver() { # cecd keeps /dev/cec0 open, so the old module can't be unloaded under it. - systemctl --user stop cecd.service 2>/dev/null + user_systemctl stop cecd.service 2>/dev/null sudo modprobe -r cros_ec_cec 2>/dev/null sudo modprobe cros_ec_cec 2>/dev/null - systemctl --user start cecd.service 2>/dev/null + user_systemctl start cecd.service 2>/dev/null } cec_driver_enable() { @@ -154,10 +154,10 @@ cec_enable() { sudo tee "$CEC_STEAM_ENV" >/dev/null printf '%s\n' "# Written by Steamify: overrides STEAM_ENABLE_CEC=0 from the gaming mode script." \ '[Service]' "EnvironmentFile=-$CEC_STEAM_ENV" | sudo tee "$CEC_STEAM_DROPIN" >/dev/null - systemctl --user daemon-reload + user_systemctl daemon-reload cec_link_steamos_manager if compgen -G "/dev/cec*" >/dev/null; then - systemctl --user restart cecd.service 2>/dev/null + user_systemctl restart cecd.service 2>/dev/null ok "HDMI-CEC on ($(cd /dev && echo cec*)); Steam shows its settings from the next gaming mode start." info "Turn on CEC on your TV too (e.g. Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink)." else @@ -168,20 +168,20 @@ cec_enable() { cec_disable() { info "Removing HDMI-CEC..." - systemctl --user disable --now cecd.service cec-audio-control.socket cec-audio-control.service 2>/dev/null - systemctl --user disable steamos-manager-configure-cecd.service 2>/dev/null + user_systemctl disable --now cecd.service cec-audio-control.socket cec-audio-control.service 2>/dev/null + user_systemctl disable steamos-manager-configure-cecd.service 2>/dev/null sudo pacman -Rns --noconfirm "${CEC_PKGS[@]}" 2>/dev/null cec_driver_disable sudo rm -f "$CEC_ORDER_DROPIN" "$CEC_STEAM_DROPIN" "$CEC_STEAM_ENV" sudo rmdir "$(dirname "$CEC_ORDER_DROPIN")" "$(dirname "$CEC_STEAM_DROPIN")" "$(dirname "$CEC_STEAM_ENV")" 2>/dev/null - systemctl --user daemon-reload + user_systemctl daemon-reload if [[ -n "$(state_get cec installed_linuxconsole)" ]]; then sudo pacman -Rns --noconfirm linuxconsole 2>/dev/null state_clear cec fi sudo udevadm control --reload - systemctl --user is-active -q steamos-manager.service 2>/dev/null && - systemctl --user restart steamos-manager.service + user_systemctl is-active -q steamos-manager.service 2>/dev/null && + user_systemctl restart steamos-manager.service ok "HDMI-CEC removed." } @@ -189,5 +189,5 @@ cec_overview() { # For the menu's kernel overview. local dev="none" compgen -G "/dev/cec*" >/dev/null && dev="$(cd /dev && echo cec*)" - echo "CEC devices: $dev cecd: $(systemctl --user is-active cecd 2>/dev/null)" + echo "CEC devices: $dev cecd: $(user_systemctl is-active cecd 2>/dev/null)" } diff --git a/lib/common.sh b/lib/common.sh index 478e5e1..648d18d 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -44,6 +44,30 @@ require_root_helper() { fi } +user_session() { + # A running user systemd and session bus. Not in the installer's chroot, + # where the ISO applies Steamify for a user who has never logged in. + [[ -S "${XDG_RUNTIME_DIR:-/nonexistent}/bus" ]] +} + +user_systemctl() { + # systemctl --user, also without a session: then only the unit files + # change (enable/disable through --root); starting and stopping is left + # to the first login, which starts what's enabled anyway. + if user_session; then systemctl --user "$@"; return; fi + local a verb="" args=() + for a in "$@"; do + [[ "$a" == --now ]] && continue + [[ -z "$verb" && "$a" != -* ]] && verb="$a" + args+=("$a") + done + case "$verb" in + enable|disable|is-enabled|mask|unmask) systemctl --user --root=/ "${args[@]}" ;; + is-active) return 1 ;; + *) return 0 ;; + esac +} + plasma_applets() { # Print "containment:applet" for every applet of plugin $1 in the # user's Plasma layout, for kwriteconfig6 --group paths. diff --git a/lib/first-login.sh b/lib/first-login.sh new file mode 100644 index 0000000..055b68b --- /dev/null +++ b/lib/first-login.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# The ISO applies Steamify in the installer, before the user ever logged +# in (steamify.sh --defaults without a session). What needs Plasma's first +# layout is finished at the first desktop login, by a one-time autostart +# that then opens the app, next to CachyOS Hello. +# Sourced by steamify.sh; not meant to be run on its own. + +FIRST_LOGIN_ENTRY="${XDG_CONFIG_HOME:-$HOME/.config}/autostart/steamify-first-login.desktop" +FIRST_LOGIN_SCRIPT="$STEAMIFY_BIN/first-login" + +first_login_schedule() { + install_executable "$FIRST_LOGIN_SCRIPT" 755 << EOF2 +#!/bin/bash +# Installed by Steamify CachyOS: runs once, at the first desktop login. +set -o pipefail +curl -fsSL --max-time 30 "$WIZARD_URL" | bash -s -- --first-login +rm -f "$FIRST_LOGIN_ENTRY" "\$0" +[[ -x "$WIZARD_APP_LAUNCHER" ]] && exec "$WIZARD_APP_LAUNCHER" +EOF2 + printf '%s\n' '[Desktop Entry]' 'Type=Application' 'Name=Steamify first login' \ + "Exec=$FIRST_LOGIN_SCRIPT" 'OnlyShowIn=KDE;' 'X-KDE-autostart-phase=2' 'NoDisplay=true' | + install_executable "$FIRST_LOGIN_ENTRY" 644 +} + +first_login_run() { + # plasmashell has just laid out the desktop from the look-and-feel + # package; wait for its config, then put single user's launcher + # settings on the new launcher. + local _ + for _ in $(seq 30); do + [[ -n "$(plasma_applets org.kde.plasma.kickoff)" ]] && break + sleep 2 + done + if single_status; then + stop_plasmashell_for_edit + single_launcher + restart_plasmashell_if_stopped + fi + return 0 +} diff --git a/lib/state.sh b/lib/state.sh index 8bef63c..818ee09 100644 --- a/lib/state.sh +++ b/lib/state.sh @@ -123,7 +123,7 @@ migrate_layout() { "$(wizard_desktop_dir)/$WIZARD_DESKTOP_NAME"; do [[ -f "$f" ]] && sed -i "s|$old_bin/|$STEAMIFY_BIN/|g" "$f" done - systemctl --user daemon-reload 2>/dev/null + user_systemctl daemon-reload 2>/dev/null fi if [[ -f "$old_icon" ]]; then if [[ -e "$WIZARD_ICON" ]]; then rm -f "$old_icon"; else mv "$old_icon" "$WIZARD_ICON"; fi diff --git a/lib/steam-desktop.sh b/lib/steam-desktop.sh index a240f2f..51216fe 100644 --- a/lib/steam-desktop.sh +++ b/lib/steam-desktop.sh @@ -22,18 +22,18 @@ steam_enable() { rm -f "$HOME/.config/autostart/steam.desktop" # Reload user systemd context and activate the background loop - systemctl --user daemon-reload - systemctl --user enable --now steam-desktop-autostart.service + user_systemctl daemon-reload + user_systemctl enable --now steam-desktop-autostart.service ok "Steam UI parameters and keyboard autostart deployed successfully." } steam_disable() { info "Removing Steam desktop autostart..." - systemctl --user disable --now steam-desktop-autostart.service 2>/dev/null + user_systemctl disable --now steam-desktop-autostart.service 2>/dev/null rm -f "$HOME/.config/systemd/user/steam-desktop-autostart.service" \ "$HOME/.config/environment.d/99-kde-virtual-keyboard.conf" - systemctl --user daemon-reload + user_systemctl daemon-reload ok "Steam desktop settings removed (takes full effect at next login)." } diff --git a/lib/steam-game.sh b/lib/steam-game.sh index c7ea9c8..ff85c82 100644 --- a/lib/steam-game.sh +++ b/lib/steam-game.sh @@ -72,8 +72,8 @@ steamgame_edit() { fi "$1"; local rc=$? if [[ "$was_running" == true ]]; then - if systemctl --user is-enabled -q steam-desktop-autostart.service 2>/dev/null; then - systemctl --user start steam-desktop-autostart.service + if user_systemctl is-enabled -q steam-desktop-autostart.service 2>/dev/null; then + user_systemctl start steam-desktop-autostart.service else systemd-run --user --collect -q steam -silent >/dev/null 2>&1 fi diff --git a/lib/steam-machine.sh b/lib/steam-machine.sh index 54634b5..66fe10d 100644 --- a/lib/steam-machine.sh +++ b/lib/steam-machine.sh @@ -405,7 +405,7 @@ EOF sudo pacman -S --needed --noconfirm steamos-manager inputplumber || { err "Installing hardware manager packages failed."; return 1; } sudo systemctl enable --now inputplumber.service sudo systemctl enable --now steamos-manager.service - systemctl --user enable steamos-manager.service 2>/dev/null + user_systemctl enable steamos-manager.service 2>/dev/null # HDMI-CEC ran before this and couldn't link cecd to steamos-manager yet. cec_status && cec_link_steamos_manager @@ -422,7 +422,7 @@ EOF machine_disable() { info "Removing Steam Machine support..." - systemctl --user disable --now steamos-manager.service 2>/dev/null + user_systemctl 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 sudo pacman -Rns --noconfirm steamos-manager inputplumber 2>/dev/null diff --git a/lib/update-notifier.sh b/lib/update-notifier.sh index 7f96cea..6b20c34 100644 --- a/lib/update-notifier.sh +++ b/lib/update-notifier.sh @@ -12,8 +12,8 @@ NOTIFY_NAME=steamify-update-check notify_status() { [[ -f "$NOTIFY_SCRIPT" ]] && - systemctl --user is-enabled -q "$NOTIFY_NAME.timer" 2>/dev/null && - systemctl --user is-enabled -q "$NOTIFY_NAME.service" 2>/dev/null + user_systemctl is-enabled -q "$NOTIFY_NAME.timer" 2>/dev/null && + user_systemctl is-enabled -q "$NOTIFY_NAME.service" 2>/dev/null } notify_seen() { @@ -40,19 +40,19 @@ notify_enable() { mkdir -p "$NOTIFY_UNITS" service_file "$NOTIFY_NAME.service" NOTIFY_SCRIPT="$NOTIFY_SCRIPT" > "$NOTIFY_UNITS/$NOTIFY_NAME.service" service_file "$NOTIFY_NAME.timer" > "$NOTIFY_UNITS/$NOTIFY_NAME.timer" - systemctl --user daemon-reload - systemctl --user enable "$NOTIFY_NAME.service" >/dev/null 2>&1 && - systemctl --user enable --now "$NOTIFY_NAME.timer" >/dev/null 2>&1 || + user_systemctl daemon-reload + user_systemctl enable "$NOTIFY_NAME.service" >/dev/null 2>&1 && + user_systemctl enable --now "$NOTIFY_NAME.timer" >/dev/null 2>&1 || { err "Enabling the update check failed."; return 1; } state_set notify seen "$VERSION" ok "Update notifications on: you'll get a notification when there's a new Steamify." } notify_disable() { - systemctl --user disable --now "$NOTIFY_NAME.timer" "$NOTIFY_NAME.service" >/dev/null 2>&1 + user_systemctl disable --now "$NOTIFY_NAME.timer" "$NOTIFY_NAME.service" >/dev/null 2>&1 rm -f "$NOTIFY_UNITS/$NOTIFY_NAME.service" "$NOTIFY_UNITS/$NOTIFY_NAME.timer" "$NOTIFY_SCRIPT" rmdir "$STEAMIFY_BIN" 2>/dev/null || true - systemctl --user daemon-reload + user_systemctl daemon-reload state_clear notify ok "Update notifications off." } diff --git a/lib/vapor-theme.sh b/lib/vapor-theme.sh index 45e7c7e..ba0be9d 100644 --- a/lib/vapor-theme.sh +++ b/lib/vapor-theme.sh @@ -54,9 +54,17 @@ theme_enable() { # Only remove it again on disable if we were the ones installing it. state_set theme installed_pkg 1 fi + # Without a session (the ISO's installer), for a user who has never + # logged in: the layout still CachyOS's default from /etc/skel is + # removed (after the backup below), and Plasma builds its first layout + # from the look-and-feel package, as --resetLayout does below. + local offline=false layout=~/.config/plasma-org.kde.plasma.desktop-appletsrc if [[ -z "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]]; then - err "Run this from the Plasma desktop: applying the Vapor layout needs a running Plasma session." - return 1 + if [[ -e "$layout" ]] && ! cmp -s "$layout" "/etc/skel/.config/${layout##*/}"; then + err "Run this from the Plasma desktop: applying the Vapor layout needs a running Plasma session." + return 1 + fi + offline=true fi # Only the first time, so a re-apply doesn't overwrite the originals. @@ -87,6 +95,12 @@ theme_enable() { kset theme kdeglobals General ColorScheme --delete plasma-apply-colorscheme Vapor >/dev/null 2>&1 || true + if [[ "$offline" == true ]]; then + rm -f ~/.config/plasma-org.kde.plasma.desktop-appletsrc ~/.config/plasmashellrc + extras_enable || warn "SteamOS desktop extras not installed (see above); the theme itself is on." + ok "Vapor theme set; Plasma lays out the Vapor desktop at the first login." + return 0 + fi info "Applying the Vapor theme with its desktop and window layout..." # --resetLayout is "Desktop and window layout" in System Settings: the # SteamOS panel, launcher icon and wallpaper. It needs plasmashell up. diff --git a/lib/vram-booster.sh b/lib/vram-booster.sh index 8dce31d..8336d08 100644 --- a/lib/vram-booster.sh +++ b/lib/vram-booster.sh @@ -101,7 +101,7 @@ vram_selectable() { vram_supported || vram_status; } vram_status() { pacman -Q "${VRAM_PKGS[@]}" >/dev/null 2>&1 && systemctl is-enabled -q dmemcg-booster-system.service 2>/dev/null && - systemctl --user is-enabled -q dmemcg-booster-user.service 2>/dev/null + user_systemctl is-enabled -q dmemcg-booster-user.service 2>/dev/null } vram_enable() { @@ -115,18 +115,18 @@ vram_enable() { fi sudo systemctl enable --now dmemcg-booster-system.service >/dev/null 2>&1 || { err "Starting dmemcg-booster failed."; return 1; } - systemctl --user enable --now dmemcg-booster-user.service >/dev/null 2>&1 || + user_systemctl enable --now dmemcg-booster-user.service >/dev/null 2>&1 || { err "Starting dmemcg-booster for $USER failed."; return 1; } kset vram kcgroupsrc "Foreground Booster" autostart true # Started by the Plasma session from now on; start it in this one too. [[ "${XDG_CURRENT_DESKTOP:-}" == KDE ]] && - systemctl --user start plasma-foreground-booster.service >/dev/null 2>&1 + user_systemctl start plasma-foreground-booster.service >/dev/null 2>&1 ok "VRAM booster on: the game in front keeps its VRAM." } vram_disable() { local pkgs - systemctl --user disable --now plasma-foreground-booster.service dmemcg-booster-user.service >/dev/null 2>&1 + user_systemctl disable --now plasma-foreground-booster.service dmemcg-booster-user.service >/dev/null 2>&1 sudo systemctl disable --now dmemcg-booster-system.service >/dev/null 2>&1 krevert vram pkgs="$(state_get vram installed_pkgs)" diff --git a/steamify.sh b/steamify.sh index 0a3e0aa..1290d59 100755 --- a/steamify.sh +++ b/steamify.sh @@ -15,11 +15,11 @@ set -uo pipefail # Release version, see CHANGELOG.md. -VERSION=2.5.2 +VERSION=2.6.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut steam-game update-notifier menu backend; do +for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut steam-game update-notifier first-login menu backend; do # shellcheck source=/dev/null source "$SCRIPT_DIR/lib/$lib.sh" done @@ -28,6 +28,10 @@ require_root_helper BACKEND=false [[ "${1:-}" == --backend ]] && BACKEND=true +# --defaults: apply what the menu would preselect, without the menu or any +# prompt (the Steam Machine ISO's first login runs this; sudo must not ask). +DEFAULTS=false +[[ "${1:-}" == --defaults ]] && DEFAULTS=true if [[ "$BACKEND" == false ]]; then echo -e "${c_bold}Steamify CachyOS${c_reset} v$VERSION" @@ -114,6 +118,25 @@ if [[ "$BACKEND" == true ]]; then exit $? fi +if [[ "${1:-}" == --first-login ]]; then + first_login_run + exit 0 +fi + +if [[ "$DEFAULTS" == true ]]; then + RESTART_FOR_LOGIN=false + REAPPLY=false + sudo -n true 2>/dev/null || { err "--defaults needs sudo without a password."; exit 1; } + detect_components + plan_changes + apply_changes + # Run by the installer: the rest waits for the first desktop login. + user_session || first_login_schedule + [[ ${#FAILED[@]} -eq 0 ]] || { warn "These had problems (see above): ${FAILED[*]}"; exit 1; } + ok "Done; the changes take effect after a restart." + exit 0 +fi + # 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