diff --git a/lib/default.nix b/lib/default.nix index f5d49c3..e55d227 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -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; }; }; diff --git a/lib/examples/reverseproxy.nix b/lib/examples/reverseproxy.nix new file mode 100644 index 0000000..1b6e0c1 --- /dev/null +++ b/lib/examples/reverseproxy.nix @@ -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; + # }]; + # }; +} diff --git a/lib/module/default.nix b/lib/module/default.nix index ba8e8f9..882f290 100644 --- a/lib/module/default.nix +++ b/lib/module/default.nix @@ -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; diff --git a/lib/reverseproxy/default.nix b/lib/reverseproxy/default.nix new file mode 100644 index 0000000..25ae814 --- /dev/null +++ b/lib/reverseproxy/default.nix @@ -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); + }; +} diff --git a/modules/home/programs/hyprland/default.nix b/modules/home/programs/hyprland/default.nix index cc0d4c7..9763e17 100644 --- a/modules/home/programs/hyprland/default.nix +++ b/modules/home/programs/hyprland/default.nix @@ -48,7 +48,6 @@ in meson nautilus networkmanagerapplet - nm-tray nomacs nwg-look overskride diff --git a/modules/nixos/services/actual/default.nix b/modules/nixos/services/actual/default.nix index bed6a3f..39ac775 100644 --- a/modules/nixos/services/actual/default.nix +++ b/modules/nixos/services/actual/default.nix @@ -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 = { diff --git a/modules/nixos/services/actual/options.nix b/modules/nixos/services/actual/options.nix index 55c11b9..3d1c30e 100644 --- a/modules/nixos/services/actual/options.nix +++ b/modules/nixos/services/actual/options.nix @@ -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; }; } diff --git a/modules/nixos/services/nextcloud/default.nix b/modules/nixos/services/nextcloud/default.nix index 82f2760..a26ea28 100755 --- a/modules/nixos/services/nextcloud/default.nix +++ b/modules/nixos/services/nextcloud/default.nix @@ -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; diff --git a/modules/nixos/services/traefik/default.nix b/modules/nixos/services/traefik/default.nix index 462ffbf..a73c80f 100755 --- a/modules/nixos/services/traefik/default.nix +++ b/modules/nixos/services/traefik/default.nix @@ -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; }; }; }; diff --git a/modules/nixos/services/traefik/options.nix b/modules/nixos/services/traefik/options.nix index 6a5e541..7b163e0 100644 --- a/modules/nixos/services/traefik/options.nix +++ b/modules/nixos/services/traefik/options.nix @@ -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"; }; } diff --git a/packages/nextcloud/assistant/default.nix b/packages/nextcloud/assistant/default.nix new file mode 100644 index 0000000..edf1849 --- /dev/null +++ b/packages/nextcloud/assistant/default.nix @@ -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"; +} diff --git a/packages/nextcloud/pdfviewer/default.nix b/packages/nextcloud/pdfviewer/default.nix new file mode 100644 index 0000000..cd1bcf0 --- /dev/null +++ b/packages/nextcloud/pdfviewer/default.nix @@ -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"; +} diff --git a/packages/nextcloud/photos/default.nix b/packages/nextcloud/photos/default.nix new file mode 100644 index 0000000..34d8121 --- /dev/null +++ b/packages/nextcloud/photos/default.nix @@ -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"; +} \ No newline at end of file diff --git a/systems/x86_64-linux/jallen-nas/apps.nix b/systems/x86_64-linux/jallen-nas/apps.nix index 6c78d73..af1fd64 100755 --- a/systems/x86_64-linux/jallen-nas/apps.nix +++ b/systems/x86_64-linux/jallen-nas/apps.nix @@ -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"