feat: Add nginx module

Change-Id: I34fbb926c4b7eab344c1c14de4e4b5f82c6c30eb
Reviewed-on: https://git.clicks.codes/c/Infra/NixFiles/+/785
Reviewed-by: Samuel Shuert <coded@clicks.codes>
Tested-by: Skyler Grey <minion@clicks.codes>
diff --git a/lib/modules/default.nix b/lib/modules/default.nix
new file mode 100644
index 0000000..f0b6092
--- /dev/null
+++ b/lib/modules/default.nix
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, ... }: {
+  modules = {
+    unset = lib.modules.mkIf false null;
+  };
+}
diff --git a/lib/nginx/http/default.nix b/lib/nginx/http/default.nix
new file mode 100644
index 0000000..ad0e822
--- /dev/null
+++ b/lib/nginx/http/default.nix
@@ -0,0 +1,53 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, inputs, ... }:
+{
+  nginx.http = let
+    _directory = listContents: root: {
+      inherit root listContents;
+      _type = "directory";
+      headers = null;
+    };
+    _redirect = permanent: to: {
+      inherit to permanent;
+      _type = "redirect";
+      headers = null;
+    };
+    _reverseProxy = protocol: host: port: {
+      inherit protocol host port;
+      _type = "reverseProxy";
+      headers = null;
+    };
+  in {
+    # Header Manipulation
+    addHeader = name: value: content: {
+      inherit name value content;
+      _type = "header";
+    };
+    unsafeAddCrossOriginHeader = lib.clicks.nginx.http.addHeader "Access-Control-Allow-Origin" "*";
+
+    # Location translatable directives
+
+    directory = _directory true;
+    privateDirectory = _directory false;
+
+    file = path: {
+      inherit path;
+      _type = "file";
+    };
+
+    redirect = _redirect false;
+    redirectPermanent = _redirect true;
+
+    reverseProxySecure = _reverseProxy "https";
+    reverseProxy = _reverseProxy "http";
+
+    status = code: {
+      inherit code;
+      _type = "status";
+      headers = null;
+    };
+  };
+}
diff --git a/lib/nginx/http/internal/README.md b/lib/nginx/http/internal/README.md
new file mode 100644
index 0000000..7b0813c
--- /dev/null
+++ b/lib/nginx/http/internal/README.md
@@ -0,0 +1,7 @@
+<!--
+SPDX-FileCopyrightText: 2024 Clicks Codes
+
+SPDX-License-Identifier: GPL-3.0-only
+-->
+
+dag ordering should start at 100 and should be spaced 50 apart
diff --git a/lib/nginx/http/internal/default.nix b/lib/nginx/http/internal/default.nix
new file mode 100644
index 0000000..767bc6f
--- /dev/null
+++ b/lib/nginx/http/internal/default.nix
@@ -0,0 +1,173 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, inputs, ... }: {
+  nginx.http.internal = {
+    generateWwwRedirects = hosts: let
+      withWww = name: host: if host.www
+        then {
+          "${name}" = lib.attrsets.removeAttrs host [ "www" ];
+          "www.${name}" = {
+            routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere (lib.clicks.nginx.http.redirectPermanent "https://${name}/$1");
+            service = null;
+            enableHttp = host.enableHttp;
+            authWith = null;
+          };
+        }
+        else {
+          "${name}" = lib.attrsets.removeAttrs host [ "www" ];
+        };
+    in lib.pipe hosts [
+      (lib.attrsets.mapAttrsToList withWww)
+      (lib.lists.foldr (host: merged: merged // host) {})
+    ];
+
+    assertExactlyOneOfServiceOrRoutes = hosts: let
+      validate = name: host: let
+        routesProvided = host.routes != null && host.routes != {};
+        serviceProvided = host.service != null;
+      in
+        if !serviceProvided && !routesProvided
+        then builtins.throw "${name}: You must provide one of `service` or `routes`"
+        else if serviceProvided && routesProvided
+          then builtins.throw "${name}: You may only provide one of `service` or `routes`"
+          else host;
+    in lib.attrsets.mapAttrs validate hosts;
+
+    assertTailscaleAuthServiceOnly = hosts: let
+      validate = name: host:
+        if host.authWith != "tailscale" || host.routes == null || host.routes == {}
+        then host
+        else builtins.throw "${name}: You may not use tailscale auth when manually specifying `routes`. Please use `service` instead";
+    in lib.attrsets.mapAttrs validate hosts;
+
+    translateServiceToRoutes = hosts: let
+      validate = name: value:
+        lib.trivial.throwIf (value.service != null && value.routes != null && value.routes != {}) "Both `service` and `routes` cannot be set at the same time" value;
+      translate = name: value:
+        lib.attrsets.removeAttrs (
+          if (value.service != null)
+          then value // {
+            routes."/" = lib.home-manager.hm.dag.entryAnywhere value.service;
+          }
+          else value
+        ) [ "service" ];
+    in lib.attrsets.mapAttrs translate
+      (lib.attrsets.mapAttrs validate hosts);
+
+    translateRoutesDagToList = hosts: let
+      hostTranslateRoutesDagToList = name: host: host // {
+        routes = let
+          HMDagRoutes = lib.home-manager.hm.dag.topoSort host.routes;
+        in map ({name, data}: { inherit name; value = data; }) HMDagRoutes.result;
+      };
+    in lib.attrsets.mapAttrs hostTranslateRoutesDagToList hosts;
+
+    unwrapHeaders = hosts: let
+      unwrapHeader = route:
+        if lib.types.isType "header" route.value
+          then unwrapHeader {
+            name = route.name;
+            value = lib.attrsets.recursiveUpdate route.value.content {
+              headers = (route.value.headers or {}) // {
+                "${route.value.name}" = "${route.value.value}";
+              };
+            };
+          }
+          else route;
+
+      hostUnwrapHeaders = name: host: host // {
+        routes = lib.lists.forEach host.routes unwrapHeader;
+      };
+    in lib.attrsets.mapAttrs hostUnwrapHeaders hosts;
+
+    translateRoutesToLocations = hosts: let
+      addHeaderToLocation = header: location:
+        location // {
+          extraConfig = (location.extraConfig or "") + "\nadd_header \"${header.name}\" \"${header.value}\";";
+        };
+      addHeadersToLocation = headers: location:
+        if headers == null
+          then location
+          else lib.trivial.pipe headers [
+            lib.attrsets.attrsToList
+            (lib.lists.foldr addHeaderToLocation location)
+          ];
+      serviceTypeHandlers = {
+        directory = service: {
+          root = service.root;
+          index = "index.html index.htm";
+          extraConfig = lib.strings.optionalString
+            service.listContents
+            "autoindex on;";
+        };
+        file = service: {
+          root = "/";
+          tryFiles = "${service.path} =404";
+        };
+        redirect = service: let
+          statusCode =
+            if service.permanent
+            then "307"
+            else "308";
+        in {
+          return = "${statusCode} ${service.to}";
+        };
+        reverseProxy = service: {
+          proxyPass = "${service.protocol}://${service.host}:${builtins.toString service.port}";
+          proxyWebsockets = true;
+          recommendedProxySettings = true;
+        };
+        status = service: {
+          return = builtins.toString service.statusCode;
+        };
+      };
+      translateServiceToLocation = service: lib.trivial.pipe service.value [
+        serviceTypeHandlers.${service.value._type}
+        (addHeadersToLocation service.value.headers)
+        (location: { name = service.name; value = location; })
+      ];
+      hostTranslateRoutesToLocations = name: host: lib.attrsets.removeAttrs (host // {
+        locations = lib.lists.forEach host.routes translateServiceToLocation;
+      }) [ "routes" ];
+    in lib.attrsets.mapAttrs hostTranslateRoutesToLocations hosts;
+
+    setLocationPriorities = hosts: let
+      priorityByIndex = index: 100 + index * 50;
+      locationWithPriority = index: location: {
+        name = location.name;
+        value = location.value // {
+          priority = location.value.priority or priorityByIndex index;
+        };
+      };
+      setHostLocationPriority = name: host: host // {
+        locations = lib.pipe host.locations [
+          (lib.lists.imap0 locationWithPriority)
+          lib.attrsets.listToAttrs
+        ];
+      };
+    in lib.attrsets.mapAttrs setHostLocationPriority hosts;
+
+    addListenDefaults = hosts: let
+      setDefaults = name: host: lib.attrsets.removeAttrs (host // {
+        onlySSL = !host.enableHttp;
+        addSSL = host.enableHttp;
+
+        useACMEHost = name;
+      }) [ "enableHttp" ];
+    in lib.attrsets.mapAttrs setDefaults hosts;
+
+    serviceTranslation = hosts: lib.trivial.pipe hosts [
+      lib.clicks.nginx.http.internal.assertExactlyOneOfServiceOrRoutes
+      lib.clicks.nginx.http.internal.assertTailscaleAuthServiceOnly
+      lib.clicks.nginx.http.internal.generateWwwRedirects
+      lib.clicks.nginx.http.internal.translateServiceToRoutes
+      lib.clicks.nginx.http.internal.translateRoutesDagToList
+      lib.clicks.nginx.http.internal.unwrapHeaders
+      lib.clicks.nginx.http.internal.translateRoutesToLocations
+      lib.clicks.nginx.http.internal.setLocationPriorities
+      lib.clicks.nginx.http.internal.addListenDefaults
+    ];
+  };
+}
diff --git a/lib/nginx/http/internal/lib.nginx.http.internal.assertion.spec.nix b/lib/nginx/http/internal/lib.nginx.http.internal.assertion.spec.nix
new file mode 100644
index 0000000..946ef61
--- /dev/null
+++ b/lib/nginx/http/internal/lib.nginx.http.internal.assertion.spec.nix
@@ -0,0 +1,71 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, ... }: let
+  tailscaleAuthWithRoute = {
+    "calibre.clicks.codes" = {
+      routes."/somePath" = lib.home-manager.hm.dag.entryAnywhere {
+        host = "generic";
+        port = 1024;
+        _type = "reverseProxy";
+        headers = null;
+      };
+      dnsProvider = "cloudflare";
+      service = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+  };
+  serviceAndRoutes = {
+    "calibre.clicks.codes" = {
+      routes."/somePath" = lib.home-manager.hm.dag.entryAnywhere {
+        host = "generic";
+        port = 1024;
+        _type = "reverseProxy";
+        headers = null;
+      };
+      dnsProvider = "cloudflare";
+      service = {
+        host = "generic";
+        port = 1024;
+        _type = "reverseProxy";
+        headers = null;
+      };
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+  };
+  neitherServiceOrRoutes = {
+    "calibre.clicks.codes" = {
+      routes = null;
+      dnsProvider = "cloudflare";
+      service = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+  };
+in {
+  testAssertTailscaleAuthServiceOnlyFail = {
+    /* This is NOT considered a step, here we test that it passes through input */
+    expr = lib.clicks.nginx.http.internal.assertTailscaleAuthServiceOnly tailscaleAuthWithRoute;
+    expectedError = {
+      type = "ThrownError";
+      msg = "calibre.clicks.codes: You may not use tailscale auth when manually specifying `routes`. Please use `service` instead";
+    };
+  };
+  testAssertExactlyOneOServiceOrRoutesBothProvidedFail = {
+    expr = lib.clicks.nginx.http.internal.assertExactlyOneOfServiceOrRoutes serviceAndRoutes;
+    expectedError = {
+      type = "ThrownError";
+      msg = "calibre.clicks.codes: You may only provide one of `service` or `routes`";
+    };
+  };
+  testAssertExactlyOneOfServiceOrRoutesNeitherProvidedFail = {
+    expr = lib.clicks.nginx.http.internal.assertExactlyOneOfServiceOrRoutes neitherServiceOrRoutes;
+    expectedError = {
+      type = "ThrownError";
+      msg = "calibre.clicks.codes: You must provide one of `service` or `routes`";
+    };
+  };
+}
diff --git a/lib/nginx/http/internal/lib.nginx.http.internal.design.spec.nix b/lib/nginx/http/internal/lib.nginx.http.internal.design.spec.nix
new file mode 100644
index 0000000..6710523
--- /dev/null
+++ b/lib/nginx/http/internal/lib.nginx.http.internal.design.spec.nix
@@ -0,0 +1,238 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, ... }: let
+  step0 = {
+    "calibre.clicks.codes" = {
+      service = lib.clicks.nginx.http.reverseProxy "generic" 1024;
+      www = true;
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+  };
+  step1 = {
+    "calibre.clicks.codes" = {
+      service = {
+        host = "generic";
+        port = 1024;
+        protocol = "http";
+        _type = "reverseProxy";
+        headers = null;
+      };
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      service = null;
+      enableHttp = false;
+    };
+  };
+  step2 = {
+    "calibre.clicks.codes" = {
+      routes."/" = lib.home-manager.hm.dag.entryAnywhere {
+        host = "generic";
+        port = 1024;
+        protocol = "http";
+        _type = "reverseProxy";
+        headers = null;
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step3 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            host = "generic";
+            port = 1024;
+            protocol = "http";
+            _type = "reverseProxy";
+            headers = null;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step4 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            host = "generic";
+            port = 1024;
+            protocol = "http";
+            _type = "reverseProxy";
+            headers = null;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step5 = {
+    "calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "/";
+          value = {
+            proxyPass = "http://generic:1024";
+            proxyWebsockets = true;
+            recommendedProxySettings = true;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            return = "307 https://calibre.clicks.codes/$1";
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step6 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "http://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step7 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "http://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+      };
+      onlySSL = true;
+      addSSL = false;
+      useACMEHost = "calibre.clicks.codes";
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      onlySSL = true;
+      addSSL = false;
+      useACMEHost = "www.calibre.clicks.codes";
+      authWith = null;
+    };
+  };
+in {
+  testGenerateWwwRedirects = {
+    expr = lib.clicks.nginx.http.internal.generateWwwRedirects step0;
+    expected = step1;
+  };
+  testTranslateServiceToRoutes = {
+    expr = lib.clicks.nginx.http.internal.translateServiceToRoutes step1;
+    expected = step2;
+  };
+  testTranslateRoutesDagToList = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesDagToList step2;
+    expected = step3;
+  };
+  testUnwrapHeaderPassthrough = {
+    expr = lib.clicks.nginx.http.internal.unwrapHeaders step3;
+    expected = step4;
+  };
+  testTranslateRoutesToLocations = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesToLocations step4;
+    expected = step5;
+  };
+  testSetLocationPriorities = {
+    expr = lib.clicks.nginx.http.internal.setLocationPriorities step5;
+    expected = step6;
+  };
+  testAddListenDefaults = {
+    expr = lib.clicks.nginx.http.internal.addListenDefaults step6;
+    expected = step7;
+  };
+}
diff --git a/lib/nginx/http/internal/lib.nginx.http.internal.header.spec.nix b/lib/nginx/http/internal/lib.nginx.http.internal.header.spec.nix
new file mode 100644
index 0000000..97756f4
--- /dev/null
+++ b/lib/nginx/http/internal/lib.nginx.http.internal.header.spec.nix
@@ -0,0 +1,258 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, ... }: let
+  step0 = {
+    "calibre.clicks.codes" = {
+      service = lib.clicks.nginx.http.unsafeAddCrossOriginHeader (lib.clicks.nginx.http.reverseProxy "generic" 1024);
+      www = true;
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+  };
+  step1 = {
+    "calibre.clicks.codes" = {
+      service = {
+        _type = "header";
+        name = "Access-Control-Allow-Origin";
+        value = "*";
+        content = {
+          host = "generic";
+          port = 1024;
+          protocol = "http";
+          _type = "reverseProxy";
+          headers = null;
+        };
+      };
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      service = null;
+      enableHttp = false;
+    };
+  };
+  step2 = {
+    "calibre.clicks.codes" = {
+      routes."/" = lib.home-manager.hm.dag.entryAnywhere {
+        _type = "header";
+        name = "Access-Control-Allow-Origin";
+        value = "*";
+        content = {
+          host = "generic";
+          port = 1024;
+          protocol = "http";
+          _type = "reverseProxy";
+          headers = null;
+        };
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step3 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            _type = "header";
+            name = "Access-Control-Allow-Origin";
+            value = "*";
+            content = {
+              host = "generic";
+              port = 1024;
+              protocol = "http";
+              _type = "reverseProxy";
+              headers = null;
+            };
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step4 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            host = "generic";
+            port = 1024;
+            protocol = "http";
+            _type = "reverseProxy";
+            headers = {
+              "Access-Control-Allow-Origin" = "*";
+            };
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step5 = {
+    "calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "/";
+          value = {
+            proxyPass = "http://generic:1024";
+            proxyWebsockets = true;
+            recommendedProxySettings = true;
+            extraConfig = "\nadd_header \"Access-Control-Allow-Origin\" \"*\";";
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            return = "307 https://calibre.clicks.codes/$1";
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step6 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "http://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+        extraConfig = "\nadd_header \"Access-Control-Allow-Origin\" \"*\";";
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step7 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "http://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+        extraConfig = "\nadd_header \"Access-Control-Allow-Origin\" \"*\";";
+      };
+      onlySSL = true;
+      addSSL = false;
+      useACMEHost = "calibre.clicks.codes";
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      onlySSL = true;
+      addSSL = false;
+      useACMEHost = "www.calibre.clicks.codes";
+      authWith = null;
+    };
+  };
+in {
+  testGenerateWwwRedirects = {
+    expr = lib.clicks.nginx.http.internal.generateWwwRedirects step0;
+    expected = step1;
+  };
+  testTranslateServiceToRoutes = {
+    expr = lib.clicks.nginx.http.internal.translateServiceToRoutes step1;
+    expected = step2;
+  };
+  testTranslateRoutesDagToList = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesDagToList step2;
+    expected = step3;
+  };
+  testUnwrapHeader = {
+    expr = lib.clicks.nginx.http.internal.unwrapHeaders step3;
+    expected = step4;
+  };
+  testTranslateRoutesToLocations = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesToLocations step4;
+    expected = step5;
+  };
+  testSetLocationPriorities = {
+    expr = lib.clicks.nginx.http.internal.setLocationPriorities step5;
+    expected = step6;
+  };
+  testAddListenDefaults = {
+    expr = lib.clicks.nginx.http.internal.addListenDefaults step6;
+    expected = step7;
+  };
+}
diff --git a/lib/nginx/http/internal/lib.nginx.http.internal.headers.spec.nix b/lib/nginx/http/internal/lib.nginx.http.internal.headers.spec.nix
new file mode 100644
index 0000000..66ced1d
--- /dev/null
+++ b/lib/nginx/http/internal/lib.nginx.http.internal.headers.spec.nix
@@ -0,0 +1,277 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, ... }: let
+  step0 = {
+    "calibre.clicks.codes" = {
+      service = lib.pipe (lib.clicks.nginx.http.reverseProxy "generic" 1024) [
+        lib.clicks.nginx.http.unsafeAddCrossOriginHeader
+        (lib.clicks.nginx.http.addHeader "X-Clacks-Overhead" "GNU Terry Pratchett")
+      ];
+      www = true;
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+  };
+  step1 = {
+    "calibre.clicks.codes" = {
+      service = {
+        _type = "header";
+        name = "X-Clacks-Overhead";
+        value = "GNU Terry Pratchett";
+        content = {
+          _type = "header";
+          name = "Access-Control-Allow-Origin";
+          value = "*";
+          content = {
+            host = "generic";
+            port = 1024;
+            protocol = "http";
+            _type = "reverseProxy";
+            headers = null;
+          };
+        };
+      };
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      service = null;
+      enableHttp = false;
+    };
+  };
+  step2 = {
+    "calibre.clicks.codes" = {
+      routes."/" = lib.home-manager.hm.dag.entryAnywhere {
+        _type = "header";
+        name = "X-Clacks-Overhead";
+        value = "GNU Terry Pratchett";
+        content = {
+          _type = "header";
+          name = "Access-Control-Allow-Origin";
+          value = "*";
+          content = {
+            host = "generic";
+            port = 1024;
+            protocol = "http";
+            _type = "reverseProxy";
+            headers = null;
+          };
+        };
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step3 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            _type = "header";
+            name = "X-Clacks-Overhead";
+            value = "GNU Terry Pratchett";
+            content = {
+              _type = "header";
+              name = "Access-Control-Allow-Origin";
+              value = "*";
+              content = {
+                host = "generic";
+                port = 1024;
+                protocol = "http";
+                _type = "reverseProxy";
+                headers = null;
+              };
+            };
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step4 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            host = "generic";
+            port = 1024;
+            protocol = "http";
+            _type = "reverseProxy";
+            headers = {
+              "Access-Control-Allow-Origin" = "*";
+              "X-Clacks-Overhead" = "GNU Terry Pratchett";
+            };
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step5 = {
+    "calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "/";
+          value = {
+            proxyPass = "http://generic:1024";
+            proxyWebsockets = true;
+            recommendedProxySettings = true;
+            extraConfig = "\nadd_header \"X-Clacks-Overhead\" \"GNU Terry Pratchett\";\nadd_header \"Access-Control-Allow-Origin\" \"*\";";
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            return = "307 https://calibre.clicks.codes/$1";
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step6 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "http://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+        extraConfig = "\nadd_header \"X-Clacks-Overhead\" \"GNU Terry Pratchett\";\nadd_header \"Access-Control-Allow-Origin\" \"*\";";
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step7 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "http://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+        extraConfig = "\nadd_header \"X-Clacks-Overhead\" \"GNU Terry Pratchett\";\nadd_header \"Access-Control-Allow-Origin\" \"*\";";
+      };
+      onlySSL = true;
+      addSSL = false;
+      useACMEHost = "calibre.clicks.codes";
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      onlySSL = true;
+      addSSL = false;
+      useACMEHost = "www.calibre.clicks.codes";
+      authWith = null;
+    };
+  };
+in {
+  testGenerateWwwRedirects = {
+    expr = lib.clicks.nginx.http.internal.generateWwwRedirects step0;
+    expected = step1;
+  };
+  testTranslateServiceToRoutes = {
+    expr = lib.clicks.nginx.http.internal.translateServiceToRoutes step1;
+    expected = step2;
+  };
+  testTranslateRoutesDagToList = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesDagToList step2;
+    expected = step3;
+  };
+  testUnwrapHeader = {
+    expr = lib.clicks.nginx.http.internal.unwrapHeaders step3;
+    expected = step4;
+  };
+  testTranslateRoutesToLocations = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesToLocations step4;
+    expected = step5;
+  };
+  testSetLocationPriorities = {
+    expr = lib.clicks.nginx.http.internal.setLocationPriorities step5;
+    expected = step6;
+  };
+  testAddListenDefaults = {
+    expr = lib.clicks.nginx.http.internal.addListenDefaults step6;
+    expected = step7;
+  };
+}
diff --git a/lib/nginx/http/internal/lib.nginx.http.internal.http.spec.nix b/lib/nginx/http/internal/lib.nginx.http.internal.http.spec.nix
new file mode 100644
index 0000000..4f56d96
--- /dev/null
+++ b/lib/nginx/http/internal/lib.nginx.http.internal.http.spec.nix
@@ -0,0 +1,238 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, ... }: let
+  step0 = {
+    "calibre.clicks.codes" = {
+      service = lib.clicks.nginx.http.reverseProxy "generic" 1024;
+      www = true;
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = true;
+    };
+  };
+  step1 = {
+    "calibre.clicks.codes" = {
+      service = {
+        host = "generic";
+        port = 1024;
+        protocol = "http";
+        _type = "reverseProxy";
+        headers = null;
+      };
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = true;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      service = null;
+      enableHttp = true;
+    };
+  };
+  step2 = {
+    "calibre.clicks.codes" = {
+      routes."/" = lib.home-manager.hm.dag.entryAnywhere {
+        host = "generic";
+        port = 1024;
+        protocol = "http";
+        _type = "reverseProxy";
+        headers = null;
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = true;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      enableHttp = true;
+    };
+  };
+  step3 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            host = "generic";
+            port = 1024;
+            protocol = "http";
+            _type = "reverseProxy";
+            headers = null;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = true;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = true;
+    };
+  };
+  step4 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            host = "generic";
+            port = 1024;
+            protocol = "http";
+            _type = "reverseProxy";
+            headers = null;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = true;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = true;
+    };
+  };
+  step5 = {
+    "calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "/";
+          value = {
+            proxyPass = "http://generic:1024";
+            proxyWebsockets = true;
+            recommendedProxySettings = true;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = true;
+    };
+    "www.calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            return = "307 https://calibre.clicks.codes/$1";
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = true;
+    };
+  };
+  step6 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "http://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = true;
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      authWith = null;
+      enableHttp = true;
+    };
+  };
+  step7 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "http://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+      };
+      onlySSL = false;
+      addSSL = true;
+      useACMEHost = "calibre.clicks.codes";
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      onlySSL = false;
+      addSSL = true;
+      useACMEHost = "www.calibre.clicks.codes";
+      authWith = null;
+    };
+  };
+in {
+  testGenerateWwwRedirects = {
+    expr = lib.clicks.nginx.http.internal.generateWwwRedirects step0;
+    expected = step1;
+  };
+  testTranslateServiceToRoutes = {
+    expr = lib.clicks.nginx.http.internal.translateServiceToRoutes step1;
+    expected = step2;
+  };
+  testTranslateRoutesDagToList = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesDagToList step2;
+    expected = step3;
+  };
+  testUnwrapHeaderPassthrough = {
+    expr = lib.clicks.nginx.http.internal.unwrapHeaders step3;
+    expected = step4;
+  };
+  testTranslateRoutesToLocations = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesToLocations step4;
+    expected = step5;
+  };
+  testSetLocationPriorities = {
+    expr = lib.clicks.nginx.http.internal.setLocationPriorities step5;
+    expected = step6;
+  };
+  testAddListenDefaults = {
+    expr = lib.clicks.nginx.http.internal.addListenDefaults step6;
+    expected = step7;
+  };
+}
diff --git a/lib/nginx/http/internal/lib.nginx.http.internal.https-proxy.spec.nix b/lib/nginx/http/internal/lib.nginx.http.internal.https-proxy.spec.nix
new file mode 100644
index 0000000..77ad490
--- /dev/null
+++ b/lib/nginx/http/internal/lib.nginx.http.internal.https-proxy.spec.nix
@@ -0,0 +1,238 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, ... }: let
+  step0 = {
+    "calibre.clicks.codes" = {
+      service = lib.clicks.nginx.http.reverseProxySecure "generic" 1024;
+      www = true;
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+  };
+  step1 = {
+    "calibre.clicks.codes" = {
+      service = {
+        host = "generic";
+        port = 1024;
+        protocol = "https";
+        _type = "reverseProxy";
+        headers = null;
+      };
+      dnsProvider = "cloudflare";
+      routes = null;
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      service = null;
+      enableHttp = false;
+    };
+  };
+  step2 = {
+    "calibre.clicks.codes" = {
+      routes."/" = lib.home-manager.hm.dag.entryAnywhere {
+        host = "generic";
+        port = 1024;
+        protocol = "https";
+        _type = "reverseProxy";
+        headers = null;
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes."~ ^/?([^\r\n]*)$" = lib.home-manager.hm.dag.entryAnywhere {
+        to = "https://calibre.clicks.codes/$1";
+        permanent = true;
+        _type = "redirect";
+        headers = null;
+      };
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step3 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            host = "generic";
+            port = 1024;
+            protocol = "https";
+            _type = "reverseProxy";
+            headers = null;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step4 = {
+    "calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "/";
+          value = {
+            host = "generic";
+            port = 1024;
+            protocol = "https";
+            _type = "reverseProxy";
+            headers = null;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      routes = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            to = "https://calibre.clicks.codes/$1";
+            permanent = true;
+            _type = "redirect";
+            headers = null;
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step5 = {
+    "calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "/";
+          value = {
+            proxyPass = "https://generic:1024";
+            proxyWebsockets = true;
+            recommendedProxySettings = true;
+          };
+        }
+      ];
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      locations = [
+        {
+          name = "~ ^/?([^\r\n]*)$";
+          value = {
+            return = "307 https://calibre.clicks.codes/$1";
+          };
+        }
+      ];
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step6 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "https://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+      };
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+      enableHttp = false;
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      authWith = null;
+      enableHttp = false;
+    };
+  };
+  step7 = {
+    "calibre.clicks.codes" = {
+      locations."/" = {
+        priority = 100;
+        proxyPass = "https://generic:1024";
+        proxyWebsockets = true;
+        recommendedProxySettings = true;
+      };
+      onlySSL = true;
+      addSSL = false;
+      useACMEHost = "calibre.clicks.codes";
+      dnsProvider = "cloudflare";
+      authWith = "tailscale";
+    };
+    "www.calibre.clicks.codes" = {
+      locations."~ ^/?([^\r\n]*)$" = {
+        return = "307 https://calibre.clicks.codes/$1";
+        priority = 100;
+      };
+      onlySSL = true;
+      addSSL = false;
+      useACMEHost = "www.calibre.clicks.codes";
+      authWith = null;
+    };
+  };
+in {
+  testGenerateWwwRedirects = {
+    expr = lib.clicks.nginx.http.internal.generateWwwRedirects step0;
+    expected = step1;
+  };
+  testTranslateServiceToRoutes = {
+    expr = lib.clicks.nginx.http.internal.translateServiceToRoutes step1;
+    expected = step2;
+  };
+  testTranslateRoutesDagToList = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesDagToList step2;
+    expected = step3;
+  };
+  testUnwrapHeaderPassthrough = {
+    expr = lib.clicks.nginx.http.internal.unwrapHeaders step3;
+    expected = step4;
+  };
+  testTranslateRoutesToLocations = {
+    expr = lib.clicks.nginx.http.internal.translateRoutesToLocations step4;
+    expected = step5;
+  };
+  testSetLocationPriorities = {
+    expr = lib.clicks.nginx.http.internal.setLocationPriorities step5;
+    expected = step6;
+  };
+  testAddListenDefaults = {
+    expr = lib.clicks.nginx.http.internal.addListenDefaults step6;
+    expected = step7;
+  };
+}
diff --git a/lib/nginx/http/lib.nginx.http.status-code.spec.nix b/lib/nginx/http/lib.nginx.http.status-code.spec.nix
new file mode 100644
index 0000000..67b4d86
--- /dev/null
+++ b/lib/nginx/http/lib.nginx.http.status-code.spec.nix
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, ... }: {
+  testSuccess = {
+    expr = lib.clicks.nginx.http.status 200;
+    expected = {
+      code = 200;
+      _type = "status";
+      headers = null;
+    };
+  };
+  testNotFound = {
+    expr = lib.clicks.nginx.http.status 404;
+    expected = {
+      code = 404;
+      _type = "status";
+      headers = null;
+    };
+  };
+}
diff --git a/lib/nginx/stream/default.nix b/lib/nginx/stream/default.nix
new file mode 100644
index 0000000..d6a3183
--- /dev/null
+++ b/lib/nginx/stream/default.nix
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ lib, inputs, ... }:
+{
+  nginx.stream = {
+    proxyStream = external: internal: protocol: {
+      inherit external internal protocol;
+      haproxy = true;
+    };
+    stream = external: internal: protocol: {
+      inherit external internal protocol;
+      haproxy = false;
+    };
+  };
+}
diff --git a/lib/types/modules/README.md b/lib/types/modules/README.md
new file mode 100644
index 0000000..b13a8af
--- /dev/null
+++ b/lib/types/modules/README.md
@@ -0,0 +1,60 @@
+<!--
+Copyright (c) 2003-2016 Eelco Dolstra and the Nixpkgs/NixOS contributors
+SPDX-FileCopyrightText: 2024 Clicks Codes
+
+SPDX-License-Identifier: MIT
+-->
+
+# lib.clicks.types.modules.taggedSubmodule
+
+This is a cleaned-up version of the type proposed in https://github.com/NixOS/nixpkgs/pull/254790, and as-such is under the same license as nixpkgs
+
+{ *`types`*, *`specialArgs`* ? {} }
+
+This is like `types.oneOf`, but takes an attrsSet of submodule in types. Those need to have a type option
+    which is used to find the correct submodule.
+
+::: {#ex-tagged-submodule .example}
+### Tagged submodules
+```nix
+let
+  submoduleA = submodule {
+    options = {
+      _type = mkOption {
+        type = str;
+      };
+      foo = mkOption {
+        type = int;
+      };
+    };
+  };
+  submoduleB = submodule {
+    options = {
+      _type = mkOption {
+        type = str;
+      };
+      bar = mkOption {
+        type = int;
+      };
+    };
+  };
+in
+options.mod = mkOption {
+  type = attrsOf (taggedSubmodule {
+    types = {
+      a = submoduleA;
+      b = submoduleB;
+    };
+  });
+};
+config.mod = {
+  someA = {
+    _type = "a";
+    foo = 123;
+  };
+  someB = {
+    _type = "b";
+    bar = 456;
+  };
+};
+```
diff --git a/lib/types/modules/default.nix b/lib/types/modules/default.nix
new file mode 100644
index 0000000..d9d11d3
--- /dev/null
+++ b/lib/types/modules/default.nix
@@ -0,0 +1,26 @@
+# Copyright (c) 2003-2016 Eelco Dolstra and the Nixpkgs/NixOS contributors
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: MIT
+
+{
+  lib,
+  ...
+}: {
+  types.modules = {
+    taggedSubmodule =
+      { types
+      , specialArgs ? {}
+      }: lib.types.mkOptionType rec {
+      name = "taggedSubmodule";
+      description = "submodules tagged by ._type";
+      check = x: if x ? _type then types.${x._type}.check x else throw "No ._type option set in:\n${lib.generators.toPretty {} x}";
+      merge = loc: lib.lists.foldl'
+        (res: def: types.${def.value._type}.merge loc [
+          (lib.attrsets.recursiveUpdate { value._module.args = specialArgs; } def)
+        ])
+        { };
+      nestedTypes = types;
+    };
+  };
+}
diff --git a/lib/types/nginx/README.md b/lib/types/nginx/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/types/nginx/README.md
diff --git a/lib/types/nginx/default.nix b/lib/types/nginx/default.nix
new file mode 100644
index 0000000..3b48b7a
--- /dev/null
+++ b/lib/types/nginx/default.nix
@@ -0,0 +1,157 @@
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{
+  lib,
+  ...
+}: {
+  types.nginx = {
+    host = config: lib.types.submodule (
+      { ... }@submodule: {
+        options = {
+          dnsProvider = lib.mkOption {
+            type = lib.types.nullOr lib.types.str;
+            description = "DNS Provider for this host's / these hosts certificate";
+            default = config.security.acme.defaults.dnsProvider;
+          };
+          www = lib.mkOption {
+            type = lib.types.bool;
+            description = "Automatically create www.<address>";
+            default = true;
+          };
+          service = lib.mkOption {
+            type = lib.types.nullOr (lib.clicks.types.modules.taggedSubmodule {
+              types = lib.clicks.types.nginx.services;
+            });
+            description = "Service which sits at \"/\"";
+            default = null;
+          };
+          routes = lib.mkOption {
+            type = lib.types.nullOr (lib.home-manager.hm.types.dagOf (lib.clicks.types.modules.taggedSubmodule {
+              types = lib.clicks.types.nginx.services;
+            }));
+            description = "An attrset of paths to the service wanted";
+            default = null;
+          };
+          authWith = lib.mkOption {
+            type = lib.types.nullOr (lib.types.enum [ "tailscale" ]);
+            description = "Authenticate with this service on the nginx level";
+            default = null;
+          };
+          enableHttp = lib.mkEnableOption "Listen additionally on port 80";
+        };
+      }
+    );
+    services = {
+      reverseProxy = lib.types.submodule {
+        options = {
+          host = lib.mkOption {
+            type = lib.types.str;
+            description = "Local host of the service";
+            default = "127.0.0.1";
+          };
+          port = lib.mkOption {
+            type = lib.types.port;
+            description = "Port the service is running on";
+          };
+          protocol = lib.mkOption {
+            type = lib.types.str;
+            description = "Protocol to connect with, normally 'http'";
+            default = "http";
+          };
+          _type = lib.mkOption {
+            type = lib.types.str;
+            internal = true;
+            description = "The type of service";
+            default = "reverseProxy";
+          };
+          headers = lib.mkOption {
+            type = lib.types.nullOr (lib.types.attrsOf lib.types.str);
+            description = "Headers and their values";
+            default = null;
+          };
+        };
+      };
+      redirect = lib.types.submodule {
+        options = {
+          to = lib.mkOption {
+            type = lib.types.str;
+            description = "Where to redirect to";
+          };
+          permanent = lib.mkEnableOption "Make this redirect with a HTTP 308 Permanent Redirect rather than 307 Temporary Redirect";
+          _type = lib.mkOption {
+            type = lib.types.str;
+            internal = true;
+            description = "The type of service";
+            default = "redirect";
+          };
+          headers = lib.mkOption {
+            type = lib.types.nullOr (lib.types.attrsOf lib.types.str);
+            description = "Headers and their values";
+            default = null;
+          };
+        };
+      };
+      directory = lib.types.submodule {
+        options = {
+          root = lib.mkOption {
+            type = lib.types.str;
+            description = "The root path of the directory";
+          };
+          listContents = lib.mkEnableOption "Make this directory and subdirectories give a listing when there's no index file";
+          _type = lib.mkOption {
+            type = lib.types.str;
+            internal = true;
+            description = "The type of service";
+            default = "directory";
+          };
+          headers = lib.mkOption {
+            type = lib.types.nullOr (lib.types.attrsOf lib.types.str);
+            description = "Headers and their values";
+            default = null;
+          };
+        };
+      };
+      file = lib.types.submodule {
+        options = {
+          path = lib.mkOption {
+            type = lib.types.str;
+            description = "Path to the file";
+          };
+          _type = lib.mkOption {
+            type = lib.types.str;
+            internal = true;
+            description = "The type of service";
+            default = "file";
+          };
+          headers = lib.mkOption {
+            type = lib.types.nullOr (lib.types.attrsOf lib.types.str);
+            description = "Headers and their values";
+            default = null;
+          };
+        };
+      };
+      status = lib.types.submodule {
+        options = {
+          code = lib.mkOption {
+            type = lib.types.number;
+            description = "HTTP status code to return";
+            default = 200;
+          };
+          _type = lib.mkOption {
+            type = lib.types.str;
+            internal = true;
+            description = "The type of service";
+            default = "status";
+          };
+          headers = lib.mkOption {
+            type = lib.types.nullOr (lib.types.attrsOf lib.types.str);
+            description = "Headers and their values";
+            default = null;
+          };
+        };
+      };
+    };
+  };
+}