{ lib, config, ... }: with lib; let vm-module = { options = { ip = mkOption { type = types.str; }; ssh = mkOption { type = types.nullOr types.ints.unsigned; default = null; }; aliases = mkOption { type = types.listOf types.str; default = [ ]; }; port-forward = mkOption { type = types.listOf types.ints.unsigned; default = [ ]; }; }; }; hypervisor-module = { config, name, ... }: { options = { ip = mkOption { type = types.str; }; host = mkOption { type = types.str; }; vms = mkOption { type = types.attrsOf (types.submodule vm-module); }; port-forward = mkOption { type = types.listOf types.ints.unsigned; default = [ ]; }; domain-list = mkOption { type = types.listOf types.str; internal = true; readOnly = true; }; ports = mkOption { type = types.listOf types.ints.unsigned; internal = true; readOnly = true; }; redirects = mkOption { type = types.unspecified; internal = true; readOnly = true; }; }; config = rec { domain-list = flatten ( mapAttrsToList (main: vm: [ main vm.aliases ]) config.vms ); ports = config.port-forward ++ flatten (mapAttrsToList (_: vm: vm.port-forward ++ optional (!isNull vm.ssh) vm.ssh) config.vms); redirects = { stream = flatten ( mapAttrsToList ( _: vm: optional (!isNull vm.ssh) { input = vm.ssh; out = 22; ip = vm.ip; } ++ map (port: { input = port; out = port; ip = vm.ip; }) vm.port-forward ) config.vms ); http = mapAttrs (_: vm: { inherit (vm) ip aliases; }) config.vms; domain-list = domain-list; }; }; }; entry-module = { config, name, ... }: { options = { host = mkOption { type = types.str; }; hypervisors = mkOption { type = types.attrsOf (types.submodule hypervisor-module); }; redirects = mkOption { type = types.unspecified; internal = true; readOnly = true; }; hosts-redirects = mkOption { type = types.unspecified; internal = true; readOnly = true; }; }; config = rec { redirects = { stream = flatten ( mapAttrsToList ( _: hyp: map (port: { input = port; out = port; ip = hyp.ip; }) hyp.ports ) config.hypervisors ); http = mapAttrs (_: hyp: { ip = hyp.ip; aliases = hyp.domain-list; }) config.hypervisors; domain-list = flatten ( mapAttrsToList (fqdn: hyp: [ fqdn ] ++ hyp.redirects.domain-list) config.hypervisors ); }; hosts-redirects = mergeAttrs (listToAttrs ( mapAttrsToList (main: hyp: { name = hyp.host; value = { fqdn = main; inherit (hyp.redirects) stream http domain-list; }; }) config.hypervisors )) { ${config.host} = { fqdn = name; inherit (redirects) stream http domain-list; }; }; }; }; cfg = config.kat-proxies; hosts-redirects = zipAttrsWith (_: vals: mergeAttrsList vals) ( mapAttrsToList (_: entry: entry.hosts-redirects) cfg.entries ); hostname = config.networking.hostName; redirects = hosts-redirects.${hostname}; in { options.kat-proxies = { enable = mkEnableOption "nginx configuration of proxies"; entries = mkOption { type = types.attrsOf (types.submodule entry-module); }; internal-webroot = mkOption { type = types.package; }; }; config = mkIf cfg.enable { security.acme.certs.${redirects.fqdn}.extraDomainNames = redirects.domain-list; networking.firewall.allowedTCPPorts = [ 80 443 ] ++ map ({ input, ... }: input) redirects.stream; services.nginx = { enable = true; virtualHosts = mapAttrs ( _: { aliases, ip }: { useACMEHost = redirects.fqdn; forceSSL = true; acmeFallbackHost = ip; acmeFallbackRecommendedProxySettings = true; serverAliases = aliases; locations = { "/.${hostname}" = { extraConfig = '' internal; error_page 404 =418 /.${hostname}/error/418.html; ''; root = cfg.internal-webroot; }; "/" = { recommendedProxySettings = true; proxyPass = "https://${ip}/"; extraConfig = '' proxy_set_header Connection '''; proxy_http_version 1.1; chunked_transfer_encoding off; proxy_buffering off; proxy_cache off; error_page 502 =599 "/.${hostname}/error/599.html"; ''; }; }; } ) redirects.http // { ${redirects.fqdn} = { default = true; enableACME = true; addSSL = true; locations = { "/.${hostname}" = { extraConfig = '' internal; error_page 404 =418 /.${hostname}/error/418.html; ''; root = cfg.internal-webroot; }; "/" = { extraConfig = '' return 418; error_page 418 =418 /.${hostname}/error/418.html; ''; }; }; }; }; streamConfig = concatStringsSep "\n" ( map ( { input, ip, out, }: '' server { listen ${toString input}; listen [::]:${toString input}; proxy_pass ${ip}:${toString out}; } '' ) redirects.stream ); }; }; }