allow Home Manager to be used as a NixOS module

This is a NixOS module that is intended to be imported into a NixOS
system configuration. It allows the system users to be set up directly
from the system configuration.

The actual profile switch is performed by a oneshot systemd unit per
configured user that acts much like the regular `home-manager switch`
command.

With this implementation, the NixOS module does not work properly with
the `nixos-rebuild build-vm` command. This can be solved by using the
`users.users.<name?>.packages` option to install packages but this
does not work flawlessly with certain Nixpkgs packages. In particular,
for programs using the Qt libraries.
This commit is contained in:
Robert Helgesson 2017-10-14 20:56:02 +02:00
parent 563a20fc82
commit 1bc59f7290
No known key found for this signature in database
GPG key ID: C3DB11069E65DC86
8 changed files with 161 additions and 27 deletions

View file

@ -9,4 +9,6 @@ rec {
install = import ./home-manager/install.nix {
inherit home-manager pkgs;
};
nixos = import ./nixos;
}

View file

@ -291,6 +291,7 @@ in
# script's "check" and the "write" phases.
home.activation.writeBoundary = dag.entryAnywhere "";
# Install packages to the user environment.
home.activation.installPackages = dag.entryAfter ["writeBoundary"] ''
$DRY_RUN_CMD nix-env -i ${cfg.path}
'';

View file

@ -568,6 +568,52 @@ in
GTK configurations.
'';
}
{
time = "2018-02-06T20:23:34+00:00";
message = ''
It is now possible to use Home Manager as a NixOS module.
This allows you to prepare user environments from the system
configuration file, which often is more convenient than
using the 'home-manager' tool. It also opens up additional
possibilities, for example, to automatically configure user
environments in NixOS declarative containers or on systems
deployed through NixOps.
This feature should be considered experimental for now and
some critial limitations apply. For example, it is currently
not possible to use 'nixos-rebuild build-vm' when using the
Home Manager NixOS module. That said, it should be
reasonably robust and stable for simpler use cases.
To make Home Manager available in your NixOS system
configuration you can add
imports = [
"''${builtins.fetchTarball https://github.com/rycee/home-manager/archive/master.tar.gz}/nixos"
];
to your 'configuration.nix' file. This will introduce a new
NixOS option called 'home-manager.users' whose type is an
attribute set mapping user names to Home Manager
configurations.
For example, a NixOS configuration may include the lines
users.users.eve.isNormalUser = true;
home-manager.users.eve = {
home.packages = [ pkgs.atool pkgs.httpie ];
programs.bash.enable = true;
};
and after a 'nixos-rebuild switch' the user eve's
environment should include a basic Bash configuration and
the packages atool and httpie.
More detailed documentation on the intricacies of this new
feature is slowly forthcoming.
'';
}
];
};
}

View file

@ -3,6 +3,9 @@
# Whether to enable module type checking.
, check ? true
# Whether these modules are inside a NixOS submodule.
, nixosSubmodule ? false
}:
with lib;
@ -75,10 +78,17 @@ let
];
pkgsModule = {
options.nixosSubmodule = mkOption {
type = types.bool;
internal = true;
readOnly = true;
};
config._module.args.baseModules = modules;
config._module.args.pkgs = lib.mkDefault pkgs;
config._module.check = check;
config.lib = import ./lib { inherit lib; };
config.nixosSubmodule = nixosSubmodule;
config.nixpkgs.system = mkDefault pkgs.system;
};

View file

