refactor(nixos): model local routes as HTTP routes

This commit is contained in:
2026-09-15 19:37:27 -04:00
parent 4f6ade4ed5
commit 7f11154ceb
4 changed files with 36 additions and 61 deletions

View File

@@ -148,11 +148,13 @@
enable = true; enable = true;
vlan = 20; vlan = 20;
address = "10.23.20.110"; address = "10.23.20.110";
}; reverseProxy = {
enable = true;
registry.local-route.${config.networking.hostName}.homepage = { route = {
domain = "home"; domain = "home";
port = 8082; http.port = 8082;
};
};
}; };
guests.copyparty = { guests.copyparty = {

View File

@@ -9,59 +9,46 @@ let
mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}"; mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}";
mkBackend = address: route: "http://${address}:${toString route.port}"; mkBackend = address: port: "http://${address}:${toString port}";
mkPublicVirtualHost = host: entry: mkVirtualHost = host: entry: mode:
let let
compositeEntry = registry.join host entry; compositeEntry = registry.join host entry;
route = compositeEntry.route; route = compositeEntry.route;
address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}");
isHttp = mode == "http";
port = if isHttp then route.http.port else route.port;
in in
{ {
inherit host entry; inherit host entry;
registryName = "route"; registryName = "route";
name = route.domain; name = route.domain;
value = { value = {
forceSSL = true; forceSSL = !isHttp;
enableACME = true; enableACME = !isHttp;
locations."/".proxyPass = mkBackend address route; locations."/".proxyPass = mkBackend address port;
}; };
}; };
mkLocalVirtualHost = host: entry: generatedHttpsVirtualHostList = lib.concatLists (
let
compositeEntry = registry.join host entry;
route = compositeEntry.local-route;
address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "local-route" host entry}");
in
{
inherit host entry;
registryName = "local-route";
name = route.domain;
value = {
forceSSL = false;
enableACME = false;
locations."/".proxyPass = mkBackend address route;
};
};
generatedPublicVirtualHostList = lib.concatLists (
lib.mapAttrsToList ( lib.mapAttrsToList (
host: routes: host: routes:
lib.mapAttrsToList (entry: _route: mkPublicVirtualHost host entry) ( lib.mapAttrsToList (entry: _route: mkVirtualHost host entry "https") (
lib.filterAttrs (_entry: route: route.port != null) routes lib.filterAttrs (_entry: route: route.port != null) routes
) )
) registry.route ) registry.route
); );
generatedLocalVirtualHostList = lib.concatLists ( generatedHttpVirtualHostList = lib.concatLists (
lib.mapAttrsToList ( lib.mapAttrsToList (
host: routes: host: routes:
lib.mapAttrsToList (entry: _route: mkLocalVirtualHost host entry) routes lib.mapAttrsToList (entry: _route: mkVirtualHost host entry "http") (
) registry.local-route lib.filterAttrs (_entry: route: route.http.port != null) routes
)
) registry.route
); );
generatedVirtualHostList = generatedPublicVirtualHostList ++ generatedLocalVirtualHostList; generatedVirtualHostList = generatedHttpsVirtualHostList ++ generatedHttpVirtualHostList;
virtualHostsByDomain = lib.foldl' ( virtualHostsByDomain = lib.foldl' (
acc: virtualHost: acc: virtualHost:
@@ -76,7 +63,7 @@ let
domain: virtualHosts: domain: virtualHosts:
if lib.length virtualHosts > 1 then if lib.length virtualHosts > 1 then
[ [
"duplicate reverse-proxy domain ${domain} in ${lib.concatMapStringsSep " and " (virtualHost: mkRegistryPath virtualHost.registryName virtualHost.host virtualHost.entry) virtualHosts}" "duplicate route domain ${domain} in ${lib.concatMapStringsSep " and " (virtualHost: mkRegistryPath virtualHost.registryName virtualHost.host virtualHost.entry) virtualHosts}"
] ]
else else
[ ] [ ]

View File

@@ -1,25 +0,0 @@
{ lib, ... }:
{
checks = [
{
assertion = lib.registry.predicates.uniqueValue (entry: entry.value.domain);
message = "Local route domains must be unique.";
}
];
type = lib.types.submodule {
options = {
domain = lib.mkOption {
type = lib.types.str;
example = "home";
description = "Local HTTP domain served by this route entry.";
};
port = lib.mkOption {
type = lib.types.port;
example = 8082;
description = "Backend TCP port served by the matching address registry entry.";
};
};
};
}

View File

@@ -12,6 +12,10 @@
); );
message = "Route UDP ports must be unique."; message = "Route UDP ports must be unique.";
} }
{
assertion = entries: lib.all (entry: entry.value.port == null || entry.value.http.port == null) entries;
message = "Route entries must not set both port and http.port.";
}
]; ];
type = lib.types.submodule { type = lib.types.submodule {
@@ -19,14 +23,21 @@
domain = lib.mkOption { domain = lib.mkOption {
type = lib.types.str; type = lib.types.str;
example = "files.alexion.dev"; example = "files.alexion.dev";
description = "Public domain served by this route entry."; description = "Domain served by this route entry.";
}; };
port = lib.mkOption { port = lib.mkOption {
type = lib.types.nullOr lib.types.port; type = lib.types.nullOr lib.types.port;
default = null; default = null;
example = 3923; example = 3923;
description = "Backend TCP port served by the matching address registry entry."; description = "HTTPS backend TCP port served by the matching address registry entry.";
};
http.port = lib.mkOption {
type = lib.types.nullOr lib.types.port;
default = null;
example = 8082;
description = "HTTP backend TCP port served by the matching address registry entry.";
}; };
udp.ports = lib.mkOption { udp.ports = lib.mkOption {