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