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