From 1ad0dd751e78964daa78470bf05cdcbd30fbcbc2 Mon Sep 17 00:00:00 2001 From: Pierre Kisters <1524059+lhns@users.noreply.github.com> Date: Tue, 29 Sep 2026 00:00:59 +0200 Subject: [PATCH] modules/cleanup.nix: orphan cleanup on every switch Every module imports cleanup.nix (also exported as homeManagerModules.cleanup), which - puts steam-frame-nix-cleanup (install.sh cleanup) on PATH; - runs `cleanup --orphans --quiet` after linkGeneration on every switch, keeping what the configuration still uses (steamFrame.cleanup.keep, set by the modules: debugger while steamvrDebugger is on, the Firefox desktop profile's user.js while its fix applies); in a dry run it runs with --dry-run; - runs `cleanup --all` instead when Home Manager's `uninstall = true;` is set (the manual uninstall route); - removes links of older versions at paths Home Manager is about to own before checkLinkTargets (steamFrame.cleanup.migrateLinks), which would count as collisions otherwise. install.sh cleanup gains --quiet (only actions, deferrals and warnings; the header only when something is printed). The Jellyfin module's own removal of the old shim copy goes: cleanup does it. --- flake.nix | 1 + install.sh | 29 ++++++++----- modules/cleanup.nix | 73 ++++++++++++++++++++++++++++++++ modules/clipboard-sync.nix | 2 + modules/dashboard-windows.nix | 2 +- modules/firefox.nix | 4 ++ modules/frame-controls.nix | 2 +- modules/hidden-apps.nix | 1 + modules/jellyfin.nix | 14 ++---- modules/keyboard-layout.nix | 1 + modules/launcher-menu.nix | 1 + modules/portal.nix | 1 + modules/steam-close-button.nix | 2 +- modules/steam-keyboard-patch.nix | 2 +- modules/steam-ui-patches.nix | 2 +- modules/steamvr-debugger.nix | 3 +- modules/vr-keyboard.nix | 2 +- modules/window-curvature.nix | 2 +- 18 files changed, 115 insertions(+), 29 deletions(-) create mode 100644 modules/cleanup.nix diff --git a/flake.nix b/flake.nix index 9bc052a..6d44301 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,7 @@ steam-ui-patches = ./modules/steam-ui-patches.nix; launcher-menu = ./modules/launcher-menu.nix; steamvr-debugger = ./modules/steamvr-debugger.nix; + cleanup = ./modules/cleanup.nix; dashboard-windows = ./modules/dashboard-windows.nix; steam-close-button = ./modules/steam-close-button.nix; window-curvature = ./modules/window-curvature.nix; diff --git a/install.sh b/install.sh index b768a05..c05a1c6 100755 --- a/install.sh +++ b/install.sh @@ -77,6 +77,7 @@ Commands: --keep still in use (with --orphans): debugger, firefox-desktop-userjs= --dry-run only print what would be done + --quiet print only actions, deferrals and warnings SteamVR's VRWebHelper.DebuggerEnabled can't be changed while SteamVR runs; it is then restored when SteamVR stops (a drop-in in /run/user/, gone at reboot). @@ -427,14 +428,18 @@ JF_SHIM_MARKER="$SFN_STATE/jellyfin-hwdec-shim" UI_STATE="$SFN_STATE/ui-patches" CLEAN_DRY=0 +CLEAN_QUIET=0 +CLEAN_HEADER='' CLEAN_ACTIONS=0 CLEAN_DEFERRED=() declare -A CLEAN_GONE=() -c_do() { CLEAN_ACTIONS=$((CLEAN_ACTIONS + 1)); if (( CLEAN_DRY )); then info "would $1"; return 1; fi; } -c_done() { info "$1"; } -c_left() { info "left alone: $1"; } -c_defer() { CLEAN_DEFERRED+=("$1"); info "deferred: $1"; } +# Output; the header only before the first line (--quiet). +c_line() { if [[ -n $CLEAN_HEADER ]]; then step "$CLEAN_HEADER"; CLEAN_HEADER=''; fi; info "$1"; } +c_do() { CLEAN_ACTIONS=$((CLEAN_ACTIONS + 1)); if (( CLEAN_DRY )); then c_line "would $1"; return 1; fi; } +c_done() { c_line "$1"; } +c_left() { (( CLEAN_QUIET )) || c_line "left alone: $1"; } +c_defer() { CLEAN_DEFERRED+=("$1"); c_line "deferred: $1"; } c_rm() { # path why c_do "remove $1 ($2)" || { CLEAN_GONE[$1]=1; return 0; } @@ -573,7 +578,7 @@ debugger_remove_hook() { } clean_debugger() { # keep - local keep=$1 prior='' state cur + local keep=$1 prior='' state cur out if [[ -f $DEBUGGER_ARMED ]]; then prior="$(tr -d '[:space:]' <"$DEBUGGER_ARMED")" [[ -e $DEBUGGER_MARKER_V1 ]] && c_rm "$DEBUGGER_MARKER_V1" "old debugger marker, superseded by ${DEBUGGER_ARMED##*/}" @@ -604,8 +609,9 @@ clean_debugger() { # keep cur="$(jq -r '.VRWebHelper.DebuggerEnabled // "absent"' "$VRSETTINGS" 2>/dev/null || echo '?')" if [[ $cur == true ]]; then if c_do "restore VRWebHelper.DebuggerEnabled in $VRSETTINGS (${prior/nofile/absent})"; then - debugger_restore "$VRSETTINGS" "$DEBUGGER_ARMED" 2>&1 | sed 's/^steam-frame-nix: / /' \ + out="$(debugger_restore "$VRSETTINGS" "$DEBUGGER_ARMED" 2>&1)" \ || warn "restoring VRWebHelper.DebuggerEnabled failed; $DEBUGGER_ARMED kept" + [[ -z $out ]] || c_line "${out//steam-frame-nix: /}" fi fi if [[ -f $DEBUGGER_ARMED && ( $cur != true || CLEAN_DRY -eq 1 ) ]]; then @@ -672,7 +678,7 @@ clean_firefox() { # keep_profile ('' = none) kind="desktop profile user.js link"; keys='"full-screen-api.ignore-widgets"' ;; /nix/store/*-firefox-user.js|/nix/store/*-firefox-desktop-user.js) kind="user.js link of an older version"; keys="$(ff_keys "$u")" - [[ -e $u ]] || info "note: $t is gone; values it set stay in $prof/prefs.js" ;; + [[ -e $u ]] || c_line "note: $t is gone; values it set stay in $prof/prefs.js" ;; *) c_left "$u (link to $t)"; continue ;; esac elif [[ -f $u && $(head -n1 "$u") == "$FF_MARKER" ]]; then @@ -745,7 +751,7 @@ clean_jellyfin() { else rc=0; out="$(jf_strip "$JF_OVERRIDE")" || rc=$? if (( rc == 0 )); then - [[ ${out%%$'\n'*} == devices=1 ]] && info "devices=all goes too (it came with the hwdec shim entries)" + [[ ${out%%$'\n'*} == devices=1 ]] && c_line "devices=all goes too (it came with the hwdec shim entries)" out=${out#*$'\n'}; [[ $out == devices=? ]] && out='' if [[ -z $(tr -d '[:space:]' <<<"$out") ]]; then c_rm "$JF_OVERRIDE" "Flatpak override with only the hwdec shim entries" @@ -795,6 +801,7 @@ cmd_cleanup() { --all) mode=all; shift ;; --orphans) mode=orphans; shift ;; --dry-run|-n) CLEAN_DRY=1; shift ;; + --quiet|-q) CLEAN_QUIET=1; shift ;; --keep) [[ $# -ge 2 ]] || die "--keep needs an artifact" k=$2; shift 2; keeps=$((keeps + 1)) @@ -812,7 +819,9 @@ cmd_cleanup() { need_not_root CLEAN_ACTIONS=0; CLEAN_DEFERRED=(); CLEAN_GONE=() - step "steam-frame-nix: cleaning up files outside Nix ($mode$( (( CLEAN_DRY )) && echo ', dry run'))" + CLEAN_HEADER="steam-frame-nix: cleaning up files outside Nix ($mode" + if (( CLEAN_DRY )); then CLEAN_HEADER+=", dry run)"; else CLEAN_HEADER+=")"; fi + if (( ! CLEAN_QUIET )); then step "$CLEAN_HEADER"; CLEAN_HEADER=''; fi clean_debugger "$keep_debugger" clean_icons clean_firefox "$keep_ff" @@ -820,7 +829,7 @@ cmd_cleanup() { clean_ui_state "$([[ $mode == all ]] && echo 1 || echo 0)" c_rmdir "$SFN_STATE" if [[ $mode == all ]]; then c_rmdir "$SFN_RUNTIME"; fi - (( CLEAN_ACTIONS || ${#CLEAN_DEFERRED[@]} )) || info "nothing to clean up" + (( CLEAN_ACTIONS || ${#CLEAN_DEFERRED[@]} || CLEAN_QUIET )) || info "nothing to clean up" return 0 } diff --git a/modules/cleanup.nix b/modules/cleanup.nix new file mode 100644 index 0000000..5db1f2e --- /dev/null +++ b/modules/cleanup.nix @@ -0,0 +1,73 @@ +# Cleanup of what steam-frame-nix writes outside the Nix store (install.sh +# cleanup, see there for the list). Imported by every module. +# - steam-frame-nix-cleanup on PATH (e.g. `steam-frame-nix-cleanup --all` +# before removing steam-frame-nix from a configuration). +# - On every switch, `cleanup --orphans` removes what the configuration no +# longer uses (other modules list what they still use in `keep`); with +# Home Manager's `uninstall = true;` it runs `cleanup --all` instead. +# - Before checkLinkTargets, links of older versions at paths Home Manager is +# about to own (`migrateLinks`) are removed, so they don't count as +# collisions. +{ config, lib, pkgs, ... }: +let + cfg = config.steamFrame.cleanup; + cleanup = pkgs.callPackage ./cleanup/package.nix { }; + args = if config.uninstall then [ "--all" ] + else [ "--orphans" "--quiet" ] ++ lib.concatMap (k: [ "--keep" k ]) (lib.unique cfg.keep); + env = "STEAM_FRAME_NIX_RUNTIME_DIR=${lib.escapeShellArg config.steamFrame.session.runtimeDir}"; +in { + imports = [ ./session.nix ]; + + options.steamFrame.cleanup = { + package = lib.mkOption { + type = lib.types.package; + readOnly = true; + default = cleanup; + defaultText = lib.literalMD "steam-frame-nix-cleanup (`install.sh cleanup`)"; + description = "The steam-frame-nix-cleanup command (`install.sh cleanup`)."; + }; + keep = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + internal = true; + description = '' + Artifacts the configuration still uses (`install.sh cleanup --keep`), + set by the modules. + ''; + }; + migrateLinks = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + path = lib.mkOption { type = lib.types.str; description = "Path relative to the home directory."; }; + target = lib.mkOption { type = lib.types.str; description = "Glob the old link's target must match."; }; + }; + }); + default = [ ]; + internal = true; + description = '' + Links an older version made where Home Manager now puts its own: + removed before checkLinkTargets if they still point to `target`. + ''; + }; + }; + + config = { + home.packages = [ cleanup ]; + + home.activation.steamFrameNixMigrate = lib.hm.dag.entryBefore [ "checkLinkTargets" ] + (lib.concatMapStrings (l: '' + if [[ -L "$HOME"/${lib.escapeShellArg l.path} && $(readlink "$HOME"/${lib.escapeShellArg l.path}) == ${l.target} ]]; then + run rm $VERBOSE_ARG "$HOME"/${lib.escapeShellArg l.path} + echo "steam-frame-nix: removed the old link $HOME/"${lib.escapeShellArg l.path} + fi + '') cfg.migrateLinks); + + home.activation.steamFrameNixCleanup = lib.hm.dag.entryAfter [ "writeBoundary" "linkGeneration" ] '' + if [[ -v DRY_RUN ]]; then + ${env} ${lib.getExe cleanup} --dry-run ${lib.escapeShellArgs args} + else + run env ${env} ${lib.getExe cleanup} ${lib.escapeShellArgs args} + fi + ''; + }; +} diff --git a/modules/clipboard-sync.nix b/modules/clipboard-sync.nix index 35689bb..7d36887 100644 --- a/modules/clipboard-sync.nix +++ b/modules/clipboard-sync.nix @@ -7,6 +7,8 @@ let cfg = config.steamFrame.clipboardSync; in { + imports = [ ./cleanup.nix ]; + options.steamFrame.clipboardSync = { enable = lib.mkOption { type = lib.types.bool; diff --git a/modules/dashboard-windows.nix b/modules/dashboard-windows.nix index d9a20ba..8524730 100644 --- a/modules/dashboard-windows.nix +++ b/modules/dashboard-windows.nix @@ -47,7 +47,7 @@ let new = [ "steamFrame" "dashboard" "windows" ]; in { imports = [ - ./steam-ui-patches.nix ./steamvr-debugger.nix + ./cleanup.nix ./steam-ui-patches.nix ./steamvr-debugger.nix (lib.mkRenamedOptionModule (old ++ [ "windowMaxScale" ]) (new ++ [ "maxScale" ])) ] ++ lib.concatMap (kind: map (bound: lib.mkRenamedOptionModule diff --git a/modules/firefox.nix b/modules/firefox.nix index dac7cab..705c69e 100644 --- a/modules/firefox.nix +++ b/modules/firefox.nix @@ -119,6 +119,8 @@ let --file-forwarding org.mozilla.firefox "''${profile[@]}" "$@" ''); in { + imports = [ ./cleanup.nix ]; + options.steamFrame.firefox = { enable = lib.mkEnableOption '' the Firefox Flatpak (org.mozilla.firefox) launcher with Steam Frame @@ -167,6 +169,8 @@ in { # Always, so disabling removes our user.js links (and old copies). home.activation.firefoxProfiles = lib.hm.dag.entryAfter [ "writeBoundary" ] "run ${profileSync}\n"; + # The desktop profile's user.js link (steam-frame-nix-cleanup). + steamFrame.cleanup.keep = lib.optional desktopFix "firefox-desktop-userjs=${cfg.desktopProfile}"; } (lib.mkIf cfg.enable { # stable: the branch the launcher runs (the extension point has no diff --git a/modules/frame-controls.nix b/modules/frame-controls.nix index e913bab..946e61d 100644 --- a/modules/frame-controls.nix +++ b/modules/frame-controls.nix @@ -19,7 +19,7 @@ let inherit description; }; in { - imports = [ ./steam-ui-patches.nix ./steamvr-debugger.nix ]; + imports = [ ./cleanup.nix ./steam-ui-patches.nix ./steamvr-debugger.nix ]; options.steamFrame.dashboard.frameControls = { enable = lib.mkEnableOption '' diff --git a/modules/hidden-apps.nix b/modules/hidden-apps.nix index bef1881..c4b6d34 100644 --- a/modules/hidden-apps.nix +++ b/modules/hidden-apps.nix @@ -2,6 +2,7 @@ # Hidden=true masks the system one. { config, lib, ... }: { imports = [ + ./cleanup.nix (lib.mkRenamedOptionModule [ "steamFrame" "hiddenApps" ] [ "steamFrame" "launcherMenu" "hiddenApps" ]) ]; diff --git a/modules/jellyfin.nix b/modules/jellyfin.nix index d4e8352..47f44cc 100644 --- a/modules/jellyfin.nix +++ b/modules/jellyfin.nix @@ -31,6 +31,8 @@ let "${key}=${if lib.isList value then lib.concatMapStrings (v: "${v};") value else value}\n") entries)) override)); in { + imports = [ ./cleanup.nix ]; + options.steamFrame.jellyfin.hardwareDecoding = { enable = lib.mkOption { type = lib.types.bool; @@ -55,18 +57,8 @@ in { }; }; + # The shim copy of earlier versions is removed by steam-frame-nix-cleanup. config = lib.mkMerge [ - { - # Remove the shim copy of earlier versions (copied into the app's data - # dir, marked in $XDG_STATE_HOME/steam-frame-nix). - home.activation.steamFrameJellyfinHwdec = lib.hm.dag.entryAfter [ "writeBoundary" ] '' - marker="''${XDG_STATE_HOME:-$HOME/.local/state}/steam-frame-nix/jellyfin-hwdec-shim" - if [ -e "$marker" ]; then - run rm -f "$HOME/.var/app/${app}/mpv-hwdec-shim.so" "$marker" - run rmdir --ignore-fail-on-non-empty "''${marker%/*}" - fi - ''; - } (lib.mkIf (cfg.enable && !useNixFlatpak) { # force: `flatpak override --user` replaces the link with a file. xdg.dataFile."flatpak/overrides/${app}" = { source = overrideFile; force = true; }; diff --git a/modules/keyboard-layout.nix b/modules/keyboard-layout.nix index 914c3b7..52b8914 100644 --- a/modules/keyboard-layout.nix +++ b/modules/keyboard-layout.nix @@ -7,6 +7,7 @@ let cfg = config.steamFrame.keyboard; in { imports = [ + ./cleanup.nix (lib.mkRenamedOptionModule [ "steamFrame" "keyboardLayout" ] [ "steamFrame" "keyboard" "layout" ]) (lib.mkRenamedOptionModule [ "steamFrame" "keyboardVariant" ] [ "steamFrame" "keyboard" "variant" ]) ]; diff --git a/modules/launcher-menu.nix b/modules/launcher-menu.nix index d36e19a..c60fa01 100644 --- a/modules/launcher-menu.nix +++ b/modules/launcher-menu.nix @@ -27,6 +27,7 @@ let }; in { imports = [ + ./cleanup.nix ./steam-ui-patches.nix (lib.mkRenamedOptionModule [ "steamFrame" "launcherMenu" "launchDebounce" ] diff --git a/modules/portal.nix b/modules/portal.nix index d816f35..717b5b2 100644 --- a/modules/portal.nix +++ b/modules/portal.nix @@ -9,6 +9,7 @@ let dir = "xdg-desktop-portal/gamescope-portals"; in { imports = [ + ./cleanup.nix (lib.mkRenamedOptionModule [ "steamFrame" "portalFix" "enable" ] [ "steamFrame" "session" "portalFix" "enable" ]) diff --git a/modules/steam-close-button.nix b/modules/steam-close-button.nix index 6fe20a0..ba78860 100644 --- a/modules/steam-close-button.nix +++ b/modules/steam-close-button.nix @@ -6,7 +6,7 @@ let cfg = config.steamFrame.dashboard.steamCloseButton; inherit (import ./lib { inherit pkgs; }) mkPatch; in { - imports = [ ./steam-ui-patches.nix ./steamvr-debugger.nix ]; + imports = [ ./cleanup.nix ./steam-ui-patches.nix ./steamvr-debugger.nix ]; options.steamFrame.dashboard.steamCloseButton.enable = lib.mkEnableOption '' a Close (X) button on the dashboard's Steam window. It hides Steam diff --git a/modules/steam-keyboard-patch.nix b/modules/steam-keyboard-patch.nix index e59d01d..7e86e5d 100644 --- a/modules/steam-keyboard-patch.nix +++ b/modules/steam-keyboard-patch.nix @@ -17,7 +17,7 @@ let cfg = config.steamFrame.keyboard.vr.extraKeys; in { imports = [ - ./session.nix + ./session.nix ./cleanup.nix (lib.mkRenamedOptionModule [ "steamFrame" "steamKeyboardPatch" "enable" ] [ "steamFrame" "keyboard" "vr" "extraKeys" "enable" ]) diff --git a/modules/steam-ui-patches.nix b/modules/steam-ui-patches.nix index d0bbae4..8408013 100644 --- a/modules/steam-ui-patches.nix +++ b/modules/steam-ui-patches.nix @@ -92,7 +92,7 @@ let patches = map entry cfg.patches; }); in { - imports = [ ./session.nix ]; + imports = [ ./session.nix ./cleanup.nix ]; options.steamFrame.uiPatches.lib = mkOption { type = types.attrsOf types.raw; diff --git a/modules/steamvr-debugger.nix b/modules/steamvr-debugger.nix index 20491e4..a483a64 100644 --- a/modules/steamvr-debugger.nix +++ b/modules/steamvr-debugger.nix @@ -53,7 +53,7 @@ let ''; }; in { - imports = [ ./steam-ui-patches.nix ]; + imports = [ ./cleanup.nix ./steam-ui-patches.nix ]; options.steamFrame.steamvrDebugger.enable = lib.mkOption { type = lib.types.bool; @@ -74,6 +74,7 @@ in { config = { steamFrame.steamvrDebugger.enable = lib.mkDefault (lib.any usesDebugger config.steamFrame.uiPatches.patches); + steamFrame.cleanup.keep = lib.optional cfg.enable "debugger"; systemd.user.services.steamvr-webhelper-debugger = { Unit = { diff --git a/modules/vr-keyboard.nix b/modules/vr-keyboard.nix index 4c02be3..32abcce 100644 --- a/modules/vr-keyboard.nix +++ b/modules/vr-keyboard.nix @@ -85,7 +85,7 @@ let }; }; in { - imports = [ ./session.nix ./steam-ui-patches.nix ./keyboard-layout.nix ]; + imports = [ ./session.nix ./cleanup.nix ./steam-ui-patches.nix ./keyboard-layout.nix ]; options.steamFrame.keyboard.vr = { enable = mkEnableOption '' diff --git a/modules/window-curvature.nix b/modules/window-curvature.nix index 8c69fe1..6386c5e 100644 --- a/modules/window-curvature.nix +++ b/modules/window-curvature.nix @@ -15,7 +15,7 @@ let }; in { imports = [ - ./steam-ui-patches.nix ./steamvr-debugger.nix + ./cleanup.nix ./steam-ui-patches.nix ./steamvr-debugger.nix (lib.mkRemovedOptionModule (path ++ [ "barDragRoom" ]) "Not needed: SteamVR keeps sending the bar's coordinates past its edge during a drag.") (lib.mkRemovedOptionModule (path ++ [ "snap" ])