at 23.05-pre 9.1 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.php80.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 overriden 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 "Password file for the postgresql connection. Must be readable by user `nginx`. Ignored if `database.host` is set to `localhost`, as peer authentication will be used."; 74 }; 75 dbname = mkOption { 76 type = types.str; 77 default = "roundcube"; 78 description = lib.mdDoc "Name of the postgresql database"; 79 }; 80 }; 81 82 plugins = mkOption { 83 type = types.listOf types.str; 84 default = []; 85 description = lib.mdDoc '' 86 List of roundcube plugins to enable. Currently, only those directly shipped with Roundcube are supported. 87 ''; 88 }; 89 90 dicts = mkOption { 91 type = types.listOf types.package; 92 default = []; 93 example = literalExpression "with pkgs.aspellDicts; [ en fr de ]"; 94 description = lib.mdDoc '' 95 List of aspell dictionnaries for spell checking. If empty, spell checking is disabled. 96 ''; 97 }; 98 99 maxAttachmentSize = mkOption { 100 type = types.int; 101 default = 18; 102 description = lib.mdDoc '' 103 The maximum attachment size in MB. 104 105 Note: Since roundcube only uses 70% of max upload values configured in php 106 30% is added automatically to [](#opt-services.roundcube.maxAttachmentSize). 107 ''; 108 apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M"; 109 }; 110 111 extraConfig = mkOption { 112 type = types.lines; 113 default = ""; 114 description = lib.mdDoc "Extra configuration for roundcube webmail instance"; 115 }; 116 }; 117 118 config = mkIf cfg.enable { 119 # backward compatibility: if password is set but not passwordFile, make one. 120 services.roundcube.database.passwordFile = mkIf (!localDB && cfg.database.password != "") (mkDefault ("${pkgs.writeText "roundcube-password" cfg.database.password}")); 121 warnings = lib.optional (!localDB && cfg.database.password != "") "services.roundcube.database.password is deprecated and insecure; use services.roundcube.database.passwordFile instead"; 122 123 environment.etc."roundcube/config.inc.php".text = '' 124 <?php 125 126 ${lib.optionalString (!localDB) "$password = file_get_contents('${cfg.database.passwordFile}');"} 127 128 $config = array(); 129 $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}'; 130 $config['log_driver'] = 'syslog'; 131 $config['max_message_size'] = '${cfg.maxAttachmentSize}'; 132 $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}]; 133 $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key'); 134 $config['mime_types'] = '${pkgs.nginx}/conf/mime.types'; 135 $config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"}; 136 # by default, spellchecking uses a third-party cloud services 137 $config['spellcheck_engine'] = 'pspell'; 138 $config['spellcheck_languages'] = array(${lib.concatMapStringsSep ", " (dict: let p = builtins.parseDrvName dict.shortName; in "'${p.name}' => '${dict.fullName}'") cfg.dicts}); 139 140 ${cfg.extraConfig} 141 ''; 142 143 services.nginx = { 144 enable = true; 145 virtualHosts = { 146 ${cfg.hostName} = { 147 forceSSL = mkDefault true; 148 enableACME = mkDefault true; 149 locations."/" = { 150 root = cfg.package; 151 index = "index.php"; 152 extraConfig = '' 153 location ~* \.php$ { 154 fastcgi_split_path_info ^(.+\.php)(/.+)$; 155 fastcgi_pass unix:${fpm.socket}; 156 include ${config.services.nginx.package}/conf/fastcgi_params; 157 include ${pkgs.nginx}/conf/fastcgi.conf; 158 } 159 ''; 160 }; 161 }; 162 }; 163 }; 164 165 services.postgresql = mkIf localDB { 166 enable = true; 167 ensureDatabases = [ cfg.database.dbname ]; 168 ensureUsers = [ { 169 name = cfg.database.username; 170 ensurePermissions = { 171 "DATABASE ${cfg.database.username}" = "ALL PRIVILEGES"; 172 }; 173 } ]; 174 }; 175 176 users.users.${user} = mkIf localDB { 177 group = user; 178 isSystemUser = true; 179 createHome = false; 180 }; 181 users.groups.${user} = mkIf localDB {}; 182 183 services.phpfpm.pools.roundcube = { 184 user = if localDB then user else "nginx"; 185 phpOptions = '' 186 error_log = 'stderr' 187 log_errors = on 188 post_max_size = ${cfg.maxAttachmentSize} 189 upload_max_filesize = ${cfg.maxAttachmentSize} 190 ''; 191 settings = mapAttrs (name: mkDefault) { 192 "listen.owner" = "nginx"; 193 "listen.group" = "nginx"; 194 "listen.mode" = "0660"; 195 "pm" = "dynamic"; 196 "pm.max_children" = 75; 197 "pm.start_servers" = 2; 198 "pm.min_spare_servers" = 1; 199 "pm.max_spare_servers" = 20; 200 "pm.max_requests" = 500; 201 "catch_workers_output" = true; 202 }; 203 phpPackage = phpWithPspell; 204 phpEnv.ASPELL_CONF = "dict-dir ${pkgs.aspellWithDicts (_: cfg.dicts)}/lib/aspell"; 205 }; 206 systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ]; 207 208 # Restart on config changes. 209 systemd.services.phpfpm-roundcube.restartTriggers = [ 210 config.environment.etc."roundcube/config.inc.php".source 211 ]; 212 213 systemd.services.roundcube-setup = mkMerge [ 214 (mkIf (cfg.database.host == "localhost") { 215 requires = [ "postgresql.service" ]; 216 after = [ "postgresql.service" ]; 217 path = [ config.services.postgresql.package ]; 218 }) 219 { 220 wantedBy = [ "multi-user.target" ]; 221 script = let 222 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}"; 223 in 224 '' 225 version="$(${psql} -t <<< "select value from system where name = 'roundcube-version';" || true)" 226 if ! (grep -E '[a-zA-Z0-9]' <<< "$version"); then 227 ${psql} -f ${cfg.package}/SQL/postgres.initial.sql 228 fi 229 230 if [ ! -f /var/lib/roundcube/des_key ]; then 231 base64 /dev/urandom | head -c 24 > /var/lib/roundcube/des_key; 232 # we need to log out everyone in case change the des_key 233 # from the default when upgrading from nixos 19.09 234 ${psql} <<< 'TRUNCATE TABLE session;' 235 fi 236 237 ${phpWithPspell}/bin/php ${cfg.package}/bin/update.sh 238 ''; 239 serviceConfig = { 240 Type = "oneshot"; 241 StateDirectory = "roundcube"; 242 User = if localDB then user else "nginx"; 243 # so that the des_key is not world readable 244 StateDirectoryMode = "0700"; 245 }; 246 } 247 ]; 248 }; 249}