{ config, lib, registry, ... }: let cfg = config.modules.services.reverse-proxy; mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}"; mkBackend = address: port: "http://${address}:${toString port}"; mkVirtualHost = host: entry: mode: let compositeEntry = registry.join host entry; inherit (compositeEntry) route; address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); isHttp = mode == "http"; port = if isHttp then route.http.port else route.port; in { inherit host entry; registryName = "route"; name = route.domain; value = { forceSSL = !isHttp; enableACME = !isHttp; locations."/".proxyPass = mkBackend address port; }; }; generatedHttpsVirtualHostList = lib.concatLists ( lib.mapAttrsToList ( host: routes: lib.mapAttrsToList (entry: _route: mkVirtualHost host entry "https") ( lib.filterAttrs (_entry: route: route.port != null) routes ) ) registry.route ); generatedHttpVirtualHostList = lib.concatLists ( lib.mapAttrsToList ( host: routes: lib.mapAttrsToList (entry: _route: mkVirtualHost host entry "http") ( lib.filterAttrs (_entry: route: route.http.port != null) routes ) ) registry.route ); generatedVirtualHostList = generatedHttpsVirtualHostList ++ generatedHttpVirtualHostList; virtualHostsByDomain = lib.foldl' ( acc: virtualHost: acc // { ${virtualHost.name} = (acc.${virtualHost.name} or [ ]) ++ [ virtualHost ]; } ) { } generatedVirtualHostList; duplicateDomainMessages = lib.concatLists ( lib.mapAttrsToList ( domain: virtualHosts: if lib.length virtualHosts > 1 then [ "duplicate route domain ${domain} in ${ lib.concatMapStringsSep " and " ( virtualHost: mkRegistryPath virtualHost.registryName virtualHost.host virtualHost.entry ) virtualHosts }" ] else [ ] ) virtualHostsByDomain ); generatedVirtualHosts = if duplicateDomainMessages != [ ] then throw (builtins.head duplicateDomainMessages) else lib.listToAttrs generatedVirtualHostList; mkUdpStream = host: entry: port: let compositeEntry = registry.join host entry; address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); in { inherit host entry port address ; }; generatedUdpStreams = lib.concatLists ( lib.mapAttrsToList ( host: routes: lib.concatLists ( lib.mapAttrsToList (entry: route: map (mkUdpStream host entry) route.udp.ports) routes ) ) registry.route ); generatedStreamConfig = lib.concatMapStringsSep "\n" (stream: '' server { listen ${toString stream.port} udp; proxy_pass ${stream.address}:${toString stream.port}; } '') generatedUdpStreams; in { options.modules.services.reverse-proxy = { enable = lib.mkEnableOption "the public edge reverse proxy"; }; config = lib.mkIf cfg.enable { networking.firewall.allowedTCPPorts = [ 80 443 ]; networking.firewall.allowedUDPPorts = map (stream: stream.port) generatedUdpStreams; security.acme = { acceptTerms = true; defaults.email = "contact@alexion.dev"; }; services.nginx = { enable = true; commonHttpConfig = '' server { listen 0.0.0.0:443 ssl default_server; listen [::0]:443 ssl default_server; ssl_reject_handshake on; } ''; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedProxySettings = true; recommendedTlsSettings = true; streamConfig = lib.mkIf (generatedUdpStreams != [ ]) generatedStreamConfig; virtualHosts = generatedVirtualHosts; }; }; }