reverse proxy stuff

This commit is contained in:
mjallen18
2025-09-09 20:41:37 -05:00
parent 6567bb1348
commit f58006cf8a
14 changed files with 433 additions and 27 deletions

View File

@@ -10,6 +10,9 @@
# Import system utilities
system = import ./system { inherit inputs; };
# Import reverse proxy utilities
reverseproxy = import ./reverseproxy { inherit inputs; };
# Import examples
examples = import ./examples { inherit inputs; };
};

View File

@@ -0,0 +1,113 @@
# Example usage of the reverse proxy utilities
{ inputs, lib, ... }:
let
inherit (lib.mjallen-lib.reverseproxy)
mkReverseProxy
mkReverseProxies
templates
middlewares
urls
;
in
{
# Example 1: Simple reverse proxy for a local service
simpleProxy = mkReverseProxy {
name = "myapp";
subdomain = "myapp";
url = "http://127.0.0.1:3000";
};
# Example 2: Authenticated service with custom middlewares
authProxy = mkReverseProxy {
name = "admin-panel";
subdomain = "admin";
url = "http://127.0.0.1:8080";
middlewares = middlewares.authBasic;
};
# Example 3: Container-based service
containerProxy = mkReverseProxy {
name = "nextcloud";
subdomain = "cloud";
url = urls.container "nextcloud" 80;
middlewares = middlewares.basic;
};
# Example 4: Multiple proxies at once
multipleProxies = mkReverseProxies [
{
name = "grafana";
subdomain = "grafana";
url = urls.localhost 3000;
middlewares = middlewares.authBasic;
}
{
name = "prometheus";
subdomain = "prometheus";
url = urls.localhost 9090;
middlewares = middlewares.internal;
}
{
name = "alertmanager";
subdomain = "alerts";
url = urls.localhost 9093;
middlewares = middlewares.authBasic;
}
];
# Example 5: Using templates for common patterns
webappExample = templates.webapp {
name = "webapp";
subdomain = "app";
port = 8080;
};
authWebappExample = templates.authWebapp {
name = "secure-app";
subdomain = "secure";
port = 9000;
};
containerExample = templates.containerService {
name = "gitea";
subdomain = "git";
containerName = "gitea";
port = 3000;
};
internalExample = templates.internalService {
name = "internal-api";
subdomain = "api-internal";
port = 8000;
};
# Example 6: Custom domain and advanced configuration
customProxy = mkReverseProxy {
name = "custom-service";
subdomain = "custom";
url = "http://10.0.1.100:8080";
domain = "example.com";
priority = 20;
rule = "Host(`custom.example.com`) && PathPrefix(`/api`)";
middlewares = [ "crowdsec" "whitelist-geoblock" "rate-limit" ];
};
# Example usage in a Traefik configuration:
#
# mjallen.services.traefik = {
# enable = true;
# extraServices = multipleProxies.extraServices;
# extraRouters = multipleProxies.extraRouters;
# };
#
# Or for individual proxies:
#
# mjallen.services.traefik = {
# enable = true;
# extraServices = [ simpleProxy.service ];
# extraRouters = [{
# inherit (simpleProxy.router) subdomain entryPoints middlewares;
# service = simpleProxy.router.service;
# }];
# };
}

View File

