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/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;
+  };
+}