at 23.11-pre 10 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 5 inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption; 6 inherit (lib) literalExpression mapAttrs optional optionalString types; 7 8 cfg = config.services.limesurvey; 9 fpm = config.services.phpfpm.pools.limesurvey; 10 11 user = "limesurvey"; 12 group = config.services.httpd.group; 13 stateDir = "/var/lib/limesurvey"; 14 15 pkg = pkgs.limesurvey; 16 17 configType = with types; oneOf [ (attrsOf configType) str int bool ] // { 18 description = "limesurvey config type (str, int, bool or attribute set thereof)"; 19 }; 20 21 limesurveyConfig = pkgs.writeText "config.php" '' 22 <?php 23 return json_decode('${builtins.toJSON cfg.config}', true); 24 ?> 25 ''; 26 27 mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; 28 pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; 29 30in 31{ 32 # interface 33 34 options.services.limesurvey = { 35 enable = mkEnableOption (lib.mdDoc "Limesurvey web application"); 36 37 encryptionKey = mkOption { 38 type = types.str; 39 default = "E17687FC77CEE247F0E22BB3ECF27FDE8BEC310A892347EC13013ABA11AA7EB5"; 40 description = lib.mdDoc '' 41 This is a 32-byte key used to encrypt variables in the database. 42 You _must_ change this from the default value. 43 ''; 44 }; 45 46 encryptionNonce = mkOption { 47 type = types.str; 48 default = "1ACC8555619929DB91310BE848025A427B0F364A884FFA77"; 49 description = lib.mdDoc '' 50 This is a 24-byte nonce used to encrypt variables in the database. 51 You _must_ change this from the default value. 52 ''; 53 }; 54 55 database = { 56 type = mkOption { 57 type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ]; 58 example = "pgsql"; 59 default = "mysql"; 60 description = lib.mdDoc "Database engine to use."; 61 }; 62 63 dbEngine = mkOption { 64 type = types.enum [ "MyISAM" "InnoDB" ]; 65 default = "InnoDB"; 66 description = lib.mdDoc "Database storage engine to use."; 67 }; 68 69 host = mkOption { 70 type = types.str; 71 default = "localhost"; 72 description = lib.mdDoc "Database host address."; 73 }; 74 75 port = mkOption { 76 type = types.port; 77 default = if cfg.database.type == "pgsql" then 5442 else 3306; 78 defaultText = literalExpression "3306"; 79 description = lib.mdDoc "Database host port."; 80 }; 81 82 name = mkOption { 83 type = types.str; 84 default = "limesurvey"; 85 description = lib.mdDoc "Database name."; 86 }; 87 88 user = mkOption { 89 type = types.str; 90 default = "limesurvey"; 91 description = lib.mdDoc "Database user."; 92 }; 93 94 passwordFile = mkOption { 95 type = types.nullOr types.path; 96 default = null; 97 example = "/run/keys/limesurvey-dbpassword"; 98 description = lib.mdDoc '' 99 A file containing the password corresponding to 100 {option}`database.user`. 101 ''; 102 }; 103 104 socket = mkOption { 105 type = types.nullOr types.path; 106 default = 107 if mysqlLocal then "/run/mysqld/mysqld.sock" 108 else if pgsqlLocal then "/run/postgresql" 109 else null 110 ; 111 defaultText = literalExpression "/run/mysqld/mysqld.sock"; 112 description = lib.mdDoc "Path to the unix socket file to use for authentication."; 113 }; 114 115 createLocally = mkOption { 116 type = types.bool; 117 default = cfg.database.type == "mysql"; 118 defaultText = literalExpression "true"; 119 description = lib.mdDoc '' 120 Create the database and database user locally. 121 This currently only applies if database type "mysql" is selected. 122 ''; 123 }; 124 }; 125 126 virtualHost = mkOption { 127 type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); 128 example = literalExpression '' 129 { 130 hostName = "survey.example.org"; 131 adminAddr = "webmaster@example.org"; 132 forceSSL = true; 133 enableACME = true; 134 } 135 ''; 136 description = lib.mdDoc '' 137 Apache configuration can be done by adapting `services.httpd.virtualHosts.<name>`. 138 See [](#opt-services.httpd.virtualHosts) for further information. 139 ''; 140 }; 141 142 poolConfig = mkOption { 143 type = with types; attrsOf (oneOf [ str int bool ]); 144 default = { 145 "pm" = "dynamic"; 146 "pm.max_children" = 32; 147 "pm.start_servers" = 2; 148 "pm.min_spare_servers" = 2; 149 "pm.max_spare_servers" = 4; 150 "pm.max_requests" = 500; 151 }; 152 description = lib.mdDoc '' 153 Options for the LimeSurvey PHP pool. See the documentation on `php-fpm.conf` 154 for details on configuration directives. 155 ''; 156 }; 157 158 config = mkOption { 159 type = configType; 160 default = {}; 161 description = lib.mdDoc '' 162 LimeSurvey configuration. Refer to 163 <https://manual.limesurvey.org/Optional_settings> 164 for details on supported values. 165 ''; 166 }; 167 }; 168 169 # implementation 170 171 config = mkIf cfg.enable { 172 173 assertions = [ 174 { assertion = cfg.database.createLocally -> cfg.database.type == "mysql"; 175 message = "services.limesurvey.createLocally is currently only supported for database type 'mysql'"; 176 } 177 { assertion = cfg.database.createLocally -> cfg.database.user == user; 178 message = "services.limesurvey.database.user must be set to ${user} if services.limesurvey.database.createLocally is set true"; 179 } 180 { assertion = cfg.database.createLocally -> cfg.database.socket != null; 181 message = "services.limesurvey.database.socket must be set if services.limesurvey.database.createLocally is set to true"; 182 } 183 { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; 184 message = "a password cannot be specified if services.limesurvey.database.createLocally is set to true"; 185 } 186 ]; 187 188 services.limesurvey.config = mapAttrs (name: mkDefault) { 189 runtimePath = "${stateDir}/tmp/runtime"; 190 components = { 191 db = { 192 connectionString = "${cfg.database.type}:dbname=${cfg.database.name};host=${if pgsqlLocal then cfg.database.socket else cfg.database.host};port=${toString cfg.database.port}" + 193 optionalString mysqlLocal ";socket=${cfg.database.socket}"; 194 username = cfg.database.user; 195 password = mkIf (cfg.database.passwordFile != null) "file_get_contents(\"${toString cfg.database.passwordFile}\");"; 196 tablePrefix = "limesurvey_"; 197 }; 198 assetManager.basePath = "${stateDir}/tmp/assets"; 199 urlManager = { 200 urlFormat = "path"; 201 showScriptName = false; 202 }; 203 }; 204 config = { 205 tempdir = "${stateDir}/tmp"; 206 uploaddir = "${stateDir}/upload"; 207 encryptionnonce = cfg.encryptionNonce; 208 encryptionsecretboxkey = cfg.encryptionKey; 209 force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on"; 210 config.defaultlang = "en"; 211 }; 212 }; 213 214 services.mysql = mkIf mysqlLocal { 215 enable = true; 216 package = mkDefault pkgs.mariadb; 217 ensureDatabases = [ cfg.database.name ]; 218 ensureUsers = [ 219 { name = cfg.database.user; 220 ensurePermissions = { 221 "${cfg.database.name}.*" = "SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP, INDEX"; 222 }; 223 } 224 ]; 225 }; 226 227 services.phpfpm.pools.limesurvey = { 228 inherit user group; 229 phpPackage = pkgs.php80; 230 phpEnv.DBENGINE = "${cfg.database.dbEngine}"; 231 phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}"; 232 settings = { 233 "listen.owner" = config.services.httpd.user; 234 "listen.group" = config.services.httpd.group; 235 } // cfg.poolConfig; 236 }; 237 238 services.httpd = { 239 enable = true; 240 adminAddr = mkDefault cfg.virtualHost.adminAddr; 241 extraModules = [ "proxy_fcgi" ]; 242 virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { 243 documentRoot = mkForce "${pkg}/share/limesurvey"; 244 extraConfig = '' 245 Alias "/tmp" "${stateDir}/tmp" 246 <Directory "${stateDir}"> 247 AllowOverride all 248 Require all granted 249 Options -Indexes +FollowSymlinks 250 </Directory> 251 252 Alias "/upload" "${stateDir}/upload" 253 <Directory "${stateDir}/upload"> 254 AllowOverride all 255 Require all granted 256 Options -Indexes 257 </Directory> 258 259 <Directory "${pkg}/share/limesurvey"> 260 <FilesMatch "\.php$"> 261 <If "-f %{REQUEST_FILENAME}"> 262 SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" 263 </If> 264 </FilesMatch> 265 266 AllowOverride all 267 Options -Indexes 268 DirectoryIndex index.php 269 </Directory> 270 ''; 271 } ]; 272 }; 273 274 systemd.tmpfiles.rules = [ 275 "d ${stateDir} 0750 ${user} ${group} - -" 276 "d ${stateDir}/tmp 0750 ${user} ${group} - -" 277 "d ${stateDir}/tmp/assets 0750 ${user} ${group} - -" 278 "d ${stateDir}/tmp/runtime 0750 ${user} ${group} - -" 279 "d ${stateDir}/tmp/upload 0750 ${user} ${group} - -" 280 "C ${stateDir}/upload 0750 ${user} ${group} - ${pkg}/share/limesurvey/upload" 281 ]; 282 283 systemd.services.limesurvey-init = { 284 wantedBy = [ "multi-user.target" ]; 285 before = [ "phpfpm-limesurvey.service" ]; 286 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 287 environment.DBENGINE = "${cfg.database.dbEngine}"; 288 environment.LIMESURVEY_CONFIG = limesurveyConfig; 289 script = '' 290 # update or install the database as required 291 ${pkgs.php80}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \ 292 ${pkgs.php80}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose 293 ''; 294 serviceConfig = { 295 User = user; 296 Group = group; 297 Type = "oneshot"; 298 }; 299 }; 300 301 systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 302 303 users.users.${user} = { 304 group = group; 305 isSystemUser = true; 306 }; 307 308 }; 309}