How do you share flake between NixOS and MacOS?

I have a mac running with nix-darwin and a mac in NixOS running with home-manager and flake. As I feel the pain for maintaining two separate dotfiles repo, I wonder if it is possible to extract home-manager related part as a flake and make a dotfile repo, shared by two OS.

However, one thing that I cannot think through is that I am starting my build with flake now, and the system related configuration will be imported here(e.g. configuration.nix).

{
  description = "Configuration for my sysmem";

  inputs = {
    nixpkgs = { url = "github:nixos/nixpkgs/nixos-unstable"; };
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs = { nixpkgs.follows = "nixpkgs"; };
    };
  };

  outputs = { nixpkgs, home-manager, ... }:
    let
      system = "x86_64-linux";

      pkgs = import nixpkgs {
        inherit system;
        config = { allowUnfree = true; };
      };

      lib = nixpkgs.lib;
    in {
      homeManagerConfigurations = {
        hugosum =
          home-manager.lib.homeManagerConfigurations { inherit system pkgs; };
      };
      nixosConfigurations = {
        nixos = lib.nixosSystem {
          inherit system;

          modules = [
            ./configuration.nix
            home-manager.nixosModules.home-manager
            {
              home-manager.useGlobalPkgs = true;
              home-manager.useUserPackages = true;
              home-manager.users.johnwinston = import ./home-manager.nix;
            }
            ./container.nix
            ./hardware-configuration.nix
            ./services.nix
            ./shell.nix
            ./xserver.nix
          ];
        };
      };
    };
}

How can I extract the configuration.nix for system configuration, and use flake for home-manager? Is my best shot to create an alias that will trigger two builds, one for system and one for home-manager?

2 Likes

I did this for my dotfiles: https://github.com/tlater/dotfiles

It’s not trivial, but I think the flake is reasonably self-evident.

I don’t think there’s a better way to do this currently, since the configuration home-manager’s lib creates is very different from what the system module expects. Feel free to ask specifics if it’s unclear.

To be more specific, we don’t extract the configuration from the system config, but instead define the configuration separately as a module that is then fed through the correct function to make either a homeManagerConfiguration or a nixosConfiguration.

1 Like

Thank you so much!! What you have there is inspiring. Let me try listing out anything special so I can help others to learn for your dot and I can learn more from you:

  • using _module.args.dotroot = ./..; to create a dotroot for using absolute path

  • separate configuration into different file based on their function(?), and compose them back in as a profile here: https://github.com/TLATER/dotfiles/blob/d37ea7a8ed1cbeba33c49a7f498af14d254902a2/nixpkgs/profiles.nix

  • separating dotfiles and nix definition(nixpkgs) there. So it will be easier to ditch nix(if needed)

  • using Flake as the input of another Flake! This one is huge for me, with this I realize its potential. This is much better than using a simple configuration.nix

And really I am suprised that I am able to read your code! I have been struggling with nix for about 2 months since installing NixOS, and I feel like I have been using it like JSON, whereas your code is like Javascript LOL cool thanks!

Hah, I take that as a compliment :slight_smile: As for the slight questions:

Yeah, that was a handy hack, though I think there are probably better ways to achieve that particular detail :wink:

More or less. I work as a software consultant and often need to work on customer hardware. I want to quickly get a comfortable working environment set up whenever that happens, so I make my dotfiles deliberately composable. The modules roughly relate to what I think I may want to disable in certain situations, but may not actually make much sense - it’s not very battle tested yet.

My thought was mostly to enable sharing snippets with non-nix users more easily, but yes, it’s been useful when I’ve had to work in environments where I couldn’t get nix.

@TLATER There is something I don’t really understand about _module.args. Right now I have to include _module.args in two places in order to avoid exception:

I put it here:

and here(which I added in to fix the error):

Why can’t my first definition of _module.args passed to all modules imported inside profile? Is there a better way to avoid repetition there?

Adding the error would be better to understand the problem.

Sorry for missing that the first place. This is the error I got:

error: attribute 'dotfiles' missing

       at /nix/store/bwhjq6kj5i15v84drj3s3x9hk1md1iqi-source/lib/modules.nix:305:28:

          304|         builtins.addErrorContext (context name)
          305|           (args.${name} or config._module.args.${name})
             |                            ^
          306|       ) (lib.functionArgs f);
(use '--show-trace' to show detailed location information)

error: attribute 'dotfiles' missing

As far as I understand, this means that there is no dotfiles key defined inside config._module.args. If you get this error when you remove the _module.args.dotfiles = ./dotfiles inside “profiles/default.nix”, that means the one inside createProfile doesn’t work.

Also, I might be missing something but you are not actually using createProfile anywhere in your repository. Is this expected?

Also, I might be missing something but you are not actually using createProfile anywhere in your repository. Is this expected?

Yes that is expected. This flake will be used as an input, and I am using it like this on my machine right now:

{
  description = "Configuration for my sysmem";

  inputs = {
    nixpkgs = { url = "github:nixos/nixpkgs/nixos-unstable"; };
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs = { nixpkgs.follows = "nixpkgs"; };
    };
    dotfiles.url = "github:winston0410/universal-dotfiles/master";
  };

  outputs = { nixpkgs, home-manager, dotfiles, ... }:
    let
      system = "x86_64-linux";

      pkgs = import nixpkgs {
        inherit system;
        config = { allowUnfree = true; };
      };

      lib = nixpkgs.lib;

      username = "hugosum";
    in {
      nixosConfigurations = {
        nixos = lib.nixosSystem {
          inherit system;

          modules = [
            home-manager.nixosModules.home-manager
            (dotfiles.lib.createProfile {
              imports = [
                ./configuration.nix
                dotfiles.modules.zsh
                dotfiles.modules.lorri
                dotfiles.modules.container
                ./hardware-configuration.nix
                (dotfiles.modules.leftwm username)
                ./xserver.nix
              ];
            } dotfiles.profiles.user.dev username)
          ];
        };
      };
    };
}

I am struggling with this one, as I thought it will be defined globally and affect all modules downward, but it seems not the case right now. Unfortunately I am not able to find any documentation so it is a guessing game right now.

I can’t see anything wrong after looking into your configuration. I don’t know why it’s not working.

Regarding documentation, there is a section under “Value Types” on NixOS manual that briefly talks about specialArgs and _module.args.

My understanding of module arguments is similar to yours, in that they should be available to every module regardless of where they are defined. So it is possible there is something wrong with the configuration that we are not seeing.

At the end, I just ditched that _module.args. I have no clue how to solve it.

@TLATER I have to share this with you, now my pcs config is now a oneliner!

1 Like

I want to go the very extreme yesterday and enclose the home-manager.nixosModules.home-manager inside my partially applied profile function like this, but I am getting error where options for home-manager doesn’t exist. Is it related with the resolve order(but I guess import will always resolve first?)?

  outputs = { nixpkgs, home-manager, ... }: rec {
    lib = {
      createProfile = moduleList: username:
        let list = (builtins.map (m: (m username)) moduleList);
        in {
          imports = [
            # Try import home-manager here
            home-manager.nixosModules.home-manager
            ((import ./options.nix) username)
          ] ++ list;
          home-manager.useGlobalPkgs = true;
          home-manager.useUserPackages = true;
        };
    };

I’m unsure, actually, nix is generally lazy so that function shouldn’t be evaluated until it is called. Where is it called and what is the exact error?