From b7b32d0aa91483074f657d07dc8cd19679af725b Mon Sep 17 00:00:00 2001 From: Pierre Kisters <1524059+lhns@users.noreply.github.com> Date: Tue, 29 Sep 2026 00:02:57 +0200 Subject: [PATCH] steamvr-debugger: set DebuggerEnabled only while SteamVR runs The key used to be merged into steamvr.vrsettings for good (reset at the next start after disabling, via a marker). Now it is on only while SteamVR runs and nothing depends on Nix to undo it: - before each SteamVR start, the oneshot runs `install.sh steamvr-debugger-arm`: the key's value goes to ~/.local/state/steam-frame-nix/steamvr-debugger.armed ("absent", "false", "nofile"), the key is set (jq --indent 3, mode kept), and a runtime drop-in $XDG_RUNTIME_DIR/systemd/user/steamvr.service.d/ 50-steam-frame-nix-debugger.conf runs a /usr/bin-only restore script from $XDG_RUNTIME_DIR/steam-frame-nix on ExecStopPost= (daemon-reload only when newly written: once per boot). A key that is already true without .armed is the user's own and never touched. The old marker migrates to .armed ("absent"). - when SteamVR stops, the key goes back to its previous value (removed if it was absent, the file too if it only existed for the key) and .armed is removed. .armed is the only trace a power loss can leave; the next start or steam-frame-nix-cleanup resolves it. - disabled: no unit and no drop-in at all; the orphan cleanup restores the key once SteamVR is stopped (or leaves it to the runtime drop-in while it runs). A daemon-reload from a oneshot pulled in by a pending start job was tried on dummy user units: the job survives and the new ExecStopPost= applies, also on restart (stop, then the oneshot, then start). checks.cleanup covers arming, re-arming, restoring and the user's own key. --- install.sh | 52 ++++++++++++++++ modules/cleanup/check.nix | 32 ++++++++++ modules/cleanup/package.nix | 8 ++- modules/steamvr-debugger.nix | 116 +++++++++++++++-------------------- 4 files changed, 140 insertions(+), 68 deletions(-) diff --git a/install.sh b/install.sh index c05a1c6..369e458 100755 --- a/install.sh +++ b/install.sh @@ -86,6 +86,10 @@ Commands: Show Nix, Home Manager and user service state, and what 'cleanup --all' would remove. + steamvr-debugger-arm + Internal (run before each SteamVR start by steamFrame.steamvrDebugger): + set VRWebHelper.DebuggerEnabled until SteamVR stops. + Options: --yes, -y Don't ask; answer yes to every question. --help, -h Show this help. @@ -577,6 +581,53 @@ debugger_remove_hook() { return 0 } +# Before each SteamVR start (the steamvr-webhelper-debugger oneshot of +# steamFrame.steamvrDebugger): remember the key's value in +# steamvr-debugger.armed, set it to true and make sure the runtime drop-in +# puts it back when SteamVR stops. A key that is already true (without +# .armed) is the user's own and never touched. +cmd_debugger_arm() { + local cur tmp + need_not_root + command -v jq >/dev/null 2>&1 || die "jq not found" + if [[ -f $DEBUGGER_MARKER_V1 ]]; then # marker of older versions: key was absent + mkdir -p "$SFN_STATE" + [[ -f $DEBUGGER_ARMED ]] || printf 'absent\n' >"$DEBUGGER_ARMED" + rm -f -- "$DEBUGGER_MARKER_V1" + info "migrated $DEBUGGER_MARKER_V1 to $DEBUGGER_ARMED" + fi + if [[ ! -f $DEBUGGER_ARMED ]]; then + if [[ -f $VRSETTINGS ]]; then + cur="$(jq -r 'if (.VRWebHelper | type) == "object" and (.VRWebHelper | has("DebuggerEnabled")) + then .VRWebHelper.DebuggerEnabled | tostring else "absent" end' "$VRSETTINGS")" \ + || die "can't read $VRSETTINGS" + else + cur=nofile + fi + case $cur in + true) info "VRWebHelper.DebuggerEnabled is already true (not set by steam-frame-nix): left as is"; return 0 ;; + false|absent|nofile) ;; + *) warn "VRWebHelper.DebuggerEnabled is $cur; left as is"; return 0 ;; + esac + mkdir -p "$SFN_STATE" + printf '%s\n' "$cur" >"$DEBUGGER_ARMED" + fi + if [[ ! -f $VRSETTINGS ]]; then + mkdir -p "${VRSETTINGS%/*}" + printf '{}\n' >"$VRSETTINGS" + fi + if [[ $(jq '.VRWebHelper.DebuggerEnabled' "$VRSETTINGS") != true ]]; then + tmp="$(mktemp "$VRSETTINGS.XXXXXX")" + jq --indent 3 '.VRWebHelper.DebuggerEnabled = true' "$VRSETTINGS" >"$tmp" || { rm -f "$tmp"; die "can't write $VRSETTINGS"; } + chmod --reference="$VRSETTINGS" "$tmp" + mv -f "$tmp" "$VRSETTINGS" + info "VRWebHelper.DebuggerEnabled set to true until SteamVR stops (before: $(<"$DEBUGGER_ARMED"))" + else + info "VRWebHelper.DebuggerEnabled already true (set by steam-frame-nix)" + fi + debugger_ensure_hook || true +} + clean_debugger() { # keep local keep=$1 prior='' state cur out if [[ -f $DEBUGGER_ARMED ]]; then @@ -1085,6 +1136,7 @@ main() { uninstall) cmd_uninstall "$@" ;; status) cmd_status "$@" ;; cleanup) cmd_cleanup "$@" ;; + steamvr-debugger-arm) cmd_debugger_arm "$@" ;; -h|--help|help) usage ;; '') usage >&2; exit 2 ;; *) printf 'unknown command: %s\n\n' "$cmd" >&2; usage >&2; exit 2 ;; diff --git a/modules/cleanup/check.nix b/modules/cleanup/check.nix index d470227..534ce9a 100644 --- a/modules/cleanup/check.nix +++ b/modules/cleanup/check.nix @@ -194,5 +194,37 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } '' has "$res" "left alone: VRWebHelper.DebuggerEnabled = true" [ "$(jq -c . $V)" = '{"VRWebHelper":{"DebuggerEnabled":true}}' ] || fail "user's key changed" echo "D ok" + + # --- E: steamvr-debugger-arm (before each SteamVR start) --- + arm() { bash ${../../install.sh} steamvr-debugger-arm; } + fresh e + printf '{\n "steamvr" : {}\n}\n' > $V + touch $S/steamvr-debugger # old marker: migrated + echo activating > $STUB/state + res=$(arm); echo "$res" + [ "$(cat $S/steamvr-debugger.armed)" = absent ] || fail "armed" + gone $S/steamvr-debugger + [ "$(jq -c .VRWebHelper $V)" = '{"DebuggerEnabled":true}' ] || fail "arm: $(cat $V)" + there $root/run/steam-frame-nix/steamvr-debugger-restore + [ "$(grep -c daemon-reload $STUB/log)" = 1 ] || fail "arm reload" + before=$(snap); res=$(arm) + [ "$(snap)" = "$before" ] || fail "second arm changed files" + [ "$(grep -c daemon-reload $STUB/log)" = 1 ] || fail "second arm reloaded" + bash $root/run/steam-frame-nix/steamvr-debugger-restore # SteamVR stops + [ "$(jq -c . $V)" = '{"steamvr":{}}' ] || fail "arm restore: $(cat $V)" + gone $S/steamvr-debugger.armed + res=$(arm) # restart: armed again + [ "$(cat $S/steamvr-debugger.armed)" = absent ] || fail "re-arm" + bash $root/run/steam-frame-nix/steamvr-debugger-restore + # the user's own true: never armed, never restored + printf '{"VRWebHelper":{"DebuggerEnabled":true}}\n' > $V + res=$(arm); has "$res" "not set by steam-frame-nix" + gone $S/steamvr-debugger.armed + # no file yet: created, and removed again if nothing else was added + rm $V; res=$(arm) + [ "$(cat $S/steamvr-debugger.armed)" = nofile ] || fail "nofile" + bash $root/run/steam-frame-nix/steamvr-debugger-restore + gone $V $S/steamvr-debugger.armed + echo "E ok" touch $out '' diff --git a/modules/cleanup/package.nix b/modules/cleanup/package.nix index 88c4156..3b4ece7 100644 --- a/modules/cleanup/package.nix +++ b/modules/cleanup/package.nix @@ -1,10 +1,12 @@ # steam-frame-nix-cleanup: `install.sh cleanup` (see there) with its tools # from nixpkgs. `nix run github:lhns/steam-frame-nix#cleanup -- --all`. -{ writeShellApplication, bash, coreutils, findutils, jq, gnugrep, gnused, gawk, procps }: +# Other install.sh commands the modules run: name + command. +{ writeShellApplication, bash, coreutils, findutils, jq, gnugrep, gnused, gawk, procps +, name ? "steam-frame-nix-cleanup", command ? "cleanup" }: writeShellApplication { - name = "steam-frame-nix-cleanup"; + inherit name; runtimeInputs = [ bash coreutils findutils jq gnugrep gnused gawk procps ]; text = '' - exec bash ${../../install.sh} cleanup "$@" + exec bash ${../../install.sh} ${command} "$@" ''; } diff --git a/modules/steamvr-debugger.nix b/modules/steamvr-debugger.nix index a483a64..2aea4a7 100644 --- a/modules/steamvr-debugger.nix +++ b/modules/steamvr-debugger.nix @@ -2,55 +2,35 @@ # 127.0.0.1:8087, for dashboard patches. On automatically (mkDefault) when a # patch targets port 8087. # -# The port opens only with VRWebHelper/DebuggerEnabled (requires a SteamVR -# restart). steamvr.vrsettings is rewritten by SteamVR from memory, so it -# can't be a store link or be edited while SteamVR runs: a oneshot merges just -# that key with jq before each steamvr.service start (drop-in Wants/After; -# ExecStartPre= would be too late, vrserver starts in the unit's own chain). -# Disabled, it resets the key only if it set it (marker in -# $XDG_STATE_HOME/steam-frame-nix); a manual setting is kept. -# Developer Mode forwards the port to 0.0.0.0:8088 (README, "DevTools on the -# LAN"); our patches use 127.0.0.1 only. +# The port opens only with VRWebHelper/DebuggerEnabled in +# ~/.config/openvr/config/steamvr.vrsettings, which SteamVR rewrites from +# memory, so it can't be a store link or be edited while SteamVR runs. The key +# is set only while SteamVR runs: +# - before each start, the steamvr-webhelper-debugger oneshot (drop-in +# Wants/After on steamvr.service; ExecStartPre= would be too late, vrserver +# starts in the unit's own chain) runs `install.sh steamvr-debugger-arm`: +# it keeps the key's value in $XDG_STATE_HOME/steam-frame-nix/ +# steamvr-debugger.armed (a key already true is the user's own and left +# alone), sets the key, and writes a runtime drop-in +# ($XDG_RUNTIME_DIR/systemd/user/steamvr.service.d) with a /usr/bin-only +# restore script (daemon-reload only when it is new, i.e. once per boot); +# - when SteamVR stops, that ExecStopPost= puts the value back and removes +# .armed. The runtime pieces don't depend on Nix, so this also works after +# a rollback or uninstall; they are gone at reboot, and .armed (the only +# trace after a power loss) is resolved by the next start or +# steam-frame-nix-cleanup. +# Off: no unit and no drop-in; cleanup restores the key once SteamVR is +# stopped. Developer Mode forwards the port to 0.0.0.0:8088 (README, +# "DevTools on the LAN"); our patches use 127.0.0.1 only. { config, lib, pkgs, ... }: let cfg = config.steamFrame.steamvrDebugger; usesDebugger = p: builtins.match "[a-z]+://[^/]*:8087(/.*)?" p.endpoint != null; - merge = pkgs.writeShellApplication { + arm = pkgs.callPackage ./cleanup/package.nix { name = "steamvr-webhelper-debugger"; - runtimeInputs = [ pkgs.jq pkgs.coreutils ]; - text = '' - f="$HOME/.config/openvr/config/steamvr.vrsettings" - marker="''${XDG_STATE_HOME:-$HOME/.local/state}/steam-frame-nix/steamvr-debugger" - ${if cfg.enable then '' - want=true - '' else '' - if [ ! -e "$marker" ]; then - echo "not managed (VRWebHelper.DebuggerEnabled left as is)" - exit 0 - fi - want=false - ''} - mkdir -p "$(dirname "$f")" - [ -s "$f" ] || printf '{}\n' > "$f" - if [ "$(jq '.VRWebHelper.DebuggerEnabled' "$f")" = "$want" ]; then - echo "VRWebHelper.DebuggerEnabled already $want" - else - tmp="$(mktemp "$f.XXXXXX")" - trap 'rm -f "$tmp"' EXIT - jq --indent 3 --argjson v "$want" '.VRWebHelper.DebuggerEnabled = $v' "$f" > "$tmp" - chmod --reference="$f" "$tmp" - mv "$tmp" "$f" - echo "VRWebHelper.DebuggerEnabled set to $want (effective from this SteamVR start)" - fi - if [ "$want" = true ]; then - mkdir -p "$(dirname "$marker")" - touch "$marker" - else - rm -f "$marker" - fi - ''; + command = "steamvr-debugger-arm"; }; in { imports = [ ./cleanup.nix ./steam-ui-patches.nix ]; @@ -65,33 +45,39 @@ in { description = '' SteamVR's web helper debugger (dashboard DevTools on 127.0.0.1:8087, VRWebHelper/DebuggerEnabled in steamvr.vrsettings), needed by SteamVR - dashboard patches, which turn it on automatically. Takes effect after one SteamVR - restart; off resets the key at the next start. Developer Mode also - forwards the port to the LAN (0.0.0.0:8088). + dashboard patches, which turn it on automatically. Set only while + SteamVR runs (from its next start; the first time needs one SteamVR + restart) and put back to its previous value when SteamVR stops. + Developer Mode also forwards the port to the LAN (0.0.0.0:8088). ''; }; - config = { - steamFrame.steamvrDebugger.enable = - lib.mkDefault (lib.any usesDebugger config.steamFrame.uiPatches.patches); - steamFrame.cleanup.keep = lib.optional cfg.enable "debugger"; + config = lib.mkMerge [ + { + steamFrame.steamvrDebugger.enable = + lib.mkDefault (lib.any usesDebugger config.steamFrame.uiPatches.patches); + } + (lib.mkIf cfg.enable { + steamFrame.cleanup.keep = [ "debugger" ]; - systemd.user.services.steamvr-webhelper-debugger = { - Unit = { - Description = "Set SteamVR VRWebHelper/DebuggerEnabled (steam-frame-nix)"; - Before = [ "steamvr.service" ]; + systemd.user.services.steamvr-webhelper-debugger = { + Unit = { + Description = "Set SteamVR VRWebHelper/DebuggerEnabled until SteamVR stops (steam-frame-nix)"; + Before = [ "steamvr.service" ]; + }; + Service = { + Type = "oneshot"; + Environment = [ "STEAM_FRAME_NIX_RUNTIME_DIR=%t" ]; + ExecStart = lib.getExe arm; + }; }; - Service = { - Type = "oneshot"; - ExecStart = "${merge}/bin/steamvr-webhelper-debugger"; - }; - }; - # Wants=, not Requires=: SteamVR still starts if the merge fails. - xdg.configFile."systemd/user/steamvr.service.d/webhelper-debugger.conf".text = '' - [Unit] - Wants=steamvr-webhelper-debugger.service - After=steamvr-webhelper-debugger.service - ''; - }; + # Wants=, not Requires=: SteamVR still starts if this fails. + xdg.configFile."systemd/user/steamvr.service.d/webhelper-debugger.conf".text = '' + [Unit] + Wants=steamvr-webhelper-debugger.service + After=steamvr-webhelper-debugger.service + ''; + }) + ]; }