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