at 23.11-pre 9.4 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 example = literalExpression '' 64 { 65 DOMAIN = "https://bitwarden.example.com"; 66 SIGNUPS_ALLOWED = false; 67 68 # Vaultwarden currently recommends running behind a reverse proxy 69 # (nginx or similar) for TLS termination, see 70 # https://github.com/dani-garcia/vaultwarden/wiki/Hardening-Guide#reverse-proxying 71 # > you should avoid enabling HTTPS via vaultwarden's built-in Rocket TLS support, 72 # > especially if your instance is publicly accessible. 73 # 74 # A suitable NixOS nginx reverse proxy example config might be: 75 # 76 # services.nginx.virtualHosts."bitwarden.example.com" = { 77 # enableACME = true; 78 # forceSSL = true; 79 # locations."/" = { 80 # proxyPass = "http://127.0.0.1:''${toString config.services.vaultwarden.config.ROCKET_PORT}"; 81 # }; 82 # }; 83 ROCKET_ADDRESS = "127.0.0.1"; 84 ROCKET_PORT = 8222; 85 86 ROCKET_LOG = "critical"; 87 88 # This example assumes a mailserver running on localhost, 89 # thus without transport encryption. 90 # If you use an external mail server, follow: 91 # https://github.com/dani-garcia/vaultwarden/wiki/SMTP-configuration 92 SMTP_HOST = "127.0.0.1"; 93 SMTP_PORT = 25; 94 SMTP_SSL = false; 95 96 SMTP_FROM = "admin@bitwarden.example.com"; 97 SMTP_FROM_NAME = "example.com Bitwarden server"; 98 } 99 ''; 100 description = lib.mdDoc '' 101 The configuration of vaultwarden is done through environment variables, 102 therefore it is recommended to use upper snake case (e.g. {env}`DISABLE_2FA_REMEMBER`). 103 104 However, camel case (e.g. `disable2FARemember`) is also supported: 105 The NixOS module will convert it automatically to 106 upper case snake case (e.g. {env}`DISABLE_2FA_REMEMBER`). 107 In this conversion digits (0-9) are handled just like upper case characters, 108 so `foo2` would be converted to {env}`FOO_2`. 109 Names already in this format remain unchanged, so `FOO2` remains `FOO2` if passed as such, 110 even though `foo2` would have been converted to {env}`FOO_2`. 111 This allows working around any potential future conflicting naming conventions. 112 113 Based on the attributes passed to this config option an environment file will be generated 114 that is passed to vaultwarden's systemd service. 115 116 The available configuration options can be found in 117 [the environment template file](https://github.com/dani-garcia/vaultwarden/blob/${vaultwarden.version}/.env.template). 118 119 See ()[#opt-services.vaultwarden.environmentFile) for how 120 to set up access to the Admin UI to invite initial users. 121 ''; 122 }; 123 124 environmentFile = mkOption { 125 type = with types; nullOr path; 126 default = null; 127 example = "/var/lib/vaultwarden.env"; 128 description = lib.mdDoc '' 129 Additional environment file as defined in {manpage}`systemd.exec(5)`. 130 131 Secrets like {env}`ADMIN_TOKEN` and {env}`SMTP_PASSWORD` 132 may be passed to the service without adding them to the world-readable Nix store. 133 134 Note that this file needs to be available on the host on which 135 `vaultwarden` is running. 136 137 As a concrete example, to make the Admin UI available 138 (from which new users can be invited initially), 139 the secret {env}`ADMIN_TOKEN` needs to be defined as described 140 [here](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page). 141 Setting `environmentFile` to `/var/lib/vaultwarden.env` 142 and ensuring permissions with e.g. 143 `chown vaultwarden:vaultwarden /var/lib/vaultwarden.env` 144 (the `vaultwarden` user will only exist after activating with 145 `enable = true;` before this), we can set the contents of the file to have 146 contents such as: 147 148 ``` 149 # Admin secret token, see 150 # https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page 151 ADMIN_TOKEN=...copy-paste a unique generated secret token here... 152 ``` 153 ''; 154 }; 155 156 package = mkOption { 157 type = package; 158 default = pkgs.vaultwarden; 159 defaultText = literalExpression "pkgs.vaultwarden"; 160 description = lib.mdDoc "Vaultwarden package to use."; 161 }; 162 163 webVaultPackage = mkOption { 164 type = package; 165 default = pkgs.vaultwarden.webvault; 166 defaultText = literalExpression "pkgs.vaultwarden.webvault"; 167 description = lib.mdDoc "Web vault package to use."; 168 }; 169 }; 170 171 config = mkIf cfg.enable { 172 assertions = [ { 173 assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite"; 174 message = "Backups for database backends other than sqlite will need customization"; 175 } ]; 176 177 users.users.vaultwarden = { 178 inherit group; 179 isSystemUser = true; 180 }; 181 users.groups.vaultwarden = { }; 182 183 systemd.services.vaultwarden = { 184 aliases = [ "bitwarden_rs.service" ]; 185 after = [ "network.target" ]; 186 path = with pkgs; [ openssl ]; 187 serviceConfig = { 188 User = user; 189 Group = group; 190 EnvironmentFile = [ configFile ] ++ optional (cfg.environmentFile != null) cfg.environmentFile; 191 ExecStart = "${vaultwarden}/bin/vaultwarden"; 192 LimitNOFILE = "1048576"; 193 PrivateTmp = "true"; 194 PrivateDevices = "true"; 195 ProtectHome = "true"; 196 ProtectSystem = "strict"; 197 AmbientCapabilities = "CAP_NET_BIND_SERVICE"; 198 StateDirectory = "bitwarden_rs"; 199 StateDirectoryMode = "0700"; 200 Restart = "always"; 201 }; 202 wantedBy = [ "multi-user.target" ]; 203 }; 204 205 systemd.services.backup-vaultwarden = mkIf (cfg.backupDir != null) { 206 aliases = [ "backup-bitwarden_rs.service" ]; 207 description = "Backup vaultwarden"; 208 environment = { 209 DATA_FOLDER = "/var/lib/bitwarden_rs"; 210 BACKUP_FOLDER = cfg.backupDir; 211 }; 212 path = with pkgs; [ sqlite ]; 213 # if both services are started at the same time, vaultwarden fails with "database is locked" 214 before = [ "vaultwarden.service" ]; 215 serviceConfig = { 216 SyslogIdentifier = "backup-vaultwarden"; 217 Type = "oneshot"; 218 User = mkDefault user; 219 Group = mkDefault group; 220 ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}"; 221 }; 222 wantedBy = [ "multi-user.target" ]; 223 }; 224 225 systemd.timers.backup-vaultwarden = mkIf (cfg.backupDir != null) { 226 aliases = [ "backup-bitwarden_rs.timer" ]; 227 description = "Backup vaultwarden on time"; 228 timerConfig = { 229 OnCalendar = mkDefault "23:00"; 230 Persistent = "true"; 231 Unit = "backup-vaultwarden.service"; 232 }; 233 wantedBy = [ "multi-user.target" ]; 234 }; 235 }; 236 237 # uses attributes of the linked package 238 meta.buildDocsInSandbox = false; 239}