How to properly structure my nix config?

I think I have a rather standard situation, but Im not really sure what the way to go here is.

Setup

I have the following machines:

  • macOS:
    • laptop A, aarch64
  • nixOS:
    • server, x86_64

I currently manage the macOS machine with nix-darwin + home-manager, the nixOS setup with, well nix + home-manager. Of course, both machines have things that should be shared, and things that shouldn’t. My config is currently structured like this:

|- flake.nix
|- apps/
|  |- build-switch
|- hosts/
|  |- darwin/
|     |- default.nix
|  |- nixOS/
|     |- default.nix
|- modules/
|  |- shared/
|  |- darwin/
|  |  |- home-manager.nix
|  |- nixOS/
|  |  |- home-manager.nix

This worked so far quite well.

Problem

I need to add an additional MacBook to the setup and a Ubuntu machine. For the new MacBook I will just create a new folders in hosts/darwin, one for each host, and put the host specific stuff there. What Im not sure is how to do the Ubuntu part… My flake.nix looks like this:

# flake.nix

{
  description = "my config";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
    home-manager.url = "github:nix-community/home-manager";
    darwin = {
      url = "github:LnL7/nix-darwin/master";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };
  outputs =
    { self
    , darwin
    , home-manager
    , nixpkgs
    ,
    } @ inputs:
    let
      user = "iilak";
      linuxSystems = [ "x86_64-linux" "aarch64-linux" ];
      darwinSystems = [ "x86_64-darwin" "aarch64-darwin" ];

      forAllSystems = f: nixpkgs.lib.genAttrs (linuxSystems ++ darwinSystems) f;
      devShell = system:
        let
          pkgs = nixpkgs.legacyPackages.${system};
        in
        {
          default = with pkgs;
            mkShell {
              inherit (preCommitCheck) shellHook;
              buildInputs = preCommitCheck.enabledPackages;
              nativeBuildInputs = with pkgs; [ bashInteractive git ];
            };
        };
      mkApp = scriptName: targetSystem: system: {
        type = "app";
        program = "${(nixpkgs.legacyPackages.${system}.writeScriptBin scriptName ''
        #!/usr/bin/env bash
        PATH=${nixpkgs.legacyPackages.${system}.git}/bin:$PATH
        echo "Running ${scriptName} for ${targetSystem}/${system}"
        exec ${self}/apps/${scriptName} ${targetSystem} ${system}
      '')}/bin/${scriptName}";
      };
      mkLinuxApps = system: {
        "test" = mkApp "build-switch" "test" system;
      };
      mkDarwinApps = system: {
        "mac" = mkApp "build-switch" "mac" system;
      };
    in
    {
      devShells = forAllSystems devShell;
      apps = nixpkgs.lib.genAttrs linuxSystems mkLinuxApps // nixpkgs.lib.genAttrs darwinSystems mkDarwinApps;
      darwinConfigurations = nixpkgs.lib.genAttrs darwinSystems (
        system:
        darwin.lib.darwinSystem {
          inherit system;
          specialArgs = { inherit inputs; };
          modules = [
            home-manager.darwinModules.home-manager
            nix-homebrew.darwinModules.nix-homebrew
            {
              nix-homebrew =
                {
                  inherit user;
                  enable = true;
                  mutableTaps = false;
                  autoMigrate = true;
                };
            }
            ./hosts/darwin
          ];
        }
      );
      nixosConfigurations = nixpkgs.lib.genAttrs linuxSystems (
        system:
          nixpkgs.lib.nixosSystem {
            inherit system;
            specialArgs = {inherit inputs;};
            modules = [
              home-manager.nixosModules.home-manager
              {
                home-manager = {
                  useGlobalPkgs = true;
                  useUserPackages = true;
                  users.${user} = import ./modules/nixos/home-manager.nix;
                };
              }
              ./hosts/nixos
            ];
          }
      );
    };
}

As you can see I used here nixpkgs.lib.nixosSystem and darwin.lib.darwinSystem and then supply the relevant home-manager.nix as a module. But how do you do this for a scenario where you just want to manage you home directory with home-manager?

I tried this here, but this feels somehow out of place with the other two configs…

homeConfigurations = {
    example_case = home-manager.lib.homeManagerConfiguration 
        pkgs = nixpkgs.legacyPackages.${system};
        modules = [
          ./modules/ubuntu/example_case/home-manager.nix
        ];
        extraSpecialArgs =
          { inherit inputs; }
          // {
            isNixOS = false;
            impurePaths = {                
              workingDir = "/home/utm/.config/nix";
            };
          };
      }
  };

If you have any specific suggestions, that would be great. Otherwise, some examples of other people doing this kind of thing, would be also much appreciated.

I based a lot of this on this repo, but of course more examples are always welcome.

I’m not exactly sure if I understand your problem. I mean why don’t you use a different directory structure if you current is blocking what you’re trying to achieve?

e.g. I used to use the following structure. For a NixOS host with a home-manager configuration:

hosts/<name>/configuration.nix
hosts/<name>/home.nix

For a host which is not NixOS with a home-manager configuration:

hosts/<name>/home.nix

Now you could collect all NixOS configurations in a file hosts/hosts.nix and all home-manager configurations in a file hosts/homes.nix. The two files are referenced in you flake.nix.

I personally don’t see a benefit putting the architecture or the host operating system into the host directory structure.

Sometimes the simplest solutions are the best.

Thanks a lot. Nothing was stopping me, expect my fear of missing something fundamental about nix that made the author of the original template choose this split.

Migrated to something that fits my system more and everything seems to work just fine!

1 Like