nixos/vaultwarden: add configureNginx+configurePostgres options (#469132)

This commit is contained in:
Sandro
2025-12-14 02:20:57 +00:00
committed by GitHub
@@ -7,6 +7,7 @@
let
cfg = config.services.vaultwarden;
user = config.users.users.vaultwarden.name;
group = config.users.groups.vaultwarden.name;
@@ -115,21 +116,7 @@ in
DOMAIN = "https://bitwarden.example.com";
SIGNUPS_ALLOWED = false;
# Vaultwarden currently recommends running behind a reverse proxy
# (nginx or similar) for TLS termination, see
# https://github.com/dani-garcia/vaultwarden/wiki/Hardening-Guide#reverse-proxying
# > you should avoid enabling HTTPS via vaultwarden's built-in Rocket TLS support,
# > especially if your instance is publicly accessible.
#
# A suitable NixOS nginx reverse proxy example config might be:
#
# services.nginx.virtualHosts."bitwarden.example.com" = {
# enableACME = true;
# forceSSL = true;
# locations."/" = {
# proxyPass = "http://127.0.0.1:''${toString config.services.vaultwarden.config.ROCKET_PORT}";
# };
# };
# Vaultwarden recommends running behind a reverse proxy, the configureNginx option can be used for that.
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = 8222;
@@ -171,6 +158,24 @@ in
'';
};
configureNginx = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to configure nginx to serve VaultWarden.";
};
configurePostgres = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to configure a local PostgreSQL server.";
};
domain = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = "The domain under which VaultWarden will be reachable.";
};
environmentFile = lib.mkOption {
type = with lib.types; coercedTo path lib.singleton (listOf path);
default = [ ];
@@ -210,100 +215,153 @@ in
assertion = cfg.backupDir != null -> !(lib.hasPrefix dataDir cfg.backupDir);
message = "Backup directory can not be in ${dataDir}";
}
{
assertion = cfg.configureNginx -> cfg.domain != null;
message = "Setting services.vaultwarden.configureNginx to true requires configuring services.vaultwarden.domain!";
}
];
users.users.vaultwarden = {
inherit group;
isSystemUser = true;
};
users.groups.vaultwarden = { };
services = {
nginx = lib.mkIf cfg.configureNginx {
enable = true;
upstreams.vaultwarden.servers."127.0.0.1:${toString cfg.config.ROCKET_PORT}" = { };
virtualHosts.${cfg.domain} = {
forceSSL = true;
locations = {
"/".proxyPass = "http://vaultwarden";
"= /notifications/anonymous-hub" = {
proxyPass = "http://vaultwarden";
proxyWebsockets = true;
};
"= /notifications/hub" = {
proxyPass = "http://vaultwarden";
proxyWebsockets = true;
};
};
};
};
systemd.services.vaultwarden = {
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = with pkgs; [ openssl ];
serviceConfig = {
User = user;
Group = group;
EnvironmentFile = [ configFile ] ++ cfg.environmentFile;
ExecStart = lib.getExe vaultwarden;
LimitNOFILE = "1048576";
CapabilityBoundingSet = [ "" ];
DeviceAllow = [ "" ];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = !useSendmail;
PrivateDevices = !useSendmail;
PrivateTmp = true;
PrivateUsers = !useSendmail;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "noaccess";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
postgresql = lib.mkIf cfg.configurePostgres {
enable = true;
ensureDatabases = [ "vaultwarden" ];
ensureUsers = [
{
name = "vaultwarden";
ensureDBOwnership = true;
}
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
inherit StateDirectory;
StateDirectoryMode = "0700";
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
]
++ lib.optionals (!useSendmail) [
"~@privileged"
];
Restart = "always";
UMask = "0077";
};
wantedBy = [ "multi-user.target" ];
vaultwarden.config = lib.mkMerge [
{
DATABASE_URL = lib.mkIf cfg.configurePostgres "postgresql:///vaultwarden?host=/run/postgresql";
DOMAIN = lib.mkIf (cfg.domain != null) "https://${cfg.domain}";
}
(lib.mkIf cfg.configureNginx {
ENABLE_WEBSOCKET = true;
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = lib.mkDefault 8222;
})
];
};
systemd.services.backup-vaultwarden = lib.mkIf (cfg.backupDir != null) {
description = "Backup vaultwarden";
environment = {
DATA_FOLDER = dataDir;
BACKUP_FOLDER = cfg.backupDir;
systemd = {
services.vaultwarden = {
after = [ "network-online.target" ] ++ lib.optional cfg.configurePostgres "postgresql.target";
requires = lib.mkIf cfg.configurePostgres [ "postgresql.target" ];
wants = [ "network-online.target" ];
path = with pkgs; [ openssl ];
serviceConfig = {
User = user;
Group = group;
EnvironmentFile = [ configFile ] ++ cfg.environmentFile;
ExecStart = lib.getExe vaultwarden;
LimitNOFILE = "1048576";
CapabilityBoundingSet = [ "" ];
DeviceAllow = [ "" ];
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = !useSendmail;
PrivateDevices = !useSendmail;
PrivateTmp = true;
PrivateUsers = !useSendmail;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "noaccess";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
inherit StateDirectory;
StateDirectoryMode = "0700";
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
]
++ lib.optionals (!useSendmail) [
"~@privileged"
];
Restart = "always";
UMask = "0077";
};
wantedBy = [ "multi-user.target" ];
};
path = with pkgs; [ sqlite ];
# if both services are started at the same time, vaultwarden fails with "database is locked"
before = [ "vaultwarden.service" ];
serviceConfig = {
SyslogIdentifier = "backup-vaultwarden";
Type = "oneshot";
User = lib.mkDefault user;
Group = lib.mkDefault group;
ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}";
services.backup-vaultwarden = lib.mkIf (cfg.backupDir != null) {
description = "Backup vaultwarden";
environment = {
DATA_FOLDER = dataDir;
BACKUP_FOLDER = cfg.backupDir;
};
path = with pkgs; [ sqlite ];
# if both services are started at the same time, vaultwarden fails with "database is locked"
before = [ "vaultwarden.service" ];
serviceConfig = {
SyslogIdentifier = "backup-vaultwarden";
Type = "oneshot";
User = lib.mkDefault user;
Group = lib.mkDefault group;
ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}";
};
wantedBy = [ "multi-user.target" ];
};
timers.backup-vaultwarden = lib.mkIf (cfg.backupDir != null) {
description = "Backup vaultwarden on time";
timerConfig = {
OnCalendar = lib.mkDefault "23:00";
Persistent = "true";
Unit = "backup-vaultwarden.service";
};
wantedBy = [ "multi-user.target" ];
};
tmpfiles.settings = lib.mkIf (cfg.backupDir != null) {
"10-vaultwarden".${cfg.backupDir}.d = {
inherit user group;
mode = "0770";
};
};
wantedBy = [ "multi-user.target" ];
};
systemd.timers.backup-vaultwarden = lib.mkIf (cfg.backupDir != null) {
description = "Backup vaultwarden on time";
timerConfig = {
OnCalendar = lib.mkDefault "23:00";
Persistent = "true";
Unit = "backup-vaultwarden.service";
};
wantedBy = [ "multi-user.target" ];
};
systemd.tmpfiles.settings = lib.mkIf (cfg.backupDir != null) {
"10-vaultwarden".${cfg.backupDir}.d = {
inherit user group;
mode = "0770";
users = {
groups.vaultwarden = { };
users.vaultwarden = {
inherit group;
isSystemUser = true;
};
};
};