Is this a good way to modularize home manager home.nix for home/work?

I was originally going to post this because it wasn’t quite working and I needed help. Maybe when I have more nested attributes they won’t quite merge correctly and I’ll need to better understand merging then.

Since it is working though, I guess this is a request for feedback and to see what others do to solve the problem of modularizing their home-manager configs.

Here is a screenshot I think describes everything:

I want to know if this is a good way to modularize or am I missing out on a simpler solution?

I use home-manager for personal things and work things across both laptops, desktops, and hopefully soon phones with android on nix. I make 4 files for this:

  • home.nix
  • core.nix
  • personal.nix
  • work.nix

Personal and work have a lot in common, so I’m putting those common things in core.nix. For instance my git.userName is always the same, but my work.nix and personal.nix will customize git.userEmail differently as seen in the screenshot.

I’m curious to see how others solve the problem and/or they have any suggested improvements to this method.

Figuring out I needed to use lib.mkMerge and that // wouldn’t work correctly for me was a little tough and required going to IRC quite a few times until I understood or asked my question clearly enough.

I don’t have any ideas on how that would be improved, but wanted to note my experience/struggles nonetheless in case others may think of ideas to smooth it out.

I haven’t actually used home manager myself but my impression was it made use of the same module system that nixos uses. In light of that, instead of manually merging stuff, can’t you just have your root config import a dynamic set of modules conditional on the environment?

1 Like

I was told this, but wasn’t quite sure how to do it. I’ll take another stab at it now.

Take a look at some of the example configs in this topic to get a feel for how it is done:

If it’s like NixOS then you just need something like

{ config, pkgs, home, ... }:

{
  imports = [ ./core.nix ]
    ++ if builtins.getEnv "MYENV" == "HOME"
       then [ ./personal.nix ]
       else [ ./work.nix ];
}

Given this approach you could also just merge core.nix into the root config instead of having it be a separate file.

Incidentally what’s the with import <nixpkgs> {}; for? Isn’t that the same thing as pkgs?

Wha lilyball is doing is more or less what I’m doing too, just in a slightly more roundabout way. In my configuration I split things up based on machine, so my laptop is called uranus and my desktop neptune (I really need better names, my old machine was jupiter and I still cannot remember which machine is which). What I then do is change the $NIX_PATH variable to point to the specific machine configuration (see here). You can extend this in various ways, since I only have two machine at the moment managed with NixOS I just have config options for whether a machine can do x or y (like gaming). I had some initial troubles doing what you did, though nothing requiring merging or diverging sets of packages/settings. I think this would be the how I’d configure the system, check the hostname and conditionally import based on that or use configuration options (like config.mine.work) to conditionally enable things instead of importing. I might be moving towards options myself instead of imports because I occasionally run into corner cases of how I’ve structured my own config.

I ended up doing something like:

{ config, pkgs, home, ... }:
with import <nixpkgs> {};
with lib;
let
  myEnv = builtins.getEnv "MYENV";
  emacsHEAD = import ./emacs.nix;
in
{
  imports = if myEnv != ""
               then if myEnv == "personal" then
                 lib.info "loading PERSONAL home manager environment"
                   [ ~/Sync/personal.nix ]
                    else
                      if myEnv == "work" then
                        lib.info "loading WORK home manager environment"
                          [ ~/Sync/work.nix ]
                      else
                        lib.warn "MYENV is not one of 'personal' or 'work', ONLY core home environment will be available!" []
               else
                 lib.warn "MYENV not specified, ONLY core home environment will be available!" [];
  nixpkgs.config.allowUnfree = true;
  programs = {
    home-manager.enable = true;
    emacs = {
      enable = true;
      package = emacsHEAD;
    };
  };

  services = {
    syncthing = {
      enable = true;
    };
  };

Then in work.nix for example I just structure it the same way and everything gets merged correctly from the import line [ ~/Sync/work.nix ] under lib.info "loading WORK home manager environment". For reference this is my work.nix:

{ config, pkgs, home, ... }:
with import <nixpkgs> {};
with lib;
{
  programs =
    {
      git = {
        userEmail = "cody@workEmail.com";
      };
    };
  home = {
    packages = [ teams ];
  };
  services = {
  };
}

For some reason though I can’t install nonfree packages from within the work.nix file like teams. It seems it’s not respecting the nixpkgs.config.allowUnfree = true; in home.nix. Will have to fix that later though.

1 Like

I think it’s because you are importing nixpkgs again with empty parameters ( with import <nixpkgs> {};) which resets the allowUnfree option you set erlier. You shouldn’t need to import nixpkgs in work.nix file at all. Just use pkgs parameter to install packages.

My approach is to have a folder hosts, which contains a single $(hostname).nix per host I want to configure.

hosts/default.nix is a symlink to the appropriate $(hostname).nix. From home.nix I include ./hosts as any other module.

And through the $(hostname).nix I enable “features”, programs, configs that are defined using mkOption and similar in other modules.

Parts of this configuration can be seen in my home-manager config (its not quite in the state I’d like it to be, as I had to completely reformat my laptopn and I’m trying to catch up my old nix-arch-combo to a pure nixOS):

2 Likes