diff --git a/modules/default.nix b/modules/default.nix
index 1b1d97a6..a8729947 100644
--- a/modules/default.nix
+++ b/modules/default.nix
@@ -12,6 +12,7 @@ let
modules = [
./home-environment.nix
+ ./files.nix
./manual.nix
./misc/fontconfig.nix
./misc/gtk.nix
diff --git a/modules/files.nix b/modules/files.nix
new file mode 100644
index 00000000..19def8b5
--- /dev/null
+++ b/modules/files.nix
@@ -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 HOME.
+ '';
+ };
+
+ 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.
+
+ 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
+ );
+ };
+ };
+}
diff --git a/modules/home-environment.nix b/modules/home-environment.nix
index 9b900ef0..2026e7d4 100644
--- a/modules/home-environment.nix
+++ b/modules/home-environment.nix
@@ -96,54 +96,6 @@ in
meta.maintainers = [ maintainers.rycee ];
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 HOME.
- '';
- };
-
- 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.
-
- 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 {
type = languageSubModule;
default = {};
@@ -226,20 +178,6 @@ in
};
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 =
let
maybeSet = name: value:
@@ -259,130 +197,6 @@ in
# script's "check" and the "write" phases.
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"] ''
$DRY_RUN_CMD nix-env -i ${cfg.path}
'';
@@ -418,35 +232,6 @@ in
${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
pkgs.stdenv.mkDerivation {
name = "home-manager-generation";
@@ -459,7 +244,7 @@ in
substituteInPlace $out/activate \
--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
'';
};