mirror of
https://github.com/nix-community/home-manager
synced 2024-11-10 07:04:17 +00:00
Separate home files module from home-environment.nix
This commit is contained in:
parent
0672936134
commit
f0a1d69f50
3 changed files with 239 additions and 216 deletions
|
@ -12,6 +12,7 @@ let
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
./home-environment.nix
|
./home-environment.nix
|
||||||
|
./files.nix
|
||||||
./manual.nix
|
./manual.nix
|
||||||
./misc/fontconfig.nix
|
./misc/fontconfig.nix
|
||||||
./misc/gtk.nix
|
./misc/gtk.nix
|
||||||
|
|
237
modules/files.nix
Normal file
237
modules/files.nix
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
{ pkgs, config, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
with import ./lib/dag.nix { inherit lib; };
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
cfg = config.home.file;
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
home.file = mkOption {
|
||||||
|
description = "Attribute set of files to link into the user home.";
|
||||||
|
default = {};
|
||||||
|
type = types.loaOf (types.submodule (
|
||||||
|
{ name, config, ... }: {
|
||||||
|
options = {
|
||||||
|
target = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
Path to target file relative to <envar>HOME</envar>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
text = mkOption {
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr types.lines;
|
||||||
|
description = "Text of the file.";
|
||||||
|
};
|
||||||
|
|
||||||
|
source = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = ''
|
||||||
|
Path of the source file. The file name must not start
|
||||||
|
with a period since Nix will not allow such names in
|
||||||
|
the Nix store.
|
||||||
|
</para><para>
|
||||||
|
This may refer to a directory.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mode = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "444";
|
||||||
|
description = "The permissions to apply to the file.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
target = mkDefault name;
|
||||||
|
source = mkIf (config.text != null) (
|
||||||
|
let name' = "user-etc-" + baseNameOf name;
|
||||||
|
in mkDefault (pkgs.writeText name' config.text)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
home-files = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
internal = true;
|
||||||
|
description = "Package to contain all home files";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
assertions = [
|
||||||
|
(let
|
||||||
|
badFiles =
|
||||||
|
filter (f: hasPrefix "." (baseNameOf f))
|
||||||
|
(map (v: toString v.source)
|
||||||
|
(attrValues cfg));
|
||||||
|
badFilesStr = toString badFiles;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assertion = badFiles == [];
|
||||||
|
message = "Source file names must not start with '.': ${badFilesStr}";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
# This verifies that the links we are about to create will not
|
||||||
|
# overwrite an existing file.
|
||||||
|
home.activation.checkLinkTargets = dagEntryBefore ["writeBoundary"] (
|
||||||
|
let
|
||||||
|
pattern = "-home-manager-files/";
|
||||||
|
check = pkgs.writeText "check" ''
|
||||||
|
. ${./lib-bash/color-echo.sh}
|
||||||
|
|
||||||
|
newGenFiles="$1"
|
||||||
|
shift
|
||||||
|
for sourcePath in "$@" ; do
|
||||||
|
relativePath="''${sourcePath#$newGenFiles/}"
|
||||||
|
targetPath="$HOME/$relativePath"
|
||||||
|
if [[ -e "$targetPath" \
|
||||||
|
&& ! "$(readlink "$targetPath")" =~ "${pattern}" ]] ; then
|
||||||
|
errorEcho "Existing file '$targetPath' is in the way"
|
||||||
|
collision=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -v collision ]] ; then
|
||||||
|
errorEcho "Please move the above files and try again"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
''
|
||||||
|
function checkNewGenCollision() {
|
||||||
|
local newGenFiles
|
||||||
|
newGenFiles="$(readlink -e "$newGenPath/home-files")"
|
||||||
|
find "$newGenFiles" -type f -print0 -or -type l -print0 \
|
||||||
|
| xargs -0 bash ${check} "$newGenFiles"
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNewGenCollision || exit 1
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
home.activation.linkGeneration = dagEntryAfter ["writeBoundary"] (
|
||||||
|
let
|
||||||
|
pattern = "-home-manager-files/";
|
||||||
|
|
||||||
|
link = pkgs.writeText "link" ''
|
||||||
|
newGenFiles="$1"
|
||||||
|
shift
|
||||||
|
for sourcePath in "$@" ; do
|
||||||
|
relativePath="''${sourcePath#$newGenFiles/}"
|
||||||
|
targetPath="$HOME/$relativePath"
|
||||||
|
$DRY_RUN_CMD mkdir -p $VERBOSE_ARG "$(dirname "$targetPath")"
|
||||||
|
$DRY_RUN_CMD ln -nsf $VERBOSE_ARG "$sourcePath" "$targetPath"
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
cleanup = pkgs.writeText "cleanup" ''
|
||||||
|
. ${./lib-bash/color-echo.sh}
|
||||||
|
|
||||||
|
newGenFiles="$1"
|
||||||
|
oldGenFiles="$2"
|
||||||
|
shift 2
|
||||||
|
for sourcePath in "$@" ; do
|
||||||
|
relativePath="''${sourcePath#$oldGenFiles/}"
|
||||||
|
targetPath="$HOME/$relativePath"
|
||||||
|
if [[ -e "$newGenFiles/$relativePath" ]] ; then
|
||||||
|
$VERBOSE_ECHO "Checking $targetPath: exists"
|
||||||
|
elif [[ ! "$(readlink "$targetPath")" =~ "${pattern}" ]] ; then
|
||||||
|
warnEcho "Path '$targetPath' not link into Home Manager generation. Skipping delete."
|
||||||
|
else
|
||||||
|
$VERBOSE_ECHO "Checking $targetPath: gone (deleting)"
|
||||||
|
$DRY_RUN_CMD rm $VERBOSE_ARG "$targetPath"
|
||||||
|
|
||||||
|
# Recursively delete empty parent directories.
|
||||||
|
targetDir="$(dirname "$relativePath")"
|
||||||
|
if [[ "$targetDir" != "." ]] ; then
|
||||||
|
pushd "$HOME" > /dev/null
|
||||||
|
|
||||||
|
# Call rmdir with a relative path excluding $HOME.
|
||||||
|
# Otherwise, it might try to delete $HOME and exit
|
||||||
|
# with a permission error.
|
||||||
|
$DRY_RUN_CMD rmdir $VERBOSE_ARG \
|
||||||
|
-p --ignore-fail-on-non-empty \
|
||||||
|
"$targetDir"
|
||||||
|
|
||||||
|
popd > /dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
''
|
||||||
|
function linkNewGen() {
|
||||||
|
local newGenFiles
|
||||||
|
newGenFiles="$(readlink -e "$newGenPath/home-files")"
|
||||||
|
find "$newGenFiles" -type f -print0 -or -type l -print0 \
|
||||||
|
| xargs -0 bash ${link} "$newGenFiles"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanOldGen() {
|
||||||
|
if [[ ! -v oldGenPath ]] ; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Cleaning up orphan links from $HOME"
|
||||||
|
|
||||||
|
local newGenFiles oldGenFiles
|
||||||
|
newGenFiles="$(readlink -e "$newGenPath/home-files")"
|
||||||
|
oldGenFiles="$(readlink -e "$oldGenPath/home-files")"
|
||||||
|
find "$oldGenFiles" -type f -print0 -or -type l -print0 \
|
||||||
|
| xargs -0 bash ${cleanup} "$newGenFiles" "$oldGenFiles"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! -v oldGenPath || "$oldGenPath" != "$newGenPath" ]] ; then
|
||||||
|
echo "Creating profile generation $newGenNum"
|
||||||
|
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenProfilePath"
|
||||||
|
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG $(basename "$newGenProfilePath") "$genProfilePath"
|
||||||
|
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenGcPath"
|
||||||
|
else
|
||||||
|
echo "No change so reusing latest profile generation $oldGenNum"
|
||||||
|
fi
|
||||||
|
|
||||||
|
linkNewGen
|
||||||
|
cleanOldGen
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
home-files = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "home-manager-files";
|
||||||
|
|
||||||
|
phases = [ "installPhase" ];
|
||||||
|
|
||||||
|
installPhase =
|
||||||
|
"mkdir -p $out\n" +
|
||||||
|
concatStringsSep "\n" (
|
||||||
|
mapAttrsToList (n: v:
|
||||||
|
''
|
||||||
|
target="$(realpath -m "$out/${v.target}")"
|
||||||
|
|
||||||
|
# Target file must be within $HOME.
|
||||||
|
if [[ ! "$target" =~ "$out" ]] ; then
|
||||||
|
echo "Error installing file '${v.target}' outside \$HOME" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "${v.source}" ]; then
|
||||||
|
mkdir -pv "$(dirname "$out/${v.target}")"
|
||||||
|
ln -sv "${v.source}" "$target"
|
||||||
|
else
|
||||||
|
install -D -m${v.mode} "${v.source}" "$target"
|
||||||
|
fi
|
||||||
|
''
|
||||||
|
) cfg
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -96,54 +96,6 @@ in
|
||||||
meta.maintainers = [ maintainers.rycee ];
|
meta.maintainers = [ maintainers.rycee ];
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
home.file = mkOption {
|
|
||||||
description = "Attribute set of files to link into the user home.";
|
|
||||||
default = {};
|
|
||||||
type = types.loaOf (types.submodule (
|
|
||||||
{ name, config, ... }: {
|
|
||||||
options = {
|
|
||||||
target = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = ''
|
|
||||||
Path to target file relative to <envar>HOME</envar>.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
text = mkOption {
|
|
||||||
default = null;
|
|
||||||
type = types.nullOr types.lines;
|
|
||||||
description = "Text of the file.";
|
|
||||||
};
|
|
||||||
|
|
||||||
source = mkOption {
|
|
||||||
type = types.path;
|
|
||||||
description = ''
|
|
||||||
Path of the source file. The file name must not start
|
|
||||||
with a period since Nix will not allow such names in
|
|
||||||
the Nix store.
|
|
||||||
</para><para>
|
|
||||||
This may refer to a directory.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
mode = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "444";
|
|
||||||
description = "The permissions to apply to the file.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
target = mkDefault name;
|
|
||||||
source = mkIf (config.text != null) (
|
|
||||||
let name' = "user-etc-" + baseNameOf name;
|
|
||||||
in mkDefault (pkgs.writeText name' config.text)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
home.language = mkOption {
|
home.language = mkOption {
|
||||||
type = languageSubModule;
|
type = languageSubModule;
|
||||||
default = {};
|
default = {};
|
||||||
|
@ -226,20 +178,6 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
assertions = [
|
|
||||||
(let
|
|
||||||
badFiles =
|
|
||||||
filter (f: hasPrefix "." (baseNameOf f))
|
|
||||||
(map (v: toString v.source)
|
|
||||||
(attrValues cfg.file));
|
|
||||||
badFilesStr = toString badFiles;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assertion = badFiles == [];
|
|
||||||
message = "Source file names must not start with '.': ${badFilesStr}";
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
home.sessionVariables =
|
home.sessionVariables =
|
||||||
let
|
let
|
||||||
maybeSet = name: value:
|
maybeSet = name: value:
|
||||||
|
@ -259,130 +197,6 @@ in
|
||||||
# script's "check" and the "write" phases.
|
# script's "check" and the "write" phases.
|
||||||
home.activation.writeBoundary = dagEntryAnywhere "";
|
home.activation.writeBoundary = dagEntryAnywhere "";
|
||||||
|
|
||||||
# This verifies that the links we are about to create will not
|
|
||||||
# overwrite an existing file.
|
|
||||||
home.activation.checkLinkTargets = dagEntryBefore ["writeBoundary"] (
|
|
||||||
let
|
|
||||||
pattern = "-home-manager-files/";
|
|
||||||
check = pkgs.writeText "check" ''
|
|
||||||
. ${./lib-bash/color-echo.sh}
|
|
||||||
|
|
||||||
newGenFiles="$1"
|
|
||||||
shift
|
|
||||||
for sourcePath in "$@" ; do
|
|
||||||
relativePath="''${sourcePath#$newGenFiles/}"
|
|
||||||
targetPath="$HOME/$relativePath"
|
|
||||||
if [[ -e "$targetPath" \
|
|
||||||
&& ! "$(readlink "$targetPath")" =~ "${pattern}" ]] ; then
|
|
||||||
errorEcho "Existing file '$targetPath' is in the way"
|
|
||||||
collision=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -v collision ]] ; then
|
|
||||||
errorEcho "Please move the above files and try again"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
''
|
|
||||||
function checkNewGenCollision() {
|
|
||||||
local newGenFiles
|
|
||||||
newGenFiles="$(readlink -e "$newGenPath/home-files")"
|
|
||||||
find "$newGenFiles" -type f -print0 -or -type l -print0 \
|
|
||||||
| xargs -0 bash ${check} "$newGenFiles"
|
|
||||||
}
|
|
||||||
|
|
||||||
checkNewGenCollision || exit 1
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
home.activation.linkGeneration = dagEntryAfter ["writeBoundary"] (
|
|
||||||
let
|
|
||||||
pattern = "-home-manager-files/";
|
|
||||||
|
|
||||||
link = pkgs.writeText "link" ''
|
|
||||||
newGenFiles="$1"
|
|
||||||
shift
|
|
||||||
for sourcePath in "$@" ; do
|
|
||||||
relativePath="''${sourcePath#$newGenFiles/}"
|
|
||||||
targetPath="$HOME/$relativePath"
|
|
||||||
$DRY_RUN_CMD mkdir -p $VERBOSE_ARG "$(dirname "$targetPath")"
|
|
||||||
$DRY_RUN_CMD ln -nsf $VERBOSE_ARG "$sourcePath" "$targetPath"
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
|
|
||||||
cleanup = pkgs.writeText "cleanup" ''
|
|
||||||
. ${./lib-bash/color-echo.sh}
|
|
||||||
|
|
||||||
newGenFiles="$1"
|
|
||||||
oldGenFiles="$2"
|
|
||||||
shift 2
|
|
||||||
for sourcePath in "$@" ; do
|
|
||||||
relativePath="''${sourcePath#$oldGenFiles/}"
|
|
||||||
targetPath="$HOME/$relativePath"
|
|
||||||
if [[ -e "$newGenFiles/$relativePath" ]] ; then
|
|
||||||
$VERBOSE_ECHO "Checking $targetPath: exists"
|
|
||||||
elif [[ ! "$(readlink "$targetPath")" =~ "${pattern}" ]] ; then
|
|
||||||
warnEcho "Path '$targetPath' not link into Home Manager generation. Skipping delete."
|
|
||||||
else
|
|
||||||
$VERBOSE_ECHO "Checking $targetPath: gone (deleting)"
|
|
||||||
$DRY_RUN_CMD rm $VERBOSE_ARG "$targetPath"
|
|
||||||
|
|
||||||
# Recursively delete empty parent directories.
|
|
||||||
targetDir="$(dirname "$relativePath")"
|
|
||||||
if [[ "$targetDir" != "." ]] ; then
|
|
||||||
pushd "$HOME" > /dev/null
|
|
||||||
|
|
||||||
# Call rmdir with a relative path excluding $HOME.
|
|
||||||
# Otherwise, it might try to delete $HOME and exit
|
|
||||||
# with a permission error.
|
|
||||||
$DRY_RUN_CMD rmdir $VERBOSE_ARG \
|
|
||||||
-p --ignore-fail-on-non-empty \
|
|
||||||
"$targetDir"
|
|
||||||
|
|
||||||
popd > /dev/null
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
''
|
|
||||||
function linkNewGen() {
|
|
||||||
local newGenFiles
|
|
||||||
newGenFiles="$(readlink -e "$newGenPath/home-files")"
|
|
||||||
find "$newGenFiles" -type f -print0 -or -type l -print0 \
|
|
||||||
| xargs -0 bash ${link} "$newGenFiles"
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanOldGen() {
|
|
||||||
if [[ ! -v oldGenPath ]] ; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Cleaning up orphan links from $HOME"
|
|
||||||
|
|
||||||
local newGenFiles oldGenFiles
|
|
||||||
newGenFiles="$(readlink -e "$newGenPath/home-files")"
|
|
||||||
oldGenFiles="$(readlink -e "$oldGenPath/home-files")"
|
|
||||||
find "$oldGenFiles" -type f -print0 -or -type l -print0 \
|
|
||||||
| xargs -0 bash ${cleanup} "$newGenFiles" "$oldGenFiles"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ ! -v oldGenPath || "$oldGenPath" != "$newGenPath" ]] ; then
|
|
||||||
echo "Creating profile generation $newGenNum"
|
|
||||||
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenProfilePath"
|
|
||||||
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG $(basename "$newGenProfilePath") "$genProfilePath"
|
|
||||||
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenGcPath"
|
|
||||||
else
|
|
||||||
echo "No change so reusing latest profile generation $oldGenNum"
|
|
||||||
fi
|
|
||||||
|
|
||||||
linkNewGen
|
|
||||||
cleanOldGen
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
home.activation.installPackages = dagEntryAfter ["writeBoundary"] ''
|
home.activation.installPackages = dagEntryAfter ["writeBoundary"] ''
|
||||||
$DRY_RUN_CMD nix-env -i ${cfg.path}
|
$DRY_RUN_CMD nix-env -i ${cfg.path}
|
||||||
'';
|
'';
|
||||||
|
@ -418,35 +232,6 @@ in
|
||||||
|
|
||||||
${activationCmds}
|
${activationCmds}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
home-files = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "home-manager-files";
|
|
||||||
|
|
||||||
phases = [ "installPhase" ];
|
|
||||||
|
|
||||||
installPhase =
|
|
||||||
"mkdir -p $out\n" +
|
|
||||||
concatStringsSep "\n" (
|
|
||||||
mapAttrsToList (n: v:
|
|
||||||
''
|
|
||||||
target="$(realpath -m "$out/${v.target}")"
|
|
||||||
|
|
||||||
# Target file must be within $HOME.
|
|
||||||
if [[ ! "$target" =~ "$out" ]] ; then
|
|
||||||
echo "Error installing file '${v.target}' outside \$HOME" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "${v.source}" ]; then
|
|
||||||
mkdir -pv "$(dirname "$out/${v.target}")"
|
|
||||||
ln -sv "${v.source}" "$target"
|
|
||||||
else
|
|
||||||
install -D -m${v.mode} "${v.source}" "$target"
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
) cfg.file
|
|
||||||
);
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
pkgs.stdenv.mkDerivation {
|
pkgs.stdenv.mkDerivation {
|
||||||
name = "home-manager-generation";
|
name = "home-manager-generation";
|
||||||
|
@ -459,7 +244,7 @@ in
|
||||||
substituteInPlace $out/activate \
|
substituteInPlace $out/activate \
|
||||||
--subst-var-by GENERATION_DIR $out
|
--subst-var-by GENERATION_DIR $out
|
||||||
|
|
||||||
ln -s ${home-files} $out/home-files
|
ln -s ${config.home-files} $out/home-files
|
||||||
ln -s ${cfg.path} $out/home-path
|
ln -s ${cfg.path} $out/home-path
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue