Skip to content

System and home in one feature

A feature such as a desktop often has two parts: system configuration, and home-manager configuration for each user. The usual answers each have a cost:

  • home-manager.sharedModules in the NixOS module. It does not work for standalone home-manager, and a module that is imported from two features is added two times.
  • extraSpecialArgs to give home-manager the values that NixOS gets from specialArgs. You write them two times, and standalone home-manager needs a third copy.
  • osConfig ? null and lib.mkIf (osConfig != null), because osConfig does not exist in standalone mode.
  • lib.mkIf pkgs.stdenv.isLinux to send a package list to the system on one machine and to the home on another. A mkIf around imports causes infinite recursion.

In yen, the feature is aspects. at says where each part goes. A user’s home-manager content goes into each NixOS host that the user lives on. A standalone home builds its own homeConfigurations output.

modules/fleet.nix
# NixOS hosts hold users. A home is a standalone home-manager configuration, for a machine without NixOS.
{
yen,
inputs,
...
}:
{
yen.kinds.fleet = {
spawns = {
host = _: yen.host;
home = _: yen.home;
};
sinks.out.reduce = yen.lib.reduce.merge;
};
yen.kinds.host = {
spawns.user = yen.lib.pointing "hosts" yen.user;
sinks.nixos.out =
{ host, nixos, ... }:
{
nixosConfigurations.${host.name} = inputs.nixpkgs.lib.nixosSystem { modules = nixos; };
};
};
yen.kinds.user = {
fields.hosts = yen.lib.listOf yen.kinds.host;
# a user's home-manager content goes into each NixOS host it lives on
sinks.homeManager.nixos = # (1)
{ user, homeManager, ... }:
{
home-manager.users.${user.name}.imports = homeManager;
};
};
yen.kinds.home = {
fields = {
user = yen.kinds.user;
system = yen.lib.str;
};
sinks.homeManager.out = # (2)
{ home, homeManager, ... }:
{
homeConfigurations.${home.name} = inputs.home-manager.lib.homeManagerConfiguration {
pkgs = inputs.nixpkgs.legacyPackages.${home.system};
modules = homeManager;
};
};
};
yen.aspects.home-manager = {
at = yen.lib.below yen.kinds.user;
nixos = {
imports = [ inputs.home-manager.nixosModules.home-manager ];
home-manager.useGlobalPkgs = true;
};
};
yen.fleet.main = { };
yen.flake = { out, ... }: out;
}

In fleet.nix, (1) is the homeManager sink of a user: it forwards the content to the nixos sink of each host the user lives on (forwards). (2) is the homeManager sink of a standalone home: it builds the homeConfigurations output. In machines.nix, (1) is the home ana@work of the user ana, and (2) is the aspect that every home gets.

This is the multi context aspect of the dendritic design guide. The home part is not a private module that the system part imports. It is an aspect at the users of every host that has the desktop. Two features that both need it do not import it two times: an aspect is given once per scope.

modules/gnome.nix
# One feature, two levels. The desktop is a system concern; its settings are a home concern of each user on it.
{ yen, ... }:
let
inherit (yen.lib) all within;
in
{
yen.aspects.gnome = {
at = yen.host.desk; # (1)
nixos.services.xserver.desktopManager.gnome.enable = true;
};
# no home-manager.sharedModules: the users of a host that has gnome get this
yen.aspects.gnome-home = {
at = all [
yen.kinds.user
(within yen.aspects.gnome) # (2)
];
homeManager.dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark";
};
}

(1) is the system part: it is active at the host desk. (2) makes the home part active at every user within a host where gnome is active (predicates).

The same concern, a different level per machine

Section titled “The same concern, a different level per machine”

On NixOS, fonts are system packages. On the work laptop there is no NixOS, so they go into the home. The two aspects have different at: (1) is every host, and (2) is every home. No module looks at pkgs.stdenv to find out where it is.

modules/fonts.nix
# Fonts are a system concern on NixOS, and a home concern where there is no NixOS. `at` decides, not mkIf.
{ yen, ... }:
{
yen.aspects.fonts-system = {
at = yen.kinds.host; # (1)
nixos =
{ pkgs, ... }:
{
fonts.packages = [ pkgs.inter ];
};
};
yen.aspects.fonts-home = {
at = yen.kinds.home; # (2)
homeManager =
{ pkgs, ... }:
{
home.packages = [ pkgs.inter ];
fonts.fontconfig.enable = true;
};
};
}

A home-manager module gets the same yen args as a NixOS module: the host, the user, and any sink. There is no extraSpecialArgs. To read the host’s NixOS options from home-manager, see osConfig in home-manager.

modules/args.nix
# A home-manager module reads its host and its user as args: no extraSpecialArgs, no osConfig.
{ yen, ... }:
{
yen.aspects.prompt = {
at = yen.lib.all [
yen.kinds.user
(yen.lib.within yen.kinds.host) # (1)
];
homeManager =
{ host, user, ... }: # (2)
{
home.sessionVariables.PROMPT_TAG = "${user.name}@${host.name}";
};
};
}

(1) makes the aspect active at every user within a host. (2) asks for the host and the user.

ContributeCommunitySponsor