I'm misunderstanding something

I had a flake.nix file that properly set up agenix secrets and ran nextcloud. It partially looked like this:

  outputs = { self, nixpkgs, agenix, home-manager, ... }:
    let
      lib = nixpkgs.lib;
      system = "x86_64-linux";
    in {
      nixosConfigurations = {
        nix0 = lib.nixosSystem {
          defaultPackage.${system} = home-manager.defaultPackage.${system};
          modules = [ 
            ./conf.nix 
            agenix.nixosModules.default
            {
                environment.systemPackages = [ 
                    agenix.packages.x86_64-linux.default 
                ];
            }
            {
                age.secrets.nextcloud_admin_pass.file = ./nextcloud_admin_pass.age;
                age.secrets.nextcloud_admin_pass.owner = "nextcloud";
                age.secrets.easydns.file = ./easydns.age;
            }
            ./nextcloud.nix
...

Now I’m reorganizing my files and trying to move those agenix secrets into my nextcloud.nix file and I can’t figure out what I’m doing wrong. Here’s the start of the nextcloud.nix file, all I did was move agenix stuff into it out of the flake:

{ self, config, lib, pkgs, agenix, ... }: {
  agenix.nixosModules.default =
    {
        environment.systemPackages = [ 
            agenix.packages.x86_64-linux.default 
        ];
    };
    
      age.secrets.nextcloud_admin_pass.file = ./nextcloud_admin_pass.age;
      age.secrets.nextcloud_admin_pass.owner = "nextcloud";
      age.secrets.easydns.file = ./easydns.age;
    
...

But this gives me an error:

       error: The option `age' does not exist. Definition values:
       - In `/nix/store/y7dxqpdcik28jm89bwz5f27lxxgq1zij-source/nixos/nextcloud.nix':
           {
             secrets = {
               easydns = {
                 file = /nix/store/y7dxqpdcik28jm89bwz5f27lxxgq1zij-source/nixos/easydns.age;
               };
           ...

I can’t figure out how the age option got created properly in the flake.nix but doesn’t work in the nextcloud.nix.

Entire source is available at GitHub - montyz/nixes --the directory first_attempt is what I had that was working. I’ve been trying to restructure my files and get stuck here.

You misread your original configuration:

agenix.nixosModule.default

and

{
    environment.systemPackages = [ 
        agenix.packages.x86_64-linux.default 
    ];
}

are both modules (and so are the {...} block, and ./nextcloud.nix passed afterwards)!

What you wrote:

agenix.nixosModules.default = { ... };

does not make sense.

I think using:

imports = [ agenix.nixosModules.default ];

environment.systemPackages = [ agenix.packages.x86_64-linux.default ];
  
age.secrets.nextcloud_admin_pass.file = ./nextcloud_admin_pass.age;
age.secrets.nextcloud_admin_pass.owner = "nextcloud";
age.secrets.easydns.file = ./easydns.age;

should work.

Thanks, but doing that gives me

       error: infinite recursion encountered

       at /nix/store/bn7d9rhgmmz0l4x9ykv5q10gzq1rkxci-source/lib/modules.nix:506:28:

          505|         builtins.addErrorContext (context name)
          506|           (args.${name} or config._module.args.${name})
             |                            ^
          507|       ) (lib.functionArgs f);

If I remove that import, the error is

       error: The option `age' does not exist. Definition values:
       - In `/nix/store/wr3fgpv0nkqan6nllsq6gsx5n813xlzj-source/nixos/nextcloud.nix':
           {
             secrets = {
               easydns = {
                 file = /nix/store/wr3fgpv0nkqan6nllsq6gsx5n813xlzj-source/nixos/easydns.age;
               };
           ...

I guess I need to read up on modules

nix.dev recently grew a module deep-dive that looks good, should be on the recommended reading list for anyone using NixOS in anger: Module system deep dive — nix.dev documentation

It’s long, but the early bits probably tell you all you need to know. Specifically the section on imports in this case.

4 Likes

After watching that video and reading the tutorial a bit, what I think now is that my nextcloud.nix module is missing the option declaration of the age attribute, which agenix provides. I thought that by loading agenix into the flake and then passing it as an argument through my configuration.nix into the nextcloud.nix, it would be available to nextcloud.nix, but it is not.

Specifically, in my flake I have:

  inputs = {
...
    agenix.url = "github:ryantm/agenix";
  };
  outputs = {
    self,
    nixpkgs,
    home-manager,
    agenix,
    ...
  } @ inputs: let
    inherit (self) outputs;
  in {
    nixosConfigurations = {
      nix0 = nixpkgs.lib.nixosSystem {
        specialArgs = {inherit inputs outputs;};
        # > Our main nixos configuration file <
        modules = [./nixos/configuration.nix];
      };
    };

And then in configuration.nix:

{
  inputs,
  lib,
  config,
  pkgs,
  agenix,
  ...
}: {
  # You can import other NixOS modules here
  imports = [
    ./nextcloud.nix
...

And finally in nextcloud.nix:

{ self, config, lib, pkgs, agenix, ... }: {
  
  environment.systemPackages = [ agenix.packages.x86_64-linux.default ];  
  age.secrets.nextcloud_admin_pass.file = ./nextcloud_admin_pass.age;
...

Somewhere in that chain it seems that agenix and the options in particular are not being passed, but I don’t know where or why or how to debug it.

You need to either:

  1. pass agenix as a specialArg and import agenix module in configuration.nix

Or

  1. add agenix nixos module in modules inside call to nixosSystem in flake.nix

Nix merges all modules when evaluating a system; both of these approaches result in effectively the same thing.

You’re just forgetting to actually import the module. You need to add to nextcloud.nix (or as @VTimofeenko says any other random module in your configuration):

{ self, config, lib, pkgs, agenix, ... }: {
  # btw, given you `{inherit inputs;}`, shouldn't
  # this be `inputs.agenix`? Where are you getting
  # an `agenix` module arg?
  imports = [
    agenix.nixosModules.default
  ];
  
  environment.systemPackages = [ agenix.packages.x86_64-linux.default ];  
  age.secrets.nextcloud_admin_pass.file = ./nextcloud_admin_pass.age;
...

This should not give you infinite recursion, assuming you’re not doing something weird. Maybe it’s because the agenix arg doesn’t actually exist, but I don’t get how that would create this error, so I assume you’re ommitting some relevant code.

I think I have applied the advice above but am getting an infinite recursion error. Here’s what I think I’m doing, please correct my errors. Even after going through the modules tutorial, I don’t feel like I have the big picture of how module system is supposed to work, especially with external modules like agenix.

In my flake.nix, this imports(?) the agenix module.

  inputs = {
    agenix.url = "github:ryantm/agenix";
  };

This part passes agenix into the outputs, which then gets inherited to the inputs of the in block that follows. Then inside the nixosConfigurations.nix0.modules I load(?) the agenix module. That should make it available to other modules, I think.

  outputs = {
    self,
    nixpkgs,
    home-manager,
    agenix,
    ...
  } @ inputs: let
    inherit (self) outputs;
  in {
        
    # NixOS configuration entrypoint
    # Available through 'nixos-rebuild --flake .#nix0'
    nixosConfigurations = {
      nix0 = nixpkgs.lib.nixosSystem {
        specialArgs = {inherit inputs outputs;};
        # > Our main nixos configuration file <
        modules = [./nixos/configuration.nix
          agenix.nixosModules.default
        ];
      };
    };

Now in configuration.nix, it starts with

{
  inputs,
  lib,
  config,
  pkgs,
  agenix,
  ...
}: {
  imports = [
        ./nextcloud.nix

The flake has passed the agenix argument as an attribute set that refers to the agenix module contents?
That would get passed along implicitly to nextcloud.nix via the imports section, I think?

Then finally in nextcloud.nix, agenix is again passed as an argument.

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

  imports = [
    agenix.nixosModules.default
  ];
  environment.systemPackages = [ agenix.packages.x86_64-linux.default ];
    
  age.secrets.nextcloud_admin_pass.file = ./nextcloud_admin_pass.age;

This gives me the following error:

       … while calling anonymous lambda

         at /nix/store/bn7d9rhgmmz0l4x9ykv5q10gzq1rkxci-source/lib/modules.nix:504:44:

          503|       context = name: ''while evaluating the module argument `${name}' in "${key}":'';
          504|       extraArgs = builtins.mapAttrs (name: _:
             |                                            ^
          505|         builtins.addErrorContext (context name)

       … while evaluating the module argument `agenix' in "/nix/store/gf814vyk457kvsbahimcg0jl517xxyis-source/nixos/nextcloud.nix":

       error: infinite recursion encountered

       at /nix/store/bn7d9rhgmmz0l4x9ykv5q10gzq1rkxci-source/lib/modules.nix:506:28:

          505|         builtins.addErrorContext (context name)
          506|           (args.${name} or config._module.args.${name})
             |                            ^
          507|       ) (lib.functionArgs f);

As an aside, is there anyone out there doing Nix office hours? I feel like 10 minutes with an expert would clue me in on the concepts I’m missing and get this to work.

TL;DR: I think the problem in your code is double import of agenix.nixosModules.default. Get rid of agenix import in nextcloud.nix.

The central ideas behind the way Nix processes modules is merging and fixed points in functions. If I recall correctly, when evaluating a configuration, the order is:

  1. Collect and merge all the imports from all the modules
  2. Merge all the .options from all modules and imports
  3. Merge .config from all modules and imports

“Fixed point” in this context means “apply a function until output equals input and there is no point in proceeding”. In this case – merge modules until it stops making sense or abort if it looks like an infinite recursion.

This part passes agenix into the outputs, which then gets inherited to the inputs of the in block that follows. Then inside the nixosConfigurations.nix0.modules I load(?) the agenix module. That should make it available to other modules, I thnk.

What’s happening here is that you are bringing all .options and .config from agenix.nixosModules.default into your system configuration.

The flake has passed the agenix argument as an attribute set that refers to the agenix module contents?

“agenix” here is the “agenix” -the-flake-input.

That would get passed along implicitly to nextcloud.nix via the imports section, I think?

Nope, the module gets merged into a giant merged attribute set (see above) and that’s how modules can share data.

As an aside, is there anyone out there doing Nix office hours? I feel like 10 minutes with an expert would clue me in on the concepts I’m missing and get this to work.

There’s Nix office hours by Tweag.

It does not. It simply adds the agenix flake as an input to your flake, without implying anything about using its module or packages.

IMO, you should be able to pass the inputs attrset to your nixosSystem args precisely so end users like you never need to think about how you go from this to an imported module, but that’s not the reality.

Unless you use the outputs variable you declare there in your flake somewhere else, that’s probably superfluous by the way. inherit isn’t some super fancy object oriented programming primitive or anything.

In this case your outputs is also just your unevaluated flake output function without your inputs, AIUI, so it’s basically never a useful variable within a flake.

I was wrong, it’s just an internal copy of everything in self. self.nixosConfigurations == self.outputs.nixosConfigurations. You should still never use that variable, but this is interesting trivia.

The terminology is wrong, but that’s a kind of layman’s way of expressing what I’ll explain in a second. I assume you misunderstood it though.

This is where it goes completely wrong.

This:

is a set of function arguments.

Here you define a function that takes an attribute set as an argument, which may have any attributes (...).

You also specifically state that this attribute set must have inputs, lib, config, pkgs and agenix as arguments. Your function then returns an attribute set.

You pass this function into modules here:

nixpkgs.lib.nixosSystem takes this function, gives it a default set of arguments so that it evaluates and gives back the attribute set, merges that attribute set with the attribute sets of any other modules you defined in modules or in the imports section of any other modules, does some type checking and default variable setting based on all modules’ options attributes with the (sometimes implicit) config attributes of all modules, and then finally builds a script from the resulting massive attribute set. This script is then executed to deploy your NixOS config.

The function arguments nixpkgs.lib.nixosSystem gives the function are roughly defined here: nixpkgs/nixos/lib/eval-config.nix at af69be669f0257e734c3fb97828910f69fcb45a1 · NixOS/nixpkgs · GitHub

That file is rather confusing, because you can actually set the arguments from within modules using _module.args, so don’t spend too much time grokking it. The basic list is something like:

{
  config,
  pkgs,
  lib,
  ...
}

As you may be able to see from that file, it adds any arguments you set in specialArgs. So in your case, it also contains inputs and outputs.

You however never added an agenix argument. Your code should therefore fail with an undefined function argument. If you wanted to add an agenix argument, you would need to define it:

nixosConfigurations = {
      nix0 = nixpkgs.lib.nixosSystem {
        specialArgs = {
          inherit inputs outputs;
          inherit (inputs) agenix;
        };

Or, foregoing the inherit shorthand to demistify what is just syntactic sugar you seem to misunderstand:

nixosConfigurations = {
      nix0 = nixpkgs.lib.nixosSystem {
        specialArgs = {
          inherit inputs outputs;
          agenix = inputs.agenix;
        };

I think this is the big mistake and misunderstanding.

In addition, you now do this:

This will add the agenix module to the module system a second time, in addition to the first time here:

I think both these mistakes are needed in conjunction to cause that error you’re seeing. Presumably the agenix module adds agenix to _module.args for one reason or another, and you then try to use some part of it as a module again, causing infinite recursion?

Adding that inherit argument and removing the import from the nextcloud.nix file fixed it! Thanks!

I will try to go to the next nix office hours by Tweag, it seems like this could be simpler but I don’t know how exactly.