@ -32,7 +32,7 @@ in
};
};
config = mkIf cfg.enable {
config = mkIf (cfg.enable && !config.nixosSubmodule) {
home.packages = [
(import ../../home-manager {
inherit pkgs;

View file

@ -1,14 +1,14 @@
systemctlPath:
''
#!/usr/bin/env bash
function isStartable() {
local service="$1"
[[ $(${systemctlPath} --user show -p RefuseManualStart "$service") == *=no ]]
[[ $(systemctl --user show -p RefuseManualStart "$service") == *=no ]]
}
function isStoppable() {
if [[ -v oldGenPath ]] ; then
local service="$1"
[[ $(${systemctlPath} --user show -p RefuseManualStop "$service") == *=no ]]
[[ $(systemctl --user show -p RefuseManualStop "$service") == *=no ]]
fi
}
@ -53,19 +53,19 @@ function systemdPostReload() {
--old-line-format='-%L' \
--unchanged-line-format=' %L' \
"$oldServiceFiles" "$newServiceFiles" \
> $servicesDiffFile || true
> "$servicesDiffFile" || true
local -a maybeRestart=( $(grep '^ ' $servicesDiffFile | cut -c2-) )
local -a maybeStop=( $(grep '^-' $servicesDiffFile | cut -c2-) )
local -a maybeStart=( $(grep '^+' $servicesDiffFile | cut -c2-) )
local -a maybeRestart=( $(grep '^ ' "$servicesDiffFile" | cut -c2-) )
local -a maybeStop=( $(grep '^-' "$servicesDiffFile" | cut -c2-) )
local -a maybeStart=( $(grep '^+' "$servicesDiffFile" | cut -c2-) )
local -a toRestart=( )
local -a toStop=( )
local -a toStart=( )
for f in ''${maybeRestart[@]} ; do
for f in "${maybeRestart[@]}" ; do
if isStoppable "$f" \
&& isStartable "$f" \
&& ${systemctlPath} --quiet --user is-active "$f" \
&& systemctl --quiet --user is-active "$f" \
&& ! cmp --quiet \
"$oldUserServicePath/$f" \
"$newUserServicePath/$f" ; then
@ -73,32 +73,32 @@ function systemdPostReload() {
fi
done
for f in ''${maybeStop[@]} ; do
for f in "${maybeStop[@]}" ; do
if isStoppable "$f" ; then
toStop+=("$f")
fi
done
for f in ''${maybeStart[@]} ; do
for f in "${maybeStart[@]}" ; do
if isStartable "$f" ; then
toStart+=("$f")
fi
done
rm -r $workDir
rm -r "$workDir"
local sugg=""
if [[ -n "''${toRestart[@]}" ]] ; then
sugg="''${sugg}systemctl --user restart ''${toRestart[@]}\n"
if [[ -n "${toRestart[@]}" ]] ; then
sugg="${sugg}systemctl --user restart ${toRestart[@]}\n"
fi
if [[ -n "''${toStop[@]}" ]] ; then
sugg="''${sugg}systemctl --user stop ''${toStop[@]}\n"
if [[ -n "${toStop[@]}" ]] ; then
sugg="${sugg}systemctl --user stop ${toStop[@]}\n"
fi
if [[ -n "''${toStart[@]}" ]] ; then
sugg="''${sugg}systemctl --user start ''${toStart[@]}\n"
if [[ -n "${toStart[@]}" ]] ; then
sugg="${sugg}systemctl --user start ${toStart[@]}\n"
fi
if [[ -n "$sugg" ]] ; then
@ -107,6 +107,8 @@ function systemdPostReload() {
fi
}
$DRY_RUN_CMD ${systemctlPath} --user daemon-reload
oldGenPath="$1"
newGenPath="$2"
$DRY_RUN_CMD systemctl --user daemon-reload
systemdPostReload
''

View file

@ -145,14 +145,30 @@ in
(buildServices "timer" cfg.timers)
);
# Run systemd service reload if user is logged in. If we're
# running this from the NixOS module then XDG_RUNTIME_DIR is not
# set and systemd commands will fail. We'll therefore have to
# set it ourselves in that case.
home.activation.reloadSystemD = dag.entryAfter ["linkGeneration"] (
if cfg.startServices then
let
autoReloadCmd = ''
${pkgs.ruby}/bin/ruby ${./systemd-activate.rb} \
"''${oldGenPath=}" "$newGenPath" "${servicesStartTimeoutMs}"
'';
legacyReloadCmd = ''
bash ${./systemd-activate.sh} "''${oldGenPath=}" "$newGenPath"
'';
in
''
PATH=${dirOf cfg.systemctlPath} \
${pkgs.ruby}/bin/ruby ${./systemd-activate.rb} \
"''${oldGenPath=}" "$newGenPath" "${servicesStartTimeoutMs}"
if who | grep -q '^${config.home.username} '; then
XDG_RUNTIME_DIR=''${XDG_RUNTIME_DIR:-/run/user/$(id -u)} \
PATH=${dirOf cfg.systemctlPath}:$PATH \
${if cfg.startServices then autoReloadCmd else legacyReloadCmd}
else
echo "User ${config.home.username} not logged in. Skipping."
fi
''
else import ./systemd-activate.nix cfg.systemctlPath
);
})
];

57
nixos/default.nix Normal file
View file

@ -0,0 +1,57 @@
{ config, lib, pkgs, utils, ... }:
with lib;
let
cfg = config.home-manager;
hmModule = types.submodule ({name, ...}: {
imports = import ../modules/modules.nix {
inherit lib pkgs;
nixosSubmodule = true;
};
config = {
home.username = config.users.users.${name}.name;
home.homeDirectory = config.users.users.${name}.home;
};
});
in
{
options = {
home-manager.users = mkOption {
type = types.attrsOf hmModule;
default = {};
description = ''
Per-user Home Manager configuration.
'';
};
};
config = mkIf (cfg.users != {}) {
systemd.services = mapAttrs' (username: usercfg:
nameValuePair ("home-manager-${utils.escapeSystemdPath username}") {
description = "Home Manager environment for ${username}";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = username;
Type = "oneshot";
RemainAfterExit = "yes";
SyslogIdentifier = "hm-activate-${username}";
# The activation script is run by a login shell to make sure
# that the user is given a sane Nix environment.
ExecStart = pkgs.writeScript "activate-${username}" ''
#! ${pkgs.stdenv.shell} -el
echo Activating home-manager configuration for ${username}
exec ${usercfg.home.activationPackage}/activate
'';
};
}
) cfg.users;
};
}