forked from mirrors/nixpkgs
nixos/dolibarr: init
Co-authored: Ryan Lahfa <masterancpp@gmail.com>
This commit is contained in:
parent
ac52fbb3ba
commit
c2563fe476
|
@ -157,6 +157,14 @@
|
||||||
<link linkend="opt-services.schleuder.enable">services.schleuder</link>.
|
<link linkend="opt-services.schleuder.enable">services.schleuder</link>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<link xlink:href="https://www.dolibarr.org/">Dolibarr</link>,
|
||||||
|
an enterprise resource planning and customer relationship
|
||||||
|
manager. Enable using
|
||||||
|
<link linkend="opt-services.dolibarr.enable">services.dolibarr</link>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<link xlink:href="https://www.expressvpn.com">expressvpn</link>,
|
<link xlink:href="https://www.expressvpn.com">expressvpn</link>,
|
||||||
|
|
|
@ -66,6 +66,8 @@ In addition to numerous new and upgraded packages, this release has the followin
|
||||||
|
|
||||||
- [schleuder](https://schleuder.org/), a mailing list manager with PGP support. Enable using [services.schleuder](#opt-services.schleuder.enable).
|
- [schleuder](https://schleuder.org/), a mailing list manager with PGP support. Enable using [services.schleuder](#opt-services.schleuder.enable).
|
||||||
|
|
||||||
|
- [Dolibarr](https://www.dolibarr.org/), an enterprise resource planning and customer relationship manager. Enable using [services.dolibarr](#opt-services.dolibarr.enable).
|
||||||
|
|
||||||
- [expressvpn](https://www.expressvpn.com), the CLI client for ExpressVPN. Available as [services.expressvpn](#opt-services.expressvpn.enable).
|
- [expressvpn](https://www.expressvpn.com), the CLI client for ExpressVPN. Available as [services.expressvpn](#opt-services.expressvpn.enable).
|
||||||
|
|
||||||
- [Grafana Tempo](https://www.grafana.com/oss/tempo/), a distributed tracing store. Available as [services.tempo](#opt-services.tempo.enable).
|
- [Grafana Tempo](https://www.grafana.com/oss/tempo/), a distributed tracing store. Available as [services.tempo](#opt-services.tempo.enable).
|
||||||
|
|
|
@ -1056,6 +1056,7 @@
|
||||||
./services/web-apps/discourse.nix
|
./services/web-apps/discourse.nix
|
||||||
./services/web-apps/documize.nix
|
./services/web-apps/documize.nix
|
||||||
./services/web-apps/dokuwiki.nix
|
./services/web-apps/dokuwiki.nix
|
||||||
|
./services/web-apps/dolibarr.nix
|
||||||
./services/web-apps/engelsystem.nix
|
./services/web-apps/engelsystem.nix
|
||||||
./services/web-apps/ethercalc.nix
|
./services/web-apps/ethercalc.nix
|
||||||
./services/web-apps/fluidd.nix
|
./services/web-apps/fluidd.nix
|
||||||
|
|
320
nixos/modules/services/web-apps/dolibarr.nix
Normal file
320
nixos/modules/services/web-apps/dolibarr.nix
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) any boolToString concatStringsSep isBool isString literalExpression mapAttrsToList mkDefault mkEnableOption mkIf mkOption optionalAttrs types;
|
||||||
|
|
||||||
|
package = pkgs.dolibarr.override { inherit (cfg) stateDir; };
|
||||||
|
|
||||||
|
cfg = config.services.dolibarr;
|
||||||
|
vhostCfg = config.services.nginx.virtualHosts."${cfg.domain}";
|
||||||
|
|
||||||
|
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 isNull v then "null"
|
||||||
|
else toString v
|
||||||
|
;
|
||||||
|
in
|
||||||
|
pkgs.writeText filename ''
|
||||||
|
<?php
|
||||||
|
${concatStringsSep "\n" (mapAttrsToList (k: v: "\$${k} = ${toStr k v};") settings)}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# see https://github.com/Dolibarr/dolibarr/blob/develop/htdocs/install/install.forced.sample.php for all possible values
|
||||||
|
install = {
|
||||||
|
force_install_noedit = 2;
|
||||||
|
force_install_main_data_root = "${cfg.stateDir}/documents";
|
||||||
|
force_install_nophpinfo = true;
|
||||||
|
force_install_lockinstall = "444";
|
||||||
|
force_install_distrib = "nixos";
|
||||||
|
force_install_type = "mysqli";
|
||||||
|
force_install_dbserver = cfg.database.host;
|
||||||
|
force_install_port = toString cfg.database.port;
|
||||||
|
force_install_database = cfg.database.name;
|
||||||
|
force_install_databaselogin = cfg.database.user;
|
||||||
|
|
||||||
|
force_install_mainforcehttps = vhostCfg.forceSSL;
|
||||||
|
force_install_createuser = false;
|
||||||
|
force_install_dolibarrlogin = null;
|
||||||
|
} // optionalAttrs (cfg.database.passwordFile != null) {
|
||||||
|
force_install_databasepass = ''file_get_contents("${cfg.database.passwordFile}")'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# interface
|
||||||
|
options.services.dolibarr = {
|
||||||
|
enable = mkEnableOption "dolibarr";
|
||||||
|
|
||||||
|
domain = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "localhost";
|
||||||
|
description = ''
|
||||||
|
Domain name of your server.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "dolibarr";
|
||||||
|
description = ''
|
||||||
|
User account under which dolibarr runs.
|
||||||
|
|
||||||
|
<note><para>
|
||||||
|
If left as the default value this user will automatically be created
|
||||||
|
on system activation, otherwise you are responsible for
|
||||||
|
ensuring the user exists before the dolibarr application starts.
|
||||||
|
</para></note>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "dolibarr";
|
||||||
|
description = ''
|
||||||
|
Group account under which dolibarr runs.
|
||||||
|
|
||||||
|
<note><para>
|
||||||
|
If left as the default value this group will automatically be created
|
||||||
|
on system activation, otherwise you are responsible for
|
||||||
|
ensuring the group exists before the dolibarr application starts.
|
||||||
|
</para></note>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
stateDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/var/lib/dolibarr";
|
||||||
|
description = ''
|
||||||
|
State and configuration directory dolibarr will use.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = "dolibarr";
|
||||||
|
description = "Database name.";
|
||||||
|
};
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "dolibarr";
|
||||||
|
description = "Database username.";
|
||||||
|
};
|
||||||
|
passwordFile = mkOption {
|
||||||
|
type = with types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
example = "/run/keys/dolibarr-dbpassword";
|
||||||
|
description = "Database password file.";
|
||||||
|
};
|
||||||
|
createLocally = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Create the database and database user locally.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
type = with types; (attrsOf (oneOf [ bool int str ]));
|
||||||
|
default = { };
|
||||||
|
description = lib.mdDoc "Dolibarr settings, see <https://github.com/Dolibarr/dolibarr/blob/develop/htdocs/conf/conf.php.example> for details.";
|
||||||
|
};
|
||||||
|
|
||||||
|
nginx = mkOption {
|
||||||
|
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 ''
|
||||||
|
{
|
||||||
|
serverAliases = [
|
||||||
|
"dolibarr.''${config.networking.domain}"
|
||||||
|
"erp.''${config.networking.domain}"
|
||||||
|
];
|
||||||
|
enableACME = false;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
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 ]);
|
||||||
|
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 the Dolibarr PHP pool. See the documentation on [`php-fpm.conf`](https://www.php.net/manual/en/install.fpm.configuration.php)
|
||||||
|
for details on configuration directives.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# implementation
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
|
||||||
|
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;
|
||||||
|
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 = ''
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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);"
|
||||||
|
|
||||||
|
# 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";
|
||||||
|
};
|
||||||
|
|
||||||
|
unitConfig = {
|
||||||
|
ConditionFileNotEmpty = "${cfg.stateDir}/conf.php";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.dolibarr = mkIf (cfg.user == "dolibarr" ) {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = cfg.group;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups = optionalAttrs (cfg.group == "dolibarr") {
|
||||||
|
dolibarr = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users."${config.services.nginx.group}".extraGroups = [ cfg.group ];
|
||||||
|
};
|
||||||
|
}
|
59
nixos/tests/dolibarr.nix
Normal file
59
nixos/tests/dolibarr.nix
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import ./make-test-python.nix ({ pkgs, lib, ... }: {
|
||||||
|
name = "dolibarr";
|
||||||
|
meta.maintainers = [ lib.maintainers.raitobezarius ];
|
||||||
|
|
||||||
|
nodes.machine =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
services.dolibarr = {
|
||||||
|
enable = true;
|
||||||
|
domain = "localhost";
|
||||||
|
nginx = {
|
||||||
|
forceSSL = false;
|
||||||
|
enableACME = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
csrf_token = None
|
||||||
|
class TokenParser(HTMLParser):
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
attrs = dict(attrs) # attrs is an assoc list originally
|
||||||
|
if tag == 'input' and attrs.get('name') == 'token':
|
||||||
|
csrf_token = attrs.get('value')
|
||||||
|
print(f'[+] Caught CSRF token: {csrf_token}')
|
||||||
|
def handle_endtag(self, tag): pass
|
||||||
|
def handle_data(self, data): pass
|
||||||
|
|
||||||
|
machine.wait_for_unit("phpfpm-dolibarr.service")
|
||||||
|
machine.wait_for_unit("nginx.service")
|
||||||
|
machine.wait_for_open_port(80)
|
||||||
|
# Sanity checks on URLs.
|
||||||
|
# machine.succeed("curl -fL http://localhost/index.php")
|
||||||
|
# machine.succeed("curl -fL http://localhost/")
|
||||||
|
# Perform installation.
|
||||||
|
machine.succeed('curl -fL -X POST http://localhost/install/check.php -F selectlang=auto')
|
||||||
|
machine.succeed('curl -fL -X POST http://localhost/install/fileconf.php -F selectlang=auto')
|
||||||
|
# First time is to write the configuration file correctly.
|
||||||
|
machine.succeed('curl -fL -X POST http://localhost/install/step1.php -F "testpost=ok" -F "action=set" -F "selectlang=auto"')
|
||||||
|
# Now, we have a proper conf.php in $stateDir.
|
||||||
|
assert 'nixos' in machine.succeed("cat /var/lib/dolibarr/conf.php")
|
||||||
|
machine.succeed('curl -fL -X POST http://localhost/install/step2.php --data "testpost=ok&action=set&dolibarr_main_db_character_set=utf8&dolibarr_main_db_collation=utf8_unicode_ci&selectlang=auto"')
|
||||||
|
machine.succeed('curl -fL -X POST http://localhost/install/step4.php --data "testpost=ok&action=set&selectlang=auto"')
|
||||||
|
machine.succeed('curl -fL -X POST http://localhost/install/step5.php --data "testpost=ok&action=set&login=root&pass=hunter2&pass_verif=hunter2&selectlang=auto"')
|
||||||
|
# Now, we have installed the machine, let's verify we still have the right configuration.
|
||||||
|
assert 'nixos' in machine.succeed("cat /var/lib/dolibarr/conf.php")
|
||||||
|
# We do not want any redirect now as we have installed the machine.
|
||||||
|
machine.succeed('curl -f -X POST http://localhost')
|
||||||
|
# Test authentication to the webservice.
|
||||||
|
parser = TokenParser()
|
||||||
|
parser.feed(machine.succeed('curl -f -X GET http://localhost/index.php?mainmenu=login&username=root'))
|
||||||
|
machine.succeed(f'curl -f -X POST http://localhost/index.php?mainmenu=login&token={csrf_token}&username=root&password=hunter2')
|
||||||
|
'';
|
||||||
|
})
|
|
@ -1,4 +1,4 @@
|
||||||
{ stdenv, lib, fetchFromGitHub, stateDir ? "/var/lib/dolibarr" }:
|
{ stdenv, lib, fetchFromGitHub, nixosTests, stateDir ? "/var/lib/dolibarr" }:
|
||||||
|
|
||||||
stdenv.mkDerivation rec {
|
stdenv.mkDerivation rec {
|
||||||
pname = "dolibarr";
|
pname = "dolibarr";
|
||||||
|
@ -30,6 +30,8 @@ stdenv.mkDerivation rec {
|
||||||
cp -r * $out
|
cp -r * $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
passthru.tests = { inherit (nixosTests) dolibarr; };
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
description = "A enterprise resource planning (ERP) and customer relationship manager (CRM) server";
|
description = "A enterprise resource planning (ERP) and customer relationship manager (CRM) server";
|
||||||
homepage = "https://dolibarr.org/";
|
homepage = "https://dolibarr.org/";
|
||||||
|
|
Loading…
Reference in a new issue