at 23.11-beta 9.5 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.vaultwarden; 7 user = config.users.users.vaultwarden.name; 8 group = config.users.groups.vaultwarden.name; 9 10 # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER). 11 nameToEnvVar = name: 12 let 13 parts = builtins.split "([A-Z0-9]+)" name; 14 partsToEnvVar = parts: foldl' (key: x: let last = stringLength key - 1; in 15 if isList x then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x 16 else if key != "" && elem (substring 0 1 x) lowerChars then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ] 17 substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x 18 else key + toUpper x) "" parts; 19 in if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts; 20 21 # Due to the different naming schemes allowed for config keys, 22 # we can only check for values consistently after converting them to their corresponding environment variable name. 23 configEnv = 24 let 25 configEnv = concatMapAttrs (name: value: optionalAttrs (value != null) { 26 ${nameToEnvVar name} = if isBool value then boolToString value else toString value; 27 }) cfg.config; 28 in { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") { 29 WEB_VAULT_FOLDER = "${cfg.webVaultPackage}/share/vaultwarden/vault"; 30 } // configEnv; 31 32 configFile = pkgs.writeText "vaultwarden.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv)); 33 34 vaultwarden = cfg.package.override { inherit (cfg) dbBackend; }; 35 36in { 37 imports = [ 38 (mkRenamedOptionModule [ "services" "bitwarden_rs" ] [ "services" "vaultwarden" ]) 39 ]; 40 41 options.services.vaultwarden = with types; { 42 enable = mkEnableOption (lib.mdDoc "vaultwarden"); 43 44 dbBackend = mkOption { 45 type = enum [ "sqlite" "mysql" "postgresql" ]; 46 default = "sqlite"; 47 description = lib.mdDoc '' 48 Which database backend vaultwarden will be using. 49 ''; 50 }; 51 52 backupDir = mkOption { 53 type = nullOr str; 54 default = null; 55 description = lib.mdDoc '' 56 The directory under which vaultwarden will backup its persistent data. 57 ''; 58 }; 59 60 config = mkOption { 61 type = attrsOf (nullOr (oneOf [ bool int str ])); 62 default = { 63 ROCKET_ADDRESS = "::1"; # default to localhost 64 ROCKET_PORT = 8222; 65 }; 66 example = literalExpression '' 67 { 68 DOMAIN = "https://bitwarden.example.com"; 69 SIGNUPS_ALLOWED = false; 70 71 # Vaultwarden currently recommends running behind a reverse proxy 72 # (nginx or similar) for TLS termination, see 73 # https://github.com/dani-garcia/vaultwarden/wiki/Hardening-Guide#reverse-proxying 74 # > you should avoid enabling HTTPS via vaultwarden's built-in Rocket TLS support, 75 # > especially if your instance is publicly accessible. 76 # 77 # A suitable NixOS nginx reverse proxy example config might be: 78 # 79 # services.nginx.virtualHosts."bitwarden.example.com" = { 80 # enableACME = true; 81 # forceSSL = true; 82 # locations."/" = { 83 # proxyPass = "http://127.0.0.1:''${toString config.services.vaultwarden.config.ROCKET_PORT}"; 84 # }; 85 # }; 86 ROCKET_ADDRESS = "127.0.0.1"; 87 ROCKET_PORT = 8222; 88 89 ROCKET_LOG = "critical"; 90 91 # This example assumes a mailserver running on localhost, 92 # thus without transport encryption. 93 # If you use an external mail server, follow: 94 # https://github.com/dani-garcia/vaultwarden/wiki/SMTP-configuration 95 SMTP_HOST = "127.0.0.1"; 96 SMTP_PORT = 25; 97 SMTP_SSL = false; 98 99 SMTP_FROM = "admin@bitwarden.example.com"; 100 SMTP_FROM_NAME = "example.com Bitwarden server"; 101 } 102 ''; 103 description = lib.mdDoc '' 104 The configuration of vaultwarden is done through environment variables, 105 therefore it is recommended to use upper snake case (e.g. {env}`DISABLE_2FA_REMEMBER`). 106 107 However, camel case (e.g. `disable2FARemember`) is also supported: 108 The NixOS module will convert it automatically to 109 upper case snake case (e.g. {env}`DISABLE_2FA_REMEMBER`). 110 In this conversion digits (0-9) are handled just like upper case characters, 111 so `foo2` would be converted to {env}`FOO_2`. 112 Names already in this format remain unchanged, so `FOO2` remains `FOO2` if passed as such, 113 even though `foo2` would have been converted to {env}`FOO_2`. 114 This allows working around any potential future conflicting naming conventions. 115 116 Based on the attributes passed to this config option an environment file will be generated 117 that is passed to vaultwarden's systemd service. 118 119 The available configuration options can be found in 120 [the environment template file](https://github.com/dani-garcia/vaultwarden/blob/${vaultwarden.version}/.env.template). 121 122 See [](#opt-services.vaultwarden.environmentFile) for how 123 to set up access to the Admin UI to invite initial users. 124 ''; 125 }; 126 127 environmentFile = mkOption { 128 type = with types; nullOr path; 129 default = null; 130 example = "/var/lib/vaultwarden.env"; 131 description = lib.mdDoc '' 132 Additional environment file as defined in {manpage}`systemd.exec(5)`. 133 134 Secrets like {env}`ADMIN_TOKEN` and {env}`SMTP_PASSWORD` 135 may be passed to the service without adding them to the world-readable Nix store. 136 137 Note that this file needs to be available on the host on which 138 `vaultwarden` is running. 139 140 As a concrete example, to make the Admin UI available 141 (from which new users can be invited initially), 142 the secret {env}`ADMIN_TOKEN` needs to be defined as described 143 [here](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page). 144 Setting `environmentFile` to `/var/lib/vaultwarden.env` 145 and ensuring permissions with e.g. 146 `chown vaultwarden:vaultwarden /var/lib/vaultwarden.env` 147 (the `vaultwarden` user will only exist after activating with 148 `enable = true;` before this), we can set the contents of the file to have 149 contents such as: 150 151 ``` 152 # Admin secret token, see 153 # https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page 154 ADMIN_TOKEN=...copy-paste a unique generated secret token here... 155 ``` 156 ''; 157 }; 158 159 package = mkOption { 160 type = package; 161 default = pkgs.vaultwarden; 162 defaultText = literalExpression "pkgs.vaultwarden"; 163 description = lib.mdDoc "Vaultwarden package to use."; 164 }; 165 166 webVaultPackage = mkOption { 167 type = package; 168 default = pkgs.vaultwarden.webvault; 169 defaultText = literalExpression "pkgs.vaultwarden.webvault"; 170 description = lib.mdDoc "Web vault package to use."; 171 }; 172 }; 173 174 config = mkIf cfg.enable { 175 assertions = [ { 176 assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite"; 177 message = "Backups for database backends other than sqlite will need customization"; 178 } ]; 179 180 users.users.vaultwarden = { 181 inherit group; 182 isSystemUser = true; 183 }; 184 users.groups.vaultwarden = { }; 185 186 systemd.services.vaultwarden = { 187 aliases = [ "bitwarden_rs.service" ]; 188 after = [ "network.target" ]; 189 path = with pkgs; [ openssl ]; 190 serviceConfig = { 191 User = user; 192 Group = group; 193 EnvironmentFile = [ configFile ] ++ optional (cfg.environmentFile != null) cfg.environmentFile; 194 ExecStart = "${vaultwarden}/bin/vaultwarden"; 195 LimitNOFILE = "1048576"; 196 PrivateTmp = "true"; 197 PrivateDevices = "true"; 198 ProtectHome = "true"; 199 ProtectSystem = "strict"; 200 AmbientCapabilities = "CAP_NET_BIND_SERVICE"; 201 StateDirectory = "bitwarden_rs"; 202 StateDirectoryMode = "0700"; 203 Restart = "always"; 204 }; 205 wantedBy = [ "multi-user.target" ]; 206 }; 207 208 systemd.services.backup-vaultwarden = mkIf (cfg.backupDir != null) { 209 aliases = [ "backup-bitwarden_rs.service" ]; 210 description = "Backup vaultwarden"; 211 environment = { 212 DATA_FOLDER = "/var/lib/bitwarden_rs"; 213 BACKUP_FOLDER = cfg.backupDir; 214 }; 215 path = with pkgs; [ sqlite ]; 216 # if both services are started at the same time, vaultwarden fails with "database is locked" 217 before = [ "vaultwarden.service" ]; 218 serviceConfig = { 219 SyslogIdentifier = "backup-vaultwarden"; 220 Type = "oneshot"; 221 User = mkDefault user; 222 Group = mkDefault group; 223 ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}"; 224 }; 225 wantedBy = [ "multi-user.target" ]; 226 }; 227 228 systemd.timers.backup-vaultwarden = mkIf (cfg.backupDir != null) { 229 aliases = [ "backup-bitwarden_rs.timer" ]; 230 description = "Backup vaultwarden on time"; 231 timerConfig = { 232 OnCalendar = mkDefault "23:00"; 233 Persistent = "true"; 234 Unit = "backup-vaultwarden.service"; 235 }; 236 wantedBy = [ "multi-user.target" ]; 237 }; 238 }; 239 240 # uses attributes of the linked package 241 meta.buildDocsInSandbox = false; 242}