Merge pull request #2 from ClicksMinutePer/bitwarden-vaultwarden

Bitwarden vaultwarden merge to main
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dbc3671
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+secrets/.decrypted~*
\ No newline at end of file
diff --git a/.sops.yaml b/.sops.yaml
index 0c67bd5..e9b72bc 100644
--- a/.sops.yaml
+++ b/.sops.yaml
@@ -1,9 +1,11 @@
 keys:
   - &clicks_minion age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy
+  - &clicks_coded age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd
   - &server_dc1 age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr
 creation_rules:
   - path_regex: secrets/.*
     key_groups:
     - age:
       - *clicks_minion
+      - *clicks_coded
       - *server_dc1
diff --git a/flake.nix b/flake.nix
index 8dab2a3..3b81fe7 100644
--- a/flake.nix
+++ b/flake.nix
@@ -60,7 +60,7 @@
                 users.mutableUsers = false;
               }
             ];
-            specialArgs = { base = null; };
+            specialArgs = { base = null; drive_paths = import ./variables/drive_paths.nix; };
           };
         in
         base.extendModules {
diff --git a/modules/caddy.nix b/modules/caddy.nix
index a76672e..eddcf80 100644
--- a/modules/caddy.nix
+++ b/modules/caddy.nix
@@ -1,8 +1,9 @@
-{ base, config, pkgs, lib, ... }: lib.recursiveUpdate {
+{ base, config, pkgs, lib, ... }: lib.recursiveUpdate
+{
   services.caddy.enable = true;
   services.caddy.configFile = lib.pipe ./caddy/caddyfile.nix [
     import
-    (f: f { inherit pkgs lib; })
+    (f: f { inherit pkgs lib config; })
     builtins.toJSON
     (pkgs.writeText "caddy.json")
   ];
@@ -17,28 +18,29 @@
     sopsFile = ../secrets/caddy.json;
     format = "json";
   };
-} (
-  let
-    isDerived = base != null;
-  in
-  if isDerived
-  then
+}
+  (
     let
-      caddy_json = base.config.services.caddy.configFile;
+      isDerived = base != null;
     in
-    {
-      scalpel.trafos."caddy.json" = {
-        source = toString caddy_json;
-        matchers."cloudflare_token".secret =
-          config.sops.secrets.cloudflare_token.path;
-        owner = config.users.users.root.name;
-        group = config.users.users.nobody.group;
-        mode = "0400";
-      };
+    if isDerived
+    then
+      let
+        caddy_json = base.config.services.caddy.configFile;
+      in
+      {
+        scalpel.trafos."caddy.json" = {
+          source = toString caddy_json;
+          matchers."cloudflare_token".secret =
+            config.sops.secrets.cloudflare_token.path;
+          owner = config.users.users.root.name;
+          group = config.users.users.nobody.group;
+          mode = "0400";
+        };
 
-      services.caddy.configFile = lib.mkForce config.scalpel.trafos."caddy.json".destination;
+        services.caddy.configFile = lib.mkForce config.scalpel.trafos."caddy.json".destination;
 
-      systemd.services.caddy.reloadTriggers = [ caddy_json ];
-    }
-  else { }
-)
+        systemd.services.caddy.reloadTriggers = [ caddy_json ];
+      }
+    else { }
+  )
diff --git a/modules/caddy/caddyfile.nix b/modules/caddy/caddyfile.nix
index 8ebf226..f3c8b20 100644
--- a/modules/caddy/caddyfile.nix
+++ b/modules/caddy/caddyfile.nix
@@ -73,7 +73,7 @@
     ];
   };
 in
