about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorMichael Smith <shmitty@protonmail.com>2023-06-27 12:30:06 +0200
committerMichael Smith <shmitty@protonmail.com>2023-06-27 12:30:33 +0200
commit7532dbaa32f661f8f8743e24629516f853e39f9e (patch)
tree725f3443bb30aa4c73b643877c505672de2c2de6 /nixos
parent30874bced705e3679e5ab044895e51c89c6921ba (diff)
nixos/anuko-time-tracker: init
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/release-notes/rl-2311.section.md2
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/web-apps/anuko-time-tracker.nix354
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/anuko-time-tracker.nix17
5 files changed, 375 insertions, 0 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md
index 00d52376b18a8..70613c392a371 100644
--- a/nixos/doc/manual/release-notes/rl-2311.section.md
+++ b/nixos/doc/manual/release-notes/rl-2311.section.md
@@ -16,6 +16,8 @@
 
 - [GoToSocial](https://gotosocial.org/), an ActivityPub social network server, written in Golang. Available as [services.gotosocial](#opt-services.gotosocial.enable).
 
+- [Anuko Time Tracker](https://github.com/anuko/timetracker), a simple, easy to use, open source time tracking system. Available as [services.anuko-time-tracker](#opt-services.anuko-time-tracker.enable).
+
 - [sitespeed-io](https://sitespeed.io), a tool that can generate metrics (timings, diagnostics) for websites. Available as [services.sitespeed-io](#opt-services.sitespeed-io.enable).
 
 - [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services.
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 43fcc68ded42a..ebf52804d4ec7 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1168,6 +1168,7 @@
   ./services/wayland/cage.nix
   ./services/web-apps/akkoma.nix
   ./services/web-apps/alps.nix
+  ./services/web-apps/anuko-time-tracker.nix
   ./services/web-apps/atlassian/confluence.nix
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
diff --git a/nixos/modules/services/web-apps/anuko-time-tracker.nix b/nixos/modules/services/web-apps/anuko-time-tracker.nix
new file mode 100644
index 0000000000000..c50a0328e34d8
--- /dev/null
+++ b/nixos/modules/services/web-apps/anuko-time-tracker.nix
@@ -0,0 +1,354 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.anuko-time-tracker;
+  configFile = let
+    smtpPassword = if cfg.settings.email.smtpPasswordFile == null
+                   then "''"
+                   else "trim(file_get_contents('${cfg.settings.email.smtpPasswordFile}'))";
+
+  in pkgs.writeText "config.php" ''
+    <?php
+    // Set include path for PEAR and its modules, which we include in the distribution.
+    // Updated for the correct location in the nix store.
+    set_include_path('${cfg.package}/WEB-INF/lib/pear' . PATH_SEPARATOR . get_include_path());
+    define('DSN', 'mysqli://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}?charset=utf8mb4');
+    define('MULTIORG_MODE', ${lib.boolToString cfg.settings.multiorgMode});
+    define('EMAIL_REQUIRED', ${lib.boolToString cfg.settings.emailRequired});
+    define('WEEKEND_START_DAY', ${toString cfg.settings.weekendStartDay});
+    define('FORUM_LINK', '${cfg.settings.forumLink}');
+    define('HELP_LINK', '${cfg.settings.helpLink}');
+    define('SENDER', '${cfg.settings.email.sender}');
+    define('MAIL_MODE', '${cfg.settings.email.mode}');
+    define('MAIL_SMTP_HOST', '${toString cfg.settings.email.smtpHost}');
+    define('MAIL_SMTP_PORT', '${toString cfg.settings.email.smtpPort}');
+    define('MAIL_SMTP_USER', '${cfg.settings.email.smtpUser}');
+    define('MAIL_SMTP_PASSWORD', ${smtpPassword});
+    define('MAIL_SMTP_AUTH', ${lib.boolToString cfg.settings.email.smtpAuth});
+    define('MAIL_SMTP_DEBUG', ${lib.boolToString cfg.settings.email.smtpDebug});
+    define('DEFAULT_CSS', 'default.css');
+    define('RTL_CSS', 'rtl.css'); // For right to left languages.
+    define('LANG_DEFAULT', '${cfg.settings.defaultLanguage}');
+    define('CURRENCY_DEFAULT', '${cfg.settings.defaultCurrency}');
+    define('EXPORT_DECIMAL_DURATION', ${lib.boolToString cfg.settings.exportDecimalDuration});
+    define('REPORT_FOOTER', ${lib.boolToString cfg.settings.reportFooter});
+    define('AUTH_MODULE', 'db');
+  '';
+  package = pkgs.stdenv.mkDerivation rec {
+    pname = "anuko-time-tracker";
+    inherit (src) version;
+    src = cfg.package;
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+
+      ln -s ${configFile} $out/WEB-INF/config.php
+
+      # Link writable templates_c directory
+      rm -rf $out/WEB-INF/templates_c
+      ln -s ${cfg.dataDir}/templates_c $out/WEB-INF/templates_c
+
+      # ln -fs ${cfg.dataDir}/templates_c $out/WEB-INF/templates_c
+    '';
+  };
+in
+{
+  options.services.anuko-time-tracker = {
+    enable = lib.mkEnableOption (lib.mdDoc "Anuko Time Tracker");
+
+    package = lib.mkPackageOptionMD pkgs "anuko-time-tracker" {};
+
+    database = {
+      createLocally = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc "Create the database and database user locally.";
+      };
+
+      host = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc "Database host.";
+        default = "localhost";
+      };
+
+      name = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc "Database name.";
+        default = "anuko_time_tracker";
+      };
+
+      user = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc "Database username.";
+        default = "anuko_time_tracker";
+      };
+
+      passwordFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        description = lib.mdDoc "Database user password file.";
+        default = null;
+      };
+    };
+
+    poolConfig = lib.mkOption {
+      type = lib.types.attrsOf (lib.types.oneOf [ lib.types.str lib.types.int lib.types.bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = lib.mdDoc ''
+        Options for Anuko Time Tracker's PHP-FPM pool.
+      '';
+    };
+
+    dataDir = lib.mkOption {
+      type = lib.types.str;
+      default = "/var/lib/anuko-time-tracker";
+      description = lib.mdDoc "Default data folder for Anuko Time Tracker.";
+      example = "/mnt/anuko-time-tracker";
+    };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "anuko_time_tracker";
+      description = lib.mdDoc "User under which Anuko Time Tracker runs.";
+    };
+
+    virtualHost = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = "localhost";
+      description = lib.mdDoc ''
+        Name of the nginx virtualhost to use and setup. If null, do not setup
+        any virtualhost.
+      '';
+    };
+
+    settings = {
+      multiorgMode = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Defines whether users see the Register option in the menu of Time Tracker that allows them
+          to self-register and create new organizations (top groups).
+        '';
+      };
+
+      emailRequired = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc "Defines whether an email is required for new registrations.";
+      };
+
+      weekendStartDay = lib.mkOption {
+        type = lib.types.int;
+        default = 6;
+        description = lib.mdDoc ''
+          This option defines which days are highlighted with weekend color.
+          6 means Saturday. For Saudi Arabia, etc. set it to 4 for Thursday and Friday to be
+          weekend days.
+        '';
+      };
+
+      forumLink = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc "Forum link from the main menu.";
+        default = "https://www.anuko.com/forum/viewforum.php?f=4";
+      };
+
+      helpLink = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc "Help link from the main menu.";
+        default = "https://www.anuko.com/time-tracker/user-guide/index.htm";
+      };
+
+      email = {
+        sender = lib.mkOption {
+          type = lib.types.str;
+          description = lib.mdDoc "Default sender for mail.";
+          default = "Anuko Time Tracker <bounces@example.com>";
+        };
+
+        mode = lib.mkOption {
+          type = lib.types.str;
+          description = lib.mdDoc "Mail sending mode. Can be 'mail' or 'smtp'.";
+          default = "smtp";
+        };
+
+        smtpHost = lib.mkOption {
+          type = lib.types.str;
+          description = lib.mdDoc "MTA hostname.";
+          default = "localhost";
+        };
+
+        smtpPort = lib.mkOption {
+          type = lib.types.int;
+          description = lib.mdDoc "MTA port.";
+          default = 25;
+        };
+
+        smtpUser = lib.mkOption {
+          type = lib.types.str;
+          description = lib.mdDoc "MTA authentication username.";
+          default = "";
+        };
+
+        smtpAuth = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = lib.mdDoc "MTA requires authentication.";
+        };
+
+        smtpPasswordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/var/lib/anuko-time-tracker/secrets/smtp-password";
+          description = lib.mdDoc ''
+            Path to file containing the MTA authentication password.
+          '';
+        };
+
+        smtpDebug = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = lib.mdDoc "Debug mail sending.";
+        };
+      };
+
+      defaultLanguage = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc ''
+          Defines Anuko Time Tracker default language. It is used on Time Tracker login page.
+          After login, a language set for user group is used.
+          Empty string means the language is defined by user browser.
+        '';
+        default = "";
+        example = "nl";
+      };
+
+      defaultCurrency = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc ''
+          Defines a default currency symbol for new groups.
+          Use €, £, a more specific dollar like US$, CAD, etc.
+        '';
+        default = "$";
+        example = "€";
+      };
+
+      exportDecimalDuration = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Defines whether time duration values are decimal in CSV and XML data
+          exports (1.25 vs 1:15).
+        '';
+      };
+
+      reportFooter = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc "Defines whether to use a footer on reports.";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = ''
+          <option>services.anuko-time-tracker.database.passwordFile</option> cannot be specified if
+          <option>services.anuko-time-tracker.database.createLocally</option> is set to true.
+        '';
+      }
+      {
+        assertion = cfg.settings.email.smtpAuth -> (cfg.settings.email.smtpPasswordFile != null);
+        message = ''
+          <option>services.anuko-time-tracker.settings.email.smtpPasswordFile</option> needs to be set if
+          <option>services.anuko-time-tracker.settings.email.smtpAuth</option> is enabled.
+        '';
+      }
+    ];
+
+    services.phpfpm = {
+      pools.anuko-time-tracker = {
+        inherit (cfg) user;
+        group = config.services.nginx.group;
+        settings = {
+          "listen.owner" = config.services.nginx.user;
+          "listen.group" = config.services.nginx.group;
+        } // cfg.poolConfig;
+      };
+    };
+
+    services.nginx = lib.mkIf (cfg.virtualHost != null) {
+      enable = true;
+      virtualHosts = {
+        "${cfg.virtualHost}" = {
+          root = lib.mkForce "${package}";
+          locations."/".index = "index.php";
+          locations."~ [^/]\\.php(/|$)" = {
+            extraConfig = ''
+              fastcgi_split_path_info ^(.+?\.php)(/.*)$;
+              fastcgi_pass unix:${config.services.phpfpm.pools.anuko-time-tracker.socket};
+            '';
+          };
+        };
+      };
+    };
+
+    services.mysql = lib.mkIf cfg.database.createLocally {
+      enable = lib.mkDefault true;
+      package = lib.mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [{
+        name = cfg.database.user;
+        ensurePermissions = {
+          "${cfg.database.name}.*" = "ALL PRIVILEGES";
+        };
+      }];
+    };
+
+    systemd = {
+      services = {
+        anuko-time-tracker-setup-database = lib.mkIf cfg.database.createLocally {
+          description = "Set up Anuko Time Tracker database";
+          serviceConfig = {
+            Type = "oneshot";
+            RemainAfterExit = true;
+          };
+          wantedBy = [ "phpfpm-anuko-time-tracker.service" ];
+          after = [ "mysql.service" ];
+          script =
+            let
+              mysql = "${config.services.mysql.package}/bin/mysql";
+            in
+            ''
+              if [ ! -f ${cfg.dataDir}/.dbexists ]; then
+                # Load database schema provided with package
+                ${mysql} ${cfg.database.name} < ${cfg.package}/mysql.sql
+
+                touch ${cfg.dataDir}/.dbexists
+              fi
+            '';
+        };
+      };
+      tmpfiles.rules = [
+        "d ${cfg.dataDir} 0750 ${cfg.user} ${config.services.nginx.group} -"
+        "d ${cfg.dataDir}/templates_c 0750 ${cfg.user} ${config.services.nginx.group} -"
+      ];
+    };
+
+    users.users."${cfg.user}" = {
+      isSystemUser = true;
+      group = config.services.nginx.group;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ michaelshmitty ];
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 6e9c893c809e4..068e3ce7b2487 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -107,6 +107,7 @@ in {
   allTerminfo = handleTest ./all-terminfo.nix {};
   alps = handleTest ./alps.nix {};
   amazon-init-shell = handleTest ./amazon-init-shell.nix {};
+  anuko-time-tracker = handleTest ./anuko-time-tracker.nix {};
   apcupsd = handleTest ./apcupsd.nix {};
   apfs = runTest ./apfs.nix;
   apparmor = handleTest ./apparmor.nix {};
diff --git a/nixos/tests/anuko-time-tracker.nix b/nixos/tests/anuko-time-tracker.nix
new file mode 100644
index 0000000000000..18c3bf5cf6957
--- /dev/null
+++ b/nixos/tests/anuko-time-tracker.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "anuko-time-tracker";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ michaelshmitty ];
+  };
+  nodes = {
+    machine = {
+      services.anuko-time-tracker.enable = true;
+    };
+  };
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("phpfpm-anuko-time-tracker")
+    machine.wait_for_open_port(80);
+    machine.wait_until_succeeds("curl -s --fail -L http://localhost/time.php | grep 'Anuko Time Tracker'")
+  '';
+})