2022-09-09 14:08:57 +00:00
{ 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 = [ " f o r c e _ i n s t a l l _ d a t a b a s e p a s s " " d o l i b a r r _ m a i n _ d b _ p a s s " " d o l i b a r r _ m a i n _ i n s t a n c e _ u n i q u e _ i d " ] ;
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 " n u l l "
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 } / d o c u m e n t s " ;
force_install_nophpinfo = true ;
force_install_lockinstall = " 4 4 4 " ;
force_install_distrib = " n i x o s " ;
force_install_type = " m y s q l i " ;
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 = '' f i l e _ g e t _ c o n t e n t s ( " ${ cfg . database . passwordFile } " ) '' ;
} ;
in
{
# interface
options . services . dolibarr = {
2022-09-11 15:47:08 +00:00
enable = mkEnableOption ( lib . mdDoc " d o l i b a r r " ) ;
2022-09-09 14:08:57 +00:00
domain = mkOption {
type = types . str ;
default = " l o c a l h o s t " ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc ''
2022-09-09 14:08:57 +00:00
Domain name of your server .
'' ;
} ;
user = mkOption {
type = types . str ;
default = " d o l i b a r r " ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc ''
2022-09-09 14:08:57 +00:00
User account under which dolibarr runs .
2022-09-11 15:47:08 +00:00
: : : { . note }
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 .
: : :
2022-09-09 14:08:57 +00:00
'' ;
} ;
group = mkOption {
type = types . str ;
default = " d o l i b a r r " ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc ''
2022-09-09 14:08:57 +00:00
Group account under which dolibarr runs .
2022-09-11 15:47:08 +00:00
: : : { . note }
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 .
: : :
2022-09-09 14:08:57 +00:00
'' ;
} ;
stateDir = mkOption {
type = types . str ;
default = " / v a r / l i b / d o l i b a r r " ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc ''
2022-09-09 14:08:57 +00:00
State and configuration directory dolibarr will use .
'' ;
} ;
database = {
host = mkOption {
type = types . str ;
default = " l o c a l h o s t " ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc " D a t a b a s e h o s t a d d r e s s . " ;
2022-09-09 14:08:57 +00:00
} ;
port = mkOption {
type = types . port ;
default = 3306 ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc " D a t a b a s e h o s t p o r t . " ;
2022-09-09 14:08:57 +00:00
} ;
name = mkOption {
type = types . str ;
default = " d o l i b a r r " ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc " D a t a b a s e n a m e . " ;
2022-09-09 14:08:57 +00:00
} ;
user = mkOption {
type = types . str ;
default = " d o l i b a r r " ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc " D a t a b a s e u s e r n a m e . " ;
2022-09-09 14:08:57 +00:00
} ;
passwordFile = mkOption {
type = with types ; nullOr path ;
default = null ;
example = " / r u n / k e y s / d o l i b a r r - d b p a s s w o r d " ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc " D a t a b a s e p a s s w o r d f i l e . " ;
2022-09-09 14:08:57 +00:00
} ;
createLocally = mkOption {
type = types . bool ;
default = true ;
2022-09-11 15:47:08 +00:00
description = lib . mdDoc " C r e a t e t h e d a t a b a s e a n d d a t a b a s e u s e r l o c a l l y . " ;
2022-09-09 14:08:57 +00:00
} ;
} ;
settings = mkOption {
type = with types ; ( attrsOf ( oneOf [ bool int str ] ) ) ;
default = { } ;
description = lib . mdDoc " D o l i b a r r s e t t i n g s , s e e < h t t p s : / / g i t h u b . c o m / D o l i b a r r / d o l i b a r r / b l o b / d e v e l o p / h t d o c s / c o n f / c o n f . p h p . e x a m p l e > f o r d e t a i l s . " ;
} ;
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 = [
" d o l i b a r r . ' ' ${ config . networking . domain } "
" e r p . ' ' ${ 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 = {
" p m " = " d y n a m i c " ;
" p m . m a x _ c h i l d r e n " = 32 ;
" p m . s t a r t _ s e r v e r s " = 2 ;
" p m . m i n _ s p a r e _ s e r v e r s " = 2 ;
" p m . m a x _ s p a r e _ s e r v e r s " = 4 ;
" p m . m a x _ r e q u e s t s " = 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 = " s e r v i c e s . d o l i b a r r . d a t a b a s e . u s e r m u s t m a t c h s e r v i c e s . d o l i b a r r . u s e r i f t h e d a t a b a s e i s t o b e a u t o m a t i c a l l y p r o v i s i o n e d " ;
}
] ;
services . dolibarr . settings = {
dolibarr_main_url_root = " h t t p s : / / ${ cfg . domain } " ;
dolibarr_main_document_root = " ${ package } / h t d o c s " ;
dolibarr_main_url_root_alt = " / c u s t o m " ;
dolibarr_main_data_root = " ${ cfg . stateDir } / d o c u m e n t s " ;
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 = " l l x _ " ;
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 = " m y s q l i " ;
dolibarr_main_db_character_set = mkDefault " u t f 8 " ;
dolibarr_main_db_collation = mkDefault " u t f 8 _ u n i c o d e _ c i " ;
# Authentication settings
dolibarr_main_authentication = mkDefault " d o l i b a r r " ;
# Security settings
dolibarr_main_prod = true ;
dolibarr_main_force_https = vhostCfg . forceSSL ;
dolibarr_main_restrict_os_commands = " ${ pkgs . mariadb } / b i n / m y s q l d u m p , ${ pkgs . mariadb } / b i n / m y s q l " ;
dolibarr_nocsrfcheck = false ;
dolibarr_main_instance_unique_id = ''
file_get_contents ( " ${ cfg . stateDir } / d o l i b a r r _ m a i n _ i n s t a n c e _ u n i q u e _ i d " )
'' ;
dolibarr_mailing_limit_sendbyweb = false ;
} ;
systemd . tmpfiles . rules = [
" d ' ${ cfg . stateDir } ' 0 7 5 0 ${ cfg . user } ${ cfg . group } "
" d ' ${ cfg . stateDir } / d o c u m e n t s ' 0 7 5 0 ${ cfg . user } ${ cfg . group } "
" f ' ${ cfg . stateDir } / c o n f . p h p ' 0 6 6 0 ${ cfg . user } ${ cfg . group } "
" L ' ${ cfg . stateDir } / i n s t a l l . f o r c e d . p h p ' - ${ cfg . user } ${ cfg . group } - ${ mkConfigFile " i n s t a l l . f o r c e d . p h p " 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 } . * " = " A L L P R I V I L E G E S " ; } ;
}
] ;
} ;
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 } / h t d o c s " ;
locations . " / " . index = " i n d e x . p h p " ;
locations . " ~ [ ^ / ] \\ . p h p ( / | $ ) " = {
extraConfig = ''
fastcgi_split_path_info ^ ( . + ? \ . php ) ( /. * ) $ ;
fastcgi_pass unix:$ { config . services . phpfpm . pools . dolibarr . socket } ;
'' ;
} ;
} )
] ) ;
systemd . services . " p h p f p m - d o l i b a r r " . after = mkIf cfg . database . createLocally [ " m y s q l . s e r v i c e " ] ;
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 = " L a x "
; open_basedir = " ${ package } / h t d o c s , ${ cfg . stateDir } "
allow_url_fopen = 0
disable_functions = " p c n t l _ a l a r m , p c n t l _ f o r k , p c n t l _ w a i t p i d , p c n t l _ w a i t , p c n t l _ w i f e x i t e d , p c n t l _ w i f s t o p p e d , p c n t l _ w i f s i g n a l e d , p c n t l _ w i f c o n t i n u e d , p c n t l _ w e x i t s t a t u s , p c n t l _ w t e r m s i g , p c n t l _ w s t o p s i g , p c n t l _ s i g n a l , p c n t l _ s i g n a l _ g e t _ h a n d l e r , p c n t l _ s i g n a l _ d i s p a t c h , p c n t l _ g e t _ l a s t _ e r r o r , p c n t l _ s t r e r r o r , p c n t l _ s i g p r o c m a s k , p c n t l _ s i g w a i t i n f o , p c n t l _ s i g t i m e d w a i t , p c n t l _ e x e c , p c n t l _ g e t p r i o r i t y , p c n t l _ s e t p r i o r i t y , p c n t l _ a s y n c _ s i g n a l s "
'' ;
} ;
settings = {
" l i s t e n . m o d e " = " 0 6 6 0 " ;
" l i s t e n . o w n e r " = cfg . user ;
" l i s t e n . g r o u p " = 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 = " d o l i b a r r c o n f i g u r a t i o n f i l e m a n a g e m e n t v i a N i x O S " ;
wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
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 " i n c l u d e ' ${ cfg . stateDir } / c o n f . p h p ' ; f i l e _ p u t _ c o n t e n t s ( ' ${ cfg . stateDir } / d o l i b a r r _ m a i n _ i n s t a n c e _ u n i q u e _ i d ' , \$ d o l i b a r r _ m a i n _ i n s t a n c e _ u n i q u e _ i d ) ; "
# replace configuration file generated by installer with the NixOS generated configuration file
install - m 644 $ { mkConfigFile " c o n f . p h p " cfg . settings } ' $ { cfg . stateDir } /conf.php '
'' ;
serviceConfig = {
Type = " o n e s h o t " ;
User = cfg . user ;
Group = cfg . group ;
RemainAfterExit = " y e s " ;
} ;
unitConfig = {
ConditionFileNotEmpty = " ${ cfg . stateDir } / c o n f . p h p " ;
} ;
} ;
users . users . dolibarr = mkIf ( cfg . user == " d o l i b a r r " ) {
isSystemUser = true ;
group = cfg . group ;
} ;
users . groups = optionalAttrs ( cfg . group == " d o l i b a r r " ) {
dolibarr = { } ;
} ;
users . users . " ${ config . services . nginx . group } " . extraGroups = [ cfg . group ] ;
} ;
}