1 { config, pkgs, lib, ... }:
3 inherit (lib) any boolToString concatStringsSep isBool isString mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption optionalAttrs types mkPackageOption;
5 package = cfg.package.override { inherit (cfg) stateDir; };
7 cfg = config.services.dolibarr;
8 vhostCfg = lib.optionalAttrs (cfg.nginx != null) config.services.nginx.virtualHosts."${cfg.domain}";
10 mkConfigFile = filename: settings:
12 # hack in special logic for secrets so we read them from a separate file avoiding the nix store
13 secretKeys = [ "force_install_databasepass" "dolibarr_main_db_pass" "dolibarr_main_instance_unique_id" ];
16 if (any (str: k == str) secretKeys) then v
17 else if isString v then "'${v}'"
18 else if isBool v then boolToString v
19 else if v == null then "null"
23 pkgs.writeText filename ''
25 ${concatStringsSep "\n" (mapAttrsToList (k: v: "\$${k} = ${toStr k v};") settings)}
28 # see https://github.com/Dolibarr/dolibarr/blob/develop/htdocs/install/install.forced.sample.php for all possible values
30 force_install_noedit = 2;
31 force_install_main_data_root = "${cfg.stateDir}/documents";
32 force_install_nophpinfo = true;
33 force_install_lockinstall = "444";
34 force_install_distrib = "nixos";
35 force_install_type = "mysqli";
36 force_install_dbserver = cfg.database.host;
37 force_install_port = toString cfg.database.port;
38 force_install_database = cfg.database.name;
39 force_install_databaselogin = cfg.database.user;
41 force_install_mainforcehttps = vhostCfg.forceSSL or false;
42 force_install_createuser = false;
43 force_install_dolibarrlogin = null;
44 } // optionalAttrs (cfg.database.passwordFile != null) {
45 force_install_databasepass = ''file_get_contents("${cfg.database.passwordFile}")'';
50 options.services.dolibarr = {
51 enable = mkEnableOption "dolibarr";
53 package = mkPackageOption pkgs "dolibarr" { };
57 default = "localhost";
59 Domain name of your server.
67 User account under which dolibarr runs.
70 If left as the default value this user will automatically be created
71 on system activation, otherwise you are responsible for
72 ensuring the user exists before the dolibarr application starts.
81 Group account under which dolibarr runs.
84 If left as the default value this group will automatically be created
85 on system activation, otherwise you are responsible for
86 ensuring the group exists before the dolibarr application starts.
93 default = "/var/lib/dolibarr";
95 State and configuration directory dolibarr will use.
102 default = "localhost";
103 description = "Database host address.";
108 description = "Database host port.";
112 default = "dolibarr";
113 description = "Database name.";
117 default = "dolibarr";
118 description = "Database username.";
120 passwordFile = mkOption {
121 type = with types; nullOr path;
123 example = "/run/keys/dolibarr-dbpassword";
124 description = "Database password file.";
126 createLocally = mkOption {
129 description = "Create the database and database user locally.";
133 settings = mkOption {
134 type = with types; (attrsOf (oneOf [ bool int str ]));
136 description = "Dolibarr settings, see <https://github.com/Dolibarr/dolibarr/blob/develop/htdocs/conf/conf.php.example> for details.";
140 type = types.nullOr (types.submodule (
142 (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
144 # enable encryption by default,
145 # as sensitive login and Dolibarr (ERP) data should not be transmitted in clear text.
146 options.forceSSL.default = true;
147 options.enableACME.default = true;
151 example = lib.literalExpression ''
154 "dolibarr.''${config.networking.domain}"
155 "erp.''${config.networking.domain}"
161 With this option, you can customize an nginx virtual host which already has sensible defaults for Dolibarr.
162 Set to {} if you do not need any customization to the virtual host.
163 If enabled, then by default, the {option}`serverName` is
165 SSL is active, and certificates are acquired via ACME.
166 If this is set to null (the default), no nginx virtualHost will be configured.
170 poolConfig = mkOption {
171 type = with types; attrsOf (oneOf [ str int bool ]);
174 "pm.max_children" = 32;
175 "pm.start_servers" = 2;
176 "pm.min_spare_servers" = 2;
177 "pm.max_spare_servers" = 4;
178 "pm.max_requests" = 500;
181 Options for the Dolibarr PHP pool. See the documentation on [`php-fpm.conf`](https://www.php.net/manual/en/install.fpm.configuration.php)
182 for details on configuration directives.
188 config = mkIf cfg.enable (mkMerge [
192 { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
193 message = "services.dolibarr.database.user must match services.dolibarr.user if the database is to be automatically provisioned";
197 services.dolibarr.settings = {
198 dolibarr_main_url_root = "https://${cfg.domain}";
199 dolibarr_main_document_root = "${package}/htdocs";
200 dolibarr_main_url_root_alt = "/custom";
201 dolibarr_main_data_root = "${cfg.stateDir}/documents";
203 dolibarr_main_db_host = cfg.database.host;
204 dolibarr_main_db_port = toString cfg.database.port;
205 dolibarr_main_db_name = cfg.database.name;
206 dolibarr_main_db_prefix = "llx_";
207 dolibarr_main_db_user = cfg.database.user;
208 dolibarr_main_db_pass = mkIf (cfg.database.passwordFile != null) ''
209 file_get_contents("${cfg.database.passwordFile}")
211 dolibarr_main_db_type = "mysqli";
212 dolibarr_main_db_character_set = mkDefault "utf8";
213 dolibarr_main_db_collation = mkDefault "utf8_unicode_ci";
215 # Authentication settings
216 dolibarr_main_authentication = mkDefault "dolibarr";
219 dolibarr_main_prod = true;
220 dolibarr_main_force_https = vhostCfg.forceSSL or false;
221 dolibarr_main_restrict_os_commands = "${pkgs.mariadb}/bin/mysqldump, ${pkgs.mariadb}/bin/mysql";
222 dolibarr_nocsrfcheck = false;
223 dolibarr_main_instance_unique_id = ''
224 file_get_contents("${cfg.stateDir}/dolibarr_main_instance_unique_id")
226 dolibarr_mailing_limit_sendbyweb = false;
229 systemd.tmpfiles.rules = [
230 "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group}"
231 "d '${cfg.stateDir}/documents' 0750 ${cfg.user} ${cfg.group}"
232 "f '${cfg.stateDir}/conf.php' 0660 ${cfg.user} ${cfg.group}"
233 "L '${cfg.stateDir}/install.forced.php' - ${cfg.user} ${cfg.group} - ${mkConfigFile "install.forced.php" install}"
236 services.mysql = mkIf cfg.database.createLocally {
237 enable = mkDefault true;
238 package = mkDefault pkgs.mariadb;
239 ensureDatabases = [ cfg.database.name ];
241 { name = cfg.database.user;
242 ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
247 services.nginx.enable = mkIf (cfg.nginx != null) true;
248 services.nginx.virtualHosts."${cfg.domain}" = mkIf (cfg.nginx != null) (lib.mkMerge [
251 root = lib.mkForce "${package}/htdocs";
252 locations."/".index = "index.php";
253 locations."~ [^/]\\.php(/|$)" = {
255 fastcgi_split_path_info ^(.+?\.php)(/.*)$;
256 fastcgi_pass unix:${config.services.phpfpm.pools.dolibarr.socket};
262 systemd.services."phpfpm-dolibarr".after = mkIf cfg.database.createLocally [ "mysql.service" ];
263 services.phpfpm.pools.dolibarr = {
264 inherit (cfg) user group;
265 phpPackage = pkgs.php.buildEnv {
266 extensions = { enabled, all }: enabled ++ [ all.calendar ];
267 # recommended by dolibarr web application
269 session.use_strict_mode = 1
270 session.cookie_samesite = "Lax"
271 ; open_basedir = "${package}/htdocs, ${cfg.stateDir}"
273 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"
278 "listen.mode" = "0660";
279 "listen.owner" = cfg.user;
280 "listen.group" = cfg.group;
284 # there are several challenges with dolibarr and NixOS which we can address here
285 # - the dolibarr installer cannot be entirely automated, though it can partially be by including a file called install.forced.php
286 # - the dolibarr installer requires write access to its config file during installation, though not afterwards
287 # - 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
288 systemd.services.dolibarr-config = {
289 description = "dolibarr configuration file management via NixOS";
290 wantedBy = [ "multi-user.target" ];
293 # 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
294 ${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);"
296 # replace configuration file generated by installer with the NixOS generated configuration file
297 install -m 644 ${mkConfigFile "conf.php" cfg.settings} '${cfg.stateDir}/conf.php'
304 RemainAfterExit = "yes";
308 ConditionFileNotEmpty = "${cfg.stateDir}/conf.php";
312 users.users.dolibarr = mkIf (cfg.user == "dolibarr" ) {
317 users.groups = optionalAttrs (cfg.group == "dolibarr") {
321 (mkIf (cfg.nginx != null) {
322 users.users."${config.services.nginx.group}".extraGroups = mkIf (cfg.nginx != null) [ cfg.group ];