Move all flakes into modules/ and sep't, create deploy script for a1d2

Change-Id: Ie4d50fb8f16da193195beb139922a366b72b0b0a
diff --git a/modules/common/nginx.nix b/modules/common/nginx.nix
new file mode 100644
index 0000000..6bd95e3
--- /dev/null
+++ b/modules/common/nginx.nix
@@ -0,0 +1,199 @@
+{ config, lib, pkgs, helpers, base, ... }:
+lib.recursiveUpdate {
+  options.clicks = {
+    nginx = {
+      services = lib.mkOption {
+        type = with lib.types;
+          listOf (submodule {
+            options = {
+              host = lib.mkOption { type = str; };
+              extraHosts = lib.mkOption { type = listOf str; };
+              secure = lib.mkOption { type = bool; };
+              service = lib.mkOption {
+                type = let
+                  validServiceTypes = {
+                    "redirect" = {
+                      to = [ "string" str ];
+                      permanent = [ "bool" bool ];
+                    };
+                    "reverseproxy" = { to = [ "string" str ]; };
+                    "php" = {
+                      root = [ "string" str ];
+                      socket = [ "string" str ];
+                    };
+                    "directory" = {
+                      private = [ "bool" bool ];
+                      root = [ "string" str ];
+                    };
+                    "file" = { path = [ "string" str ]; };
+                    "path" = {
+                      path = [ "string" str ];
+                      service = [ "set" serviceType ];
+                    };
+                    "compose" = { services = [ "list" (listOf serviceType) ]; };
+                    "status" = { statusCode = [ "int" int ]; };
+                  };
+
+                  serviceType = mkOptionType {
+                    name = "Service";
+
+                    description = "clicks Nginx service";
+                    descriptionClass = "noun";
+
+                    check = (x:
+                      if (builtins.typeOf x) != "set" then
+                        lib.warn
+                        "clicks nginx services must be sets but ${x} is not a set"
+                        false
+                      else if !(builtins.hasAttr "type" x) then
+                        lib.warn
+                        "clicks nginx services must have a type attribute but ${x} does not"
+                        false
+                      else if !(builtins.hasAttr x.type validServiceTypes) then
+                        lib.warn
+                        "clicks nginx services must have a valid type, but ${x.type} is not one"
+                        false
+                      else
+                        (let
+                          optionTypes =
+                            (builtins.mapAttrs (n: o: builtins.elemAt o 0)
+                              validServiceTypes.${x.type}) // {
+                                type = "string";
+                              };
+                        in (lib.pipe x [
+                          (builtins.mapAttrs (n: o:
+                            (builtins.hasAttr n optionTypes) && optionTypes.${n}
+                            == (builtins.typeOf o)))
+                          lib.attrValues
+                          (builtins.all (x: x))
+                        ]) && (lib.pipe optionTypes [
+                          (builtins.mapAttrs (n: _: builtins.hasAttr n x))
+                          lib.attrValues
+                          (builtins.all (x: x))
+                        ])));
+                  };
+                in serviceType;
+              };
+              type = lib.mkOption { type = strMatching "hosts"; };
+            };
+          });
+        example = lib.literalExpression ''
+          with helpers.nginx; [
+            (Host "example.clicks.codes" (ReverseProxy "generic:1001"))
+          ]'';
+        description = lib.mdDoc ''
+          Connects hostnames to services for your nginx server. We recommend using the Clicks helper to generate these
+        '';
+        default = [ ];
+      };
+      serviceAliases = lib.mkOption {
+        type = with lib.types;
+          listOf (submodule {
+            options = {
+              host = lib.mkOption {
+                type = str;
+                example = "example.clicks.codes";
+                description = ''
+                  The ServerName of the server. If you override this in the nginx server block, you still need to put in the name of the attribute
+                '';
+              };
+              aliases = lib.mkOption {
+                type = listOf str;
+                example = [ "example2.clicks.codes" "example.coded.codes" ];
+                description = ''
+                  A list of servers to add as aliases
+                '';
+              };
+              type = lib.mkOption { type = strMatching "aliases"; };
+            };
+          });
+        example = lib.literalExpression ''
+          with helpers.nginx; [
+            (Host "example.clicks.codes" (ReverseProxy "generic:1001"))
+          ]'';
+        description = lib.mdDoc ''
+          Adds additional host names to your nginx server. If you're using `clicks.nginx.services`
+          you should generally use a Hosts block instead
+        '';
+        default = [ ];
+      };
+      streams = lib.mkOption {
+        type = with lib.types;
+          listOf (submodule {
+            options = {
+              internal = lib.mkOption { type = str; };
+              external = lib.mkOption { type = port; };
+              protocol = lib.mkOption { type = strMatching "^(tcp|udp)$"; };
+              haproxy = lib.mkOption { type = bool; };
+            };
+          });
+        example = lib.literalExpression ''
+          with helpers.nginx; [
+            (Stream 1001 "generic:1002" "tcp")
+          ]'';
+        description = lib.mdDoc ''
+          A list of servers to be placed in the nginx streams block. We recommend using the Clicks helper to generate these
+        '';
+        default = [ ];
+      };
+    };
+  };
+  config = {
+    services.nginx = {
+      enable = true;
+      enableReload = true;
+
+      virtualHosts = lib.recursiveUpdate (helpers.nginx.Merge
+        config.clicks.nginx.services) # clicks.nginx.services
+        (lib.pipe config.clicks.nginx.serviceAliases [
+          (map (alias: {
+            name = alias.host;
+            value.serverAliases = alias.aliases;
+          }))
+          builtins.listToAttrs
+        ]); # clicks.nginx.serviceAliases
+
+      streamConfig = builtins.concatStringsSep "\n" (map (stream: ''
+        server {
+            listen ${builtins.toString stream.external}${
+              lib.optionalString (stream.protocol == "udp") " udp"
+            };
+            proxy_pass ${stream.internal};
+            ${if stream.haproxy then "proxy_protocol on;" else ""}
+        }
+      '') config.clicks.nginx.streams);
+    };
+
+    networking.firewall.allowedTCPPorts = lib.pipe config.clicks.nginx.streams [
+      (builtins.filter (stream: stream.protocol == "tcp"))
+      (map (stream: stream.external))
+    ];
+    networking.firewall.allowedUDPPorts = lib.pipe config.clicks.nginx.streams [
+      (builtins.filter (stream: stream.protocol == "udp"))
+      (map (stream: stream.external))
+    ];
+
+    security.acme.defaults = {
+      email = "admin@clicks.codes";
+      credentialsFile = config.sops.secrets.cloudflare_cert__api_token.path;
+    };
+    security.acme.acceptTerms = true;
+
+    sops.secrets.cloudflare_cert__api_token = {
+      mode = "0660";
+      owner = config.users.users.nginx.name;
+      group = config.users.users.acme.group;
+      sopsFile = ../../secrets/cloudflare-cert.env.bin;
+      format = "binary";
+    };
+
+    users.users.nginx.extraGroups = [ config.users.users.acme.group ];
+  };
+} (if base != null then {
+  config.security.acme.certs = lib.mkForce (builtins.mapAttrs (_: v:
+    (lib.filterAttrs (n: _: n != "directory") v) // {
+      webroot = null;
+      dnsProvider = "cloudflare";
+    }) base.config.security.acme.certs);
+} else
+  { })