home-manager: internalize uninstall

This adds a Boolean option `uninstall`. When enabled this option will
reset side-effecting configurations to their "empty" state. The intent
is that this will cause the activation script to remove all managed
files and packages.

Doing it this way should hopefully be more robust than the previous
solution. It also allows a somewhat more convenient uninstall process
when using Flakes; put `uninstall = true` in your existing
configuration and then do a switch.

Also add simple uninstall test in CI test job.
This commit is contained in:
Robert Helgesson 2024-01-06 00:22:27 +01:00
parent 93e804e7f8
commit 7403ed4980
No known key found for this signature in database
GPG key ID: 96E745BD17AA17ED
7 changed files with 118 additions and 77 deletions

View file

@ -27,4 +27,5 @@ jobs:
- run: nix-build --show-trace -A docs.jsonModuleMaintainers
- run: ./format -c
- run: nix-shell --show-trace . -A install
- run: yes | home-manager -I home-manager=. uninstall
- run: nix-shell --show-trace --arg enableBig false --pure tests -A run.all

View file

@ -10,6 +10,27 @@ This release has the following notable changes:
- The `.release` file in the Home Manager project root has been
removed. Please use the `release.json` file instead.
- The {command}`home-manager uninstall` command has been reworked to,
hopefully, be more robust. The new implementation makes use of a new
Boolean configuration option [uninstall](#opt-uninstall) that can
also be used in a pure Nix Flake setup.
Specifically, if you are using a Flake only installation, then you
can clean up a Home Manager installation by adding
``` nix
uninstall = true;
```
to your existing configuration and then build and activate. This
will override any other configuration and cause, for example, the
removal of all managed files.
Please be very careful when enabling this option since activating
the built configuration will not only remove the managed files but
_all_ Home Manager state from your user environment. This includes
removing all your historic Home Manager generations!
## State Version Changes {#sec-release-24.05-state-version-changes}
The state version in this release includes the changes below. These

View file

@ -11,32 +11,13 @@ export TEXTDOMAINDIR=@OUT@/share/locale
# shellcheck disable=1091
source @HOME_MANAGER_LIB@
function nixProfileList() {
# We attempt to use `--json` first (added in Nix 2.17). Otherwise attempt to
# parse the legacy output format.
{
nix profile list --json 2>/dev/null \
| jq -r --arg name "$1" '.elements[].storePaths[] | select(endswith($name))'
} || {
nix profile list \
| { grep "$1\$" || test $? = 1; } \
| cut -d ' ' -f 4
}
}
function removeByName() {
nixProfileList "$1" | xargs -t $DRY_RUN_CMD nix profile remove $VERBOSE_ARG
}
function setNixProfileCommands() {
if [[ -e $HOME/.nix-profile/manifest.json \
|| -e ${XDG_STATE_HOME:-$HOME/.local/state}/nix/profile/manifest.json ]] ; then
LIST_OUTPATH_CMD="nix profile list"
REMOVE_CMD="removeByName"
else
LIST_OUTPATH_CMD="nix-env -q --out-path"
REMOVE_CMD="nix-env --uninstall"
fi
}
@ -846,30 +827,17 @@ function doUninstall() {
y|Y)
_i "Switching to empty Home Manager configuration..."
HOME_MANAGER_CONFIG="$(mktemp --tmpdir home-manager.XXXXXXXXXX)"
echo "{ lib, ... }: {" > "$HOME_MANAGER_CONFIG"
echo " home.file = lib.mkForce {};" >> "$HOME_MANAGER_CONFIG"
echo " home.stateVersion = \"18.09\";" >> "$HOME_MANAGER_CONFIG"
echo " manual.manpages.enable = false;" >> "$HOME_MANAGER_CONFIG"
echo "}" >> "$HOME_MANAGER_CONFIG"
doSwitch
$DRY_RUN_CMD $REMOVE_CMD home-manager-path || true
rm "$HOME_MANAGER_CONFIG"
if [[ -e $HM_DATA_HOME ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG -r "$HM_DATA_HOME"
fi
if [[ -e $HM_STATE_DIR ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG -r "$HM_STATE_DIR"
fi
if [[ -e $HM_PROFILE_DIR ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG "$HM_PROFILE_DIR/home-manager"*
fi
if [[ -e $HM_GCROOT_LEGACY_PATH ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG "$HM_GCROOT_LEGACY_PATH"
fi
cat > "$HOME_MANAGER_CONFIG" <<EOF
{
uninstall = true;
home.username = "$USER";
home.homeDirectory = "$HOME";
home.stateVersion = "23.11";
}
EOF
# shellcheck disable=2064
trap "rm '$HOME_MANAGER_CONFIG'" EXIT
doSwitch --switch
;;
*)
_i "Yay!"

View file

@ -585,44 +585,14 @@ in
if config.submoduleSupport.externalPackageInstall
then
''
# We don't use `cfg.profileDirectory` here because it defaults to
# `/etc/profiles/per-user/<user>` which is constructed by NixOS or
# nix-darwin and won't require uninstalling `home-manager-path`.
if [[ -e $HOME/.nix-profile/manifest.json \
|| -e "''${XDG_STATE_HOME:-$HOME/.local/state}/nix/profile/manifest.json" ]] ; then
nix profile list \
| { grep 'home-manager-path$' || test $? = 1; } \
| cut -d ' ' -f 4 \
| xargs -rt $DRY_RUN_CMD nix profile remove $VERBOSE_ARG
else
if nix-env -q | grep '^home-manager-path$'; then
$DRY_RUN_CMD nix-env -e home-manager-path
fi
fi
nixProfileRemove home-manager-path
''
else
''
function nixProfileList() {
# We attempt to use `--json` first (added in Nix 2.17). Otherwise attempt to
# parse the legacy output format.
{
nix profile list --json 2>/dev/null \
| jq -r --arg name "$1" '.elements[].storePaths[] | select(endswith($name))'
} || {
nix profile list \
| { grep "$1\$" || test $? = 1; } \
| cut -d ' ' -f 4
}
}
function nixRemoveProfileByName() {
nixProfileList "$1" | xargs -t $DRY_RUN_CMD nix profile remove $VERBOSE_ARG
}
function nixReplaceProfile() {
local oldNix="$(command -v nix)"
nixRemoveProfileByName 'home-manager-path'
nixProfileRemove 'home-manager-path'
$DRY_RUN_CMD $oldNix profile install $1
}
@ -644,7 +614,7 @@ in
_iError $'Oops, Nix failed to install your new Home Manager profile!\n\nPerhaps there is a conflict with a package that was installed using\n"%s"? Try running\n\n %s\n\nand if there is a conflicting package you can remove it with\n\n %s\n\nThen try activating your Home Manager configuration again.' "$INSTALL_CMD" "$LIST_CMD" "$REMOVE_CMD_SYNTAX"
exit 1
fi
unset -f nixProfileList nixRemoveProfileByName nixReplaceProfile
unset -f nixReplaceProfile
unset INSTALL_CMD INSTALL_CMD_ACTUAL LIST_CMD REMOVE_CMD_SYNTAX
''
);

View file

@ -34,7 +34,8 @@ function migrateProfile() {
function setupVars() {
declare -r stateHome="${XDG_STATE_HOME:-$HOME/.local/state}"
declare -r userNixStateDir="$stateHome/nix"
declare -r hmGcrootsDir="$stateHome/home-manager/gcroots"
declare -gr hmStatePath="$stateHome/home-manager"
declare -r hmGcrootsDir="$hmStatePath/gcroots"
declare -r globalNixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
declare -r globalProfilesDir="$globalNixStateDir/profiles/per-user/$USER"
@ -55,6 +56,7 @@ function setupVars() {
exit 1
fi
declare -gr hmDataPath="${XDG_DATA_HOME:-$HOME/.local/share}/home-manager"
declare -gr genProfilePath="$profilesDir/home-manager"
declare -gr newGenPath="@GENERATION_DIR@";
declare -gr newGenGcPath="$hmGcrootsDir/current-home"
@ -88,6 +90,34 @@ function setupVars() {
fi
}
# Helper used to list content of a `nix profile` profile.
function nixProfileList() {
# We attempt to use `--json` first (added in Nix 2.17). Otherwise attempt to
# parse the legacy output format.
{
nix profile list --json 2>/dev/null \
| jq -r --arg name "$1" '.elements[].storePaths[] | select(endswith($name))'
} || {
nix profile list \
| { grep "$1\$" || test $? = 1; } \
| cut -d ' ' -f 4
}
}
# Helper used to remove a package from a Nix profile. Supports both `nix-env`
# and `nix profile`.
function nixProfileRemove() {
# We don't use `cfg.profileDirectory` here because it defaults to
# `/etc/profiles/per-user/<user>` which is constructed by NixOS or
# nix-darwin and won't require uninstalling `home-manager-path`.
if [[ -e $HOME/.nix-profile/manifest.json \
|| -e ${XDG_STATE_HOME:-$HOME/.local/state}/nix/profile/manifest.json ]] ; then
nixProfileList "$1" | xargs -t $DRY_RUN_CMD nix profile remove $VERBOSE_ARG
else
$DRY_RUN_CMD nix-env -e "$1" > $DRY_RUN_NULL 2>&1
fi
}
function checkUsername() {
local expectedUser="$1"

View file

@ -0,0 +1,50 @@
{ config, lib, pkgs, ... }:
let
inherit (lib) mkIf mkOption types;
in {
options.uninstall = mkOption {
type = types.bool;
default = false;
description = ''
Whether to set up a minimal configuration that will remove all managed
files and packages.
Use this with extreme care since running the generated activation script
will remove all Home Manager state from your user environment. This
includes removing all your historic Home Manager generations.
'';
};
config = mkIf config.uninstall {
home.packages = lib.mkForce [ ];
home.file = lib.mkForce { };
home.stateVersion = lib.mkForce "23.11";
home.enableNixpkgsReleaseCheck = lib.mkForce false;
manual.manpages.enable = lib.mkForce false;
news.display = lib.mkForce "silent";
home.activation.uninstall =
lib.hm.dag.entryAfter [ "installPackages" "linkGeneration" ] ''
nixProfileRemove home-manager-path
if [[ -e $hmDataPath ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG -r "$hmDataPath"
fi
if [[ -e $hmStatePath ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG -r "$hmStatePath"
fi
if [[ -e $genProfilePath ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG "$genProfilePath"*
fi
if [[ -e $legacyGenGcPath ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG "$legacyGenGcPath"
fi
'';
};
}

View file

@ -37,6 +37,7 @@ let
./misc/specialisation.nix
./misc/submodule-support.nix
./misc/tmpfiles.nix
./misc/uninstall.nix
./misc/version.nix
./misc/vte.nix
./misc/xdg-desktop-entries.nix