home-manager: handle missing per-user profiles directory

Specifically, if the global per-user profiles path do not exist and we
cannot create it during the activation, then place our profile in the
Home Manager data directory. We prefer to use the global location,
though, since it makes it visible to `nix-collect-garbage`.

This is intended to improve compatibility with Nix version 2.14 and
later, which no longer creates the per-user directories.

Also, use the Home Manager data directory to manage the gcroot for the
current generation. It does not have to sit in the global per-user
gcroots directory since it should never be eligible for GC.
This commit is contained in:
Robert Helgesson 2023-03-04 10:39:02 +01:00
parent 0f3dfc16d0
commit f69816489d
No known key found for this signature in database
GPG key ID: 36BDAA14C2797E89
6 changed files with 149 additions and 98 deletions

View file

@ -19,7 +19,7 @@ function removeByName() {
}
function setNixProfileCommands() {
if [[ -e ~/.nix-profile/manifest.json ]] ; then
if [[ -e $HOME/.nix-profile/manifest.json ]] ; then
LIST_OUTPATH_CMD="nix profile list"
REMOVE_CMD="removeByName"
else
@ -93,6 +93,23 @@ function setHomeManagerNixPath() {
done
}
# 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 -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_GCROOT_LEGACY_PATH="$globalGcrootsDir/current-home"
if [[ -d "$globalProfilesDir" ]]; then
declare -gr HM_PROFILE_DIR="$globalProfilesDir"
else
declare -gr HM_PROFILE_DIR="$HM_STATE_DIR/profiles"
fi
}
function setFlakeAttribute() {
local configFlake="${XDG_CONFIG_HOME:-$HOME/.config}/nixpkgs/flake.nix"
if [[ -z $FLAKE_ARG && ! -v HOME_MANAGER_CONFIG && -e "$configFlake" ]]; then
@ -339,7 +356,7 @@ function doListGens() {
color="always"
fi
pushd "$NIX_STATE_DIR/profiles/per-user/$USER" > /dev/null
pushd "$HM_PROFILE_DIR" > /dev/null
# shellcheck disable=2012
ls --color=$color -gG --time-style=long-iso --sort time home-manager-*-link \
| cut -d' ' -f 4- \
@ -352,7 +369,7 @@ function doListGens() {
function doRmGenerations() {
setVerboseAndDryRun
pushd "$NIX_STATE_DIR/profiles/per-user/$USER" > /dev/null
pushd "$HM_PROFILE_DIR" > /dev/null
for generationId in "$@"; do
local linkName="home-manager-$generationId-link"
@ -370,17 +387,10 @@ function doRmGenerations() {
popd > /dev/null
}
function doRmAllGenerations() {
$DRY_RUN_CMD rm $VERBOSE_ARG \
"$NIX_STATE_DIR/profiles/per-user/$USER/home-manager"*
}
function doExpireGenerations() {
local profileDir="$NIX_STATE_DIR/profiles/per-user/$USER"
local generations
generations="$( \
find "$profileDir" -name 'home-manager-*-link' -not -newermt "$1" \
find "$HM_PROFILE_DIR" -name 'home-manager-*-link' -not -newermt "$1" \
| sed 's/^.*-\([0-9]*\)-link$/\1/' \
)"
@ -482,6 +492,7 @@ function doUninstall() {
read -r -n 1 -p "$(_i 'Really uninstall Home Manager?') [y/n] " confirmation
echo
# shellcheck disable=2086
case $confirmation in
y|Y)
_i "Switching to empty Home Manager configuration..."
@ -493,10 +504,22 @@ function doUninstall() {
doSwitch
$DRY_RUN_CMD $REMOVE_CMD home-manager-path || true
rm "$HOME_MANAGER_CONFIG"
$DRY_RUN_CMD rm $VERBOSE_ARG -r \
"${XDG_DATA_HOME:-$HOME/.local/share}/home-manager"
$DRY_RUN_CMD rm $VERBOSE_ARG \
"$NIX_STATE_DIR/gcroots/per-user/$USER/current-home"
if [[ -e $HM_DATA_HOME ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG -r "$HM_DATA_HOME"
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
if [[ -e $HM_STATE_DIR ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG -r "$HM_STATE_DIR"
fi
;;
*)
_i "Yay!"
@ -504,22 +527,6 @@ function doUninstall() {
;;
esac
local deleteProfiles
read -r -n 1 \
-p "$(_i 'Remove all Home Manager generations?') [y/n] " \
deleteProfiles
echo
case $deleteProfiles in
y|Y)
doRmAllGenerations
_i 'All generations are now eligible for garbage collection.'
;;
*)
_i 'Leaving generations but they may still be garbage collected.'
;;
esac
_i "Home Manager is uninstalled but your home.nix is left untouched."
}
@ -591,8 +598,6 @@ function doHelp() {
echo " uninstall Remove Home Manager"
}
readonly NIX_STATE_DIR="${NIX_STATE_DIR:-/nix/var/nix}"
EXTRA_NIX_PATH=()
HOME_MANAGER_CONFIG_ATTRIBUTE=""
PASSTHROUGH_OPTS=()
@ -601,6 +606,7 @@ COMMAND_ARGS=()
FLAKE_ARG=""
setHomeManagerNixPath
setHomeManagerPathVariables
while [[ $# -gt 0 ]]; do
opt="$1"

View file

@ -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: 2022-03-26 15:08+0100\n"
"POT-Creation-Date: 2023-03-07 23:36+0100\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"
@ -26,15 +26,15 @@ msgstr ""
msgid "No configuration file found. Please create one at %s"
msgstr ""
#: home-manager/home-manager:122
#: home-manager/home-manager:146
msgid "Can't inspect options of a flake configuration"
msgstr ""
#: home-manager/home-manager:162
#: home-manager/home-manager:185
msgid "Can't instantiate a flake configuration"
msgstr ""
#: home-manager/home-manager:237
#: home-manager/home-manager:258
msgid ""
"There is %d unread and relevant news item.\n"
"Read it by running the command \"%s news\"."
@ -44,92 +44,80 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
#: home-manager/home-manager:251
#: home-manager/home-manager:272
msgid "Unknown \"news.display\" setting \"%s\"."
msgstr ""
#: home-manager/home-manager:258
#: home-manager/home-manager:279
#, sh-format
msgid "Please set the $EDITOR environment variable"
msgstr ""
#: home-manager/home-manager:273
#: home-manager/home-manager:294
msgid "Cannot run build in read-only directory"
msgstr ""
#: home-manager/home-manager:355
#: home-manager/home-manager:378
msgid "No generation with ID %s"
msgstr ""
#: home-manager/home-manager:357
#: home-manager/home-manager:380
msgid "Cannot remove the current generation %s"
msgstr ""
#: home-manager/home-manager:359
#: home-manager/home-manager:382
msgid "Removing generation %s"
msgstr ""
#: home-manager/home-manager:385
#: home-manager/home-manager:401
msgid "No generations to expire"
msgstr ""
#: home-manager/home-manager:396
#: home-manager/home-manager:412
msgid "No home-manager packages seem to be installed."
msgstr ""
#: home-manager/home-manager:453
#: home-manager/home-manager:469
msgid "Unknown argument %s"
msgstr ""
#: home-manager/home-manager:469
#: home-manager/home-manager:485
msgid "This will remove Home Manager from your system."
msgstr ""
#: home-manager/home-manager:472
#: home-manager/home-manager:488
msgid "This is a dry run, nothing will actually be uninstalled."
msgstr ""
#: home-manager/home-manager:476
#: home-manager/home-manager:492
msgid "Really uninstall Home Manager?"
msgstr ""
#: home-manager/home-manager:481
#: home-manager/home-manager:498
msgid "Switching to empty Home Manager configuration..."
msgstr ""
#: home-manager/home-manager:493
#: home-manager/home-manager:525
msgid "Yay!"
msgstr ""
#: home-manager/home-manager:500
msgid "Remove all Home Manager generations?"
msgstr ""
#: home-manager/home-manager:507
msgid "All generations are now eligible for garbage collection."
msgstr ""
#: home-manager/home-manager:510
msgid "Leaving generations but they may still be garbage collected."
msgstr ""
#: home-manager/home-manager:514
#: home-manager/home-manager:530
msgid "Home Manager is uninstalled but your home.nix is left untouched."
msgstr ""
#: home-manager/home-manager:673
#: home-manager/home-manager:695
msgid "%s: unknown option '%s'"
msgstr ""
#: home-manager/home-manager:674
#: home-manager/home-manager:696
msgid "Run '%s --help' for usage help"
msgstr ""
#: home-manager/home-manager:708
#: home-manager/home-manager:730
msgid "expire-generations expects one argument, got %d."
msgstr ""
#: home-manager/home-manager:730
#: home-manager/home-manager:752
msgid "Unknown command: %s"
msgstr ""

View file

@ -272,7 +272,10 @@ in
$DRY_RUN_CMD nix-env $VERBOSE_ARG --profile "$genProfilePath" --set "$newGenPath"
fi
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenGcPath"
$DRY_RUN_CMD nix-store --realise "$newGenPath" --add-root "$newGenGcPath" > "$DRY_RUN_NULL"
if [[ -e "$legacyGenGcPath" ]]; then
$DRY_RUN_CMD rm $VERBOSE_ARG "$legacyGenGcPath"
fi
else
_i "No change so reusing latest profile generation %s" "$oldGenNum"
fi

View file

@ -584,7 +584,7 @@ in
if config.submoduleSupport.externalPackageInstall
then
''
if [[ -e "$nixProfilePath"/manifest.json ]] ; then
if [[ -e $HOME/.nix-profile/manifest.json ]] ; then
nix profile list \
| { grep 'home-manager-path$' || test $? = 1; } \
| cut -d ' ' -f 4 \
@ -608,7 +608,7 @@ in
$DRY_RUN_CMD $oldNix profile install $1
}
if [[ -e "$nixProfilePath"/manifest.json ]] ; then
if [[ -e $HOME/.nix-profile/manifest.json ]] ; then
INSTALL_CMD="nix profile install"
INSTALL_CMD_ACTUAL="nixReplaceProfile"
LIST_CMD="nix profile list"

View file

@ -1,14 +1,60 @@
# 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`.
function migrateProfile() {
declare -r stateHome="${XDG_STATE_HOME:-$HOME/.local/state}"
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"
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"
fi
rm "$oldProfilesDir"/home-manager-*
}
function setupVars() {
local nixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
local profilesPath="$nixStateDir/profiles/per-user/$USER"
local gcPath="$nixStateDir/gcroots/per-user/$USER"
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 -gr nixProfilePath="$profilesPath/profile"
declare -gr genProfilePath="$profilesPath/home-manager"
declare -r stateHome="${XDG_STATE_HOME:-$HOME/.local/state}"
declare -r hmStateDir="$stateHome/home-manager"
declare -r hmGcrootsDir="$hmStateDir/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.
#
# shellcheck disable=2174
if [[ -d "$globalProfilesDir" ]] || mkdir -m 0755 -p "$globalProfilesDir"; then
declare -r hmProfilesDir="$globalProfilesDir"
else
declare -r hmProfilesDir="$hmStateDir/profiles"
mkdir -m 0755 -p "$hmProfilesDir"
fi
declare -gr genProfilePath="$hmProfilesDir/home-manager"
declare -gr newGenPath="@GENERATION_DIR@";
declare -gr newGenGcPath="$gcPath/current-home"
declare -gr newGenGcPath="$hmGcrootsDir/current-home"
declare -gr legacyGenGcPath="$globalGcrootsDir/current-home"
local greatestGenNum
declare greatestGenNum
greatestGenNum=$( \
nix-env --list-generations --profile "$genProfilePath" \
| tail -1 \
@ -21,9 +67,9 @@ function setupVars() {
declare -gr newGenNum=1
fi
if [[ -e $profilesPath/home-manager ]] ; then
oldGenPath="$(readlink -e "$profilesPath/home-manager")"
declare -gr oldGenPath
if [[ -e $genProfilePath ]] ; then
declare -g oldGenPath
oldGenPath="$(readlink -e "$genProfilePath")"
fi
$VERBOSE_RUN _i "Sanity checking oldGenNum and oldGenPath"
@ -31,7 +77,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:-}" \
"$profilesPath" "$gcPath"
"$hmProfilesDir" "$hmGcrootsDir"
exit 1
fi
}
@ -58,9 +104,12 @@ setupVars
if [[ -v DRY_RUN ]] ; then
_i "This is a dry run"
export DRY_RUN_CMD=echo
export DRY_RUN_NULL=/dev/stdout
else
$VERBOSE_RUN _i "This is a live run"
export DRY_RUN_CMD=""
export DRY_RUN_NULL=/dev/null
fi
if [[ -v VERBOSE ]]; then
@ -77,5 +126,6 @@ else
fi
$VERBOSE_ECHO " newGenPath=$newGenPath"
$VERBOSE_ECHO " newGenNum=$newGenNum"
$VERBOSE_ECHO " newGenGcPath=$newGenGcPath"
$VERBOSE_ECHO " genProfilePath=$genProfilePath"
$VERBOSE_ECHO " newGenGcPath=$newGenGcPath"
$VERBOSE_ECHO " legacyGenGcPath=$legacyGenGcPath"

View file

@ -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: 2022-03-26 15:08+0100\n"
"POT-Creation-Date: 2023-03-07 23:36+0100\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"
@ -17,23 +17,23 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: modules/files.nix:233
#: modules/files.nix:234
msgid "Creating home file links in %s"
msgstr ""
#: modules/files.nix:246
#: modules/files.nix:247
msgid "Cleaning up orphan links from %s"
msgstr ""
#: modules/files.nix:262
#: modules/files.nix:263
msgid "Creating profile generation %s"
msgstr ""
#: modules/files.nix:276
#: modules/files.nix:280
msgid "No change so reusing latest profile generation %s"
msgstr ""
#: modules/home-environment.nix:607
#: modules/home-environment.nix:625
msgid ""
"Oops, Nix failed to install your new Home Manager profile!\n"
"\n"
@ -49,15 +49,19 @@ msgid ""
"Then try activating your Home Manager configuration again."
msgstr ""
#: modules/home-environment.nix:639
#: modules/home-environment.nix:658
msgid "Activating %s"
msgstr ""
#: modules/lib-bash/activation-init.sh:31
#: modules/lib-bash/activation-init.sh:18
msgid "Migrating profiles from %s to %s"
msgstr ""
#: modules/lib-bash/activation-init.sh:77
msgid "Sanity checking oldGenNum and oldGenPath"
msgstr ""
#: modules/lib-bash/activation-init.sh:34
#: modules/lib-bash/activation-init.sh:80
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"
@ -73,26 +77,26 @@ msgid ""
"and trying home-manager switch again. Good luck!"
msgstr ""
#: modules/lib-bash/activation-init.sh:51
#: modules/lib-bash/activation-init.sh:97
msgid "Starting Home Manager activation"
msgstr ""
#: modules/lib-bash/activation-init.sh:55
#: modules/lib-bash/activation-init.sh:101
msgid "Sanity checking Nix"
msgstr ""
#: modules/lib-bash/activation-init.sh:61
#: modules/lib-bash/activation-init.sh:107
msgid "This is a dry run"
msgstr ""
#: modules/lib-bash/activation-init.sh:64
#: modules/lib-bash/activation-init.sh:111
msgid "This is a live run"
msgstr ""
#: modules/lib-bash/activation-init.sh:69
#: modules/lib-bash/activation-init.sh:118
msgid "Using Nix version: %s"
msgstr ""
#: modules/lib-bash/activation-init.sh:72
#: modules/lib-bash/activation-init.sh:121
msgid "Activation variables:"
msgstr ""