-{ pkgs, lib }: {
+{ pkgs, lib, config }: {
   apps = {
     http.servers = {
       srv0 = {
@@ -279,6 +279,13 @@
               };
             }
           ))
+          (HTTPReverseProxyRoute [ "passwords.clicks.codes" ] [ "localhost:8452" ])
+          (HTTPReverseProxyRoute [
+            "syncthing.clicks.codes"
+            "syncthing.coded.codes"
+            "syncthing.thecoded.prof"
+            "syncthing.hopescaramels.com"
+          ] [ "localhost:8384" ])
         ];
       };
       srv1 = {
diff --git a/modules/drivePaths.nix b/modules/drivePaths.nix
new file mode 100644
index 0000000..addd64b
--- /dev/null
+++ b/modules/drivePaths.nix
@@ -0,0 +1,8 @@
+{ drive_paths, lib, ... }: {
+  fileSystems = lib.mapAttrs'
+    (name: value: {
+      name = value.path;
+      value.device = "/dev/disk/by-uuid/${value.uuid}";
+    })
+    drive_paths;
+}
diff --git a/modules/home-manager-users.nix b/modules/home-manager-users.nix
index 9189240..0e1dbd6 100644
--- a/modules/home-manager-users.nix
+++ b/modules/home-manager-users.nix
@@ -26,7 +26,7 @@
 } // (
   if (base != null)
   then {
-/*    users.groups = lib.mapAttrs'
+    /*    users.groups = lib.mapAttrs'
       (_: user: {
         name = user.group;
         value = { };
diff --git a/modules/postgres.nix b/modules/postgres.nix
index 742e3d4..8f6c5f1 100644
--- a/modules/postgres.nix
+++ b/modules/postgres.nix
@@ -10,6 +10,10 @@
       log_destination = lib.mkForce "syslog";
     };
 
+    ensureDatabases = [
+      "vaultwarden"
+    ];
+
     ensureUsers = [
       {
         name = "clicks_grafana";
@@ -24,6 +28,12 @@
           "DATABASE synapse" = "ALL PRIVILEGES";
         };
       }
+      {
+        name = "vaultwarden";
+        ensurePermissions = {
+          "DATABASE vaultwarden" = "ALL PRIVILEGES";
+        };
+      }
     ] ++ (map
       (name: (
         {
@@ -55,6 +65,7 @@
     )
     (lib.mkAfter (lib.pipe [
       { user = "clicks_grafana"; passwordFile = config.sops.secrets.clicks_grafana_db_password.path; }
+      { user = "vaultwarden"; passwordFile = config.sops.secrets.clicks_bitwarden_db_password.path; }
     ] [
       (map (userData: ''
         $PSQL -tAc "ALTER USER ${userData.user} PASSWORD '$(cat ${userData.passwordFile})';"
@@ -65,6 +76,7 @@
 
   sops.secrets = lib.pipe [
     "clicks_grafana_db_password"
+    "clicks_bitwarden_db_password"
   ] [
     (map (name: {
       inherit name;
diff --git a/modules/scalpel.nix b/modules/scalpel.nix
index 569d2b4..0e0e257 100644
--- a/modules/scalpel.nix
+++ b/modules/scalpel.nix
@@ -1,6 +1,8 @@
-{ lib, config, ... }: let
+{ lib, config, ... }:
+let
   cfg = config.scalpel;
-in {
+in
+{
   system.activationScripts.scalpelCreateStore.text = lib.mkForce ''
     echo "[scalpel] Ensuring existance of ${cfg.secretsDir}"
     mkdir -p ${cfg.secretsDir}
diff --git a/modules/syncthing.nix b/modules/syncthing.nix
new file mode 100644
index 0000000..16c93dd
--- /dev/null
+++ b/modules/syncthing.nix
@@ -0,0 +1,11 @@
+{ pkgs, ... }: {
+  environment.systemPackages = with pkgs; [ syncthing ];
+
+  services.syncthing.enable = true;
+  services.syncthing.openDefaultPorts = true;
+
+  services.syncthing.extraOptions.gui = {
+    user = "admin";
+    password = "$2y$10$nXJNERNUllFWDUrP4Io1zeJQUtiiZwUj1Js8dglDoc.SvhC9kqddm";
+  };
+}
diff --git a/modules/vaultwarden.nix b/modules/vaultwarden.nix
new file mode 100644
index 0000000..091ef50
--- /dev/null
+++ b/modules/vaultwarden.nix
@@ -0,0 +1,133 @@
+{ pkgs, drive_paths, lib, config, ... }: {
+  environment.systemPackages = with pkgs; [ vaultwarden ];
+
+  services.vaultwarden.enable = true;
+  services.vaultwarden.dbBackend = "postgresql";
+
+  sops.secrets = lib.pipe [ "ADMIN_TOKEN", "SMTP_PASSWORD", "YUBICO_SECRET_KEY", "HIBP_API_KEY" ] [
+    (name: {
+      inherit name; value = {
+      mode = "0400";
+      owner = config.users.users.root.name;
+      group = config.users.users.nobody.group;
+      sopsFile = ../secrets/vaultwarden.json;
+      format = "json";
+    };
+    })
+    builtins.listToAttrs
+  ];
+} // (
+  let
+    isDerived = base != null;
+  in
+  if isDerived
+  # We cannot use mkIf as both sides are evaluated no matter the condition value
+  # Given we use base as an attrset, mkIf will error if base is null in here
+  then
+    with lib;
+    let
+      cfg = services.vaultwarden;
+
+      vaultwarden_config = {
+        # Server Settings
+        DOMAIN = "https://passwords.clicks.codes";
+        ROCKET_ADDRESS = "127.0.0.1";
+        ROCKET_PORT = 8452;
+
+
+        # General Settings
+        SIGNUPS_ALLOWED = false;
+        INVITATIONS_ALLOWED = true;
+        SIGNUPS_DOMAINS_WHITELIST = "clicks.codes,coded.codes,thecoded.prof,starrysky.fyi,hopescaramels.com,pinea.dev";
+
+        # TODO: Set folder locations for storing data.
+        RSA_KEY_FILENAME = "${drive_paths.root}/bitwarden/rsa_key";
+        ICON_CACHE_FOLDER = "${drive_paths.root}/bitwarden/icon_cache";
+        ATTACHMENTS_FOLDER = "${drive_paths.External4000HDD}/bitwarden/attachments";
+        SENDS_FOLDER = "${drive_paths.External4000HDD}/bitwarden/sends";
+        TMP_FOLDER = "${drive_paths.External4000HDD}/bitwarden/tmp";
+
+        DISABLE_2FA_REMEMBER = true;
+
+        # Admin Account
+        ADMIN_TOKEN = "!!ADMIN_TOKEN!!";
+
+
+        # Database Settings
+        DATABASE_URL = "postgresql://vaultwarden:!!clicks_bitwarden_db_secret!!@127.0.0.1:${config.services.postgresql.port}/vaultwarden";
+
+
+        # Mail Settings
+        SMTP_HOST = "127.0.0.1";
+        SMTP_FROM = "bitwarden@clicks.codes";
+        SMTP_FROM_NAME = "Clicks Bitwarden";
+        SMTP_SECURITY = "starttls";
+        SMTP_PORT = 587;
+
+        SMTP_USERNAME = "FILL_ME_IN";
+        SMTP_PASSWORD = "!!SMTP_PASSWORD!!";
+
+        REQUIRE_DEVICE_EMAIL = true;
+
+
+        # YubiKey Settings
+        YUBICO_CLIENT_ID = "89788";
+        YUBICO_SECRET_KEY = "!!YUBICO_SECRET_KEY!!";
+
+
+        # TODO: Buy a license
+        # HIBP Settings
+        # HIBP_API_KEY="!!HIBP_API_KEY!!";
+      };
+
+      nameToEnvVar = name:
+        let
+          parts = builtins.split "([A-Z0-9]+)" name;
+          partsToEnvVar = parts: foldl'
+            (key: x:
+              let last = stringLength key - 1; in
+              if isList x then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x
+              else if key != "" && elem (substring 0 1 x) lowerChars then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ]
+                substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x
+              else key + toUpper x) ""
+            parts;
+        in
+        if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts;
+
+      # Due to the different naming schemes allowed for config keys,
+      # we can only check for values consistently after converting them to their corresponding environment variable name.
+      configEnv =
+        let
+          configEnv = concatMapAttrs
+            (name: value: optionalAttrs (value != null) {
+              ${nameToEnvVar name} = if isBool value then boolToString value else toString value;
+            })
+            vaultwarden_config;
+        in
+        { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
+          WEB_VAULT_FOLDER = "${cfg.webVaultPackage}/share/vaultwarden/vault";
+        } // configEnv;
+
+      configFile = pkgs.writeText "vaultwarden.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
+    in
+    {
+      scalpel.trafos."vaultwarden.env" = {
+        source = toString configFile;
+        matchers."ADMIN_TOKEN".secret =
+          config.sops.secrets.ADMIN_TOKEN.path;
+        matchers."SMTP_PASSWORD".secret =
+          config.sops.secrets.SMTP_PASSWORD.path;
+        matchers."YUBICO_SECRET_KEY".secret =
+          config.sops.secrets.YUBICO_SECRET_KEY.path;
+        matchers."HIBP_API_KEY".secret =
+          config.sops.secrets.HIBP_API_KEY.path;
+        matchers."clicks_bitwarden_db_secret".secret =
+          config.sops.secrets.clicks_bitwarden_db_password.path;
+        owner = config.users.users.vaultwarden.name;
+        group = config.users.groups.vaultwarden.name;
+        mode = "0400";
+      };
+
+      services.vaultwarden.environmentFile = config.scalpel.trafos."vaultwarden.env".destination;
+    } else { }
+)
diff --git a/secrets/caddy.json b/secrets/caddy.json
index 6e917f6..1e60b3f 100644
--- a/secrets/caddy.json
+++ b/secrets/caddy.json
@@ -8,11 +8,15 @@
 		"age": [
 			{
 				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYWlBkT0NlcGtabzZPL0FG\ncjNHMEtYWCtkMFNIaDNNdjBuV3ZQQWZNN2w0CkxLa1U5SW1JbE54NnIvL2NXZktY\nNWZNSHFTKzBPYU8rbzY3c002dEZFTGMKLS0tIDBaN2I5OEMrOWgxODdhMXhwRURJ\nYm9CS0Jxb3U0THdnRzlrMWVwYXdwWFkKGqM2dWaZYXL7kFW9YCVEeKF2OwlIyI2l\nKu/X3VslSlwx+B6vAwTFTqnJc0XbiVbelP4/HRC2IQOFjp+5jEJJLQ==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBacDQxSnFyRHppcTZ5RGtx\nTktlU3lxV3ZxS3ZRMG1kUEpBVWxlTFdwM0h3CjRRWkJGMzZqaUNJRmhDU2d4eXd1\nVkFrL09tZk1WMlJpaTRTb2RNZk9RajgKLS0tIHY2Mkovd1VjY1JEVHErZGxYRno2\nWnplTTdNaGpmWUxMZUJpbkIxMFdoek0KfOooGbcME83hOWy2KRA+qkOiIQccdeT/\nb7VthIvwE301M7gN2AtOgxNMri9zDCx+ZQ9F4dglVYwUXfMaHrSG1A==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxanhZKzQ2Qlh0YzlLRnor\nVTZ2S0xEdXVaTWYwc3pRVDd5UXhqVloxWHpnCjJUUG5QZGlJR0tDeDFzTnd6bTM0\nUWxlY1N3anJUMDh4Y1YxYlpzWnRsWG8KLS0tIHI1ZGtROFVxY3l6eFFIMWRpN3Jt\nSmNpWHAyeFNvYllwUlFKbkI3WVFCMkUKUnEeqqClufvhkCrwzKdon7PYMagJupQf\n89WTPHVyAgsua1cUn/ZiG042d9VavijrAHruUEj2j2c0kKFAwf6FFQ==\n-----END AGE ENCRYPTED FILE-----\n"
 			},
 			{
 				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsL0l4Z0NtNWwreVF0VXJr\nYlF4dEhIQ3NLWXowcGRYa1BPb2lYejdYMFdFCm9JdXVHT1JZZnpZS0hoTlpTRmh6\ncW5nMnZDdlg5SmtHQURxbWNBdnRJQjQKLS0tIEV5V2l5RW5KVUFGbW5FOFlGcHFo\nWkhrUDhGNmtDOE8zMzlhU3lKUWNKRkEKcXASv8OxPxeX9ZulhWkXl2qCVSSRFdQ5\nWqx+rHejzQhO81cT0Wf+uiYQDvX+otJQW5Akgv8zcDMQxSyGgG+B+A==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQRWZid2QwSzBRbDR6eEVE\nS2cwRDF5NGNyVVhlMmRVanhjRHhGWmtwVnhBCitQcnk4T0hNR3RkWUh4WUN6RzhV\nSGwyWlMxdFkzditXemZzeWpFN1pPTmMKLS0tIG8zaU5QMmpBRDdvNXhwNTBFTERO\naXFZSS80amFPeHIxeDMveWxvd09VV28KSNJAKsWCOTLJsb0britOs+EunO3kdXyX\nS1fs2bpaa2tH92Fn13LZOVoiLatR8ppD2Sd8hkZ3kQp2dG3s4TiJjw==\n-----END AGE ENCRYPTED FILE-----\n"
 			}
 		],
 		"lastmodified": "2023-05-24T15:18:01Z",
diff --git a/secrets/dmarc.json b/secrets/dmarc.json
index d901b27..9108107 100644
--- a/secrets/dmarc.json
+++ b/secrets/dmarc.json
@@ -9,11 +9,15 @@
 		"age": [
 			{
 				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZK2ZGNmRYTFdaTHdmVzlv\nR3k0N2ExRzRMTmlJMzY4YUJ5MVVzVXJBamdFClpWYmpnOHo5M29yTitaTW9FS0Er\ndVVwNWZlNU1FdU9vTDBVVDFpQUFNU1kKLS0tIHcraTdWVm5BM1RnYzVxM3ZhQWQr\nUjV6S2s2QS9WaWtkYWh1MTBwbERhejAKY3hnjrF+WYkL2vvAL6CQoKW2VU7KTgAv\njtYYgqkpUjPixLG4V/Pik5b6sDAMiVvySscv8xVLyh+hNt3XG25hoQ==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzN1RsSVN2d2V6TW0zQzNr\nY2VMUVlyMXc4UGEwR1JrVks3aWFvR0oxckNRCmg4aEVSdnlTR3ljQmlmTzd1d1k5\nNDZiTUFveEN5eVVsTXJEaE4xZmlxVm8KLS0tIFFyWEFLeC93cjBibWxjUmtEeVpF\ndlBKWmw3S3gvUlVnVHdVamEydHFUYjAKI1LIzSdTBniqMq8aVBdwmzsHGC978VCw\nQwqI3SWDRHuuHnMXba2KLBbOYBrYJU4bUtbBP0STdc2Bj991hrRPow==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaTEd0cno2VXJYUWxZYk0z\nRU1xTUd2VVlOUnBrZkI2YTRqQjNkRWpCOWxBCmw3RDFCd2VWK1R3OUNibHlBM3d3\nc1BSNk82Y1ZXUnRCYS9rd2h4K2hGZFUKLS0tIHRtLzJ0aUFEd243alU1T2YvR2cw\nWmhPd2lySUFRdnArVXJsdUtzNnJmYjQKJJcjV9kPeTRFCKK3Z5Kj0QXJz0/8ShKV\n0wy7PFAE+Peuj0uoCtrY9HGUq3SHtCQI8QH1DcMbUVlDw/y/eLIMow==\n-----END AGE ENCRYPTED FILE-----\n"
 			},
 			{
 				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByMzUxaWREZ3kwaHFsaEdN\nTmhYK2F5UmlJVUplOUFmUmFiTkkvOElsL1c0Cmd2M3VGNEJwYVFmcjVhWlVZeHlm\nTVFFc1NsVXZTSXQ4N1lBTXh0K094bUkKLS0tIGxwelYxMWJEZ2doNGZxcXYxQlRY\nR1ZyeUJjRmowTFJJNFVPdFVkZDVpZTAK35F1nG0+jyKXlmD7t20KBTkIDriJCeh6\n8jENrMEu/MKMyRXLkNo4v9Y4D27BVT7V7l6JU0Ym0lgTS9uPp9SyIQ==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2WmdmWm5vdjkwZys5RzF1\nTXdLd2pmMWxadCtVdm1XS1M2ektNYTVKZFZvCkM4MmE4MnFWaGRVZVMxM2VVUkJS\nREVFbnR4eVBOY3plYzJiQXZsbDEyVlEKLS0tIC9TSDdjWmc0VUhOWTZtRUtaeFd1\ncmQwYUdqY1ZQdlQ1dGNCeGl2S09VRVkKh3qYAebROd/ItSSHSLLbfdasiEK2CZUg\nuIEUalZnL3Uuf5AitGOkytTJE3E/0skfCCyI08qvMkW9TYs9iurPJA==\n-----END AGE ENCRYPTED FILE-----\n"
 			}
 		],
 		"lastmodified": "2023-05-12T00:43:43Z",
diff --git a/secrets/matrix.json b/secrets/matrix.json
index 9937ecc..c6e6d30 100644
--- a/secrets/matrix.json
+++ b/secrets/matrix.json
@@ -9,11 +9,15 @@
 		"age": [
 			{
 				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNYjhKSVpncnhMdmpyV01U\naWgvYzZOZ1AxNHpnUWZvaTNtczdQT0RPcWhnCm1yaGJLdWNvOGVTWEpwTkdSV2t3\nTUtqZlk1bHcybDlNaWtFbnJHVmpyUW8KLS0tIC9yQUZ0dTFjOGRrZTBGVEZvNzFq\nbjgxbkFyZTUreWg2U3B2RU9RSVJhV0kKkvw+LLVg6cw5suarhIhIU4CjcJH3fW3Q\nCnhbNzHSPNrdNJi98g+r5JyjeIlDgd64xrmFD4Ef70ABEiybVLgy5A==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4Y1RhZTBSOUt0NDVkdld2\nSWxhTUJwSGc3RytuOVJ6aE5yemJFb3dyMVhJCnBLK3J0cWxvSGRyTFJHVkthZ2Fm\nRzZjR3E2M0hMQ2dDN2p3MnBOV3U2WUkKLS0tIGduaUNHQzJGQmxXVWMyemo4amtO\nV01leVVuQkI2TmQxdHkvWGtjaVBsZ0kKiwGQlGQmhmwC6wSgPpNb3AB4Ls093pSJ\nL4H4qu6kcK4NYpBouBy1R0ahFdq6Mcq+FmNjvA7DftWhqF+AsXI7Pg==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKb3hRWU1YcjByNFdKajgx\nUk8xZFRnK09hMVpybXd6elhMTEg0dHF2SmlNCm9FS0lEZG5ZeVFNckZuZ3JSMzRZ\nMUw1aGI4Rk5EVnVWemJKYXZXL0dYOTAKLS0tIE1SN1RpVXN0WTVUWFBZS0h1RURw\nQXhPQ2gyaDhheDhBT1ZOd3VjNFBEb1UKjOgdV1HDe4wjQrpJcMRt9MJIEM0PszWH\nudgoTHyAXlHjW9/Mr/d/mrQbGW1pRXvhd1hpx6aw+fcDzxKmhJPrGw==\n-----END AGE ENCRYPTED FILE-----\n"
 			},
 			{
 				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvK25uL25GVHVkNmlNVjZq\nVFgvQXVtcFVhMEQ2QlQ3alcyWXRTU2xXNXhNClBzY1hXblJ4b2QxNFlycFdreldQ\nRWhFMjBNN1JjRjg1TUJJRUlqbHRZZHMKLS0tIGZxVXJtMGtlZG5aTStJQUEzYi9H\nRm1JOEgrelZPUXJNMkppVVVROUpFSEUKpDpQ+CclvVsdEf1JD/EmrRVmsbj24VPM\nn4jfNwDDWTn2kflz2p/TiVdEXskqxdR81EjmnhSZw5lnqF206p8AdQ==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRV2VZbzJuUXQwRmVRZTdK\nR3V3VXdEODFlRCsvengwMDJHWlh3VFFObEdFClJvNm5NVEZ6dnVpWHVHRnRVWXVG\nSk1jRkdjSUxxZmY5b3l6NForQVNBWHMKLS0tIFhIdVpleG1sS25RaWxHZlFTSDk4\nbk9iaGdWVkUxN0dTWkx0VEJaWHJZQzQKhPDYlcEqCOwrxuSEaXMxGFajxwTP5dS0\nV6AhyPwAO2rW0NX6frtDrs1un1el5QKEvm9Bcnli/PNzUbXDbtkjSg==\n-----END AGE ENCRYPTED FILE-----\n"
 			}
 		],
 		"lastmodified": "2023-06-08T07:26:46Z",
diff --git a/secrets/matrix_private_key.pem b/secrets/matrix_private_key.pem
index fa67e12..9c1fbb6 100644
--- a/secrets/matrix_private_key.pem
+++ b/secrets/matrix_private_key.pem
@@ -8,11 +8,15 @@
 		"age": [
 			{
 				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvcFEyTzJkV2UrbFd6UTFR\nT2EvMjRUcXlEUGJaSkxDRUgrb2FWUVlrOFNBCjRIM0RKM0dwV295cjFzMjNiVFNv\nRjlCcTBJMFc0UjkzUUN6SXRwSHFOazAKLS0tIFZBVmNmVnBEdVpzQzRCM2xFdEhH\nN3NSZGFlVUxvSW0yL2Q4M2owY3l0Y3cK25ylhmfVmV1GimAEBysntMcbHJeT1cso\n0MfqBvRVFxOx1Ewi8uE6vUXzO1mlUOjt21FeaWSdUq/2oKpKtr9qZw==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmVnh6aFhoeUVmVHM5V0Nn\nQnlPWVU1UzdhdjErODJmSVZQRE9zUHQzWUdNCkZxSEFoZFhjem5BdEtjTUE0Ym4r\ndE0welcrVGN6WndpekJ0YVhZaHpaOTgKLS0tIHpBT09RZWJGZEtzL1JVSWFad1h1\nUnVBNzJielltRUhSdmpJSVhuTk5ZZUEKrDP6qA8Tjvezl0S+PqHzeIKY1LhInrGI\n/8E/rAgDSnA5R9N0W2kU8zu5isTDY/d0Z/pZtHtsZ+y1lVbC/R3ePg==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4R2Q0NVZjdmhFOTNLZGUx\nMXdEVmJnb0RCZGtpNUJaSEU1OGxxeG9ZZ2djCjU2ekhkWHloa2xzMWZJZGxnci8v\nZVdWaHMyU3oyN285Yzc2QmxndVRqMzQKLS0tIGU2alJ6aitMY05DajFhTzhZeWhw\ncERYZ1lkZG4wSFM1NlprNkNoWnQwelkKPhuZzm2oltQ2Tj7et9BQwQWgJSHJVzaG\nI5kN9Jcw5t9Rypi9UWEQJVlWHAcK5x7uw/9mt/YY5iBQu49h+orinw==\n-----END AGE ENCRYPTED FILE-----\n"
 			},
 			{
 				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxZm1ycXhVeEFCenFlcUQy\nWGEzN0cxYkd1alVsbkcxN25jK0paQjl4azBNClp0OVhXV1RjNlVOVDJObE1iYWd2\nSzVIUVRKUlc5ZEI0ZnVuWklhVG1zNDQKLS0tIDBpZFJaZzhxMzhad2tlWDFQNUJI\nVjdIblVqS1FhRTVHUkVoQXF5MGNwaDQK18s1piDOpkneG/Z+8CIW4X2c33QciDSD\nSgO3BIxY1KhGsRN/5LL7622WIeRjtMikysEcUYsG34XbEbUp7E7ibw==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6OWhPMkZZb2hIcnlYY0Nm\nZ3ZPSTZwZkVkdThRektYbE9MVVJDUUtDZGlJClIycnNzOG1IMitMMFhXN3dmWFRG\ncy9Ld0VCTFZON3RjT2tEUms2ZlUzaGsKLS0tIHFiYy8wUU05NEVyTk91aHc5MVp4\nU1JvZTJ4L2V2dkxYdWlXMVp6TjlUSEkKveW9GxrGhFrL1EEcngsS2/0qvY1d6K5b\nmajAs6GMOD9ADfwA52JxSPaPEIpvvQTvOzp9LCDZvyU4kN4xoybU9Q==\n-----END AGE ENCRYPTED FILE-----\n"
 			}
 		],
 		"lastmodified": "2023-05-20T17:59:44Z",
diff --git a/secrets/postgres.json b/secrets/postgres.json
index b8e71af..ae3bcfc 100644
--- a/secrets/postgres.json
+++ b/secrets/postgres.json
@@ -1,5 +1,6 @@
 {
 	"clicks_grafana_db_password": "ENC[AES256_GCM,data:tFByC3OyhRLkDlfjwq3Kmc7PnTHWmkXpXuqOGb2AzA9dkAijPggPhgvCrbkY8/oL8QwQDaI24+XV3U/8A2UwLbzu0L5oaWV/E4EJbyvi8UKp8Wg8Au25E0nD5tJZm7QQ3FVERgoUefcB8AEPJ4Z8Rgx1PuoBeun9toT1GkJtmuYNNHpOcFrbmaI/Qf1MP+yFZLYjvB1jz07V04RGTv4jow61lWFknS2aPJyat43Ogp64lIkfjen7zCvj3CWghfJx87uxeXsnFHMrRwfONozUdw19Bq1uLUJ7xvPqDtr/1WKi1xvBe5ez7/PkPslNJlIToIlL89xN/lOm2iQR2BNeXg==,iv:ruC4PzKpWYsz2qe0KImUo0YhRt2cisYx306yfPtzi6c=,tag:U8vg7w1zyqXAWH3WzNAHFA==,type:str]",
+	"clicks_bitwarden_db_password": "ENC[AES256_GCM,data:Xr9h/QVazxn1tzSNJgPH9uk6RE2bk0fNyOVNxSlflQ14wnna0xA9Uw4CcN9DbPyvBCvYsKrxTJOAzLOn/K4rc/G1C7dPKpGADiTH5O+1wX+PifrDHtQyuqvaarwO3QA20ZW48A==,iv:yKYDF3X9fNO+rbnguo5DAiXfCkjH06VkpGBjB9NGv/s=,tag:W8fAVyJGh6jQxwHMN+RAPg==,type:str]",
 	"sops": {
 		"kms": null,
 		"gcp_kms": null,
@@ -8,15 +9,19 @@
 		"age": [
 			{
 				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLZ09qSTZ0UE52bWdoVG14\nZE1GNnBMWXM1Qy9rMTlEU3JBbEJhM3hHdFZVCnFRZHNwK1AwUzFHT1VwM3Y4TnFU\nOG9TUFFqU3RJaVpPUTZTVmdYaEFwL2cKLS0tIGhENFNmSTdLbXdwWVZYQjJnOG13\nQkhPZERQaEpqS2VsWUZTSU5TZ2FSSW8K3aeCAWFK2g8ho3qIwIDVjE5xG3vsSeQh\nz4TDQQyZF3QRtOtiqmG6xn5FJnQYATiOLtbTfJhBbHM++0TLwHTXsg==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRa01sZHdFalJFTFVxQ25K\nMms0c1pIeUpHK1MvcGZWN1hwS094OC9RMEZ3CnR0a2xzbmxScUhOcE5CRVdjZFha\nV2h4TVV3TGxFV0tUK3lQSDl4OWpEQlEKLS0tIGVkRFp4N25maW83TGlxTy9WRGxQ\nQ3NESWw5VFNGRlFKRnRlemNsK1NRSHcK34seVyvvseCzn3abwcCAu3rhOUQYMUMC\nLepvm8ahPRcI5Hl3cbX6c1Td6QQ5kPC3QYyngKX4M+jhWMfpuDbwHQ==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMMThlZzkzUW1hWUp0djZl\nMmVsRzZNdnA0QmRXaU5JeXBVcDB2Lytya3hNCitSVi8rdkp3UmVXWTV0MGV3QWJF\nZHJxMk1UaDF0b01qTm1sL3ozNjJpZmsKLS0tIGtDcFF2cTFmS3d2UFNYamFCOFdm\nUnk1dUhvR1R6cTlzNVVqdU5CdG9YS2MK0yfOBwjloIjtaWtJf6hRsjm9LCOHbB96\nzRl95IazEXhBUwB9WC2RsH6l8/Ja4AHvTnCmznItbvQL/LassZsMGQ==\n-----END AGE ENCRYPTED FILE-----\n"
 			},
 			{
 				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
-				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwNVB5L3hPcUFuaU1qNzdX\nL3psaUdpcHp1WkxmYS9Sdi9IY1BLbEd0aHdRCkp1TWlEWHY1RERMNWltK3N4a3d3\nYzhENENNYnVlaDdBcXBYYnQwSWRkTGMKLS0tIDlvWWlwNkNleVB5Y0RpZGJhRUR4\ndlVzY2plQ2loU2YwZXpIRnh2UFVFZTgKrgf/fmoatZbtnUSn1zVs0UJdcyipCqn7\nFh+K3dbT25JYD2U4glE3xW6D9TMTUBNGguHE3MQPJaK74FfGp7L/Bw==\n-----END AGE ENCRYPTED FILE-----\n"
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByN0hMaXF2S2xiTTNqbm9h\nSGNieGNjNThJTk0wZXNQejBXaW9MOEp2akNFCm8rWVc3WG9pVndERFZzUnFOZnVG\naFBxTENlQ0x0ZjdqUHF6SFBPKzQ4bUEKLS0tIGVJTmdveTZrNTRiSTl0cHRQeWhl\nY2RmUTVQVTNoMFhLdkc3WFZEcHAycnMKqr42TSx7Pqcu62XgX4gj/iq2tbkZjFxg\nOcWBsLzqOsu/r0w5cK2Ple6JFGIJwmT2SqVqZh1pPbPwYHHXHbEphg==\n-----END AGE ENCRYPTED FILE-----\n"
 			}
 		],
-		"lastmodified": "2023-05-20T19:42:49Z",
-		"mac": "ENC[AES256_GCM,data:UgiFqVr7UMXZUsbYtx7Q09Z9BwqXtAfqvaXybzsvT1XKFRaXnWT/XwjihU7EPjsDO9KzG3lXKln9DMzshAFJw3hjpN4D0LOmFySyduZl2qt5O/yKL7rrxhGgs57hITYB53DQvbkJ87W2sRAdpJlKWB1rrJ5J6RCqoxG/A3HjDAY=,iv:earJ7dyBUR2BGUVG7OhxgWMsPJl+JRB5xbKCe64AA7Q=,tag:UcuGo+aSfMkCHWtg8IDOSA==,type:str]",
+		"lastmodified": "2023-06-13T21:01:47Z",
+		"mac": "ENC[AES256_GCM,data:vm4yvl63zP9abxXuCtjjRh9LOVq63Q3nc8o8p2wj+v0pd1ltVZAMKmi/c6f4q+s12M0EYNQbqspLpG4OYva6WinAIyVJIvjvBJGKseH+lB2GrO+3jwlanOiHRl2ueAhihfGgj3dgqGiLUKoT1jFewZMoAY+PiGhp+Bup/Ziky9g=,iv:4YGy2zExsKUGQ9WAir0MFH9mFirbCPkjS523w1wpys4=,tag:nF8pO9I57AGAydFupYYU0w==,type:str]",
 		"pgp": null,
 		"unencrypted_suffix": "_unencrypted",
 		"version": "3.7.3"
diff --git a/secrets/vaultwarden.json b/secrets/vaultwarden.json
new file mode 100644
index 0000000..4de32f1
--- /dev/null
+++ b/secrets/vaultwarden.json
@@ -0,0 +1,31 @@
+{
+	"ADMIN_TOKEN": "ENC[AES256_GCM,data:kbtCkvQJcIZ4sQbnTXCYj864WQywrd/98v3VOynoiIw8xd/H0orOX0QZ7zZDuHbbAukOul5ZUzjPah5razGMEECIkhWqVYnAtyhr,iv:UTrKoA8uhNaUT7nDfBMzMkHpLq/gpl9+nrx5ySnNGEc=,tag:dQgR4m9neYKvLzwMlzHGag==,type:str]",
+	"SMTP_PASSWORD": "ENC[AES256_GCM,data:UdICw05COL/YmQ3x6JUuIQTyde1/XGK916DSpHpCf7xOOV5j/yjMNXQWh0J46lPMLwoKPqjx4L8oPEDr,iv:5QMUlTXWCUlCiqqCsc5t8En3wZitH5ygWXf9O3wNZoE=,tag:TIQtGWNfsRv1WgKzAmfJNg==,type:str]",
+	"YUBICO_SECRET_KEY": "ENC[AES256_GCM,data:AfUPdtEYUMPLLm3omfTD4IhHG4B5SQ6df1ZfvQ==,iv:rP66dYyeOsqkN+ZD80U/5jj//PWn7Ox++1L4OETX4m0=,tag:EiujU9WcLs7M1hMMR5UIHQ==,type:str]",
+	"HIBP_API_KEY": "ENC[AES256_GCM,data:ZODeXVLFsw==,iv:lEybxmKCQ8SbssGWE3UqSagz5M97o2CWGW0HAiqJyD8=,tag:c0uT1dKtln5frysnjS0UPA==,type:str]",
+	"sops": {
+		"kms": null,
+		"gcp_kms": null,
+		"azure_kv": null,
+		"hc_vault": null,
+		"age": [
+			{
+				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxeGlkQm5DeFdTRlJQUkt3\nNld3bnpvMVlzOXprT25aTVR5YUg2R2psRHlRCkxVbHFkQmZtaEJHam9wblJ2NGxl\nRWhBVXB3Y1B3MVRmdFVuc2x4Snd3YW8KLS0tIDlpbHo3aUpvQ1JidUZuOUpEVGdo\nWEtVR0hDR09DWEZTaU4vTTI1VndnQ1kK+gHHkQvm6W9+u7CroftmI2ruMdy18vBr\nf0m+GwOs3tsVGCZn6G+WfPJ3LClZPt4Z66iHU2eNUHTjbM/CvRgJ4A==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBudStENzJUTHp5Tm5FaEN5\nVmltSmFJODNqc0RQL0I1S0EyaUlhcktlVUZNCnFQWExWSVgyWlljZno0ZmQyRVJm\nYjFuZy9lMVRmbmMvOUQ0OHQzaG1PRVUKLS0tIGFPc0FXQ0lQMUE4b0xnTHhhUEQy\nenM2emwzWTQzd2VNY3BPYkFhZ3RpNjgKGi3Twdd8XYulM6L9wRtlRNlG0m/+HyUb\nsgemlvcWo85dGK4HwftjVvT4Dkc1X9lXMV2Km3+5GtqXLZLWIsg8aQ==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUd2ZiN1F6Q25IN3BpbTRG\nSXI4OW1PdG5rb2krai94ck0wZFh5enBJSFZBCjRnNHB5c1BmNEJIb3FiUEFmQjlV\nKzNpOUJBYzhvd1JSZDZidWVLMVpnZjAKLS0tIFBsOXllU0VZS3NmZDJTZm80bzNp\nazlOUjFPT0MxREVFNVhINVliYm5vRG8KSWa74oUBA4XnnN296zlRvCyhUr2qkm1j\nXlMbq8gYpoL8ttqqyoXfevY7ifezt+U2ookzlONXe52ZENShLofqZg==\n-----END AGE ENCRYPTED FILE-----\n"
+			}
+		],
+		"lastmodified": "2023-06-13T16:56:57Z",
+		"mac": "ENC[AES256_GCM,data:WbnHPT/N4XUdTsUb4ousd7Tt/3FY+yHEwMt0PEQAySnjudQBO0ygtZoQHl9ot/9TUMgb12w35nx3pG4BGmS/BBDUuMNiUsmzG6ct1a1Wa6o60VbSSjftXJ36Jipz6rse7o708UlJd3D7xWcUOxA6xgwHZfNgG//dLmVjpLkIG6E=,iv:SckbBqSi7n79Km/GeG4LkpedSZ6tl4jyqSnjr/1r/3g=,tag:oCdW2dpzrChjwL9k6g8GcA==,type:str]",
+		"pgp": null,
+		"unencrypted_suffix": "_unencrypted",
+		"version": "3.7.3"
+	}
+}
\ No newline at end of file
diff --git a/services/kavita/default.nix b/services/kavita/default.nix
index c724ad7..4691474 100644
--- a/services/kavita/default.nix
+++ b/services/kavita/default.nix
@@ -1 +1 @@
-{ pkgs, config, lib, ... }: {}
+{ pkgs, config, lib, ... }: { }
diff --git a/services/mailu/default.nix b/services/mailu/default.nix
index 0967ef4..ffcd441 100644
--- a/services/mailu/default.nix
+++ b/services/mailu/default.nix
@@ -1 +1 @@
-{}
+{ }
diff --git a/variables/drive_paths.nix b/variables/drive_paths.nix
new file mode 100644
index 0000000..3ae3c73
--- /dev/null
+++ b/variables/drive_paths.nix
@@ -0,0 +1,19 @@
+{
+  root = "/";
+  Internal120SSD = {
+    path = "/mnt/120InternalSSD";
+    uuid = "3051c23d-143a-4344-8ad9-e0a8cf3b8a3d";
+  };
+  External1000SSD = {
+    path = "/mnt/1000ExternalSSD";
+    uuid = "24d30ffe-91ed-4e41-b40d-f42b02e144a9";
+  };
+  External2000HDD = {
+    path = "/mnt/2000ExternalHDD";
+    uuid = "77866be0-869c-44f7-b375-0edb76fb1895";
+  };
+  External4000HDD = {
+    path = "/mnt/4000ExternalHDD";
+    uuid = "dda57e4d-81b7-4f52-b3ac-f14544b3aaf4";
+  };
+}