at 25.11-pre 11 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 inherit (lib) 10 mkDefault 11 mkEnableOption 12 mkPackageOption 13 mkForce 14 mkIf 15 mkMerge 16 mkOption 17 types 18 ; 19 inherit (lib) 20 concatStringsSep 21 literalExpression 22 mapAttrsToList 23 optional 24 optionalString 25 ; 26 27 cfg = config.services.moodle; 28 fpm = config.services.phpfpm.pools.moodle; 29 30 user = "moodle"; 31 group = config.services.httpd.group; 32 stateDir = "/var/lib/moodle"; 33 34 moodleConfig = pkgs.writeText "config.php" '' 35 <?php // Moodle configuration file 36 37 unset($CFG); 38 global $CFG; 39 $CFG = new stdClass(); 40 41 $CFG->dbtype = '${ 42 { 43 mysql = "mariadb"; 44 pgsql = "pgsql"; 45 } 46 .${cfg.database.type} 47 }'; 48 $CFG->dblibrary = 'native'; 49 $CFG->dbhost = '${cfg.database.host}'; 50 $CFG->dbname = '${cfg.database.name}'; 51 $CFG->dbuser = '${cfg.database.user}'; 52 ${optionalString ( 53 cfg.database.passwordFile != null 54 ) "$CFG->dbpass = file_get_contents('${cfg.database.passwordFile}');"} 55 $CFG->prefix = 'mdl_'; 56 $CFG->dboptions = array ( 57 'dbpersist' => 0, 58 'dbport' => '${toString cfg.database.port}', 59 ${optionalString (cfg.database.socket != null) "'dbsocket' => '${cfg.database.socket}',"} 60 'dbcollation' => 'utf8mb4_unicode_ci', 61 ); 62 63 $CFG->wwwroot = '${ 64 if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then 65 "https" 66 else 67 "http" 68 }://${cfg.virtualHost.hostName}'; 69 $CFG->dataroot = '${stateDir}'; 70 $CFG->admin = 'admin'; 71 72 $CFG->directorypermissions = 02777; 73 $CFG->disableupdateautodeploy = true; 74 75 $CFG->pathtogs = '${pkgs.ghostscript}/bin/gs'; 76 $CFG->pathtophp = '${phpExt}/bin/php'; 77 $CFG->pathtodu = '${pkgs.coreutils}/bin/du'; 78 $CFG->aspellpath = '${pkgs.aspell}/bin/aspell'; 79 $CFG->pathtodot = '${pkgs.graphviz}/bin/dot'; 80 81 ${cfg.extraConfig} 82 83 require_once('${cfg.package}/share/moodle/lib/setup.php'); 84 85 // There is no php closing tag in this file, 86 // it is intentional because it prevents trailing whitespace problems! 87 ''; 88 89 mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; 90 pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; 91 92 phpExt = pkgs.php83.buildEnv { 93 extensions = 94 { all, ... }: 95 with all; 96 [ 97 iconv 98 mbstring 99 curl 100 openssl 101 tokenizer 102 soap 103 ctype 104 zip 105 gd 106 simplexml 107 dom 108 intl 109 sqlite3 110 pgsql 111 pdo_sqlite 112 pdo_pgsql 113 pdo_odbc 114 pdo_mysql 115 pdo 116 mysqli 117 session 118 zlib 119 xmlreader 120 fileinfo 121 filter 122 opcache 123 exif 124 sodium 125 ]; 126 extraConfig = "max_input_vars = 5000"; 127 }; 128in 129{ 130 # interface 131 options.services.moodle = { 132 enable = mkEnableOption "Moodle web application"; 133 134 package = mkPackageOption pkgs "moodle" { }; 135 136 initialPassword = mkOption { 137 type = types.str; 138 example = "correcthorsebatterystaple"; 139 description = '' 140 Specifies the initial password for the admin, i.e. the password assigned if the user does not already exist. 141 The password specified here is world-readable in the Nix store, so it should be changed promptly. 142 ''; 143 }; 144 145 database = { 146 type = mkOption { 147 type = types.enum [ 148 "mysql" 149 "pgsql" 150 ]; 151 default = "mysql"; 152 description = "Database engine to use."; 153 }; 154 155 host = mkOption { 156 type = types.str; 157 default = "localhost"; 158 description = "Database host address."; 159 }; 160 161 port = mkOption { 162 type = types.port; 163 description = "Database host port."; 164 default = 165 { 166 mysql = 3306; 167 pgsql = 5432; 168 } 169 .${cfg.database.type}; 170 defaultText = literalExpression "3306"; 171 }; 172 173 name = mkOption { 174 type = types.str; 175 default = "moodle"; 176 description = "Database name."; 177 }; 178 179 user = mkOption { 180 type = types.str; 181 default = "moodle"; 182 description = "Database user."; 183 }; 184 185 passwordFile = mkOption { 186 type = types.nullOr types.path; 187 default = null; 188 example = "/run/keys/moodle-dbpassword"; 189 description = '' 190 A file containing the password corresponding to 191 {option}`database.user`. 192 ''; 193 }; 194 195 socket = mkOption { 196 type = types.nullOr types.path; 197 default = 198 if mysqlLocal then 199 "/run/mysqld/mysqld.sock" 200 else if pgsqlLocal then 201 "/run/postgresql" 202 else 203 null; 204 defaultText = literalExpression "/run/mysqld/mysqld.sock"; 205 description = "Path to the unix socket file to use for authentication."; 206 }; 207 208 createLocally = mkOption { 209 type = types.bool; 210 default = true; 211 description = "Create the database and database user locally."; 212 }; 213 }; 214 215 virtualHost = mkOption { 216 type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); 217 example = literalExpression '' 218 { 219 hostName = "moodle.example.org"; 220 adminAddr = "webmaster@example.org"; 221 forceSSL = true; 222 enableACME = true; 223 } 224 ''; 225 description = '' 226 Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`. 227 See [](#opt-services.httpd.virtualHosts) for further information. 228 ''; 229 }; 230 231 poolConfig = mkOption { 232 type = 233 with types; 234 attrsOf (oneOf [ 235 str 236 int 237 bool 238 ]); 239 default = { 240 "pm" = "dynamic"; 241 "pm.max_children" = 32; 242 "pm.start_servers" = 2; 243 "pm.min_spare_servers" = 2; 244 "pm.max_spare_servers" = 4; 245 "pm.max_requests" = 500; 246 }; 247 description = '' 248 Options for the Moodle PHP pool. See the documentation on `php-fpm.conf` 249 for details on configuration directives. 250 ''; 251 }; 252 253 extraConfig = mkOption { 254 type = types.lines; 255 default = ""; 256 description = '' 257 Any additional text to be appended to the config.php 258 configuration file. This is a PHP script. For configuration 259 details, see <https://docs.moodle.org/37/en/Configuration_file>. 260 ''; 261 example = '' 262 $CFG->disableupdatenotifications = true; 263 ''; 264 }; 265 }; 266 267 # implementation 268 config = mkIf cfg.enable { 269 270 assertions = [ 271 { 272 assertion = 273 cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name; 274 message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true"; 275 } 276 { 277 assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; 278 message = "a password cannot be specified if services.moodle.database.createLocally is set to true"; 279 } 280 ]; 281 282 services.mysql = mkIf mysqlLocal { 283 enable = true; 284 package = mkDefault pkgs.mariadb; 285 ensureDatabases = [ cfg.database.name ]; 286 ensureUsers = [ 287 { 288 name = cfg.database.user; 289 ensurePermissions = { 290 "${cfg.database.name}.*" = 291 "SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER"; 292 }; 293 } 294 ]; 295 }; 296 297 services.postgresql = mkIf pgsqlLocal { 298 enable = true; 299 ensureDatabases = [ cfg.database.name ]; 300 ensureUsers = [ 301 { 302 name = cfg.database.user; 303 ensureDBOwnership = true; 304 } 305 ]; 306 }; 307 308 services.phpfpm.pools.moodle = { 309 inherit user group; 310 phpPackage = phpExt; 311 phpEnv.MOODLE_CONFIG = "${moodleConfig}"; 312 phpOptions = '' 313 zend_extension = opcache.so 314 opcache.enable = 1 315 max_input_vars = 5000 316 ''; 317 settings = { 318 "listen.owner" = config.services.httpd.user; 319 "listen.group" = config.services.httpd.group; 320 } // cfg.poolConfig; 321 }; 322 323 services.httpd = { 324 enable = true; 325 adminAddr = mkDefault cfg.virtualHost.adminAddr; 326 extraModules = [ "proxy_fcgi" ]; 327 virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ 328 cfg.virtualHost 329 { 330 documentRoot = mkForce "${cfg.package}/share/moodle"; 331 extraConfig = '' 332 <Directory "${cfg.package}/share/moodle"> 333 <FilesMatch "\.php$"> 334 <If "-f %{REQUEST_FILENAME}"> 335 SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" 336 </If> 337 </FilesMatch> 338 Options -Indexes 339 DirectoryIndex index.php 340 </Directory> 341 ''; 342 } 343 ]; 344 }; 345 346 systemd.tmpfiles.settings."10-moodle".${stateDir}.d = { 347 inherit user group; 348 mode = "0750"; 349 }; 350 351 systemd.services.moodle-init = { 352 wantedBy = [ "multi-user.target" ]; 353 before = [ "phpfpm-moodle.service" ]; 354 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 355 environment.MOODLE_CONFIG = moodleConfig; 356 script = '' 357 ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$? 358 359 [ "$rc" == 1 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \ 360 --non-interactive \ 361 --allow-unstable 362 363 [ "$rc" == 2 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \ 364 --agree-license \ 365 --adminpass=${cfg.initialPassword} 366 367 true 368 ''; 369 serviceConfig = { 370 User = user; 371 Group = group; 372 Type = "oneshot"; 373 }; 374 }; 375 376 systemd.services.moodle-cron = { 377 description = "Moodle cron service"; 378 after = [ "moodle-init.service" ]; 379 environment.MOODLE_CONFIG = moodleConfig; 380 serviceConfig = { 381 User = user; 382 Group = group; 383 ExecStart = "${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php"; 384 }; 385 }; 386 387 systemd.timers.moodle-cron = { 388 description = "Moodle cron timer"; 389 wantedBy = [ "timers.target" ]; 390 timerConfig = { 391 OnCalendar = "minutely"; 392 }; 393 }; 394 395 systemd.services.httpd.after = 396 optional mysqlLocal "mysql.service" 397 ++ optional pgsqlLocal "postgresql.service"; 398 399 users.users.${user} = { 400 group = group; 401 isSystemUser = true; 402 }; 403 }; 404}