From de0070c4cfa9d19261174989e90edf371aabd239 Mon Sep 17 00:00:00 2001 From: Robert Helgesson Date: Tue, 27 Jul 2021 23:27:01 +0200 Subject: [PATCH] files: add preliminary support for full paths This represents the first step in migrating `home.file` to support arbitrary absolute paths. This is to allow Home Manager to manage files anywhere, provided the user has sufficient privileges. For example, with this change the configuration home.file."test/one".text = "foo"; home.file."/test/two".text = "foo"; will result in the files "$HOME/test/one" and "/test/two", respectively. Note, a relative file name will still be relative `$HOME`. To allow a reasonable transition between the old and new path handling we introduce the notion of "generation directory layout version". The version is simply a file `version` within the generation directory containing a number indicating the version number. The version 0 (also implied if the version file is missing) indicates the legacy layout where managed file paths always are relative `$HOME`. Version 1 indicates the new layout where managed file paths are relative `/`. --- modules/files.nix | 58 +++++++++++++++++++++-------- modules/home-environment.nix | 7 ++++ modules/lib-bash/activation-init.sh | 21 +++++++++-- 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/modules/files.nix b/modules/files.nix index 1ecc1821..cb00ce6a 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -163,11 +163,12 @@ in home.activation.linkGeneration = hm.dag.entryAfter ["writeBoundary"] ( let link = pkgs.writeShellScript "link" '' - newGenFiles="$1" - shift + rootPath="$1" + newGenFiles="$2" + shift 2 for sourcePath in "$@" ; do relativePath="''${sourcePath#$newGenFiles/}" - targetPath="$HOME/$relativePath" + targetPath="$rootPath/$relativePath" if [[ -e "$targetPath" && ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then backup="$targetPath.$HOME_MANAGER_BACKUP_EXT" $DRY_RUN_CMD mv $VERBOSE_ARG "$targetPath" "$backup" || errorEcho "Moving '$targetPath' failed!" @@ -184,10 +185,11 @@ in # considered part of a Home Manager generation. homeFilePattern="$(readlink -e ${escapeShellArg builtins.storeDir})/*-home-manager-files/*" - newGenFiles="$1" - shift 1 + rootPath="$1" + newGenFiles="$2" + shift 2 for relativePath in "$@" ; do - targetPath="$HOME/$relativePath" + targetPath="$rootPath/$relativePath" if [[ -e "$newGenFiles/$relativePath" ]] ; then $VERBOSE_ECHO "Checking $targetPath: exists" elif [[ ! "$(readlink "$targetPath")" == $homeFilePattern ]] ; then @@ -199,7 +201,8 @@ in # Recursively delete empty parent directories. targetDir="$(dirname "$relativePath")" if [[ "$targetDir" != "." ]] ; then - pushd "$HOME" > /dev/null + # TODO + # pushd "$HOME" > /dev/null # Call rmdir with a relative path excluding $HOME. # Otherwise, it might try to delete $HOME and exit @@ -208,7 +211,7 @@ in -p --ignore-fail-on-non-empty \ "$targetDir" - popd > /dev/null + # popd > /dev/null fi fi done @@ -216,12 +219,17 @@ in in '' function linkNewGen() { - echo "Creating home file links in $HOME" + echo "Creating home file links" + + local rootPath= + if [[ $newGenLayoutVersion -eq 0 ]] ; then + rootPath="$HOME" + fi local newGenFiles newGenFiles="$(readlink -e "$newGenPath/home-files")" find "$newGenFiles" \( -type f -or -type l \) \ - -exec bash ${link} "$newGenFiles" {} + + -exec bash ${link} "$rootPath" "$newGenFiles" {} + } function cleanOldGen() { @@ -229,17 +237,35 @@ in return fi - echo "Cleaning up orphan links from $HOME" + echo "Cleaning up orphan links" local newGenFiles oldGenFiles newGenFiles="$(readlink -e "$newGenPath/home-files")" oldGenFiles="$(readlink -e "$oldGenPath/home-files")" - # Apply the cleanup script on each leaf in the old - # generation. The find command below will print the - # relative path of the entry. - find "$oldGenFiles" '(' -type f -or -type l ')' -printf '%P\0' \ - | xargs -0 bash ${cleanup} "$newGenFiles" + # When transitioning from layout 0 to 1 we need to prefix all old + # paths with the home directory. Conversely, if we ever go from + # layout 1 to 0 we need to "subtract" the home directory from the + # old generation path, this is done by appending an "antiprefix" to + # the layout 1 paths. + local prefix= antiprefix= + if [[ $oldGenLayoutVersion -eq 0 && $newGenLayoutVersion -ge 1 ]] ; then + prefix="''${HOME#/}/" + elif [[ $oldGenLayoutVersion -ge 1 && $newGenLayoutVersion -eq 0 ]] ; then + antiprefix="/$HOME" + fi + + local rootPath= + if [[ $newGenLayoutVersion -eq 0 ]] ; then + rootPath="$HOME" + fi + + $VERBOSE_ECHO "Orphan link cleanup uses prefix=$prefix antiprefix=$antiprefix rootPath=$rootPath" + + # Apply the cleanup script on each leaf in the old generation. The + # find command below will print the relative path of the entry. + find "$oldGenFiles$antiprefix" '(' -type f -or -type l ')' -printf "$prefix%P\0" \ + | xargs -0 bash ${cleanup} "$rootPath" "$newGenFiles" } cleanOldGen diff --git a/modules/home-environment.nix b/modules/home-environment.nix index ee9c3bd2..e13991d9 100644 --- a/modules/home-environment.nix +++ b/modules/home-environment.nix @@ -622,6 +622,13 @@ in mkdir $out/bin ln -s $out/activate $out/bin/home-manager-generation + # The generation directory layout version. + # + # - Version 0 (also implied when file is missing) means + # "legacy layout". + # - Version 1 adds full home file paths. + echo 0 > $out/version + substituteInPlace $out/activate \ --subst-var-by GENERATION_DIR $out diff --git a/modules/lib-bash/activation-init.sh b/modules/lib-bash/activation-init.sh index f95008ee..ab1a7ec3 100755 --- a/modules/lib-bash/activation-init.sh +++ b/modules/lib-bash/activation-init.sh @@ -5,9 +5,17 @@ function setupVars() { local profilesPath="$nixStateDir/profiles/per-user/$USER" local gcPath="$nixStateDir/gcroots/per-user/$USER" - genProfilePath="$profilesPath/home-manager" - newGenPath="@GENERATION_DIR@"; - newGenGcPath="$gcPath/current-home" + declare -gr genProfilePath="$profilesPath/home-manager" + declare -gr newGenPath="@GENERATION_DIR@"; + declare -gr newGenGcPath="$gcPath/current-home" + + declare -g newGenLayoutVersion + if [[ -f $newGenPath/version ]]; then + newGenLayoutVersion=$(< "$newGenPath/version") + else + newGenLayoutVersion=0 + fi + readonly newGenLayoutVersion local greatestGenNum greatestGenNum=$( \ @@ -23,7 +31,14 @@ function setupVars() { fi if [[ -e $profilesPath/home-manager ]] ; then + declare -g oldGenPath oldGenLayoutVersion oldGenPath="$(readlink -e "$profilesPath/home-manager")" + if [[ -f $oldGenPath/version ]]; then + oldGenLayoutVersion=$(< "$oldGenPath/version") + else + oldGenLayoutVersion=0 + fi + readonly oldGenPath oldGenLayoutVersion fi $VERBOSE_ECHO "Sanity checking oldGenNum and oldGenPath"