Strategy to use same config at work and home

Hi,

I’m quite new to Nix, but I managed to create my own “dotfiles” with Home Manager and I’m quite happy with the tooling I managed to setup.

Now I’d like to take this with me at my work computer. Most of my configuration is fine, except for:

  • Git: Different user, email and secret management
  • Home: Different user and home folder
  • Fish Shell: Extend with new alias

What would be a good way to achieve this? I though about creating a new repository for my work configuration (in the company’s space) or just extending my personal configuration with a new user and load the Git configuration from a company’s repository.

Suggestions? Any example I could look to get inspired?

Thanks

I haven’t taken the plunge into home manager yet, but I’ve done this with my /etc/nixos/configuration.nix.

My approach is to keep my configuration in a git repo, with clones on my home and work machines.

I have an import like adminInfo = import ./admin-info.nix {}; in configuration.nix. And I have two files in the repo:

home-admin-info.nix:

{}:

{
  user = "matt";
  hostName = "alpha";
  domain = "mchenryfamily.org";
}

work-admin-info.nix:

{}:

{
  user = "mmchenry";
  hostName = "mmchenry-nixos";
  domain = "work.com";
}

The only thing that’s not version controlled in git is the admin-info.nix symlink, which points to the appropriate *-admin-info.nix on each machine.

Then elsewhere in configuration.nix, I can refer to these variables, e.g. networking.hostName = adminInfo.hostName.

I also have hardware-configuration.nix symlinked to home-hardware-info.nix or work-hardware-info.nix, since the machines need different kernel modules etc.

I use a single flake for everything. The flake has a separate configuration for each machine, and every machine has its own module which is given to mkSystem (I don’t use home-manager independently of NixOS/nix-darwin but I assume you can set a per-host configuration). The module I use for each host follows the same structure, which is a file hosts/$hostname/default.nix. A sample host file might look like

{ config, lib, pkgs, ... }:

with lib;

{
  imports = [
    # import the arch-os file
    ../x86_64-darwin.nix
  ];

  # per-host user overrides
  home-manager.users.lily.imports = [ ./home/lily ];

  # this is a work machine, I have a module for that
  my-work-internal.enable = true;

  # hardware-specific stuff
  security.pam.enableSudoTouchIdAuth = true;

  # every host sticks with the state version current when I set it up
  system.stateVersion = 4;
}

This one is for nix-darwin but my NixOS hosts are structured the same way. The x86_64-darwin.nix file then imports a ./darwin.nix file, and that sets a bunch of defaults that I want to apply to all of my macOS machines and imports ../common/darwin. That’s the entry-point into my modules for darwin, there’s a similar common/nixos, and both of them import the shared common/default.nix. The difference between hosts/darwin.nix and common/darwin is the former only sets options (often with mkDefault so hosts can override easily) and the latter defines options.

The point of this setup is I’ve got as much shared between NixOS and nix-darwin as possible, as much shared between hosts as possible, and there’s a clear place to look for anything. I have all of my user config in common/users/$username, which is mostly home-manager config. I also define shared modules for home-manager in common/home-manager so I can reuse stuff between users as much as possible.

I do use the same username everywhere, but because I have separate files for the OS-level user configuration and the home-manager config, if I need to change usernames for one machine I can just define a separate user that imports the same config, but I haven’t needed that yet. Or I suppose I could define an OS-level “username” option that I can override per-host to get an identical setup with a different username.

I also make liberal use of the idea of defining new modules that have an enable option and set a bunch of stuff, so I can just turn sections of my config on or off on a per-host basis. I did this mostly so that way I can share my config to a headless raspberry pi and easily turn off anything that I don’t need in that minimal environment (such as direnv, or the set of tools I use for Rust development).

Thanks for you answer!

Both approaches are interesting. The use of a symlink makes it easy to have the local clone work for different scenarios; using different modules is a bit harder for me to understand (I will have to read more about mkDefault) but I grasp the idea behind it.

I also found this other approach with Home manager.

At work I know that they prefer that my “work” setup is not part of my public setup. How would you organize that?

This is addressed by my solution – I have one flake for each system I use (personal, work, servers, etc.) and one flake for common configuration that they all have as an input. The result is that I can make the common configuration flake public (it’s sersorrel/sys), whilst leaving the actual system flakes (based on the template sersorrel/sys-tem) private.

It has some downsides (have to manually update each system flake separately, mixing nixpkgs versions makes writing the common flake very annoying, and it’s almost impossible to test every machine flake before committing updates to the common flake), but being able to make only parts of my configuration public is important for me, so it feels like it’s worth it.

To solve for situations like this, I export multiple different module for each software component I use. So instead of having everything in one monolithic profile, I have my zsh setup in one module, my preferred editor config in another, etc, etc.

Then I have several options. My personal flake exports multiple systems that I use, my home desktop workstation, my laptop, etc. These systems import the modules they want to use directly from within this public flake.

However, I have some work machines which are not public but I still use some of my public profiles individually by consuming my public flake as an input and simply importing my desired profiles as you would any other module. A contrived example:

# private work flake
{
  # inputs elided
  outputs = inputs: {
    nixosConfigurations.myWork = inputs.nixpkgs.nixosSystem {
      modules = [
        inputs.myPublicFlake.nixosModules.zsh
        # whatever else I want to import from `myPublicFlake`
        ./private-in-repo-module.nix
      ];
    };
  };
}