{ config, lib, registry, ... }: let cfg = config.modules.services.reverse-proxy; mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}"; mkBackend = address: route: "http://${address}:${toString route.port}"; mkVirtualHost = host: entry: let compositeEntry = registry.join host entry; route = compositeEntry.route; address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); in { inherit host entry; name = route.domain; value = { forceSSL = true; enableACME = true; locations."/".proxyPass = mkBackend address route; }; }; generatedVirtualHostList = lib.concatLists ( lib.mapAttrsToList ( host: routes: lib.mapAttrsToList (entry: _route: mkVirtualHost host entry) ( lib.filterAttrs (_entry: route: route.port != null) routes ) ) registry.route ); 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 "route" 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; }; }; }