2017-01-07 18:16:26 +00:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
|
|
|
|
cfg = config.home;
|
|
|
|
|
|
|
|
languageSubModule = types.submodule {
|
|
|
|
options = {
|
|
|
|
base = mkOption {
|
2017-02-12 00:18:37 +00:00
|
|
|
default = null;
|
|
|
|
type = types.nullOr types.str;
|
2017-01-07 18:16:26 +00:00
|
|
|
description = ''
|
|
|
|
The language to use unless overridden by a more specific option.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
address = mkOption {
|
|
|
|
default = null;
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
description = ''
|
|
|
|
The language to use for addresses.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
monetary = mkOption {
|
|
|
|
default = null;
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
description = ''
|
|
|
|
The language to use for formatting currencies and money amounts.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
paper = mkOption {
|
|
|
|
default = null;
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
description = ''
|
|
|
|
The language to use for paper sizes.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
time = mkOption {
|
|
|
|
default = null;
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
description = ''
|
|
|
|
The language to use for formatting times.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
keyboardSubModule = types.submodule {
|
|
|
|
options = {
|
|
|
|
layout = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "us";
|
|
|
|
description = ''
|
|
|
|
Keyboard layout.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
model = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "pc104";
|
|
|
|
example = "presario";
|
|
|
|
description = ''
|
|
|
|
Keyboard model.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
options = mkOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = [];
|
|
|
|
example = ["grp:caps_toggle" "grp_led:scroll"];
|
|
|
|
description = ''
|
|
|
|
X keyboard options; layout switching goes here.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
variant = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "";
|
|
|
|
example = "colemak";
|
|
|
|
description = ''
|
|
|
|
X keyboard variant.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
2017-01-15 19:03:55 +00:00
|
|
|
description = ''
|
|
|
|
Path to target file relative to <envar>HOME</envar>.
|
|
|
|
'';
|
2017-01-07 18:16:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
text = mkOption {
|
|
|
|
default = null;
|
|
|
|
type = types.nullOr types.lines;
|
|
|
|
description = "Text of the file.";
|
|
|
|
};
|
|
|
|
|
|
|
|
source = mkOption {
|
|
|
|
type = types.path;
|
2017-02-21 20:41:05 +00:00
|
|
|
description = ''
|
2017-03-10 22:56:43 +00:00
|
|
|
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.
|
2017-02-21 20:41:05 +00:00
|
|
|
'';
|
2017-01-07 18:16:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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 = {};
|
|
|
|
description = "Language configuration.";
|
|
|
|
};
|
|
|
|
|
|
|
|
home.keyboard = mkOption {
|
|
|
|
type = keyboardSubModule;
|
|
|
|
default = {};
|
|
|
|
description = "Keyboard configuration.";
|
|
|
|
};
|
|
|
|
|
|
|
|
home.sessionVariables = mkOption {
|
|
|
|
default = {};
|
|
|
|
type = types.attrs;
|
2017-01-15 19:03:55 +00:00
|
|
|
example = { EDITOR = "emacs"; GS_OPTIONS = "-sPAPERSIZE=a4"; };
|
2017-01-16 22:54:45 +00:00
|
|
|
description = ''
|
|
|
|
Environment variables to always set at login.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
home.sessionVariableSetter = mkOption {
|
2017-01-17 00:13:31 +00:00
|
|
|
default = "bash";
|
2017-01-16 22:54:45 +00:00
|
|
|
type = types.enum [ "pam" "bash" ];
|
2017-01-17 00:13:31 +00:00
|
|
|
example = "pam";
|
2017-01-16 22:54:45 +00:00
|
|
|
description = ''
|
|
|
|
Identifies the module that should set the session variables.
|
|
|
|
</para><para>
|
|
|
|
If "bash" is set then <varname>config.bash.enable</varname>
|
|
|
|
must also be enabled.
|
|
|
|
</para><para>
|
|
|
|
If "pam" is set then PAM must be used to set the system
|
2017-01-17 00:13:31 +00:00
|
|
|
environment. Also mind that typical environment variables
|
|
|
|
might not be set by the time PAM starts up.
|
2017-01-16 22:54:45 +00:00
|
|
|
'';
|
2017-01-07 18:16:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
home.packages = mkOption {
|
|
|
|
type = types.listOf types.package;
|
|
|
|
default = [];
|
|
|
|
description = "The set of packages to appear in the user environment.";
|
|
|
|
};
|
|
|
|
|
|
|
|
home.path = mkOption {
|
|
|
|
internal = true;
|
|
|
|
description = "The derivation installing the user packages.";
|
|
|
|
};
|
|
|
|
|
|
|
|
home.activation = mkOption {
|
|
|
|
internal = true;
|
|
|
|
default = {};
|
|
|
|
type = types.attrs;
|
2017-01-15 23:06:27 +00:00
|
|
|
description = ''
|
|
|
|
Activation scripts for the home environment.
|
|
|
|
</para><para>
|
|
|
|
Any script should respect the <varname>DRY_RUN</varname>
|
|
|
|
variable, if it is set then no actual action should be taken.
|
|
|
|
The variable <varname>DRY_RUN_CMD</varname> is set to
|
|
|
|
<code>echo</code> if dry run is enabled. Thus, many cases you
|
|
|
|
can use the idiom <code>$DRY_RUN_CMD rm -rf /</code>.
|
|
|
|
'';
|
2017-01-07 18:16:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
home.activationPackage = mkOption {
|
|
|
|
internal = true;
|
|
|
|
type = types.package;
|
|
|
|
description = "The package containing the complete activation script.";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
config = {
|
2017-02-21 20:41:05 +00:00
|
|
|
assertions = [
|
|
|
|
(let
|
|
|
|
badFiles =
|
2017-02-21 20:55:07 +00:00
|
|
|
filter (f: hasPrefix "." (baseNameOf f))
|
|
|
|
(map (v: toString v.source)
|
2017-02-21 20:41:05 +00:00
|
|
|
(attrValues cfg.file));
|
|
|
|
badFilesStr = toString badFiles;
|
|
|
|
in
|
|
|
|
{
|
|
|
|
assertion = badFiles == [];
|
|
|
|
message = "Source file names must not start with '.': ${badFilesStr}";
|
|
|
|
})
|
|
|
|
];
|
|
|
|
|
2017-01-07 18:16:26 +00:00
|
|
|
home.sessionVariables =
|
|
|
|
let
|
|
|
|
maybeSet = name: value:
|
|
|
|
listToAttrs (optional (value != null) { inherit name value; });
|
|
|
|
in
|
|
|
|
(maybeSet "LANG" cfg.language.base)
|
|
|
|
//
|
|
|
|
(maybeSet "LC_ADDRESS" cfg.language.address)
|
|
|
|
//
|
|
|
|
(maybeSet "LC_MONETARY" cfg.language.monetary)
|
|
|
|
//
|
|
|
|
(maybeSet "LC_PAPER" cfg.language.paper)
|
|
|
|
//
|
|
|
|
(maybeSet "LC_TIME" cfg.language.time);
|
|
|
|
|
2017-01-16 19:26:42 +00:00
|
|
|
home.activation.linkGeneration =
|
2017-01-07 18:16:26 +00:00
|
|
|
let
|
|
|
|
link = pkgs.writeText "link" ''
|
2017-01-08 21:06:53 +00:00
|
|
|
newGenFiles="$1"
|
|
|
|
shift
|
2017-01-07 18:16:26 +00:00
|
|
|
for sourcePath in "$@" ; do
|
2017-03-10 22:56:43 +00:00
|
|
|
relativePath="''${sourcePath#$newGenFiles/}"
|
2017-01-08 21:06:53 +00:00
|
|
|
targetPath="$HOME/$relativePath"
|
2017-01-21 11:27:50 +00:00
|
|
|
$DRY_RUN_CMD mkdir -p $VERBOSE_ARG "$(dirname "$targetPath")"
|
2017-03-10 22:56:43 +00:00
|
|
|
$DRY_RUN_CMD ln -nsf $VERBOSE_ARG "$sourcePath" "$targetPath"
|
2017-01-07 18:16:26 +00:00
|
|
|
done
|
|
|
|
'';
|
|
|
|
|
|
|
|
cleanup = pkgs.writeText "cleanup" ''
|
2017-01-08 21:06:53 +00:00
|
|
|
newGenFiles="$1"
|
|
|
|
oldGenFiles="$2"
|
|
|
|
shift 2
|
2017-01-07 18:16:26 +00:00
|
|
|
for sourcePath in "$@" ; do
|
2017-03-10 22:56:43 +00:00
|
|
|
relativePath="''${sourcePath#$oldGenFiles/}"
|
2017-01-08 21:06:53 +00:00
|
|
|
targetPath="$HOME/$relativePath"
|
2017-03-10 22:56:43 +00:00
|
|
|
if [[ -e "$newGenFiles/$relativePath" ]] ; then
|
2017-02-09 21:32:09 +00:00
|
|
|
$VERBOSE_ECHO "Checking $targetPath exists"
|
2017-01-07 18:16:26 +00:00
|
|
|
else
|
2017-02-09 21:32:09 +00:00
|
|
|
echo "Checking $targetPath gone (deleting)"
|
2017-01-21 11:27:50 +00:00
|
|
|
$DRY_RUN_CMD rm $VERBOSE_ARG "$targetPath"
|
|
|
|
$DRY_RUN_CMD rmdir --ignore-fail-on-non-empty \
|
|
|
|
$VERBOSE_ARG -p "$(dirname "$targetPath")"
|
2017-01-07 18:16:26 +00:00
|
|
|
fi
|
|
|
|
done
|
|
|
|
'';
|
|
|
|
in
|
|
|
|
''
|
2017-01-08 21:06:53 +00:00
|
|
|
function linkNewGen() {
|
|
|
|
local newGenFiles
|
|
|
|
newGenFiles="$(readlink -e "$newGenPath/home-files")"
|
2017-03-10 22:56:43 +00:00
|
|
|
find "$newGenFiles" -type f -print0 -or -type l -print0 \
|
2017-01-08 21:06:53 +00:00
|
|
|
| xargs -0 bash ${link} "$newGenFiles"
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanOldGen() {
|
2017-03-28 22:10:30 +00:00
|
|
|
if [[ ! -v oldGenPath ]] ; then
|
2017-01-08 21:06:53 +00:00
|
|
|
return
|
2017-01-07 18:16:26 +00:00
|
|
|
fi
|
2017-01-08 21:06:53 +00:00
|
|
|
|
|
|
|
echo "Cleaning up orphan links from $HOME"
|
|
|
|
|
|
|
|
local newGenFiles oldGenFiles
|
|
|
|
newGenFiles="$(readlink -e "$newGenPath/home-files")"
|
|
|
|
oldGenFiles="$(readlink -e "$oldGenPath/home-files")"
|
2017-03-10 22:56:43 +00:00
|
|
|
find "$oldGenFiles" -type f -print0 -or -type l -print0 \
|
2017-01-08 21:06:53 +00:00
|
|
|
| xargs -0 bash ${cleanup} "$newGenFiles" "$oldGenFiles"
|
|
|
|
}
|
|
|
|
|
2017-03-28 22:10:30 +00:00
|
|
|
if [[ ! -v oldGenPath || "$oldGenPath" != "$newGenPath" ]] ; then
|
2017-01-21 11:27:50 +00:00
|
|
|
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenProfilePath"
|
2017-02-05 22:00:04 +00:00
|
|
|
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenProfilePath" "$genProfilePath"
|
2017-01-21 11:27:50 +00:00
|
|
|
$DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenGcPath"
|
2017-01-08 21:06:53 +00:00
|
|
|
linkNewGen
|
|
|
|
cleanOldGen
|
2017-01-07 18:16:26 +00:00
|
|
|
else
|
|
|
|
echo "Same home files as previous generation ... doing nothing"
|
|
|
|
fi
|
|
|
|
'';
|
|
|
|
|
|
|
|
home.activation.installPackages =
|
|
|
|
''
|
2017-02-05 10:51:33 +00:00
|
|
|
$DRY_RUN_CMD nix-env -i ${cfg.path}
|
2017-01-07 18:16:26 +00:00
|
|
|
'';
|
|
|
|
|
|
|
|
home.activationPackage =
|
|
|
|
let
|
|
|
|
addHeader = n: v:
|
|
|
|
v // {
|
|
|
|
text = ''
|
|
|
|
echo Activating ${n}
|
|
|
|
${v.text}
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
toDepString = n: v: if isString v then noDepEntry v else v;
|
|
|
|
activationWithDeps =
|
|
|
|
mapAttrs addHeader (mapAttrs toDepString cfg.activation);
|
|
|
|
activationCmds =
|
|
|
|
textClosureMap id activationWithDeps (attrNames activationWithDeps);
|
|
|
|
|
|
|
|
sf = pkgs.writeText "activation-script" ''
|
|
|
|
#!${pkgs.stdenv.shell}
|
|
|
|
|
2017-03-25 20:48:17 +00:00
|
|
|
set -eu
|
|
|
|
set -o pipefail
|
2017-01-16 19:26:42 +00:00
|
|
|
|
|
|
|
${builtins.readFile ./activation-init.sh}
|
2017-01-15 23:06:27 +00:00
|
|
|
|
2017-01-07 18:16:26 +00:00
|
|
|
${activationCmds}
|
|
|
|
'';
|
2017-01-08 21:06:53 +00:00
|
|
|
|
|
|
|
home-files = pkgs.stdenv.mkDerivation {
|
|
|
|
name = "home-manager-files";
|
|
|
|
|
|
|
|
phases = [ "installPhase" ];
|
|
|
|
|
|
|
|
installPhase =
|
2017-02-09 21:32:09 +00:00
|
|
|
"mkdir -p $out\n" +
|
2017-01-08 21:06:53 +00:00
|
|
|
concatStringsSep "\n" (
|
2017-03-10 22:56:43 +00:00
|
|
|
mapAttrsToList (n: v:
|
|
|
|
''
|
|
|
|
if [ -d "${v.source}" ]; then
|
|
|
|
mkdir -pv "$(dirname "$out/${v.target}")"
|
|
|
|
ln -sv "${v.source}" "$out/${v.target}"
|
|
|
|
else
|
|
|
|
install -D -m${v.mode} "${v.source}" "$out/${v.target}"
|
|
|
|
fi
|
|
|
|
''
|
2017-01-08 21:06:53 +00:00
|
|
|
) cfg.file
|
|
|
|
);
|
|
|
|
};
|
2017-01-07 18:16:26 +00:00
|
|
|
in
|
|
|
|
pkgs.stdenv.mkDerivation {
|
2017-01-08 21:06:53 +00:00
|
|
|
name = "home-manager-generation";
|
2017-01-07 18:16:26 +00:00
|
|
|
|
|
|
|
phases = [ "installPhase" ];
|
|
|
|
|
|
|
|
installPhase = ''
|
2017-02-09 21:32:09 +00:00
|
|
|
install -D -m755 ${sf} $out/activate
|
2017-01-08 21:06:53 +00:00
|
|
|
|
|
|
|
substituteInPlace $out/activate \
|
|
|
|
--subst-var-by GENERATION_DIR $out
|
|
|
|
|
2017-02-09 21:32:09 +00:00
|
|
|
ln -s ${home-files} $out/home-files
|
2017-01-07 18:16:26 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
home.path = pkgs.buildEnv {
|
|
|
|
name = "home-manager-path";
|
|
|
|
|
|
|
|
paths = cfg.packages;
|
|
|
|
|
|
|
|
meta = {
|
|
|
|
description = "Environment of packages installed through home-manager";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|