diff --git a/default.nix b/default.nix index 2988bbbf0..9ae182323 100644 --- a/default.nix +++ b/default.nix @@ -9,4 +9,6 @@ rec { install = import ./home-manager/install.nix { inherit home-manager pkgs; }; + + nixos = import ./nixos; } diff --git a/modules/home-environment.nix b/modules/home-environment.nix index 197e9dc57..6c98343d7 100644 --- a/modules/home-environment.nix +++ b/modules/home-environment.nix @@ -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} ''; diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 47efef6a1..0fffe2ea8 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -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. + ''; + } ]; }; } diff --git a/modules/modules.nix b/modules/modules.nix index c179c7325..7e9e6dae7 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -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; }; diff --git a/modules/programs/home-manager.nix b/modules/programs/home-manager.nix index 306b14a21..e9d48117d 100644 --- a/modules/programs/home-manager.nix +++ b/modules/programs/home-manager.nix @@ -32,7 +32,7 @@ in }; }; - config = mkIf cfg.enable { + config = mkIf (cfg.enable && !config.nixosSubmodule) { home.packages = [ (import ../../home-manager { inherit pkgs; diff --git a/modules/systemd-activate.nix b/modules/systemd-activate.sh similarity index 66% rename from modules/systemd-activate.nix rename to modules/systemd-activate.sh index 5e3b5773d..1c464693c 100644 --- a/modules/systemd-activate.nix +++ b/modules/systemd-activate.sh @@ -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 -'' diff --git a/modules/systemd.nix b/modules/systemd.nix index 3ee5ccc8e..9eaba7c22 100644 --- a/modules/systemd.nix +++ b/modules/systemd.nix @@ -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 ); }) ]; diff --git a/nixos/default.nix b/nixos/default.nix new file mode 100644 index 000000000..d72d8a195 --- /dev/null +++ b/nixos/default.nix @@ -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; + }; +}