{ config, lib, registry, ... }: let generatedUdpStreams = lib.concatLists ( lib.mapAttrsToList ( host: routes: lib.concatLists ( lib.mapAttrsToList ( entry: route: lib.optionals (route.protocol == "udp") (map (mkUdpStream host entry) route.ports) ) routes ) ) registry.route ); mkUdpStream = host: entry: port: let address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); compositeEntry = registry.join host entry; in { inherit host entry port address ; }; generatedStreamConfig = lib.concatMapStringsSep "\n" (stream: '' server { listen ${toString stream.port} udp; proxy_pass ${stream.address}:${toString stream.port}; } '') generatedUdpStreams; generatedVirtualHosts = if duplicateDomainMessages != [ ] then throw (builtins.head duplicateDomainMessages) else lib.listToAttrs 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 ); virtualHostsByDomain = lib.foldl' ( acc: virtualHost: acc // { ${virtualHost.name} = (acc.${virtualHost.name} or [ ]) ++ [ virtualHost ]; } ) { } generatedVirtualHostList; generatedVirtualHostList = generatedHttpsVirtualHostList ++ generatedHttpVirtualHostList; generatedHttpsVirtualHostList = lib.concatLists ( lib.mapAttrsToList ( host: routes: lib.mapAttrsToList (entry: _route: mkVirtualHost host entry "https") ( lib.filterAttrs (_entry: route: route.protocol == "https") routes ) ) registry.route ); mkVirtualHost = host: entry: mode: let isHttp = mode == "http"; address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); compositeEntry = registry.join host entry; inherit (compositeEntry) route; inherit (route) port; in { inherit host entry; registryName = "route"; name = route.domain; value = { forceSSL = !isHttp; enableACME = !isHttp; extraConfig = lib.optionalString (route.clientMaxBodySize != null) '' client_max_body_size ${route.clientMaxBodySize}; ''; locations."/".proxyPass = mkBackend address port; }; }; mkBackend = address: port: "http://${address}:${toString port}"; generatedHttpVirtualHostList = lib.concatLists ( lib.mapAttrsToList ( host: routes: lib.mapAttrsToList (entry: _route: mkVirtualHost host entry "http") ( lib.filterAttrs (_entry: route: route.protocol == "http") routes ) ) registry.route ); mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}"; in { options.modules.services.reverse-proxy = { enable = lib.mkEnableOption "the public edge reverse proxy"; }; config = lib.mkIf config.modules.services.reverse-proxy.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; }; }; }