mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 01:00:13 +02:00
Install: name what waits for a restart; uninstall survives a failed Nix uninstall
A fresh install into a running session left the keyboard layout and the SteamVR dashboard patches (VR pet, the VR keyboard's suggestion strip, ...) silently off until a reboot: gamescope reads XKB_DEFAULT_* only at its start, and SteamVR opens its DevTools port (VRWebHelper.DebuggerEnabled) only at its start. Neither can be applied at runtime. - install.sh restart-check (run on every switch by session.nix, and by install at its end): compares the keyboard-layout drop-in with the running gamescope session's environment and checks whether SteamVR runs without port 8087; install ends with "Restart once" listing them. - uninstall: a failing nix-installer uninstall (e.g. /nix busy) no longer aborts the script before the ~/.config/home-manager link and Home Manager state are removed; it says to reboot and run uninstall again. - cleanup check: restart-check on fake cgroup/proc dirs.
This commit is contained in:
1 parent
92e9d17e09
commit
6b12d5329f
7 files changed
+191
-14
No files matched your search
@@ -81,7 +81,13 @@ The installer installs Nix (skipped if Nix already works), uses
|
||||
`~/.config/home-manager` or `--flake <dir-or-flakeref>` (if there is none, it
|
||||
creates `~/nix-config` from the [template](template) with your user name) and
|
||||
runs `home-manager switch`; what it sets up is listed under
|
||||
[Set up by install.sh](#set-up-by-installsh). Re-running it just switches
|
||||
[Set up by install.sh](#set-up-by-installsh). Everything works in the running
|
||||
session right away, except two settings read only at a process start: the
|
||||
[keyboard layout](docs/session.md#keyboard-layout) (next Steam session start)
|
||||
and the [SteamVR dashboard patches](docs/steamvr-debugger.md) (VR pet, the VR
|
||||
keyboard's suggestion strip, window curvature and the other patches of
|
||||
SteamVR's dashboard; next SteamVR start). If you use them, the installer ends
|
||||
with "Restart once": reboot once. Re-running it just switches
|
||||
again (`--yes` answers every question). Afterwards edit
|
||||
`~/nix-config/home.nix` and switch (see [Usage](#usage)).
|
||||
|
||||
@@ -482,7 +488,10 @@ stops Home Manager's user services (reverting the UI patches), runs
|
||||
`cleanup --all`, runs `home-manager uninstall`, then removes Nix and the
|
||||
per-user Nix state (see [Set up by install.sh](#set-up-by-installsh)). If
|
||||
SteamVR is running, its key is restored when SteamVR stops (the closing
|
||||
message says so). Your configuration, `*.hm-backup-*` files, app data and
|
||||
message says so). If Nix's own uninstaller fails (e.g. something still uses
|
||||
`/nix`), the rest still runs (the `~/.config/home-manager` link goes) except
|
||||
the per-user Nix state, and the closing message says to reboot and run
|
||||
`uninstall` again. Your configuration, `*.hm-backup-*` files, app data and
|
||||
Flatpaks stay.
|
||||
|
||||
To drop steam-frame-nix from a Home Manager configuration you keep, first
|
||||
|
||||
+2
-2
@@ -47,8 +47,8 @@ scripts/
|
||||
modules/
|
||||
session.nix switch: outer bus/runtime dir, user services on switch
|
||||
cleanup.nix switch: `cleanup --orphans`; steam-frame-nix-cleanup on PATH
|
||||
cleanup/package.nix build: install.sh as a command (cleanup, steamvr-debugger-arm)
|
||||
cleanup/check.nix test: install.sh cleanup on fake home/runtime dirs
|
||||
cleanup/package.nix build: install.sh as a command (cleanup, steamvr-debugger-arm, restart-check)
|
||||
cleanup/check.nix test: install.sh cleanup and restart-check on fake home/runtime dirs
|
||||
portal.nix Steam session portal config (session.portalFix)
|
||||
portal/check.nix test: KDE FileChooser on by default, absent when disabled
|
||||
applications-menu.nix applications.menu link for KDE apps (session.applicationsMenu)
|
||||
|
||||
+8
-1
@@ -23,6 +23,9 @@ running") and skips `reloadSystemd`.
|
||||
- After every switch this module reloads the Steam session's user manager
|
||||
and applies `session.services.start` / `stop` / `restart`, which other
|
||||
modules fill (you can add your own units).
|
||||
- Then it names what the running session can't pick up until a restart:
|
||||
the [keyboard layout](#keyboard-layout) and the
|
||||
[SteamVR debugger](steamvr-debugger.md) of the dashboard patches.
|
||||
|
||||
**Configuration:** apps that keep secrets in the wallet get a
|
||||
[launcher](launchers.md) with `keyring.enable`. Another app that must reach
|
||||
@@ -104,7 +107,11 @@ the link dangles, which is the same as no menu.
|
||||
`~/.config/environment.d` isn't read on the Frame.
|
||||
|
||||
**What you get:** the layout in the Steam session, from the next Steam
|
||||
session start. The layout also picks the
|
||||
session start (e.g. reboot): gamescope reads it only when it starts, and
|
||||
sends its own keymap to the X apps again whenever the VR keyboard types, so
|
||||
it can't be set in a running session. While the running session has another
|
||||
layout, the switch (and `install.sh install` at its end) says so
|
||||
(`install.sh restart-check`). The layout also picks the
|
||||
[VR keyboard](keyboard.md#swipe-and-suggestions)'s default dictionary
|
||||
language.
|
||||
|
||||
|
||||
@@ -21,8 +21,11 @@ to its previous value when SteamVR stops, also after a rollback or uninstall
|
||||
(without Nix). A value you set to `true` yourself is never touched.
|
||||
|
||||
**The first time, restart SteamVR once** (e.g. reboot); until then the
|
||||
dashboard patches wait. Turned off, the setting is restored at the switch
|
||||
(or when SteamVR stops, if it runs).
|
||||
dashboard patches wait. SteamVR opens the port only when it starts, so no
|
||||
switch or install can do it for a running SteamVR; the switch (and
|
||||
`install.sh install` at its end) says so while SteamVR runs without the port
|
||||
(`install.sh restart-check`). Turned off, the setting is restored at the
|
||||
switch (or when SteamVR stops, if it runs).
|
||||
|
||||
## Security
|
||||
|
||||
|
||||
+112
-4
@@ -90,6 +90,12 @@ Commands:
|
||||
Internal (run before each SteamVR start by steamFrame.steamvrDebugger):
|
||||
set VRWebHelper.DebuggerEnabled until SteamVR stops.
|
||||
|
||||
restart-check
|
||||
Internal (run on every switch, and by install): name what of the
|
||||
linked configuration only takes effect after a restart of the Steam
|
||||
session or SteamVR (the keyboard layout, the SteamVR dashboard
|
||||
patches).
|
||||
|
||||
Options:
|
||||
--yes, -y Don't ask; answer yes to every question.
|
||||
--help, -h Show this help.
|
||||
@@ -218,11 +224,13 @@ install_nix() {
|
||||
}
|
||||
|
||||
uninstall_nix() {
|
||||
local rc=0
|
||||
need_sudo
|
||||
ro_unlock
|
||||
step "Uninstalling Nix"
|
||||
sudo "$NIX_INSTALLER_BIN" uninstall --no-confirm
|
||||
sudo "$NIX_INSTALLER_BIN" uninstall --no-confirm || rc=$?
|
||||
ro_relock
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
ensure_nix() {
|
||||
@@ -357,6 +365,10 @@ cmd_install() {
|
||||
step "Activating Home Manager (conflicting files are renamed to *.$backup)"
|
||||
hm switch --flake "$ref" -b "$backup"
|
||||
|
||||
# Settings read only at a process start (the activation warned already).
|
||||
local pending=()
|
||||
mapfile -t pending < <(restart_pending)
|
||||
|
||||
local switch_cmd="home-manager switch --flake $ref" edit="your configuration"
|
||||
if [[ -n $dir ]]; then
|
||||
edit="$dir/home.nix"
|
||||
@@ -367,6 +379,11 @@ cmd_install() {
|
||||
fi
|
||||
|
||||
step "Done"
|
||||
if (( ${#pending[@]} )); then
|
||||
printf '\n%sRestart once%s (reboot, or restart the Steam session) for:\n' "$Y" "$N"
|
||||
printf ' - %s\n' "${pending[@]}"
|
||||
printf '%s\n' " Everything else already works in the running session."
|
||||
fi
|
||||
cat <<EOF
|
||||
|
||||
Next steps:
|
||||
@@ -908,6 +925,85 @@ cmd_cleanup() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- what waits for a restart -------------------------------------------------
|
||||
#
|
||||
# Two settings are read only when their process starts, so a switch (or the
|
||||
# first install) can't apply them to the running session:
|
||||
# - the keyboard layout (keyboard-layout.nix: XKB_DEFAULT_* in a drop-in on
|
||||
# gamescope-session.service), read by gamescope at its start;
|
||||
# - SteamVR's DevTools port (steamvr-debugger.nix: a Wants= drop-in on
|
||||
# steamvr.service sets VRWebHelper.DebuggerEnabled before SteamVR starts),
|
||||
# which the dashboard patches (VR pet, the keyboard's suggestion strip, ...)
|
||||
# need.
|
||||
# Both are compared with the running session; neither can be applied at
|
||||
# runtime (gamescope sends its own keymap to Xwayland again, e.g. whenever
|
||||
# the VR keyboard types, and only a SteamVR start opens the port).
|
||||
|
||||
SFN_PROC="${STEAM_FRAME_NIX_PROC:-/proc}"
|
||||
SFN_CGROUP="${STEAM_FRAME_NIX_CGROUP:-/sys/fs/cgroup}"
|
||||
LAYOUT_DROPIN="$USER_UNIT_DIR/gamescope-session.service.d/keyboard.conf"
|
||||
DEBUGGER_HM_DROPIN="$USER_UNIT_DIR/steamvr.service.d/webhelper-debugger.conf"
|
||||
DEBUGGER_PORT=8087
|
||||
|
||||
# Value of Environment=<name>=... in a unit file (empty if unset).
|
||||
unit_env() { # file name
|
||||
sed -n "s/^Environment=$2=//p" "$1" 2>/dev/null | tail -n1
|
||||
}
|
||||
|
||||
# XKB_DEFAULT_<what> of the running gamescope session: from the first
|
||||
# process of gamescope-session.service whose environment is readable
|
||||
# (gamescope itself isn't: it has capabilities). Fails if unknown.
|
||||
session_xkb() { # LAYOUT|VARIANT
|
||||
local cg pid env found=1
|
||||
[[ $(c_systemctl show -p ActiveState --value gamescope-session.service 2>/dev/null) == active ]] || return 1
|
||||
cg="$(c_systemctl show -p ControlGroup --value gamescope-session.service 2>/dev/null)" && [[ -n $cg ]] || return 1
|
||||
[[ -r $SFN_CGROUP$cg/cgroup.procs ]] || return 1
|
||||
while read -r pid; do
|
||||
env="$( { tr '\0' '\n' <"$SFN_PROC/$pid/environ"; } 2>/dev/null)" || continue
|
||||
[[ -n $env ]] || continue
|
||||
sed -n "s/^XKB_DEFAULT_$1=//p" <<<"$env" | tail -n1
|
||||
found=0
|
||||
break
|
||||
done <"$SFN_CGROUP$cg/cgroup.procs"
|
||||
return $found
|
||||
}
|
||||
|
||||
# Whether something listens on 127.0.0.1/::1/any :<port> (/proc/net/tcp*).
|
||||
port_listening() { # port
|
||||
local hex
|
||||
local hex f files=()
|
||||
printf -v hex '%04X' "$1"
|
||||
for f in "$SFN_PROC/net/tcp" "$SFN_PROC/net/tcp6"; do [[ -r $f ]] && files+=("$f"); done
|
||||
(( ${#files[@]} )) || return 1
|
||||
awk -v p=":$hex" 'FNR > 1 && $4 == "0A" && substr($2, length($2) - 4) == p { f = 1 } END { exit !f }' "${files[@]}"
|
||||
}
|
||||
|
||||
# One line per setting that waits for a restart.
|
||||
restart_pending() {
|
||||
local want_l want_v run_l run_v
|
||||
if [[ -e $LAYOUT_DROPIN ]]; then
|
||||
want_l="$(unit_env "$LAYOUT_DROPIN" XKB_DEFAULT_LAYOUT)"
|
||||
want_v="$(unit_env "$LAYOUT_DROPIN" XKB_DEFAULT_VARIANT)"
|
||||
if run_l="$(session_xkb LAYOUT)" && run_v="$(session_xkb VARIANT)" \
|
||||
&& [[ $run_l != "$want_l" || $run_v != "$want_v" ]]; then
|
||||
echo "keyboard layout ${want_l:-us}${want_v:+ ($want_v)} (the running Steam session has ${run_l:-us}${run_v:+ ($run_v)}): from the next start of the Steam session"
|
||||
fi
|
||||
fi
|
||||
if [[ -e $DEBUGGER_HM_DROPIN && $(steamvr_state) == running ]] && ! port_listening "$DEBUGGER_PORT"; then
|
||||
echo "SteamVR dashboard patches (e.g. the VR pet, the VR keyboard's suggestion strip; DevTools port $DEBUGGER_PORT not open yet): from the next SteamVR start"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
cmd_restart_check() {
|
||||
local pending=()
|
||||
need_not_root
|
||||
mapfile -t pending < <(restart_pending)
|
||||
(( ${#pending[@]} )) || return 0
|
||||
warn "steam-frame-nix: some settings take effect only after a restart (reboot once, or restart the Steam session):"
|
||||
printf ' - %s\n' "${pending[@]}" >&2
|
||||
}
|
||||
|
||||
# --- uninstall --------------------------------------------------------------
|
||||
|
||||
# User units installed by Home Manager (unit files resolving into /nix/store).
|
||||
@@ -1061,10 +1157,17 @@ cmd_uninstall() {
|
||||
cmd_cleanup --all
|
||||
remove_hm
|
||||
|
||||
# A failed Nix uninstall (e.g. /nix still busy) must not skip the rest:
|
||||
# Home Manager is gone by now, so its config link goes regardless; the
|
||||
# per-user Nix files stay for a Nix that may still be there.
|
||||
local nix_failed=0
|
||||
if (( ! keep_nix )); then
|
||||
if [[ -x $NIX_INSTALLER_BIN ]]; then
|
||||
stop_nix_processes
|
||||
uninstall_nix
|
||||
if ! uninstall_nix; then
|
||||
warn "the Nix uninstall failed (see above)"
|
||||
nix_failed=1 keep_nix=1
|
||||
fi
|
||||
elif command -v nix >/dev/null 2>&1; then
|
||||
warn "Nix wasn't installed by nix-installer ($NIX_INSTALLER_BIN missing); not removing it"
|
||||
keep_nix=1
|
||||
@@ -1084,10 +1187,14 @@ Intentionally left in place:
|
||||
- app data, e.g. ~/.local/share/docker, Firefox profiles, and Flatpak apps
|
||||
Log out or reboot so running sessions drop the removed tweaks.
|
||||
EOF
|
||||
if (( ${#CLEAN_DEFERRED[@]} )); then
|
||||
if (( ${#CLEAN_DEFERRED[@]} || nix_failed )); then
|
||||
printf '\nNot done yet:\n'
|
||||
printf ' - %s\n' "${CLEAN_DEFERRED[@]}"
|
||||
(( ${#CLEAN_DEFERRED[@]} )) && printf ' - %s\n' "${CLEAN_DEFERRED[@]}"
|
||||
(( nix_failed )) && printf '%s\n' \
|
||||
" - Nix: its uninstaller failed. Reboot (so nothing uses /nix), then run" \
|
||||
" uninstall again; it also removes ~/.nix-profile and ~/.local/state/nix."
|
||||
fi
|
||||
(( ! nix_failed ))
|
||||
}
|
||||
|
||||
# --- status -----------------------------------------------------------------
|
||||
@@ -1161,6 +1268,7 @@ main() {
|
||||
status) cmd_status "$@" ;;
|
||||
cleanup) cmd_cleanup "$@" ;;
|
||||
steamvr-debugger-arm) cmd_debugger_arm "$@" ;;
|
||||
restart-check) cmd_restart_check "$@" ;;
|
||||
-h|--help|help) usage ;;
|
||||
'') usage >&2; exit 2 ;;
|
||||
*) printf 'unknown command: %s\n\n' "$cmd" >&2; usage >&2; exit 2 ;;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# artifact any version wrote (removed or restored) next to look-alikes that
|
||||
# aren't ours (left alone), --dry-run changes nothing, a second run changes
|
||||
# nothing, SteamVR running defers the debugger key to a runtime drop-in.
|
||||
# Also `install.sh restart-check` (what waits for a session/SteamVR restart).
|
||||
{ pkgs }:
|
||||
let
|
||||
cleanup = pkgs.callPackage ./package.nix { };
|
||||
@@ -17,7 +18,11 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } ''
|
||||
cat > $STUB/systemctl <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
echo "$*" >> "$STUB/log"
|
||||
case "$*" in *"show -p ActiveState --value steamvr.service"*) cat "$STUB/state" ;; esac
|
||||
case "$*" in
|
||||
*"show -p ActiveState --value steamvr.service"*) cat "$STUB/state" ;;
|
||||
*"show -p ActiveState --value gamescope-session.service"*) cat "$STUB/gs-state" ;;
|
||||
*"show -p ControlGroup --value gamescope-session.service"*) echo /gs.service ;;
|
||||
esac
|
||||
SH
|
||||
chmod +x $STUB/systemctl
|
||||
sed -i "1s|.*|#!${pkgs.bash}/bin/bash|" $STUB/systemctl
|
||||
@@ -27,7 +32,7 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } ''
|
||||
fresh() {
|
||||
root=$PWD/$1; rm -rf $root; mkdir -p $root/home $root/run
|
||||
HOME=$root/home XDG_RUNTIME_DIR=$root/run STEAM_FRAME_NIX_RUNTIME_DIR=$root/run
|
||||
: > $STUB/log; echo inactive > $STUB/state
|
||||
: > $STUB/log; echo inactive > $STUB/state; echo inactive > $STUB/gs-state
|
||||
S=$HOME/.local/state/steam-frame-nix
|
||||
V=$HOME/.config/openvr/config/steamvr.vrsettings
|
||||
I=$HOME/.local/share/icons/hicolor/scalable/apps
|
||||
@@ -263,5 +268,37 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } ''
|
||||
bash $root/run/steam-frame-nix/steamvr-debugger-restore
|
||||
gone $V $S/steamvr-debugger.armed
|
||||
echo "E ok"
|
||||
|
||||
# --- F: restart-check (settings read only at a process start) ---
|
||||
export STEAM_FRAME_NIX_PROC STEAM_FRAME_NIX_CGROUP
|
||||
rc() { bash ${../../install.sh} restart-check 2>&1; }
|
||||
fresh f
|
||||
STEAM_FRAME_NIX_PROC=$root/proc STEAM_FRAME_NIX_CGROUP=$root/cg
|
||||
U=$HOME/.config/systemd/user
|
||||
mkdir -p $U/gamescope-session.service.d $U/steamvr.service.d $root/cg/gs.service $root/proc/net $root/proc/10 $root/proc/11
|
||||
printf '[Service]\nEnvironment=XKB_DEFAULT_LAYOUT=de\n' > $U/gamescope-session.service.d/keyboard.conf
|
||||
printf '[Unit]\nWants=steamvr-webhelper-debugger.service\n' > $U/steamvr.service.d/webhelper-debugger.conf
|
||||
printf '10\n11\n' > $root/cg/gs.service/cgroup.procs
|
||||
: > $root/proc/10/environ; chmod 000 $root/proc/10/environ # gamescope: unreadable, skipped
|
||||
printf 'HOME=/h\0XKB_DEFAULT_LAYOUT=us\0' > $root/proc/11/environ
|
||||
tcp() { printf ' sl local_address rem_address st\n'; for l; do printf ' 0: %s 00000000:0000 0A\n' "$l"; done; }
|
||||
tcp 0100007F:1F90 > $root/proc/net/tcp # only Steam's 8080
|
||||
# nothing runs: nothing waits
|
||||
res=$(rc); [ -z "$res" ] || fail "restart-check while stopped: $res"
|
||||
# fresh install into a running session: both wait
|
||||
echo active > $STUB/gs-state; echo active > $STUB/state
|
||||
res=$(rc); echo "$res"
|
||||
has "$res" "keyboard layout de (the running Steam session has us): from the next start of the Steam session"
|
||||
has "$res" "SteamVR dashboard patches"
|
||||
# after the restarts: nothing waits
|
||||
printf 'XKB_DEFAULT_LAYOUT=de\0' > $root/proc/11/environ
|
||||
tcp 0100007F:1F90 0100007F:1F97 > $root/proc/net/tcp
|
||||
res=$(rc); [ -z "$res" ] || fail "restart-check after restart: $res"
|
||||
# a variant change waits; no layout configured: no check
|
||||
printf '[Service]\nEnvironment=XKB_DEFAULT_LAYOUT=de\nEnvironment=XKB_DEFAULT_VARIANT=nodeadkeys\n' > $U/gamescope-session.service.d/keyboard.conf
|
||||
res=$(rc); has "$res" "keyboard layout de (nodeadkeys) (the running Steam session has de)"
|
||||
rm -r $U/gamescope-session.service.d $U/steamvr.service.d; : > $root/proc/net/tcp
|
||||
res=$(rc); [ -z "$res" ] || fail "restart-check without the drop-ins: $res"
|
||||
echo "F ok"
|
||||
touch $out
|
||||
''
|
||||
+14
-1
@@ -6,10 +6,17 @@
|
||||
# home-manager's reloadSystemd is skipped ("User systemd daemon not running"),
|
||||
# so `steamFrameUserServices` does it against the outer session: always
|
||||
# daemon-reload, then start/stop/restart the units in `session.services`.
|
||||
{ config, lib, ... }:
|
||||
# Then `steamFrameRestartCheck` (`install.sh restart-check`) names what the
|
||||
# running session can't pick up (read only at a process start): the keyboard
|
||||
# layout, the SteamVR dashboard patches' DevTools port.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.steamFrame.session;
|
||||
units = lib.escapeShellArgs;
|
||||
restartCheck = pkgs.callPackage ./cleanup/package.nix {
|
||||
name = "steam-frame-nix-restart-check";
|
||||
command = "restart-check";
|
||||
};
|
||||
rename = from: to: lib.mkRenamedOptionModule ([ "steamFrame" ] ++ from) [ "steamFrame" "session" to ];
|
||||
in {
|
||||
imports = [
|
||||
@@ -91,6 +98,12 @@ in {
|
||||
|
||||
# In a subshell: the outer session's environment must not leak into
|
||||
# activation steps that run later.
|
||||
# Read-only; a warning, never a failed switch.
|
||||
config.home.activation.steamFrameRestartCheck = lib.hm.dag.entryAfter [ "steamFrameUserServices" ] ''
|
||||
STEAM_FRAME_NIX_RUNTIME_DIR=${lib.escapeShellArg cfg.runtimeDir} XDG_CONFIG_HOME=${lib.escapeShellArg config.xdg.configHome} \
|
||||
${lib.getExe restartCheck} || true
|
||||
'';
|
||||
|
||||
config.home.activation.steamFrameUserServices = lib.hm.dag.entryAfter [ "reloadSystemd" ] (''
|
||||
(
|
||||
export XDG_RUNTIME_DIR=${lib.escapeShellArg cfg.runtimeDir} DBUS_SESSION_BUS_ADDRESS=${lib.escapeShellArg cfg.bus}
|
||||
|
||||
Reference in new issue
Block a user