Nginx default locations across all virtualHosts

Hello,

I would like to ensure that every Nginx virtual has defined some common locations (e.g., security.txt, or block possible left-overs from developers like .git or .bash_history). I was trying to create a module, which recursively updates the virtual host’s location (via mapAttrs + recursiveUpdate, but as a result I’m only ending in an infinite recursion. Since I can have virtual hosts defined on different places, I would like to know a way which is transparent and doesn’t require to modify all the places where virtual hosts are really defined. Since I’m a begginer with NixOS, I’m a bit lost now.

What would be the (best) way to have services.nginx, where all virtual hosts share some default locations or even settings (like enforced SSL)? Thanks!

You will need to add an extra layer of indirection to the module system to break the recursion:

{ config, lib, ... }:

let
  hosts = config.nginxVirtualHostsWithDefaults;

in {
  options.nginxVirtualHostsWithDefaults = lib.mkOption {
    type = lib.types.listOf lib.types.str;
    default = [ ];
  };

  config = lib.mkIf (lib.length hosts > 0) {
    nginx.virtualHosts = lib.genAttrs hosts (name: {
      # Default options for each host in
      # `options.nginxVirtualHostsWithDefaults` here...
    });
  };
}

Then in any other module you can add hosts, even across multiple modules, for example

# moduleA.nix

{
  nginxVirtualHostsWithDefaults = [ "hostA" ];
  services.nginx.virtualHosts.hostA = {
    # Other nginx config for `hostA`
  };
}
# moduleB.nix

{
  nginxVirtualHostsWithDefaults = [ "hostB" ];
  services.nginx.virtualHosts.hostB = {
    # Other nginx config for `hostB`
  };
}

Thank you @olmokramer for the valuable hint!

It’s a step forward, but still requires to specify the names of virtual hosts to apply defaults to, i.e. it’s prone to errors when one can forget to set the new virtual hosts with the defaults. I understand that there is probably no way how to make this fully dynamical, i.e. query for all virtualHosts and apply to them automatically. Because anytime I manipulate with services.nginx.virtualHosts with mapAttrs or attrNames, I end-up again in infinite recursion.

Yeah I realised that after I had posted the above example. You can adapt the example, though, to accept an attrset from hostname to extra config for that host, which is slightly better, but still not perfect:

{ config, lib, ... }:

{
  options.nginxVirtualHostsWithDefaults = lib.mkOption {
    type = lib.types.attrsOf lib.types.attrs;
    default = { };
  };

  config.nginx.virtualHosts = lib.mapAttrs (name: vhostConfig: {
    # Default options for each host in
    # `options.nginxVirtualHostsWithDefaults` here...
  } // vhostConfig) config.nginxVirtualHostsWithDefaults;
}
# hostA.nix

{
  nginxVirtualHostsWithDefaults.hostA = {
    # Other nginx config for `hostA`
  };
}

To get exactly what you’re asking for is impossible, yes, because you’re trying to update nginx.virtualHosts with a value that is calculated from on nginx.virtualHosts, hence the infinite loop.

You can also override options.services.nginx.virtualHosts to inject default values into every virtualhost.
This is what I do to set listen addresses globally:

{  
  options.services.nginx.virtualHosts = lib.mkOption {
    type = lib.types.attrsOf (lib.types.submodule {
      config.listen = lib.mkDefault [
        { addr = "0.0.0.0"; port = 443; ssl = true; }
        { addr = "[2a01:4f9:2b:1605::1]"; port = 443; ssl = true; }
      ];
    });
  };
}
1 Like

Wait… what? You can override options? :exploding_head:

Thanks @Mic92! I’m almost certain I’ll be able to find a great use for that :slight_smile:

Ok didn’t know that either, thanks for the tip @Mic92!

Thank you @Mic92 , this is exactly what I needed!

Thanks for the answers. I’m afraid I need a bit of clarification. How exactly do I use that override. If I put Mic92’s code into my configuration.nix verbatim, I get something like

error: Module '/etc/nixos/configuration.nix' has an unsupported attribute `boot'. This is caused by introducing a top-level `config' or `options' attribute.