2018-10-01 16:12:56 +01:00
|
|
|
{ lib, config, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.services.roundcube;
|
2019-08-08 03:36:49 +01:00
|
|
|
fpm = config.services.phpfpm.pools.roundcube;
|
2020-01-05 12:00:00 +00:00
|
|
|
localDB = cfg.database.host == "localhost";
|
|
|
|
user = cfg.database.username;
|
2020-04-12 22:31:56 +01:00
|
|
|
phpWithPspell = pkgs.php.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled);
|
2018-10-01 16:12:56 +01:00
|
|
|
in
|
|
|
|
{
|
|
|
|
options.services.roundcube = {
|
2018-11-28 16:33:26 +00:00
|
|
|
enable = mkOption {
|
2018-10-11 09:09:29 +01:00
|
|
|
type = types.bool;
|
2018-11-28 16:33:26 +00:00
|
|
|
default = false;
|
2018-10-11 09:09:29 +01:00
|
|
|
description = ''
|
2018-11-28 16:33:26 +00:00
|
|
|
Whether to enable roundcube.
|
|
|
|
|
|
|
|
Also enables nginx virtual host management.
|
2018-10-11 09:09:29 +01:00
|
|
|
Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.<name></literal>.
|
|
|
|
See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
|
|
|
|
'';
|
2018-10-01 16:12:56 +01:00
|
|
|
};
|
|
|
|
|
2018-10-11 09:09:29 +01:00
|
|
|
hostName = mkOption {
|
2018-10-01 16:12:56 +01:00
|
|
|
type = types.str;
|
2018-11-28 16:33:26 +00:00
|
|
|
example = "webmail.example.com";
|
|
|
|
description = "Hostname to use for the nginx vhost";
|
2018-10-01 16:12:56 +01:00
|
|
|
};
|
|
|
|
|
2019-01-31 21:45:14 +00:00
|
|
|
package = mkOption {
|
|
|
|
type = types.package;
|
|
|
|
default = pkgs.roundcube;
|
|
|
|
|
|
|
|
example = literalExample ''
|
|
|
|
roundcube.withPlugins (plugins: [ plugins.persistent_login ])
|
|
|
|
'';
|
|
|
|
|
|
|
|
description = ''
|
|
|
|
The package which contains roundcube's sources. Can be overriden to create
|
|
|
|
an environment which contains roundcube and third-party plugins.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2018-10-11 09:09:29 +01:00
|
|
|
database = {
|
|
|
|
username = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "roundcube";
|
2020-01-05 12:00:00 +00:00
|
|
|
description = ''
|
|
|
|
Username for the postgresql connection.
|
|
|
|
If <literal>database.host</literal> is set to <literal>localhost</literal>, a unix user and group of the same name will be created as well.
|
|
|
|
'';
|
2018-10-11 09:09:29 +01:00
|
|
|
};
|
|
|
|
host = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "localhost";
|
2018-11-28 16:33:26 +00:00
|
|
|
description = ''
|
|
|
|
Host of the postgresql server. If this is not set to
|
|
|
|
<literal>localhost</literal>, you have to create the
|
|
|
|
postgresql user and database yourself, with appropriate
|
|
|
|
permissions.
|
|
|
|
'';
|
2018-10-11 09:09:29 +01:00
|
|
|
};
|
|
|
|
password = mkOption {
|
|
|
|
type = types.str;
|
2020-01-05 12:00:00 +00:00
|
|
|
description = "Password for the postgresql connection. Do not use: the password will be stored world readable in the store; use <literal>passwordFile</literal> instead.";
|
|
|
|
default = "";
|
|
|
|
};
|
|
|
|
passwordFile = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
description = "Password file for the postgresql connection. Must be readable by user <literal>nginx</literal>. Ignored if <literal>database.host</literal> is set to <literal>localhost</literal>, as peer authentication will be used.";
|
2018-10-11 09:09:29 +01:00
|
|
|
};
|
|
|
|
dbname = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "roundcube";
|
|
|
|
description = "Name of the postgresql database";
|
|
|
|
};
|
|
|
|
};
|
2018-10-01 16:12:56 +01:00
|
|
|
|
2018-10-11 09:09:29 +01:00
|
|
|
plugins = mkOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = [];
|
|
|
|
description = ''
|
2018-11-28 16:33:26 +00:00
|
|
|
List of roundcube plugins to enable. Currently, only those directly shipped with Roundcube are supported.
|
2018-10-01 16:12:56 +01:00
|
|
|
'';
|
2018-10-11 09:09:29 +01:00
|
|
|
};
|
|
|
|
|
2020-04-10 13:00:00 +01:00
|
|
|
dicts = mkOption {
|
|
|
|
type = types.listOf types.package;
|
|
|
|
default = [];
|
|
|
|
example = literalExample "with pkgs.aspellDicts; [ en fr de ]";
|
|
|
|
description = ''
|
|
|
|
List of aspell dictionnaries for spell checking. If empty, spell checking is disabled.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2020-07-05 10:40:15 +01:00
|
|
|
maxAttachmentSize = mkOption {
|
|
|
|
type = types.int;
|
|
|
|
default = 18;
|
|
|
|
description = ''
|
2020-07-08 10:09:01 +01:00
|
|
|
The maximum attachment size in MB.
|
|
|
|
|
|
|
|
Note: Since roundcube only uses 70% of max upload values configured in php
|
|
|
|
30% is added automatically to <xref linkend="opt-services.roundcube.maxAttachmentSize"/>.
|
2020-07-05 10:40:15 +01:00
|
|
|
'';
|
|
|
|
apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M";
|
|
|
|
};
|
|
|
|
|
2018-10-11 09:09:29 +01:00
|
|
|
extraConfig = mkOption {
|
|
|
|
type = types.lines;
|
|
|
|
default = "";
|
|
|
|
description = "Extra configuration for roundcube webmail instance";
|
2018-10-01 16:12:56 +01:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
2020-01-05 12:00:00 +00:00
|
|
|
# backward compatibility: if password is set but not passwordFile, make one.
|
|
|
|
services.roundcube.database.passwordFile = mkIf (!localDB && cfg.database.password != "") (mkDefault ("${pkgs.writeText "roundcube-password" cfg.database.password}"));
|
|
|
|
warnings = lib.optional (!localDB && cfg.database.password != "") "services.roundcube.database.password is deprecated and insecure; use services.roundcube.database.passwordFile instead";
|
|
|
|
|
2018-10-11 09:09:29 +01:00
|
|
|
environment.etc."roundcube/config.inc.php".text = ''
|
|
|
|
<?php
|
2018-10-01 16:12:56 +01:00
|
|
|
|
2020-01-05 12:00:00 +00:00
|
|
|
${lib.optionalString (!localDB) "$password = file_get_contents('${cfg.database.passwordFile}');"}
|
|
|
|
|
2018-10-11 09:09:29 +01:00
|
|
|
$config = array();
|
2020-01-05 12:00:00 +00:00
|
|
|
$config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}';
|
2018-10-11 09:09:29 +01:00
|
|
|
$config['log_driver'] = 'syslog';
|
2020-07-05 10:40:15 +01:00
|
|
|
$config['max_message_size'] = '${cfg.maxAttachmentSize}';
|
2018-10-11 09:09:29 +01:00
|
|
|
$config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
|
2020-01-05 12:00:00 +00:00
|
|
|
$config['des_key'] = file_get_contents('/var/lib/roundcube/des_key');
|
2020-01-05 12:00:00 +00:00
|
|
|
$config['mime_types'] = '${pkgs.nginx}/conf/mime.types';
|
2020-04-10 13:00:00 +01:00
|
|
|
$config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"};
|
|
|
|
# by default, spellchecking uses a third-party cloud services
|
|
|
|
$config['spellcheck_engine'] = 'pspell';
|
|
|
|
$config['spellcheck_languages'] = array(${lib.concatMapStringsSep ", " (dict: let p = builtins.parseDrvName dict.shortName; in "'${p.name}' => '${dict.fullName}'") cfg.dicts});
|
|
|
|
|
2018-10-11 09:09:29 +01:00
|
|
|
${cfg.extraConfig}
|
|
|
|
'';
|
|
|
|
|
2018-11-28 16:33:26 +00:00
|
|
|
services.nginx = {
|
2018-10-11 09:09:29 +01:00
|
|
|
enable = true;
|
|
|
|
virtualHosts = {
|
|
|
|
${cfg.hostName} = {
|
|
|
|
forceSSL = mkDefault true;
|
|
|
|
enableACME = mkDefault true;
|
|
|
|
locations."/" = {
|
2019-01-31 21:45:14 +00:00
|
|
|
root = cfg.package;
|
2018-10-11 09:09:29 +01:00
|
|
|
index = "index.php";
|
|
|
|
extraConfig = ''
|
|
|
|
location ~* \.php$ {
|
|
|
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
2019-08-08 03:36:49 +01:00
|
|
|
fastcgi_pass unix:${fpm.socket};
|
2018-10-11 09:09:29 +01:00
|
|
|
include ${pkgs.nginx}/conf/fastcgi_params;
|
|
|
|
include ${pkgs.nginx}/conf/fastcgi.conf;
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
};
|
2018-10-01 16:12:56 +01:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2020-01-05 12:00:00 +00:00
|
|
|
services.postgresql = mkIf localDB {
|
2018-10-11 09:09:29 +01:00
|
|
|
enable = true;
|
2020-01-05 12:00:00 +00:00
|
|
|
ensureDatabases = [ cfg.database.dbname ];
|
|
|
|
ensureUsers = [ {
|
|
|
|
name = cfg.database.username;
|
|
|
|
ensurePermissions = {
|
|
|
|
"DATABASE ${cfg.database.username}" = "ALL PRIVILEGES";
|
|
|
|
};
|
|
|
|
} ];
|
2018-10-11 09:09:29 +01:00
|
|
|
};
|
|
|
|
|
2020-01-05 12:00:00 +00:00
|
|
|
users.users.${user} = mkIf localDB {
|
|
|
|
group = user;
|
|
|
|
isSystemUser = true;
|
|
|
|
createHome = false;
|
|
|
|
};
|
|
|
|
users.groups.${user} = mkIf localDB {};
|
|
|
|
|
2019-07-03 17:11:38 +01:00
|
|
|
services.phpfpm.pools.roundcube = {
|
2020-01-05 12:00:00 +00:00
|
|
|
user = if localDB then user else "nginx";
|
2019-08-08 03:36:49 +01:00
|
|
|
phpOptions = ''
|
|
|
|
error_log = 'stderr'
|
|
|
|
log_errors = on
|
2020-07-05 10:40:15 +01:00
|
|
|
post_max_size = ${cfg.maxAttachmentSize}
|
|
|
|
upload_max_filesize = ${cfg.maxAttachmentSize}
|
2019-07-03 17:11:38 +01:00
|
|
|
'';
|
2019-08-08 03:36:49 +01:00
|
|
|
settings = mapAttrs (name: mkDefault) {
|
|
|
|
"listen.owner" = "nginx";
|
|
|
|
"listen.group" = "nginx";
|
|
|
|
"listen.mode" = "0660";
|
|
|
|
"pm" = "dynamic";
|
|
|
|
"pm.max_children" = 75;
|
|
|
|
"pm.start_servers" = 2;
|
|
|
|
"pm.min_spare_servers" = 1;
|
|
|
|
"pm.max_spare_servers" = 20;
|
|
|
|
"pm.max_requests" = 500;
|
|
|
|
"catch_workers_output" = true;
|
|
|
|
};
|
2020-04-10 13:00:00 +01:00
|
|
|
phpPackage = phpWithPspell;
|
|
|
|
phpEnv.ASPELL_CONF = "dict-dir ${pkgs.aspellWithDicts (_: cfg.dicts)}/lib/aspell";
|
2019-07-03 17:11:38 +01:00
|
|
|
};
|
2018-10-11 09:09:29 +01:00
|
|
|
systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ];
|
|
|
|
|
2020-11-08 21:20:18 +00:00
|
|
|
# Restart on config changes.
|
|
|
|
systemd.services.phpfpm-roundcube.restartTriggers = [
|
|
|
|
config.environment.etc."roundcube/config.inc.php".source
|
|
|
|
];
|
|
|
|
|
2020-01-05 12:00:00 +00:00
|
|
|
systemd.services.roundcube-setup = mkMerge [
|
2019-04-02 15:02:53 +01:00
|
|
|
(mkIf (cfg.database.host == "localhost") {
|
|
|
|
requires = [ "postgresql.service" ];
|
|
|
|
after = [ "postgresql.service" ];
|
|
|
|
path = [ config.services.postgresql.package ];
|
|
|
|
})
|
|
|
|
{
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
2020-01-05 12:00:00 +00:00
|
|
|
script = let
|
|
|
|
psql = "${lib.optionalString (!localDB) "PGPASSFILE=${cfg.database.passwordFile}"} ${pkgs.postgresql}/bin/psql ${lib.optionalString (!localDB) "-h ${cfg.database.host} -U ${cfg.database.username} "} ${cfg.database.dbname}";
|
|
|
|
in
|
|
|
|
''
|
|
|
|
version="$(${psql} -t <<< "select value from system where name = 'roundcube-version';" || true)"
|
|
|
|
if ! (grep -E '[a-zA-Z0-9]' <<< "$version"); then
|
|
|
|
${psql} -f ${cfg.package}/SQL/postgres.initial.sql
|
2018-10-11 09:09:29 +01:00
|
|
|
fi
|
2018-11-28 16:33:26 +00:00
|
|
|
|
2020-01-05 12:00:00 +00:00
|
|
|
if [ ! -f /var/lib/roundcube/des_key ]; then
|
|
|
|
base64 /dev/urandom | head -c 24 > /var/lib/roundcube/des_key;
|
|
|
|
# we need to log out everyone in case change the des_key
|
|
|
|
# from the default when upgrading from nixos 19.09
|
|
|
|
${psql} <<< 'TRUNCATE TABLE session;'
|
|
|
|
fi
|
|
|
|
|
2020-04-10 13:00:00 +01:00
|
|
|
${phpWithPspell}/bin/php ${cfg.package}/bin/update.sh
|
2019-04-02 15:02:53 +01:00
|
|
|
'';
|
2020-01-05 12:00:00 +00:00
|
|
|
serviceConfig = {
|
|
|
|
Type = "oneshot";
|
|
|
|
StateDirectory = "roundcube";
|
|
|
|
User = if localDB then user else "nginx";
|
2020-01-05 12:00:00 +00:00
|
|
|
# so that the des_key is not world readable
|
|
|
|
StateDirectoryMode = "0700";
|
2020-01-05 12:00:00 +00:00
|
|
|
};
|
2019-04-02 15:02:53 +01:00
|
|
|
}
|
|
|
|
];
|
2018-10-01 16:12:56 +01:00
|
|
|
};
|
|
|
|
}
|