diff --git a/nixos/modules/services/web-apps/dolibarr.nix b/nixos/modules/services/web-apps/dolibarr.nix index 3f9f853e3b252..136720ef67b4d 100644 --- a/nixos/modules/services/web-apps/dolibarr.nix +++ b/nixos/modules/services/web-apps/dolibarr.nix @@ -1,49 +1,81 @@ -{ config, pkgs, lib, ... }: +{ + config, + pkgs, + lib, + ... +}: let - inherit (lib) any boolToString concatStringsSep isBool isString mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption optionalAttrs types mkPackageOption; + inherit (lib) + any + boolToString + concatStringsSep + isBool + isString + mapAttrsToList + mkDefault + mkEnableOption + mkIf + mkMerge + mkOption + optionalAttrs + types + mkPackageOption + ; package = cfg.package.override { inherit (cfg) stateDir; }; cfg = config.services.dolibarr; vhostCfg = lib.optionalAttrs (cfg.nginx != null) config.services.nginx.virtualHosts."${cfg.domain}"; - mkConfigFile = filename: settings: + mkConfigFile = + filename: settings: let # hack in special logic for secrets so we read them from a separate file avoiding the nix store - secretKeys = [ "force_install_databasepass" "dolibarr_main_db_pass" "dolibarr_main_instance_unique_id" ]; - - toStr = k: v: - if (any (str: k == str) secretKeys) then v - else if isString v then "'${v}'" - else if isBool v then boolToString v - else if v == null then "null" - else toString v - ; + secretKeys = [ + "force_install_databasepass" + "dolibarr_main_db_pass" + "dolibarr_main_instance_unique_id" + ]; + + toStr = + k: v: + if (any (str: k == str) secretKeys) then + v + else if isString v then + "'${v}'" + else if isBool v then + boolToString v + else if v == null then + "null" + else + toString v; in - pkgs.writeText filename '' - for details."; }; nginx = mkOption { - type = types.nullOr (types.submodule ( - lib.recursiveUpdate - (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) - { + type = types.nullOr ( + types.submodule ( + lib.recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) { # enable encryption by default, # as sensitive login and Dolibarr (ERP) data should not be transmitted in clear text. options.forceSSL.default = true; options.enableACME.default = true; } - )); + ) + ); default = null; example = lib.literalExpression '' { @@ -158,17 +196,23 @@ in } ''; description = '' - With this option, you can customize an nginx virtual host which already has sensible defaults for Dolibarr. - Set to {} if you do not need any customization to the virtual host. - If enabled, then by default, the {option}`serverName` is - `''${domain}`, - SSL is active, and certificates are acquired via ACME. - If this is set to null (the default), no nginx virtualHost will be configured. + With this option, you can customize an nginx virtual host which already has sensible defaults for Dolibarr. + Set to {} if you do not need any customization to the virtual host. + If enabled, then by default, the {option}`serverName` is + `''${domain}`, + SSL is active, and certificates are acquired via ACME. + If this is set to null (the default), no nginx virtualHost will be configured. ''; }; poolConfig = mkOption { - type = with types; attrsOf (oneOf [ str int bool ]); + type = + with types; + attrsOf (oneOf [ + str + int + bool + ]); default = { "pm" = "dynamic"; "pm.max_children" = 32; @@ -188,138 +232,144 @@ in config = mkIf cfg.enable (mkMerge [ { - assertions = [ - { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user; - message = "services.dolibarr.database.user must match services.dolibarr.user if the database is to be automatically provisioned"; - } - ]; - - services.dolibarr.settings = { - dolibarr_main_url_root = "https://${cfg.domain}"; - dolibarr_main_document_root = "${package}/htdocs"; - dolibarr_main_url_root_alt = "/custom"; - dolibarr_main_data_root = "${cfg.stateDir}/documents"; - - dolibarr_main_db_host = cfg.database.host; - dolibarr_main_db_port = toString cfg.database.port; - dolibarr_main_db_name = cfg.database.name; - dolibarr_main_db_prefix = "llx_"; - dolibarr_main_db_user = cfg.database.user; - dolibarr_main_db_pass = mkIf (cfg.database.passwordFile != null) '' - file_get_contents("${cfg.database.passwordFile}") - ''; - dolibarr_main_db_type = "mysqli"; - dolibarr_main_db_character_set = mkDefault "utf8"; - dolibarr_main_db_collation = mkDefault "utf8_unicode_ci"; - - # Authentication settings - dolibarr_main_authentication = mkDefault "dolibarr"; - - # Security settings - dolibarr_main_prod = true; - dolibarr_main_force_https = vhostCfg.forceSSL or false; - dolibarr_main_restrict_os_commands = "${pkgs.mariadb}/bin/mysqldump, ${pkgs.mariadb}/bin/mysql"; - dolibarr_nocsrfcheck = false; - dolibarr_main_instance_unique_id = '' - file_get_contents("${cfg.stateDir}/dolibarr_main_instance_unique_id") - ''; - dolibarr_mailing_limit_sendbyweb = false; - }; - - systemd.tmpfiles.rules = [ - "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group}" - "d '${cfg.stateDir}/documents' 0750 ${cfg.user} ${cfg.group}" - "f '${cfg.stateDir}/conf.php' 0660 ${cfg.user} ${cfg.group}" - "L '${cfg.stateDir}/install.forced.php' - ${cfg.user} ${cfg.group} - ${mkConfigFile "install.forced.php" install}" - ]; - - services.mysql = mkIf cfg.database.createLocally { - enable = mkDefault true; - package = mkDefault pkgs.mariadb; - ensureDatabases = [ cfg.database.name ]; - ensureUsers = [ - { name = cfg.database.user; - ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; + assertions = [ + { + assertion = cfg.database.createLocally -> cfg.database.user == cfg.user; + message = "services.dolibarr.database.user must match services.dolibarr.user if the database is to be automatically provisioned"; } ]; - }; - services.nginx.enable = mkIf (cfg.nginx != null) true; - services.nginx.virtualHosts."${cfg.domain}" = mkIf (cfg.nginx != null) (lib.mkMerge [ - cfg.nginx - ({ - root = lib.mkForce "${package}/htdocs"; - locations."/".index = "index.php"; - locations."~ [^/]\\.php(/|$)" = { + services.dolibarr.settings = { + dolibarr_main_url_root = "https://${cfg.domain}"; + dolibarr_main_document_root = "${package}/htdocs"; + dolibarr_main_url_root_alt = "/custom"; + dolibarr_main_data_root = "${cfg.stateDir}/documents"; + + dolibarr_main_db_host = cfg.database.host; + dolibarr_main_db_port = toString cfg.database.port; + dolibarr_main_db_name = cfg.database.name; + dolibarr_main_db_prefix = "llx_"; + dolibarr_main_db_user = cfg.database.user; + dolibarr_main_db_pass = mkIf (cfg.database.passwordFile != null) '' + file_get_contents("${cfg.database.passwordFile}") + ''; + dolibarr_main_db_type = "mysqli"; + dolibarr_main_db_character_set = mkDefault "utf8"; + dolibarr_main_db_collation = mkDefault "utf8_unicode_ci"; + + # Authentication settings + dolibarr_main_authentication = mkDefault "dolibarr"; + + # Security settings + dolibarr_main_prod = true; + dolibarr_main_force_https = vhostCfg.forceSSL or false; + dolibarr_main_restrict_os_commands = "${pkgs.mariadb}/bin/mysqldump, ${pkgs.mariadb}/bin/mysql"; + dolibarr_nocsrfcheck = false; + dolibarr_main_instance_unique_id = '' + file_get_contents("${cfg.stateDir}/dolibarr_main_instance_unique_id") + ''; + dolibarr_mailing_limit_sendbyweb = false; + }; + + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group}" + "d '${cfg.stateDir}/documents' 0750 ${cfg.user} ${cfg.group}" + "f '${cfg.stateDir}/conf.php' 0660 ${cfg.user} ${cfg.group}" + "L '${cfg.stateDir}/install.forced.php' - ${cfg.user} ${cfg.group} - ${mkConfigFile "install.forced.php" install}" + ]; + + services.mysql = mkIf cfg.database.createLocally { + enable = mkDefault true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.name ]; + ensureUsers = [ + { + name = cfg.database.user; + ensurePermissions = { + "${cfg.database.name}.*" = "ALL PRIVILEGES"; + }; + } + ]; + }; + + services.nginx.enable = mkIf (cfg.nginx != null) true; + services.nginx.virtualHosts."${cfg.domain}" = mkIf (cfg.nginx != null) ( + lib.mkMerge [ + cfg.nginx + ({ + root = lib.mkForce "${package}/htdocs"; + locations."/".index = "index.php"; + locations."~ [^/]\\.php(/|$)" = { + extraConfig = '' + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + fastcgi_pass unix:${config.services.phpfpm.pools.dolibarr.socket}; + ''; + }; + }) + ] + ); + + systemd.services."phpfpm-dolibarr".after = mkIf cfg.database.createLocally [ "mysql.service" ]; + services.phpfpm.pools.dolibarr = { + inherit (cfg) user group; + phpPackage = pkgs.php.buildEnv { + extensions = { enabled, all }: enabled ++ [ all.calendar ]; + # recommended by dolibarr web application extraConfig = '' - fastcgi_split_path_info ^(.+?\.php)(/.*)$; - fastcgi_pass unix:${config.services.phpfpm.pools.dolibarr.socket}; + session.use_strict_mode = 1 + session.cookie_samesite = "Lax" + ; open_basedir = "${package}/htdocs, ${cfg.stateDir}" + allow_url_fopen = 0 + disable_functions = "pcntl_alarm, pcntl_fork, pcntl_waitpid, pcntl_wait, pcntl_wifexited, pcntl_wifstopped, pcntl_wifsignaled, pcntl_wifcontinued, pcntl_wexitstatus, pcntl_wtermsig, pcntl_wstopsig, pcntl_signal, pcntl_signal_get_handler, pcntl_signal_dispatch, pcntl_get_last_error, pcntl_strerror, pcntl_sigprocmask, pcntl_sigwaitinfo, pcntl_sigtimedwait, pcntl_exec, pcntl_getpriority, pcntl_setpriority, pcntl_async_signals" ''; }; - }) - ]); - - systemd.services."phpfpm-dolibarr".after = mkIf cfg.database.createLocally [ "mysql.service" ]; - services.phpfpm.pools.dolibarr = { - inherit (cfg) user group; - phpPackage = pkgs.php.buildEnv { - extensions = { enabled, all }: enabled ++ [ all.calendar ]; - # recommended by dolibarr web application - extraConfig = '' - session.use_strict_mode = 1 - session.cookie_samesite = "Lax" - ; open_basedir = "${package}/htdocs, ${cfg.stateDir}" - allow_url_fopen = 0 - disable_functions = "pcntl_alarm, pcntl_fork, pcntl_waitpid, pcntl_wait, pcntl_wifexited, pcntl_wifstopped, pcntl_wifsignaled, pcntl_wifcontinued, pcntl_wexitstatus, pcntl_wtermsig, pcntl_wstopsig, pcntl_signal, pcntl_signal_get_handler, pcntl_signal_dispatch, pcntl_get_last_error, pcntl_strerror, pcntl_sigprocmask, pcntl_sigwaitinfo, pcntl_sigtimedwait, pcntl_exec, pcntl_getpriority, pcntl_setpriority, pcntl_async_signals" - ''; + + settings = { + "listen.mode" = "0660"; + "listen.owner" = cfg.user; + "listen.group" = cfg.group; + } // cfg.poolConfig; }; - settings = { - "listen.mode" = "0660"; - "listen.owner" = cfg.user; - "listen.group" = cfg.group; - } // cfg.poolConfig; - }; + # there are several challenges with dolibarr and NixOS which we can address here + # - the dolibarr installer cannot be entirely automated, though it can partially be by including a file called install.forced.php + # - the dolibarr installer requires write access to its config file during installation, though not afterwards + # - the dolibarr config file generally holds secrets generated by the installer, though the config file is a php file so we can read and write these secrets from an external file + systemd.services.dolibarr-config = { + description = "dolibarr configuration file management via NixOS"; + wantedBy = [ "multi-user.target" ]; - # there are several challenges with dolibarr and NixOS which we can address here - # - the dolibarr installer cannot be entirely automated, though it can partially be by including a file called install.forced.php - # - the dolibarr installer requires write access to its config file during installation, though not afterwards - # - the dolibarr config file generally holds secrets generated by the installer, though the config file is a php file so we can read and write these secrets from an external file - systemd.services.dolibarr-config = { - description = "dolibarr configuration file management via NixOS"; - wantedBy = [ "multi-user.target" ]; + script = '' + # extract the 'main instance unique id' secret that the dolibarr installer generated for us, store it in a file for use by our own NixOS generated configuration file + ${pkgs.php}/bin/php -r "include '${cfg.stateDir}/conf.php'; file_put_contents('${cfg.stateDir}/dolibarr_main_instance_unique_id', \$dolibarr_main_instance_unique_id);" - script = '' - # extract the 'main instance unique id' secret that the dolibarr installer generated for us, store it in a file for use by our own NixOS generated configuration file - ${pkgs.php}/bin/php -r "include '${cfg.stateDir}/conf.php'; file_put_contents('${cfg.stateDir}/dolibarr_main_instance_unique_id', \$dolibarr_main_instance_unique_id);" + # replace configuration file generated by installer with the NixOS generated configuration file + install -m 440 ${mkConfigFile "conf.php" cfg.settings} '${cfg.stateDir}/conf.php' + ''; - # replace configuration file generated by installer with the NixOS generated configuration file - install -m 644 ${mkConfigFile "conf.php" cfg.settings} '${cfg.stateDir}/conf.php' - ''; + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + RemainAfterExit = "yes"; + }; - serviceConfig = { - Type = "oneshot"; - User = cfg.user; - Group = cfg.group; - RemainAfterExit = "yes"; + unitConfig = { + ConditionFileNotEmpty = "${cfg.stateDir}/conf.php"; + }; }; - unitConfig = { - ConditionFileNotEmpty = "${cfg.stateDir}/conf.php"; + users.users.dolibarr = mkIf (cfg.user == "dolibarr") { + isSystemUser = true; + group = cfg.group; }; - }; - users.users.dolibarr = mkIf (cfg.user == "dolibarr" ) { - isSystemUser = true; - group = cfg.group; - }; - - users.groups = optionalAttrs (cfg.group == "dolibarr") { - dolibarr = { }; - }; - } - (mkIf (cfg.nginx != null) { - users.users."${config.services.nginx.group}".extraGroups = mkIf (cfg.nginx != null) [ cfg.group ]; - }) -]); + users.groups = optionalAttrs (cfg.group == "dolibarr") { + dolibarr = { }; + }; + } + (mkIf (cfg.nginx != null) { + users.users."${config.services.nginx.group}".extraGroups = mkIf (cfg.nginx != null) [ cfg.group ]; + }) + ]); }