From 3afb7d5cf4c547065800402ceb49f6e62c38698c Mon Sep 17 00:00:00 2001 From: Katharina Heidenreich Date: Sat, 4 Apr 2026 16:51:21 +0200 Subject: [PATCH] feat: add known hosts --- config/endpoints.nix | 24 ++++++++ intermediate/nginx.nix | 64 ++++++++++++++++++++- validation/endpoints.nix | 117 ++++++++++++++++++++++++--------------- 3 files changed, 159 insertions(+), 46 deletions(-) diff --git a/config/endpoints.nix b/config/endpoints.nix index e2ef134..b59b16f 100644 --- a/config/endpoints.nix +++ b/config/endpoints.nix @@ -38,6 +38,30 @@ in proxyWebsockets = true; }; } + { + type = "inline"; + domain = net.devices.remote_proxy.domain; + endpoint = "/.well-known/matrix/server"; + force_ssl = true; + port = 443; + content = { + contentType = "application/json"; + status = 200; + body = ''{"m.server":"${net.devices.remote_proxy.domain}:443"}''; + }; + } + { + type = "inline"; + domain = net.devices.remote_proxy.domain; + endpoint = "/.well-known/matrix/client"; + force_ssl = true; + port = 443; + content = { + contentType = "application/json"; + status = 200; + body = ''{"m.homeserver":{"base_url":"https://${net.devices.remote_proxy.domain}"}}''; + }; + } { type = "proxy"; domain = "torrent.${net.local_domain}"; diff --git a/intermediate/nginx.nix b/intermediate/nginx.nix index 0e0b836..e44f4da 100644 --- a/intermediate/nginx.nix +++ b/intermediate/nginx.nix @@ -205,6 +205,33 @@ let proxyWebsockets = route.content.proxyWebsockets; }; } + else if route.type == "inline" then + let + inlineBody = + if builtins.isString route.content then + route.content + else + route.content.body; + inlineStatus = + if builtins.isAttrs route.content && route.content ? status then + route.content.status + else + 200; + inlineContentType = + if builtins.isAttrs route.content && route.content ? contentType then + route.content.contentType + else + "text/plain; charset=utf-8"; + in + { + name = "= ${route.endpoint}"; + value = { + return = "${toString inlineStatus} ${builtins.toJSON inlineBody}"; + extraConfig = '' + default_type ${inlineContentType}; + ''; + }; + } else let statusValue = @@ -284,10 +311,43 @@ let value = base // exposureConfig // sslConfig; }; - virtualHostsData = builtins.listToAttrs (lib.mapAttrsToList mkVirtualHost groupedByHost); + baseVirtualHostsData = builtins.listToAttrs (lib.mapAttrsToList mkVirtualHost groupedByHost); + + tlsDomains = + lib.unique (map (route: route.domain) (lib.filter (route: route.force_ssl) mappedEndpoints)); + + mkRedirectVhost = domain: { + name = "redirect_${sanitizeHostKey domain}_80"; + value = { + serverName = domain; + listen = [ + { + addr = "0.0.0.0"; + port = 80; + } + ]; + locations."/" = { + return = "301 https://$host$request_uri"; + }; + locations."^~ /.well-known/acme-challenge/" = { + root = "/var/lib/acme/acme-challenge"; + extraConfig = '' + auth_basic off; + auth_request off; + ''; + }; + }; + }; + + redirectVirtualHostsData = builtins.listToAttrs (map mkRedirectVhost tlsDomains); + + virtualHostsData = baseVirtualHostsData // redirectVirtualHostsData; nginxUsedPorts = - lib.unique (map (route: route.port) mappedEndpoints); + lib.unique ( + (map (route: route.port) mappedEndpoints) + ++ lib.optional (tlsDomains != []) 80 + ); acmeDomains = lib.unique (map (route: route.domain) (lib.filter (route: route.force_ssl) mappedEndpoints)); diff --git a/validation/endpoints.nix b/validation/endpoints.nix index 74e6ae8..cf60b0f 100644 --- a/validation/endpoints.nix +++ b/validation/endpoints.nix @@ -5,6 +5,7 @@ let allowedProxyContentKeys = [ "type" "ip" "port" "proxyWebsockets" ]; allowedWebContentKeys = [ "type" "files" ]; allowedWebFileKeys = [ "path" "filePath" "contentType" "status" ]; + allowedInlineContentKeys = [ "body" "contentType" "status" ]; ensureNoUnknownKeys = context: obj: allowedKeys: let @@ -37,10 +38,10 @@ let else throw "Endpoint at index ${toString index} must define type as a string."; _type = - if lib.elem typeValue [ "proxy" "web" ] then + if lib.elem typeValue [ "proxy" "web" "inline" ] then null else - throw "Endpoint at index ${toString index} type must be 'proxy' or 'web'."; + throw "Endpoint at index ${toString index} type must be 'proxy', 'web', or 'inline'."; _domain = if endpoint ? domain && builtins.isString endpoint.domain && endpoint.domain != "" then null @@ -62,13 +63,14 @@ let else throw "Endpoint at index ${toString index} must define force_ssl as a bool."; contentValue = - if endpoint ? content && builtins.isAttrs endpoint.content then + if endpoint ? content then endpoint.content else - throw "Endpoint at index ${toString index} must define content as an attrset."; + throw "Endpoint at index ${toString index} must define content."; _content = if typeValue == "proxy" then let + _attrs = if builtins.isAttrs contentValue then null else throw "Proxy endpoint at index ${toString index} must define content as an attrset."; ____ = ensureNoUnknownKeys "Proxy content at endpoint index ${toString index}" contentValue allowedProxyContentKeys; in if !(contentValue ? type) || !builtins.isString contentValue.type then @@ -82,51 +84,78 @@ let else null else - let - ____ = ensureNoUnknownKeys "Web content at endpoint index ${toString index}" contentValue allowedWebContentKeys; - filesValue = - if contentValue ? files && builtins.isList contentValue.files then - contentValue.files - else - throw "Web endpoint at index ${toString index} must define content.files as a list."; - _____ = - if contentValue ? type && contentValue.type == "store" then - null - else - throw "Web endpoint at index ${toString index} must define content.type = 'store'."; - ______ = lib.imap0 (fileIndex: file: + if typeValue == "web" then + let + _attrs = if builtins.isAttrs contentValue then null else throw "Web endpoint at index ${toString index} must define content as an attrset."; + ____ = ensureNoUnknownKeys "Web content at endpoint index ${toString index}" contentValue allowedWebContentKeys; + filesValue = + if contentValue ? files && builtins.isList contentValue.files then + contentValue.files + else + throw "Web endpoint at index ${toString index} must define content.files as a list."; + _____ = + if contentValue ? type && contentValue.type == "store" then + null + else + throw "Web endpoint at index ${toString index} must define content.type = 'store'."; + ______ = lib.imap0 (fileIndex: file: + let + _______ = + if builtins.isAttrs file then + null + else + throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must be an attrset."; + ________ = ensureNoUnknownKeys "Web endpoint at index ${toString index} file at position ${toString fileIndex}" file allowedWebFileKeys; + _________ = + if file ? path && builtins.isString file.path && file.path != "" && builtins.substring 0 1 file.path != "/" then + null + else + throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define path as a relative path string."; + __________ = + if file ? filePath && (builtins.isString file.filePath || builtins.isPath file.filePath) then + null + else + throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define filePath as string or path."; + ___________ = + if file ? contentType && builtins.isString file.contentType && file.contentType != "" then + null + else + throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define contentType as non-empty string."; + ____________ = + if file ? status && builtins.isInt file.status then + null + else + throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define status as int."; + in + file + ) filesValue; + in + null + else + if builtins.isString contentValue then + null + else if builtins.isAttrs contentValue then let + ____ = ensureNoUnknownKeys "Inline content at endpoint index ${toString index}" contentValue allowedInlineContentKeys; + _____ = + if contentValue ? body && builtins.isString contentValue.body then + null + else + throw "Inline endpoint at index ${toString index} must define content.body as a string when content is an attrset."; + ______ = + if !(contentValue ? contentType) || (builtins.isString contentValue.contentType && contentValue.contentType != "") then + null + else + throw "Inline endpoint at index ${toString index} content.contentType must be a non-empty string when provided."; _______ = - if builtins.isAttrs file then + if !(contentValue ? status) || builtins.isInt contentValue.status then null else - throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must be an attrset."; - ________ = ensureNoUnknownKeys "Web endpoint at index ${toString index} file at position ${toString fileIndex}" file allowedWebFileKeys; - _________ = - if file ? path && builtins.isString file.path && file.path != "" && builtins.substring 0 1 file.path != "/" then - null - else - throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define path as a relative path string."; - __________ = - if file ? filePath && (builtins.isString file.filePath || builtins.isPath file.filePath) then - null - else - throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define filePath as string or path."; - ___________ = - if file ? contentType && builtins.isString file.contentType && file.contentType != "" then - null - else - throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define contentType as non-empty string."; - ____________ = - if file ? status && builtins.isInt file.status then - null - else - throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define status as int."; + throw "Inline endpoint at index ${toString index} content.status must be an int when provided."; in - file - ) filesValue; - in - null; + null + else + throw "Inline endpoint at index ${toString index} must define content as a string or an attrset."; in endpoint;