Nixos Migrating from channels to Flakes - Include input packages in output modules

I’ve had NixOS on several machines for a few years now and finally decided to bite the bullet and migrate my configuration to flakes. I had been using syncthing to share common configuration and some custom packages via modules between my machines and I’m hoping to do something similar with flakes.

My initial attempts are to create something like this -

The base system flake looks as such:

{
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
inputs.remoteConfig = {
   url = "git+ssh://<remote repo url>";
   inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, remoteConfig}: {
  nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {
    system = "x86_64-linux";
    modules = [
      ./configuration.nix
      remoteConfig.nixosModules.graphics
      remoteConfig.nixosModules.console
    ];
  };
};
}

The remote config flake then looks like:

{
inputs = {
  nixpkgs.url = "nixpkgs/nixos-unstable";
  customPackage = {
    url = "git+ssh://<custom package url>";
    inputs.nixpkgs.follows = "nixpkgs";
  };
};
outputs = {self, nixpkgs, customPackage }: {
  nixosModules = {
    graphics = import ./graphicsConfig.nix;
    console = import ./consoleConfig.nix;
  };
};
}

customPackage is a placeholder flake I’ve created and successfully built; in reality I have a few bits of code I was previously storing in separate repos that were built with nix expressions. Under my old configuration setup consoleConfig.nix would then contain imports and those imports would then include callPackage expressions to bring my custom packages into my system configuration in a modular fashion.

The problem now is that under flakes I don’t know how to make those subsequently imported modules aware of the remoteConfig’s flake inputs. I had hoped that by importing the modules in the flake outputs the modules would become aware of the inputs somehow but that’s not the case.

Is there some incantation of arguments that I need to pass into the remoteConfig imports or is this wrong approach entirely?

I am not a flake expert so maybe wait to have the usual nix guru here, but here is my understanding. First flake does not really like inputs (see that ongoing discussion). However in your specific case I guess you don’t need inputs and can use instead _module.args (there is also specialArgs but it is not really recommended as discussed here). The idea is to change your modules to accept an additional input (e.g. in your case the set of inputs listing all your flake packages):

{ config, pkgs, remoteConfig, ... }:

Then, you can use remoteConfig in your configuration, to call packages, modules… And you provide this value in your configuration:

  modules = [
    {_module.args = inputs;}
    ./configuration.nix
   # …

An example of PR doing that is given here.

If you want to keep your modules consumable by downstream users, then you have to restrict yourself to the default module arguments, since we cannot assume users will be using the same set of any specialArgs¹ ² that one may set.

The way I have done this in my own system is to define anything dependant on the flake’s inputs inline where the module is exported, so that it can be referenced directly. To give you a concrete example, here is my nix profile:

As you see, here anything relying on inputs is used directly where the module is defined, and anything that doesn’t rely on any arguments other than those provided by default to the module system are imported from another file (notice the imports). My system is also using std to keep things manageable, so if you open the link to see the full file it may not be immediately obvious what is going on. Therefore, to illustrate a minimal example using just vanilla Nix and flakes based on yours:

{
inputs = {
  nixpkgs.url = "nixpkgs/nixos-unstable";
  customPackage = {
    url = "git+ssh://<custom package url>";
    inputs.nixpkgs.follows = "nixpkgs";
  };
};
outputs = {self, nixpkgs, customPackage }: {
  nixosModules = {
    graphics = {
      # some module options relying on `self` or other inputs
      someOption = self.whatever.you.want.to.reference;
      # everything defined in this file should not rely on anything specific to your flake
      imports = [./graphicsConfig.nix];
    };
  };
};
}
1 Like

I kind of have to revert what I said back then and today I would indeed prefer specialArgs/extraSpecialArgs over _module.args, still extraArgs shouldn’t be used.

My antipathy to specialArgs back then came from a misunderstanding of the comments that discouraged extraArgs.

Both specialArgs and _module.args compose well enough and can be used together.

If I would have to choose between _module.args and specialArgs I’d use specialArgs for my flake inputs and _module.args only within returned nixosModules meant for external consumption (which again would only be rare, that they add something to the args at all.

3 Likes

I ended up taking @nrdxp 's advice and defining my imports as modules directly in the flake and importing the external files there along side including my custom package inputs as overlays:

{
inputs = {
  nixpkgs.url = "nixpkgs/nixos-unstable";
  customPackage = {
    url = "git+ssh://<custom package url>";
    inputs.nixpkgs.follows = "nixpkgs";
  };
};
outputs = {self, nixpkgs, customPackage }: {
  nixosModules = {
    graphics = import ./graphicsConfig.nix;
    console = {
      nixpkgs.overlays = [
        inputs.customPackage.overlays.default
      ];
      imports =  [./consoleConfig.nix];
    };
  };
};
}

I could then use my customPackage inside my imported files anywhere I’d use pkgs.