I want to be able to run nginx in a dev shell for testing. It is simple to do that by including pkgs.nginx in the packages list, and passing it a conf file. However I’d like to be able to share some config between local dev and the production environment (which configures Nginx using the Nixpkgs NixOS module).
Is there a way to talk to that module directly and gain access to the nginx.conf derivation, and provide the same config I use for the production service? I’ve figured out how to reference it with callPackage, but the contents of the attrset that yields seem pretty opaque. Or perhaps this is the wrong approach entirely.
That’s sort of the opposite of what I need. I don’t want to write Nginx config file(s), I want to use the Nginx NixOS module to produce an Nginx file that I can then pass directly as an argument to Nginx.
I’ve made some progress troubleshooting. My flake has a test output currently:
test = forAllSystems (pkgs:
{
test = pkgs.callPackage "${pkgs.path}/nixos/modules/services/web-servers/nginx/" {services.nginx.enable = true;};
}
);
When I build it, I get an error message.
$ nix build '.#test.aarch64-linux.test.config.content.environment.etc."nginx/nginx.conf".content.source'
error:
… while evaluating a branch condition
at /nix/store/mn45pdsl9nx5j8mwkylxi715nj9vpk5i-qjg5hnnkydk3mri5k6rydhj08x9s7xya-source/nixos/modules/services/web-servers/nginx/default.nix:162:6:
161| configFile =
162| (if cfg.validateConfigFile then pkgs.writers.writeNginxConfig else pkgs.writeText) "nginx.conf"
| ^
163| ''
error: attribute 'services' missing
at /nix/store/mn45pdsl9nx5j8mwkylxi715nj9vpk5i-qjg5hnnkydk3mri5k6rydhj08x9s7xya-source/nixos/modules/services/web-servers/nginx/default.nix:11:9:
10| let
11| cfg = config.services.nginx;
| ^
12| inherit (config.security.acme) certs;
I’ve fiddled around with the shape of the attrset I pass in. If I change it to config.services.nginx.enable, it seems to override all of the default configs and it breaks in a different way. I also tried using override on the package instead but that did not change the behavior.
If you don’t set enableReload, you could try to pass the nginx config attrset to pkgs.writers.writeNginxConfig, but a lot of the magic happens in let bindings so that probably isn’t very feasible.
So, my suggestion would be to evaluate the module system and set the enableReload in the module set you evaluate (doesn’t have to be the actual config).
In either case, you’re mucking with module internals, and all of this may change with upstream changes.
In the general case, it’s technically possible to create read-only options in NixOS, and those would be an excellent place to put things like this generated file; it’d be neat if more modules did so, but doing so also means exposing it as a public API thing; I think few module authors are motivated to promise you this kind of access.
You could submit a PR that exposes this for nginx, and you could also start an RFC standardizing the handling of configuration files (and final configuration attrsets) such that they’re always available via a read-only option. But that’s not the state today.
I’d like to do this outside of the context of a full NixOS configuration. It seems like pkgs.lib.evalModules might be the way to go, but the documentation is thin and I’m not seeing any clear instructions for its input.
I think creating a NixOS container config with only Nginx and grabbing just the config might be simpler; you won’t be building the entire NixOS configuration anyway.
You don’t even have to go that far, it’s not like any of this will be built if you just grab the .config attribute. Just evaluating NixOS with only the nginx config set should be enough, no need to create a container config.
It might be a little faster to evaluate if you don’t import all modules, but I also don’t think it’s worth the effort.
Well OK, I do not think in terms of specifically Nginx, I use the trick enough that there are some cases where full /etc/ file name list needs to be evaluated and filtered to request the contents of only one file, so I say that going all the way is not that bad anyway rather than checking that in the specific case you can go a bit less far.
As long as you work within the evaluation context, and what you’re looking for is exposed in an option, you don’t need a specific configuration. That solution is fairly generic, not specifically for nginx.
This should be true for anything exposed in NixOS, somewhere down the line, though some things might end up as raw strings in the activationScript or a line in a systemd.service.script because they’re bound in a let and therefore are not easily digestible; for those you have no way around patching the module to expose this, or actually running a NixOS system, and yeah, there a container might make sense. That’s far from true for configuration files simply exposed via environment.etc though.
This is possible but unreasonable, because it depends on fragile impl details - and nixos currently does not provide a way to independently use modules like this.
Then you can wrap that in parens and access attrs from config.
If you really want to use evalModules directly and only import the applicable modules, you’ll need to figure out what those several-dozen modules are yourself. And you’ll also need to set some important module args as well, most-likely.
Thanks so much for the discussion on this everyone, and especially to @waffle8946 for putting together a clear example. I have a working solution, here’s my exact code.