From 1c043c723976a8d10f3c43062f2a43663f887a63 Mon Sep 17 00:00:00 2001 From: alexion Date: Thu, 10 Sep 2026 08:43:17 -0400 Subject: [PATCH] feat(nixos): add UDP route ingress --- configs/nixos/hosts/pikachu/default.nix | 12 +++- .../nixos/modules/services/reverse-proxy.nix | 55 ++++++++++++++++++- configs/nixos/registry/address.nix | 7 --- configs/nixos/registry/route.nix | 15 ++++- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/configs/nixos/hosts/pikachu/default.nix b/configs/nixos/hosts/pikachu/default.nix index 8df5a32..6bfe1a9 100644 --- a/configs/nixos/hosts/pikachu/default.nix +++ b/configs/nixos/hosts/pikachu/default.nix @@ -64,7 +64,17 @@ pools.pikachu = { }; }; - registry.address.pikachu.self = "10.23.10.102"; + registry.address.pikachu = { + self = "10.23.10.102"; + valheim = "10.23.10.102"; + }; + registry.route.pikachu.valheim = { + domain = "valheim.alexion.dev"; + udp.ports = [ + config.modules.services.games.valheim.port + (config.modules.services.games.valheim.port + 1) + ]; + }; modules.services.games.valheim = { enable = true; diff --git a/configs/nixos/modules/services/reverse-proxy.nix b/configs/nixos/modules/services/reverse-proxy.nix index 5670d62..599ff38 100644 --- a/configs/nixos/modules/services/reverse-proxy.nix +++ b/configs/nixos/modules/services/reverse-proxy.nix @@ -30,7 +30,9 @@ let generatedVirtualHostList = lib.concatLists ( lib.mapAttrsToList ( host: routes: - lib.mapAttrsToList (entry: _route: mkVirtualHost host entry) routes + lib.mapAttrsToList (entry: _route: mkVirtualHost host entry) ( + lib.filterAttrs (_entry: route: route.port != null) routes + ) ) registry.route ); @@ -59,6 +61,55 @@ let throw (builtins.head duplicateDomainMessages) else lib.listToAttrs generatedVirtualHostList; + + mkUdpStream = host: entry: port: + let + compositeEntry = registry.join host entry; + address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); + in + { + inherit host entry port address; + }; + + generatedUdpStreams = lib.concatLists ( + lib.mapAttrsToList ( + host: routes: + lib.concatLists ( + lib.mapAttrsToList (entry: route: map (mkUdpStream host entry) route.udp.ports) routes + ) + ) registry.route + ); + + udpStreamsByPort = lib.foldl' ( + acc: stream: + acc + // { + ${toString stream.port} = (acc.${toString stream.port} or [ ]) ++ [ stream ]; + } + ) { } generatedUdpStreams; + + duplicateUdpPortMessages = lib.concatLists ( + lib.mapAttrsToList ( + port: streams: + if lib.length streams > 1 then + [ + "duplicate UDP route port ${port} in ${lib.concatMapStringsSep " and " (stream: mkRegistryPath "route" stream.host stream.entry) streams}" + ] + else + [ ] + ) udpStreamsByPort + ); + + generatedStreamConfig = + if duplicateUdpPortMessages != [ ] then + throw (builtins.head duplicateUdpPortMessages) + else + lib.concatMapStringsSep "\n" (stream: '' + server { + listen ${toString stream.port} udp; + proxy_pass ${stream.address}:${toString stream.port}; + } + '') generatedUdpStreams; in { options.modules.services.reverse-proxy = { @@ -70,6 +121,7 @@ in 80 443 ]; + networking.firewall.allowedUDPPorts = map (stream: stream.port) generatedUdpStreams; security.acme = { acceptTerms = true; @@ -89,6 +141,7 @@ in recommendedOptimisation = true; recommendedProxySettings = true; recommendedTlsSettings = true; + streamConfig = lib.mkIf (generatedUdpStreams != [ ]) generatedStreamConfig; virtualHosts = generatedVirtualHosts; }; }; diff --git a/configs/nixos/registry/address.nix b/configs/nixos/registry/address.nix index 3f4eb65..8a6fd91 100644 --- a/configs/nixos/registry/address.nix +++ b/configs/nixos/registry/address.nix @@ -1,11 +1,4 @@ { lib, ... }: { type = lib.types.str; - - checks = [ - { - assertion = lib.registry.predicates.uniqueValue (entry: entry.value); - message = "Address registry values must be unique."; - } - ]; } diff --git a/configs/nixos/registry/route.nix b/configs/nixos/registry/route.nix index bee18a9..38439e7 100644 --- a/configs/nixos/registry/route.nix +++ b/configs/nixos/registry/route.nix @@ -12,14 +12,25 @@ domain = lib.mkOption { type = lib.types.str; example = "files.alexion.dev"; - description = "Public domain served by this reverse-proxy route entry."; + description = "Public domain served by this route entry."; }; port = lib.mkOption { - type = lib.types.ints.between 1 65535; + type = lib.types.nullOr lib.types.port; + default = null; example = 3923; description = "Backend TCP port served by the matching address registry entry."; }; + + udp.ports = lib.mkOption { + type = lib.types.listOf lib.types.port; + default = [ ]; + example = [ + 2456 + 2457 + ]; + description = "UDP ports served by the matching address registry entry."; + }; }; }; }