@@ -90,6 +90,14 @@ rec {
mkBoolOpt' = mkOpt' types.bool;
mkReverseProxyOpt = {
enable = mkBoolOpt false "Enable reverse proxy support";
subdomain = mkOpt types.str "" "subdomain of the service";
middlewares = mkOpt (types.listOf types.str) [ ] "List of middlewares to use";
};
# Standard enable/disable patterns
enabled = {
enable = true;

View File

@@ -0,0 +1,153 @@
{ inputs }:
let
inherit (inputs.nixpkgs.lib)
mkOption
types
listToAttrs
nameValuePair
;
in
rec {
# Create a service configuration for Traefik
mkService = {
name,
url,
loadBalancer ? { },
}: {
inherit name url;
config = {
loadBalancer = {
servers = [{ inherit url; }];
} // loadBalancer;
};
};
# Create a router configuration for Traefik
mkRouter = {
subdomain,
domain ? "mjallen.dev",
service,
entryPoints ? [ "websecure" ],
middlewares ? [ "crowdsec" "whitelist-geoblock" ],
priority ? null,
rule ? null,
tls ? { certResolver = "letsencrypt"; },
}: {
inherit subdomain service entryPoints middlewares;
config = {
inherit entryPoints service middlewares tls;
rule = if rule != null then rule else "Host(`${subdomain}.${domain}`)";
} // (if priority != null then { inherit priority; } else { });
};
# Create both service and router for a simple reverse proxy setup
mkReverseProxy = {
name,
subdomain,
url,
domain ? "mjallen.dev",
entryPoints ? [ "websecure" ],
middlewares ? [ "crowdsec" "whitelist-geoblock" ],
priority ? null,
rule ? null,
tls ? { certResolver = "letsencrypt"; },
loadBalancer ? { },
}: {
service = mkService {
inherit name url loadBalancer;
};
router = mkRouter {
inherit subdomain domain entryPoints middlewares priority rule tls;
service = name;
};
};
# Convert a list of services to the format expected by Traefik module
servicesToConfig = services:
listToAttrs (map (service: nameValuePair service.name service.config) services);
# Convert a list of routers to the format expected by Traefik module
routersToConfig = routers:
listToAttrs (map (router: nameValuePair router.subdomain router.config) routers);
# Helper to create multiple reverse proxies at once
mkReverseProxies = proxies:
let
results = map mkReverseProxy proxies;
services = map (result: result.service) results;
routers = map (result: result.router) results;
in
{
services = servicesToConfig services;
routers = routersToConfig routers;
extraServices = services;
extraRouters = map (router: {
inherit (router) subdomain entryPoints middlewares;
service = router.service;
}) routers;
};
# Common middleware configurations
middlewares = {
# Authentication middleware
auth = [ "authentik" ];
# Basic security (default)
basic = [ "crowdsec" "whitelist-geoblock" ];
# Internal only access
internal = [ "crowdsec" "whitelist-geoblock" "internal-ipallowlist" ];
# WebSocket support
websocket = [ "crowdsec" "whitelist-geoblock" "onlyoffice-websocket" ];
# Authenticated with basic security
authBasic = [ "crowdsec" "whitelist-geoblock" "authentik" ];
};
# Common service URL builders
urls = {
# Local container service
container = containerName: port: "http://\${config.containers.${containerName}.localAddress}:${toString port}";
# Local host service
localhost = port: "http://127.0.0.1:${toString port}";
# Network service
network = ip: port: "http://${ip}:${toString port}";
# Server IP service (using your server IP pattern)
server = port: "http://\${serverIp}:${toString port}";
};
# Pre-configured reverse proxy templates
templates = {
# Standard web application
webapp = { name, subdomain, port, ... }@args:
mkReverseProxy ({
url = urls.localhost port;
middlewares = middlewares.basic;
} // args);
# Authenticated web application
authWebapp = { name, subdomain, port, ... }@args:
mkReverseProxy ({
url = urls.localhost port;
middlewares = middlewares.authBasic;
} // args);
# Container-based service
containerService = { name, subdomain, containerName, port, ... }@args:
mkReverseProxy ({
url = urls.container containerName port;
middlewares = middlewares.basic;
} // args);
# Internal-only service
internalService = { name, subdomain, port, ... }@args:
mkReverseProxy ({
url = urls.localhost port;
middlewares = middlewares.internal;
} // args);
};
}

View File

@@ -48,7 +48,6 @@ in
meson
nautilus
networkmanagerapplet
nm-tray
nomacs
nwg-look
overskride

View File

@@ -12,6 +12,14 @@ let
hostAddress = "10.0.1.3";
actualUserId = config.users.users.nix-apps.uid;
actualGroupId = config.users.groups.jallen-nas.gid;
# Create reverse proxy configuration using mkReverseProxy
reverseProxyConfig = lib.${namespace}.mkReverseProxy {
name = "actual";
subdomain = cfg.reverseProxy.subdomain;
url = "http://${cfg.localAddress}:${toString cfg.port}";
middlewares = cfg.reverseProxy.middlewares;
};
in
{
imports = [ ./options.nix ];
@@ -98,19 +106,23 @@ in
};
};
services.traefik.dynamicConfigOptions = lib.mkIf cfg.reverseProxy.enable {
services.actual.loadBalancer.servers = [
{
url = "http://${cfg.localAddress}:${toString cfg.port}";
}
];
routers.actual = {
entryPoints = [ "websecure" ];
rule = "Host(`${cfg.reverseProxy.host}`)";
service = "actual";
middlewares = cfg.reverseProxy.middlewares;
tls.certResolver = "letsencrypt";
};
# services.traefik.dynamicConfigOptions = lib.mkIf cfg.reverseProxy.enable {
# services.actual.loadBalancer.servers = [
# {
# url = "http://${cfg.localAddress}:${toString cfg.port}";
# }
# ];
# routers.actual = {
# entryPoints = [ "websecure" ];
# rule = "Host(`${cfg.reverseProxy.host}`)";
# service = "actual";
# middlewares = cfg.reverseProxy.middlewares;
# tls.certResolver = "letsencrypt";
# };
# };
${namespace}.services.traefik = lib.mkIf cfg.reverseProxy.enable {
reverseProxies = [ reverseProxyConfig ];
};
networking = {

View File

@@ -1,6 +1,6 @@
{ lib, namespace, ... }:
let
inherit (lib.${namespace}) mkOpt mkBoolOpt;
inherit (lib.${namespace}) mkOpt mkReverseProxyOpt;
in
with lib;
{
@@ -13,13 +13,6 @@ with lib;
dataDir = mkOpt types.str "" "Path to the data dir";
reverseProxy = {
enable = mkBoolOpt false "Enable reverse proxy support";
host = mkOpt types.str "" "Address of the proxy";
middlewares = with types; mkOpt (listOf str) [ ] "List of middlewares to use";
};
reverseProxy = mkReverseProxyOpt;
};
}

View File

@@ -1,6 +1,7 @@
{
config,
lib,
pkgs,
namespace,
...
}:
@@ -18,6 +19,10 @@ let
nextcloudPortExtHttp = 9988;
nextcloudPortExtHttps = 9943;
onlyofficePortExt = 9943;
nextcloudPhotos = pkgs.${namespace}.photos;
nextcloudPdfViewer = pkgs.${namespace}.pdfviewer;
nextcloudAssist = pkgs.${namespace}.assistant;
in
{
imports = [ ./options.nix ];
@@ -83,13 +88,34 @@ in
# datadir = "/data";
database.createLocally = true;
hostName = "cloud.mjallen.dev";
appstoreEnable = true;
appstoreEnable = false;
caching.redis = true;
configureRedis = true;
enableImagemagick = true;
https = true;
secretFile = secretsFile;
extraApps = {
inherit (pkgs.nextcloud31Packages.apps) app_api
bookmarks
mail
calendar
contacts
integration_openai
integration_paperless
maps
oidc_login
onlyoffice
previewgenerator
recognize
richdocuments
user_oidc;
inherit nextcloudPhotos
nextcloudPdfViewer
nextcloudAssist;
};
config = {
adminuser = "mjallen";
adminpassFile = adminpass;

View File

@@ -8,6 +8,48 @@ with lib;
let
cfg = config.${namespace}.services.traefik;
# Process extraServices into service configurations
extraServiceConfigs =
let
makeService = service: nameValuePair service.name {
loadBalancer.servers = [
{
url = service.url;
}
];
};
in
listToAttrs (map makeService cfg.extraServices);
# Process extraRouters into router configurations
extraRouterConfigs =
let
makeRouter = router: nameValuePair router.subdomain {
entryPoints = router.entryPoints;
rule = "Host(`${router.subdomain}.${domain}`)";
service = router.service;
middlewares = router.middlewares ++ [
"crowdsec"
"whitelist-geoblock"
];
tls.certResolver = "letsencrypt";
};
in
listToAttrs (map makeRouter cfg.extraRouters);
# Process reverseProxies into service and router configurations
reverseProxyServiceConfigs =
let
makeService = reverseProxy: nameValuePair reverseProxy.service.name reverseProxy.service.config;
in
listToAttrs (map makeService cfg.reverseProxies);
reverseProxyRouterConfigs =
let
makeRouter = reverseProxy: nameValuePair reverseProxy.router.subdomain reverseProxy.router.config;
in
listToAttrs (map makeRouter cfg.reverseProxies);
domain = "mjallen.dev";
serverIp = "10.0.1.3";
@@ -318,7 +360,7 @@ in
url = paperlessUrl;
}
];
};
} // extraServiceConfigs // reverseProxyServiceConfigs;
routers = {
auth = {
@@ -447,7 +489,7 @@ in
];
tls.certResolver = "letsencrypt";
};
};
} // extraRouterConfigs // reverseProxyRouterConfigs;
};
};
};

