diff --git a/lib.nix b/lib.nix index 53a68cd2..5a5fcfa1 100644 --- a/lib.nix +++ b/lib.nix @@ -8,7 +8,7 @@ inherit (nixlib.strings) splitString toLower; inherit (nixlib.lists) imap0 elemAt; inherit (nixlib.attrsets) mapAttrs listToAttrs nameValuePair; - inherit (nixlib.strings) substring fixedWidthString replaceStrings concatMapStringsSep; + inherit (nixlib.strings) hasPrefix hasInfix substring fixedWidthString replaceStrings concatMapStringsSep; inherit (nixlib.trivial) flip toHexString bitOr; toHexStringLower = v: toLower (toHexString v); @@ -35,6 +35,7 @@ mkWinPath = replaceStrings ["/"] ["\\"]; mkBaseDn = domain: concatMapStringsSep "," (part: "dc=${part}") (splitString "." domain); + mkAddress6 = addr: if hasInfix ":" addr && ! hasPrefix "[" addr then "[${addr}]" else addr; mapListToAttrs = f: l: listToAttrs (map f l); @@ -77,7 +78,7 @@ in { lib = { domain = "gensokyo.zone"; inherit treeToModulesOutput userIs - eui64 mkWinPath mkBaseDn + eui64 mkWinPath mkBaseDn mkAddress6 toHexStringLower hexCharToInt mapListToAttrs mkAlmostOptionDefault mkAlmostDefault mkAlmostForce mapOverride mapOptionDefaults mapAlmostOptionDefaults mapDefaults diff --git a/modules/nixos/keycloak.nix b/modules/nixos/keycloak.nix new file mode 100644 index 00000000..3b15701e --- /dev/null +++ b/modules/nixos/keycloak.nix @@ -0,0 +1,20 @@ +{config, lib, ...}: let + inherit (lib.options) mkOption; + inherit (lib.modules) mkOptionDefault; + cfg = config.services.keycloak; +in { + options.services.keycloak = with lib.types; { + protocol = mkOption { + type = enum [ "http" "https" ]; + readOnly = true; + }; + port = mkOption { + type = port; + readOnly = true; + }; + }; + config.services.keycloak = { + protocol = mkOptionDefault (if cfg.sslCertificate != null then "https" else "http"); + port = mkOptionDefault cfg.settings."${cfg.protocol}-port"; + }; +} diff --git a/modules/nixos/network/resolve.nix b/modules/nixos/network/resolve.nix index 451c49ec..6e122f02 100644 --- a/modules/nixos/network/resolve.nix +++ b/modules/nixos/network/resolve.nix @@ -1,6 +1,6 @@ {config, lib, ...}: let inherit (lib.options) mkOption mkEnableOption; - inherit (lib.modules) mkIf mkDefault mkOptionDefault; + inherit (lib.modules) mkIf mkOptionDefault; inherit (lib.lists) filter optional; inherit (lib.strings) hasInfix concatStrings; inherit (config.services) resolved; diff --git a/modules/nixos/nginx/listen.nix b/modules/nixos/nginx/listen.nix index a04da268..6390efaf 100644 --- a/modules/nixos/nginx/listen.nix +++ b/modules/nixos/nginx/listen.nix @@ -4,12 +4,11 @@ inputs, ... }: let - inherit (inputs.self.lib.lib) mkAlmostOptionDefault; + inherit (inputs.self.lib.lib) mkAlmostOptionDefault mkAddress6; inherit (lib.options) mkOption mkEnableOption; inherit (lib.modules) mkIf mkMerge mkBefore mkOptionDefault mkForce; inherit (lib.attrsets) attrValues mapAttrs; inherit (lib.lists) optional filter concatMap; - inherit (lib.strings) hasPrefix hasInfix; inherit (config.services) nginx; listenModule = { config, virtualHost, listenKind, ... }: { options = with lib.types; { @@ -76,9 +75,8 @@ listenConfigs = let # TODO: handle quic listener..? mkListenHost = { addr, port }: let - addr' = if hasInfix ":" addr && !hasPrefix "[" addr then "[${addr}]" else addr; host = - if addr != null then "${addr'}:${toString port}" + if addr != null then "${mkAddress6 addr}:${toString port}" else toString port; in assert port != null; host; mkDirective = addr: let diff --git a/modules/nixos/nginx/ssl.nix b/modules/nixos/nginx/ssl.nix index 6bcbd70e..ea82bdc1 100644 --- a/modules/nixos/nginx/ssl.nix +++ b/modules/nixos/nginx/ssl.nix @@ -73,14 +73,14 @@ cert = let mkCopyCert = copyCert: { name = mkDefault copyCert.name; - keyPath = mkAlmostOptionDefault copyCert.keyPath; - path = mkAlmostOptionDefault copyCert.path; + keyPath = mkDefault copyCert.keyPath; + path = mkDefault copyCert.path; }; copyCertVhost = mkCopyCert nginx.virtualHosts.${cfg.cert.copyFromVhost}.ssl.cert; copyCertStreamServer = mkCopyCert nginx.stream.servers.${cfg.cert.copyFromStreamServer}.ssl.cert; in mkMerge [ - (mkIf (cfg.cert.copyFromVhost != null) copyCertVhost) (mkIf (cfg.cert.copyFromStreamServer != null) copyCertStreamServer) + (mkIf (cfg.cert.copyFromVhost != null) copyCertVhost) ]; }; }; diff --git a/modules/nixos/nginx/stream.nix b/modules/nixos/nginx/stream.nix index 575c5d5a..cde7a320 100644 --- a/modules/nixos/nginx/stream.nix +++ b/modules/nixos/nginx/stream.nix @@ -1,13 +1,14 @@ { config, lib, + gensokyo-zone, ... }: let + inherit (gensokyo-zone.lib) mkAddress6; inherit (lib.options) mkOption mkEnableOption; inherit (lib.modules) mkIf mkMerge mkBefore mkOptionDefault; inherit (lib.attrsets) mapAttrsToList; inherit (lib.lists) optional; - inherit (lib.strings) hasPrefix hasInfix; cfg = config.services.nginx.stream; upstreamServerModule = {config, name, ...}: { options = with lib.types; { @@ -45,9 +46,7 @@ config = let settings = mapAttrsToList (key: value: "${key}=${toString value}") config.settings; in { - server = let - addr = if hasInfix ":" config.addr && ! hasPrefix "[" config.addr then "[${config.addr}]" else config.addr; - in mkOptionDefault "${addr}:${toString config.port}"; + server = mkOptionDefault "${mkAddress6 config.addr}:${toString config.port}"; serverConfig = mkMerge ( [ (mkBefore config.server) ] ++ settings @@ -76,6 +75,9 @@ servers = mkOption { type = attrsOf upstreamServer; }; + ssl = { + enable = mkEnableOption "ssl upstream"; + }; extraConfig = mkOption { type = lines; default = ""; @@ -119,11 +121,35 @@ type = lines; internal = true; }; + proxy = { + upstream = mkOption { + type = nullOr str; + default = null; + }; + url = mkOption { + type = nullOr str; + }; + }; }; config = { - streamConfig = mkMerge [ + proxy = { + url = mkOptionDefault ( + if config.proxy.upstream != null then cfg.upstreams.${config.proxy.upstream}.name + else null + ); + }; + streamConfig = let + proxyUpstream = cfg.upstreams.${config.proxy.upstream}; + in mkMerge [ config.extraConfig + (mkIf (config.proxy.upstream != null && proxyUpstream.ssl.enable) '' + proxy_ssl on; + proxy_ssl_verify off; + '') + (mkIf (config.proxy.url != null) '' + proxy_pass ${config.proxy.url}; + '') ]; serverBlock = mkOptionDefault '' server { diff --git a/modules/system/access.nix b/modules/system/access.nix index 23820e3e..eaaba2e6 100644 --- a/modules/system/access.nix +++ b/modules/system/access.nix @@ -8,10 +8,12 @@ }: let inherit (inputs.self) nixosConfigurations; inherit (inputs.self.lib) systems; - inherit (inputs.self.lib.lib) domain; + inherit (inputs.self.lib.lib) domain mkAddress6; inherit (lib.options) mkOption mkEnableOption; inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault; - inherit (lib.attrsets) mapAttrs; + inherit (lib.attrsets) mapAttrs attrValues; + inherit (lib.lists) findSingle; + inherit (lib.trivial) mapNullable; cfg = config.access; systemConfig = config; systemAccess = access; @@ -42,20 +44,29 @@ config = { networking.access = { moduleArgAttrs = let - mkGetAddressFor = addressForAttr: hostName: network: let + mkGetAddressFor = nameAllowed: addressForAttr: hostName: network: let forSystem = access.systemFor hostName; - err = throw "no lan interface found between ${config.networking.hostName} and ${hostName}"; + err = throw "no interface found between ${config.networking.hostName} -> ${hostName}@${network}"; + fallback = if nameAllowed + then lib.warn "getAddressFor hostname fallback for ${config.networking.hostName} -> ${hostName}@${network}" (access.getHostnameFor hostName network) + else err; + local = forSystem.access.${addressForAttr}.local or forSystem.access.address4ForNetwork.local or fallback; + int = forSystem.access.${addressForAttr}.int or forSystem.access.address4ForNetwork.int or fallback; + tail = forSystem.access.${addressForAttr}.tail or fallback; in { lan = - if has'Int then forSystem.access.${addressForAttr}.int or forSystem.access.${addressForAttr}.local or err - else if has'Local then forSystem.access.${addressForAttr}.local or err - else err; - ${if has'Local then "local" else null} = forSystem.access.${addressForAttr}.local or err; - ${if has'Int then "int" else null} = forSystem.access.${addressForAttr}.int or err; - ${if has'Tail then "tail" else null} = forSystem.access.${addressForAttr}.tail or err; - }.${network} or err; + if hostName == system.name then forSystem.access.${addressForAttr}.localhost + else if has'Int then int + else if has'Local then local + else fallback; + ${if has'Local then "local" else null} = local; + ${if has'Int then "int" else null} = int; + ${if has'Tail then "tail" else null} = tail; + }.${network} or fallback; in { - inherit (systemAccess) hostnameForNetwork address4ForNetwork address6ForNetwork; + inherit (systemAccess) + hostnameForNetwork address4ForNetwork address6ForNetwork + systemForService systemForServiceId; addressForNetwork = systemAccess.${addressForAttr}; systemFor = hostName: if hostName == config.networking.hostName @@ -73,21 +84,39 @@ if hostName == config.networking.hostName then config else systemAccess.nixosForOrNull hostName; - getAddressFor = mkGetAddressFor addressForAttr; - getAddress4For = mkGetAddressFor "address4ForNetwork"; - getAddress6For = mkGetAddressFor "address6ForNetwork"; + getAddressFor = mkGetAddressFor true addressForAttr; + getAddress4For = mkGetAddressFor false "address4ForNetwork"; + getAddress6For = mkGetAddressFor false "address6ForNetwork"; getHostnameFor = hostName: network: let forSystem = access.systemFor hostName; err = throw "no ${network} interface found between ${config.networking.hostName} and ${hostName}"; in { lan = - if has'Int then forSystem.access.hostnameForNetwork.int or forSystem.access.hostnameForNetwork.local or err + if hostName == system.name then forSystem.access.hostnameForNetwork.localhost + else if has'Int then forSystem.access.hostnameForNetwork.int or forSystem.access.hostnameForNetwork.local or err else if has'Local then forSystem.access.hostnameForNetwork.local or err else err; ${if has'Local then "local" else null} = forSystem.access.hostnameForNetwork.local or err; ${if has'Int then "int" else null} = forSystem.access.hostnameForNetwork.int or err; ${if has'Tail then "tail" else null} = forSystem.access.hostnameForNetwork.tail or err; }.${network} or err; + proxyUrlFor = { + system ? if serviceId != null then access.systemForServiceId serviceId else access.systemForService serviceName, + serviceName ? mapNullable (serviceId: (findSingle (s: s.id == serviceId) null null (attrValues system.exports.services)).name) serviceId, + serviceId ? null, + service ? system.exports.services.${serviceName}, + portName ? "default", + network ? "lan", + scheme ? null, + }: let + port = service.ports.${portName}; + scheme' = if scheme == null then port.protocol else scheme; + port' = if !port.enable + then throw "${system.name}.exports.services.${service.name}.ports.${portName} isn't enabled" + else ":${toString port.port}"; + host = access.getAddressFor system.name network; + url = "${scheme'}://${mkAddress6 host}${port'}"; + in assert service.enable; url; }; }; networking.tempAddresses = mkIf cfg.global.enable ( @@ -110,7 +139,6 @@ in { type = str; default = domain; }; - tailscale.enable = mkEnableOption "tailscale access"; global.enable = mkEnableOption "globally routeable"; hostnameForNetwork = mkOption { type = attrsOf str; @@ -143,6 +171,7 @@ in { hostnameForNetwork = mkMerge [ (mapAttrs (_: mapNetworkFqdn) config.network.networks) { + localhost = mkOptionDefault "localhost"; lan = mkMerge [ (mapNetwork' mkDefault "fqdn" int) (mapNetworkFqdn local) @@ -153,6 +182,7 @@ in { address4ForNetwork = mkMerge [ (mapAttrs (_: mapNetwork4) config.network.networks) { + localhost = mkOptionDefault "127.0.0.1"; lan = mkMerge [ (mapNetwork' mkDefault "address4" int) (mapNetwork4 local) @@ -162,6 +192,7 @@ in { address6ForNetwork = mkMerge [ (mapAttrs (_: mapNetwork6) config.network.networks) { + localhost = mkOptionDefault "::1"; lan = mkMerge [ (mapNetwork' mkDefault "address6" int) (mapNetwork6 local) @@ -176,6 +207,16 @@ in { systemForOrNull = hostName: systems.${hostName}.config or null; nixosFor = hostName: nixosConfigurations.${hostName}.config or (access.systemFor hostName).built.config; nixosForOrNull = hostName: nixosConfigurations.${hostName}.config or (access.systemForOrNull hostName).built.config or null; + systemForService = service: let + hasService = system: system.config.exports.services.${service}.enable; + notFound = throw "no system found serving ${service}"; + multiple = throw "multiple systems found serving ${service}"; + in (findSingle hasService notFound multiple (attrValues systems)).config; + systemForServiceId = serviceId: let + hasService = system: findSingle (service: service.id == serviceId && service.enable) null multiple (attrValues system.config.exports.services) != null; + notFound = throw "no system found serving ${serviceId}"; + multiple = throw "multiple systems found serving ${serviceId}"; + in (findSingle hasService notFound multiple (attrValues systems)).config; }; }; } diff --git a/modules/system/exports/dnsmasq.nix b/modules/system/exports/dnsmasq.nix new file mode 100644 index 00000000..dd9dadec --- /dev/null +++ b/modules/system/exports/dnsmasq.nix @@ -0,0 +1,22 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.dnsmasq = { config, ... }: { + id = mkAlmostOptionDefault "dns"; + nixos = { + serviceAttr = "dnsmasq"; + }; + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 53; + transport = "udp"; + }; + tcp = { + port = config.ports.default.port; + transport = "tcp"; + }; + }; + }; +} diff --git a/modules/system/exports/exports.nix b/modules/system/exports/exports.nix new file mode 100644 index 00000000..72981787 --- /dev/null +++ b/modules/system/exports/exports.nix @@ -0,0 +1,48 @@ +{ + config, + name, + lib, + ... +}: let + inherit (lib.options) mkOption mkEnableOption; + cfg = config.exports; + systemConfig = config; + exportModule = { + config, + name, + ... + }: { + options = with lib.types; { + enable = mkEnableOption "exported service"; + name = mkOption { + type = str; + default = name; + }; + serviceName = mkOption { + type = str; + default = name; + }; + id = mkOption { + type = str; + default = cfg.services.${config.serviceName}.id/* or config.name*/; + }; + }; + }; +in { + options.exports = with lib.types; { + exports = mkOption { + type = attrsOf (submoduleWith { + modules = [exportModule]; + specialArgs = { + machine = name; + inherit systemConfig; + }; + }); + default = {}; + }; + }; + + config = { + _module.args.exports = cfg; + }; +} diff --git a/modules/system/exports/freeipa.nix b/modules/system/exports/freeipa.nix new file mode 100644 index 00000000..718ba3f2 --- /dev/null +++ b/modules/system/exports/freeipa.nix @@ -0,0 +1,18 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.freeipa = { + id = mkAlmostOptionDefault "freeipa"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 443; + protocol = "https"; + }; + redirect = { + port = 80; + protocol = "http"; + }; + }; + }; +} diff --git a/modules/system/exports/freepbx.nix b/modules/system/exports/freepbx.nix new file mode 100644 index 00000000..39cf085e --- /dev/null +++ b/modules/system/exports/freepbx.nix @@ -0,0 +1,34 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.freepbx = { + id = mkAlmostOptionDefault "pbx"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + http = { + port = 80; + protocol = "http"; + }; + https = { + port = 443; + protocol = "https"; + }; + ucp = { + port = 8001; + protocol = "http"; + }; + ucp-ssl = { + port = 8003; + protocol = "https"; + }; + asterisk = { + port = 8088; + protocol = "http"; + }; + asterisk-ssl = { + port = 8089; + protocol = "https"; + }; + }; + }; +} diff --git a/modules/system/exports/home-assistant.nix b/modules/system/exports/home-assistant.nix new file mode 100644 index 00000000..3a8e3d6a --- /dev/null +++ b/modules/system/exports/home-assistant.nix @@ -0,0 +1,45 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.modules) mkIf; + inherit (lib.attrsets) mapAttrs; + inherit (lib.lists) all imap0; + inherit (lib.trivial) id; +in { + config.exports.services.home-assistant = { config, ... }: let + mkAssertion = f: nixosConfig: let + cfg = nixosConfig.services.home-assistant; + in f nixosConfig cfg; + assertPort = nixosConfig: cfg: { + assertion = config.ports.default.port == cfg.config.http.server_port; + message = "port mismatch"; + }; + assertHomekitPort = let + portName = i: "homekit${toString i}"; + mkAssertPort = i: homekit: config.ports.${portName i}.port or null == homekit.port; + in nixosConfig: cfg: { + assertion = all id (imap0 mkAssertPort cfg.config.homekit); + message = "homekit port mismatch"; + }; + in { + id = mkAlmostOptionDefault "home"; + nixos = { + serviceAttr = "home-assistant"; + assertions = mkIf config.enable [ + (mkAssertion assertPort) + (mkAssertion assertHomekitPort) + ]; + }; + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 8123; + protocol = "http"; + }; + homekit0 = { + port = 21063; + transport = "tcp"; + }; + # TODO: cast udp port range 32768 to 60999 + }; + }; +} diff --git a/modules/system/exports/invidious.nix b/modules/system/exports/invidious.nix new file mode 100644 index 00000000..147790e1 --- /dev/null +++ b/modules/system/exports/invidious.nix @@ -0,0 +1,21 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.modules) mkIf; +in { + config.exports.services.invidious = { config, ... }: { + id = mkAlmostOptionDefault "yt"; + nixos = { + serviceAttr = "invidious"; + assertions = mkIf config.enable [ + (nixosConfig: { + assertion = config.ports.default.port == nixosConfig.services.invidious.port; + message = "port mismatch"; + }) + ]; + }; + ports.default = mapAlmostOptionDefaults { + port = 3000; + protocol = "http"; + }; + }; +} diff --git a/modules/system/exports/kerberos.nix b/modules/system/exports/kerberos.nix new file mode 100644 index 00000000..f122ee1a --- /dev/null +++ b/modules/system/exports/kerberos.nix @@ -0,0 +1,35 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.kerberos = { config, ... }: { + id = "krb5"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 88; + transport = "tcp"; + }; + udp = { + port = config.ports.default.port; + transport = "udp"; + }; + kadmin = { + port = 749; + transport = "tcp"; + }; + kpasswd = { + port = 464; + transport = "tcp"; + }; + kpasswd-udp = { + port = config.ports.kpasswd.port; + transport = "udp"; + }; + ticket4 = { + enable = false; + port = 4444; + transport = "udp"; + }; + }; + }; +} diff --git a/modules/system/exports/keycloak.nix b/modules/system/exports/keycloak.nix new file mode 100644 index 00000000..3dab812e --- /dev/null +++ b/modules/system/exports/keycloak.nix @@ -0,0 +1,37 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.modules) mkIf; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.keycloak = { config, ... }: { + id = mkAlmostOptionDefault "sso"; + nixos = { + serviceAttr = "keycloak"; + assertions = let + mkAssertion = f: nixosConfig: let + cfg = nixosConfig.services.keycloak; + in f nixosConfig cfg; + in mkIf config.enable [ + (mkAssertion (nixosConfig: cfg: { + assertion = config.ports.${cfg.protocol}.port == cfg.port; + message = "port mismatch"; + })) + (mkAssertion (nixosConfig: cfg: { + assertion = config.ports.${cfg.protocol}.enable; + message = "port enable mismatch"; + })) + ]; + }; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + http = { + enable = !config.ports.https.enable; + port = 8080; + protocol = "http"; + }; + https = { + port = 8443; + protocol = "https"; + }; + }; + }; +} diff --git a/modules/system/exports/ldap.nix b/modules/system/exports/ldap.nix new file mode 100644 index 00000000..be3489b5 --- /dev/null +++ b/modules/system/exports/ldap.nix @@ -0,0 +1,19 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.ldap = { config, ... }: { + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 389; + transport = "tcp"; + }; + ssl = { + port = 636; + ssl = true; + listen = "wan"; + }; + }; + }; +} diff --git a/modules/system/exports/mosquitto.nix b/modules/system/exports/mosquitto.nix new file mode 100644 index 00000000..b180454d --- /dev/null +++ b/modules/system/exports/mosquitto.nix @@ -0,0 +1,38 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.modules) mkIf; + inherit (lib.attrsets) mapAttrs; + inherit (lib.lists) all imap0; + inherit (lib.trivial) id; +in { + config.exports.services.mosquitto = { config, ... }: { + id = mkAlmostOptionDefault "mqtt"; + nixos = { + serviceAttr = "mosquitto"; + assertions = mkIf config.enable [ + (nixosConfig: let + cfg = nixosConfig.services.mosquitto; + portName = i: + if i == 0 then "default" + else "listener${toString i}"; + mkAssertPort = i: listener: config.ports.${portName i}.port or null == listener.port; + in { + assertion = all id (imap0 mkAssertPort cfg.listeners); + message = "port mismatch"; + }) + ]; + }; + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 1883; + transport = "tcp"; + }; + ssl = { + enable = false; + port = 8883; + ssl = true; + }; + }; + }; +} diff --git a/modules/system/exports/motion.nix b/modules/system/exports/motion.nix new file mode 100644 index 00000000..4ba16e88 --- /dev/null +++ b/modules/system/exports/motion.nix @@ -0,0 +1,18 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.motion = { config, ... }: { + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 8080; + protocol = "http"; + }; + stream = { + port = 8081; + protocol = "http"; + }; + }; + }; +} diff --git a/modules/system/exports/nfs.nix b/modules/system/exports/nfs.nix new file mode 100644 index 00000000..2a52afde --- /dev/null +++ b/modules/system/exports/nfs.nix @@ -0,0 +1,77 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.modules) mkIf; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.nfs = { config, ... }: let + mkAssertion = f: nixosConfig: let + cfg = nixosConfig.services.nfs; + in f nixosConfig cfg; + mkAssertionPort = portName: mkAssertion (nixosConfig: cfg: let + portAttr = "${portName}Port"; + in { + assertion = mkAssertPort config.ports.${portName} cfg.server.${portAttr}; + message = "${portAttr} mismatch"; + }); + mkAssertPort = port: cfgPort: let + cmpPort = if port.enable then port.port else null; + in cfgPort == cmpPort; + in { + nixos = { + serviceAttrPath = [ "services" "nfs" "server" ]; + assertions = mkIf config.enable [ + (mkAssertionPort "statd") + (mkAssertionPort "lockd") + (mkAssertionPort "mountd") + (mkAssertion (nixosConfig: cfg: { + assertion = nixosConfig.services.rpcbind.enable == config.ports.rpcbind.enable; + message = "rpcbind enable mismatch"; + })) + ]; + }; + # TODO: expose over wan + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 2049; + transport = "tcp"; + }; + udp = { + port = config.ports.default.port; + transport = "udp"; + }; + rpcbind = { + port = 111; + transport = "tcp"; + }; + rpcbind-udp = { + port = config.ports.rpcbind.port; + transport = "udp"; + }; + statd = { + port = 4000; + transport = "tcp"; + }; + statd-udp = { + port = config.ports.statd.port; + transport = "udp"; + }; + lockd = { + port = 4001; + transport = "tcp"; + }; + lockd-udp = { + port = config.ports.lockd.port; + transport = "udp"; + }; + mountd = { + port = 4002; + transport = "tcp"; + }; + mountd-udp = { + port = config.ports.mountd.port; + transport = "udp"; + }; + }; + }; +} diff --git a/modules/system/exports/plex.nix b/modules/system/exports/plex.nix new file mode 100644 index 00000000..3b289fa6 --- /dev/null +++ b/modules/system/exports/plex.nix @@ -0,0 +1,43 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.plex = { + nixos.serviceAttr = "plex"; + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + default = { + port = 32400; + protocol = "http"; + }; + roku = { + port = 8324; + transport = "tcp"; + }; + dlna-tcp = { + port = 32469; + transport = "tcp"; + }; + dlna-udp = { + port = 1900; + transport = "udp"; + }; + gdm0 = { + port = 32410; + transport = "udp"; + }; + gdm1 = { + port = 32412; + transport = "udp"; + }; + gdm2 = { + port = 32413; + transport = "udp"; + }; + gdm3 = { + port = 32414; + transport = "udp"; + }; + }; + }; +} diff --git a/modules/system/exports/postgresql.nix b/modules/system/exports/postgresql.nix new file mode 100644 index 00000000..29fe1997 --- /dev/null +++ b/modules/system/exports/postgresql.nix @@ -0,0 +1,27 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults; + inherit (lib.modules) mkIf; +in { + config.exports.services.postgresql = { config, ... }: let + mkAssertion = f: nixosConfig: let + cfg = nixosConfig.services.postgresql; + in f nixosConfig cfg; + in { + nixos = { + assertions = mkIf config.enable [ + (mkAssertion (nixosConfig: cfg: { + assertion = config.ports.default.port == cfg.settings.port; + message = "port mismatch"; + })) + (mkAssertion (nixosConfig: cfg: { + assertion = config.ports.default.enable == cfg.enableTCPIP; + message = "enableTCPIP mismatch"; + })) + ]; + }; + ports.default = mapAlmostOptionDefaults { + port = 5432; + transport = "tcp"; + }; + }; +} diff --git a/modules/system/exports/prox.nix b/modules/system/exports/prox.nix new file mode 100644 index 00000000..b82a0270 --- /dev/null +++ b/modules/system/exports/prox.nix @@ -0,0 +1,12 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; +in { + config.exports.services.proxmox = { config, ... }: { + id = mkAlmostOptionDefault "prox"; + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports.default = mapAlmostOptionDefaults { + port = 8006; + protocol = "https"; + }; + }; +} diff --git a/modules/system/exports/samba.nix b/modules/system/exports/samba.nix new file mode 100644 index 00000000..7cf31484 --- /dev/null +++ b/modules/system/exports/samba.nix @@ -0,0 +1,29 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.samba = { + id = mkAlmostOptionDefault "smb"; + nixos.serviceAttr = "samba"; + # TODO: expose over wan + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + port0 = { + port = 137; + transport = "udp"; + }; + port1 = { + port = 138; + transport = "udp"; + }; + port2 = { + port = 139; + transport = "tcp"; + }; + default = { + port = 445; + transport = "tcp"; + }; + }; + }; +} diff --git a/modules/system/exports/services.nix b/modules/system/exports/services.nix new file mode 100644 index 00000000..acf97d9a --- /dev/null +++ b/modules/system/exports/services.nix @@ -0,0 +1,155 @@ +{ + config, + name, + lib, + gensokyo-zone, + ... +}: let + inherit (gensokyo-zone.lib) mkAlmostOptionDefault; + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.modules) mkIf mkMerge mkOptionDefault; + inherit (lib.attrsets) mapAttrsToList getAttrFromPath; + inherit (lib.trivial) mapNullable; + inherit (lib.strings) concatStringsSep; + systemConfig = config; + portModule = {config, service, ...}: { + options = with lib.types; { + enable = + mkEnableOption "port" + // { + default = true; + }; + listen = mkOption { + type = enum ["wan" "lan" "int" "localhost"]; + }; + protocol = mkOption { + type = nullOr (enum ["http" "https"]); + default = null; + }; + transport = mkOption { + type = enum ["tcp" "udp" "unix"]; + }; + path = mkOption { + type = nullOr path; + default = null; + description = "unix socket path"; + }; + ssl = mkOption { + type = bool; + default = false; + }; + port = mkOption { + type = nullOr int; + }; + }; + config = { + transport = mkMerge [ + (mkIf (config.protocol == "http" || config.protocol == "https") (mkOptionDefault "tcp")) + (mkIf config.ssl (mkOptionDefault "tcp")) + ]; + ssl = mkIf (config.protocol == "https") ( + mkAlmostOptionDefault true + ); + listen = mkOptionDefault service.defaults.port.listen; + }; + }; + serviceModule = { + config, + name, + ... + }: { + options = with lib.types; { + enable = mkEnableOption "hosted service"; + name = mkOption { + type = str; + default = name; + }; + id = mkOption { + type = str; + default = config.name; + }; + ports = mkOption { + type = attrsOf (submoduleWith { + modules = [portModule]; + specialArgs = { + service = config; + }; + }); + }; + nixos = { + serviceAttr = mkOption { + type = nullOr str; + default = null; + }; + serviceAttrPath = mkOption { + type = nullOr (listOf str); + }; + assertions = mkOption { + type = listOf (functionTo attrs); + default = [ ]; + }; + }; + defaults = { + port = { + listen = mkOption { + type = str; + default = "int"; + }; + }; + }; + }; + config = { + nixos = { + serviceAttrPath = mkOptionDefault ( + mapNullable (serviceAttr: ["services" config.nixos.serviceAttr]) config.nixos.serviceAttr + ); + assertions = let + serviceConfig = getAttrFromPath config.nixos.serviceAttrPath; + mkAssertion = f: nixosConfig: let + cfg = serviceConfig nixosConfig; + in f nixosConfig cfg; + enableAssertion = nixosConfig: cfg: { + assertion = (! cfg ? enable) || (config.enable == cfg.enable); + message = "enable == nixosConfig.${concatStringsSep "." config.nixos.serviceAttrPath}.enable"; + }; + in [ + (mkIf (config.nixos.serviceAttrPath != null) ( + mkAssertion enableAssertion + )) + ]; + }; + }; + }; + nixosModule = {config, system, ...}: let + mapAssertion = service: a: let + res = a config; + in res // { + message = "system.exports.${service.name}: " + res.message or "assertion failed"; + }; + assertions = mapAttrsToList (_: service: map (mapAssertion service) service.nixos.assertions) system.exports.services; + in { + config = { + assertions = mkMerge assertions; + # TODO: export ports via firewall according to enable/listen/etc + }; + }; +in { + options.exports = with lib.types; { + services = mkOption { + type = attrsOf (submoduleWith { + modules = [serviceModule]; + specialArgs = { + machine = name; + inherit systemConfig; + }; + }); + default = {}; + }; + }; + + config = { + modules = mkIf (config.type == "NixOS") [ + nixosModule + ]; + }; +} diff --git a/modules/system/exports/tailscale.nix b/modules/system/exports/tailscale.nix new file mode 100644 index 00000000..ccaa97f7 --- /dev/null +++ b/modules/system/exports/tailscale.nix @@ -0,0 +1,7 @@ +{...}: { + config.exports.services.tailscale = { + id = "tail"; + nixos.serviceAttr = "tailscale"; + ports = {}; + }; +} diff --git a/modules/system/exports/unifi.nix b/modules/system/exports/unifi.nix new file mode 100644 index 00000000..59c75ba3 --- /dev/null +++ b/modules/system/exports/unifi.nix @@ -0,0 +1,47 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.attrsets) mapAttrs; +in { + config.exports.services.unifi = { config, ... }: { + nixos.serviceAttr = "unifi"; + defaults.port.listen = mkAlmostOptionDefault "lan"; + ports = mapAttrs (_: mapAlmostOptionDefaults) { + management = { + # remote login + port = 8443; + protocol = "https"; + listen = "int"; + }; + uap = { + # UAP to inform controller + port = 8080; + transport = "tcp"; + }; + portal = { + # HTTP portal redirect, if guest portal is enabled + port = 8880; + protocol = "http"; + }; + portal-secure = { + # HTTPS portal redirect + port = 8843; + protocol = "https"; + }; + speedtest = { + # UniFi mobile speed test + port = 6789; + transport = "tcp"; + }; + stun = { + port = 3478; + transport = "udp"; + listen = "wan"; + }; + discovery = { + # device discovery + port = 10001; + transport = "udp"; + }; + }; + }; +} diff --git a/modules/system/exports/vouch.nix b/modules/system/exports/vouch.nix new file mode 100644 index 00000000..b3324771 --- /dev/null +++ b/modules/system/exports/vouch.nix @@ -0,0 +1,22 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.modules) mkIf; +in { + config.exports.services.vouch-proxy = { config, ... }: { + id = mkAlmostOptionDefault "login"; + defaults.port.listen = mkAlmostOptionDefault "localhost"; + nixos = { + serviceAttr = "vouch-proxy"; + assertions = mkIf config.enable [ + (nixosConfig: { + assertion = config.ports.default.port == nixosConfig.services.vouch-proxy.settings.vouch.port; + message = "port mismatch"; + }) + ]; + }; + ports.default = mapAlmostOptionDefaults { + port = 30746; + protocol = "http"; + }; + }; +} diff --git a/modules/system/exports/zigbee2mqtt.nix b/modules/system/exports/zigbee2mqtt.nix new file mode 100644 index 00000000..00fcbbd2 --- /dev/null +++ b/modules/system/exports/zigbee2mqtt.nix @@ -0,0 +1,21 @@ +{lib, gensokyo-zone, ...}: let + inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault; + inherit (lib.modules) mkIf; +in { + config.exports.services.zigbee2mqtt = { config, ... }: { + id = mkAlmostOptionDefault "z2m"; + nixos = { + serviceAttr = "zigbee2mqtt"; + assertions = mkIf config.enable [ + (nixosConfig: { + assertion = config.ports.default.port == nixosConfig.services.zigbee2mqtt.settings.frontend.port; + message = "port mismatch"; + }) + ]; + }; + ports.default = mapAlmostOptionDefaults { + port = 8072; + protocol = "http"; + }; + }; +} diff --git a/modules/system/host.nix b/modules/system/host.nix index 55f6aab9..ae5e76c0 100644 --- a/modules/system/host.nix +++ b/modules/system/host.nix @@ -15,6 +15,11 @@ in { inherit (lib.types) str listOf attrs unspecified enum; inherit (lib.options) mkOption; in { + name = mkOption { + type = str; + default = name; + readOnly = true; + }; arch = mkOption { description = "Processor architecture of the host"; type = str; diff --git a/nixos/access/freeipa.nix b/nixos/access/freeipa.nix index d2bb1dcd..959eb4c8 100644 --- a/nixos/access/freeipa.nix +++ b/nixos/access/freeipa.nix @@ -6,7 +6,7 @@ }: let inherit (lib.options) mkOption mkEnableOption; - inherit (lib.modules) mkIf mkMerge mkBefore mkDefault; + inherit (lib.modules) mkIf mkMerge mkBefore mkDefault mkOptionDefault; inherit (lib.strings) optionalString concatStringsSep; inherit (config.services) tailscale; inherit (config.services) nginx; @@ -86,6 +86,10 @@ in { default = 4444; }; kpasswd = mkOption { + type = port; + default = 464; + }; + kadmin = mkOption { type = port; default = 749; }; @@ -108,46 +112,58 @@ in { }; config = { services.nginx = { - access.ldap = { - enable = mkDefault true; - host = mkDefault access.host; - port = mkDefault 389; - sslPort = mkDefault access.ldapPort; - useACMEHost = mkDefault virtualHosts.freeipa.ssl.cert.name; - bind.sslPort = mkIf access.preread.enable (mkDefault access.preread.ldapPort); + access.freeipa = { + host = mkOptionDefault (config.lib.access.getAddressFor (config.lib.access.systemForService "freeipa").name "lan"); }; resolver.addresses = mkIf access.preread.enable (mkMerge [ (mkDefault [ "[::1]:5353" "127.0.0.1:5353" ]) (mkIf config.systemd.network.enable [ "127.0.0.53" ]) ]); defaultSSLListenPort = mkIf access.preread.enable access.preread.port; - streamConfig = let - upstreams' = { - freeipa = "${access.host}:${toString access.port}"; - ldap_freeipa = "${nginx.access.ldap.host}:${toString nginx.access.ldap.sslPort}"; - ldap = "localhost:${toString nginx.access.ldap.bind.sslPort}"; - nginx = "localhost:${toString nginx.defaultSSLListenPort}"; - samba = if config.services.samba.enable - then "localhost:445" - else "smb.local.${config.networking.domain}:445"; + stream = { + upstreams = { + freeipa.servers.access = let + system = config.lib.access.systemForService "freeipa"; + inherit (system.exports.services) freeipa; + in { + addr = mkDefault (config.lib.access.getAddressFor system.name "lan"); + port = mkOptionDefault freeipa.ports.default.port; + }; + samba_access.servers.access = let + system = config.lib.access.systemForService "samba"; + inherit (system.exports.services) samba; + in { + addr = mkDefault (config.lib.access.getAddressFor system.name "lan"); + port = mkOptionDefault samba.ports.default.port; + }; + ldaps_access.servers.access = { + addr = mkDefault "localhost"; + port = mkOptionDefault nginx.stream.servers.ldap.listen.ldaps.port; + }; + nginx.servers.access = { + addr = mkDefault "localhost"; + port = mkOptionDefault nginx.defaultSSLListenPort; + }; + }; + servers = { + ldap = { + listen = { + ldaps.port = mkIf access.preread.enable (mkDefault access.preread.ldapPort); + }; + proxy.upstream = mkDefault "ldap"; + ssl.cert.copyFromVhost = mkDefault "freeipa"; + }; + }; + }; + streamConfig = let + upstreams = { + freeipa = "freeipa"; + ldap = "ldaps_access"; + ldap_freeipa = "ldaps"; + samba = "samba_access"; + nginx = "nginx"; }; - upstreams = builtins.mapAttrs (name: _: name) upstreams'; preread = '' - upstream freeipa { - server ${upstreams'.freeipa}; - } - upstream ldap_freeipa { - server ${upstreams'.ldap_freeipa}; - } - upstream ldap { - server ${upstreams'.ldap}; - } - upstream samba { - server ${upstreams'.samba}; - } - upstream nginx { - server ${upstreams'.nginx}; - } map $ssl_preread_server_name $ssl_server_name { hostnames; ${virtualHosts.freeipa.serverName} ${upstreams.freeipa}; @@ -204,6 +220,11 @@ in { listen [::]:${toString access.kerberos.ports.kpasswd} udp; proxy_pass ${access.host}:${toString access.kerberos.ports.kpasswd}; } + server { + listen 0.0.0.0:${toString access.kerberos.ports.kadmin}; + listen [::]:${toString access.kerberos.ports.kadmin}; + proxy_pass ${access.host}:${toString access.kerberos.ports.kadmin}; + } ''; in mkMerge [ (mkIf access.preread.enable preread) @@ -265,6 +286,7 @@ in { (mkIf access.kerberos.enable [ access.kerberos.ports.ticket access.kerberos.ports.kpasswd + access.kerberos.ports.kadmin ]) (mkIf access.preread.enable [ 636 diff --git a/nixos/access/freepbx.nix b/nixos/access/freepbx.nix index 4d96b035..54d72024 100644 --- a/nixos/access/freepbx.nix +++ b/nixos/access/freepbx.nix @@ -1,49 +1,21 @@ { config, + access, lib, ... }: let - inherit (lib.options) mkOption; inherit (lib.modules) mkIf mkDefault; - inherit (lib.lists) head optional; - inherit (lib.strings) splitString; + inherit (lib.lists) optional; inherit (config.services) nginx; - access = nginx.access.freepbx; - hasSsl = nginx.virtualHosts.freepbx'ucp.listen'.ucpSsl.enable; + system = access.systemForService "freepbx"; + inherit (system.exports.services) freepbx; in { - options.services.nginx.access.freepbx = with lib.types; { - host = mkOption { - type = str; - default = config.lib.access.getHostnameFor "freepbx" "lan"; - }; - url = mkOption { - type = str; - default = "https://${access.host}"; - }; - asteriskPort = mkOption { - type = port; - default = 8088; - }; - asteriskSslPort = mkOption { - type = port; - default = 8089; - }; - ucpPort = mkOption { - type = port; - default = 8001; - }; - ucpSslPort = mkOption { - type = port; - default = 8003; - }; - ucpUrl = mkOption { - type = str; - default = "https://${access.host}:${toString access.ucpSslPort}"; - }; - }; config.services.nginx = { virtualHosts = let - proxyScheme = head (splitString ":" access.url); + proxyScheme = "https"; + url = access.proxyUrlFor { serviceName = "freepbx"; portName = proxyScheme; }; + ucpUrl = access.proxyUrlFor { serviceName = "freepbx"; portName = "ucp-ssl"; }; + # TODO: ports.asterisk/asterisk-ssl? extraConfig = '' proxy_buffer_size 128k; proxy_buffers 4 256k; @@ -57,11 +29,11 @@ in { ''; locations = { "/" = { - proxyPass = access.url; + proxyPass = mkDefault url; }; "/socket.io" = { proxy.websocket.enable = true; - proxyPass = "${access.ucpUrl}/socket.io"; + proxyPass = mkDefault "${ucpUrl}/socket.io"; extraConfig = '' proxy_hide_header Access-Control-Allow-Origin; add_header Access-Control-Allow-Origin $pbx_scheme://$host; @@ -81,11 +53,11 @@ in { ssl.cert.copyFromVhost = "freepbx"; listen' = { ucp = { - port = access.ucpPort; + port = mkDefault freepbx.ports.ucp.port; extraParameters = [ "default_server" ]; }; ucpSsl = { - port = access.ucpSslPort; + port = mkDefault freepbx.ports.ucp-ssl.port; ssl = true; extraParameters = [ "default_server" ]; }; @@ -93,8 +65,9 @@ in { proxy.websocket.enable = true; vouch.enable = mkDefault true; local.denyGlobal = mkDefault nginx.virtualHosts.freepbx.local.denyGlobal; - locations = { - inherit (locations) "/socket.io"; + locations."/socket.io" = { + inherit (locations."/socket.io") proxy extraConfig; + proxyPass = mkDefault nginx.virtualHosts.freepbx.locations."/socket.io".proxyPass; }; inherit extraConfig kTLS; }; @@ -103,23 +76,34 @@ in { http = { }; https.ssl = true; ucp = { - port = access.ucpPort; + port = mkDefault nginx.virtualHosts.freepbx'ucp.listen'.ucp.port; }; ucpSsl = { - port = access.ucpSslPort; + port = mkDefault nginx.virtualHosts.freepbx'ucp.listen'.ucpSsl.port; ssl = true; }; }; ssl.cert.copyFromVhost = "freepbx"; local.enable = true; - inherit name locations extraConfig kTLS; + locations = { + "/" = { + proxyPass = mkDefault nginx.virtualHosts.freepbx.locations."/".proxyPass; + }; + "/socket.io" = { + inherit (locations."/socket.io") proxy extraConfig; + proxyPass = mkDefault nginx.virtualHosts.freepbx.locations."/socket.io".proxyPass; + }; + }; + inherit name extraConfig kTLS; }; }; }; config.networking.firewall = let - websocketPorts = [access.ucpPort] ++ optional hasSsl access.ucpSslPort; + websocketPorts = virtualHost: [ + virtualHost.listen'.ucp.port + ] ++ optional virtualHost.listen'.ucpSsl.enable virtualHost.listen'.ucpSsl.port; in { - interfaces.local.allowedTCPPorts = websocketPorts; - allowedTCPPorts = mkIf (!nginx.virtualHosts.freepbx'ucp.local.denyGlobal) websocketPorts; + interfaces.local.allowedTCPPorts = websocketPorts nginx.virtualHosts.freepbx'local; + allowedTCPPorts = mkIf (!nginx.virtualHosts.freepbx'ucp.local.denyGlobal) (websocketPorts nginx.virtualHosts.freepbx'ucp); }; } diff --git a/nixos/access/home-assistant.nix b/nixos/access/home-assistant.nix index d3d0be9f..4b18f65b 100644 --- a/nixos/access/home-assistant.nix +++ b/nixos/access/home-assistant.nix @@ -1,9 +1,10 @@ { config, lib, + access, ... }: let - inherit (lib.modules) mkIf mkDefault; + inherit (lib.modules) mkDefault; inherit (config.services) home-assistant nginx; name.shortServer = mkDefault "home"; listen' = { @@ -24,8 +25,9 @@ in { websocket.enable = true; headers.enableRecommended = true; }; - proxyPass = mkIf home-assistant.enable (mkDefault - "http://localhost:${toString home-assistant.config.http.server_port}" + proxyPass = mkDefault ( + if home-assistant.enable then "http://localhost:${toString home-assistant.config.http.server_port}" + else access.proxyUrlFor { serviceName = "home-assistant"; } ); }; }; diff --git a/nixos/access/invidious.nix b/nixos/access/invidious.nix index 5999e02b..7b1cb1ac 100644 --- a/nixos/access/invidious.nix +++ b/nixos/access/invidious.nix @@ -1,9 +1,10 @@ { config, + access, lib, ... }: let - inherit (lib.modules) mkIf mkMerge mkBefore mkDefault; + inherit (lib.modules) mkMerge mkBefore mkDefault; inherit (lib.strings) replaceStrings concatStringsSep concatMapStringsSep escapeRegex; inherit (config.services.nginx) virtualHosts; cfg = config.services.invidious; @@ -86,8 +87,9 @@ in { location { vouch.requireAuth = true; - proxyPass = mkIf cfg.enable ( - mkDefault "http://localhost:${toString cfg.port}" + proxyPass = mkDefault (if cfg.enable + then "http://localhost:${toString cfg.port}" + else access.proxyUrlFor { serviceName = "invidious"; } ); } ]; diff --git a/nixos/access/keycloak.nix b/nixos/access/keycloak.nix index e1371a79..d92e0696 100644 --- a/nixos/access/keycloak.nix +++ b/nixos/access/keycloak.nix @@ -1,9 +1,10 @@ { config, lib, + access, ... }: let - inherit (lib.modules) mkIf mkDefault; + inherit (lib.modules) mkDefault; cfg = config.services.keycloak; inherit (config.services) nginx; in { @@ -13,11 +14,11 @@ in { name.shortServer = mkDefault "sso"; ssl.force = mkDefault true; locations."/".proxyPass = let - url = mkDefault (if cfg.sslCertificate != null - then "https://localhost:${toString cfg.settings.https-port}" - else "http://localhost:${toString cfg.settings.http-port}" - ); - in mkIf cfg.enable (mkDefault url); + url = mkDefault "${cfg.protocol}://localhost:${toString cfg.port}"; + in mkDefault ( + if cfg.enable then url + else access.proxyUrlFor { serviceName = "keycloak"; portName = "https"; } + ); }; keycloak'local = { name.shortServer = mkDefault "sso"; diff --git a/nixos/access/kitchencam.nix b/nixos/access/kitchencam.nix index 9c1810ad..8b8d15e5 100644 --- a/nixos/access/kitchencam.nix +++ b/nixos/access/kitchencam.nix @@ -1,60 +1,44 @@ { config, lib, + access, ... }: let inherit (lib.options) mkOption; - inherit (lib.modules) mkIf mkMerge mkDefault; - inherit (lib.lists) concatMap; + inherit (lib.modules) mkDefault; + inherit (lib.attrsets) mapAttrs; inherit (config.services) nginx; - inherit (config.services.nginx) virtualHosts; - access = config.services.nginx.access.kitchencam; + system = access.systemForServiceId "kitchen"; + inherit (system.exports.services) motion; in { - options.services.nginx.access.kitchencam = with lib.types; { - streamPort = mkOption { - type = port; - default = 8081; - }; - host = mkOption { - type = str; - default = "kitchencam.local.${config.networking.domain}"; - }; - url = mkOption { - type = str; - default = "http://${access.host}:8080"; - }; - streamUrl = mkOption { - type = str; - default = "http://${access.host}:${toString access.streamPort}"; - }; - useACMEHost = mkOption { - type = nullOr str; - default = null; - }; - }; config.services.nginx = { virtualHosts = let + url = access.proxyUrlFor { inherit system; service = motion; }; + streamUrl = access.proxyUrlFor { inherit system; service = motion; portName = "stream"; }; extraConfig = '' proxy_redirect off; proxy_buffering off; ''; locations = { "/" = { - proxyPass = access.url; + proxyPass = mkDefault url; }; "~ ^/[0-9]+/(stream|motion|substream|current|source|status\\.json)$" = { - proxyPass = access.streamUrl; + proxyPass = mkDefault streamUrl; inherit extraConfig; }; "~ ^/(stream|motion|substream|current|source|cameras\\.json|status\\.json)$" = { - proxyPass = access.streamUrl; + proxyPass = mkDefault streamUrl; inherit extraConfig; }; }; listen' = { http = { }; https.ssl = true; - stream.port = mkDefault access.streamPort; + stream = { + enable = mkDefault motion.ports.stream.enable; + port = mkDefault motion.ports.stream.port; + }; }; name.shortServer = mkDefault "kitchen"; kTLS = mkDefault true; @@ -64,13 +48,18 @@ in { vouch.enable = true; }; kitchencam'local = { - inherit name locations listen' kTLS; + inherit name listen' kTLS; ssl.cert.copyFromVhost = "kitchencam"; local.enable = true; + locations = mapAttrs (name: location: location // { + proxyPass = mkDefault nginx.virtualHosts.kitchencam.locations.${name}.proxyPass; + }) locations; }; }; }; - config.networking.firewall.allowedTCPPorts = [ - access.streamPort + config.networking.firewall.allowedTCPPorts = let + inherit (nginx.virtualHosts.kitchencam) listen'; + in [ + listen'.stream.port ]; } diff --git a/nixos/access/ldap.nix b/nixos/access/ldap.nix index f1c1027a..5e45216d 100644 --- a/nixos/access/ldap.nix +++ b/nixos/access/ldap.nix @@ -1,32 +1,21 @@ { config, lib, + gensokyo-zone, + access, ... }: let - inherit (lib.options) mkOption mkEnableOption; - inherit (lib.modules) mkIf mkMerge; - inherit (lib.strings) concatMapStringsSep optionalString; - inherit (config.services.nginx) virtualHosts; - inherit (config.networking.access) cidrForNetwork localaddrs; - access = config.services.nginx.access.ldap; + inherit (gensokyo-zone.lib) mkAlmostOptionDefault; + inherit (lib.options) mkOption; + inherit (lib.modules) mkIf mkOptionDefault; + inherit (config.services) nginx; portPlaintext = 389; portSsl = 636; - allows = let - mkAllow = cidr: "allow ${cidr};"; - allows = concatMapStringsSep "\n" mkAllow cidrForNetwork.allLocal.all + optionalString localaddrs.enable '' - include ${localaddrs.stateDir}/*.nginx.conf; - ''; - in '' - ${allows} - deny all; - ''; + system = access.systemForService "ldap"; + inherit (system.exports.services) ldap; in { options.services.nginx.access.ldap = with lib.types; { - enable = mkEnableOption "LDAP proxy"; - host = mkOption { - type = str; - }; domain = mkOption { type = str; default = "ldap.${config.networking.domain}"; @@ -43,67 +32,49 @@ in { type = str; default = "ldap.tail.${config.networking.domain}"; }; - port = mkOption { - type = port; - default = portSsl; - }; - sslPort = mkOption { - type = port; - default = portSsl; - }; - bind = { - sslPort = mkOption { - type = port; - default = portSsl; - }; - port = mkOption { - type = port; - default = portPlaintext; - }; - }; - useACMEHost = mkOption { - type = nullOr str; - default = virtualHosts.${access.domain}.useACMEHost or null; - }; }; config = { services.nginx = { - streamConfig = let - cert = config.security.acme.certs.${access.useACMEHost}; - proxySsl = port: optionalString (port == portSsl) '' - proxy_ssl on; - proxy_ssl_verify off; - ''; - in mkIf access.enable (mkMerge [ - '' - server { - listen 0.0.0.0:${toString access.bind.port}; - listen [::]:${toString access.bind.port}; - ${allows} - proxy_pass ${access.host}:${toString access.port}; - ${proxySsl access.port} - } - '' - (mkIf (access.useACMEHost != null) '' - server { - listen 0.0.0.0:${toString access.bind.sslPort} ssl; - listen [::]:${toString access.bind.sslPort} ssl; - ssl_certificate ${cert.directory}/fullchain.pem; - ssl_certificate_key ${cert.directory}/key.pem; - ssl_trusted_certificate ${cert.directory}/chain.pem; - proxy_pass ${access.host}:${toString access.sslPort}; - ${proxySsl access.sslPort} - } - '') - ]); + stream = { + upstreams = let + addr = mkAlmostOptionDefault (access.getAddressFor system.name "lan"); + in { + ldap.servers.access = { + inherit addr; + port = mkOptionDefault ldap.ports.default.port; + }; + ldaps = { + enable = mkAlmostOptionDefault ldap.ports.ssl.enable; + ssl.enable = mkAlmostOptionDefault true; + servers.access = { + inherit addr; + port = mkOptionDefault ldap.ports.ssl.port; + }; + }; + }; + servers.ldap = { + listen = { + ldap.port = mkOptionDefault portPlaintext; + ldaps = { + port = mkOptionDefault portSsl; + ssl = true; + }; + }; + proxy.upstream = mkAlmostOptionDefault ( + if nginx.stream.upstreams.ldaps.enable then "ldaps" else "ldap" + ); + }; + }; }; - networking.firewall = { + networking.firewall = let + inherit (nginx.stream.servers.ldap) listen; + in { interfaces.local.allowedTCPPorts = [ - access.bind.port + listen.ldap.port ]; - allowedTCPPorts = [ - access.bind.sslPort + allowedTCPPorts = mkIf listen.ldaps.enable [ + listen.ldaps.port ]; }; }; diff --git a/nixos/access/mosquitto.nix b/nixos/access/mosquitto.nix index 84345f34..efb92519 100644 --- a/nixos/access/mosquitto.nix +++ b/nixos/access/mosquitto.nix @@ -1,72 +1,59 @@ { config, lib, - inputs, + access, + gensokyo-zone, ... }: let - inherit (inputs.self.lib.lib) mkAlmostOptionDefault; - inherit (lib.options) mkOption mkEnableOption; - inherit (lib.modules) mkIf mkMerge mkOptionDefault; + inherit (gensokyo-zone.lib) mkAlmostOptionDefault; + inherit (lib.modules) mkIf mkOptionDefault; inherit (config.services) nginx; - access = nginx.access.mosquitto; portPlaintext = 1883; portSsl = 8883; + system = access.systemForService "mosquitto"; + inherit (system.exports.services) mosquitto; in { - options.services.nginx.access.mosquitto = with lib.types; { - enable = mkEnableOption "MQTT proxy"; - host = mkOption { - type = str; - }; - port = mkOption { - type = port; - default = portPlaintext; - }; - bind = { - sslPort = mkOption { - type = port; - default = portSsl; - }; - port = mkOption { - type = port; - default = portPlaintext; - }; - }; - }; config = { services.nginx = { stream = { - upstreams.mosquitto = { - servers.access = { - addr = mkAlmostOptionDefault access.host; - port = mkOptionDefault access.port; + upstreams = let + addr = mkAlmostOptionDefault (access.getAddressFor system.name "lan"); + in { + mqtt.servers.access = { + inherit addr; + port = mkOptionDefault mosquitto.ports.default.port; + }; + mqtts = { + enable = mkAlmostOptionDefault mosquitto.ports.ssl.enable; + ssl.enable = true; + servers.access = { + inherit addr; + port = mkOptionDefault mosquitto.ports.ssl.port; + }; }; }; servers.mosquitto = { listen = { - mqtt.port = portPlaintext; + mqtt.port = mkOptionDefault portPlaintext; mqtts = { ssl = true; - port = portSsl; + port = mkOptionDefault portSsl; }; }; - extraConfig = let - proxySsl = port: mkIf (port == portSsl) '' - proxy_ssl on; - proxy_ssl_verify off; - ''; - in mkMerge [ - "proxy_pass ${nginx.stream.upstreams.mosquitto.name};" - (proxySsl access.port) - ]; + proxy.upstream = mkAlmostOptionDefault ( + if nginx.stream.upstreams.mqtts.enable then "mqtts" else "mqtt" + ); }; }; }; networking.firewall = { - interfaces.local.allowedTCPPorts = [ - access.bind.port - (mkIf nginx.stream.servers.mosquitto.listen.mqtts.enable access.bind.sslPort) + interfaces.local.allowedTCPPorts = let + inherit (nginx.stream.servers.mosquitto) listen; + in [ + listen.mqtt.port + (mkIf listen.mqtts.enable listen.mqtts.port) ]; }; }; diff --git a/nixos/access/plex.nix b/nixos/access/plex.nix index 1ac57768..24a314fb 100644 --- a/nixos/access/plex.nix +++ b/nixos/access/plex.nix @@ -1,27 +1,14 @@ { config, lib, + access, ... }: let - inherit (lib.options) mkOption; - inherit (lib.modules) mkIf mkDefault mkOptionDefault; + inherit (lib.modules) mkIf mkDefault; inherit (config.services) nginx; cfg = config.services.plex; - access = nginx.access.plex; in { - options.services.nginx.access.plex = with lib.types; { - url = mkOption { - type = str; - }; - externalPort = mkOption { - type = nullOr port; - default = null; - }; - }; config.services.nginx = { - access.plex = mkIf cfg.enable { - url = mkOptionDefault "http://localhost:${toString cfg.port}"; - }; virtualHosts = let extraConfig = '' # Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause @@ -44,8 +31,11 @@ in { proxy_buffering off; ''; locations."/" = { - proxy.websocket.enable = true; - proxyPass = access.url; + proxy.websocket.enable = mkDefault true; + proxyPass = mkDefault (if cfg.enable + then "http://localhost:${toString cfg.port}" + else access.proxyUrlFor { serviceName = "plex"; } + ); }; name.shortServer = mkDefault "plex"; kTLS = mkDefault true; @@ -56,8 +46,8 @@ in { http = { }; https.ssl = true; external = { - enable = mkDefault (access.externalPort != null); - port = mkDefault access.externalPort; + enable = mkDefault false; + port = mkDefault 32400; extraParameters = [ "default_server" ]; }; }; @@ -69,7 +59,9 @@ in { }; }; }; - config.networking.firewall.allowedTCPPorts = mkIf (access.externalPort != null) [ - access.externalPort + config.networking.firewall.allowedTCPPorts = let + inherit (nginx.virtualHosts.plex) listen'; + in mkIf listen'.external.enable [ + listen'.external.port ]; } diff --git a/nixos/access/proxmox.nix b/nixos/access/proxmox.nix index dadab43d..3fa4932c 100644 --- a/nixos/access/proxmox.nix +++ b/nixos/access/proxmox.nix @@ -1,12 +1,13 @@ { config, lib, + access, ... }: let - inherit (lib.modules) mkMerge mkDefault; + inherit (lib.modules) mkDefault; inherit (lib.strings) escapeRegex; inherit (config.services) nginx tailscale; - proxyPass = "https://reisen.local.${config.networking.domain}:8006/"; + proxyPass = access.proxyUrlFor { serviceName = "proxmox"; } + "/"; in { config.services.nginx.virtualHosts = let locations."/" = { diff --git a/nixos/access/unifi.nix b/nixos/access/unifi.nix index 608c7e13..27592e81 100644 --- a/nixos/access/unifi.nix +++ b/nixos/access/unifi.nix @@ -1,70 +1,37 @@ { config, lib, + access, ... }: let - inherit (lib.options) mkOption mkEnableOption; - inherit (lib.modules) mkIf mkDefault mkOptionDefault; - inherit (config.services) nginx tailscale unifi; - access = nginx.access.unifi; + inherit (lib.modules) mkDefault; + inherit (config.services) nginx; in { - options.services.nginx.access.unifi = with lib.types; { - global = { - management = mkEnableOption "global management port access"; - }; - host = mkOption { - type = str; - }; - url = mkOption { - type = str; - default = "https://${access.host}:${toString access.managementPort}"; - }; - managementPort = mkOption { - type = port; - default = 8443; - }; - }; config.services.nginx = { - access.unifi = mkIf unifi.enable { - host = mkOptionDefault "localhost"; - }; virtualHosts = let extraConfig = '' proxy_redirect off; proxy_buffering off; ''; - locations."/" = { - proxyPass = mkDefault access.url; - }; name.shortServer = mkDefault "unifi"; kTLS = mkDefault true; in { - unifi'management = mkIf access.global.management { - listen'.management = { - port = access.managementPort; - ssl = true; - extraParameters = [ "default_server" ]; - }; - ssl = { - force = true; - cert.copyFromVhost = "unifi"; - }; - inherit name locations extraConfig kTLS; - }; unifi = { - inherit name locations extraConfig kTLS; + inherit name extraConfig kTLS; vouch.enable = mkDefault true; ssl.force = mkDefault true; + locations."/" = { + proxyPass = mkDefault (access.proxyUrlFor { serviceName = "unifi"; portName = "management"; }); + }; }; unifi'local = { - inherit name locations extraConfig kTLS; + inherit name extraConfig kTLS; ssl.cert.copyFromVhost = "unifi"; local.enable = true; + locations."/" = { + proxyPass = mkDefault nginx.virtualHosts.unifi.locations."/".proxyPass; + }; }; }; }; - config.networking.firewall = { - interfaces.local.allowedTCPPorts = [access.managementPort]; - allowedTCPPorts = mkIf access.global.management [access.managementPort]; - }; } diff --git a/nixos/access/vouch.nix b/nixos/access/vouch.nix index aa2a48f5..124fee04 100644 --- a/nixos/access/vouch.nix +++ b/nixos/access/vouch.nix @@ -1,23 +1,16 @@ { config, lib, + access, ... }: let - inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault; + inherit (lib.modules) mkIf mkMerge mkDefault; inherit (config) networking; inherit (config.services) tailscale nginx; cfg = config.services.vouch-proxy; in { config.services.nginx = { virtualHosts = let - localVouchUrl = let - inherit (cfg.settings.vouch) listen; - host = - if listen == "0.0.0.0" || listen == "[::]" - then "localhost" - else listen; - in - "http://${host}:${toString cfg.settings.vouch.port}"; locations = { "/" = { ssl.force = true; @@ -54,7 +47,9 @@ in { locations = mkMerge [ locations { - "/".proxyPass = mkIf cfg.enable (mkDefault localVouchUrl); + "/".proxyPass = mkDefault ( + access.proxyUrlFor { serviceName = "vouch-proxy"; serviceId = "login"; } + ); } ]; }; @@ -73,7 +68,9 @@ in { locations = mkMerge [ locations { - "/".proxyPass = mkDefault nginx.virtualHosts.vouch.locations."/".proxyPass; + "/".proxyPass = mkDefault ( + access.proxyUrlFor { serviceName = "vouch-proxy"; serviceId = "login.local"; } + ); } (localLocations "sso.local.${networking.domain}") ]; diff --git a/nixos/access/zigbee2mqtt.nix b/nixos/access/zigbee2mqtt.nix index 689f8377..2778d225 100644 --- a/nixos/access/zigbee2mqtt.nix +++ b/nixos/access/zigbee2mqtt.nix @@ -1,9 +1,10 @@ { config, lib, + access, ... }: let - inherit (lib.modules) mkIf mkDefault; + inherit (lib.modules) mkDefault; inherit (config.services) nginx zigbee2mqtt; name.shortServer = mkDefault "z2m"; in { @@ -12,8 +13,9 @@ in { zigbee2mqtt = { locations."/" = { proxy.websocket.enable = true; - proxyPass = mkIf zigbee2mqtt.enable ( - mkDefault "http://localhost:${toString zigbee2mqtt.settings.frontend.port}" + proxyPass = mkDefault ( + if zigbee2mqtt.enable then "http://localhost:${toString zigbee2mqtt.settings.frontend.port}" + else access.proxyUrlFor { serviceName = "zigbee2mqtt"; } ); }; inherit name; diff --git a/nixos/int.nix b/nixos/int.nix index 302803bf..45dc5d03 100644 --- a/nixos/int.nix +++ b/nixos/int.nix @@ -1,4 +1,4 @@ -{config, lib, access, ...}: let +{lib, access, ...}: let inherit (lib.modules) mkDefault; in { config = { @@ -9,7 +9,7 @@ in { ipv6SendRAConfig = { Managed = mkDefault false; EmitDNS = mkDefault true; - DNS = [ (access.getAddress6For "utsuho" "int") ]; + DNS = [ (access.systemForService "dnsmasq").access.address6ForNetwork.int ]; # Domains = [ "int.${networking.domain}" ]; EmitDomains = mkDefault false; RouterPreference = mkDefault "low"; diff --git a/nixos/invidious.nix b/nixos/invidious.nix index 9ae81e38..792353fe 100644 --- a/nixos/invidious.nix +++ b/nixos/invidious.nix @@ -16,7 +16,7 @@ in { invidious_hmac_key = commonSecret; }; - networking.firewall.interfaces.local.allowedTCPPorts = [cfg.port]; + networking.firewall.interfaces.int.allowedTCPPorts = [cfg.port]; users.groups.invidious = {}; users.users.invidious = { isSystemUser = true; diff --git a/nixos/keycloak.nix b/nixos/keycloak.nix index 4ef4ec07..2c953454 100644 --- a/nixos/keycloak.nix +++ b/nixos/keycloak.nix @@ -34,7 +34,7 @@ in { }; networking.firewall.interfaces.int.allowedTCPPorts = mkIf cfg.enable [ - (if cfg.sslCertificate != null then cfg.settings.https-port else cfg.settings.http-port) + cfg.port ]; systemd.services.keycloak = mkIf cfg.enable { serviceConfig.DynamicUser = mkForce false; @@ -52,7 +52,7 @@ in { settings = { hostname = mkDefault (if hostname-strict then hostname else null); - proxy = mkDefault (if cfg.sslCertificate != null then "reencrypt" else "edge"); + proxy = mkDefault (if cfg.protocol == "https" then "reencrypt" else "edge"); hostname-strict = mkDefault hostname-strict; hostname-strict-https = mkDefault hostname-strict; proxy-headers = mkDefault "xforwarded"; diff --git a/nixos/krb5.nix b/nixos/krb5.nix index e1435e6f..e80b9745 100644 --- a/nixos/krb5.nix +++ b/nixos/krb5.nix @@ -18,8 +18,9 @@ in { }; gensokyo-zone = let toLdap = replaceStrings [ "idp." ] [ "ldap." ]; - lanName = access.getHostnameFor "freeipa" "lan"; - localName = access.getHostnameFor "freeipa" "local"; + system = access.systemForService "kerberos"; + lanName = access.getHostnameFor system.name "lan"; + localName = access.getHostnameFor system.name "local"; ldapLan = toLdap lanName; ldapLocal = toLdap localName; in { diff --git a/nixos/reisen-ct/network.nix b/nixos/reisen-ct/network.nix index 5b89c699..9b581756 100644 --- a/nixos/reisen-ct/network.nix +++ b/nixos/reisen-ct/network.nix @@ -29,7 +29,7 @@ in { networkConfig.MulticastDNS = true; }; networking.nameservers' = mkIf enableDns (mkBefore [ - { address = access.getAddressFor "utsuho" "lan"; } + { address = access.getAddressFor (access.systemForService "dnsmasq").name "lan"; } ]); # prioritize our resolver over systemd-resolved! system.nssDatabases.hosts = let diff --git a/nixos/sssd.nix b/nixos/sssd.nix index 733b80de..8f64d203 100644 --- a/nixos/sssd.nix +++ b/nixos/sssd.nix @@ -1,7 +1,8 @@ { gensokyo-zone, access, config, lib, ... }: let inherit (gensokyo-zone.lib) mkAlmostOptionDefault; inherit (lib.modules) mkIf mkBefore mkAfter mkDefault; - inherit (lib.strings) replaceStrings; + inherit (lib.lists) tail; + inherit (lib.strings) splitString concatStringsSep; cfg = config.services.sssd; in { imports = [ @@ -12,29 +13,34 @@ in { services.sssd = { enable = (mkDefault true); gensokyo-zone = let - toService = service: replaceStrings [ "idp." ] [ "${service}." ]; + serviceFragment = service: service; + toService = service: hostname: let + segments = splitString "." hostname; + in concatStringsSep "." ([ (serviceFragment service) ] ++ tail segments); toFreeipa = toService "freeipa"; - toLdap = toService "ldap"; - lanName = access.getHostnameFor "freeipa" "lan"; - localName = access.getHostnameFor "freeipa" "local"; tailName = access.getHostnameFor "hakurei" "tail"; - localToo = lanName != localName; - servers = mkBefore [ - lanName - (mkIf localToo localName) - ]; - backups = mkAlmostOptionDefault (mkAfter [ - (toFreeipa lanName) - (mkIf config.services.tailscale.enable (toFreeipa tailName)) - ]); - in { - krb5.servers = { - inherit servers backups; + mkServers = serviceName: let + system = access.systemForService serviceName; + lanName = access.getHostnameFor system.name "lan"; + localName = access.getHostnameFor system.name "local"; + localToo = lanName != localName; + in { + servers = mkBefore [ + lanName + (mkIf localToo localName) + ]; + backups = mkAlmostOptionDefault (mkAfter [ + (toFreeipa lanName) + (mkIf config.services.tailscale.enable (toFreeipa tailName)) + ]); }; + in { + krb5.servers = mkServers "kerberos"; + ipa.servers = mkServers "freeipa"; ldap = { uris = { backups = mkAlmostOptionDefault (mkAfter [ - (mkIf config.services.tailscale.enable (toLdap tailName)) + (mkIf config.services.tailscale.enable (toService "ldap" tailName)) ]); }; bind.passwordFile = mkIf (cfg.gensokyo-zone.backend == "ldap") config.sops.secrets.gensokyo-zone-peep-passwords.path; diff --git a/nixos/unifi.nix b/nixos/unifi.nix index 7a6c1934..995e6b79 100644 --- a/nixos/unifi.nix +++ b/nixos/unifi.nix @@ -4,7 +4,7 @@ lib, ... }: let - inherit (lib.modules) mkIf mkMerge mkDefault; + inherit (lib.modules) mkIf mkDefault; cfg = config.services.unifi; in { services.unifi = { @@ -14,21 +14,25 @@ in { #mongodbPackage = mkDefault pkgs.mongodb-5_0; }; - networking.firewall.interfaces.local = mkIf cfg.enable { - allowedTCPPorts = mkMerge [ - [ + networking.firewall = mkIf cfg.enable { + interfaces.int = { + allowedTCPPorts = [ 8443 # remote login - ] - (mkIf (!cfg.openFirewall) [ + ]; + }; + interfaces.local = { + allowedTCPPorts = mkIf (!cfg.openFirewall) [ 8080 # Port for UAP to inform controller. 8880 # Port for HTTP portal redirect, if guest portal is enabled. 8843 # Port for HTTPS portal redirect, ditto. 6789 # Port for UniFi mobile speed test. - ]) - ]; + ]; + allowedUDPPorts = mkIf (!cfg.openFirewall) [ + 10001 # UDP port used for device discovery. + ]; + }; allowedUDPPorts = mkIf (!cfg.openFirewall) [ 3478 # UDP port used for STUN. - 10001 # UDP port used for device discovery. ]; }; diff --git a/nixos/zigbee2mqtt.nix b/nixos/zigbee2mqtt.nix index eca1124d..158299fe 100644 --- a/nixos/zigbee2mqtt.nix +++ b/nixos/zigbee2mqtt.nix @@ -27,10 +27,9 @@ in { user = "z2m"; password = "!secret z2m_pass"; server = let - utsuho = access.nixosFor "utsuho"; - mqttHost = access.getHostnameFor "utsuho" "lan"; + url = access.proxyUrlFor { serviceName = "mosquitto"; scheme = "mqtt"; }; in mkIf (!config.services.mosquitto.enable) ( - assert utsuho.services.mosquitto.enable; mkAlmostDefault "mqtt://${mqttHost}:1883" + mkAlmostDefault url ); }; homeassistant = true; diff --git a/systems/aya/default.nix b/systems/aya/default.nix index cfd2f0c3..dfed7948 100644 --- a/systems/aya/default.nix +++ b/systems/aya/default.nix @@ -7,5 +7,9 @@ _: { modules = [ ./nixos.nix ]; - access.tailscale.enable = true; + exports = { + services = { + tailscale.enable = true; + }; + }; } diff --git a/systems/default.nix b/systems/default.nix index 1d155284..9d7df1b8 100644 --- a/systems/default.nix +++ b/systems/default.nix @@ -12,6 +12,7 @@ ]; specialArgs = { inherit name inputs std meta; + inherit (inputs.self.lib) gensokyo-zone; }; }) (set.map (_: c: c) meta.systems); diff --git a/systems/freeipa/default.nix b/systems/freeipa/default.nix index 56153e93..f8fa3461 100644 --- a/systems/freeipa/default.nix +++ b/systems/freeipa/default.nix @@ -30,4 +30,11 @@ _: { mode = "0600"; }; }; + exports = { + services = { + freeipa.enable = true; + ldap.enable = true; + kerberos.enable = true; + }; + }; } diff --git a/systems/freepbx/default.nix b/systems/freepbx/default.nix index e2dd7067..cc6054df 100644 --- a/systems/freepbx/default.nix +++ b/systems/freepbx/default.nix @@ -14,4 +14,9 @@ _: { }; }; }; + exports = { + services = { + freepbx.enable = true; + }; + }; } diff --git a/systems/hakurei/default.nix b/systems/hakurei/default.nix index 25c3cd13..b4cc01eb 100644 --- a/systems/hakurei/default.nix +++ b/systems/hakurei/default.nix @@ -14,7 +14,19 @@ _: { }; }; access = { - tailscale.enable = true; global.enable = true; }; + exports = { + services = { + tailscale.enable = true; + samba.enable = true; + vouch-proxy = { + enable = true; + id = "login.local"; + }; + }; + exports = { + plex.enable = true; + }; + }; } diff --git a/systems/hakurei/nixos.nix b/systems/hakurei/nixos.nix index 92902858..303e9801 100644 --- a/systems/hakurei/nixos.nix +++ b/systems/hakurei/nixos.nix @@ -3,16 +3,11 @@ meta, lib, access, + gensokyo-zone, ... }: let + inherit (gensokyo-zone.lib) mkAddress6; inherit (lib.modules) mkIf mkMerge; - keycloak = access.nixosFor "keycloak"; - mediabox = access.nixosFor "mediabox"; - tei = access.nixosFor "tei"; - utsuho = access.nixosFor "utsuho"; - inherit (mediabox.services) plex; - inherit (tei.services) home-assistant zigbee2mqtt; - inherit (utsuho.services) unifi mosquitto; inherit (config.services) nginx; inherit (nginx) virtualHosts; in { @@ -218,29 +213,8 @@ in { }; }; - services.nginx = let - inherit (nginx) access; - #inherit (config.lib.access) getHostnameFor; - getHostnameFor = config.lib.access.getAddress4For; - in { + services.nginx = { vouch.enableLocal = false; - access.mosquitto = assert mosquitto.enable; { - host = getHostnameFor "utsuho" "lan"; - }; - access.plex = assert plex.enable; { - url = "http://${getHostnameFor "mediabox" "lan"}:${toString plex.port}"; - externalPort = 41324; - }; - access.unifi = assert unifi.enable; { - host = getHostnameFor "utsuho" "lan"; - }; - access.freeipa = { - host = getHostnameFor "freeipa" "lan"; - kerberos.ports.kpasswd = 464; - }; - access.kitchencam = { - streamPort = 41081; - }; stream.servers = { mosquitto.ssl.cert.name = "mosquitto"; }; @@ -254,18 +228,11 @@ in { # we're not the real sso record-holder, so don't respond globally.. local.denyGlobal = true; ssl.cert.enable = true; - locations."/".proxyPass = "https://${getHostnameFor "keycloak" "lan"}:8443"; }; - vouch = let - inherit (keycloak.services) vouch-proxy; - in assert vouch-proxy.enable; { + vouch = { ssl.cert.enable = true; - locations."/".proxyPass = "http://${getHostnameFor "keycloak" "lan"}:${toString vouch-proxy.settings.vouch.port}"; }; - vouch'local = let - vouch-proxy = config.services.vouch-proxy; - in assert vouch-proxy.enable; { - locations."/".proxyPass = "http://localhost:${toString vouch-proxy.settings.vouch.port}"; + vouch'local = { # we're not running another for tailscale sorry... name.includeTailscale = true; }; @@ -274,29 +241,27 @@ in { local.denyGlobal = true; ssl.cert.enable = true; }; - home-assistant = assert home-assistant.enable; { + home-assistant = { # not the real hass record-holder, so don't respond globally.. local.denyGlobal = true; ssl.cert.enable = true; - locations."/".proxyPass = "http://${getHostnameFor "tei" "lan"}:${toString home-assistant.config.http.server_port}"; }; - zigbee2mqtt = assert zigbee2mqtt.enable; { + zigbee2mqtt = { # not the real z2m record-holder, so don't respond globally.. local.denyGlobal = true; ssl.cert.enable = true; - locations."/".proxyPass = "http://${getHostnameFor "tei" "lan"}:${toString zigbee2mqtt.settings.frontend.port}"; }; grocy = { # not the real grocy record-holder, so don't respond globally.. local.denyGlobal = true; ssl.cert.enable = true; - locations."/".proxyPass = "http://${getHostnameFor "tei" "lan"}"; + locations."/".proxyPass = "http://${mkAddress6 (access.getAddressFor "tei" "lan")}"; }; barcodebuddy = { # not the real bbuddy record-holder, so don't respond globally.. local.denyGlobal = true; ssl.cert.enable = true; - locations."/".proxyPass = "http://${getHostnameFor "tei" "lan"}"; + locations."/".proxyPass = "http://${mkAddress6 (access.getAddressFor "tei" "lan")}"; }; freepbx = { ssl.cert.enable = true; @@ -305,14 +270,17 @@ in { proxied.enable = "cloudflared"; ssl.cert.enable = true; }; - plex.ssl.cert.enable = true; + plex = { + ssl.cert.enable = true; + listen'.external = { + enable = true; + port = 41324; + }; + }; kitchencam.ssl.cert.enable = true; invidious = { ssl.cert.enable = true; }; - invidious'int = { - locations."/".proxyPass = "http://${getHostnameFor "mediabox" "lan"}:${toString mediabox.services.invidious.port}"; - }; }; }; services.samba.tls = { diff --git a/systems/keycloak/default.nix b/systems/keycloak/default.nix index cfd2f0c3..9883aa89 100644 --- a/systems/keycloak/default.nix +++ b/systems/keycloak/default.nix @@ -7,5 +7,10 @@ _: { modules = [ ./nixos.nix ]; - access.tailscale.enable = true; + exports = { + services = { + keycloak.enable = true; + vouch-proxy.enable = true; + }; + }; } diff --git a/systems/keycloak/nixos.nix b/systems/keycloak/nixos.nix index 7953619f..3ee0e070 100644 --- a/systems/keycloak/nixos.nix +++ b/systems/keycloak/nixos.nix @@ -1,4 +1,4 @@ -{meta, config, ...}: { +{meta, config, access, ...}: { imports = let inherit (meta) nixos; in [ @@ -13,21 +13,25 @@ services.cloudflared = let tunnelId = "c9a4b8c9-42d9-4566-8cff-eb63ca26809d"; - inherit (config.services) keycloak vouch-proxy; in { tunnels.${tunnelId} = { default = "http_status:404"; credentialsFile = config.sops.secrets.cloudflared-tunnel-keycloak.path; ingress = let - keycloakHost = if keycloak.settings.hostname != null then keycloak.settings.hostname else "sso.${config.networking.domain}"; - keyCloakScheme = if keycloak.sslCertificate != null then "https" else "http"; - keycloakPort = keycloak.settings."${keyCloakScheme}-port"; + keycloak'system = access.systemForService "keycloak"; + inherit (keycloak'system.exports.services) keycloak; + vouch'system = access.systemForServiceId "login"; + inherit (vouch'system.exports.services) vouch-proxy; in { - ${keycloakHost} = assert keycloak.enable; { - service = "${keyCloakScheme}://localhost:${toString keycloakPort}"; - originRequest.${if keyCloakScheme == "https" then "noTLSVerify" else null} = true; + "${keycloak.id}.${config.networking.domain}" = let + portName = if keycloak.ports.https.enable then "https" else "http"; + in { + service = access.proxyUrlFor { system = keycloak'system; service = keycloak; inherit portName; }; + originRequest.${if keycloak.ports.${portName}.protocol == "https" then "noTLSVerify" else null} = true; + }; + "${vouch-proxy.id}.${config.networking.domain}" = { + service = access.proxyUrlFor { system = vouch'system; service = vouch-proxy; }; }; - ${vouch-proxy.domain}.service = assert vouch-proxy.enable; "http://localhost:${toString vouch-proxy.settings.vouch.port}"; }; }; }; diff --git a/systems/kitchencam/default.nix b/systems/kitchencam/default.nix index d3078d7e..f47d7670 100644 --- a/systems/kitchencam/default.nix +++ b/systems/kitchencam/default.nix @@ -15,4 +15,13 @@ _: { address6 = "fd0a::ba27:ebff:fea8:f4ff"; }; }; + exports = { + services = { + motion = { + id = "kitchen"; + enable = true; + ports.stream.port = 41081; + }; + }; + }; } diff --git a/systems/litterbox/default.nix b/systems/litterbox/default.nix index cfd2f0c3..dfed7948 100644 --- a/systems/litterbox/default.nix +++ b/systems/litterbox/default.nix @@ -7,5 +7,9 @@ _: { modules = [ ./nixos.nix ]; - access.tailscale.enable = true; + exports = { + services = { + tailscale.enable = true; + }; + }; } diff --git a/systems/mediabox/default.nix b/systems/mediabox/default.nix index ab61cf26..28997677 100644 --- a/systems/mediabox/default.nix +++ b/systems/mediabox/default.nix @@ -7,4 +7,10 @@ _: { modules = [ ./nixos.nix ]; + exports = { + services = { + plex.enable = true; + invidious.enable = true; + }; + }; } diff --git a/systems/reimu/default.nix b/systems/reimu/default.nix index cfd2f0c3..795b5857 100644 --- a/systems/reimu/default.nix +++ b/systems/reimu/default.nix @@ -7,5 +7,10 @@ _: { modules = [ ./nixos.nix ]; - access.tailscale.enable = true; + exports = { + services = { + tailscale.enable = true; + nfs.enable = true; + }; + }; } diff --git a/systems/reisen/default.nix b/systems/reisen/default.nix index 599c5c83..ae4a8fb3 100644 --- a/systems/reisen/default.nix +++ b/systems/reisen/default.nix @@ -10,4 +10,9 @@ _: { address6 = "fd0c::2"; }; }; + exports = { + services = { + proxmox.enable = true; + }; + }; } diff --git a/systems/tei/cloudflared.nix b/systems/tei/cloudflared.nix index f00c8c38..831c836e 100644 --- a/systems/tei/cloudflared.nix +++ b/systems/tei/cloudflared.nix @@ -1,46 +1,14 @@ { - access, config, lib, + access, ... }: let inherit (lib.modules) mkIf; - inherit (lib.attrsets) listToAttrs nameValuePair; - inherit (access) nixosFor; - inherit (config.networking) hostName; - inherit (config.services) nginx; + inherit (config.services) home-assistant nginx; cfg = config.services.cloudflared; apartment = "5e85d878-c6b2-4b15-b803-9aeb63d63543"; - accessHostFor = { - hostName, - system ? nixosFor hostName, - network ? "lan", - ... - }: let - host = access.getHostnameFor hostName network; - in - if hostName == config.networking.hostName - then "localhost" - else host; - ingressForNginx = { - host ? system.networking.fqdn, - port ? 80, - hostName, - system ? nixosFor hostName, - } @ args: - nameValuePair host { - service = "http://${accessHostFor args}:${toString port}"; - }; - ingressForHass = { - host ? system.services.home-assistant.domain, - port ? system.services.home-assistant.config.http.server_port, - hostName, - system ? nixosFor hostName, - ... - } @ args: - nameValuePair host { - service = "http://${accessHostFor args}:${toString port}"; - }; + localNginx = "http://localhost:${toString nginx.defaultHTTPListenPort}"; in { sops.secrets.cloudflared-tunnel-apartment.owner = cfg.user; services.cloudflared = { @@ -48,21 +16,20 @@ in { ${apartment} = { credentialsFile = config.sops.secrets.cloudflared-tunnel-apartment.path; default = "http_status:404"; - ingress = listToAttrs [ - (ingressForNginx { - host = nginx.virtualHosts.zigbee2mqtt.serverName; - inherit hostName; - }) - (ingressForNginx { - host = nginx.virtualHosts.grocy.serverName; - inherit hostName; - }) - (ingressForNginx { - host = nginx.virtualHosts.barcodebuddy.serverName; - inherit hostName; - }) - (ingressForHass {inherit hostName;}) - ]; + ingress = { + ${nginx.virtualHosts.zigbee2mqtt.serverName} = { + service = localNginx; + }; + ${nginx.virtualHosts.grocy.serverName} = { + service = localNginx; + }; + ${nginx.virtualHosts.barcodebuddy.serverName} = { + service = localNginx; + }; + ${home-assistant.domain} = assert home-assistant.enable; { + service = access.proxyUrlFor { serviceName = "home-assistant"; }; + }; + }; }; }; }; diff --git a/systems/tei/default.nix b/systems/tei/default.nix index cfd2f0c3..44bea27e 100644 --- a/systems/tei/default.nix +++ b/systems/tei/default.nix @@ -7,5 +7,12 @@ _: { modules = [ ./nixos.nix ]; - access.tailscale.enable = true; + exports = { + services = { + tailscale.enable = true; + home-assistant.enable = true; + zigbee2mqtt.enable = true; + postgresql.enable = true; + }; + }; } diff --git a/systems/utsuho/default.nix b/systems/utsuho/default.nix index ab61cf26..879dfb0f 100644 --- a/systems/utsuho/default.nix +++ b/systems/utsuho/default.nix @@ -7,4 +7,11 @@ _: { modules = [ ./nixos.nix ]; + exports = { + services = { + unifi.enable = true; + mosquitto.enable = true; + dnsmasq.enable = true; + }; + }; } diff --git a/systems/utsuho/nixos.nix b/systems/utsuho/nixos.nix index 640f4cd8..f1ec2cb4 100644 --- a/systems/utsuho/nixos.nix +++ b/systems/utsuho/nixos.nix @@ -18,7 +18,6 @@ in { ]; services.cloudflared = let - inherit (config.services) unifi; inherit (nginx) virtualHosts defaultHTTPListenPort; tunnelId = "28bcd3fc-3467-4997-806b-546ba9995028"; localNginx = "http://localhost:${toString defaultHTTPListenPort}"; @@ -27,7 +26,7 @@ in { default = "http_status:404"; credentialsFile = config.sops.secrets.cloudflared-tunnel-utsuho.path; ingress = { - ${virtualHosts.unifi.serverName} = assert unifi.enable; { + ${virtualHosts.unifi.serverName} = { service = localNginx; }; }; diff --git a/tf/cloudflare_tunnels.tf b/tf/cloudflare_tunnels.tf index 7bc01d25..06a252d2 100644 --- a/tf/cloudflare_tunnels.tf +++ b/tf/cloudflare_tunnels.tf @@ -100,7 +100,6 @@ module "tewi" { zone_id = cloudflare_zone.gensokyo-zone_zone.id subdomains = [ "home", - "id", "z2m", "grocy", "bbuddy", diff --git a/tree.nix b/tree.nix index 99121a37..e2f9c294 100644 --- a/tree.nix +++ b/tree.nix @@ -67,6 +67,7 @@ "modules/system/network".functor.enable = true; "modules/system/proxmox".functor.enable = true; "modules/system/extern".functor.enable = true; + "modules/system/exports".functor.enable = true; "modules/home".functor.enable = true; "modules/type".functor.enable = true; "modules/extern/home".functor.enable = true;