From 676f5c4b3117be7dbe24cc0beacc070b982decfb Mon Sep 17 00:00:00 2001 From: Cornelius Mika Date: Mon, 6 Nov 2017 10:28:42 +0100 Subject: [PATCH 1/8] files: allow arbitrary paths as home file names By sanitizing the home file name in the derivation name, the home file name is no longer exposed to the naming restrictions for nix store paths. For example, it is now possible to define home files with spaces in their names without providing a target or source attribute. --- modules/files.nix | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/modules/files.nix b/modules/files.nix index e294f97f0..71b940d20 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -6,8 +6,31 @@ with import ./lib/dag.nix { inherit lib; }; let cfg = config.home.file; + homeDirectory = config.home.homeDirectory; + # Figures out a valid Nix store name for the given path. + storeFileName = path: + let + # All characters that are considered safe. Note "-" is not + # included to avoid "-" followed by digit being interpreted as a + # version. + safeChars = + [ "+" "." "_" "?" "=" ] + ++ lowerChars + ++ upperChars + ++ stringToCharacters "0123456789"; + + empties = l: genList (x: "") (length l); + + unsafeInName = stringToCharacters ( + replaceStrings safeChars (empties safeChars) path + ); + + safeName = replaceStrings unsafeInName (empties unsafeInName) path; + in + "home_file_" + safeName; + in { @@ -53,8 +76,7 @@ in config = { target = mkDefault name; source = mkIf (config.text != null) ( - let name' = "user-etc-" + baseNameOf name; - in mkDefault (pkgs.writeText name' config.text) + mkDefault (pkgs.writeText (storeFileName name) config.text) ); }; }) From ccb291ce66ccecb9532015860c383c0e7ebe33ac Mon Sep 17 00:00:00 2001 From: Cornelius Mika Date: Mon, 6 Nov 2017 10:28:44 +0100 Subject: [PATCH 2/8] files: add option 'executable' This also deprecates the `home.file..mode` option, which is misleading because the Nix store only allows modes 'r--' and 'r-x'. --- modules/files.nix | 51 ++++++++++++++++++++++++++++++++++++++----- modules/misc/news.nix | 17 +++++++++++++++ modules/misc/xdg.nix | 8 +------ modules/xsession.nix | 2 +- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/modules/files.nix b/modules/files.nix index 71b940d20..011f685e3 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -67,16 +67,34 @@ in }; mode = mkOption { - type = types.str; - default = "444"; - description = "The permissions to apply to the file."; + type = types.nullOr types.str; + default = null; + description = '' + The permissions to apply to the file. + + DEPRECATED: use home.file.<name?>.executable + instead. + ''; + }; + + executable = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Set the execute bit. If null, defaults to the mode + of the source file or to false + for files created through the text option. + ''; }; }; config = { target = mkDefault name; source = mkIf (config.text != null) ( - mkDefault (pkgs.writeText (storeFileName name) config.text) + mkDefault (pkgs.writeTextFile { + inherit (config) executable text; + name = storeFileName name; + }) ); }; }) @@ -105,6 +123,19 @@ in }) ]; + warnings = + let + badFiles = + map (f: f.target) + (filter (f: f.mode != null) + (attrValues cfg)); + badFilesStr = toString badFiles; + in + mkIf (badFiles != []) [ + ("The 'mode' field is deprecated for 'home.file', " + + "use 'executable' instead: ${badFilesStr}") + ]; + # This verifies that the links we are about to create will not # overwrite an existing file. home.activation.checkLinkTargets = dagEntryBefore ["writeBoundary"] ( @@ -238,7 +269,15 @@ in "mkdir -p $out\n" + concatStringsSep "\n" ( mapAttrsToList (n: v: - '' + let + mode = + if v.mode != null + then v.mode + else + if v.executable != null + then (if v.executable then "+x" else "-x") + else "+r"; # Acts as a no-op. + in '' target="$(realpath -m "$out/${v.target}")" # Target file must be within $HOME. @@ -251,7 +290,7 @@ in mkdir -p "$(dirname "$out/${v.target}")" ln -s "${v.source}" "$target" else - install -D -m${v.mode} "${v.source}" "$target" + install -D -m${mode} "${v.source}" "$target" fi '' ) cfg diff --git a/modules/misc/news.nix b/modules/misc/news.nix index ac969f160..44b9d7f59 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -425,6 +425,23 @@ in A new window manager module is available: 'xsession.windowManager.i3'. ''; } + + { + time = "2017-11-06T13:23:17+00:00"; + condition = any (f: f.mode != null) (attrValues config.home.file); + message = '' + The + + home.file..mode + + option is now deprecated. Please use + + home.file..executable + + instead. The 'mode' option will be completely removed + December 6, 2017. + ''; + } ]; }; } diff --git a/modules/misc/xdg.nix b/modules/misc/xdg.nix index 8dcf1fba4..7dd5f456d 100644 --- a/modules/misc/xdg.nix +++ b/modules/misc/xdg.nix @@ -126,13 +126,7 @@ in }) { - home.file = - let - f = n: v: { - inherit (v) source target; - mode = if v.executable then "777" else "444"; - }; - in mapAttrsToList f cfg.configFile; + home.file = cfg.configFile; } ]; } diff --git a/modules/xsession.nix b/modules/xsession.nix index d51bb6b48..9f06e76be 100644 --- a/modules/xsession.nix +++ b/modules/xsession.nix @@ -105,7 +105,7 @@ in ''; home.file.".xsession" = { - mode = "555"; + executable = true; text = '' if [[ ! -v HM_XPROFILE_SOURCED ]]; then . ~/.xprofile From 811bc1b8e514e46da7de5ab250c94b9a31657157 Mon Sep 17 00:00:00 2001 From: Cornelius Mika Date: Mon, 6 Nov 2017 10:28:47 +0100 Subject: [PATCH 3/8] files: extract common variable Also improve the pattern used to determine whether a symlink target points to a Home Manager file path. --- modules/files.nix | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/files.nix b/modules/files.nix index 011f685e3..8871aa1a2 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -31,6 +31,10 @@ let in "home_file_" + safeName; + # A symbolic link whose target path matches this pattern will be + # considered part of a Home Manager generation. + homeFilePattern = "${builtins.storeDir}/*-home-manager-files/*"; + in { @@ -140,7 +144,6 @@ in # overwrite an existing file. home.activation.checkLinkTargets = dagEntryBefore ["writeBoundary"] ( let - pattern = "-home-manager-files/"; check = pkgs.writeText "check" '' . ${./lib-bash/color-echo.sh} @@ -150,7 +153,7 @@ in relativePath="''${sourcePath#$newGenFiles/}" targetPath="$HOME/$relativePath" if [[ -e "$targetPath" \ - && ! "$(readlink "$targetPath")" =~ "${pattern}" ]] ; then + && ! "$(readlink "$targetPath")" == ${homeFilePattern} ]] ; then errorEcho "Existing file '$targetPath' is in the way" collision=1 fi @@ -176,8 +179,6 @@ in home.activation.linkGeneration = dagEntryAfter ["writeBoundary"] ( let - pattern = "-home-manager-files/"; - link = pkgs.writeText "link" '' newGenFiles="$1" shift @@ -200,7 +201,7 @@ in targetPath="$HOME/$relativePath" if [[ -e "$newGenFiles/$relativePath" ]] ; then $VERBOSE_ECHO "Checking $targetPath: exists" - elif [[ ! "$(readlink "$targetPath")" =~ "${pattern}" ]] ; then + elif [[ ! "$(readlink "$targetPath")" == ${homeFilePattern} ]] ; then warnEcho "Path '$targetPath' not link into Home Manager generation. Skipping delete." else $VERBOSE_ECHO "Checking $targetPath: gone (deleting)" From f04cc227a6b41c7d9775d5b9749dc8a99c844dc3 Mon Sep 17 00:00:00 2001 From: Cornelius Mika Date: Mon, 6 Nov 2017 10:28:48 +0100 Subject: [PATCH 4/8] home-environment: clean up activation script creation --- modules/home-environment.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/home-environment.nix b/modules/home-environment.nix index 892c20cc8..932f33f00 100644 --- a/modules/home-environment.nix +++ b/modules/home-environment.nix @@ -265,7 +265,7 @@ in pkgs.nix ]; - sf = pkgs.writeText "activation-script" '' + activationScript = pkgs.writeScript "activation-script" '' #!${pkgs.stdenv.shell} set -eu @@ -286,7 +286,9 @@ in phases = [ "installPhase" ]; installPhase = '' - install -D -m755 ${sf} $out/activate + mkdir -p $out + + cp ${activationScript} $out/activate substituteInPlace $out/activate \ --subst-var-by GENERATION_DIR $out From b8ddb1179662624652eefdeab5cdcaf3f352a248 Mon Sep 17 00:00:00 2001 From: Cornelius Mika Date: Mon, 6 Nov 2017 10:28:49 +0100 Subject: [PATCH 5/8] use `buildCommand` for single phase builds --- home-manager/default.nix | 4 +--- modules/files.nix | 4 +--- modules/home-environment.nix | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/home-manager/default.nix b/home-manager/default.nix index 943d61e81..394f0e7f2 100644 --- a/home-manager/default.nix +++ b/home-manager/default.nix @@ -15,9 +15,7 @@ in pkgs.stdenv.mkDerivation { name = "home-manager"; - phases = [ "installPhase" ]; - - installPhase = '' + buildCommand = '' install -v -D -m755 ${./home-manager} $out/bin/home-manager substituteInPlace $out/bin/home-manager \ diff --git a/modules/files.nix b/modules/files.nix index 8871aa1a2..11d0694b5 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -264,9 +264,7 @@ in home-files = pkgs.stdenv.mkDerivation { name = "home-manager-files"; - phases = [ "installPhase" ]; - - installPhase = + buildCommand = "mkdir -p $out\n" + concatStringsSep "\n" ( mapAttrsToList (n: v: diff --git a/modules/home-environment.nix b/modules/home-environment.nix index 932f33f00..323294a37 100644 --- a/modules/home-environment.nix +++ b/modules/home-environment.nix @@ -283,9 +283,7 @@ in pkgs.stdenv.mkDerivation { name = "home-manager-generation"; - phases = [ "installPhase" ]; - - installPhase = '' + buildCommand = '' mkdir -p $out cp ${activationScript} $out/activate From 9627fe6be63ad29f642d93218b152806bfef2c41 Mon Sep 17 00:00:00 2001 From: Cornelius Mika Date: Mon, 6 Nov 2017 10:28:50 +0100 Subject: [PATCH 6/8] files: link home files instead of copying Only copy files that need their execute bit changed or use the deprecated `mode` option. --- modules/files.nix | 73 ++++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/modules/files.nix b/modules/files.nix index 11d0694b5..66d715100 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -264,36 +264,57 @@ in home-files = pkgs.stdenv.mkDerivation { name = "home-manager-files"; - buildCommand = - "mkdir -p $out\n" + - concatStringsSep "\n" ( - mapAttrsToList (n: v: - let - mode = - if v.mode != null - then v.mode - else - if v.executable != null - then (if v.executable then "+x" else "-x") - else "+r"; # Acts as a no-op. - in '' - target="$(realpath -m "$out/${v.target}")" + # Symlink directories and files that have the right execute bit. + # Copy files that need their execute bit changed or use the + # deprecated 'mode' option. + buildCommand = '' + mkdir -p $out - # Target file must be within $HOME. - if [[ ! "$target" =~ "$out" ]] ; then - echo "Error installing file '${v.target}' outside \$HOME" >&2 - exit 1 - fi + function insertFile() { + local source="$1" + local relTarget="$2" + local executable="$3" + local mode="$4" # For backwards compatibility. - if [ -d "${v.source}" ]; then - mkdir -p "$(dirname "$out/${v.target}")" - ln -s "${v.source}" "$target" + # Figure out the real absolute path to the target. + local target + target="$(realpath -m "$out/$relTarget")" + + # Target path must be within $HOME. + if [[ ! $target =~ $out ]] ; then + echo "Error installing file '$relTarget' outside \$HOME" >&2 + exit 1 + fi + + mkdir -p "$(dirname "$target")" + if [[ -d $source ]]; then + ln -s "$source" "$target" + elif [[ $mode ]]; then + install -m "$mode" "$source" "$target" + else + [[ -x $source ]] && isExecutable=1 || isExecutable="" + if [[ $executable == symlink || $isExecutable == $executable ]]; then + ln -s "$source" "$target" + else + cp "$source" "$target" + if [[ $executable ]]; then + chmod +x "$target" else - install -D -m${mode} "${v.source}" "$target" + chmod -x "$target" fi - '' - ) cfg - ); + fi + fi + } + '' + concatStrings ( + mapAttrsToList (n: v: '' + insertFile "${v.source}" \ + "${v.target}" \ + "${if v.executable == null + then "symlink" + else builtins.toString v.executable}" \ + "${builtins.toString v.mode}" + '') cfg + ); }; }; } From 4f842d9f1b137d98ad4e6393b16b038bbab2bad2 Mon Sep 17 00:00:00 2001 From: Robert Helgesson Date: Mon, 6 Nov 2017 10:28:54 +0100 Subject: [PATCH 7/8] files: extract type of `home.file` into own file --- modules/files.nix | 86 ++----------------------------- modules/lib/file-type.nix | 105 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 82 deletions(-) create mode 100644 modules/lib/file-type.nix diff --git a/modules/files.nix b/modules/files.nix index 66d715100..c61560c5b 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -9,27 +9,9 @@ let homeDirectory = config.home.homeDirectory; - # Figures out a valid Nix store name for the given path. - storeFileName = path: - let - # All characters that are considered safe. Note "-" is not - # included to avoid "-" followed by digit being interpreted as a - # version. - safeChars = - [ "+" "." "_" "?" "=" ] - ++ lowerChars - ++ upperChars - ++ stringToCharacters "0123456789"; - - empties = l: genList (x: "") (length l); - - unsafeInName = stringToCharacters ( - replaceStrings safeChars (empties safeChars) path - ); - - safeName = replaceStrings unsafeInName (empties unsafeInName) path; - in - "home_file_" + safeName; + fileType = (import lib/file-type.nix { + inherit homeDirectory lib pkgs; + }).fileType; # A symbolic link whose target path matches this pattern will be # considered part of a Home Manager generation. @@ -42,67 +24,7 @@ in 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; - apply = removePrefix (homeDirectory + "/"); - 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.nullOr types.str; - default = null; - description = '' - The permissions to apply to the file. - - DEPRECATED: use home.file.<name?>.executable - instead. - ''; - }; - - executable = mkOption { - type = types.nullOr types.bool; - default = null; - description = '' - Set the execute bit. If null, defaults to the mode - of the source file or to false - for files created through the text option. - ''; - }; - }; - - config = { - target = mkDefault name; - source = mkIf (config.text != null) ( - mkDefault (pkgs.writeTextFile { - inherit (config) executable text; - name = storeFileName name; - }) - ); - }; - }) - ); + type = fileType "HOME" homeDirectory; }; home-files = mkOption { diff --git a/modules/lib/file-type.nix b/modules/lib/file-type.nix new file mode 100644 index 000000000..ebdcb7741 --- /dev/null +++ b/modules/lib/file-type.nix @@ -0,0 +1,105 @@ +{ homeDirectory, lib, pkgs }: + +with lib; + +let + + # Figures out a valid Nix store name for the given path. + storeFileName = path: + let + # All characters that are considered safe. Note "-" is not + # included to avoid "-" followed by digit being interpreted as a + # version. + safeChars = + [ "+" "." "_" "?" "=" ] + ++ lowerChars + ++ upperChars + ++ stringToCharacters "0123456789"; + + empties = l: genList (x: "") (length l); + + unsafeInName = stringToCharacters ( + replaceStrings safeChars (empties safeChars) path + ); + + safeName = replaceStrings unsafeInName (empties unsafeInName) path; + in + "home_file_" + safeName; + +in + +{ + # Constructs a type suitable for a `home.file` like option. The + # target path may be either absolute or relative, in which case it + # is relative the `basePath` argument (which itself must be an + # absolute path). + # + # Arguments: + # - basePathDesc docbook compatible description of the base path + # - basePath the file base path + fileType = basePathDesc: basePath: types.loaOf (types.submodule ( + { name, config, ... }: { + options = { + target = mkOption { + type = types.str; + apply = p: + let + absPath = if hasPrefix "/" p then p else "${basePath}/${p}"; + in + removePrefix (homeDirectory + "/") absPath; + description = '' + Path to target file relative to ${basePathDesc}. + ''; + }; + + 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.nullOr types.str; + default = null; + description = '' + The permissions to apply to the file. + + DEPRECATED: use home.file.<name?>.executable + instead. + ''; + }; + + executable = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Set the execute bit. If null, defaults to the mode + of the source file or to false + for files created through the text option. + ''; + }; + }; + + config = { + target = mkDefault name; + source = mkIf (config.text != null) ( + mkDefault (pkgs.writeTextFile { + inherit (config) executable text; + name = storeFileName name; + }) + ); + }; + } + )); +} From 549deb51d6f4ac9aa8fccb16a560ad4fcef2d6bc Mon Sep 17 00:00:00 2001 From: Robert Helgesson Date: Mon, 6 Nov 2017 10:28:55 +0100 Subject: [PATCH 8/8] xdg: use `fileType` for `xdg.configFile` --- modules/misc/xdg.nix | 55 ++++---------------------------------------- 1 file changed, 5 insertions(+), 50 deletions(-) diff --git a/modules/misc/xdg.nix b/modules/misc/xdg.nix index 7dd5f456d..8f47ac5dc 100644 --- a/modules/misc/xdg.nix +++ b/modules/misc/xdg.nix @@ -6,55 +6,10 @@ let cfg = config.xdg; - fileType = basePathDesc: basePath: (types.loaOf (types.submodule ( - { name, config, ... }: { - options = { - target = mkOption { - type = types.str; - apply = p: "${basePath}/${p}"; - description = '' - Path to target file relative to ${basePathDesc}. - ''; - }; - - 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. - ''; - }; - - executable = mkOption { - type = types.bool; - default = false; - description = "Whether the file should be executable."; - }; - }; - - config = { - target = mkDefault name; - source = mkIf (config.text != null) ( - let - file = pkgs.writeTextFile { - inherit (config) text executable; - name = "user-etc-" + baseNameOf name; - }; - in - mkDefault file - ); - }; - } - ))); + fileType = (import ../lib/file-type.nix { + inherit (config.home) homeDirectory; + inherit lib pkgs; + }).fileType; defaultCacheHome = "${config.home.homeDirectory}/.cache"; defaultConfigHome = "${config.home.homeDirectory}/.config"; @@ -81,7 +36,7 @@ in }; configFile = mkOption { - type = fileType "xdg.configHome" cfg.configHome; + type = fileType "xdg.configHome" cfg.configHome; default = {}; description = '' Attribute set of files to link into the user's XDG