install.sh cleanup: remove what steam-frame-nix wrote outside the Nix store

`install.sh cleanup [--dry-run] (--all | --orphans [--keep <artifact>]...)`
knows every file any version wrote outside the store and Home Manager's
links, removes only what is provably its own, prints every action and
reports everything else as "left alone"; a second run changes nothing.
Only bash, coreutils, findutils, grep, sed, awk and jq (all in /usr/bin on
SteamOS), so it also works piped from curl after a rollback or with Nix gone.

- SteamVR debugger: VRWebHelper.DebuggerEnabled is put back to its value
  from before (steamvr-debugger.armed; the old empty marker means "absent"
  and is migrated), only while SteamVR is stopped. While it runs, a runtime
  drop-in (/run/user/<uid>/systemd/user/steamvr.service.d) runs a /usr/bin-only
  restore script from /run/user/<uid>/steam-frame-nix when SteamVR stops.
- icon fallback links into Breeze and their manifest (dirs only if they
  held nothing else; hicolor's mtime bumped so Steam rescans).
- Firefox: the desktop profile's user.js link, older user.js links and
  marker-headed copies, and the values they left in prefs.js (only with the
  profile closed; deferred otherwise).
- Jellyfin: the hwdec shim entries in the Flatpak override (devices=all
  only when it came with them), an empty override file, the old shim copy.
- the dashboard patches' saved state: --all only; --orphans removes just
  stale *.json.tmp files.

`install.sh uninstall` runs `cleanup --all` before removing Home Manager
(replacing its own ui-patches removal) and names what is deferred;
`install.sh status` shows what `cleanup --all` would do. The flake exports
it as packages.<system>.cleanup / apps.cleanup
(`nix run github:lhns/steam-frame-nix#cleanup -- --all`), and
checks.cleanup runs it against fake home and runtime dirs: every artifact
next to look-alikes that aren't ours, --dry-run, second runs, SteamVR
running (deferral, restore script), the user's own debugger key.
This commit is contained in:
Pierre Kisters committed 2026-09-28 23:57:35 +02:00
1 parent 7fe68e055c
commit 7255315b73
4 files changed
+711 -17

No files matched your search

+21 -5
View File
@@ -34,18 +34,34 @@
firefox = ./modules/firefox.nix; firefox = ./modules/firefox.nix;
jellyfin = ./modules/jellyfin.nix; jellyfin = ./modules/jellyfin.nix;
}; };
systems = [ "aarch64-linux" "x86_64-linux" ];
forSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
in { in {
homeManagerModules = modules // { homeManagerModules = modules // {
default = { imports = builtins.attrValues modules; }; default = { imports = builtins.attrValues modules; };
}; };
# Removes what steam-frame-nix wrote outside the Nix store (install.sh
# cleanup): nix run github:lhns/steam-frame-nix#cleanup -- --all
packages = forSystems (pkgs: {
cleanup = pkgs.callPackage ./modules/cleanup/package.nix { };
});
apps = nixpkgs.lib.mapAttrs (_: p: {
cleanup = {
type = "app";
program = "${p.cleanup}/bin/steam-frame-nix-cleanup";
meta.description = "Remove what steam-frame-nix wrote outside the Nix store";
};
}) self.packages;
# Tests of the VR keyboard (text model, corrector, swipe decoder on the # Tests of the VR keyboard (text model, corrector, swipe decoder on the
# default German + English dictionary) and of the Jellyfin mpv shim: # default German + English dictionary), the Jellyfin mpv shim and
# nix flake check # install.sh cleanup: nix flake check
checks = nixpkgs.lib.genAttrs [ "aarch64-linux" "x86_64-linux" ] (system: { checks = forSystems (pkgs: {
jellyfin = import ./modules/jellyfin/check.nix { pkgs = nixpkgs.legacyPackages.${system}; }; cleanup = import ./modules/cleanup/check.nix { inherit pkgs; };
jellyfin = import ./modules/jellyfin/check.nix { inherit pkgs; };
vr-keyboard = (import ./modules/vr-keyboard/build.nix { vr-keyboard = (import ./modules/vr-keyboard/build.nix {
pkgs = nixpkgs.legacyPackages.${system}; inherit pkgs;
dictionary = { dictionary = {
languages = map (l: l // { keepFrequentAbove = 4.0; }) [ languages = map (l: l // { keepFrequentAbove = 4.0; }) [
{ language = "de"; hunspell = "de_DE"; words = 60000; frequencyOffset = 0.0; } { language = "de"; hunspell = "de_DE"; words = 60000; frequencyOffset = 0.0; }
+482 -12
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Install or uninstall Nix + Home Manager on SteamOS (Steam Frame, Steam Deck). # Install or uninstall Nix + Home Manager on SteamOS (Steam Frame, Steam Deck),
# and clean up what steam-frame-nix wrote outside the Nix store.
# #
# curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- install # curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- install
# curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- uninstall # curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- uninstall
# curl -fsSL https://steam-frame-nix.lhns.de | bash -s -- cleanup --all
# #
# Run `install.sh --help` for details. Works from a file or piped into bash # Run `install.sh --help` for details. Works from a file or piped into bash
# (prompts read from /dev/tty). # (prompts read from /dev/tty).
@@ -24,7 +26,7 @@ USER_UNIT_DIR="$CONFIG_HOME/systemd/user"
USER_NAME="$(id -un)" USER_NAME="$(id -un)"
USER_ID="$(id -u)" USER_ID="$(id -u)"
OUTER_RUNTIME_DIR="/run/user/$USER_ID" OUTER_RUNTIME_DIR="${STEAM_FRAME_NIX_RUNTIME_DIR:-/run/user/$USER_ID}"
ASSUME_YES=0 ASSUME_YES=0
RO_RELOCK=0 RO_RELOCK=0
@@ -58,13 +60,30 @@ Commands:
Re-running just switches again. Re-running just switches again.
uninstall [--yes] [--keep-nix] uninstall [--yes] [--keep-nix]
Stop Home Manager's user services, uninstall Home Manager, uninstall Stop Home Manager's user services, run 'cleanup --all', uninstall Home
Nix (unless --keep-nix) and remove per-user Nix leftovers and the Manager, uninstall Nix (unless --keep-nix) and remove per-user Nix
dashboard patches' saved state (~/.local/state/steam-frame-nix/ui-patches). leftovers. Your configuration directory is never deleted.
Your configuration directory is never deleted.
cleanup [--dry-run] (--all | --orphans [--keep <artifact>]...)
Remove what steam-frame-nix (any version) wrote outside the Nix store,
only where it is provably its own; everything else is reported as
"left alone". Safe to re-run. Needs bash, coreutils, findutils, jq.
--all everything, incl. the dashboard patches' saved choices
(~/.local/state/steam-frame-nix/ui-patches). Use after
rolling back to a generation without steam-frame-nix,
or before removing it.
--orphans what the configuration no longer uses (run by the Home
Manager module on every switch); keeps saved choices.
--keep still in use (with --orphans): debugger,
firefox-desktop-userjs=<profile>
--dry-run only print what would be done
SteamVR's VRWebHelper.DebuggerEnabled can't be changed while SteamVR
runs; it is then restored when SteamVR stops (a drop-in in
/run/user/<uid>, gone at reboot).
status status
Show Nix, Home Manager and user service state. Show Nix, Home Manager and user service state, and what
'cleanup --all' would remove.
Options: Options:
--yes, -y Don't ask; answer yes to every question. --yes, -y Don't ask; answer yes to every question.
@@ -358,6 +377,453 @@ Next steps:
EOF EOF
} }
# --- cleanup ----------------------------------------------------------------
#
# Everything steam-frame-nix (any version) ever wrote outside the Nix store
# and Home Manager's own links, and how it is proven to be ours:
#
# debugger VRWebHelper.DebuggerEnabled in steamvr.vrsettings. Proof:
# $SFN_STATE/steamvr-debugger.armed (holds the value before;
# older versions: the empty marker steamvr-debugger, value
# before = absent). Restored once SteamVR is stopped; while it
# runs a runtime drop-in restores it when SteamVR stops.
# Also the runtime drop-in and restore script themselves.
# icons links hicolor/scalable/apps/<name>.svg -> Breeze in the store
# (the icon-fallbacks script of 2026-09; manifest
# $SFN_STATE/icon-fallbacks).
# firefox user.js in Firefox profiles: links to the desktop profile's
# /app/etc/firefox/steam-frame-nix-desktop-user.js, older links
# to *-firefox-*user.js and copies starting with FF_MARKER, and
# the values they left in prefs.js (only with Firefox closed).
# jellyfin the hwdec shim entries in the Jellyfin Flatpak's user override
# (nix-flatpak), an empty override file, and the shim copy of
# earlier versions (marker $SFN_STATE/jellyfin-hwdec-shim).
# ui-state the dashboard patches' saved choices
# ($SFN_STATE/ui-patches/<name>.json; --all only, never
# --orphans) and stray *.json.tmp files.
# dirs $SFN_STATE and $OUTER_RUNTIME_DIR/steam-frame-nix when empty.
#
# --orphans keeps what the current configuration still uses (--keep ...);
# the Home Manager module runs it on every switch. Anything not proven ours
# is reported as "left alone" and never touched.
SFN_STATE="${XDG_STATE_HOME:-$HOME/.local/state}/steam-frame-nix"
SFN_DATA="${XDG_DATA_HOME:-$HOME/.local/share}"
SFN_RUNTIME="$OUTER_RUNTIME_DIR/steam-frame-nix"
VRSETTINGS="$HOME/.config/openvr/config/steamvr.vrsettings"
DEBUGGER_ARMED="$SFN_STATE/steamvr-debugger.armed"
DEBUGGER_MARKER_V1="$SFN_STATE/steamvr-debugger"
DEBUGGER_RESTORE="$SFN_RUNTIME/steamvr-debugger-restore"
DEBUGGER_DROPIN="$OUTER_RUNTIME_DIR/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf"
ICON_DIR="$SFN_DATA/icons/hicolor/scalable/apps"
ICON_MANIFEST="$SFN_STATE/icon-fallbacks"
FF_DIR="$HOME/.var/app/org.mozilla.firefox/config/mozilla/firefox"
FF_DESKTOP_JS=/app/etc/firefox/steam-frame-nix-desktop-user.js
FF_MARKER="// Managed by steam-frame-nix (steamFrame.firefox); rewritten on switch."
JF_APP=org.jellyfin.JellyfinDesktop
JF_OVERRIDE="$SFN_DATA/flatpak/overrides/$JF_APP"
JF_SHIM_COPY="$HOME/.var/app/$JF_APP/mpv-hwdec-shim.so"
JF_SHIM_MARKER="$SFN_STATE/jellyfin-hwdec-shim"
UI_STATE="$SFN_STATE/ui-patches"
CLEAN_DRY=0
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"; }
c_rm() { # path why
c_do "remove $1 ($2)" || { CLEAN_GONE[$1]=1; return 0; }
rm -f -- "$1" && CLEAN_GONE[$1]=1 && c_done "removed $1 ($2)"
}
# Empty, or will be once the planned removals are done (dry run).
c_empty_after() {
local e
while IFS= read -r -d '' e; do
[[ -n ${CLEAN_GONE[$e]:-} ]] || return 1
done < <(find "$1" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
}
c_rmdir() { # dir: removed if empty (after the planned removals)
[[ -d $1 && ! -L $1 ]] && c_empty_after "$1" || return 0
c_do "remove empty directory $1" || { CLEAN_GONE[$1]=1; return 0; }
rmdir -- "$1" && CLEAN_GONE[$1]=1 && c_done "removed empty directory $1"
}
c_write() { # path why: replace the contents with stdin (same inode and mode)
local tmp
if (( CLEAN_DRY )); then cat >/dev/null; c_do "rewrite $1 ($2)" || true; return 0; fi
c_do "rewrite $1 ($2)"
tmp="$(mktemp "$1.sfn-XXXXXX")"
cat >"$tmp" && cat "$tmp" >"$1"
rm -f -- "$tmp"
c_done "rewrote $1 ($2)"
}
# systemctl --user of the outer session (overridable for tests).
c_systemctl() {
if [[ -n ${STEAM_FRAME_NIX_SYSTEMCTL:-} ]]; then "$STEAM_FRAME_NIX_SYSTEMCTL" --user "$@"
elif outer_bus_ok; then outer_systemctl "$@"
else return 1; fi
}
# running | stopped | unknown
steamvr_state() {
local s
if s="$(c_systemctl show -p ActiveState --value steamvr.service 2>/dev/null)" && [[ -n $s ]]; then
case $s in inactive|failed) echo stopped ;; *) echo running ;; esac
elif pgrep -u "$USER_ID" -x vrserver >/dev/null 2>&1; then echo running
else echo unknown; fi
}
# --- the SteamVR debugger key ---
# Puts VRWebHelper.DebuggerEnabled back to the value in <armed> ("absent",
# "false"; "nofile": the file didn't exist) if it is still true, then removes
# <armed>. Standalone (also written into the runtime restore script): bash,
# coreutils and jq only.
debugger_restore() { # vrsettings armed
local f=$1 armed=$2 prior cur tmp filter
[[ -f $armed ]] || return 0
prior="$(tr -d '[:space:]' <"$armed")"
case $prior in
false) filter='.VRWebHelper.DebuggerEnabled = false' ;;
absent|nofile|'') filter='.VRWebHelper |= del(.DebuggerEnabled) | if .VRWebHelper == {} then del(.VRWebHelper) else . end' ;;
*) echo "steam-frame-nix: $armed holds '$prior', not a value it writes; left as is" >&2; return 1 ;;
esac
if [[ -f $f ]]; then
cur="$(jq -r '.VRWebHelper.DebuggerEnabled // "absent"' "$f")" || { echo "steam-frame-nix: can't read $f" >&2; return 1; }
if [[ $cur == true ]]; then
tmp="$(mktemp "$f.XXXXXX")" || return 1
if ! jq --indent 3 "$filter" "$f" >"$tmp"; then rm -f "$tmp"; return 1; fi
chmod --reference="$f" "$tmp"
mv -f "$tmp" "$f"
if [[ $prior == nofile && $(jq -c . "$f") == '{}' ]]; then
rm -f "$f"
echo "steam-frame-nix: removed $f (created only for VRWebHelper.DebuggerEnabled)"
else
echo "steam-frame-nix: VRWebHelper.DebuggerEnabled in $f restored (${prior/nofile/absent})"
fi
else
echo "steam-frame-nix: VRWebHelper.DebuggerEnabled in $f is $cur (changed since); left as is"
fi
fi
rm -f "$armed"
}
debugger_restore_script() {
# shellcheck disable=SC2016 # literal lines of the script
printf '%s\n' '#!/usr/bin/bash' \
'# steam-frame-nix: puts SteamVR'"'"'s VRWebHelper.DebuggerEnabled back to its' \
'# value from before steam-frame-nix set it. Run by the runtime drop-in' \
'# steamvr.service.d/50-steam-frame-nix-debugger.conf when SteamVR stops;' \
'# both live in $XDG_RUNTIME_DIR until reboot. /usr/bin tools only.' \
'PATH=/usr/bin:/bin${PATH:+:$PATH}'
declare -f debugger_restore
printf 'debugger_restore %q %q\n' "$VRSETTINGS" "$DEBUGGER_ARMED"
}
debugger_dropin() {
printf '%s\n' '# steam-frame-nix: restores VRWebHelper.DebuggerEnabled when SteamVR stops.' \
'# Runtime only (gone at reboot).' \
'[Service]' \
'ExecStopPost=-/usr/bin/bash %t/steam-frame-nix/steamvr-debugger-restore'
}
# Runtime drop-in + script so that stopping SteamVR restores the key, even
# after Nix and Home Manager are gone. Returns 0 if it (would have) changed.
debugger_ensure_hook() {
local changed=1
if [[ $(cat "$DEBUGGER_RESTORE" 2>/dev/null) != "$(debugger_restore_script)" ]]; then
changed=0
if c_do "write the restore script $DEBUGGER_RESTORE"; then
mkdir -p "$SFN_RUNTIME"
debugger_restore_script >"$DEBUGGER_RESTORE"
c_done "wrote the restore script $DEBUGGER_RESTORE"
fi
fi
if [[ $(cat "$DEBUGGER_DROPIN" 2>/dev/null) != "$(debugger_dropin)" ]]; then
changed=0
if c_do "write the runtime drop-in $DEBUGGER_DROPIN"; then
mkdir -p "${DEBUGGER_DROPIN%/*}"
debugger_dropin >"$DEBUGGER_DROPIN"
c_done "wrote the runtime drop-in $DEBUGGER_DROPIN"
c_systemctl daemon-reload || warn "could not reload the user manager; the restore on SteamVR stop needs: systemctl --user daemon-reload"
fi
fi
return $changed
}
debugger_remove_hook() {
[[ -f $DEBUGGER_RESTORE ]] && c_rm "$DEBUGGER_RESTORE" "debugger restore script"
c_rmdir "$SFN_RUNTIME"
[[ -f $DEBUGGER_DROPIN ]] || return 0
c_rm "$DEBUGGER_DROPIN" "runtime drop-in of the debugger restore"
# The directories only if they held nothing but the drop-in.
c_rmdir "${DEBUGGER_DROPIN%/*}"
c_rmdir "$OUTER_RUNTIME_DIR/systemd/user"
c_rmdir "$OUTER_RUNTIME_DIR/systemd"
(( CLEAN_DRY )) || c_systemctl daemon-reload || true
return 0
}
clean_debugger() { # keep
local keep=$1 prior='' state cur
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##*/}"
elif [[ -f $DEBUGGER_MARKER_V1 ]]; then
prior=absent
if c_do "migrate $DEBUGGER_MARKER_V1 to $DEBUGGER_ARMED (value before: absent)"; then
printf 'absent\n' >"$DEBUGGER_ARMED"
rm -f -- "$DEBUGGER_MARKER_V1"
c_done "migrated $DEBUGGER_MARKER_V1 to $DEBUGGER_ARMED (value before: absent)"
else
CLEAN_GONE[$DEBUGGER_MARKER_V1]=1
fi
fi
if [[ -z $prior ]]; then
if [[ -f $VRSETTINGS ]] && command -v jq >/dev/null 2>&1 \
&& [[ $(jq -r '.VRWebHelper.DebuggerEnabled // empty' "$VRSETTINGS" 2>/dev/null) == true ]] \
&& (( ! keep )); then
c_left "VRWebHelper.DebuggerEnabled = true in $VRSETTINGS (not set by steam-frame-nix)"
fi
(( keep )) || debugger_remove_hook
return 0
fi
command -v jq >/dev/null 2>&1 || { warn "jq not found; VRWebHelper.DebuggerEnabled left as is"; return 0; }
state="$(steamvr_state)"
if [[ $state == stopped ]]; then
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: / /' \
|| warn "restoring VRWebHelper.DebuggerEnabled failed; $DEBUGGER_ARMED kept"
fi
fi
if [[ -f $DEBUGGER_ARMED && ( $cur != true || CLEAN_DRY -eq 1 ) ]]; then
c_rm "$DEBUGGER_ARMED" "proof of the debugger key, no longer needed"
fi
(( keep )) || debugger_remove_hook
else
# Running (or unknown): SteamVR rewrites the file from memory; restore
# when it stops.
if [[ $state == unknown ]] && ! c_systemctl --version >/dev/null 2>&1; then
c_defer "VRWebHelper.DebuggerEnabled ($VRSETTINGS): user manager not reachable; run this again from the Steam session or with SteamVR stopped"
return 0
fi
debugger_ensure_hook || true
(( keep )) || c_defer "VRWebHelper.DebuggerEnabled ($VRSETTINGS) is restored to ${prior/nofile/absent} when SteamVR stops"
fi
return 0
}
# --- icon fallback links ---
clean_icons() {
local l t removed=0
for l in "$ICON_DIR"/*.svg; do
[[ -L $l ]] || continue
t="$(readlink "$l")"
case $t in
/nix/store/*-breeze-icons-*/share/icons/breeze/apps/*) c_rm "$l" "icon fallback link to Breeze"; removed=1 ;;
*-home-manager-files/*) ;;
esac
done
[[ -e $ICON_MANIFEST ]] && c_rm "$ICON_MANIFEST" "icon fallback manifest" && removed=1
(( removed )) || return 0
# The directories only if they held nothing but our links.
c_rmdir "$ICON_DIR"
c_rmdir "${ICON_DIR%/*}"
c_rmdir "${ICON_DIR%/*/*}"
# GTK (Steam) rescans a theme only when a theme dir's mtime changes.
if [[ -d ${ICON_DIR%/*/*} && -z ${CLEAN_GONE[${ICON_DIR%/*/*}]:-} ]]; then
if c_do "touch ${ICON_DIR%/*/*} (mtime only, so Steam rescans icons)"; then
touch -- "${ICON_DIR%/*/*}"; c_done "touched ${ICON_DIR%/*/*} (mtime only, so Steam rescans icons)"
fi
fi
return 0
}
# --- Firefox user.js ---
ff_keys() { sed -n 's/^[[:space:]]*user_pref(\("[^"]*"\),.*/\1/p' "$1" 2>/dev/null || true; }
ff_in_use() { find /proc/[0-9]*/fd -lname "$1/.parentlock" -print -quit 2>/dev/null | grep -q .; }
clean_firefox() { # keep_profile ('' = none)
local keep=$1 prof name u t kind keys pats left
[[ -d $FF_DIR ]] || return 0
for prof in "$FF_DIR"/*/; do
prof=${prof%/}; name=${prof##*/}; u=$prof/user.js
[[ -e $u || -L $u ]] || continue
keys=''
if [[ -L $u ]]; then
t="$(readlink "$u")"
case $t in
"$FF_DESKTOP_JS")
[[ $name == "$keep" ]] && continue
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" ;;
*) c_left "$u (link to $t)"; continue ;;
esac
elif [[ -f $u && $(head -n1 "$u") == "$FF_MARKER" ]]; then
kind="user.js copy of an older version"; keys="$(ff_keys "$u")"
else
c_left "$u (not written by steam-frame-nix)"; continue
fi
# Firefox stored the values the file set in prefs.js; take them out too.
# shellcheck disable=SC2001 # per line
pats="$(sed 's/.*/user_pref(&,/' <<<"$keys")"
if [[ -n $keys && -f $prof/prefs.js ]] && grep -qF "$pats" "$prof/prefs.js"; then
if ff_in_use "$prof"; then
c_defer "$u: Firefox is using profile $name; close it and run this again"
continue
fi
left="$(grep -vF "$pats" "$prof/prefs.js" || true)"
printf '%s\n' "$left" | c_write "$prof/prefs.js" "values of the $kind: ${keys//$'\n'/ }"
fi
c_rm "$u" "$kind"
c_rmdir "$prof"
done
return 0
}
# --- Jellyfin ---
# The override with our entries taken out: LD_PRELOAD of the shim,
# SFN_MPV_HWDEC, the shim's filesystems entry and, only if one of these was
# there, devices=all. Prints "devices=<0|1>" (devices=all removed), then the
# result; exit 3 if nothing of ours was found.
jf_strip() {
awk '
function trim(s) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", s); return s }
function drop(list, pat, n, a, i, out) {
n = split(list, a, ";"); out = ""
for (i = 1; i <= n; i++) { a[i] = trim(a[i]); if (a[i] == "") continue
if (a[i] ~ pat) { hit = 1; continue } out = out a[i] ";" }
return out }
FNR == 1 { pass++; sec = "" }
/^[[:space:]]*\[/ { sec = trim($0); if (pass == 2) { hdr[++nh] = sec; body[nh] = "" } next }
{
line = $0; key = trim(substr(line, 1, index(line, "=") - 1)); val = substr(line, index(line, "=") + 1)
hit = 0
if (sec == "[Environment]" && key == "LD_PRELOAD" && val ~ /^\/nix\/store\/[^\/]*mpv-hwdec-shim[^\/]*\/lib\/mpv-hwdec-shim\.so$/) { hit = 1; line = "" }
else if (sec == "[Environment]" && key == "SFN_MPV_HWDEC") { hit = 1; line = "" }
else if (sec == "[Context]" && key == "filesystems") {
v = drop(val, "^/nix/store/[^/]*mpv-hwdec-shim[^/]*(:ro)?$"); line = (v == "" ? "" : key "=" v) }
else if (sec == "[Context]" && key == "devices" && pass == 2 && ours) {
v = drop(val, "^all$"); if (hit) devs = 1; line = (v == "" ? "" : key "=" v); hit = 0 }
if (pass == 1) { if (hit) ours = 1; next }
if (trim(line) == "") next
body[nh] = body[nh] line "\n"
}
END {
if (!ours) exit 3
print "devices=" (devs ? 1 : 0)
for (i = 0; i <= nh; i++) if (body[i] != "") { if (i) printf "%s\n", hdr[i]; printf "%s\n", body[i] }
}
' "$1" "$1"
}
clean_jellyfin() {
local out rc t
if [[ -L $JF_OVERRIDE ]]; then
t="$(readlink "$JF_OVERRIDE")"
[[ $t == *-home-manager-files/* ]] || c_left "$JF_OVERRIDE (link to $t)"
elif [[ -f $JF_OVERRIDE ]]; then
if [[ -z $(tr -d '[:space:]' <"$JF_OVERRIDE") ]]; then
c_rm "$JF_OVERRIDE" "empty Flatpak override"
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=${out#*$'\n'}; [[ $out == devices=? ]] && out=''
if [[ -z $(tr -d '[:space:]' <<<"$out") ]]; then
c_rm "$JF_OVERRIDE" "Flatpak override with only the hwdec shim entries"
else
printf '%s\n' "$out" | c_write "$JF_OVERRIDE" "hwdec shim entries"
warn "$JF_OVERRIDE keeps entries of its own: $(grep -v '^\[' <<<"$out" | grep . | tr '\n' ' ')"
fi
elif (( rc == 3 )); then
c_left "$JF_OVERRIDE (no hwdec shim entries)"
else
warn "could not read $JF_OVERRIDE; left as is"
fi
fi
fi
if [[ -e $JF_SHIM_MARKER ]]; then
[[ -e $JF_SHIM_COPY || -L $JF_SHIM_COPY ]] && c_rm "$JF_SHIM_COPY" "hwdec shim copy of an older version"
c_rm "$JF_SHIM_MARKER" "its marker"
elif [[ -e $JF_SHIM_COPY ]]; then
c_left "$JF_SHIM_COPY (no marker $JF_SHIM_MARKER)"
fi
return 0
}
# --- dashboard patch state ---
clean_ui_state() { # all
local f n age=(-mmin +1) # --orphans: not one being written right now
[[ -d $UI_STATE ]] || return 0
(( $1 )) && age=()
while IFS= read -r -d '' f; do
c_rm "$f" "unfinished write of the dashboard patch state"
done < <(find "$UI_STATE" -maxdepth 1 -type f -name '*.json.tmp' "${age[@]}" -print0)
if (( $1 )); then
for f in "$UI_STATE"/*.json; do
[[ -f $f ]] || continue
n=${f##*/}; c_rm "$f" "saved choices of the dashboard patch ${n%.json}"
done
fi
c_rmdir "$UI_STATE"
return 0
}
cmd_cleanup() {
local mode='' keep_debugger=0 keep_ff='' keeps=0 k
while (( $# )); do
case $1 in
--all) mode=all; shift ;;
--orphans) mode=orphans; shift ;;
--dry-run|-n) CLEAN_DRY=1; shift ;;
--keep)
[[ $# -ge 2 ]] || die "--keep needs an artifact"
k=$2; shift 2; keeps=$((keeps + 1))
case $k in
debugger) keep_debugger=1 ;;
firefox-desktop-userjs=?*) keep_ff=${k#*=} ;;
*) die "cleanup: unknown artifact '$k' for --keep (debugger, firefox-desktop-userjs=<profile>)" ;;
esac ;;
-h|--help) usage; exit 0 ;;
*) die "cleanup: unknown option '$1' (see --help)" ;;
esac
done
[[ -n $mode ]] || die "cleanup: --all or --orphans is required (see --help)"
[[ $mode == all && $keeps -gt 0 ]] && die "cleanup: --keep only goes with --orphans"
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_debugger "$keep_debugger"
clean_icons
clean_firefox "$keep_ff"
clean_jellyfin
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"
return 0
}
# --- uninstall -------------------------------------------------------------- # --- uninstall --------------------------------------------------------------
# User units installed by Home Manager (unit files resolving into /nix/store). # User units installed by Home Manager (unit files resolving into /nix/store).
@@ -471,10 +937,6 @@ remove_leftovers() { # keep_nix
fi fi
fi fi
remove_path "$state/home-manager" remove_path "$state/home-manager"
# Saved state of the dashboard UI patches (steam-ui-patches, state = true).
# Only this subdirectory: the directory's other files have their own rules.
remove_path "$state/steam-frame-nix/ui-patches"
rmdir "$state/steam-frame-nix" 2>/dev/null || true
if [[ -L $HM_CONFIG_LINK ]]; then if [[ -L $HM_CONFIG_LINK ]]; then
info "$HM_CONFIG_LINK pointed to $(readlink "$HM_CONFIG_LINK")" info "$HM_CONFIG_LINK pointed to $(readlink "$HM_CONFIG_LINK")"
remove_path "$HM_CONFIG_LINK" remove_path "$HM_CONFIG_LINK"
@@ -512,6 +974,7 @@ cmd_uninstall() {
load_nix load_nix
stop_hm_services stop_hm_services
cmd_cleanup --all
remove_hm remove_hm
if (( ! keep_nix )); then if (( ! keep_nix )); then
@@ -534,9 +997,13 @@ cmd_uninstall() {
Intentionally left in place: Intentionally left in place:
- your configuration (e.g. $DEFAULT_CONFIG_DIR) - your configuration (e.g. $DEFAULT_CONFIG_DIR)
- files Home Manager renamed to *.hm-backup-<time> - files Home Manager renamed to *.hm-backup-<time>
- app data, e.g. ~/.local/share/docker, and Flatpak apps - app data, e.g. ~/.local/share/docker, Firefox profiles, and Flatpak apps
Log out or reboot so running sessions drop the removed tweaks. Log out or reboot so running sessions drop the removed tweaks.
EOF EOF
if (( ${#CLEAN_DEFERRED[@]} )); then
printf '\nNot done yet:\n'
printf ' - %s\n' "${CLEAN_DEFERRED[@]}"
fi
} }
# --- status ----------------------------------------------------------------- # --- status -----------------------------------------------------------------
@@ -595,6 +1062,8 @@ cmd_status() {
info "user manager not reachable at $OUTER_RUNTIME_DIR" info "user manager not reachable at $OUTER_RUNTIME_DIR"
fi fi
info "$(printf '%-30s %s' clipboard-sync "$(pgrep -u "$USER_ID" -x clipboard-sync >/dev/null && echo running || echo 'not running')")" info "$(printf '%-30s %s' clipboard-sync "$(pgrep -u "$USER_ID" -x clipboard-sync >/dev/null && echo running || echo 'not running')")"
cmd_cleanup --dry-run --all
} }
# --- main ------------------------------------------------------------------- # --- main -------------------------------------------------------------------
@@ -606,6 +1075,7 @@ main() {
install) cmd_install "$@" ;; install) cmd_install "$@" ;;
uninstall) cmd_uninstall "$@" ;; uninstall) cmd_uninstall "$@" ;;
status) cmd_status "$@" ;; status) cmd_status "$@" ;;
cleanup) cmd_cleanup "$@" ;;
-h|--help|help) usage ;; -h|--help|help) usage ;;
'') usage >&2; exit 2 ;; '') usage >&2; exit 2 ;;
*) printf 'unknown command: %s\n\n' "$cmd" >&2; usage >&2; exit 2 ;; *) printf 'unknown command: %s\n\n' "$cmd" >&2; usage >&2; exit 2 ;;
+198
View File
@@ -0,0 +1,198 @@
# Checks of `install.sh cleanup` on fake home and runtime dirs: every
# 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.
{ pkgs }:
let
cleanup = pkgs.callPackage ./package.nix { };
in
pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq ]; } ''
set -euo pipefail
fail() { echo "FAIL: $*" >&2; exit 1; }
export HOME XDG_RUNTIME_DIR STEAM_FRAME_NIX_RUNTIME_DIR STEAM_FRAME_NIX_SYSTEMCTL
unset XDG_STATE_HOME XDG_DATA_HOME XDG_CONFIG_HOME
# systemctl stub: SteamVR's state from $STUB/state, calls logged.
STUB=$PWD/stub; mkdir -p $STUB
cat > $STUB/systemctl <<'SH'
#!/usr/bin/env bash
echo "$*" >> "$STUB/log"
case "$*" in *"show -p ActiveState --value steamvr.service"*) cat "$STUB/state" ;; esac
SH
chmod +x $STUB/systemctl
sed -i "1s|.*|#!${pkgs.bash}/bin/bash|" $STUB/systemctl
export STUB
STEAM_FRAME_NIX_SYSTEMCTL=$STUB/systemctl
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
S=$HOME/.local/state/steam-frame-nix
V=$HOME/.config/openvr/config/steamvr.vrsettings
I=$HOME/.local/share/icons/hicolor/scalable/apps
F=$HOME/.var/app/org.mozilla.firefox/config/mozilla/firefox
O=$HOME/.local/share/flatpak/overrides
mkdir -p $S/ui-patches ''${V%/*} $I $F $O
}
snap() { (cd $root && find . -printf '%p %y %m %l %T@\n' | sort; find . -type f -exec sha256sum {} + | sort) ; }
has() { grep -qF -- "$2" <<< "$1" || fail "output lacks '$2':"$'\n'"$1"; }
hasnt() { ! grep -qF -- "$2" <<< "$1" || fail "output has '$2':"$'\n'"$1"; }
gone() { for p; do [ ! -e "$p" ] && [ ! -L "$p" ] || fail "still there: $p"; done; }
there() { for p; do [ -e "$p" ] || [ -L "$p" ] || fail "missing: $p"; done; }
noop() { # a further run changes nothing
local before; before=$(snap); res=$(steam-frame-nix-cleanup "$@")
[ "$(snap)" = "$before" ] || fail "second run changed files"
! grep -Eq '^ +(removed|rewrote|wrote|migrated|touched) | restored \(' <<< "$res" || fail "second run acted:"$'\n'"$res"
}
# --- A: --all, SteamVR stopped: everything ---
fresh a
printf '{\n "VRWebHelper" : {\n "DebuggerEnabled" : true,\n "DebuggerPort" : 8087\n },\n "steamvr" : {}\n}\n' > $V
chmod 600 $V
touch $S/steamvr-debugger # marker of the first version
mkdir -p $root/run/steam-frame-nix $root/run/systemd/user/steamvr.service.d
echo old > $root/run/steam-frame-nix/steamvr-debugger-restore
echo old > $root/run/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf
echo keep > $root/run/systemd/user/steamvr.service.d/other.conf
ln -s /nix/store/00000000000000000000000000000000-breeze-icons-6.30.0/share/icons/breeze/apps/64/utilities-terminal.svg $I/utilities-terminal.svg
ln -s /nix/store/00000000000000000000000000000000-home-manager-files/.local/share/icons/hicolor/scalable/apps/hm.svg $I/hm.svg
ln -s /somewhere/else.svg $I/other.svg
printf 'utilities-terminal\n' > $S/icon-fallbacks
mkdir -p $F/desktop $F/a.default $F/b.old $F/c.mine $F/d.busy $F/e.fresh
ln -s /app/etc/firefox/steam-frame-nix-desktop-user.js $F/desktop/user.js
printf 'user_pref("full-screen-api.ignore-widgets", false);\nuser_pref("other", 1);\n' > $F/desktop/prefs.js
printf '%s\nuser_pref("a.b", true);\n' '// Managed by steam-frame-nix (steamFrame.firefox); rewritten on switch.' > $F/a.default/user.js
printf 'user_pref("a.b", true);\nuser_pref("c.d", 2);\n' > $F/a.default/prefs.js
ln -s /nix/store/00000000000000000000000000000000-firefox-user.js $F/b.old/user.js
echo 'user_pref("mine", 1);' > $F/c.mine/user.js
printf '%s\nuser_pref("a.b", true);\n' '// Managed by steam-frame-nix (steamFrame.firefox); rewritten on switch.' > $F/d.busy/user.js
printf 'user_pref("a.b", true);\n' > $F/d.busy/prefs.js; touch $F/d.busy/.parentlock
ln -s /app/etc/firefox/steam-frame-nix-desktop-user.js $F/e.fresh/user.js
printf '[Context]\ndevices=all;\nfilesystems=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro;xdg-music:ro;\n\n[Environment]\nLD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so\nSFN_MPV_HWDEC=v4l2m2m-copy,auto-copy\nFOO=bar\n' > $O/org.jellyfin.JellyfinDesktop
printf '[Context]\ndevices=all;\n' > $O/org.example.Other
mkdir -p $HOME/.var/app/org.jellyfin.JellyfinDesktop
echo so > $HOME/.var/app/org.jellyfin.JellyfinDesktop/mpv-hwdec-shim.so; touch $S/jellyfin-hwdec-shim
echo '{"a":1}' > $S/ui-patches/frame-controls.json; echo '{' > $S/ui-patches/vr-cat.json.tmp
echo mine > $S/ui-patches/notes.txt
exec 9< $F/d.busy/.parentlock # Firefox "uses" d.busy
before=$(snap)
res=$(steam-frame-nix-cleanup --dry-run --all)
[ "$(snap)" = "$before" ] || fail "--dry-run changed files"
has "$res" "would remove $I/utilities-terminal.svg"
has "$res" "would restore VRWebHelper.DebuggerEnabled"
hasnt "$res" "removed "
! grep -q daemon-reload $STUB/log || fail "--dry-run reloaded systemd"
res=$(steam-frame-nix-cleanup --all); echo "$res"
# debugger: key gone (value before: absent), other keys kept, 3-space indent, mode kept
[ "$(jq -c .VRWebHelper $V)" = '{"DebuggerPort":8087}' ] || fail "vrsettings: $(cat $V)"
grep -q '^ "VRWebHelper"' $V || fail "indent"
[ "$(stat -c %a $V)" = 600 ] || fail "mode"
gone $S/steamvr-debugger $S/steamvr-debugger.armed $root/run/steam-frame-nix \
$root/run/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf
there $root/run/systemd/user/steamvr.service.d/other.conf
grep -q daemon-reload $STUB/log || fail "no daemon-reload after removing the drop-in"
# icons
gone $I/utilities-terminal.svg $S/icon-fallbacks
there $I/hm.svg $I/other.svg
has "$res" "left alone: $F/c.mine/user.js"
# firefox
gone $F/desktop/user.js $F/a.default/user.js $F/b.old/user.js $F/e.fresh
[ "$(cat $F/desktop/prefs.js)" = 'user_pref("other", 1);' ] || fail "desktop prefs.js: $(cat $F/desktop/prefs.js)"
[ "$(cat $F/a.default/prefs.js)" = 'user_pref("c.d", 2);' ] || fail "a.default prefs.js"
there $F/c.mine/user.js $F/d.busy/user.js
grep -q a.b $F/d.busy/prefs.js || fail "busy profile changed"
has "$res" "deferred: $F/d.busy/user.js: Firefox is using profile d.busy"
# jellyfin
[ "$(cat $O/org.jellyfin.JellyfinDesktop)" = "$(printf '[Context]\nfilesystems=xdg-music:ro;\n\n[Environment]\nFOO=bar')" ] \
|| fail "override: $(cat $O/org.jellyfin.JellyfinDesktop)"
has "$res" "devices=all goes too"
there $O/org.example.Other
gone $HOME/.var/app/org.jellyfin.JellyfinDesktop/mpv-hwdec-shim.so $S/jellyfin-hwdec-shim
# ui state: ours gone, foreign file kept (so the dirs stay)
gone $S/ui-patches/frame-controls.json $S/ui-patches/vr-cat.json.tmp
there $S/ui-patches/notes.txt
noop --all
exec 9<&-
res=$(steam-frame-nix-cleanup --all)
gone $F/d.busy/user.js
[ ! -s $F/d.busy/prefs.js ] || [ -z "$(tr -d '[:space:]' < $F/d.busy/prefs.js)" ] || fail "busy prefs.js after close"
rm $S/ui-patches/notes.txt
res=$(steam-frame-nix-cleanup --all)
gone $S
there $HOME/.local/share/icons/hicolor/scalable/apps/hm.svg
noop --all
echo "A ok"
# --- B: SteamVR running: deferred to the runtime drop-in ---
fresh b
printf '{\n "VRWebHelper" : {\n "DebuggerEnabled" : true\n }\n}\n' > $V
echo false > $S/steamvr-debugger.armed
echo active > $STUB/state
res=$(steam-frame-nix-cleanup --all); echo "$res"
has "$res" "deferred: VRWebHelper.DebuggerEnabled"
[ "$(jq .VRWebHelper.DebuggerEnabled $V)" = true ] || fail "changed while running"
there $S/steamvr-debugger.armed $root/run/steam-frame-nix/steamvr-debugger-restore
grep -qx 'ExecStopPost=-/usr/bin/bash %t/steam-frame-nix/steamvr-debugger-restore' \
$root/run/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf || fail "drop-in"
[ "$(grep -c daemon-reload $STUB/log)" = 1 ] || fail "daemon-reload count"
noop --all
[ "$(grep -c daemon-reload $STUB/log)" = 1 ] || fail "second run reloaded again"
# SteamVR stops: ExecStopPost runs the script
bash $root/run/steam-frame-nix/steamvr-debugger-restore
[ "$(jq -c .VRWebHelper $V)" = '{"DebuggerEnabled":false}' ] || fail "restore: $(cat $V)"
gone $S/steamvr-debugger.armed
echo inactive > $STUB/state
res=$(steam-frame-nix-cleanup --all)
gone $root/run/steam-frame-nix $root/run/systemd $S
noop --all
echo "B ok"
# --- C: --orphans with keeps ---
fresh c
printf '{"VRWebHelper":{"DebuggerEnabled":true}}\n' > $V
echo absent > $S/steamvr-debugger.armed
mkdir -p $F/desktop $F/x.default
ln -s /app/etc/firefox/steam-frame-nix-desktop-user.js $F/desktop/user.js
ln -s /app/etc/firefox/steam-frame-nix-desktop-user.js $F/x.default/user.js
printf '[Context]\ndevices=all;\nfilesystems=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim:ro;\n\n[Environment]\nLD_PRELOAD=/nix/store/00000000000000000000000000000000-mpv-hwdec-shim/lib/mpv-hwdec-shim.so\nSFN_MPV_HWDEC=x\n' > $O/org.jellyfin.JellyfinDesktop
mkdir -p $HOME/.var/app/org.jellyfin.JellyfinDesktop
echo so > $HOME/.var/app/org.jellyfin.JellyfinDesktop/mpv-hwdec-shim.so # no marker: not ours
echo '{"a":1}' > $S/ui-patches/frame-controls.json; echo '{}' > $S/ui-patches/vr-cat.json
echo '{' > $S/ui-patches/x.json.tmp; touch -d '-1 hour' $S/ui-patches/x.json.tmp
echo '{' > $S/ui-patches/y.json.tmp # being written: kept
echo active > $STUB/state
res=$(steam-frame-nix-cleanup --orphans --keep debugger --keep firefox-desktop-userjs=desktop); echo "$res"
hasnt "$res" "deferred" # kept: the next start re-arms it
there $root/run/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf $S/steamvr-debugger.armed
there $F/desktop/user.js $S/ui-patches/frame-controls.json $S/ui-patches/vr-cat.json $S/ui-patches/y.json.tmp
gone $F/x.default/user.js $O/org.jellyfin.JellyfinDesktop $S/ui-patches/x.json.tmp
there $HOME/.var/app/org.jellyfin.JellyfinDesktop/mpv-hwdec-shim.so
has "$res" "left alone: $HOME/.var/app/org.jellyfin.JellyfinDesktop/mpv-hwdec-shim.so"
noop --orphans --keep debugger --keep firefox-desktop-userjs=desktop
# SteamVR stopped (e.g. after a power loss): the key is restored, hook kept
echo inactive > $STUB/state
res=$(steam-frame-nix-cleanup --orphans --keep debugger --keep firefox-desktop-userjs=desktop)
[ "$(jq -c . $V)" = '{}' ] || fail "orphans restore: $(cat $V)"
gone $S/steamvr-debugger.armed
there $root/run/systemd/user/steamvr.service.d/50-steam-frame-nix-debugger.conf
# debugger no longer kept: runtime pieces go
res=$(steam-frame-nix-cleanup --orphans --keep firefox-desktop-userjs=desktop)
gone $root/run/steam-frame-nix $root/run/systemd
noop --orphans --keep firefox-desktop-userjs=desktop
there $S/ui-patches/frame-controls.json $HOME/.local/share/icons/hicolor/scalable/apps # not ours
! steam-frame-nix-cleanup --all --keep debugger 2>/dev/null || fail "--all --keep accepted"
! steam-frame-nix-cleanup --orphans --keep bogus 2>/dev/null || fail "unknown --keep accepted"
echo "C ok"
# --- D: the user's own key is never touched ---
fresh d
printf '{"VRWebHelper":{"DebuggerEnabled":true}}\n' > $V
res=$(steam-frame-nix-cleanup --all)
has "$res" "left alone: VRWebHelper.DebuggerEnabled = true"
[ "$(jq -c . $V)" = '{"VRWebHelper":{"DebuggerEnabled":true}}' ] || fail "user's key changed"
echo "D ok"
touch $out
''
+10
View File
@@ -0,0 +1,10 @@
# 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 }:
writeShellApplication {
name = "steam-frame-nix-cleanup";
runtimeInputs = [ bash coreutils findutils jq gnugrep gnused gawk procps ];
text = ''
exec bash ${../../install.sh} cleanup "$@"
'';
}