Add an nginx helper

- This will allow us to easily make nginx servers without having to worry about
  the normal nginx boilerplate/syntax

Change-Id: I87225864ea8983ef9742bec9c624471794be54d2
diff --git a/nginx.nix b/nginx.nix
new file mode 100644
index 0000000..1fce41b
--- /dev/null
+++ b/nginx.nix
@@ -0,0 +1,179 @@
+{ pkgs, ... }: let
+    lib = pkgs.lib;
+in {
+    Host = host: service: {
+        inherit host service;
+        extraHosts = [];
+        secure = true;
+        type = "hosts";
+    };
+    Hosts = hosts: service: {
+        inherit service;
+        host = elemAt hosts 0;
+        extraHosts = tail hosts;
+        secure = true;
+        type = "hosts";
+    };
+    InsecureHost = host: service: {
+        inherit host service;
+        extraHosts = [];
+        secure = false;
+        type = "hosts";
+    };
+    InsecureHosts = hosts: service: {
+        inherit service;
+        host = elemAt hosts 0;
+        extraHosts = tail hosts;
+        secure = false;
+        type = "hosts";
+    };
+    ReverseProxy = to: {
+        inherit to;
+        type = "reverseproxy";
+    };
+    PHP = root: socket: {
+        inherit root socket;
+        type = "php";
+    };
+    Redirect = to: permanent: {
+        inherit to permanent;
+        type = "redirect";
+    };
+    Directory = root: {
+        inherit root;
+        private = false;
+        type = "directory";
+    };
+    PrivateDirectory = root: {
+        inherit root;
+        private = true;
+        type = "directory";
+    };
+    File = path: {
+        inherit path;
+        type = "file";
+    };
+    Compose = services: {
+        inherit services;
+        type = "compose";
+    };
+    Path = path: service: {
+        inherit path service;
+        type = "path";
+    };
+
+    Merge = let
+    # builtins.length and count up
+        _iterateCompose = services: currentConfig: currentPath: secure: priority: i:
+            if i > length services
+                then currentConfig
+                else _iterateCompose services (_merge (elemAt services i) currentConfig currentPath secure priority+i) currentPath secure priority (i+1);
+
+        _iterateMerge = i: current: services:
+            if i > length services
+                then current
+                else _iterateMerge (i+1) (current++[_merge (elemAt services i) {} "/" true 1000]) services;
+
+        _merge = service: currentConfig: currentPath: secure: priority:
+            if service.type == "hosts"
+                then _merge service.service (lib.mkMerge currentConfig {
+                    name = service.host;
+                    value = {
+                        serverAliases = service.extraHosts;
+
+                        enableACME = service.secure;
+                        forceSSL = service.secure;
+                        listen = [
+                            {
+                                addr = "0.0.0.0";
+                                port = if service.secure then 443 else 80;
+                                ssl = service.secure;
+                            }
+                        ];
+                    };
+                }) currentPath service.secure priority
+            else if service.type == "reverseproxy"
+                then (lib.mkMerge currentConfig {
+                    value.locations.${currentPath} = {
+                        proxyPass = service.to;
+                        proxyWebsockets = true;
+                        recommendedProxySettings = true;
+                    };
+                })
+            else if service.type == "php"
+                then (lib.mkMerge currentConfig {
+                    value.locations.${currentPath} = {
+                        root = service.root;
+                        index = "index.php index.html index.htm";
+                        tryFiles = "$uri $uri/ ${currentPath}index.php?$query_string =404";
+                    };
+                    value.locations."~ ^${currentPath}.*\.php$" = {
+                        tryFiles = "$uri $uri/ ${currentPath}index.php?$query_string =404";
+                        extraConfig = ''
+                            include ${pkgs.nginx}/conf/fastcgi_params;
+                            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+                            fastcgi_param REDIRECT_STATUS 200;
+                            fastcgi_pass unix:${service.socket};
+                            fastcgi_intercept_errors on;
+                            ${lib.optionalString secure "fastcgi_param HTTPS on;"}
+                        '';
+                    };
+                })
+            else if service.type == "redirect"
+                then (lib.mkMerge currentConfig {
+                    value.locations.${currentPath}.return =
+                        if service.permanent
+                            then "308 ${service.to}"
+                            else "307 ${service.to}";
+                })
+            else if service.type == "directory"
+                then (lib.mkMerge currentConfig {
+                    value.locations.${currentPath} = {
+                        root = service.root;
+                        index = "index.html index.htm";
+                        tryFiles = "$uri $uri/ =404";
+                        extraConfig = lib.optionalString !service.private "autoindex on;";
+                    };
+                })
+            else if service.type == "file"
+                then (lib.mkMerge currentConfig {
+                    value.locations.${currentPath} = {
+                        tryFiles = "${service.path} =404";
+                    };
+                })
+            else if service.type == "path"
+                then _merge service.service currentConfig service.path service.secure
+            else if service.type == "compose"
+                then (_iterateCompose service.services currentConfig currentPath secure priority 0)
+            else throw "Unknown service type: ${service.type}";
+    in (services: lib.pipe services [
+        (_iterateMerge 0 [])
+        builtins.listToAttrs
+    ]);
+
+    # https://www.nginx.com/resources/wiki/start/topics/examples/full/
+
+    /**
+    Internal needs to be a string that is both a host and a port, e.g. generic:1000
+    External should only be a port
+    Protocol should be TCP or UDP
+    */
+    Stream = external: internal: protocol: ''
+        server {
+            listen ${builtins.toString external} ${protocol};
+            proxy_pass ${internal};
+        }
+    '';
+
+    Alias = host: alias: {
+        inherit host;
+        aliases = [ alias ];
+        type = "aliases";
+    };
+
+    Aliases = host: aliases: {
+        inherit host aliases;
+        type = "aliases";
+    };
+}
+