From 93f5cb2482dd20e57eb8ca6c819cdad9738f98a0 Mon Sep 17 00:00:00 2001
From: Robert Helgesson <robert@rycee.net>
Date: Fri, 7 Apr 2023 14:01:45 +0200
Subject: [PATCH] home-manager: migrate profiles to nix state directory

If the user runs a recent Nix version that places per-user profiles in
`$XDG_STATE_DIR/nix/profiles`, then migrate the home-manager profile
there.

Also clean up `setupVars` a bit.
---
 home-manager/home-manager           | 27 ++++++----
 home-manager/po/home-manager.pot    | 68 +++++++++++++-----------
 modules/lib-bash/activation-init.sh | 80 ++++++++++++++++-------------
 modules/po/hm-modules.pot           | 26 ++++++----
 4 files changed, 113 insertions(+), 88 deletions(-)

diff --git a/home-manager/home-manager b/home-manager/home-manager
index b6ca244b0..70cfe11ca 100644
--- a/home-manager/home-manager
+++ b/home-manager/home-manager
@@ -117,18 +117,25 @@ function setHomeManagerNixPath() {
 
 # Sets some useful Home Manager related paths as global read-only variables.
 function setHomeManagerPathVariables() {
-    declare -r nixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
-    declare -r globalProfilesDir="$nixStateDir/profiles/per-user/$USER"
-    declare -r globalGcrootsDir="$nixStateDir/gcroots/per-user/$USER"
+    declare -r globalNixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
+    declare -r globalProfilesDir="$globalNixStateDir/profiles/per-user/$USER"
+    declare -r globalGcrootsDir="$globalNixStateDir/gcroots/per-user/$USER"
+
+    declare -r stateHome="${XDG_STATE_HOME:-$HOME/.local/state}"
+    declare -r userNixStateDir="$stateHome/nix"
 
     declare -gr HM_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/home-manager"
-    declare -gr HM_STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/home-manager"
+    declare -gr HM_STATE_DIR="$stateHome/home-manager"
     declare -gr HM_GCROOT_LEGACY_PATH="$globalGcrootsDir/current-home"
 
-    if [[ -d "$globalProfilesDir" ]]; then
+    if [[ -d $userNixStateDir/profiles ]]; then
+        declare -gr HM_PROFILE_DIR="$userNixStateDir/profiles"
+    elif [[ -d $globalProfilesDir ]]; then
         declare -gr HM_PROFILE_DIR="$globalProfilesDir"
     else
-        declare -gr HM_PROFILE_DIR="$HM_STATE_DIR/profiles"
+        _iError 'Could not find suitable profile directory, tried %s and %s' \
+                "$HM_STATE_DIR/profiles" "$globalProfilesDir" >&2
+        exit 1
     fi
 }
 
@@ -769,6 +776,10 @@ function doUninstall() {
                 $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
@@ -776,10 +787,6 @@ function doUninstall() {
             if [[ -e $HM_GCROOT_LEGACY_PATH ]]; then
                 $DRY_RUN_CMD rm $VERBOSE_ARG "$HM_GCROOT_LEGACY_PATH"
             fi
-
-            if [[ -e $HM_STATE_DIR ]]; then
-                $DRY_RUN_CMD rm $VERBOSE_ARG -r "$HM_STATE_DIR"
-            fi
             ;;
         *)
             _i "Yay!"
diff --git a/home-manager/po/home-manager.pot b/home-manager/po/home-manager.pot
index d202eec9b..4476cdf58 100644
--- a/home-manager/po/home-manager.pot
+++ b/home-manager/po/home-manager.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Home Manager\n"
 "Report-Msgid-Bugs-To: https://github.com/nix-community/home-manager/issues\n"
-"POT-Creation-Date: 2023-03-15 20:11+0100\n"
+"POT-Creation-Date: 2023-04-10 13:49+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -25,7 +25,7 @@ msgstr ""
 #. translators: The first '%s' specifier will be replaced by either
 #. 'home.nix' or 'flake.nix'.
 #: home-manager/home-manager:88 home-manager/home-manager:92
-#: home-manager/home-manager:147
+#: home-manager/home-manager:154
 msgid ""
 "Keeping your Home Manager %s in %s is deprecated,\n"
 "please move it to %s"
@@ -35,33 +35,37 @@ msgstr ""
 msgid "No configuration file found. Please create one at %s"
 msgstr ""
 
-#: home-manager/home-manager:183
+#: home-manager/home-manager:136
+msgid "Could not find suitable profile directory, tried %s and %s"
+msgstr ""
+
+#: home-manager/home-manager:190
 msgid "Can't inspect options of a flake configuration"
 msgstr ""
 
-#: home-manager/home-manager:245 home-manager/home-manager:268
-#: home-manager/home-manager:907
+#: home-manager/home-manager:252 home-manager/home-manager:275
+#: home-manager/home-manager:970
 msgid "%s: unknown option '%s'"
 msgstr ""
 
-#: home-manager/home-manager:250 home-manager/home-manager:908
+#: home-manager/home-manager:257 home-manager/home-manager:971
 msgid "Run '%s --help' for usage help"
 msgstr ""
 
-#: home-manager/home-manager:276 home-manager/home-manager:326
+#: home-manager/home-manager:283 home-manager/home-manager:382
 msgid "The file %s already exists, leaving it unchanged..."
 msgstr ""
 
-#: home-manager/home-manager:278 home-manager/home-manager:328
+#: home-manager/home-manager:285 home-manager/home-manager:384
 msgid "Creating %s..."
 msgstr ""
 
-#: home-manager/home-manager:370
+#: home-manager/home-manager:426
 msgid "Creating initial Home Manager generation..."
 msgstr ""
 
 #. translators: The "%s" specifier will be replaced by a file path.
-#: home-manager/home-manager:375
+#: home-manager/home-manager:431
 msgid ""
 "All done! The home-manager tool should now be installed and you can edit\n"
 "\n"
@@ -72,7 +76,7 @@ msgid ""
 msgstr ""
 
 #. translators: The "%s" specifier will be replaced by a URL.
-#: home-manager/home-manager:380
+#: home-manager/home-manager:436
 msgid ""
 "Uh oh, the installation failed! Please create an issue at\n"
 "\n"
@@ -81,11 +85,11 @@ msgid ""
 "if the error seems to be the fault of Home Manager."
 msgstr ""
 
-#: home-manager/home-manager:390
+#: home-manager/home-manager:446
 msgid "Can't instantiate a flake configuration"
 msgstr ""
 
-#: home-manager/home-manager:463
+#: home-manager/home-manager:519
 msgid ""
 "There is %d unread and relevant news item.\n"
 "Read it by running the command \"%s news\"."
@@ -95,72 +99,76 @@ msgid_plural ""
 msgstr[0] ""
 msgstr[1] ""
 
-#: home-manager/home-manager:477
+#: home-manager/home-manager:533
 msgid "Unknown \"news.display\" setting \"%s\"."
 msgstr ""
 
-#: home-manager/home-manager:484
+#: home-manager/home-manager:540
 #, sh-format
 msgid "Please set the $EDITOR environment variable"
 msgstr ""
 
-#: home-manager/home-manager:499
+#: home-manager/home-manager:555
 msgid "Cannot run build in read-only directory"
 msgstr ""
 
-#: home-manager/home-manager:583
+#: home-manager/home-manager:639
 msgid "No generation with ID %s"
 msgstr ""
 
-#: home-manager/home-manager:585
+#: home-manager/home-manager:641
 msgid "Cannot remove the current generation %s"
 msgstr ""
 
-#: home-manager/home-manager:587
+#: home-manager/home-manager:643
 msgid "Removing generation %s"
 msgstr ""
 
-#: home-manager/home-manager:606
+#: home-manager/home-manager:662
 msgid "No generations to expire"
 msgstr ""
 
-#: home-manager/home-manager:617
+#: home-manager/home-manager:673
 msgid "No home-manager packages seem to be installed."
 msgstr ""
 
-#: home-manager/home-manager:674
+#: home-manager/home-manager:699
+msgid "Sorry, this command is not yet supported in flake setup"
+msgstr ""
+
+#: home-manager/home-manager:736
 msgid "Unknown argument %s"
 msgstr ""
 
-#: home-manager/home-manager:690
+#: home-manager/home-manager:752
 msgid "This will remove Home Manager from your system."
 msgstr ""
 
-#: home-manager/home-manager:693
+#: home-manager/home-manager:755
 msgid "This is a dry run, nothing will actually be uninstalled."
 msgstr ""
 
-#: home-manager/home-manager:697
+#: home-manager/home-manager:759
 msgid "Really uninstall Home Manager?"
 msgstr ""
 
-#: home-manager/home-manager:703
+#: home-manager/home-manager:765
 msgid "Switching to empty Home Manager configuration..."
 msgstr ""
 
-#: home-manager/home-manager:730
+#: home-manager/home-manager:792
 msgid "Yay!"
 msgstr ""
 
-#: home-manager/home-manager:735
+#: home-manager/home-manager:797
 msgid "Home Manager is uninstalled but your home.nix is left untouched."
 msgstr ""
 
-#: home-manager/home-manager:945
+#: home-manager/home-manager:1008
 msgid "expire-generations expects one argument, got %d."
 msgstr ""
 
-#: home-manager/home-manager:967
+#: home-manager/home-manager:1030
 msgid "Unknown command: %s"
 msgstr ""
 
diff --git a/modules/lib-bash/activation-init.sh b/modules/lib-bash/activation-init.sh
index b68b76817..546495453 100644
--- a/modules/lib-bash/activation-init.sh
+++ b/modules/lib-bash/activation-init.sh
@@ -1,55 +1,61 @@
-# Moves the existing profile from /nix to ~ to match changed behavior in Nix
-# 2.14. See https://github.com/NixOS/nix/pull/5226.
-#
-# Note, this function is intentionally unused for now. There remains a few open
-# questions about backwards compatibility and support from
-# `nix-collect-garbage`.
+# Moves the existing profile from /nix or $XDG_STATE_HOME/home-manager to
+# $XDG_STATE_HOME/nix to match changed behavior in Nix 2.14. See
+# https://github.com/NixOS/nix/pull/5226.
 function migrateProfile() {
     declare -r stateHome="${XDG_STATE_HOME:-$HOME/.local/state}"
+    declare -r userNixStateDir="$stateHome/nix"
     declare -r hmStateDir="$stateHome/home-manager"
-    declare -r nixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
 
-    declare -r newProfilesDir="$hmStateDir/profiles"
-    declare -r oldProfilesDir="$nixStateDir/profiles/per-user/$USER"
+    declare -r globalNixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
+    declare -r globalProfilesDir="$globalNixStateDir/profiles/per-user/$USER"
 
-    if [[ ! -d $newProfilesDir ]]; then
-        _i 'Migrating profiles from %s to %s' "$oldProfilesDir" "$newProfilesDir"
-        mkdir -p "$newProfilesDir"
-        for p in "$oldProfilesDir"/home-manager-*; do
-            declare -r name="${p##*/}"
-            nix-store --realise "$p" --add-root "$newProfilesDir/$name" > /dev/null
-        done
-        cp -P "$oldProfilesDir/home-manager" "$newProfilesDir"
+    if [[ -e $globalProfilesDir/home-manager ]]; then
+        declare -r oldProfilesDir="$globalProfilesDir"
+    elif [[ -e $hmStateDir/profiles/home-manager ]]; then
+        declare -r oldProfilesDir="$hmStateDir/profiles"
     fi
 
-    rm "$oldProfilesDir"/home-manager-*
+    declare -r newProfilesDir="$userNixStateDir/profiles"
+
+    if [[ -v oldProfilesDir && -e $newProfilesDir ]]; then
+        if [[ ! -e $newProfilesDir/home-manager ]]; then
+            _i 'Migrating profile from %s to %s' "$oldProfilesDir" "$newProfilesDir"
+            for p in "$oldProfilesDir"/home-manager-*; do
+                declare name="${p##*/}"
+                nix-store --realise "$p" --add-root "$newProfilesDir/$name" > /dev/null
+            done
+            cp -P "$oldProfilesDir/home-manager" "$newProfilesDir"
+        fi
+
+        rm "$oldProfilesDir/home-manager" "$oldProfilesDir"/home-manager-*
+    fi
 }
 
 function setupVars() {
-    declare -r nixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
-    declare -r globalProfilesDir="$nixStateDir/profiles/per-user/$USER"
-    declare -r globalGcrootsDir="$nixStateDir/gcroots/per-user/$USER"
-
     declare -r stateHome="${XDG_STATE_HOME:-$HOME/.local/state}"
-    declare -r hmStateDir="$stateHome/home-manager"
-    declare -r hmGcrootsDir="$hmStateDir/gcroots"
+    declare -r userNixStateDir="$stateHome/nix"
+    declare -r hmGcrootsDir="$stateHome/home-manager/gcroots"
 
-    # If the global profiles path exists or we can create it, then place the HM
-    # profile there. Otherwise place it in the HM data directory. We prefer to
-    # use the global location since it makes it visible to
-    # `nix-collect-garbage`.
-    #
-    # In the future we may perform a one-shot migration to the new location.
+    declare -r globalNixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
+    declare -r globalProfilesDir="$globalNixStateDir/profiles/per-user/$USER"
+    declare -r globalGcrootsDir="$globalNixStateDir/gcroots/per-user/$USER"
+
+    # If the user Nix profiles path exists, then place the HM profile there.
+    # Otherwise, if the global Nix per-user state directory exists then use
+    # that. If neither exists, then we give up.
     #
     # shellcheck disable=2174
-    if [[ -d "$globalProfilesDir" ]] || mkdir -m 0755 -p "$globalProfilesDir" 2>/dev/null; then
-        declare -r hmProfilesDir="$globalProfilesDir"
+    if [[ -d $userNixStateDir/profiles ]]; then
+        declare -r profilesDir="$userNixStateDir/profiles"
+    elif [[ -d $globalProfilesDir ]]; then
+        declare -r profilesDir="$globalProfilesDir"
     else
-        declare -r hmProfilesDir="$hmStateDir/profiles"
-        mkdir -m 0755 -p "$hmProfilesDir"
+        _iError 'Could not find suitable profile directory, tried %s and %s' \
+                "$userNixStateDir/profiles" "$globalProfilesDir" >&2
+        exit 1
     fi
 
-    declare -gr genProfilePath="$hmProfilesDir/home-manager"
+    declare -gr genProfilePath="$profilesDir/home-manager"
     declare -gr newGenPath="@GENERATION_DIR@";
     declare -gr newGenGcPath="$hmGcrootsDir/current-home"
     declare -gr legacyGenGcPath="$globalGcrootsDir/current-home"
@@ -77,7 +83,7 @@ function setupVars() {
             || ! -v oldGenNum && -v oldGenPath ]]; then
         _i $'The previous generation number and path are in conflict! These\nmust be either both empty or both set but are now set to\n\n    \'%s\' and \'%s\'\n\nIf you don\'t mind losing previous profile generations then\nthe easiest solution is probably to run\n\n   rm %s/home-manager*\n   rm %s/current-home\n\nand trying home-manager switch again. Good luck!' \
            "${oldGenNum:-}" "${oldGenPath:-}" \
-           "$hmProfilesDir" "$hmGcrootsDir"
+           "$profilesDir" "$hmGcrootsDir"
         exit 1
     fi
 }
@@ -99,6 +105,7 @@ _i "Starting Home Manager activation"
 $VERBOSE_RUN _i "Sanity checking Nix"
 nix-build --expr '{}' --no-out-link
 
+migrateProfile
 setupVars
 
 if [[ -v DRY_RUN ]] ; then
@@ -109,7 +116,6 @@ else
     $VERBOSE_RUN _i "This is a live run"
     export DRY_RUN_CMD=""
     export DRY_RUN_NULL=/dev/null
-
 fi
 
 if [[ -v VERBOSE ]]; then
diff --git a/modules/po/hm-modules.pot b/modules/po/hm-modules.pot
index 79687dedc..a2b658758 100644
--- a/modules/po/hm-modules.pot
+++ b/modules/po/hm-modules.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Home Manager Modules\n"
 "Report-Msgid-Bugs-To: https://github.com/nix-community/home-manager/issues\n"
-"POT-Creation-Date: 2023-03-15 20:11+0100\n"
+"POT-Creation-Date: 2023-04-10 13:49+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -53,15 +53,19 @@ msgstr ""
 msgid "Activating %s"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:16
-msgid "Migrating profiles from %s to %s"
+#: modules/lib-bash/activation-init.sh:22
+msgid "Migrating profile from %s to %s"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:75
+#: modules/lib-bash/activation-init.sh:53
+msgid "Could not find suitable profile directory, tried %s and %s"
+msgstr ""
+
+#: modules/lib-bash/activation-init.sh:81
 msgid "Sanity checking oldGenNum and oldGenPath"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:78
+#: modules/lib-bash/activation-init.sh:84
 msgid ""
 "The previous generation number and path are in conflict! These\n"
 "must be either both empty or both set but are now set to\n"
@@ -77,26 +81,26 @@ msgid ""
 "and trying home-manager switch again. Good luck!"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:95
+#: modules/lib-bash/activation-init.sh:101
 msgid "Starting Home Manager activation"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:99
+#: modules/lib-bash/activation-init.sh:105
 msgid "Sanity checking Nix"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:105
+#: modules/lib-bash/activation-init.sh:112
 msgid "This is a dry run"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:109
+#: modules/lib-bash/activation-init.sh:116
 msgid "This is a live run"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:116
+#: modules/lib-bash/activation-init.sh:122
 msgid "Using Nix version: %s"
 msgstr ""
 
-#: modules/lib-bash/activation-init.sh:119
+#: modules/lib-bash/activation-init.sh:125
 msgid "Activation variables:"
 msgstr ""