feat: add livekit
This commit is contained in:
parent
fb98563bb6
commit
eee6905637
9 changed files with 384 additions and 109 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
let
|
let
|
||||||
piTunnelEndpoints = import ../config/endpoints/pi_tunnel.nix;
|
piTunnelEndpoints = import ../config/endpoints/pi_tunnel.nix;
|
||||||
|
livekitLocalEndpoints = import ../config/endpoints/livekit_local.nix;
|
||||||
in
|
in
|
||||||
piTunnelEndpoints ++ []
|
piTunnelEndpoints ++ livekitLocalEndpoints
|
||||||
29
config/endpoints/livekit_local.nix
Normal file
29
config/endpoints/livekit_local.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
let
|
||||||
|
serviceConfig = import ../services.nix;
|
||||||
|
cfg = serviceConfig.livekit;
|
||||||
|
in
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type = "proxy";
|
||||||
|
listenPort = 443;
|
||||||
|
domain = cfg.domain;
|
||||||
|
endpoint = "/livekit/jwt/";
|
||||||
|
force_ssl = true;
|
||||||
|
content = {
|
||||||
|
host = "127.0.0.1";
|
||||||
|
port = cfg.jwt_port;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
type = "proxy";
|
||||||
|
listenPort = 443;
|
||||||
|
domain = cfg.domain;
|
||||||
|
endpoint = "/livekit/sfu/";
|
||||||
|
force_ssl = true;
|
||||||
|
content = {
|
||||||
|
host = "127.0.0.1";
|
||||||
|
port = cfg.port;
|
||||||
|
websocket = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -1,13 +1,38 @@
|
||||||
let
|
let
|
||||||
ports = [80 443 8448];
|
forwards = [{
|
||||||
entry = port:
|
port = 80;
|
||||||
|
domain = "vikunja.nudelerde.de";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
port = 443;
|
||||||
|
domain = "vikunja.nudelerde.de";
|
||||||
|
tls = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
port = 80;
|
||||||
|
domain = "nudelerde.de";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
port = 443;
|
||||||
|
domain = "nudelerde.de";
|
||||||
|
tls = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
port = 8448;
|
||||||
|
domain = "nudelerde.de";
|
||||||
|
tls = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
entry = forward:
|
||||||
{
|
{
|
||||||
type = "forwarding";
|
type = "forwarding";
|
||||||
listenPort = port;
|
listenPort = forward.port;
|
||||||
|
domain = forward.domain;
|
||||||
content = {
|
content = {
|
||||||
port = 10000 + port;
|
port = 10000 + forward.port;
|
||||||
|
tls = forward.tls or false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
in
|
in
|
||||||
map entry ports
|
map entry forwards
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,19 @@
|
||||||
{
|
{
|
||||||
nginx = {
|
nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
acmeEmail = null;
|
acmeEmail = "katharina.heidenreich02@gmail.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
nginxProxyOffset = 20000;
|
||||||
|
|
||||||
|
livekit = {
|
||||||
|
enable = true;
|
||||||
|
domain = "livekit.nudelerde.de";
|
||||||
|
keyFile = "/var/lib/livekit/livekit.keys";
|
||||||
|
port = 7880;
|
||||||
|
jwt_port = 8081;
|
||||||
|
trusted_homeservers = [ "nudelerde.de" ];
|
||||||
|
rtc_port_range_start = 50000;
|
||||||
|
rtc_port_range_end = 51000;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,36 +1,188 @@
|
||||||
let
|
let
|
||||||
lib = import <nixpkgs/lib>;
|
lib = import <nixpkgs/lib>;
|
||||||
endpoints = (import ../validation/endpoints.nix).getValidatedEndpoints (import ../config/endpoints.nix);
|
servicesConfig = import ../config/services.nix;
|
||||||
net = import ../config/network.nix;
|
endpointsConfig = (import ../validation/endpoints.nix).getEndpointsConfig (import ../config/endpoints.nix);
|
||||||
tunnelPolicy = import ../validation/tunnel_ports.nix;
|
|
||||||
|
|
||||||
normalizeEndpoint = endpoint:
|
tunnelOffset = 10000;
|
||||||
endpoint // {
|
proxyOffset = servicesConfig.nginxProxyOffset;
|
||||||
content = endpoint.content // {
|
|
||||||
host = if endpoint.type == "forwarding" then net.tunnel.host else endpoint.content.host;
|
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;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
normalizedEndpoints = map normalizeEndpoint endpoints;
|
tlsListenPorts = lib.unique (map (entry: entry.listenPort) tlsRouteEntriesRaw);
|
||||||
|
tlsRouteMaps = builtins.filter (mapDef: mapDef != null) (map mkTlsRouteMapForListenPort tlsListenPorts);
|
||||||
|
|
||||||
_forwardPortChecks = map (endpoint:
|
renderTlsServer = tlsRouteMap:
|
||||||
if endpoint.content.host == net.tunnel.host && !(tunnelPolicy.isAllowedTunnelPort endpoint.content.port) then
|
let
|
||||||
throw "Forwarding endpoint listenPort=${toString endpoint.listenPort} targets tunnel-backed local port ${toString endpoint.content.port}, which is not listed in config/network.nix tunnel.allowedPorts."
|
portString = toString tlsRouteMap.listenPort;
|
||||||
else
|
in
|
||||||
null
|
''
|
||||||
) normalizedEndpoints;
|
map $ssl_preread_server_name $proxy_upstream_${portString} {
|
||||||
|
${lib.concatStringsSep "\n" (map (entry: "${entry.domain} ${entry.upstream};") tlsRouteMap.routeEntries)}
|
||||||
|
default ${tlsRouteMap.defaultRoute};
|
||||||
|
}
|
||||||
|
|
||||||
mkStreamServer = endpoint: ''
|
|
||||||
server {
|
server {
|
||||||
listen ${toString endpoint.listenPort};
|
listen ${portString};
|
||||||
proxy_pass ${endpoint.content.host}:${toString endpoint.content.port};
|
proxy_pass $proxy_upstream_${portString};
|
||||||
|
ssl_preread on;
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
streamConfig = lib.concatStringsSep "\n" (map mkStreamServer normalizedEndpoints);
|
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
|
in
|
||||||
{
|
{
|
||||||
validatedEndpoints = normalizedEndpoints;
|
inherit endpointsConfig forwardingEndpoints proxyEndpoints streamConfig virtualHosts proxyOffset publicListenPorts localServiceAcmeDomains;
|
||||||
inherit streamConfig;
|
|
||||||
nginxUsedPorts = lib.unique (map (endpoint: endpoint.listenPort) normalizedEndpoints);
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,5 +3,6 @@
|
||||||
imports = [
|
imports = [
|
||||||
./openssh.nix
|
./openssh.nix
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
|
./livekit.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
52
services/livekit.nix
Normal file
52
services/livekit.nix
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
serviceConfig = import ../config/services.nix;
|
||||||
|
cfg = serviceConfig.livekit;
|
||||||
|
keyFile = cfg.keyFile;
|
||||||
|
publicUrl = "wss://${cfg.domain}/livekit/sfu/";
|
||||||
|
trustedHomeservers =
|
||||||
|
if builtins.isList cfg.trusted_homeservers then
|
||||||
|
cfg.trusted_homeservers
|
||||||
|
else
|
||||||
|
throw "config/services.nix livekit.trusted_homeservers must be a list of domains.";
|
||||||
|
trustedHomeserversEnv = builtins.concatStringsSep "," trustedHomeservers;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.livekit = lib.mkIf cfg.enable {
|
||||||
|
enable = true;
|
||||||
|
settings.room.auto_create = false;
|
||||||
|
inherit keyFile;
|
||||||
|
openFirewall = true;
|
||||||
|
settings = {
|
||||||
|
port = cfg.port;
|
||||||
|
rtc = {
|
||||||
|
port_range_start = cfg.rtc_port_range_start;
|
||||||
|
port_range_end = cfg.rtc_port_range_end;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.lk-jwt-service = lib.mkIf cfg.enable {
|
||||||
|
enable = true;
|
||||||
|
livekitUrl = publicUrl;
|
||||||
|
inherit keyFile;
|
||||||
|
port = cfg.jwt_port;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.livekit-key = lib.mkIf cfg.enable {
|
||||||
|
before = [ "lk-jwt-service.service" "livekit.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = with pkgs; [ livekit coreutils gawk ];
|
||||||
|
script = ''
|
||||||
|
echo "Key missing, generating key"
|
||||||
|
mkdir -p "$(dirname "${keyFile}")"
|
||||||
|
echo "lk-jwt-service: $(livekit-server generate-keys | tail -1 | awk '{print $3}')" > "${keyFile}"
|
||||||
|
'';
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
unitConfig.ConditionPathExists = "!${keyFile}";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.lk-jwt-service = lib.mkIf cfg.enable {
|
||||||
|
environment.LIVEKIT_FULL_ACCESS_HOMESERVERS = trustedHomeserversEnv;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,42 @@
|
||||||
{ config, lib, ... }:
|
{ config, lib, ... }:
|
||||||
let
|
let
|
||||||
|
nginxConfig = import ../intermediate/nginx.nix;
|
||||||
serviceConfig = import ../config/services.nix;
|
serviceConfig = import ../config/services.nix;
|
||||||
nginxModel = import ../intermediate/nginx.nix;
|
acmeDomains = nginxConfig.localServiceAcmeDomains;
|
||||||
|
acmeEmailConfigured =
|
||||||
|
config.security.acme ? defaults
|
||||||
|
&& builtins.isAttrs config.security.acme.defaults
|
||||||
|
&& config.security.acme.defaults ? email
|
||||||
|
&& builtins.isString config.security.acme.defaults.email
|
||||||
|
&& config.security.acme.defaults.email != "";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assertions = [
|
config = lib.mkIf serviceConfig.nginx.enable {
|
||||||
{
|
assertions = lib.optional (acmeDomains != [] && !acmeEmailConfigured) {
|
||||||
assertion = nginxModel.validatedEndpoints != [];
|
assertion = false;
|
||||||
message = "No endpoints configured. Add endpoint declarations under config/endpoints/.";
|
message = "TLS local proxy endpoints exist, but security.acme.defaults.email is missing or empty.";
|
||||||
}
|
};
|
||||||
];
|
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
enable = serviceConfig.nginx.enable;
|
enable = true;
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
recommendedTlsSettings = true;
|
||||||
|
virtualHosts = nginxConfig.virtualHosts;
|
||||||
|
streamConfig = nginxConfig.streamConfig;
|
||||||
|
};
|
||||||
|
|
||||||
recommendedProxySettings = true;
|
security.acme = {
|
||||||
recommendedTlsSettings = true;
|
acceptTerms = true;
|
||||||
recommendedOptimisation = true;
|
defaults.email = serviceConfig.nginx.acmeEmail;
|
||||||
recommendedGzipSettings = true;
|
certs = builtins.listToAttrs (map (domain: {
|
||||||
streamConfig = nginxModel.streamConfig;
|
name = domain;
|
||||||
|
value = {
|
||||||
|
webroot = "/var/lib/acme/acme-challenge";
|
||||||
|
group = "nginx";
|
||||||
|
};
|
||||||
|
}) acmeDomains);
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = nginxConfig.publicListenPorts;
|
||||||
};
|
};
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = nginxModel.nginxUsedPorts;
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,78 +1,63 @@
|
||||||
let
|
let
|
||||||
lib = import <nixpkgs/lib>;
|
lib = import <nixpkgs/lib>;
|
||||||
|
tunnelPorts = import ./tunnel_ports.nix;
|
||||||
|
|
||||||
allowedEndpointKeys = [ "type" "listenPort" "content" ];
|
assertAttrset = context: value:
|
||||||
allowedContentKeys = [ "host" "port" ];
|
if builtins.isAttrs value then
|
||||||
|
value
|
||||||
|
else
|
||||||
|
throw "${context} must be an attrset.";
|
||||||
|
|
||||||
ensureNoUnknownKeys = context: obj: allowedKeys:
|
assertString = context: value:
|
||||||
|
if builtins.isString value && value != "" then
|
||||||
|
value
|
||||||
|
else
|
||||||
|
throw "${context} must be a non-empty string.";
|
||||||
|
|
||||||
|
assertInt = context: value:
|
||||||
|
if builtins.isInt value then
|
||||||
|
value
|
||||||
|
else
|
||||||
|
throw "${context} must be an int.";
|
||||||
|
|
||||||
|
validateForwarding = index: endpoint:
|
||||||
let
|
let
|
||||||
unknown = builtins.filter (key: !(lib.elem key allowedKeys)) (builtins.attrNames obj);
|
content = assertAttrset "config/endpoints.nix[${toString index}].content" endpoint.content;
|
||||||
in
|
_ = assertInt "config/endpoints.nix[${toString index}].content.port" content.port;
|
||||||
if unknown != [] then
|
__ = if tunnelPorts.isAllowedTunnelPort content.port then null else throw "config/endpoints.nix[${toString index}].content.port is not in config/network.nix tunnel.allowedPorts.";
|
||||||
throw "${context} contains unknown keys: ${builtins.concatStringsSep ", " unknown}"
|
___ = if !(content ? tls) || builtins.isBool content.tls then null else throw "config/endpoints.nix[${toString index}].content.tls must be a bool.";
|
||||||
else
|
|
||||||
obj;
|
|
||||||
|
|
||||||
ensurePort = value:
|
|
||||||
builtins.isInt value && value > 0 && value <= 65535;
|
|
||||||
|
|
||||||
validateEndpointShape = index: endpoint:
|
|
||||||
let
|
|
||||||
_ = if builtins.isAttrs endpoint then null else throw "Endpoint at index ${toString index} must be an attrset.";
|
|
||||||
__ = ensureNoUnknownKeys "Endpoint at index ${toString index}" endpoint allowedEndpointKeys;
|
|
||||||
typeValue =
|
|
||||||
if endpoint ? type && builtins.isString endpoint.type then
|
|
||||||
endpoint.type
|
|
||||||
else
|
|
||||||
throw "Endpoint at index ${toString index} must define type as a string.";
|
|
||||||
_type =
|
|
||||||
if lib.elem typeValue [ "forwarding" "proxy" ] then
|
|
||||||
null
|
|
||||||
else
|
|
||||||
throw "Endpoint at index ${toString index} type must be 'forwarding' or 'proxy'.";
|
|
||||||
_listenPort =
|
|
||||||
if endpoint ? listenPort && ensurePort endpoint.listenPort then
|
|
||||||
null
|
|
||||||
else
|
|
||||||
throw "Endpoint at index ${toString index} must define listenPort as int in range 1..65535.";
|
|
||||||
contentValue =
|
|
||||||
if endpoint ? content then
|
|
||||||
endpoint.content
|
|
||||||
else
|
|
||||||
throw "Endpoint at index ${toString index} must define content.";
|
|
||||||
_content =
|
|
||||||
let
|
|
||||||
_attrs = if builtins.isAttrs contentValue then null else throw "Endpoint at index ${toString index} must define content as an attrset.";
|
|
||||||
____ = ensureNoUnknownKeys "Endpoint content at index ${toString index}" contentValue allowedContentKeys;
|
|
||||||
in
|
|
||||||
if !(contentValue ? port) || !ensurePort contentValue.port then
|
|
||||||
throw "Endpoint at index ${toString index} must define content.port as int in range 1..65535."
|
|
||||||
else if typeValue == "proxy" && (!(contentValue ? host) || !builtins.isString contentValue.host || contentValue.host == "") then
|
|
||||||
throw "Proxy endpoint at index ${toString index} must define content.host as non-empty string."
|
|
||||||
else if typeValue == "forwarding" && contentValue ? host then
|
|
||||||
throw "Forwarding endpoint at index ${toString index} must not define content.host."
|
|
||||||
else
|
|
||||||
null;
|
|
||||||
in
|
in
|
||||||
endpoint;
|
endpoint;
|
||||||
|
|
||||||
validateEndpointsShape = endpoints:
|
validateProxy = index: endpoint:
|
||||||
if !builtins.isList endpoints then
|
|
||||||
throw "config/endpoints.nix must evaluate to a list of endpoint attrsets."
|
|
||||||
else
|
|
||||||
lib.imap0 validateEndpointShape endpoints;
|
|
||||||
|
|
||||||
validateUniqueHostPath = endpoints:
|
|
||||||
let
|
let
|
||||||
keyFor = endpoint: toString endpoint.listenPort;
|
content = assertAttrset "config/endpoints.nix[${toString index}].content" endpoint.content;
|
||||||
keys = map keyFor endpoints;
|
_ = assertString "config/endpoints.nix[${toString index}].endpoint" endpoint.endpoint;
|
||||||
|
__ = assertString "config/endpoints.nix[${toString index}].content.host" content.host;
|
||||||
|
___ = assertInt "config/endpoints.nix[${toString index}].content.port" content.port;
|
||||||
|
____ = if !(endpoint ? force_ssl) || builtins.isBool endpoint.force_ssl then null else throw "config/endpoints.nix[${toString index}].force_ssl must be a bool.";
|
||||||
|
_____ = if !(content ? websocket) || builtins.isBool content.websocket then null else throw "config/endpoints.nix[${toString index}].content.websocket must be a bool.";
|
||||||
in
|
in
|
||||||
if builtins.length keys == builtins.length (lib.unique keys) then
|
endpoint;
|
||||||
endpoints
|
|
||||||
|
validateEndpoint = index: endpoint:
|
||||||
|
let
|
||||||
|
_ = assertAttrset "config/endpoints.nix[${toString index}]" endpoint;
|
||||||
|
__ = if endpoint ? type && (endpoint.type == "forwarding" || endpoint.type == "proxy") then null else throw "config/endpoints.nix[${toString index}].type must be \"forwarding\" or \"proxy\".";
|
||||||
|
___ = assertInt "config/endpoints.nix[${toString index}].listenPort" endpoint.listenPort;
|
||||||
|
____ = assertString "config/endpoints.nix[${toString index}].domain" endpoint.domain;
|
||||||
|
in
|
||||||
|
if endpoint.type == "forwarding" then
|
||||||
|
validateForwarding index endpoint
|
||||||
else
|
else
|
||||||
throw "Duplicate listenPort detected in config/endpoints.nix.";
|
validateProxy index endpoint;
|
||||||
|
|
||||||
|
getEndpointsConfig = endpoints:
|
||||||
|
if builtins.isList endpoints then
|
||||||
|
lib.imap0 validateEndpoint endpoints
|
||||||
|
else
|
||||||
|
throw "config/endpoints.nix must evaluate to a list.";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
getValidatedEndpoints = endpoints:
|
inherit getEndpointsConfig;
|
||||||
validateUniqueHostPath (validateEndpointsShape endpoints);
|
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue