feat(reverse-proxy): add Pikachu reverse proxy guest
This commit is contained in:
59
flake.nix
59
flake.nix
@@ -117,6 +117,65 @@
|
||||
${lib.optionalString (!serviceIdentityTestsPass) "exit 1"}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
reverse-proxy-basic-route =
|
||||
let
|
||||
testConfig =
|
||||
(lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
./modules/reverse-proxy.nix
|
||||
{
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes."budget.alexion.dev" = {
|
||||
path = "/";
|
||||
backend = "http://10.23.20.42:5006";
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
vhost = testConfig.services.nginx.virtualHosts."budget.alexion.dev";
|
||||
in
|
||||
pkgs.runCommand "reverse-proxy-basic-route-check" { } ''
|
||||
${
|
||||
lib.optionalString (
|
||||
!(vhost.forceSSL && vhost.enableACME && vhost.locations."/".proxyPass == "http://10.23.20.42:5006")
|
||||
) "exit 1"
|
||||
}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
reverse-proxy-secondary-path-requires-root =
|
||||
let
|
||||
testConfig =
|
||||
(lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
./modules/reverse-proxy.nix
|
||||
{
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes.raichu-files = {
|
||||
host = "files.alexion.dev";
|
||||
path = "/raichu";
|
||||
backend = "http://10.23.20.10:3923";
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
hasFailedRootAssertion = lib.any (
|
||||
assertion:
|
||||
assertion.assertion == false
|
||||
&& assertion.message == "modules.reverse-proxy.routes for files.alexion.dev must include a primary / route."
|
||||
) testConfig.assertions;
|
||||
in
|
||||
pkgs.runCommand "reverse-proxy-secondary-path-requires-root-check" { } ''
|
||||
${lib.optionalString (!hasFailedRootAssertion) "exit 1"}
|
||||
touch $out
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
51
guests/reverse-proxy.nix
Normal file
51
guests/reverse-proxy.nix
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
my,
|
||||
...
|
||||
}:
|
||||
let
|
||||
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
|
||||
{
|
||||
imports = [
|
||||
(my.guest {
|
||||
name = "reverse-proxy";
|
||||
interior = {
|
||||
modules.reverse-proxy = {
|
||||
enable = true;
|
||||
routes = config.modules.reverse-proxy.routes // legacyRoutes;
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
config = lib.mkIf config.guests.reverse-proxy.enable {
|
||||
guests.reverse-proxy = {
|
||||
limits = {
|
||||
memory = lib.mkDefault "1G";
|
||||
cpu = lib.mkDefault "100%";
|
||||
tasksMax = lib.mkDefault 256;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -74,6 +74,12 @@
|
||||
modules.git.enable = true;
|
||||
modules.toolkit.enable = true;
|
||||
|
||||
modules.services.actualbudget.reverseProxy = {
|
||||
enable = true;
|
||||
host = "budget.alexion.dev";
|
||||
backend = "http://10.23.20.42:5006";
|
||||
};
|
||||
|
||||
guests.actualbudget = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
@@ -81,6 +87,12 @@
|
||||
dataPath = "/pikachu/data/actualbudget";
|
||||
};
|
||||
|
||||
guests.reverse-proxy = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
mac = "02:98:64:5a:07:4f";
|
||||
};
|
||||
|
||||
guests.copyparty = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
|
||||
130
modules/reverse-proxy.nix
Normal file
130
modules/reverse-proxy.nix
Normal file
@@ -0,0 +1,130 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.modules.reverse-proxy;
|
||||
|
||||
routeType = lib.types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether this reverse-proxy route is active.";
|
||||
};
|
||||
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
defaultText = lib.literalMD "the route attribute name";
|
||||
description = "Primary hostname served by this route.";
|
||||
};
|
||||
|
||||
aliases = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional hostnames served by the primary route.";
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/";
|
||||
description = "URL path prefix proxied by this route.";
|
||||
};
|
||||
|
||||
backend = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Full HTTP backend URL that receives proxied traffic.";
|
||||
};
|
||||
|
||||
tls = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to serve this route with ACME-backed TLS.";
|
||||
};
|
||||
|
||||
websockets = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable websocket proxy headers for this route.";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
enabledRoutes = lib.filterAttrs (_: route: route.enable) cfg.routes;
|
||||
routesByHost = lib.groupBy (route: route.host) (lib.attrValues enabledRoutes);
|
||||
|
||||
rootRoutesByHost = lib.mapAttrs (
|
||||
host: routes:
|
||||
let
|
||||
roots = builtins.filter (route: route.path == "/") routes;
|
||||
in
|
||||
if roots == [ ] then null else builtins.head roots
|
||||
) routesByHost;
|
||||
|
||||
hasDuplicatePaths = routes:
|
||||
let
|
||||
paths = map (route: route.path) routes;
|
||||
in
|
||||
lib.length paths != lib.length (lib.unique paths);
|
||||
|
||||
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)) routes
|
||||
);
|
||||
};
|
||||
in
|
||||
{
|
||||
options.modules.reverse-proxy = {
|
||||
enable = lib.mkEnableOption "the Nginx reverse proxy route realizer";
|
||||
|
||||
routes = lib.mkOption {
|
||||
type = lib.types.attrsOf routeType;
|
||||
default = { };
|
||||
description = "Reverse-proxy route declarations keyed by stable route name.";
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "contact@alexion.dev";
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
virtualHosts = lib.mapAttrs mkVirtualHost routesByHost;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
}:
|
||||
let
|
||||
cfg = config.modules.services.actualbudget;
|
||||
rp = cfg.reverseProxy;
|
||||
in
|
||||
{
|
||||
options.modules.services.actualbudget = {
|
||||
@@ -40,23 +41,75 @@ in
|
||||
default = "storage";
|
||||
description = "Group account that owns Actual Budget's writable state.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
uid = cfg.uid;
|
||||
group = cfg.group;
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkEnableOption "the public reverse-proxy route for Actual Budget";
|
||||
|
||||
services.actual = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
settings = {
|
||||
inherit (cfg) port dataDir;
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Primary hostname served by the Actual Budget route.";
|
||||
};
|
||||
|
||||
aliases = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional hostnames served by the Actual Budget route.";
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/";
|
||||
description = "URL path prefix proxied to Actual Budget.";
|
||||
};
|
||||
|
||||
backend = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Full HTTP backend URL for the Actual Budget service.";
|
||||
};
|
||||
|
||||
tls = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to serve Actual Budget with ACME-backed TLS.";
|
||||
};
|
||||
|
||||
websockets = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable websocket proxying for Actual Budget.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf rp.enable {
|
||||
modules.reverse-proxy.routes.actualbudget = {
|
||||
inherit (rp)
|
||||
host
|
||||
aliases
|
||||
path
|
||||
backend
|
||||
tls
|
||||
websockets
|
||||
;
|
||||
};
|
||||
})
|
||||
(lib.mkIf cfg.enable {
|
||||
users.users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
uid = cfg.uid;
|
||||
group = cfg.group;
|
||||
};
|
||||
|
||||
services.actual = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
settings = {
|
||||
inherit (cfg) port dataDir;
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user