From b5003e741eb10921eb90dc8ded53c1a25715cadc Mon Sep 17 00:00:00 2001 From: Ratchanan Srirattanamet Date: Sat, 2 Nov 2024 21:54:03 +0000 Subject: [PATCH 1/4] maintainers: add @peat-psuwit to the maintainer list --- maintainers/maintainer-list.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index 30b6e40d3c19..7f7018976366 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -16881,6 +16881,12 @@ githubId = 943430; name = "David Hagege"; }; + peat-psuwit = { + name = "Ratchanan Srirattanamet"; + email = "peat@peat-network.xyz"; + github = "peat-psuwit"; + githubId = 6771175; + }; pedohorse = { github = "pedohorse"; githubId = 13556996; From 0d946aac1ee6aa2b3d2d527fae76c15edc6888c6 Mon Sep 17 00:00:00 2001 From: Ratchanan Srirattanamet Date: Fri, 15 Dec 2023 04:12:09 +0700 Subject: [PATCH 2/4] kimai: init at 2.24.0 --- pkgs/by-name/ki/kimai/package.nix | 65 +++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 pkgs/by-name/ki/kimai/package.nix diff --git a/pkgs/by-name/ki/kimai/package.nix b/pkgs/by-name/ki/kimai/package.nix new file mode 100644 index 000000000000..3e994f793c29 --- /dev/null +++ b/pkgs/by-name/ki/kimai/package.nix @@ -0,0 +1,65 @@ +{ + php, + fetchFromGitHub, + lib, +}: + +php.buildComposerProject (finalAttrs: { + pname = "kimai"; + version = "2.24.0"; + + src = fetchFromGitHub { + owner = "kimai"; + repo = "kimai"; + rev = finalAttrs.version; + hash = "sha256-C6i263sAfZwZELUIcZh/OmXZqgCKifjPYBafnH0wMC4="; + }; + + php = php.buildEnv { + extensions = ( + { enabled, all }: + enabled + ++ (with all; [ + gd + intl + mbstring + pdo + tokenizer + xml + xsl + zip + ]) + ); + + # Asset building and (later) cache building process requires a little bit + # more memory. + extraConfig = '' + memory_limit=384M + ''; + }; + + vendorHash = "sha256-3y3FfSUuDyBGP1dsuzDORDqFNj3jYix5ArM+2FS4gn4="; + + composerNoPlugins = false; + composerNoScripts = false; + + postInstall = '' + # Make available the console utility, as Kimai doesn't list this in + # composer.json. + mkdir -p "$out"/share/php/kimai "$out"/bin + ln -s "$out"/share/php/kimai/bin/console "$out"/bin/console + ''; + + meta = { + description = "Web-based multi-user time-tracking application"; + homepage = "https://www.kimai.org/"; + license = lib.licenses.agpl3Plus; + longDescription = " + Kimai is a web-based multi-user time-tracking application. Works great for + everyone: freelancers, companies, organizations - everyone can track their + times, generate reports, create invoices and do so much more. + "; + maintainers = with lib.maintainers; [ peat-psuwit ]; + platforms = lib.platforms.all; + }; +}) From 29e586e508e60e50a0dad49a36cecca968fe4728 Mon Sep 17 00:00:00 2001 From: Ratchanan Srirattanamet Date: Sat, 2 Nov 2024 05:50:17 +0700 Subject: [PATCH 3/4] nixos/kimai: init module & add test --- nixos/modules/module-list.nix | 1 + nixos/modules/services/web-apps/kimai.nix | 403 ++++++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/kimai.nix | 23 ++ pkgs/by-name/ki/kimai/package.nix | 5 + 5 files changed, 433 insertions(+) create mode 100644 nixos/modules/services/web-apps/kimai.nix create mode 100644 nixos/tests/kimai.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c991a7ec2502..5b85b0c5fad7 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1459,6 +1459,7 @@ ./services/web-apps/kasmweb/default.nix ./services/web-apps/kavita.nix ./services/web-apps/keycloak.nix + ./services/web-apps/kimai.nix ./services/web-apps/komga.nix ./services/web-apps/lanraragi.nix ./services/web-apps/lemmy.nix diff --git a/nixos/modules/services/web-apps/kimai.nix b/nixos/modules/services/web-apps/kimai.nix new file mode 100644 index 000000000000..06cb547b613a --- /dev/null +++ b/nixos/modules/services/web-apps/kimai.nix @@ -0,0 +1,403 @@ +{ + config, + pkgs, + lib, + ... +}: + +with lib; + +let + cfg = config.services.kimai; + eachSite = cfg.sites; + user = "kimai"; + webserver = config.services.${cfg.webserver}; + stateDir = hostName: "/var/lib/kimai/${hostName}"; + + pkg = + hostName: cfg: + pkgs.stdenv.mkDerivation rec { + pname = "kimai-${hostName}"; + src = cfg.package; + version = src.version; + + installPhase = '' + mkdir -p $out + cp -r * $out/ + + # Symlink .env file. This will be dynamically created at the service + # startup. + ln -sf ${stateDir hostName}/.env $out/share/php/kimai/.env + + # Symlink the var/ folder + # TODO: we may have to symlink individual folders if we want to also + # manage plugins from Nix. + rm -rf $out/share/php/kimai/var + ln -s ${stateDir hostName} $out/share/php/kimai/var + + # Symlink local.yaml. + ln -s ${kimaiConfig hostName cfg} $out/share/php/kimai/config/packages/local.yaml + ''; + }; + + kimaiConfig = + hostName: cfg: + pkgs.writeTextFile { + name = "kimai-config-${hostName}.yaml"; + text = generators.toYAML { } cfg.settings; + }; + + siteOpts = + { + lib, + name, + config, + ... + }: + { + options = { + package = mkPackageOption pkgs "kimai" { }; + + database = { + host = mkOption { + type = types.str; + default = "localhost"; + description = "Database host address."; + }; + + port = mkOption { + type = types.port; + default = 3306; + description = "Database host port."; + }; + + name = mkOption { + type = types.str; + default = "kimai"; + description = "Database name."; + }; + + user = mkOption { + type = types.str; + default = "kimai"; + description = "Database user."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/kimai-dbpassword"; + description = '' + A file containing the password corresponding to + {option}`database.user`. + ''; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = null; + defaultText = literalExpression "/run/mysqld/mysqld.sock"; + description = "Path to the unix socket file to use for authentication."; + }; + + charset = mkOption { + type = types.str; + default = "utf8mb4"; + description = "Database charset."; + }; + + serverVersion = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + MySQL *exact* version string. Not used if `createdLocally` is set, + but must be set otherwise. See + https://www.kimai.org/documentation/installation.html#column-table_name-in-where-clause-is-ambiguous + for how to set this value, especially if you're using MariaDB. + ''; + }; + + createLocally = mkOption { + type = types.bool; + default = true; + description = "Create the database and database user locally."; + }; + }; + + poolConfig = mkOption { + type = + with types; + attrsOf (oneOf [ + str + int + 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 = '' + Options for the Kimai PHP pool. See the documentation on `php-fpm.conf` + for details on configuration directives. + ''; + }; + + settings = mkOption { + type = types.attrsOf types.anything; + default = { }; + description = '' + Structural Kimai's local.yaml configuration. + Refer to + for details. + ''; + example = literalExpression '' + { + kimai = { + timesheet = { + rounding = { + default = { + begin = 15; + end = 15; + }; + }; + }; + }; + } + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/secrets/kimai.env"; + description = '' + Securely pass environment variabels to Kimai. This can be used to + set other environement variables such as MAILER_URL. + ''; + }; + }; + }; +in +{ + # interface + options = { + services.kimai = { + sites = mkOption { + type = types.attrsOf (types.submodule siteOpts); + default = { }; + description = "Specification of one or more Kimai sites to serve"; + }; + + webserver = mkOption { + type = types.enum [ "nginx" ]; + default = "nginx"; + description = '' + The webserver to configure for the PHP frontend. + + At the moment, only `nginx` is supported. PRs are welcome for support + for other web servers. + ''; + }; + }; + }; + + # implementation + config = mkIf (eachSite != { }) (mkMerge [ + { + + assertions = + (mapAttrsToList (hostName: cfg: { + assertion = cfg.database.createLocally -> cfg.database.user == user; + message = ''services.kimai.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned''; + }) eachSite) + ++ (mapAttrsToList (hostName: cfg: { + assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; + message = ''services.kimai.sites."${hostName}".database.passwordFile cannot be specified if services.kimai.sites."${hostName}".database.createLocally is set to true.''; + }) eachSite) + ++ (mapAttrsToList (hostName: cfg: { + assertion = !cfg.database.createLocally -> cfg.database.serverVersion != null; + message = ''services.kimai.sites."${hostName}".database.serverVersion must be specified if services.kimai.sites."${hostName}".database.createLocally is set to false.''; + }) eachSite); + + services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite; + ensureUsers = mapAttrsToList (hostName: cfg: { + name = cfg.database.user; + ensurePermissions = { + "${cfg.database.name}.*" = "ALL PRIVILEGES"; + }; + }) eachSite; + }; + + services.phpfpm.pools = mapAttrs' ( + hostName: cfg: + (nameValuePair "kimai-${hostName}" { + inherit user; + group = webserver.group; + settings = { + "listen.owner" = webserver.user; + "listen.group" = webserver.group; + } // cfg.poolConfig; + }) + ) eachSite; + + } + + { + systemd.tmpfiles.rules = flatten ( + mapAttrsToList (hostName: cfg: [ + "d '${stateDir hostName}' 0770 ${user} ${webserver.group} - -" + ]) eachSite + ); + + systemd.services = mkMerge [ + (mapAttrs' ( + hostName: cfg: + (nameValuePair "kimai-init-${hostName}" { + wantedBy = [ "multi-user.target" ]; + before = [ "phpfpm-kimai-${hostName}.service" ]; + after = optional cfg.database.createLocally "mysql.service"; + script = + let + envFile = "${stateDir hostName}/.env"; + appSecretFile = "${stateDir hostName}/.app_secret"; + mysql = "${config.services.mysql.package}/bin/mysql"; + + dbUser = cfg.database.user; + dbPwd = if cfg.database.passwordFile != null then ":$(cat ${cfg.database.passwordFile})" else ""; + dbHost = cfg.database.host; + dbPort = toString cfg.database.port; + dbName = cfg.database.name; + dbCharset = cfg.database.charset; + dbUnixSocket = if cfg.database.socket != null then "&unixSocket=${cfg.database.socket}" else ""; + # Note: serverVersion is a shell variable. See below. + dbUri = + "mysql://${dbUser}${dbPwd}@${dbHost}:${dbPort}" + + "/${dbName}?charset=${dbCharset}" + + "&serverVersion=$serverVersion${dbUnixSocket}"; + in + '' + set -eu + + serverVersion=${ + if !cfg.database.createLocally then + cfg.database.serverVersion + else + # Obtain MySQL version string dynamically from the running + # instance. Doctrine ORM's doc said it should be possible to + # autodetect this, however Kimai's doc insists that it has to + # be set. + # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#mysql + # https://stackoverflow.com/q/9558867 + "$(${mysql} --silent --skip-column-names --execute 'SELECT VERSION();')" + } + + # Create .env file containing DATABASE_URL and other default + # variables. Set umask to make sure .env is not readable by + # unrelated users. + oldUmask=$(umask) + umask 177 + + if ! [ -e ${appSecretFile} ]; then + tr -dc A-Za-z0-9 ${appSecretFile} + fi + + cat >${envFile} < Date: Sat, 2 Nov 2024 18:03:29 +0000 Subject: [PATCH 4/4] nixos/doc/rl: mention added module Kimai --- nixos/doc/manual/release-notes/rl-2505.section.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index 29af283010c9..4d204f92a792 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -12,7 +12,7 @@ -- Create the first release note entry in this section! +- [Kimai](https://www.kimai.org/), a web-based multi-user time-tracking application. Available as [services.kimai](option.html#opt-services.kimai).