let lib = import ; servicesConfig = import ../config/services.nix; endpointsConfig = (import ../validation/endpoints.nix).getEndpointsConfig (import ../config/endpoints.nix); tunnelOffset = 10000; proxyOffset = servicesConfig.nginxProxyOffset; forwardingEndpoints = builtins.filter (endpoint: endpoint.type == "forwarding") endpointsConfig; proxyEndpoints = builtins.filter (endpoint: endpoint.type == "proxy") endpointsConfig; forwardingTlsEndpoints = builtins.filter (endpoint: endpoint.content.tls or false) forwardingEndpoints; forwardingHttpEndpoints = builtins.filter (endpoint: !(endpoint.content.tls or false)) forwardingEndpoints; proxyTlsEndpoints = builtins.filter (endpoint: endpoint.force_ssl or false) proxyEndpoints; forwardingTarget = endpoint: "127.0.0.1:${toString endpoint.content.port}"; proxyBackendPort = endpoint: proxyOffset + endpoint.listenPort; proxyBackendTarget = endpoint: "127.0.0.1:${toString (proxyBackendPort endpoint)}"; mkTlsRouteEntryForForwarding = endpoint: { domain = endpoint.domain; listenPort = endpoint.listenPort; upstream = forwardingTarget endpoint; }; mkTlsRouteEntryForProxy = endpoint: { domain = endpoint.domain; listenPort = endpoint.listenPort; upstream = proxyBackendTarget endpoint; }; tlsRouteEntriesRaw = map mkTlsRouteEntryForForwarding forwardingTlsEndpoints ++ map mkTlsRouteEntryForProxy proxyTlsEndpoints; groupedTlsRoutes = lib.foldl' (acc: entry: let key = toString entry.listenPort; previous = acc.${key} or []; in acc // { ${key} = previous ++ [ entry ]; } ) {} tlsRouteEntriesRaw; dedupeRouteEntriesByDomain = entries: builtins.attrValues (builtins.listToAttrs (map (entry: { name = entry.domain; value = entry; }) entries)); mkTlsRouteMapForListenPort = listenPort: let routesForPort = groupedTlsRoutes.${toString listenPort} or []; routeEntries = dedupeRouteEntriesByDomain routesForPort; defaultRoute = if routeEntries == [] then null else (builtins.head routeEntries).upstream; in if defaultRoute == null then null else { inherit listenPort routeEntries defaultRoute; }; tlsListenPorts = lib.unique (map (entry: entry.listenPort) tlsRouteEntriesRaw); tlsRouteMaps = builtins.filter (mapDef: mapDef != null) (map mkTlsRouteMapForListenPort tlsListenPorts); renderTlsServer = tlsRouteMap: let portString = toString tlsRouteMap.listenPort; in '' map $ssl_preread_server_name $proxy_upstream_${portString} { ${lib.concatStringsSep "\n" (map (entry: "${entry.domain} ${entry.upstream};") tlsRouteMap.routeEntries)} default ${tlsRouteMap.defaultRoute}; } server { listen ${portString}; proxy_pass $proxy_upstream_${portString}; ssl_preread on; } ''; groupedProxyEndpoints = lib.foldl' (acc: endpoint: let key = "${endpoint.domain}:${toString endpoint.listenPort}"; previous = acc.${key} or { domain = endpoint.domain; publicListenPort = endpoint.listenPort; listenPort = proxyBackendPort endpoint; locations = {}; forceSSL = false; }; location = { proxyPass = "http://${endpoint.content.host}:${toString endpoint.content.port}"; } // lib.optionalAttrs (endpoint.content ? websocket && endpoint.content.websocket) { proxyWebsockets = true; }; in acc // { ${key} = previous // { listenPort = proxyBackendPort endpoint; forceSSL = previous.forceSSL || (endpoint.force_ssl or false); locations = previous.locations // { ${endpoint.endpoint} = location; }; }; } ) {} proxyEndpoints; proxyBackendVirtualHosts = lib.mapAttrs (_: hostConfig: { serverName = hostConfig.domain; listen = [ { addr = "127.0.0.1"; port = hostConfig.listenPort; ssl = true; } ]; onlySSL = true; extraConfig = '' absolute_redirect off; port_in_redirect off; ''; locations = hostConfig.locations; } // lib.optionalAttrs (hostConfig.forceSSL && servicesConfig.nginx.acmeEmail != null) { useACMEHost = hostConfig.domain; }) groupedProxyEndpoints; forwardingHttpVirtualHosts = builtins.listToAttrs (map (endpoint: { name = "forward-http-${endpoint.domain}-${toString endpoint.listenPort}"; value = { serverName = endpoint.domain; listen = [ { addr = "0.0.0.0"; port = endpoint.listenPort; } ]; locations."/".proxyPass = "http://${forwardingTarget endpoint}"; }; }) forwardingHttpEndpoints); forwardingHttpDomains = lib.unique (map (endpoint: endpoint.domain) forwardingHttpEndpoints); localServiceAcmeDomains = lib.unique (map (hostConfig: hostConfig.domain) (builtins.attrValues (lib.filterAttrs (_: hostConfig: hostConfig.forceSSL) groupedProxyEndpoints))); localAcmeHttpVirtualHosts = builtins.listToAttrs (map (domain: { name = "acme-http-${domain}-80"; value = { serverName = domain; listen = [ { addr = "0.0.0.0"; port = 80; } ]; locations."^~ /.well-known/acme-challenge/" = { root = "/var/lib/acme/acme-challenge"; extraConfig = '' auth_basic off; auth_request off; ''; }; locations."/" = { return = "301 https://$host$request_uri"; }; }; }) (builtins.filter (domain: !(builtins.elem domain forwardingHttpDomains)) localServiceAcmeDomains)); virtualHosts = forwardingHttpVirtualHosts // localAcmeHttpVirtualHosts // proxyBackendVirtualHosts; streamConfig = lib.concatStringsSep "\n\n" (map renderTlsServer tlsRouteMaps); publicListenPorts = lib.unique (map (endpoint: endpoint.listenPort) endpointsConfig); in { inherit endpointsConfig forwardingEndpoints proxyEndpoints streamConfig virtualHosts proxyOffset publicListenPorts localServiceAcmeDomains; }