envision-unwrapped: 0-unstable-2024-10-20 -> 1.1.1 (#360652)
[NixPkgs.git] / nixos / modules / services / web-apps / wakapi.nix
blob982b572fb2fa72ec14a404a1231dec6ca8a88ad1
2   lib,
3   pkgs,
4   config,
5   ...
6 }:
7 let
8   cfg = config.services.wakapi;
10   settingsFormat = pkgs.formats.yaml { };
11   settingsFile = settingsFormat.generate "wakapi-settings" cfg.settings;
13   inherit (lib)
14     getExe
15     mkOption
16     mkEnableOption
17     mkPackageOption
18     types
19     mkIf
20     optional
21     mkMerge
22     singleton
23     ;
26   options.services.wakapi = {
27     enable = mkEnableOption "Wakapi";
28     package = mkPackageOption pkgs "wakapi" { };
30     settings = mkOption {
31       inherit (settingsFormat) type;
32       default = { };
33       description = ''
34         Settings for Wakapi.
36         See [config.default.yml](https://github.com/muety/wakapi/blob/master/config.default.yml) for a list of all possible options.
37       '';
38     };
40     passwordSalt = mkOption {
41       type = types.nullOr types.str;
42       default = null;
43       description = ''
44         The password salt to use for Wakapi.
45       '';
46     };
47     passwordSaltFile = mkOption {
48       type = types.nullOr types.path;
49       default = null;
50       description = ''
51         The path to a file containing the password salt to use for Wakapi.
52       '';
53     };
55     smtpPassword = mkOption {
56       type = types.nullOr types.str;
57       default = null;
58       description = ''
59         The password used for the smtp mailed to used by Wakapi.
60       '';
61     };
62     smtpPasswordFile = mkOption {
63       type = types.nullOr types.path;
64       default = null;
65       description = ''
66         The path to a file containing the password for the smtp mailer used by Wakapi.
67       '';
68     };
70     database = {
71       createLocally = mkEnableOption ''
72         automatic database configuration.
74         ::: {.note}
75         Only PostgreSQL is supported for the time being.
76         :::
77       '';
79       dialect = mkOption {
80         type = types.nullOr (
81           types.enum [
82             "postgres"
83             "sqlite3"
84             "mysql"
85             "cockroach"
86             "mssql"
87           ]
88         );
89         default = cfg.settings.db.dialect or null; # handle case where dialect is not set
90         defaultText = ''
91           Database dialect from settings if {option}`services.wakatime.settings.db.dialect`
92           is set, or `null` otherwise.
93         '';
94         description = ''
95           The database type to use for Wakapi.
96         '';
97       };
99       name = mkOption {
100         type = types.str;
101         default = cfg.settings.db.name or "wakapi";
102         defaultText = ''
103           Database name from settings if {option}`services.wakatime.settings.db.name`
104           is set, or "wakapi" otherwise.
105         '';
106         description = ''
107           The name of the database to use for Wakapi.
108         '';
109       };
111       user = mkOption {
112         type = types.str;
113         default = cfg.settings.db.user or "wakapi";
114         defaultText = ''
115           User from settings if {option}`services.wakatime.settings.db.user`
116           is set, or "wakapi" otherwise.
117         '';
118         description = ''
119           The name of the user to use for Wakapi.
120         '';
121       };
122     };
123   };
125   config = mkIf cfg.enable {
126     systemd.services.wakapi = {
127       description = "Wakapi (self-hosted WakaTime-compatible backend)";
128       wants = [
129         "network-online.target"
130       ] ++ optional (cfg.database.dialect == "postgres") "postgresql.service";
131       after = [
132         "network-online.target"
133       ] ++ optional (cfg.database.dialect == "postgres") "postgresql.service";
134       wantedBy = [ "multi-user.target" ];
136       script = ''
137         exec ${getExe cfg.package} -config ${settingsFile}
138       '';
140       serviceConfig = {
141         Environment = mkMerge [
142           (mkIf (cfg.passwordSalt != null) "WAKAPI_PASSWORD_SALT=${cfg.passwordSalt}")
143           (mkIf (cfg.smtpPassword != null) "WAKAPI_MAIL_SMTP_PASS=${cfg.smtpPassword}")
144         ];
145         EnvironmentFile = [
146           (optional (cfg.passwordSaltFile != null) cfg.passwordSaltFile)
147           (optional (cfg.smtpPasswordFile != null) cfg.smtpPasswordFile)
148         ];
150         User = config.users.users.wakapi.name;
151         Group = config.users.users.wakapi.group;
153         DynamicUser = true;
154         ProtectHome = true;
155         ProtectHostname = true;
156         ProtectKernelLogs = true;
157         ProtectKernelModules = true;
158         ProtectKernelTunables = true;
159         ProtectProc = "invisible";
160         ProtectSystem = "strict";
161         RestrictAddressFamilies = [
162           "AF_INET"
163           "AF_INET6"
164           "AF_UNIX"
165         ];
166         RestrictNamespaces = true;
167         RestrictRealtime = true;
168         RestrictSUIDSGID = true;
169         StateDirectory = "wakapi";
170         StateDirectoryMode = "0700";
171         Restart = "always";
172       };
173     };
175     services.wakapi.settings = {
176       env = lib.mkDefault "production";
177     };
179     assertions = [
180       {
181         assertion = cfg.passwordSalt != null || cfg.passwordSaltFile != null;
182         message = "Either `services.wakapi.passwordSalt` or `services.wakapi.passwordSaltFile` must be set.";
183       }
184       {
185         assertion = !(cfg.passwordSalt != null && cfg.passwordSaltFile != null);
186         message = "Both `services.wakapi.passwordSalt` `services.wakapi.passwordSaltFile` should not be set at the same time.";
187       }
188       {
189         assertion = !(cfg.smtpPassword != null && cfg.smtpPasswordFile != null);
190         message = "Both `services.wakapi.smtpPassword` `services.wakapi.smtpPasswordFile` should not be set at the same time.";
191       }
192       {
193         assertion = cfg.database.createLocally -> cfg.settings.db.dialect != null;
194         message = "`services.wakapi.database.createLocally` is true, but a database dialect is not set!";
195       }
196     ];
198     warnings = [
199       (lib.optionalString (cfg.database.createLocally -> cfg.settings.db.dialect != "postgres") ''
200         You have enabled automatic database configuration, but the database dialect is not set to "posgres".
202         The Wakapi module only supports for PostgreSQL. Please set `services.wakapi.database.createLocally`
203         to `false`, or switch to "postgres" as your database dialect.
204       '')
205     ];
207     users = {
208       users.wakapi = {
209         group = "wakapi";
210         createHome = false;
211         isSystemUser = true;
212       };
213       groups.wakapi = { };
214     };
216     services.postgresql = mkIf (cfg.database.createLocally && cfg.database.dialect == "postgres") {
217       enable = true;
219       ensureDatabases = singleton cfg.database.name;
220       ensureUsers = singleton {
221         name = cfg.settings.db.user;
222         ensureDBOwnership = true;
223       };
225       authentication = ''
226         host ${cfg.settings.db.name} ${cfg.settings.db.user} 127.0.0.1/32 trust
227       '';
228     };
229   };
231   meta.maintainers = with lib.maintainers; [
232     isabelroses
233     NotAShelf
234   ];