refactor(reverse-proxy): use route registry
This commit was merged in pull request #61.
This commit is contained in:
@@ -103,3 +103,6 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
|
|||||||
Keep `git.alexion.dev:2022` working by proxying that TCP stream through the reverse-proxy guest to Gitea.
|
Keep `git.alexion.dev:2022` working by proxying that TCP stream through the reverse-proxy guest to Gitea.
|
||||||
- Remote deploy helpers that run target-side `sudo` over SSH need a TTY.
|
- Remote deploy helpers that run target-side `sudo` over SSH need a TTY.
|
||||||
Use `ssh -t` unless the target command is guaranteed not to prompt for sudo credentials.
|
Use `ssh -t` unless the target command is guaranteed not to prompt for sudo credentials.
|
||||||
|
- A NixOS module function argument default such as `registry ? config.registry` is not a safe fallback for an unprovided module argument.
|
||||||
|
The module system still queries `_module.args.registry` and fails when it is absent.
|
||||||
|
When an optional special-argument fallback is intentionally needed, bind the module arguments through `@args` and test `args ? <name>` inside the module body.
|
||||||
|
|||||||
261
flake.nix
261
flake.nix
@@ -193,20 +193,33 @@
|
|||||||
|
|
||||||
reverse-proxy-basic-route =
|
reverse-proxy-basic-route =
|
||||||
let
|
let
|
||||||
|
routeHost = lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
specialArgs.my = self.lib;
|
||||||
|
modules = [
|
||||||
|
self.lib.registry.module
|
||||||
|
{
|
||||||
|
registry.address.test.budget = "10.23.20.42";
|
||||||
|
registry.route.test.budget = {
|
||||||
|
domain = "budget.alexion.dev";
|
||||||
|
port = 5006;
|
||||||
|
};
|
||||||
|
system.stateVersion = "26.11";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
registry = self.lib.registry.collect { hosts.test = routeHost; };
|
||||||
testConfig =
|
testConfig =
|
||||||
(lib.nixosSystem {
|
(lib.nixosSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
specialArgs.my = self.lib;
|
specialArgs = {
|
||||||
|
my = self.lib;
|
||||||
|
inherit registry;
|
||||||
|
};
|
||||||
modules = [
|
modules = [
|
||||||
./modules/reverse-proxy.nix
|
./modules/reverse-proxy.nix
|
||||||
{
|
{
|
||||||
modules.reverse-proxy = {
|
modules.reverse-proxy.enable = true;
|
||||||
enable = true;
|
|
||||||
routes."budget.alexion.dev" = {
|
|
||||||
path = "/";
|
|
||||||
backend = "http://10.23.20.42:5006";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}).config;
|
}).config;
|
||||||
@@ -224,21 +237,22 @@
|
|||||||
reverse-proxy-actualbudget-guest-route =
|
reverse-proxy-actualbudget-guest-route =
|
||||||
let
|
let
|
||||||
testConfig = self.nixosConfigurations.pikachu.config;
|
testConfig = self.nixosConfigurations.pikachu.config;
|
||||||
route = testConfig.modules.reverse-proxy.routes.actualbudget;
|
route = testConfig.registry.route.pikachu.actualbudget;
|
||||||
vhost = testConfig.containers.reverse-proxy.config.services.nginx.virtualHosts."budget.alexion.dev";
|
vhost = testConfig.containers.reverse-proxy.config.services.nginx.virtualHosts."budget.alexion.dev";
|
||||||
in
|
in
|
||||||
pkgs.runCommand "reverse-proxy-actualbudget-guest-route-check" { } ''
|
pkgs.runCommand "reverse-proxy-actualbudget-guest-route-check" { } ''
|
||||||
${
|
${
|
||||||
lib.optionalString (
|
lib.optionalString (
|
||||||
!(
|
!(
|
||||||
route.host == "budget.alexion.dev"
|
route.domain == "budget.alexion.dev"
|
||||||
&& route.backend == null
|
|
||||||
&& route.endpoint == "pikachu-actualbudget"
|
|
||||||
&& route.port == 5006
|
&& route.port == 5006
|
||||||
&& route.tls
|
&& !(route ? backend)
|
||||||
&& route.aliases == [ ]
|
&& !(route ? path)
|
||||||
&& route.path == "/"
|
&& !(route ? scheme)
|
||||||
&& !route.websockets
|
&& !(route ? aliases)
|
||||||
|
&& !(route ? tls)
|
||||||
|
&& !(route ? websockets)
|
||||||
|
&& !(route ? enable)
|
||||||
&& vhost.locations."/".proxyPass == "http://10.23.20.42:5006"
|
&& vhost.locations."/".proxyPass == "http://10.23.20.42:5006"
|
||||||
)
|
)
|
||||||
) "exit 1"
|
) "exit 1"
|
||||||
@@ -313,7 +327,6 @@
|
|||||||
inputs.sops-nix.nixosModules.sops
|
inputs.sops-nix.nixosModules.sops
|
||||||
self.lib.registry.module
|
self.lib.registry.module
|
||||||
./modules/network.nix
|
./modules/network.nix
|
||||||
./modules/reverse-proxy.nix
|
|
||||||
(self.lib.guest { name = "copyparty"; })
|
(self.lib.guest { name = "copyparty"; })
|
||||||
{
|
{
|
||||||
networking.hostName = "pikachu";
|
networking.hostName = "pikachu";
|
||||||
@@ -361,34 +374,46 @@
|
|||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
specialArgs.my = self.lib;
|
specialArgs.my = self.lib;
|
||||||
modules = [
|
modules = [
|
||||||
./modules/reverse-proxy.nix
|
self.lib.registry.module
|
||||||
{
|
{
|
||||||
modules.reverse-proxy.routes.app = {
|
registry.address.routeHost.app = "10.23.20.126";
|
||||||
host = "worker.alexion.dev";
|
registry.route.routeHost.app = {
|
||||||
backend = "http://10.23.20.126:3923";
|
domain = "worker.alexion.dev";
|
||||||
|
port = 3923;
|
||||||
};
|
};
|
||||||
|
system.stateVersion = "26.11";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
endpoints = self.lib.networkEndpoints.collectFleetEndpoints {
|
registry = self.lib.registry.collect {
|
||||||
pikachu = self.nixosConfigurations.pikachu;
|
hosts = {
|
||||||
|
inherit routeHost;
|
||||||
|
pikachu = self.nixosConfigurations.pikachu;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
fleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes {
|
|
||||||
inherit routeHost;
|
|
||||||
pikachu = self.nixosConfigurations.pikachu;
|
|
||||||
} endpoints;
|
|
||||||
testConfig =
|
testConfig =
|
||||||
(self.nixosConfigurations.pikachu.extendModules {
|
(lib.nixosSystem {
|
||||||
specialArgs.reverseProxyFleetRoutes = fleetRoutes;
|
system = "x86_64-linux";
|
||||||
|
specialArgs = {
|
||||||
|
my = self.lib;
|
||||||
|
inherit registry;
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
./modules/reverse-proxy.nix
|
||||||
|
{
|
||||||
|
modules.reverse-proxy.enable = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
}).config;
|
}).config;
|
||||||
vhosts = testConfig.containers.reverse-proxy.config.services.nginx.virtualHosts;
|
vhosts = testConfig.services.nginx.virtualHosts;
|
||||||
in
|
in
|
||||||
pkgs.runCommand "reverse-proxy-fleet-routes-check" { } ''
|
pkgs.runCommand "reverse-proxy-fleet-routes-check" { } ''
|
||||||
${
|
${
|
||||||
lib.optionalString (
|
lib.optionalString (
|
||||||
!(
|
!(
|
||||||
fleetRoutes ? routeHost-app
|
registry.route ? routeHost
|
||||||
&& fleetRoutes ? pikachu-actualbudget
|
&& registry.route.routeHost ? app
|
||||||
|
&& registry.route.pikachu ? actualbudget
|
||||||
&& vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
&& vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||||
&& vhosts."budget.alexion.dev".locations."/".proxyPass == "http://10.23.20.42:5006"
|
&& vhosts."budget.alexion.dev".locations."/".proxyPass == "http://10.23.20.42:5006"
|
||||||
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||||
@@ -398,9 +423,9 @@
|
|||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
reverse-proxy-endpoint-backends =
|
reverse-proxy-address-backends =
|
||||||
let
|
let
|
||||||
endpointHost =
|
workerHost =
|
||||||
lib.nixosSystem {
|
lib.nixosSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
specialArgs.my = self.lib;
|
specialArgs.my = self.lib;
|
||||||
@@ -408,22 +433,11 @@
|
|||||||
self.lib.registry.module
|
self.lib.registry.module
|
||||||
{
|
{
|
||||||
registry.address.worker.self = "10.23.50.146";
|
registry.address.worker.self = "10.23.50.146";
|
||||||
system.stateVersion = "26.11";
|
registry.route.worker.self = {
|
||||||
}
|
domain = "worker.alexion.dev";
|
||||||
];
|
|
||||||
};
|
|
||||||
routeHost =
|
|
||||||
lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
specialArgs.my = self.lib;
|
|
||||||
modules = [
|
|
||||||
./modules/reverse-proxy.nix
|
|
||||||
{
|
|
||||||
modules.reverse-proxy.routes.app = {
|
|
||||||
host = "worker.alexion.dev";
|
|
||||||
endpoint = "worker";
|
|
||||||
port = 8080;
|
port = 8080;
|
||||||
};
|
};
|
||||||
|
system.stateVersion = "26.11";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -435,7 +449,6 @@
|
|||||||
inputs.sops-nix.nixosModules.sops
|
inputs.sops-nix.nixosModules.sops
|
||||||
self.lib.registry.module
|
self.lib.registry.module
|
||||||
./modules/network.nix
|
./modules/network.nix
|
||||||
./modules/reverse-proxy.nix
|
|
||||||
(self.lib.guest { name = "files"; })
|
(self.lib.guest { name = "files"; })
|
||||||
{
|
{
|
||||||
networking.hostName = "pikachu";
|
networking.hostName = "pikachu";
|
||||||
@@ -446,44 +459,47 @@
|
|||||||
address = "10.23.20.126";
|
address = "10.23.20.126";
|
||||||
reverseProxy = {
|
reverseProxy = {
|
||||||
enable = true;
|
enable = true;
|
||||||
host = "files.alexion.dev";
|
route = {
|
||||||
port = 3923;
|
domain = "files.alexion.dev";
|
||||||
|
port = 3923;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
system.stateVersion = "26.11";
|
system.stateVersion = "26.11";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
endpoints = self.lib.networkEndpoints.collectFleetEndpoints {
|
registry = self.lib.registry.collect {
|
||||||
worker = endpointHost;
|
hosts = {
|
||||||
pikachu = guestRouteHost;
|
worker = workerHost;
|
||||||
|
pikachu = guestRouteHost;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
fleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes {
|
|
||||||
inherit routeHost;
|
|
||||||
pikachu = guestRouteHost;
|
|
||||||
} endpoints;
|
|
||||||
testConfig =
|
testConfig =
|
||||||
(lib.nixosSystem {
|
(lib.nixosSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
specialArgs.my = self.lib;
|
specialArgs = {
|
||||||
|
my = self.lib;
|
||||||
|
inherit registry;
|
||||||
|
};
|
||||||
modules = [
|
modules = [
|
||||||
./modules/reverse-proxy.nix
|
./modules/reverse-proxy.nix
|
||||||
{
|
{
|
||||||
modules.reverse-proxy = {
|
modules.reverse-proxy.enable = true;
|
||||||
enable = true;
|
|
||||||
routes = fleetRoutes;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}).config;
|
}).config;
|
||||||
vhosts = testConfig.services.nginx.virtualHosts;
|
vhosts = testConfig.services.nginx.virtualHosts;
|
||||||
|
workerCompositeEntry = registry.join "worker" "self";
|
||||||
in
|
in
|
||||||
pkgs.runCommand "reverse-proxy-endpoint-backends-check" { } ''
|
pkgs.runCommand "reverse-proxy-address-backends-check" { } ''
|
||||||
${
|
${
|
||||||
lib.optionalString (
|
lib.optionalString (
|
||||||
!(
|
!(
|
||||||
vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.50.146:8080"
|
workerCompositeEntry.address == "10.23.50.146"
|
||||||
&& fleetRoutes."pikachu-files".endpoint == "pikachu-files"
|
&& workerCompositeEntry.route.domain == "worker.alexion.dev"
|
||||||
|
&& vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.50.146:8080"
|
||||||
|
&& registry.route.pikachu.files.port == 3923
|
||||||
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
&& vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923"
|
||||||
)
|
)
|
||||||
) "exit 1"
|
) "exit 1"
|
||||||
@@ -491,34 +507,95 @@
|
|||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
reverse-proxy-secondary-path-requires-root =
|
reverse-proxy-route-requires-address =
|
||||||
let
|
let
|
||||||
testConfig =
|
routeHost = lib.nixosSystem {
|
||||||
(lib.nixosSystem {
|
system = "x86_64-linux";
|
||||||
system = "x86_64-linux";
|
specialArgs.my = self.lib;
|
||||||
specialArgs.my = self.lib;
|
modules = [
|
||||||
modules = [
|
self.lib.registry.module
|
||||||
./modules/reverse-proxy.nix
|
{
|
||||||
{
|
registry.route.raichu.files = {
|
||||||
modules.reverse-proxy = {
|
domain = "files.alexion.dev";
|
||||||
enable = true;
|
port = 3923;
|
||||||
routes.raichu-files = {
|
};
|
||||||
host = "files.alexion.dev";
|
system.stateVersion = "26.11";
|
||||||
path = "/raichu";
|
}
|
||||||
backend = "http://10.23.20.10:3923";
|
];
|
||||||
};
|
};
|
||||||
};
|
registry = self.lib.registry.collect { hosts.raichu = routeHost; };
|
||||||
}
|
testConfig = lib.nixosSystem {
|
||||||
];
|
system = "x86_64-linux";
|
||||||
}).config;
|
specialArgs = {
|
||||||
hasFailedRootAssertion = lib.any (
|
my = self.lib;
|
||||||
assertion:
|
inherit registry;
|
||||||
assertion.assertion == false
|
};
|
||||||
&& assertion.message == "modules.reverse-proxy.routes for files.alexion.dev must include a primary / route."
|
modules = [
|
||||||
) testConfig.assertions;
|
./modules/reverse-proxy.nix
|
||||||
|
{
|
||||||
|
modules.reverse-proxy.enable = true;
|
||||||
|
system.stateVersion = "26.11";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
result = builtins.tryEval (builtins.deepSeq testConfig.config.services.nginx.virtualHosts true);
|
||||||
in
|
in
|
||||||
pkgs.runCommand "reverse-proxy-secondary-path-requires-root-check" { } ''
|
pkgs.runCommand "reverse-proxy-route-requires-address-check" { } ''
|
||||||
${lib.optionalString (!hasFailedRootAssertion) "exit 1"}
|
${lib.optionalString result.success "exit 1"}
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
|
||||||
|
reverse-proxy-duplicate-domain-fails =
|
||||||
|
let
|
||||||
|
pikachu = lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
specialArgs.my = self.lib;
|
||||||
|
modules = [
|
||||||
|
self.lib.registry.module
|
||||||
|
{
|
||||||
|
registry.address.pikachu.copyparty = "10.23.20.126";
|
||||||
|
registry.route.pikachu.copyparty = {
|
||||||
|
domain = "files.alexion.dev";
|
||||||
|
port = 3923;
|
||||||
|
};
|
||||||
|
system.stateVersion = "26.11";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
raichu = lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
specialArgs.my = self.lib;
|
||||||
|
modules = [
|
||||||
|
self.lib.registry.module
|
||||||
|
{
|
||||||
|
registry.address.raichu.files = "10.23.20.10";
|
||||||
|
registry.route.raichu.files = {
|
||||||
|
domain = "files.alexion.dev";
|
||||||
|
port = 3923;
|
||||||
|
};
|
||||||
|
system.stateVersion = "26.11";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
registry = self.lib.registry.collect { hosts = { inherit pikachu raichu; }; };
|
||||||
|
testConfig = lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
specialArgs = {
|
||||||
|
my = self.lib;
|
||||||
|
inherit registry;
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
./modules/reverse-proxy.nix
|
||||||
|
{
|
||||||
|
modules.reverse-proxy.enable = true;
|
||||||
|
system.stateVersion = "26.11";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
result = builtins.tryEval (builtins.deepSeq testConfig.config.services.nginx.virtualHosts true);
|
||||||
|
in
|
||||||
|
pkgs.runCommand "reverse-proxy-duplicate-domain-fails-check" { } ''
|
||||||
|
${lib.optionalString result.success "exit 1"}
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,40 +2,59 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
my,
|
my,
|
||||||
reverseProxyFleetRoutes ? config.modules.reverse-proxy.routes,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
giteaSshBackend = "10.23.20.45:2022";
|
giteaSshBackend = "10.23.20.45:2022";
|
||||||
legacyBackend = "http://10.23.20.32";
|
|
||||||
legacyHosts = [
|
|
||||||
"games.alexion.dev"
|
|
||||||
"git.alexion.dev"
|
|
||||||
"music.alexion.dev"
|
|
||||||
"photos.alexion.dev"
|
|
||||||
"plex.alexion.dev"
|
|
||||||
"servers.alexion.dev"
|
|
||||||
"todo.alexion.dev"
|
|
||||||
"wings.alexion.dev"
|
|
||||||
];
|
|
||||||
legacyRoutes = lib.listToAttrs (
|
|
||||||
map (host: {
|
|
||||||
name = "legacy-${lib.replaceStrings [ "." ] [ "-" ] host}";
|
|
||||||
value = {
|
|
||||||
inherit host;
|
|
||||||
backend = legacyBackend;
|
|
||||||
};
|
|
||||||
}) legacyHosts
|
|
||||||
);
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(my.guest {
|
(my.guest {
|
||||||
name = "reverse-proxy";
|
name = "reverse-proxy";
|
||||||
interior = {
|
interior = {
|
||||||
modules.reverse-proxy = {
|
modules.reverse-proxy.enable = true;
|
||||||
enable = true;
|
|
||||||
routes = reverseProxyFleetRoutes // legacyRoutes;
|
services.nginx.virtualHosts = {
|
||||||
|
"games.alexion.dev" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/".proxyPass = "http://10.23.20.32";
|
||||||
|
};
|
||||||
|
"git.alexion.dev" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/".proxyPass = "http://10.23.20.32";
|
||||||
|
};
|
||||||
|
"music.alexion.dev" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/".proxyPass = "http://10.23.20.32";
|
||||||
|
};
|
||||||
|
"photos.alexion.dev" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/".proxyPass = "http://10.23.20.32";
|
||||||
|
};
|
||||||
|
"plex.alexion.dev" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/".proxyPass = "http://10.23.20.32";
|
||||||
|
};
|
||||||
|
"servers.alexion.dev" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/".proxyPass = "http://10.23.20.32";
|
||||||
|
};
|
||||||
|
"todo.alexion.dev" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/".proxyPass = "http://10.23.20.32";
|
||||||
|
};
|
||||||
|
"wings.alexion.dev" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/".proxyPass = "http://10.23.20.32";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 2022 ];
|
networking.firewall.allowedTCPPorts = [ 2022 ];
|
||||||
|
|||||||
@@ -87,8 +87,10 @@
|
|||||||
address = "10.23.20.42";
|
address = "10.23.20.42";
|
||||||
reverseProxy = {
|
reverseProxy = {
|
||||||
enable = true;
|
enable = true;
|
||||||
host = "budget.alexion.dev";
|
route = {
|
||||||
port = 5006;
|
domain = "budget.alexion.dev";
|
||||||
|
port = 5006;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,8 +109,10 @@
|
|||||||
address = "10.23.20.126";
|
address = "10.23.20.126";
|
||||||
reverseProxy = {
|
reverseProxy = {
|
||||||
enable = true;
|
enable = true;
|
||||||
host = "files.alexion.dev";
|
route = {
|
||||||
port = 3923;
|
domain = "files.alexion.dev";
|
||||||
|
port = 3923;
|
||||||
|
};
|
||||||
trustedSources = [ "${registry.address.${config.networking.hostName}."reverse-proxy"}/32" ];
|
trustedSources = [ "${registry.address.${config.networking.hostName}."reverse-proxy"}/32" ];
|
||||||
};
|
};
|
||||||
volumes = {
|
volumes = {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ let
|
|||||||
serviceIdentityRegistry = import ./service-identities.nix { inherit lib; };
|
serviceIdentityRegistry = import ./service-identities.nix { inherit lib; };
|
||||||
registry = import ./registry.nix { inherit lib self; };
|
registry = import ./registry.nix { inherit lib self; };
|
||||||
networkEndpoints = import ./network-endpoints.nix { inherit lib; };
|
networkEndpoints = import ./network-endpoints.nix { inherit lib; };
|
||||||
reverseProxyRoutes = import ./reverse-proxy-routes.nix { inherit lib; };
|
|
||||||
|
|
||||||
collectNixFiles =
|
collectNixFiles =
|
||||||
dir:
|
dir:
|
||||||
@@ -35,16 +34,11 @@ let
|
|||||||
) (builtins.readDir dir)
|
) (builtins.readDir dir)
|
||||||
);
|
);
|
||||||
|
|
||||||
specialArgs =
|
specialArgs = {
|
||||||
let
|
inherit inputs self;
|
||||||
networkFleetEndpoints = self.lib.networkEndpoints.collectFleetEndpoints self.nixosConfigurations;
|
my = self.lib;
|
||||||
in
|
registry = self.lib.registry.collect { hosts = self.nixosConfigurations; };
|
||||||
{
|
};
|
||||||
inherit inputs networkFleetEndpoints self;
|
|
||||||
my = self.lib;
|
|
||||||
registry = self.lib.registry.collect { hosts = self.nixosConfigurations; };
|
|
||||||
reverseProxyFleetRoutes = self.lib.reverseProxyRoutes.collectFleetRoutes self.nixosConfigurations networkFleetEndpoints;
|
|
||||||
};
|
|
||||||
|
|
||||||
bridgeName = id: "br-vlan${toString id}";
|
bridgeName = id: "br-vlan${toString id}";
|
||||||
|
|
||||||
@@ -54,7 +48,6 @@ let
|
|||||||
self
|
self
|
||||||
specialArgs
|
specialArgs
|
||||||
bridgeName
|
bridgeName
|
||||||
reverseProxyRoutes
|
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,7 +88,6 @@ in
|
|||||||
bridgeName
|
bridgeName
|
||||||
registry
|
registry
|
||||||
networkEndpoints
|
networkEndpoints
|
||||||
reverseProxyRoutes
|
|
||||||
;
|
;
|
||||||
|
|
||||||
inherit (guests) deriveMac guest;
|
inherit (guests) deriveMac guest;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
self,
|
self,
|
||||||
specialArgs,
|
specialArgs,
|
||||||
bridgeName,
|
bridgeName,
|
||||||
reverseProxyRoutes,
|
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) escapeShellArg mapAttrsToList;
|
inherit (lib) escapeShellArg mapAttrsToList;
|
||||||
@@ -130,9 +129,13 @@ let
|
|||||||
Left null, this guest contributes no address to the address registry.
|
Left null, this guest contributes no address to the address registry.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
reverseProxy = reverseProxyRoutes.mkRouteOptions {
|
reverseProxy = {
|
||||||
subject = "guest route";
|
enable = lib.mkEnableOption "a reverse-proxy route for this guest";
|
||||||
enableDefault = false;
|
|
||||||
|
route = lib.mkOption {
|
||||||
|
type = self.lib.registry.definitions.route.type;
|
||||||
|
description = "Route registry entry published for this guest.";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
mounts = lib.mkOption {
|
mounts = lib.mkOption {
|
||||||
type = lib.types.attrsOf (
|
type = lib.types.attrsOf (
|
||||||
@@ -279,14 +282,7 @@ let
|
|||||||
|
|
||||||
registry.address.${config.networking.hostName}.${machineName} = lib.mkIf (cfg.address != null) cfg.address;
|
registry.address.${config.networking.hostName}.${machineName} = lib.mkIf (cfg.address != null) cfg.address;
|
||||||
|
|
||||||
modules.reverse-proxy.routes.${machineName} = lib.mkIf cfg.reverseProxy.enable (
|
registry.route.${config.networking.hostName}.${machineName} = lib.mkIf cfg.reverseProxy.enable cfg.reverseProxy.route;
|
||||||
reverseProxyRoutes.routeFields (
|
|
||||||
cfg.reverseProxy
|
|
||||||
// lib.optionalAttrs (cfg.reverseProxy.backend == null && cfg.reverseProxy.endpoint == null) {
|
|
||||||
endpoint = "${config.networking.hostName}-${machineName}";
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
assertions = [
|
assertions = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,23 @@ let
|
|||||||
in
|
in
|
||||||
nameValuePair (registryName name) (import path { inherit lib; })
|
nameValuePair (registryName name) (import path { inherit lib; })
|
||||||
) (filterAttrs (name: type: type == "regular" && hasSuffix ".nix" name) (builtins.readDir (self + "/registry")));
|
) (filterAttrs (name: type: type == "regular" && hasSuffix ".nix" name) (builtins.readDir (self + "/registry")));
|
||||||
|
join = registry: host: entry:
|
||||||
|
filterAttrs (_name: value: value != null) (
|
||||||
|
mapAttrs (registryName: _registry: (registry.${registryName}.${host} or { }).${entry} or null) definitions
|
||||||
|
);
|
||||||
|
|
||||||
|
collectData =
|
||||||
|
{ hosts }:
|
||||||
|
mapAttrs (
|
||||||
|
registryName: _registry:
|
||||||
|
mapAttrs (hostName: host: host.config.registry.${registryName}.${hostName} or { }) hosts
|
||||||
|
) definitions;
|
||||||
|
|
||||||
|
withOperations = registry:
|
||||||
|
registry
|
||||||
|
// {
|
||||||
|
join = host: entry: join registry host entry;
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit definitions;
|
inherit definitions;
|
||||||
@@ -36,10 +53,5 @@ in
|
|||||||
options.registry = mapAttrs (_name: registry: registryOption registry) definitions;
|
options.registry = mapAttrs (_name: registry: registryOption registry) definitions;
|
||||||
};
|
};
|
||||||
|
|
||||||
collect =
|
collect = args: withOperations (collectData args);
|
||||||
{ hosts }:
|
|
||||||
mapAttrs (
|
|
||||||
registryName: _registry:
|
|
||||||
mapAttrs (hostName: host: host.config.registry.${registryName}.${hostName} or { }) hosts
|
|
||||||
) definitions;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
{ lib }:
|
|
||||||
let
|
|
||||||
mkRouteOptions =
|
|
||||||
{
|
|
||||||
name ? null,
|
|
||||||
subject ? "route",
|
|
||||||
enableDefault ? true,
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
enable = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = enableDefault;
|
|
||||||
description = "Whether this reverse-proxy ${subject} is active.";
|
|
||||||
};
|
|
||||||
|
|
||||||
host = lib.mkOption (
|
|
||||||
{
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Primary hostname served by this reverse-proxy ${subject}.";
|
|
||||||
}
|
|
||||||
// lib.optionalAttrs (name != null) {
|
|
||||||
default = name;
|
|
||||||
defaultText = lib.literalMD "the route attribute name";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
aliases = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = "Additional hostnames served by this reverse-proxy ${subject}.";
|
|
||||||
};
|
|
||||||
|
|
||||||
path = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/";
|
|
||||||
description = "URL path prefix proxied by this reverse-proxy ${subject}.";
|
|
||||||
};
|
|
||||||
|
|
||||||
backend = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
description = "Full HTTP backend URL that receives proxied traffic.";
|
|
||||||
};
|
|
||||||
|
|
||||||
endpoint = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
description = "Fleet network endpoint key used to derive the backend URL.";
|
|
||||||
};
|
|
||||||
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.nullOr (lib.types.ints.between 1 65535);
|
|
||||||
default = null;
|
|
||||||
example = 3923;
|
|
||||||
description = "Backend TCP port used with the fleet network endpoint.";
|
|
||||||
};
|
|
||||||
|
|
||||||
scheme = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "http";
|
|
||||||
description = "Backend URL scheme used with the fleet network endpoint.";
|
|
||||||
};
|
|
||||||
|
|
||||||
tls = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Whether to serve this reverse-proxy ${subject} with ACME-backed TLS.";
|
|
||||||
};
|
|
||||||
|
|
||||||
websockets = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Whether to enable websocket proxy headers for this reverse-proxy ${subject}.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
routeFields = route: {
|
|
||||||
inherit (route)
|
|
||||||
host
|
|
||||||
aliases
|
|
||||||
path
|
|
||||||
backend
|
|
||||||
endpoint
|
|
||||||
port
|
|
||||||
scheme
|
|
||||||
tls
|
|
||||||
websockets
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
endpointBackend = endpoints: route:
|
|
||||||
let
|
|
||||||
endpoint = endpoints.${route.endpoint} or (
|
|
||||||
throw "Unknown reverse-proxy endpoint `${route.endpoint}`. Known endpoints: ${lib.concatStringsSep ", " (lib.attrNames endpoints)}"
|
|
||||||
);
|
|
||||||
in
|
|
||||||
"${route.scheme}://${endpoint.address}:${toString route.port}";
|
|
||||||
|
|
||||||
resolvedRouteFields = endpoints: route:
|
|
||||||
let
|
|
||||||
routeWithBackend =
|
|
||||||
if route.backend != null then
|
|
||||||
route
|
|
||||||
else if route.endpoint != null && route.port != null then
|
|
||||||
route // { backend = endpointBackend endpoints route; }
|
|
||||||
else
|
|
||||||
throw "Reverse-proxy route `${route.host}${route.path}` needs either backend or endpoint plus port.";
|
|
||||||
in
|
|
||||||
routeFields routeWithBackend;
|
|
||||||
|
|
||||||
collectFleetRoutes =
|
|
||||||
hosts: endpoints:
|
|
||||||
lib.concatMapAttrs (
|
|
||||||
hostName: host:
|
|
||||||
lib.mapAttrs' (
|
|
||||||
routeName: route: lib.nameValuePair "${hostName}-${routeName}" (resolvedRouteFields endpoints route)
|
|
||||||
) host.config.modules.reverse-proxy.routes
|
|
||||||
) hosts;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit collectFleetRoutes mkRouteOptions resolvedRouteFields routeFields;
|
|
||||||
|
|
||||||
routeType = lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
options = mkRouteOptions { inherit name; };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,73 +1,71 @@
|
|||||||
{ config, lib, my, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
registry,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.modules.reverse-proxy;
|
cfg = config.modules.reverse-proxy;
|
||||||
inherit (my) reverseProxyRoutes;
|
|
||||||
|
|
||||||
enabledRoutes = lib.filterAttrs (_: route: route.enable) cfg.routes;
|
mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}";
|
||||||
routesByHost = lib.groupBy (route: route.host) (lib.attrValues enabledRoutes);
|
|
||||||
|
|
||||||
rootRoutesByHost = lib.mapAttrs (
|
mkBackend = address: route: "http://${address}:${toString route.port}";
|
||||||
host: routes:
|
|
||||||
|
mkVirtualHost = host: entry:
|
||||||
let
|
let
|
||||||
roots = builtins.filter (route: route.path == "/") routes;
|
compositeEntry = registry.join host entry;
|
||||||
|
route = compositeEntry.route;
|
||||||
|
address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}");
|
||||||
in
|
in
|
||||||
if roots == [ ] then null else builtins.head roots
|
{
|
||||||
) routesByHost;
|
inherit host entry;
|
||||||
|
name = route.domain;
|
||||||
hasDuplicatePaths = routes:
|
value = {
|
||||||
let
|
forceSSL = true;
|
||||||
paths = map (route: route.path) routes;
|
enableACME = true;
|
||||||
in
|
locations."/".proxyPass = mkBackend address route;
|
||||||
lib.length paths != lib.length (lib.unique paths);
|
};
|
||||||
|
|
||||||
hasUnresolvedBackend = route: route.backend == null;
|
|
||||||
|
|
||||||
mkLocation = route: {
|
|
||||||
proxyPass = route.backend;
|
|
||||||
proxyWebsockets = route.websockets;
|
|
||||||
};
|
|
||||||
|
|
||||||
mkVirtualHost = host: routes:
|
|
||||||
let
|
|
||||||
rootRoute = rootRoutesByHost.${host};
|
|
||||||
in
|
|
||||||
lib.mkIf (rootRoute != null) {
|
|
||||||
forceSSL = rootRoute.tls;
|
|
||||||
enableACME = rootRoute.tls;
|
|
||||||
serverAliases = rootRoute.aliases;
|
|
||||||
locations = lib.listToAttrs (
|
|
||||||
map (route: lib.nameValuePair route.path (mkLocation route)) (
|
|
||||||
builtins.filter (route: route.backend != null) routes
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
generatedVirtualHostList = lib.concatLists (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
host: routes:
|
||||||
|
lib.mapAttrsToList (entry: _route: mkVirtualHost host entry) routes
|
||||||
|
) registry.route
|
||||||
|
);
|
||||||
|
|
||||||
|
virtualHostsByDomain = lib.foldl' (
|
||||||
|
acc: virtualHost:
|
||||||
|
acc
|
||||||
|
// {
|
||||||
|
${virtualHost.name} = (acc.${virtualHost.name} or [ ]) ++ [ virtualHost ];
|
||||||
|
}
|
||||||
|
) { } generatedVirtualHostList;
|
||||||
|
|
||||||
|
duplicateDomainMessages = lib.concatLists (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
domain: virtualHosts:
|
||||||
|
if lib.length virtualHosts > 1 then
|
||||||
|
[
|
||||||
|
"duplicate route domain ${domain} in ${lib.concatMapStringsSep " and " (virtualHost: mkRegistryPath "route" virtualHost.host virtualHost.entry) virtualHosts}"
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
) virtualHostsByDomain
|
||||||
|
);
|
||||||
|
|
||||||
|
generatedVirtualHosts =
|
||||||
|
if duplicateDomainMessages != [ ] then
|
||||||
|
throw (builtins.head duplicateDomainMessages)
|
||||||
|
else
|
||||||
|
lib.listToAttrs generatedVirtualHostList;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.reverse-proxy = {
|
options.modules.reverse-proxy = {
|
||||||
enable = lib.mkEnableOption "the Nginx reverse proxy route realizer";
|
enable = lib.mkEnableOption "the public edge reverse proxy";
|
||||||
|
|
||||||
routes = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf reverseProxyRoutes.routeType;
|
|
||||||
default = { };
|
|
||||||
description = "Reverse-proxy route declarations keyed by stable route name.";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
assertions =
|
|
||||||
lib.mapAttrsToList (host: rootRoute: {
|
|
||||||
assertion = rootRoute != null;
|
|
||||||
message = "modules.reverse-proxy.routes for ${host} must include a primary / route.";
|
|
||||||
}) rootRoutesByHost
|
|
||||||
++ lib.mapAttrsToList (host: routes: {
|
|
||||||
assertion = !hasDuplicatePaths routes;
|
|
||||||
message = "modules.reverse-proxy.routes for ${host} must not declare the same path more than once.";
|
|
||||||
}) routesByHost
|
|
||||||
++ map (route: {
|
|
||||||
assertion = !hasUnresolvedBackend route;
|
|
||||||
message = "modules.reverse-proxy.routes for ${route.host}${route.path} must resolve to a backend URL before actualization.";
|
|
||||||
}) (builtins.filter hasUnresolvedBackend (lib.attrValues enabledRoutes));
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
80
|
80
|
||||||
443
|
443
|
||||||
@@ -91,7 +89,7 @@ in
|
|||||||
recommendedOptimisation = true;
|
recommendedOptimisation = true;
|
||||||
recommendedProxySettings = true;
|
recommendedProxySettings = true;
|
||||||
recommendedTlsSettings = true;
|
recommendedTlsSettings = true;
|
||||||
virtualHosts = lib.mapAttrs mkVirtualHost routesByHost;
|
virtualHosts = generatedVirtualHosts;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
18
registry/route.nix
Normal file
18
registry/route.nix
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{ lib }:
|
||||||
|
{
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "files.alexion.dev";
|
||||||
|
description = "Public domain served by this reverse-proxy route entry.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.ints.between 1 65535;
|
||||||
|
example = 3923;
|
||||||
|
description = "Backend TCP port served by the matching address registry entry.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user