Add ERPNext

ERPNext is many things that Clicks wants. In particular, we're interested
in task tracking and ticketing. Thanks to Frappix by blaggacao, it's not
even too challenging to install on nix anymore!

We'll be using the domain frappe.clicks.codes (rather than
erpnext.clicks.codes) because we would like to be able to install more
frappe apps on the same site without it seeming weird. We will, however,
make the domain redirect to give a nicer way to remember this.

Change-Id: I418d172c5de2b6d6918e1c5f55c7f1d6e1faa2ec
Reviewed-on: https://git.clicks.codes/c/Infra/NixFiles/+/535
Reviewed-by: Samuel Shuert <coded@clicks.codes>
Tested-by: Skyler Grey <minion@clicks.codes>
diff --git a/flake.lock b/flake.lock
index abec631..d257bcd 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,5 +1,35 @@
 {
   "nodes": {
+    "blank": {
+      "locked": {
+        "lastModified": 1625557891,
+        "narHash": "sha256-O8/MWsPBGhhyPoPLHZAuoZiiHo9q6FLlEeIDEXuj6T4=",
+        "owner": "divnix",
+        "repo": "blank",
+        "rev": "5a5d2684073d9f563072ed07c871d577a6c614a8",
+        "type": "github"
+      },
+      "original": {
+        "owner": "divnix",
+        "repo": "blank",
+        "type": "github"
+      }
+    },
+    "call-flake": {
+      "locked": {
+        "lastModified": 1687380775,
+        "narHash": "sha256-bmhE1TmrJG4ba93l9WQTLuYM53kwGQAjYHRvHOeuxWU=",
+        "owner": "divnix",
+        "repo": "call-flake",
+        "rev": "74061f6c241227cd05e79b702db9a300a2e4131a",
+        "type": "github"
+      },
+      "original": {
+        "owner": "divnix",
+        "repo": "call-flake",
+        "type": "github"
+      }
+    },
     "deploy-rs": {
       "inputs": {
         "flake-compat": "flake-compat",
@@ -20,6 +50,61 @@
         "type": "github"
       }
     },
+    "devshell": {
+      "inputs": {
+        "flake-utils": "flake-utils_2",
+        "nixpkgs": [
+          "frappix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1705332421,
+        "narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=",
+        "owner": "numtide",
+        "repo": "devshell",
+        "rev": "83cb93d6d063ad290beee669f4badf9914cc16ec",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "devshell",
+        "type": "github"
+      }
+    },
+    "dmerge": {
+      "inputs": {
+        "haumea": [
+          "frappix",
+          "std",
+          "haumea"
+        ],
+        "nixlib": [
+          "frappix",
+          "std",
+          "lib"
+        ],
+        "yants": [
+          "frappix",
+          "std",
+          "yants"
+        ]
+      },
+      "locked": {
+        "lastModified": 1686862774,
+        "narHash": "sha256-ojGtRQ9pIOUrxsQEuEPerUkqIJEuod9hIflfNkY+9CE=",
+        "owner": "divnix",
+        "repo": "dmerge",
+        "rev": "9f7f7a8349d33d7bd02e0f2b484b1f076e503a96",
+        "type": "github"
+      },
+      "original": {
+        "owner": "divnix",
+        "ref": "0.2.1",
+        "repo": "dmerge",
+        "type": "github"
+      }
+    },
     "flake-compat": {
       "flake": false,
       "locked": {
@@ -54,6 +139,81 @@
         "type": "github"
       }
     },
+    "flake-utils_2": {
+      "inputs": {
+        "systems": "systems_3"
+      },
+      "locked": {
+        "lastModified": 1701680307,
+        "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_3": {
+      "inputs": {
+        "systems": "systems_4"
+      },
+      "locked": {
+        "lastModified": 1705309234,
+        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_4": {
+      "locked": {
+        "lastModified": 1653893745,
+        "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "frappix": {
+      "inputs": {
+        "devshell": "devshell",
+        "microvm": "microvm",
+        "nixago": "nixago",
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "std": "std"
+      },
+      "locked": {
+        "lastModified": 1709913632,
+        "narHash": "sha256-6L95O5+HmEyh6snWXCm5AHFLmoYUFc9/00Ep2Vt1V4g=",
+        "owner": "blaggacao",
+        "repo": "frappix",
+        "rev": "c489f6d004d55bb1f4610888269d89846774c842",
+        "type": "github"
+      },
+      "original": {
+        "owner": "blaggacao",
+        "repo": "frappix",
+        "type": "github"
+      }
+    },
     "gerrit-oauth": {
       "flake": false,
       "locked": {
@@ -66,6 +226,29 @@
         "url": "https://gerrit-ci.gerritforge.com/job/plugin-oauth-bazel-master-master/lastBuild/artifact/bazel-bin/plugins/oauth/oauth.jar"
       }
     },
+    "haumea": {
+      "inputs": {
+        "nixpkgs": [
+          "frappix",
+          "std",
+          "lib"
+        ]
+      },
+      "locked": {
+        "lastModified": 1685133229,
+        "narHash": "sha256-FePm/Gi9PBSNwiDFq3N+DWdfxFq0UKsVVTJS3cQPn94=",
+        "owner": "nix-community",
+        "repo": "haumea",
+        "rev": "34dd58385092a23018748b50f9b23de6266dffc2",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "ref": "v0.2.2",
+        "repo": "haumea",
+        "type": "github"
+      }
+    },
     "helpers": {
       "inputs": {
         "nixpkgs": "nixpkgs_2"
@@ -105,6 +288,91 @@
         "type": "github"
       }
     },
+    "incl": {
+      "inputs": {
+        "nixlib": [
+          "frappix",
+          "std",
+          "lib"
+        ]
+      },
+      "locked": {
+        "lastModified": 1669263024,
+        "narHash": "sha256-E/+23NKtxAqYG/0ydYgxlgarKnxmDbg6rCMWnOBqn9Q=",
+        "owner": "divnix",
+        "repo": "incl",
+        "rev": "ce7bebaee048e4cd7ebdb4cee7885e00c4e2abca",
+        "type": "github"
+      },
+      "original": {
+        "owner": "divnix",
+        "repo": "incl",
+        "type": "github"
+      }
+    },
+    "lib": {
+      "locked": {
+        "lastModified": 1694306727,
+        "narHash": "sha256-26fkTOJOI65NOTNKFvtcJF9mzzf/kK9swHzfYt1Dl6Q=",
+        "owner": "nix-community",
+        "repo": "nixpkgs.lib",
+        "rev": "c30b6a84c0b84ec7aecbe74466033facc9ed103f",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "nixpkgs.lib",
+        "type": "github"
+      }
+    },
+    "microvm": {
+      "inputs": {
+        "flake-utils": "flake-utils_3",
+        "nixpkgs": [
+          "frappix",
+          "nixpkgs"
+        ],
+        "spectrum": "spectrum"
+      },
+      "locked": {
+        "lastModified": 1708617817,
+        "narHash": "sha256-7zbgJ3Pzen8V3SS3yemo7hiua2gzP/gmIM8Of1t4c7M=",
+        "owner": "astro",
+        "repo": "microvm.nix",
+        "rev": "5544916016482c91874b0b25fd069a6e041c2715",
+        "type": "github"
+      },
+      "original": {
+        "owner": "astro",
+        "repo": "microvm.nix",
+        "type": "github"
+      }
+    },
+    "nixago": {
+      "inputs": {
+        "flake-utils": "flake-utils_4",
+        "nixago-exts": [
+          "frappix"
+        ],
+        "nixpkgs": [
+          "frappix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1687381756,
+        "narHash": "sha256-IUMIlYfrvj7Yli4H2vvyig8HEPpfCeMaE6+kBGPzFyk=",
+        "owner": "nix-community",
+        "repo": "nixago",
+        "rev": "dacceb10cace103b3e66552ec9719fa0d33c0dc9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "nixago",
+        "type": "github"
+      }
+    },
     "nixpkgs": {
       "locked": {
         "lastModified": 1702272962,
@@ -215,10 +483,73 @@
         "type": "github"
       }
     },
+    "nosys": {
+      "locked": {
+        "lastModified": 1668010795,
+        "narHash": "sha256-JBDVBnos8g0toU7EhIIqQ1If5m/nyBqtHhL3sicdPwI=",
+        "owner": "divnix",
+        "repo": "nosys",
+        "rev": "feade0141487801c71ff55623b421ed535dbdefa",
+        "type": "github"
+      },
+      "original": {
+        "owner": "divnix",
+        "repo": "nosys",
+        "type": "github"
+      }
+    },
+    "paisano": {
+      "inputs": {
+        "call-flake": "call-flake",
+        "nixpkgs": [
+          "frappix",
+          "std",
+          "nixpkgs"
+        ],
+        "nosys": "nosys",
+        "yants": [
+          "frappix",
+          "std",
+          "yants"
+        ]
+      },
+      "locked": {
+        "lastModified": 1708640854,
+        "narHash": "sha256-EpcAmvIS4ErqhXtVEfd2GPpU/E/s8CCRSfYzk6FZ/fY=",
+        "owner": "paisano-nix",
+        "repo": "core",
+        "rev": "adcf742bc9463c08764ca9e6955bd5e7dcf3a3fe",
+        "type": "github"
+      },
+      "original": {
+        "owner": "paisano-nix",
+        "ref": "0.2.0",
+        "repo": "core",
+        "type": "github"
+      }
+    },
+    "paisano-tui": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1708637035,
+        "narHash": "sha256-R19YURSK+MY/Rw6FZnojQS9zuDh+OoTAyngQAjjoubc=",
+        "owner": "paisano-nix",
+        "repo": "tui",
+        "rev": "231761b260587a64817e4ffae3afc15defaa15db",
+        "type": "github"
+      },
+      "original": {
+        "owner": "paisano-nix",
+        "ref": "v0.5.0",
+        "repo": "tui",
+        "type": "github"
+      }
+    },
     "root": {
       "inputs": {
         "deploy-rs": "deploy-rs",
         "flake-utils": "flake-utils",
+        "frappix": "frappix",
         "gerrit-oauth": "gerrit-oauth",
         "helpers": "helpers",
         "home-manager": "home-manager",
@@ -274,6 +605,84 @@
         "type": "github"
       }
     },
+    "spectrum": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1708358594,
+        "narHash": "sha256-e71YOotu2FYA67HoC/voJDTFsiPpZNRwmiQb4f94OxQ=",
+        "ref": "refs/heads/main",
+        "rev": "6d0e73864d28794cdbd26ab7b37259ab0e1e044c",
+        "revCount": 614,
+        "type": "git",
+        "url": "https://spectrum-os.org/git/spectrum"
+      },
+      "original": {
+        "type": "git",
+        "url": "https://spectrum-os.org/git/spectrum"
+      }
+    },
+    "std": {
+      "inputs": {
+        "arion": [
+          "frappix",
+          "std",
+          "blank"
+        ],
+        "blank": "blank",
+        "devshell": [
+          "frappix",
+          "devshell"
+        ],
+        "dmerge": "dmerge",
+        "haumea": "haumea",
+        "incl": "incl",
+        "lib": "lib",
+        "makes": [
+          "frappix",
+          "std",
+          "blank"
+        ],
+        "microvm": [
+          "frappix",
+          "microvm"
+        ],
+        "n2c": [
+          "frappix",
+          "std",
+          "blank"
+        ],
+        "nixago": [
+          "frappix",
+          "nixago"
+        ],
+        "nixpkgs": [
+          "frappix",
+          "nixpkgs"
+        ],
+        "paisano": "paisano",
+        "paisano-tui": "paisano-tui",
+        "terranix": [
+          "frappix",
+          "std",
+          "blank"
+        ],
+        "yants": "yants"
+      },
+      "locked": {
+        "lastModified": 1708642374,
+        "narHash": "sha256-A6CuQML2jqg3T7KcP9wx0o9WiCtc28Owq1AiD7sNl20=",
+        "owner": "divnix",
+        "repo": "std",
+        "rev": "9f1f2d04fc938f73c0c4a5d00d3043ae5afdf81d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "divnix",
+        "ref": "v0.33.0",
+        "repo": "std",
+        "type": "github"
+      }
+    },
     "systems": {
       "locked": {
         "lastModified": 1681028828,
@@ -304,6 +713,36 @@
         "type": "github"
       }
     },
+    "systems_3": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
+    "systems_4": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
     "utils": {
       "inputs": {
         "systems": "systems"
@@ -321,6 +760,28 @@
         "repo": "flake-utils",
         "type": "github"
       }
+    },
+    "yants": {
+      "inputs": {
+        "nixpkgs": [
+          "frappix",
+          "std",
+          "lib"
+        ]
+      },
+      "locked": {
+        "lastModified": 1686863218,
+        "narHash": "sha256-kooxYm3/3ornWtVBNHM3Zh020gACUyFX2G0VQXnB+mk=",
+        "owner": "divnix",
+        "repo": "yants",
+        "rev": "8f0da0dba57149676aa4817ec0c880fbde7a648d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "divnix",
+        "repo": "yants",
+        "type": "github"
+      }
     }
   },
   "root": "root",
diff --git a/flake.nix b/flake.nix
index 722899c..0ca0186 100644
--- a/flake.nix
+++ b/flake.nix
@@ -15,6 +15,8 @@
 
   inputs.helpers.url = "git+https://git.clicks.codes/Clicks/NixHelpers";
 
+  inputs.frappix.url = "github:blaggacao/frappix";
+
   # follow settings
   inputs.home-manager.inputs.nixpkgs.follows = "nixpkgs";
 
@@ -28,18 +30,27 @@
     flake = false;
   };
 
+  inputs.frappix.inputs.nixpkgs.follows = "nixpkgs";
+
   outputs = { self, nixpkgs, deploy-rs, home-manager, sops-nix, scalpel
-    , nixpkgs-privatebin, ... }@inputs:
+    , nixpkgs-privatebin, frappix, ... }@inputs:
     let
       system = "x86_64-linux";
       pkgs = import nixpkgs {
         inherit system;
         config.allowUnfree = true;
+
+        # frappix -> wkhtmltopdf
+        config.permittedInsecurePackages = ["openssl-1.1.1w"];
+
         overlays = [
           (final: prev: {
             inherit (nixpkgs-privatebin.legacyPackages.${system})
               privatebin pbcli;
           })
+          frappix.toolsOverlay.${system}
+          frappix.pythonOverlay.${system}
+          frappix.frappeOverlay.${system}
         ];
       };
       helpers = inputs.helpers.helpers { inherit pkgs nixpkgs; };
@@ -66,6 +77,7 @@
           ++ [
             sops-nix.nixosModules.sops
             "${nixpkgs-privatebin}/nixos/modules/services/web-apps/privatebin.nix"
+            frappix.nixosModules.${system}.frappix
           ];
           specialArgs = inputs // {
             base = null;
diff --git a/modules/common/frappe.nix b/modules/common/frappe.nix
new file mode 100644
index 0000000..8479aff
--- /dev/null
+++ b/modules/common/frappe.nix
@@ -0,0 +1,40 @@
+{ pkgs, config, ... }: {
+  services.frappe = {
+    enable = true;
+    project = "frappe";
+
+    package = pkgs.frappix.frappe;
+
+    apps = [
+      pkgs.frappix.erpnext
+    ];
+
+    sites."frappe.clicks.codes" = {
+      domains = [
+        "frappe.clicks.codes"
+      ];
+
+      apps = [
+        "frappe"
+        "erpnext"
+      ];
+    };
+
+    adminPassword = config.sops.secrets."modules/common/frappe.nix:adminPassword".path;
+
+    gunicorn_workers = 9;
+  };
+
+  services.nginx.virtualHosts."frappe.clicks.codes" = {
+    enableACME = true;
+  };
+
+  sops.secrets."modules/common/frappe.nix:adminPassword" = {
+    mode = "0400";
+    owner = config.users.users.root.name;
+    group = config.users.users.root.group;
+    sopsFile = ../../secrets/frappe.json;
+    format = "json";
+    key = "adminPassword";
+  };
+}
diff --git a/modules/common/nginx-routes.nix b/modules/common/nginx-routes.nix
index 61f8c20..d01e84c 100644
--- a/modules/common/nginx-routes.nix
+++ b/modules/common/nginx-routes.nix
@@ -12,7 +12,14 @@
     ]))
     (Hosts [ "resume.coded.codes" "resume.thecoded.prof" ] (ReverseProxy "coded:1024"))
     (Hosts [ "vaultwarden.clicks.codes" "passwords.clicks.codes" ] (ReverseProxy "generic:1028"))
