at 23.11-pre 9.9 kB view raw
1{ lib, config, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.roundcube; 7 fpm = config.services.phpfpm.pools.roundcube; 8 localDB = cfg.database.host == "localhost"; 9 user = cfg.database.username; 10 phpWithPspell = pkgs.php81.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled); 11in 12{ 13 options.services.roundcube = { 14 enable = mkOption { 15 type = types.bool; 16 default = false; 17 description = lib.mdDoc '' 18 Whether to enable roundcube. 19 20 Also enables nginx virtual host management. 21 Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`. 22 See [](#opt-services.nginx.virtualHosts) for further information. 23 ''; 24 }; 25 26 hostName = mkOption { 27 type = types.str; 28 example = "webmail.example.com"; 29 description = lib.mdDoc "Hostname to use for the nginx vhost"; 30 }; 31 32 package = mkOption { 33 type = types.package; 34 default = pkgs.roundcube; 35 defaultText = literalExpression "pkgs.roundcube"; 36 37 example = literalExpression '' 38 roundcube.withPlugins (plugins: [ plugins.persistent_login ]) 39 ''; 40 41 description = lib.mdDoc '' 42 The package which contains roundcube's sources. Can be overridden to create 43 an environment which contains roundcube and third-party plugins. 44 ''; 45 }; 46 47 database = { 48 username = mkOption { 49 type = types.str; 50 default = "roundcube"; 51 description = lib.mdDoc '' 52 Username for the postgresql connection. 53 If `database.host` is set to `localhost`, a unix user and group of the same name will be created as well. 54 ''; 55 }; 56 host = mkOption { 57 type = types.str; 58 default = "localhost"; 59 description = lib.mdDoc '' 60 Host of the postgresql server. If this is not set to 61 `localhost`, you have to create the 62 postgresql user and database yourself, with appropriate 63 permissions. 64 ''; 65 }; 66 password = mkOption { 67 type = types.str; 68 description = lib.mdDoc "Password for the postgresql connection. Do not use: the password will be stored world readable in the store; use `passwordFile` instead."; 69 default = ""; 70 }; 71 passwordFile = mkOption { 72 type = types.str; 73 description = lib.mdDoc '' 74 Password file for the postgresql connection. 75 Must be formatted according to PostgreSQL .pgpass standard (see https://www.postgresql.org/docs/current/libpq-pgpass.html) 76 but only one line, no comments and readable by user `nginx`. 77 Ignored if `database.host` is set to `localhost`, as peer authentication will be used. 78 ''; 79 }; 80 dbname = mkOption { 81 type = types.str; 82 default = "roundcube"; 83 description = lib.mdDoc "Name of the postgresql database"; 84 }; 85 }; 86 87 plugins = mkOption { 88 type = types.listOf types.str; 89 default = []; 90 description = lib.mdDoc '' 91 List of roundcube plugins to enable. Currently, only those directly shipped with Roundcube are supported. 92 ''; 93 }; 94 95 dicts = mkOption { 96 type = types.listOf types.package; 97 default = []; 98 example = literalExpression "with pkgs.aspellDicts; [ en fr de ]"; 99 description = lib.mdDoc '' 100 List of aspell dictionaries for spell checking. If empty, spell checking is disabled. 101 ''; 102 }; 103 104 maxAttachmentSize = mkOption { 105 type = types.int; 106 default = 18; 107 description = lib.mdDoc '' 108 The maximum attachment size in MB. 109 110 Note: Since roundcube only uses 70% of max upload values configured in php 111 30% is added automatically to [](#opt-services.roundcube.maxAttachmentSize). 112 ''; 113 apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M"; 114 }; 115 116 extraConfig = mkOption { 117 type = types.lines; 118 default = ""; 119 description = lib.mdDoc "Extra configuration for roundcube webmail instance"; 120 }; 121 }; 122 123 config = mkIf cfg.enable { 124 # backward compatibility: if password is set but not passwordFile, make one. 125 services.roundcube.database.passwordFile = mkIf (!localDB && cfg.database.password != "") (mkDefault ("${pkgs.writeText "roundcube-password" cfg.database.password}")); 126 warnings = lib.optional (!localDB && cfg.database.password != "") "services.roundcube.database.password is deprecated and insecure; use services.roundcube.database.passwordFile instead"; 127 128 environment.etc."roundcube/config.inc.php".text = '' 129 <?php 130 131 ${lib.optionalString (!localDB) '' 132 $password = file('${cfg.database.passwordFile}')[0]; 133 $password = preg_split('~\\\\.(*SKIP)(*FAIL)|\:~s', $password); 134 $password = end($password); 135 $password = str_replace("\\:", ":", $password); 136 $password = str_replace("\\\\", "\\", $password); 137 ''} 138 139 $config = array(); 140 $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}'; 141 $config['log_driver'] = 'syslog'; 142 $config['max_message_size'] = '${cfg.maxAttachmentSize}'; 143 $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}]; 144 $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key'); 145 $config['mime_types'] = '${pkgs.nginx}/conf/mime.types'; 146 # Roundcube uses PHP-FPM which has `PrivateTmp = true;` 147 $config['temp_dir'] = '/tmp'; 148 $config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"}; 149 # by default, spellchecking uses a third-party cloud services 150 $config['spellcheck_engine'] = 'pspell'; 151 $config['spellcheck_languages'] = array(${lib.concatMapStringsSep ", " (dict: let p = builtins.parseDrvName dict.shortName; in "'${p.name}' => '${dict.fullName}'") cfg.dicts}); 152 153 ${cfg.extraConfig} 154 ''; 155 156 services.nginx = { 157 enable = true; 158 virtualHosts = { 159 ${cfg.hostName} = { 160 forceSSL = mkDefault true; 161 enableACME = mkDefault true; 162 locations."/" = { 163 root = cfg.package; 164 index = "index.php"; 165 extraConfig = '' 166 location ~* \.php(/|$) { 167 fastcgi_split_path_info ^(.+\.php)(/.+)$; 168 fastcgi_pass unix:${fpm.socket}; 169 170 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 171 fastcgi_param PATH_INFO $fastcgi_path_info; 172 173 include ${config.services.nginx.package}/conf/fastcgi_params; 174 include ${pkgs.nginx}/conf/fastcgi.conf; 175 } 176 ''; 177 }; 178 }; 179 }; 180 }; 181 182 services.postgresql = mkIf localDB { 183 enable = true; 184 ensureDatabases = [ cfg.database.dbname ]; 185 ensureUsers = [ { 186 name = cfg.database.username; 187 ensurePermissions = { 188 "DATABASE ${cfg.database.username}" = "ALL PRIVILEGES"; 189 }; 190 } ]; 191 }; 192 193 users.users.${user} = mkIf localDB { 194 group = user; 195 isSystemUser = true; 196 createHome = false; 197 }; 198 users.groups.${user} = mkIf localDB {}; 199 200 services.phpfpm.pools.roundcube = { 201 user = if localDB then user else "nginx"; 202 phpOptions = '' 203 error_log = 'stderr' 204 log_errors = on 205 post_max_size = ${cfg.maxAttachmentSize} 206 upload_max_filesize = ${cfg.maxAttachmentSize} 207 ''; 208 settings = mapAttrs (name: mkDefault) { 209 "listen.owner" = "nginx"; 210 "listen.group" = "nginx"; 211 "listen.mode" = "0660"; 212 "pm" = "dynamic"; 213 "pm.max_children" = 75; 214 "pm.start_servers" = 2; 215 "pm.min_spare_servers" = 1; 216 "pm.max_spare_servers" = 20; 217 "pm.max_requests" = 500; 218 "catch_workers_output" = true; 219 }; 220 phpPackage = phpWithPspell; 221 phpEnv.ASPELL_CONF = "dict-dir ${pkgs.aspellWithDicts (_: cfg.dicts)}/lib/aspell"; 222 }; 223 systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ]; 224 225 # Restart on config changes. 226 systemd.services.phpfpm-roundcube.restartTriggers = [ 227 config.environment.etc."roundcube/config.inc.php".source 228 ]; 229 230 systemd.services.roundcube-setup = mkMerge [ 231 (mkIf (cfg.database.host == "localhost") { 232 requires = [ "postgresql.service" ]; 233 after = [ "postgresql.service" ]; 234 path = [ config.services.postgresql.package ]; 235 }) 236 { 237 after = [ "network-online.target" ]; 238 wantedBy = [ "multi-user.target" ]; 239 script = let 240 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}"; 241 in 242 '' 243 version="$(${psql} -t <<< "select value from system where name = 'roundcube-version';" || true)" 244 if ! (grep -E '[a-zA-Z0-9]' <<< "$version"); then 245 ${psql} -f ${cfg.package}/SQL/postgres.initial.sql 246 fi 247 248 if [ ! -f /var/lib/roundcube/des_key ]; then 249 base64 /dev/urandom | head -c 24 > /var/lib/roundcube/des_key; 250 # we need to log out everyone in case change the des_key 251 # from the default when upgrading from nixos 19.09 252 ${psql} <<< 'TRUNCATE TABLE session;' 253 fi 254 255 ${phpWithPspell}/bin/php ${cfg.package}/bin/update.sh 256 ''; 257 serviceConfig = { 258 Type = "oneshot"; 259 StateDirectory = "roundcube"; 260 User = if localDB then user else "nginx"; 261 # so that the des_key is not world readable 262 StateDirectoryMode = "0700"; 263 }; 264 } 265 ]; 266 }; 267}