diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 701a04c8b..8d92789a6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/docs/release-notes/rl-2405.md b/docs/release-notes/rl-2405.md index 6cf2c9574..d653e6040 100644 --- a/docs/release-notes/rl-2405.md +++ b/docs/release-notes/rl-2405.md @@ -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 diff --git a/home-manager/home-manager b/home-manager/home-manager index 88b3b142d..34407ed27 100644 --- a/home-manager/home-manager +++ b/home-manager/home-manager @@ -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" <` 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 '' ); diff --git a/modules/lib-bash/activation-init.sh b/modules/lib-bash/activation-init.sh index 881b63a8f..c691bf7e2 100755 --- a/modules/lib-bash/activation-init.sh +++ b/modules/lib-bash/activation-init.sh @@ -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/` 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" diff --git a/modules/misc/uninstall.nix b/modules/misc/uninstall.nix new file mode 100644 index 000000000..eeb0fa4f0 --- /dev/null +++ b/modules/misc/uninstall.nix @@ -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 + ''; + }; +} diff --git a/modules/modules.nix b/modules/modules.nix index 16b7837bf..314dfdb8d 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -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