-    (Hosts [ "taiga.clicks.codes" "projects.clicks.codes" "tasks.clicks.codes" "issues.clicks.codes" "kanban.clicks.codes" ] (ReverseProxy "generic:1029"))
+    (Hosts [
+      "erpnext.clicks.codes"
+      "projects.clicks.codes"
+      "tasks.clicks.codes"
+      "issues.clicks.codes"
+      "kanban.clicks.codes"
+      "taiga.clicks.codes"
+    ] (Redirect "https://frappe.clicks.codes/app"))
     (Host "login.clicks.codes" (ReverseProxy "127.0.0.1:9083"))
     (Hosts [ "gerrit.clicks.codes" "git.clicks.codes" ]
       (ReverseProxy "generic:1024"))
diff --git a/modules/common/taiga.nix b/modules/common/taiga.nix
deleted file mode 100644
index 85fc4e3..0000000
--- a/modules/common/taiga.nix
+++ /dev/null
@@ -1,174 +0,0 @@
-{ config, pkgs, ... }: let
-  openid_environment = {
-    ENABLE_OPENID = "True";
-    OPENID_USER_URL = "https://login.clicks.codes/realms/master/protocol/openid-connect/userinfo";
-    OPENID_TOKEN_URL = "https://login.clicks.codes/realms/master/protocol/openid-connect/token";
-    OPENID_CLIENT_ID = "taiga";
-    OPENID_NAME = "Clicks Keycloak";
-
-    # PUBLIC_REGISTER_ENABLED = "True";
-
-    OPENID_ID_FIELD = "sub";
-    OPENID_USERNAME_FIELD = "preferred_username";
-    OPENID_FULLNAME_FIELD = "name";
-    OPENID_EMAIL_FIELD = "email";
-    OPENID_SCOPE="openid email";
-
-    OPENID_FILTER = "enabled";
-    OPENID_FILTER_FIELD = "taiga_access";
-  };
-  backend_environment = openid_environment // {
-    POSTGRES_DB = "taiga";
-    POSTGRES_USER = "taiga";
-    POSTGRES_HOST = "172.20.0.1";
-
-    TAIGA_SITES_SCHEME = "https";
-    TAIGA_SITES_DOMAIN = "taiga.clicks.codes";
-    TAIGA_SUBPATH = "";
-
-    EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend";
-    DEFAULT_FROM_EMAIL = "taiga@clicks.codes";
-    EMAIL_USE_TLS = "True";
-    EMAIL_USE_SSL = "False"; # not needed when using TLS
-    EMAIL_HOST = "mail.clicks.codes";
-    EMAIL_PORT = "587";
-    EMAIL_HOST_USER = "taiga@clicks.codes";
-
-    RABBITMQ_USER = "taiga";
-
-    ENABLE_TELEMETRY = "False";
-  };
-  credential_environment_files = [
-    config.sops.secrets.taiga_credentials_env.path
-    # TODO: OPENID_CLIENT_SECRET
-  ];
-
-  host_static_folder = "/var/taiga/back/static";
-  host_media_folder = "/var/taiga/back/media";
-
-  backend_volumes = [
-    "${host_static_folder}:/taiga-back/static"
-    "${host_media_folder}:/taiga-back/media"
-  ];
-
-  taiga_version = "latest";
-  taiga_base_version = "latest";  # events, etc. only have X.X.0 versions
-in {
-  sops.secrets.taiga_credentials_env = {
-    mode = "0660";
-    owner = config.users.users.root.name;
-    group = config.users.users.root.group;
-    sopsFile = ../../secrets/taiga.env.bin;
-    format = "binary";
-  };
-
-  networking.firewall.interfaces.taiga.allowedTCPPorts = [ 5432 ];
-
-  systemd.services = {
-    "docker-network-taiga" = {
-      serviceConfig.Type = "oneshot";
-      wantedBy = [
-        "docker-taiga-back.service"
-        "docker-taiga-async.service"
-        "docker-taiga-async-rabbitmq.service"
-        "docker-taiga-front.service"
-        "docker-taiga-events.service"
-        "docker-taiga-events-rabbitmq.service"
-        "docker-taiga-protected.service"
-        "docker-taiga-gateway.service"
-      ];
-      script = ''
-        ${pkgs.docker}/bin/docker network inspect taiga > /dev/null 2>&1 || ${pkgs.docker}/bin/docker network create taiga --gateway 172.20.0.1 --subnet 172.20.0.0/16 --opt com.docker.network.bridge.name=taiga
-      '';
-    };
-    docker-taiga-back.requires = [
-      "docker-taiga-events-rabbitmq.service"
-      "docker-taiga-async-rabbitmq.service"
-      "postgresql.service"
-    ];
-    docker-taiga-async.requires = [
-      "docker-taiga-events-rabbitmq.service"
-      "docker-taiga-async-rabbitmq.service"
-      "postgresql.service"
-    ];
-    docker-taiga-gateway.requires = [
-      "docker-taiga-front.service"
-      "docker-taiga-back.service"
-      "docker-taiga-events.service"
-    ];
-    docker-taiga-events.requires = [
-      "docker-taiga-events-rabbitmq.service"
-    ];
-  };
-  virtualisation.oci-containers.containers = {
-    taiga-back = {
-      image = "taigaio/taiga-back:${taiga_version}";
-      environment = backend_environment;
-      environmentFiles = credential_environment_files;
-      volumes = backend_volumes;
-      extraOptions = [ "--network=taiga" ];
-    };
-    taiga-async = {
-      image = "taigaio/taiga-back:${taiga_version}";
-      environment = backend_environment;
-      environmentFiles = credential_environment_files;
-      volumes = backend_volumes;
-      extraOptions = [ "--network=taiga" ];
-    };
-    taiga-async-rabbitmq = {
-      image = "rabbitmq:3.8-management-alpine";
-      environment = {
-        RABBITMQ_DEFAULT_USER = "taiga";
-        RABBITMQ_DEFAULT_VHOST = "taiga";
-      };
-      environmentFiles = credential_environment_files;
-      volumes = [ "/var/taiga/rabbitmq/async:/var/lib/rabbitmq" ];
-      extraOptions = [ "--network=taiga" ];
-    };
-    taiga-front = {
-      image = "taigaio/taiga-front:${taiga_version}";
-      environment = openid_environment // {
-        TAIGA_URL = "https://taiga.clicks.codes";
-        TAIGA_WEBSOCKETS_URL = "wss://taiga.clicks.codes";
-        TAIGA_SUBPATH = "";
-      };
-      extraOptions = [ "--network=taiga" ];
-    };
-    taiga-events = {
-      image = "taigaio/taiga-events:${taiga_base_version}";
-      environment = {
-        RABBITMQ_USER = "taiga";
-      };
-      environmentFiles = credential_environment_files;
-      extraOptions = [ "--network=taiga" ];
-    };
-    taiga-events-rabbitmq = {
-      image = "rabbitmq:3.8-management-alpine";
-      environment = {
-        RABBITMQ_DEFAULT_USER = "taiga";
-        RABBITMQ_DEFAULT_VHOST = "taiga";
-      };
-      environmentFiles = credential_environment_files;
-      volumes = [ "/var/taiga/rabbitmq/events:/var/lib/rabbitmq" ];
-      extraOptions = [ "--network=taiga" ];
-    };
-    taiga-protected = {
-      image = "taigaio/taiga-protected:${taiga_base_version}";
-      environment = {
-        MAX_AGE = "600";
-      };
-      environmentFiles = credential_environment_files;
-      extraOptions = [ "--network=taiga" ];
-    };
-    taiga-gateway = {
-      image = "nginx:1.19-alpine";
-      ports = [ "127.0.0.255:1029:80/tcp" ];
-      volumes = [
-        "${./taiga/taiga-gateway.conf}:/etc/nginx/conf.d/default.conf"
-        "${host_static_folder}:/taiga/static"
-        "${host_media_folder}:/taiga/media"
-      ];
-      extraOptions = [ "--network=taiga" ];
-    };
-  };
-}
diff --git a/modules/common/taiga/taiga-gateway.conf b/modules/common/taiga/taiga-gateway.conf
deleted file mode 100644
index ebaeb68..0000000
--- a/modules/common/taiga/taiga-gateway.conf
+++ /dev/null
@@ -1,75 +0,0 @@
-server {
-    listen 80 default_server;
-
-    client_max_body_size 100M;
-    charset utf-8;
-
-    # Frontend
-    location / {
-        proxy_pass http://taiga-front/;
-        proxy_pass_header Server;
-        proxy_set_header Host $http_host;
-        proxy_redirect off;
-        proxy_set_header X-Real-IP $remote_addr;
-        proxy_set_header X-Scheme $scheme;
-    }
-
-    # API
-    location /api/ {
-        proxy_pass http://taiga-back:8000/api/;
-        proxy_pass_header Server;
-        proxy_set_header Host $http_host;
-        proxy_redirect off;
-        proxy_set_header X-Real-IP $remote_addr;
-        proxy_set_header X-Scheme $scheme;
-    }
-
-    # Admin
-    location /admin/ {
-        proxy_pass http://taiga-back:8000/admin/;
-        proxy_pass_header Server;
-        proxy_set_header Host $http_host;
-        proxy_redirect off;
-        proxy_set_header X-Real-IP $remote_addr;
-        proxy_set_header X-Scheme $scheme;
-    }
-
-    # Static
-    location /static/ {
-        alias /taiga/static/;
-    }
-
-    # Media
-    location /_protected/ {
-        internal;
-        alias /taiga/media/;
-        add_header Content-disposition "attachment";
-    }
-
-    # Unprotected section
-    location /media/exports/ {
-        alias /taiga/media/exports/;
-        add_header Content-disposition "attachment";
-    }
-
-    location /media/ {
-        proxy_set_header Host $http_host;
-        proxy_set_header X-Real-IP $remote_addr;
-        proxy_set_header X-Scheme $scheme;
-        proxy_set_header X-Forwarded-Proto $scheme;
-        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-        proxy_pass http://taiga-protected:8003/;
-        proxy_redirect off;
-    }
-
-    # Events
-    location /events {
-        proxy_pass http://taiga-events:8888/events;
-        proxy_http_version 1.1;
-        proxy_set_header Upgrade $http_upgrade;
-        proxy_set_header Connection "upgrade";
-        proxy_connect_timeout 7d;
-        proxy_send_timeout 7d;
-        proxy_read_timeout 7d;
-    }
-}
diff --git a/secrets/frappe.json b/secrets/frappe.json
new file mode 100644
index 0000000..7013b26
--- /dev/null
+++ b/secrets/frappe.json
@@ -0,0 +1,32 @@
+{
+	"adminPassword": "ENC[AES256_GCM,data:H2oWsH0OMfgJoQRVdyOaFqvDgSIMT0kCAd/mFng3CSygRDfouafdj0tIQyECtbCKbDUiRD8U2mf3fTqt4iRWlKyOIg6bhgUwsXa2WAelJqbArdqj11Bbxa+YhjCXC7NGpbu9e4P5Ne2Y30D0y4z3rTvJVW2by52UiyasM5LOAFk=,iv:EAFjmtHA6mQ0nDrHN9AA+sY25gd/CkTAHDUNPeW5LGc=,tag:sYPfQOEbKGDuJqPUtn75tg==,type:str]",
+	"sops": {
+		"kms": null,
+		"gcp_kms": null,
+		"azure_kv": null,
+		"hc_vault": null,
+		"age": [
+			{
+				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBteXMrREtaN1g0WURia1Ex\nemREWVd6aHdmREljMFkxK3Zjd2FBbW5BeFRrCmMreGVEaGlXSEVzeGhyaUJRcGdt\nVVB1TmQvSTZNeGNOV01UNjk0Q1c3UjgKLS0tIGVSWkhCZHJtSkJmQjAvRjE3UVRD\nUi9BblRjRHBRb3Nab1V1MHlBNWJXcVkKgV58dk2uKPggUlAgjvWQaF0OxmJCBSaA\niNjcRfjXrgi4n2eF/E4/a9qFgYboq7Qtxf9LbrktasXde8mSNgJkdA==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvTHFUT3NUT2QybHpEbWcz\nWlBNZDRVZVhQbkVucjRsWDlDSFJBUWVDNkZ3CmFUekhPVkZCcDZzaWV1bXpaZnZV\nOGlFN0k3K2FwbzhtYzM1R2gxTkFwdzgKLS0tIEc5NGluSjErcVZ5NXJVR3VwN1Ar\nS0lkR283a2MzRU8wK0ZKbTBIQ085RkkKJJqiChLAzCpWmr/SYB0uyYOluweBlwOo\nD/LgvCsqC3cm6AC8Zeu8amW3V0IbLHqaK+hEl7J+0mJEozeReX/ilw==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMVDBWOTBTQjVzMUFCbDU5\neGNyVTNMRVE0WTU2VmE3cWdYNmwrYWh3WEM0CmQ1cWVmOWNTVG1wTmluekV6Z2NX\na2ZuUEpBakVaeThNMkRjWTgvVzdSVlEKLS0tIEJDV3NYSUZXRTJncTQ5dlpzVHoz\nZlZrY1ZtdGhmMzcvUmFSNk90b0t1QkUKTwPhLz4e/hqoWpktlMYJLCeaoUPoWhZk\n5YrQ+Ae+7Zcx6uvmcOb8OHvFUmuC8btV9eiv9HX9wnERhsG/hKo11A==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1zunqahfz404x7v8x0gs4hv5kq2xlyvqmukhlwvpymj74805jcunq4r7ugv",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuZzl2TFhrKzJ3eVdxdG8z\nczhLSnBWQ2JVMDFvK1pPMkg2SDNDVTNwVlNJCnBHQU8zNHRycHdobFd5VGUvRGN3\nUDVUQWJTNTVkR3RBelhFODh0UllReUUKLS0tIEV3Uys5aVlndFU0UlZ4Nmx0KzdQ\nWXhxQU1RZm1yTzhSYjFaWUQ5M2JKNXMKjWssVvEOjrekOM8chB+rvD3CZYhOH+c1\nQlSz44W63LuCtYrR1ZWQDMWSwW6OOqXrUyeGOjvzwA5Tk+c2xkDd4Q==\n-----END AGE ENCRYPTED FILE-----\n"
+			}
+		],
+		"lastmodified": "2024-03-09T23:28:53Z",
+		"mac": "ENC[AES256_GCM,data:X4b8xFxvV/nSoCxcWKm26YPzU5rAYGqapB/xZ+sTCoojjYpfEOn6TBKwCWszb91sIQPmf0nHv+Z/9DX9MtKFXRLCQWEc8tM9B37mX6cbvWon2gS2FpUFy5B3Wm+FsLLEo56FLqZKj7I+fuDqgbbIEGdPhMviPr9fyGlZofsJADo=,iv:MI4yP2CMPgdoXQgRLKcTwV1Z4PslUkPOE839Ky6GeJY=,tag:BFnZ1RrIa3r/NAIXb5wRnw==,type:str]",
+		"pgp": null,
+		"unencrypted_suffix": "_unencrypted",
+		"version": "3.8.1"
+	}
+}
\ No newline at end of file