0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-20 00:50:38 +03:00
nixpkgs/nixos/modules/services/web-apps/wakapi.nix
Wolfgang Walther 41c5662cbe
nixos/postgresql: move postStart into separate unit
This avoids restarting the postgresql server, when only ensureDatabases
or ensureUsers have been changed. It will also allow to properly wait
for recovery to finish later.

To wait for "postgresql is ready" in other services, we now provide a
postgresql.target.

Resolves #400018

Co-authored-by: Marcel <me@m4rc3l.de>
2025-06-24 15:26:47 +02:00

252 lines
7.1 KiB
Nix

{
lib,
pkgs,
config,
...
}:
let
cfg = config.services.wakapi;
settingsFormat = pkgs.formats.yaml { };
settingsFile = settingsFormat.generate "wakapi-settings" cfg.settings;
inherit (lib)
getExe
mkOption
mkEnableOption
mkPackageOption
types
mkIf
optional
mkMerge
singleton
;
in
{
options.services.wakapi = {
enable = mkEnableOption "Wakapi";
package = mkPackageOption pkgs "wakapi" { };
stateDir = mkOption {
type = types.path;
default = "/var/lib/wakapi";
description = ''
The state directory where data is stored. Will also be used as the
working directory for the wakapi service.
'';
};
settings = mkOption {
inherit (settingsFormat) type;
default = { };
description = ''
Settings for Wakapi.
See [config.default.yml](https://github.com/muety/wakapi/blob/master/config.default.yml) for a list of all possible options.
'';
};
passwordSalt = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The password salt to use for Wakapi.
'';
};
passwordSaltFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
The path to a file containing the password salt to use for Wakapi.
'';
};
smtpPassword = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The password used for the smtp mailed to used by Wakapi.
'';
};
smtpPasswordFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
The path to a file containing the password for the smtp mailer used by Wakapi.
'';
};
database = {
createLocally = mkEnableOption ''
automatic database configuration.
::: {.note}
Only PostgreSQL is supported for the time being.
:::
'';
dialect = mkOption {
type = types.nullOr (
types.enum [
"postgres"
"sqlite3"
"mysql"
"cockroach"
"mssql"
]
);
default = cfg.settings.db.dialect or null; # handle case where dialect is not set
defaultText = ''
Database dialect from settings if {option}`services.wakatime.settings.db.dialect`
is set, or `null` otherwise.
'';
description = ''
The database type to use for Wakapi.
'';
};
name = mkOption {
type = types.str;
default = cfg.settings.db.name or "wakapi";
defaultText = ''
Database name from settings if {option}`services.wakatime.settings.db.name`
is set, or "wakapi" otherwise.
'';
description = ''
The name of the database to use for Wakapi.
'';
};
user = mkOption {
type = types.str;
default = cfg.settings.db.user or "wakapi";
defaultText = ''
User from settings if {option}`services.wakatime.settings.db.user`
is set, or "wakapi" otherwise.
'';
description = ''
The name of the user to use for Wakapi.
'';
};
};
};
config = mkIf cfg.enable {
systemd.services.wakapi = {
description = "Wakapi (self-hosted WakaTime-compatible backend)";
wants = [
"network-online.target"
] ++ optional (cfg.database.dialect == "postgres") "postgresql.target";
after = [
"network-online.target"
] ++ optional (cfg.database.dialect == "postgres") "postgresql.target";
wantedBy = [ "multi-user.target" ];
script = ''
exec ${getExe cfg.package} -config ${settingsFile}
'';
serviceConfig = {
Environment = mkMerge [
(mkIf (cfg.passwordSalt != null) "WAKAPI_PASSWORD_SALT=${cfg.passwordSalt}")
(mkIf (cfg.smtpPassword != null) "WAKAPI_MAIL_SMTP_PASS=${cfg.smtpPassword}")
];
EnvironmentFile =
(lib.optional (cfg.passwordSaltFile != null) cfg.passwordSaltFile)
++ (lib.optional (cfg.smtpPasswordFile != null) cfg.smtpPasswordFile);
User = config.users.users.wakapi.name;
Group = config.users.users.wakapi.group;
DynamicUser = true;
PrivateTmp = true;
PrivateUsers = true;
PrivateDevices = true;
ProtectHome = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
NoNewPrivileges = true;
ProtectProc = "invisible";
ProtectSystem = "full";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
WorkingDirectory = cfg.stateDir;
RuntimeDirectory = "wakapi";
StateDirectory = "wakapi";
StateDirectoryMode = "0700";
Restart = "always";
};
};
services.wakapi.settings = {
env = lib.mkDefault "production";
};
assertions = [
{
assertion = cfg.passwordSalt != null || cfg.passwordSaltFile != null;
message = "Either `services.wakapi.passwordSalt` or `services.wakapi.passwordSaltFile` must be set.";
}
{
assertion = !(cfg.passwordSalt != null && cfg.passwordSaltFile != null);
message = "Both `services.wakapi.passwordSalt` and `services.wakapi.passwordSaltFile` should not be set at the same time.";
}
{
assertion = !(cfg.smtpPassword != null && cfg.smtpPasswordFile != null);
message = "Both `services.wakapi.smtpPassword` and `services.wakapi.smtpPasswordFile` should not be set at the same time.";
}
{
assertion = cfg.database.createLocally -> cfg.settings.db.dialect != null;
message = "`services.wakapi.database.createLocally` is true, but a database dialect is not set!";
}
];
warnings = [
(lib.optionalString (cfg.database.createLocally && cfg.settings.db.dialect != "postgres") ''
You have enabled automatic database configuration, but the database dialect is not set to "posgres".
The Wakapi module only supports PostgreSQL. Please set `services.wakapi.database.createLocally`
to `false`, or switch to "postgres" as your database dialect.
'')
];
users = {
users.wakapi = {
group = "wakapi";
createHome = false;
isSystemUser = true;
};
groups.wakapi = { };
};
services.postgresql = mkIf (cfg.database.createLocally && cfg.database.dialect == "postgres") {
enable = true;
ensureDatabases = singleton cfg.database.name;
ensureUsers = singleton {
name = cfg.settings.db.user;
ensureDBOwnership = true;
};
authentication = ''
host ${cfg.settings.db.name} ${cfg.settings.db.user} 127.0.0.1/32 trust
'';
};
};
meta.maintainers = with lib.maintainers; [
isabelroses
NotAShelf
];
}