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 `/`.
This commit is contained in:
Robert Helgesson 2021-07-27 23:27:01 +02:00
parent 47ad3655ec
commit de0070c4cf
No known key found for this signature in database
GPG Key ID: 36BDAA14C2797E89
3 changed files with 67 additions and 19 deletions

View File

@ -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

View File

@ -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

View File

@ -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"