mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 04:04:16 +02:00
Uninstall: detach /nix lazily instead of waiting for its users
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.
This commit is contained in:
1 parent
b852a1eb07
commit
269321ea92
2 files changed
+90
-78
No files matched your search
+53
-60
@@ -28,6 +28,8 @@ USER_NAME="$(id -un)"
|
|||||||
USER_ID="$(id -u)"
|
USER_ID="$(id -u)"
|
||||||
OUTER_RUNTIME_DIR="${STEAM_FRAME_NIX_RUNTIME_DIR:-/run/user/$USER_ID}"
|
OUTER_RUNTIME_DIR="${STEAM_FRAME_NIX_RUNTIME_DIR:-/run/user/$USER_ID}"
|
||||||
SFN_PROC="${STEAM_FRAME_NIX_PROC:-/proc}" # tests: a fake /proc
|
SFN_PROC="${STEAM_FRAME_NIX_PROC:-/proc}" # tests: a fake /proc
|
||||||
|
# Runtime drop-in for the uninstall (tests: another directory).
|
||||||
|
NIX_MOUNT_DROPIN="${STEAM_FRAME_NIX_SYSTEM_RUNTIME:-/run/systemd/system}/nix.mount.d/50-steam-frame-nix-lazy-unmount.conf"
|
||||||
|
|
||||||
ASSUME_YES=0
|
ASSUME_YES=0
|
||||||
RO_RELOCK=0
|
RO_RELOCK=0
|
||||||
@@ -70,8 +72,8 @@ Commands:
|
|||||||
uninstall [--yes] [--keep-nix]
|
uninstall [--yes] [--keep-nix]
|
||||||
Stop Home Manager's user services, run 'cleanup --all', uninstall Home
|
Stop Home Manager's user services, run 'cleanup --all', uninstall Home
|
||||||
Manager, uninstall Nix (unless --keep-nix) and remove per-user Nix
|
Manager, uninstall Nix (unless --keep-nix) and remove per-user Nix
|
||||||
leftovers. Before removing Nix it lists the programs started from the
|
leftovers. /nix is detached lazily: programs started from the Nix
|
||||||
Nix store that still use /nix and waits until they are closed. Your
|
store (listed) keep running until you log out or reboot. Your
|
||||||
configuration directory (also a --clone) is never deleted.
|
configuration directory (also a --clone) is never deleted.
|
||||||
|
|
||||||
cleanup [--dry-run] [--quiet] (--all | --orphans [--keep <artifact>]...)
|
cleanup [--dry-run] [--quiet] (--all | --orphans [--keep <artifact>]...)
|
||||||
@@ -233,20 +235,45 @@ install_nix() {
|
|||||||
ro_relock
|
ro_relock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nix_mounted() { mountpoint -q /nix 2>/dev/null; }
|
||||||
|
|
||||||
uninstall_nix() {
|
uninstall_nix() {
|
||||||
local rc=0 bin=
|
local rc=0 bin='' lazy=0 users=()
|
||||||
need_sudo
|
need_sudo
|
||||||
ro_unlock
|
ro_unlock
|
||||||
step "Uninstalling Nix"
|
step "Uninstalling Nix"
|
||||||
# From a root-owned copy: the uninstaller running from /nix would keep
|
mapfile -t users < <(nix_users)
|
||||||
# /nix busy.
|
if (( ${#users[@]} )); then
|
||||||
|
info "Started from the Nix store, these keep running until you log out or reboot:"
|
||||||
|
printf ' - %s\n' "${users[@]}"
|
||||||
|
fi
|
||||||
|
# From a root-owned copy outside /nix.
|
||||||
if ! bin="$(sudo mktemp)" || ! sudo cp "$NIX_INSTALLER_BIN" "$bin" \
|
if ! bin="$(sudo mktemp)" || ! sudo cp "$NIX_INSTALLER_BIN" "$bin" \
|
||||||
|| ! sudo chmod 700 "$bin" || ! sudo "$bin" --version >/dev/null 2>&1; then
|
|| ! sudo chmod 700 "$bin" || ! sudo "$bin" --version >/dev/null 2>&1; then
|
||||||
[[ -n $bin ]] && sudo rm -f "$bin"
|
[[ -n $bin ]] && sudo rm -f "$bin"
|
||||||
bin=$NIX_INSTALLER_BIN
|
bin=$NIX_INSTALLER_BIN
|
||||||
fi
|
fi
|
||||||
|
# Programs started from the Nix store keep /nix busy, so the uninstaller's
|
||||||
|
# `systemctl stop nix.mount` would fail. With LazyUnmount= that stop
|
||||||
|
# detaches /nix; the programs keep their open files until they exit.
|
||||||
|
if nix_mounted; then
|
||||||
|
if sudo mkdir -p "${NIX_MOUNT_DROPIN%/*}" \
|
||||||
|
&& printf '%s\n' '# steam-frame-nix: removing Nix detaches /nix while programs use it.' \
|
||||||
|
'[Mount]' 'LazyUnmount=yes' | sudo tee "$NIX_MOUNT_DROPIN" >/dev/null \
|
||||||
|
&& sudo systemctl daemon-reload; then
|
||||||
|
lazy=1
|
||||||
|
else
|
||||||
|
warn "could not add $NIX_MOUNT_DROPIN; removing Nix fails if a program still uses /nix"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
sudo "$bin" uninstall --no-confirm || rc=$?
|
sudo "$bin" uninstall --no-confirm || rc=$?
|
||||||
[[ $bin == "$NIX_INSTALLER_BIN" ]] || sudo rm -f "$bin"
|
[[ $bin == "$NIX_INSTALLER_BIN" ]] || sudo rm -f "$bin"
|
||||||
|
if (( lazy )); then
|
||||||
|
sudo rm -f "$NIX_MOUNT_DROPIN"
|
||||||
|
sudo rmdir "${NIX_MOUNT_DROPIN%/*}" 2>/dev/null || true
|
||||||
|
sudo systemctl daemon-reload || true
|
||||||
|
fi
|
||||||
|
hash -r # commands found in /nix are gone
|
||||||
ro_relock
|
ro_relock
|
||||||
return "$rc"
|
return "$rc"
|
||||||
}
|
}
|
||||||
@@ -1147,13 +1174,12 @@ remove_hm() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- /nix in use ---
|
# --- programs from the Nix store ---
|
||||||
#
|
#
|
||||||
# nix-installer can't unmount /nix while a process uses it: its program, a
|
# Listed before Nix goes, for information (uninstall_nix detaches /nix
|
||||||
# library, an open file or its working directory in /nix. Only this user's
|
# lazily): their program, a library, an open file or their working
|
||||||
# processes are readable (the uninstaller stops the Nix daemon itself). This
|
# directory is in /nix. Only this user's processes are readable. This
|
||||||
# script, its subshells and its curl | bash pipeline are skipped; the
|
# script, its subshells and its curl | bash pipeline are skipped.
|
||||||
# shells and apps it was started from are not.
|
|
||||||
|
|
||||||
proc_ppid() { # pid
|
proc_ppid() { # pid
|
||||||
local k v
|
local k v
|
||||||
@@ -1171,9 +1197,7 @@ proc_pgid() { # pid
|
|||||||
|
|
||||||
# "<name> (PID <pid>)" per process using /nix.
|
# "<name> (PID <pid>)" per process using /nix.
|
||||||
nix_users() {
|
nix_users() {
|
||||||
local p pid name argv0 self=$$ pgid ancestors=" "
|
local p pid name argv0 self=$$ pgid
|
||||||
p=$self
|
|
||||||
while [[ $p =~ ^[0-9]+$ ]] && (( p > 1 )); do ancestors+="$p "; p="$(proc_ppid "$p")"; done
|
|
||||||
pgid="$(proc_pgid "$self")"
|
pgid="$(proc_pgid "$self")"
|
||||||
{ find "$SFN_PROC"/[0-9]*/{exe,cwd,root} "$SFN_PROC"/[0-9]*/fd -maxdepth 1 -lname '/nix/*' 2>/dev/null || true
|
{ find "$SFN_PROC"/[0-9]*/{exe,cwd,root} "$SFN_PROC"/[0-9]*/fd -maxdepth 1 -lname '/nix/*' 2>/dev/null || true
|
||||||
grep -ls '[[:space:]]/nix/' "$SFN_PROC"/[0-9]*/maps || true
|
grep -ls '[[:space:]]/nix/' "$SFN_PROC"/[0-9]*/maps || true
|
||||||
@@ -1184,10 +1208,6 @@ nix_users() {
|
|||||||
# argv[0]'s name says more than comm (often a thread name)
|
# argv[0]'s name says more than comm (often a thread name)
|
||||||
argv0=''; { IFS= read -r -d '' argv0 <"$SFN_PROC/$pid/cmdline"; } 2>/dev/null || true
|
argv0=''; { IFS= read -r -d '' argv0 <"$SFN_PROC/$pid/cmdline"; } 2>/dev/null || true
|
||||||
[[ ${argv0##*/} == '' || ${argv0##*/} == exe ]] || name=${argv0##*/}
|
[[ ${argv0##*/} == '' || ${argv0##*/} == exe ]] || name=${argv0##*/}
|
||||||
if [[ $ancestors == *" $pid "* ]]; then
|
|
||||||
echo "$name (PID $pid, started this uninstall: close it and run uninstall from another terminal)"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
[[ -n $pgid && $(proc_pgid "$pid") == "$pgid" ]] && continue # our pipeline
|
[[ -n $pgid && $(proc_pgid "$pid") == "$pgid" ]] && continue # our pipeline
|
||||||
p=$pid
|
p=$pid
|
||||||
while [[ $p =~ ^[0-9]+$ && $p != "$self" ]] && (( p > 1 )); do p="$(proc_ppid "$p")"; done
|
while [[ $p =~ ^[0-9]+$ && $p != "$self" ]] && (( p > 1 )); do p="$(proc_ppid "$p")"; done
|
||||||
@@ -1196,22 +1216,6 @@ nix_users() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Waits until no process uses /nix; fails if the user gives up (or can't be
|
|
||||||
# asked: --yes, no terminal). Nothing is stopped automatically.
|
|
||||||
nix_idle() {
|
|
||||||
local users=() reply
|
|
||||||
while :; do
|
|
||||||
mapfile -t users < <(nix_users)
|
|
||||||
(( ${#users[@]} )) || return 0
|
|
||||||
warn "programs started from the Nix store still use /nix, so Nix can't be removed:"
|
|
||||||
printf ' - %s\n' "${users[@]}" >&2
|
|
||||||
(( ! ASSUME_YES )) && have_tty || return 1
|
|
||||||
printf 'Close them, then press Enter to check again (a: abort) ' >/dev/tty
|
|
||||||
read -r reply </dev/tty || return 1
|
|
||||||
[[ $reply != [aA]* ]] || return 1
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_path() {
|
remove_path() {
|
||||||
if [[ -e $1 || -L $1 ]]; then rm -rf -- "$1"; info "removed $1"; fi
|
if [[ -e $1 || -L $1 ]]; then rm -rf -- "$1"; info "removed $1"; fi
|
||||||
}
|
}
|
||||||
@@ -1245,7 +1249,7 @@ remove_leftovers() { # keep_nix
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd_uninstall() {
|
cmd_uninstall() {
|
||||||
local keep_nix=0 orig_args=("$@")
|
local keep_nix=0
|
||||||
while (( $# )); do
|
while (( $# )); do
|
||||||
case $1 in
|
case $1 in
|
||||||
-y|--yes) ASSUME_YES=1; shift ;;
|
-y|--yes) ASSUME_YES=1; shift ;;
|
||||||
@@ -1256,14 +1260,6 @@ cmd_uninstall() {
|
|||||||
done
|
done
|
||||||
need_not_root
|
need_not_root
|
||||||
|
|
||||||
# A bash from Nix (e.g. via /usr/bin/env) would keep /nix busy.
|
|
||||||
if (( ! keep_nix )) && [[ $(readlink "$SFN_PROC/$$/exe") == /nix/store/* ]]; then
|
|
||||||
if [[ -f ${BASH_SOURCE[0]:-} && -x /usr/bin/bash ]]; then
|
|
||||||
exec /usr/bin/bash "${BASH_SOURCE[0]}" uninstall "${orig_args[@]}"
|
|
||||||
fi
|
|
||||||
die "this shell's bash is from Nix; run: curl -fsSL https://steam-frame-nix.lhns.de | /usr/bin/bash -s -- uninstall"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( keep_nix )); then
|
if (( keep_nix )); then
|
||||||
step "This uninstalls Home Manager (its files and services); Nix stays"
|
step "This uninstalls Home Manager (its files and services); Nix stays"
|
||||||
else
|
else
|
||||||
@@ -1275,18 +1271,17 @@ cmd_uninstall() {
|
|||||||
stop_hm_services
|
stop_hm_services
|
||||||
cmd_cleanup --all
|
cmd_cleanup --all
|
||||||
remove_hm
|
remove_hm
|
||||||
|
hash -r # commands found in Home Manager's profile are gone
|
||||||
|
|
||||||
# Nix still in use or a failed Nix uninstall must not skip the rest: Home
|
# A failed Nix uninstall must not skip the rest: Home Manager is gone by
|
||||||
# Manager is gone by now, so its config link goes regardless; the per-user
|
# now, so its config link goes regardless; the per-user Nix files stay
|
||||||
# Nix files stay for the Nix that is still there.
|
# for the Nix that is still there.
|
||||||
local nix_failed=''
|
local nix_failed=0
|
||||||
if (( ! keep_nix )); then
|
if (( ! keep_nix )); then
|
||||||
if [[ -x $NIX_INSTALLER_BIN ]]; then
|
if [[ -x $NIX_INSTALLER_BIN ]]; then
|
||||||
if ! nix_idle; then
|
if ! uninstall_nix; then
|
||||||
nix_failed=busy keep_nix=1
|
|
||||||
elif ! uninstall_nix; then
|
|
||||||
warn "the Nix uninstall failed (see above)"
|
warn "the Nix uninstall failed (see above)"
|
||||||
nix_failed=failed keep_nix=1
|
nix_failed=1 keep_nix=1
|
||||||
fi
|
fi
|
||||||
elif command -v nix >/dev/null 2>&1; then
|
elif command -v nix >/dev/null 2>&1; then
|
||||||
warn "Nix wasn't installed by nix-installer ($NIX_INSTALLER_BIN missing); not removing it"
|
warn "Nix wasn't installed by nix-installer ($NIX_INSTALLER_BIN missing); not removing it"
|
||||||
@@ -1305,19 +1300,17 @@ Intentionally left in place:
|
|||||||
- your configuration (e.g. $DEFAULT_CONFIG_DIR, also when cloned)
|
- your configuration (e.g. $DEFAULT_CONFIG_DIR, also when cloned)
|
||||||
- files Home Manager renamed to *.hm-backup-<time>
|
- files Home Manager renamed to *.hm-backup-<time>
|
||||||
- app data, e.g. ~/.local/share/docker, Firefox profiles, 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: running sessions drop the removed tweaks, and programs
|
||||||
|
that were started from Nix restart without it.
|
||||||
EOF
|
EOF
|
||||||
if (( ${#CLEAN_DEFERRED[@]} )) || [[ -n $nix_failed ]]; then
|
if (( ${#CLEAN_DEFERRED[@]} || nix_failed )); then
|
||||||
printf '\nNot done yet:\n'
|
printf '\nNot done yet:\n'
|
||||||
(( ${#CLEAN_DEFERRED[@]} )) && printf ' - %s\n' "${CLEAN_DEFERRED[@]}"
|
(( ${#CLEAN_DEFERRED[@]} )) && printf ' - %s\n' "${CLEAN_DEFERRED[@]}"
|
||||||
case $nix_failed in
|
(( nix_failed )) && printf '%s\n' \
|
||||||
busy) printf '%s\n' " - Nix: still in use by the programs listed above. Close these or reboot," ;;
|
" - Nix: its uninstaller failed. Reboot, then run uninstall again;" \
|
||||||
failed) printf '%s\n' " - Nix: its uninstaller failed. Reboot (so nothing uses /nix)," ;;
|
" it also removes ~/.nix-profile and ~/.local/state/nix."
|
||||||
esac
|
|
||||||
[[ -n $nix_failed ]] && printf '%s\n' \
|
|
||||||
" then run uninstall again; it also removes ~/.nix-profile and ~/.local/state/nix."
|
|
||||||
fi
|
fi
|
||||||
[[ -z $nix_failed ]]
|
(( ! nix_failed ))
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- status -----------------------------------------------------------------
|
# --- status -----------------------------------------------------------------
|
||||||
|
|||||||
+37
-18
@@ -5,8 +5,8 @@
|
|||||||
# Also `install.sh restart-check` (what waits for a session/SteamVR restart).
|
# Also `install.sh restart-check` (what waits for a session/SteamVR restart).
|
||||||
# Also `install.sh install --clone` (argument parsing and the clone step),
|
# Also `install.sh install --clone` (argument parsing and the clone step),
|
||||||
# against local bare repositories through a logging git, without network.
|
# against local bare repositories through a logging git, without network.
|
||||||
# Also `install.sh uninstall` stopping before Nix while programs from the
|
# Also `install.sh uninstall` removing Nix while programs from the Nix store
|
||||||
# Nix store run (a fake /proc).
|
# run (a fake /proc, a logging nix-installer).
|
||||||
{ pkgs }:
|
{ pkgs }:
|
||||||
let
|
let
|
||||||
cleanup = pkgs.callPackage ./package.nix { };
|
cleanup = pkgs.callPackage ./package.nix { };
|
||||||
@@ -388,7 +388,7 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq pkgs.git
|
|||||||
has "$res" "gh auth login"
|
has "$res" "gh auth login"
|
||||||
echo "G ok"
|
echo "G ok"
|
||||||
|
|
||||||
# --- H: uninstall: /nix in use ---
|
# --- H: uninstall: Nix goes while programs from the store still run ---
|
||||||
fresh h
|
fresh h
|
||||||
export -f fail has hasnt gone
|
export -f fail has hasnt gone
|
||||||
export root INSTALL
|
export root INSTALL
|
||||||
@@ -413,23 +413,42 @@ pkgs.runCommand "cleanup-check" { nativeBuildInputs = [ cleanup pkgs.jq pkgs.git
|
|||||||
fake 41 cwd 1 41 /usr/bin/cwd; ln -sfn /nix/store/e $P/41/cwd
|
fake 41 cwd 1 41 /usr/bin/cwd; ln -sfn /nix/store/e $P/41/cwd
|
||||||
fake 42 fd 1 42 /usr/bin/fd; ln -s /nix/store/f/file $P/42/fd/3
|
fake 42 fd 1 42 /usr/bin/fd; ln -s /nix/store/f/file $P/42/fd/3
|
||||||
fake 43 clean 1 43 /usr/bin/clean
|
fake 43 clean 1 43 /usr/bin/clean
|
||||||
printf '#!/bin/sh\n' > $root/nix-installer; chmod +x $root/nix-installer
|
# nix-installer: logs its arguments and the lazy-unmount drop-in it sees
|
||||||
mkdir -p $HOME/.config; ln -s $HOME/cfg $HOME/.config/home-manager
|
D=$root/run-system/nix.mount.d/50-steam-frame-nix-lazy-unmount.conf
|
||||||
uninst() {
|
export D LOG=$root/log RC=0
|
||||||
(STEAM_FRAME_NIX_PROC=$P; . $INSTALL; ASSUME_YES=1 NIX_INSTALLER_BIN=$root/nix-installer
|
printf '#!%s\n[ "$1" = --version ] && exit 0\necho "nix-installer $*" >> $LOG\ncat $D >> $LOG 2>/dev/null || echo "no drop-in" >> $LOG\nexit $RC\n' \
|
||||||
uninstall_nix() { echo "nix-installer uninstall"; }; cmd_uninstall) 2>&1
|
"$(command -v bash)" > $root/nix-installer
|
||||||
|
chmod +x $root/nix-installer
|
||||||
|
uninst() { # mounted: 1|0
|
||||||
|
local m=$1
|
||||||
|
: > $LOG; rm -rf $HOME/.config; mkdir -p $HOME/.config; ln -s $HOME/cfg $HOME/.config/home-manager
|
||||||
|
(STEAM_FRAME_NIX_PROC=$P STEAM_FRAME_NIX_SYSTEM_RUNTIME=$root/run-system; . $INSTALL
|
||||||
|
ASSUME_YES=1 NIX_INSTALLER_BIN=$root/nix-installer
|
||||||
|
# sudo without root: systemctl only logged
|
||||||
|
sudo() {
|
||||||
|
case $1 in -v) return 0 ;; -n) shift ;; esac
|
||||||
|
echo "sudo $*" >> $LOG
|
||||||
|
[ "$1" = systemctl ] || "$@"
|
||||||
}
|
}
|
||||||
res=$(uninst) && fail "uninstall went on with /nix in use: $res"
|
nix_mounted() { [ "$m" = 1 ]; }
|
||||||
|
cmd_uninstall </dev/null) 2>&1
|
||||||
|
}
|
||||||
|
res=$(uninst 1) || fail "uninstall failed: $res"
|
||||||
echo "$res"
|
echo "$res"
|
||||||
hasnt "$res" "nix-installer uninstall"
|
for p in "zsh (PID 30)" "app (PID 40)" "cwd (PID 41)" "fd (PID 42)" "keep running until you log out or reboot"; do has "$res" "$p"; done
|
||||||
has "$res" "zsh (PID 30, started this uninstall"
|
for p in "PID 31" "PID 32" "PID 43" "PID $$" "Not done yet"; do hasnt "$res" "$p"; done
|
||||||
for p in "app (PID 40)" "cwd (PID 41)" "fd (PID 42)" "Close these or reboot,"; do has "$res" "$p"; done
|
log=$(cat $LOG)
|
||||||
for p in "PID 31" "PID 32" "PID 43" "PID $$"; do hasnt "$res" "$p"; done
|
has "$log" "nix-installer uninstall --no-confirm"
|
||||||
gone $HOME/.config/home-manager
|
has "$log" "LazyUnmount=yes" # the drop-in was there while it ran
|
||||||
# closed: Nix is removed
|
[ "$(grep -c '^sudo systemctl daemon-reload$' $LOG)" = 2 ] || fail "daemon-reloads: $log"
|
||||||
rm -r $P/30 $P/40 $P/41 $P/42; fake $$ bash 1 $$ /usr/bin/bash
|
gone $D ''${D%/*} $HOME/.config/home-manager
|
||||||
res=$(uninst) || fail "uninstall failed: $res"
|
# /nix not a mount point: no drop-in
|
||||||
has "$res" "nix-installer uninstall"
|
res=$(uninst 0) || fail "uninstall failed: $res"
|
||||||
|
has "$(cat $LOG)" "no drop-in"
|
||||||
|
# the uninstaller fails: Nix stays, the drop-in goes
|
||||||
|
res=$(RC=1 uninst 1) && fail "uninstall succeeded: $res"
|
||||||
|
has "$res" "its uninstaller failed. Reboot, then run uninstall again"
|
||||||
|
gone $D $HOME/.config/home-manager
|
||||||
SH
|
SH
|
||||||
echo "H ok"
|
echo "H ok"
|
||||||
touch $out
|
touch $out
|
||||||
|
|||||||
Reference in new issue
Block a user