1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7
8let
9 cfg = config.services.anuko-time-tracker;
10 configFile =
11 let
12 smtpPassword =
13 if cfg.settings.email.smtpPasswordFile == null then
14 "''"
15 else
16 "trim(file_get_contents('${cfg.settings.email.smtpPasswordFile}'))";
17
18 in
19 pkgs.writeText "config.php" ''
20 <?php
21 // Set include path for PEAR and its modules, which we include in the distribution.
22 // Updated for the correct location in the nix store.
23 set_include_path('${cfg.package}/WEB-INF/lib/pear' . PATH_SEPARATOR . get_include_path());
24 define('DSN', 'mysqli://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}?charset=utf8mb4');
25 define('MULTIORG_MODE', ${lib.boolToString cfg.settings.multiorgMode});
26 define('EMAIL_REQUIRED', ${lib.boolToString cfg.settings.emailRequired});
27 define('WEEKEND_START_DAY', ${toString cfg.settings.weekendStartDay});
28 define('FORUM_LINK', '${cfg.settings.forumLink}');
29 define('HELP_LINK', '${cfg.settings.helpLink}');
30 define('SENDER', '${cfg.settings.email.sender}');
31 define('MAIL_MODE', '${cfg.settings.email.mode}');
32 define('MAIL_SMTP_HOST', '${toString cfg.settings.email.smtpHost}');
33 define('MAIL_SMTP_PORT', '${toString cfg.settings.email.smtpPort}');
34 define('MAIL_SMTP_USER', '${cfg.settings.email.smtpUser}');
35 define('MAIL_SMTP_PASSWORD', ${smtpPassword});
36 define('MAIL_SMTP_AUTH', ${lib.boolToString cfg.settings.email.smtpAuth});
37 define('MAIL_SMTP_DEBUG', ${lib.boolToString cfg.settings.email.smtpDebug});
38 define('DEFAULT_CSS', 'default.css');
39 define('RTL_CSS', 'rtl.css'); // For right to left languages.
40 define('LANG_DEFAULT', '${cfg.settings.defaultLanguage}');
41 define('CURRENCY_DEFAULT', '${cfg.settings.defaultCurrency}');
42 define('EXPORT_DECIMAL_DURATION', ${lib.boolToString cfg.settings.exportDecimalDuration});
43 define('REPORT_FOOTER', ${lib.boolToString cfg.settings.reportFooter});
44 define('AUTH_MODULE', 'db');
45 '';
46 package = pkgs.stdenv.mkDerivation rec {
47 pname = "anuko-time-tracker";
48 inherit (src) version;
49 src = cfg.package;
50 installPhase = ''
51 mkdir -p $out
52 cp -r * $out/
53
54 # Link config file
55 ln -s ${configFile} $out/WEB-INF/config.php
56
57 # Link writable templates_c directory
58 rm -rf $out/WEB-INF/templates_c
59 ln -s ${cfg.dataDir}/templates_c $out/WEB-INF/templates_c
60
61 # Remove unsafe dbinstall.php
62 rm -f $out/dbinstall.php
63 '';
64 };
65in
66{
67 options.services.anuko-time-tracker = {
68 enable = lib.mkEnableOption "Anuko Time Tracker";
69
70 package = lib.mkPackageOption pkgs "anuko-time-tracker" { };
71
72 database = {
73 createLocally = lib.mkOption {
74 type = lib.types.bool;
75 default = true;
76 description = "Create the database and database user locally.";
77 };
78
79 host = lib.mkOption {
80 type = lib.types.str;
81 description = "Database host.";
82 default = "localhost";
83 };
84
85 name = lib.mkOption {
86 type = lib.types.str;
87 description = "Database name.";
88 default = "anuko_time_tracker";
89 };
90
91 user = lib.mkOption {
92 type = lib.types.str;
93 description = "Database username.";
94 default = "anuko_time_tracker";
95 };
96
97 passwordFile = lib.mkOption {
98 type = lib.types.nullOr lib.types.str;
99 description = "Database user password file.";
100 default = null;
101 };
102 };
103
104 poolConfig = lib.mkOption {
105 type = lib.types.attrsOf (
106 lib.types.oneOf [
107 lib.types.str
108 lib.types.int
109 lib.types.bool
110 ]
111 );
112 default = {
113 "pm" = "dynamic";
114 "pm.max_children" = 32;
115 "pm.start_servers" = 2;
116 "pm.min_spare_servers" = 2;
117 "pm.max_spare_servers" = 4;
118 "pm.max_requests" = 500;
119 };
120 description = ''
121 Options for Anuko Time Tracker's PHP-FPM pool.
122 '';
123 };
124
125 hostname = lib.mkOption {
126 type = lib.types.str;
127 default =
128 if config.networking.domain != null then config.networking.fqdn else config.networking.hostName;
129 defaultText = lib.literalExpression "config.networking.fqdn";
130 example = "anuko.example.com";
131 description = ''
132 The hostname to serve Anuko Time Tracker on.
133 '';
134 };
135
136 nginx = lib.mkOption {
137 type = lib.types.submodule (
138 lib.recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) { }
139 );
140 default = { };
141 example = lib.literalExpression ''
142 {
143 serverAliases = [
144 "anuko.''${config.networking.domain}"
145 ];
146
147 # To enable encryption and let let's encrypt take care of certificate
148 forceSSL = true;
149 enableACME = true;
150 }
151 '';
152 description = ''
153 With this option, you can customize the Nginx virtualHost settings.
154 '';
155 };
156
157 dataDir = lib.mkOption {
158 type = lib.types.str;
159 default = "/var/lib/anuko-time-tracker";
160 description = "Default data folder for Anuko Time Tracker.";
161 example = "/mnt/anuko-time-tracker";
162 };
163
164 user = lib.mkOption {
165 type = lib.types.str;
166 default = "anuko_time_tracker";
167 description = "User under which Anuko Time Tracker runs.";
168 };
169
170 settings = {
171 multiorgMode = lib.mkOption {
172 type = lib.types.bool;
173 default = true;
174 description = ''
175 Defines whether users see the Register option in the menu of Time Tracker that allows them
176 to self-register and create new organizations (top groups).
177 '';
178 };
179
180 emailRequired = lib.mkOption {
181 type = lib.types.bool;
182 default = false;
183 description = "Defines whether an email is required for new registrations.";
184 };
185
186 weekendStartDay = lib.mkOption {
187 type = lib.types.int;
188 default = 6;
189 description = ''
190 This option defines which days are highlighted with weekend color.
191 6 means Saturday. For Saudi Arabia, etc. set it to 4 for Thursday and Friday to be
192 weekend days.
193 '';
194 };
195
196 forumLink = lib.mkOption {
197 type = lib.types.str;
198 description = "Forum link from the main menu.";
199 default = "https://www.anuko.com/forum/viewforum.php?f=4";
200 };
201
202 helpLink = lib.mkOption {
203 type = lib.types.str;
204 description = "Help link from the main menu.";
205 default = "https://www.anuko.com/time-tracker/user-guide/index.htm";
206 };
207
208 email = {
209 sender = lib.mkOption {
210 type = lib.types.str;
211 description = "Default sender for mail.";
212 default = "Anuko Time Tracker <bounces@example.com>";
213 };
214
215 mode = lib.mkOption {
216 type = lib.types.str;
217 description = "Mail sending mode. Can be 'mail' or 'smtp'.";
218 default = "smtp";
219 };
220
221 smtpHost = lib.mkOption {
222 type = lib.types.str;
223 description = "MTA hostname.";
224 default = "localhost";
225 };
226
227 smtpPort = lib.mkOption {
228 type = lib.types.int;
229 description = "MTA port.";
230 default = 25;
231 };
232
233 smtpUser = lib.mkOption {
234 type = lib.types.str;
235 description = "MTA authentication username.";
236 default = "";
237 };
238
239 smtpAuth = lib.mkOption {
240 type = lib.types.bool;
241 default = false;
242 description = "MTA requires authentication.";
243 };
244
245 smtpPasswordFile = lib.mkOption {
246 type = lib.types.nullOr lib.types.path;
247 default = null;
248 example = "/var/lib/anuko-time-tracker/secrets/smtp-password";
249 description = ''
250 Path to file containing the MTA authentication password.
251 '';
252 };
253
254 smtpDebug = lib.mkOption {
255 type = lib.types.bool;
256 default = false;
257 description = "Debug mail sending.";
258 };
259 };
260
261 defaultLanguage = lib.mkOption {
262 type = lib.types.str;
263 description = ''
264 Defines Anuko Time Tracker default language. It is used on Time Tracker login page.
265 After login, a language set for user group is used.
266 Empty string means the language is defined by user browser.
267 '';
268 default = "";
269 example = "nl";
270 };
271
272 defaultCurrency = lib.mkOption {
273 type = lib.types.str;
274 description = ''
275 Defines a default currency symbol for new groups.
276 Use €, £, a more specific dollar like US$, CAD, etc.
277 '';
278 default = "$";
279 example = "€";
280 };
281
282 exportDecimalDuration = lib.mkOption {
283 type = lib.types.bool;
284 default = true;
285 description = ''
286 Defines whether time duration values are decimal in CSV and XML data
287 exports (1.25 vs 1:15).
288 '';
289 };
290
291 reportFooter = lib.mkOption {
292 type = lib.types.bool;
293 default = true;
294 description = "Defines whether to use a footer on reports.";
295 };
296 };
297 };
298
299 config = lib.mkIf cfg.enable {
300
301 assertions = [
302 {
303 assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
304 message = ''
305 <option>services.anuko-time-tracker.database.passwordFile</option> cannot be specified if
306 <option>services.anuko-time-tracker.database.createLocally</option> is set to true.
307 '';
308 }
309 {
310 assertion = cfg.settings.email.smtpAuth -> (cfg.settings.email.smtpPasswordFile != null);
311 message = ''
312 <option>services.anuko-time-tracker.settings.email.smtpPasswordFile</option> needs to be set if
313 <option>services.anuko-time-tracker.settings.email.smtpAuth</option> is enabled.
314 '';
315 }
316 ];
317
318 services.phpfpm = {
319 pools.anuko-time-tracker = {
320 inherit (cfg) user;
321 group = config.services.nginx.group;
322 settings = {
323 "listen.owner" = config.services.nginx.user;
324 "listen.group" = config.services.nginx.group;
325 } // cfg.poolConfig;
326 };
327 };
328
329 services.nginx = {
330 enable = lib.mkDefault true;
331 recommendedTlsSettings = true;
332 recommendedOptimisation = true;
333 recommendedGzipSettings = true;
334 virtualHosts."${cfg.hostname}" = lib.mkMerge [
335 cfg.nginx
336 {
337 root = lib.mkForce "${package}";
338 locations = {
339 "/".index = "index.php";
340 "~ [^/]\\.php(/|$)" = {
341 extraConfig = ''
342 fastcgi_split_path_info ^(.+?\.php)(/.*)$;
343 fastcgi_pass unix:${config.services.phpfpm.pools.anuko-time-tracker.socket};
344 '';
345 };
346 };
347 }
348 ];
349 };
350
351 services.mysql = lib.mkIf cfg.database.createLocally {
352 enable = lib.mkDefault true;
353 package = lib.mkDefault pkgs.mariadb;
354 ensureDatabases = [ cfg.database.name ];
355 ensureUsers = [
356 {
357 name = cfg.database.user;
358 ensurePermissions = {
359 "${cfg.database.name}.*" = "ALL PRIVILEGES";
360 };
361 }
362 ];
363 };
364
365 systemd = {
366 services = {
367 anuko-time-tracker-setup-database = lib.mkIf cfg.database.createLocally {
368 description = "Set up Anuko Time Tracker database";
369 serviceConfig = {
370 Type = "oneshot";
371 RemainAfterExit = true;
372 };
373 wantedBy = [ "phpfpm-anuko-time-tracker.service" ];
374 after = [ "mysql.service" ];
375 script =
376 let
377 mysql = "${config.services.mysql.package}/bin/mysql";
378 in
379 ''
380 if [ ! -f ${cfg.dataDir}/.dbexists ]; then
381 # Load database schema provided with package
382 ${mysql} ${cfg.database.name} < ${cfg.package}/mysql.sql
383
384 touch ${cfg.dataDir}/.dbexists
385 fi
386 '';
387 };
388 };
389 tmpfiles.rules = [
390 "d ${cfg.dataDir} 0750 ${cfg.user} ${config.services.nginx.group} -"
391 "d ${cfg.dataDir}/templates_c 0750 ${cfg.user} ${config.services.nginx.group} -"
392 ];
393 };
394
395 users.users."${cfg.user}" = {
396 isSystemUser = true;
397 group = config.services.nginx.group;
398 };
399 };
400
401 meta.maintainers = with lib.maintainers; [ michaelshmitty ];
402}