It is built from source, which makes the first switch long. The template
lists it as a commented option; status shows clipboard-sync only while it
runs or its autostart link exists.
create_config locks the template as a path (not the git repository) and
commits everything once, with the user's git identity or, without one,
a fallback passed with -c for that commit only. The template switch no
longer warns that the tree is dirty; the marker and the one-commit rule
are unchanged.
- One systemctl --user wrapper (user_systemctl, with the test override)
instead of outer_systemctl, outer_bus_ok and c_systemctl.
- link_config for the three places that link ~/.config/home-manager;
git_noprompt, receipt_planner, FLAKES_LINE and c_done inlined.
- c_rm_link for the artifacts proven by their link target (screenshots,
the VR pet icon); a foreign link is reported with its target.
- Shorter help (--clone, cleanup, restart-check) and comments that
repeated docs/cleanup.md.
No cleanup path changes: every artifact an older published version wrote
still has its rule.
Without ~/.config/home-manager and --flake, a flake in ~/nix-config
(e.g. left by uninstall) is used as it is, like --flake ~/nix-config,
and linked to ~/.config/home-manager. A directory there without
flake.nix still stops the install.
The switch already ran, so the note only says to open a new terminal,
where the configuration is and how to apply changes. Dropped: the
"uncomment the steamFrame block" hint, the Konsole/Developer Mode hint
and the status/uninstall line.
The closing message no longer lists generic "left in place" items
(*.hm-backup-<time> files that usually don't exist, app data, Flatpak
apps). It says where the configuration stays and how install uses it
again, lists the *.hm-backup-* files Home Manager actually made (if
any), and asks to log out or reboot.
- The nix.mount drop-in is also removed when the uninstall fails or is
interrupted (exit trap), and when daemon-reload failed after writing it.
- --dir with a trailing slash no longer puts the temporary clone inside
the directory it replaces.
- The template's repository uses Nix's git when SteamOS has none, so the
template marker exists there too (--clone needs it).
- A failed Nix uninstall during the reinstall of a broken Nix says so.
- clone_config: one branch per case; shorter comments, help and docs.
- Tests: an interrupted uninstall, --dir with a trailing slash; a
redundant "used as it is" case dropped.
The template now enables git (SteamOS's own, package = null; Home
Manager writes its config) with the GitHub CLI's credential helper for
github.com and `store` (~/.git-credentials) for other hosts.
Without a Home Manager configuration, `install --clone` first creates
and activates the template in the target directory, then clones next to
it with git's helpers and GIT_TERMINAL_PROMPT=0 (never a password
prompt). If that fails with a terminal, it offers `gh auth login` and
retries once; otherwise the template stays active and the message says
how to continue. After the clone the template is removed, the clone
takes its place and Home Manager switches to it.
Only a missing (or empty) target or the untouched template is cloned
into: create_config marks its configuration with the hash of its files
in .git/steam-frame-nix-template. Anything else there is used as it is,
so a rerun continues where a run stopped (after a failed switch it just
switches); Nix and the template switch (helpers present) are skipped
when already done. The origin comparison and the pull are gone.
Programs started from the Nix store (the Steam session itself, the
desktop portal) keep /nix busy until logout, so nix-installer's
`systemctl stop nix.mount` failed and the wait for them could never end.
A runtime drop-in (LazyUnmount=yes on nix.mount, in /run, removed again
afterwards) makes that stop detach /nix; the programs keep their open
files until they exit. They are listed for information only, without a
prompt. The re-exec of a bash from the Nix store is no longer needed.
- uninstall: nix-installer's copy outside /nix is made and run as root
(a user-writable copy run by sudo could be swapped); README uninstall
paragraph shortened to one account of what happens when Nix stays.
- session.nix: the restart-check activation after steamFrameUserServices,
so the latter's comment is above it again.
- install.sh: a duplicate `local` in port_listening; comments.
- docs/cleanup.md: the installer internals of --clone (how an existing
clone is recognised, which git) and restart-check (what it compares,
test overrides); README keeps the usage.
- README install paragraph: shorter list of the dashboard patches.
nix-installer uninstall failed at `systemctl stop nix.mount` ("Job
failed") while programs started from the Nix store still ran: a terminal
session's tools, or apps that mapped Home Manager's mime.cache.
- Before nix-installer runs, after Home Manager is gone, uninstall scans
/proc for the user's processes whose exe, cwd, root, an fd or a mapped
file is in /nix, and lists them by name and PID (the shells it was
started from included; itself, its subshells and its curl | bash
pipeline skipped). Close them and press Enter to re-check, or abort;
with --yes or without a terminal it stops: "close these or reboot, then
run uninstall again". Nothing is killed (replaces the old kill prompt).
- nix-installer runs from a copy outside /nix.
- cleanup check H: a fake /proc (STEAM_FRAME_NIX_PROC) with programs from
the store stops before Nix; without them Nix is removed.
Clones your own config repository (git@host:owner/repo, https://...,
github:owner/repo) into ~/nix-config or --dir and installs it like
--flake <dir>. An existing clone of the same repository (any URL form) is
reused and, after asking, pulled with --ff-only; another repository, a
non-clone or a different branch is refused. A failed clone hints at SSH
URLs and gh auth login. Uses SteamOS' git, else Nix's.
Checked against local bare repositories through a logging git.
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.
<runtimeDir>/steam-frame-nix/vr-pet: icon.png when it links to a
*-vr-pet-icons store icon, its empty .lock and stray .icon.tmp.* links,
then the directory; kept by --orphans --keep pet. The empty runtime
directory now also goes with --orphans.
debugger_ensure_hook's "changed" status was never read; clean_icons'
case for Home Manager's links did nothing; cmd_cleanup computes --all
once. --quiet in the usage synopsis.
grep's read error (status 2) was swallowed with || true, so the profile's
prefs.js could be rewritten with nothing. Now it warns and keeps both
prefs.js and user.js for the next run.
c_write was fed through a pipe, so it ran in a subshell: its action
count and the printed header were lost, which printed "nothing to clean
up" after a rewrite, and a second header with --quiet. It takes the
content as an argument now.
ff_in_use piped find into grep -q; with pipefail, find's error on any
unreadable /proc/<pid>/fd (root's processes) made every profile look
unused, so cleanup could remove user.js and rewrite prefs.js under a
running Firefox. The Nix check's sandbox has no such processes.
Steam keeps VR screenshots (Steam button + trigger) in
~/.local/share/Steam/userdata/<account ID>/760/remote/250820/screenshots,
where Dolphin, Gwenview and the file pickers don't look.
screenshots (module screenshots, opt-in) links ~/Pictures/<name> (default
"SteamVR Screenshots") there. The account ID comes from steamUserId, or,
unset, at runtime: the Home Manager link points to
<runtimeDir>/steam-frame-nix/screenshots (tmpfs), which the oneshot user
service steam-frame-nix-screenshots points to the account last logged in
(loginusers.vdf MostRecent, else latest timestamp, else the only userdata
folder), on switch, at login and when loginusers.vdf or userdata changes.
install.sh cleanup knows the tmpfs link (--keep screenshots while used).
Flake checks: account detection on fake Steam dirs; cleanup of the link.
steamFrame.launchers.<desktop ID> takes the app's own entry (a Flatpak's
export, a Nix package's or a host file) and rewrites only its command lines
(hostEnv, env, flatpakArgs, args, wrappers) and the keys asked for
(mimeTypes, defaultFor, settings), so name, icon, translations and actions
stay the app's and follow its updates.
- Package entries are rewritten at build time; Flatpak/host file entries at
runtime by steam-frame-nix-launchers into
<session.runtimeDir>/steam-frame-nix/applications (tmpfs), on switch, at
login and when Flatpak installs/updates apps (path unit); the Home Manager
links point there.
- keyring.{enable,electron} replaces steamFrame.keyring.{flatpaks,programs}
(clean break: the old options fail with the new form).
- Firefox's launcher is the Flatpak's entry with the profile wrapper in
front (no --profile for the profile manager); Jellyfin's hardware
decoding sets flatpakArgs and env.
- cleanup --all removes the tmpfs entries.
- Check on copies of the real Element, KRDC, Firefox, gedit, Jellyfin and
Claude entries, quoting and generator behaviour.
- firefox: the prefs.js patterns built once; the extension link condition
without the redundant desktopFix (it implies a pref).
- install.sh steamvr-debugger-arm: no second copy of the old-marker
migration (cleanup, run on every switch before the arm unit exists,
already does it).
- steam-ui-patches: shorter state option description; lib's description
mentions extraArgs.
- README: uiPatches.patches fields in the options table (moved from
docs/ui-patches.md); docs/ui-patches.md: the finder helpers;
docs/steamvr-debugger.md: repeated sentences removed.
The desktop profile's user.js link (undoing the fullscreen fix there) was
kept in place on every switch and before every launch. Now the launcher
(nested desktop only) makes it right before Firefox starts, waits for
Firefox instead of exec'ing it, and once it has exited and the profile is
no longer in use removes the link and the value Firefox stored from it in
prefs.js. A second launch that hands its URL to the running Firefox returns
at once and leaves both alone; a user.js of the user's own is never
touched. The desktop profile itself is a normal Firefox profile (browser
data) and is never removed.
No activation step touches profiles anymore: leftovers of a crash and what
older versions wrote (user.js copies and links, their prefs.js values) are
steam-frame-nix-cleanup's (orphans on switch), which leaves the desktop
link alone while the profile is in use; the firefox-desktop-userjs --keep
is gone. The launcher lives in firefox/launcher.nix; checks.firefox runs it
against a fake flatpak (link only while running, forwarded second launch,
prefs.js cleaned only once unlocked, own user.js untouched, Steam session).
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.
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.
`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.
SteamOS's steamvr.service runs `rm -rf ~/.cache/SteamVR` on every SteamVR
start, which deletes vrwebhelper's browser profile and with it the
localStorage frame-controls kept its placements in, so they were lost on
every SteamVR restart and reboot.
steam-ui-patches: per-patch persistent state (`state = true`). The injector
keeps ~/.local/state/steam-frame-nix/ui-patches/<name>.json, seeds
window.__sfuiStore in a fresh page before the patch runs and writes the file
(atomically, on change, name-checked, max 64 KiB) when the page calls the
CDP binding __sfuiStoreSave.
frame-controls v5: placements saved there (v4 localStorage copy migrated
once, then removed). steam-close-button v8: "Steam hidden" saved there; a
restored hidden state hides Steam once if SteamVR already shows it when the
patch attaches (or first shows it before the patch sees Steam's frame).
README: "Changes outside Nix" lists the directory; install.sh uninstall
removes it.
- status lists every Home Manager user service instead of a hardcoded
docker.service (that's not a steam-frame-nix module)
- unique backup suffix per run (Home Manager aborts if *.backup exists)
- uninstall only force-kills processes still running from /nix/store
- drop fallbacks this installer never needs: old `nix show-config` /
`nix store ping`, the legacy per-user HM profile path, a PATH fallback,
grep-parsing the receipt without jq, `--flake=value`
install.sh installs Nix (nix-installer, steam-deck planner, flakes
enabled) and activates a Home Manager configuration, creating one from
the new flake template if there is none; uninstall stops Home Manager's
user services, runs home-manager uninstall and removes Nix and per-user
leftovers; status shows the current state. Works piped from curl.