View File

@@ -1,7 +1,34 @@
{ lib, namespace, ... }:
with lib;
let
inherit (lib.${namespace}) mkOpt mkBoolOpt;
in
{
options.${namespace}.services.traefik = {
enable = mkEnableOption "enable traefik";
extraServices = mkOpt (types.listOf (types.submodule {
options = {
name = mkOpt types.str "" "Name of the service";
url = mkOpt types.str "http://localhost:8080" "Url of the service";
};
})) [ ] "List of extra services to forward";
extraRouters = mkOpt (types.listOf (types.submodule {
options = {
entryPoints = mkOpt (types.listOf types.str) [ "websecure" ] "Entrypoint";
subdomain = mkOpt types.str "" "subdomain of the service";
service = mkOpt types.str "" "name of the service";
middlewares = mkOpt (types.listOf (types.enum [
"authentik"
"onlyoffice-websocket"
"crowdsec"
"whitelist-geoblock"
"internal-ipallowlist"
])) [ ] "List of middlewares to enable";
};
})) [ ] "List of extra services to forward";
reverseProxies = mkOpt (types.listOf types.attrs) [ ] "List of reverse proxy configurations from mkReverseProxy";
};
}

View File

@@ -0,0 +1,10 @@
{
fetchNextcloudApp,
...
}:
fetchNextcloudApp {
name = "assistant";
sha256 = "sha256-kW2rbgfhCg4RHp/RW+L1vuoyVXOp5r4Mc1VdI0g5cXA=";
url = "https://github.com/nextcloud/assistant/archive/refs/tags/v2.8.0.tar.gz";
license = "agpl3Only";
}

View File

@@ -0,0 +1,10 @@
{
fetchNextcloudApp,
...
}:
fetchNextcloudApp {
name = "files_pdfviewer";
sha256 = "sha256-TeNOzRczeXK15DURrZ5al0cvXhRj7+y1VA4axPROvD4=";
url = "https://github.com/nextcloud/files_pdfviewer/archive/refs/tags/v31.0.8.tar.gz";
license = "agpl3Only";
}

View File

@@ -0,0 +1,10 @@
{
fetchNextcloudApp,
...
}:
fetchNextcloudApp {
name = "photos";
sha256 = "sha256-F2hh/0RlLG2zcEatfd4fejRV0i2hMkwONM4P7nhdh18=";
url = "https://github.com/nextcloud/photos/archive/refs/tags/v31.0.8.tar.gz";
license = "agpl3Only";
}

View File

@@ -21,7 +21,7 @@
dataDir = "/media/nas/main/nix-app-data/actual";
reverseProxy = {
enable = true;
host = "actual.mjallen.dev";
subdomain = "actual";
middlewares = [
"crowdsec"
"whitelist-geoblock"