at 21.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 literalExample 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.php.withExtensions 60 ({ enabled, all }: with all; [ iconv mbstring curl openssl tokenizer xmlrpc soap ctype zip gd simplexml dom intl json sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo filter ]); 61in 62{ 63 # interface 64 options.services.moodle = { 65 enable = mkEnableOption "Moodle web application"; 66 67 package = mkOption { 68 type = types.package; 69 default = pkgs.moodle; 70 defaultText = "pkgs.moodle"; 71 description = "The Moodle package to use."; 72 }; 73 74 initialPassword = mkOption { 75 type = types.str; 76 example = "correcthorsebatterystaple"; 77 description = '' 78 Specifies the initial password for the admin, i.e. the password assigned if the user does not already exist. 79 The password specified here is world-readable in the Nix store, so it should be changed promptly. 80 ''; 81 }; 82 83 database = { 84 type = mkOption { 85 type = types.enum [ "mysql" "pgsql" ]; 86 default = "mysql"; 87 description = "Database engine to use."; 88 }; 89 90 host = mkOption { 91 type = types.str; 92 default = "localhost"; 93 description = "Database host address."; 94 }; 95 96 port = mkOption { 97 type = types.int; 98 description = "Database host port."; 99 default = { 100 mysql = 3306; 101 pgsql = 5432; 102 }.${cfg.database.type}; 103 defaultText = "3306"; 104 }; 105 106 name = mkOption { 107 type = types.str; 108 default = "moodle"; 109 description = "Database name."; 110 }; 111 112 user = mkOption { 113 type = types.str; 114 default = "moodle"; 115 description = "Database user."; 116 }; 117 118 passwordFile = mkOption { 119 type = types.nullOr types.path; 120 default = null; 121 example = "/run/keys/moodle-dbpassword"; 122 description = '' 123 A file containing the password corresponding to 124 <option>database.user</option>. 125 ''; 126 }; 127 128 socket = mkOption { 129 type = types.nullOr types.path; 130 default = 131 if mysqlLocal then "/run/mysqld/mysqld.sock" 132 else if pgsqlLocal then "/run/postgresql" 133 else null; 134 defaultText = "/run/mysqld/mysqld.sock"; 135 description = "Path to the unix socket file to use for authentication."; 136 }; 137 138 createLocally = mkOption { 139 type = types.bool; 140 default = true; 141 description = "Create the database and database user locally."; 142 }; 143 }; 144 145 virtualHost = mkOption { 146 type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); 147 example = literalExample '' 148 { 149 hostName = "moodle.example.org"; 150 adminAddr = "webmaster@example.org"; 151 forceSSL = true; 152 enableACME = true; 153 } 154 ''; 155 description = '' 156 Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>. 157 See <xref linkend="opt-services.httpd.virtualHosts"/> for further information. 158 ''; 159 }; 160 161 poolConfig = mkOption { 162 type = with types; attrsOf (oneOf [ str int bool ]); 163 default = { 164 "pm" = "dynamic"; 165 "pm.max_children" = 32; 166 "pm.start_servers" = 2; 167 "pm.min_spare_servers" = 2; 168 "pm.max_spare_servers" = 4; 169 "pm.max_requests" = 500; 170 }; 171 description = '' 172 Options for the Moodle PHP pool. See the documentation on <literal>php-fpm.conf</literal> 173 for details on configuration directives. 174 ''; 175 }; 176 177 extraConfig = mkOption { 178 type = types.lines; 179 default = ""; 180 description = '' 181 Any additional text to be appended to the config.php 182 configuration file. This is a PHP script. For configuration 183 details, see <link xlink:href="https://docs.moodle.org/37/en/Configuration_file"/>. 184 ''; 185 example = '' 186 $CFG->disableupdatenotifications = true; 187 ''; 188 }; 189 }; 190 191 # implementation 192 config = mkIf cfg.enable { 193 194 assertions = [ 195 { assertion = cfg.database.createLocally -> cfg.database.user == user; 196 message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true"; 197 } 198 { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; 199 message = "a password cannot be specified if services.moodle.database.createLocally is set to true"; 200 } 201 ]; 202 203 services.mysql = mkIf mysqlLocal { 204 enable = true; 205 package = mkDefault pkgs.mariadb; 206 ensureDatabases = [ cfg.database.name ]; 207 ensureUsers = [ 208 { name = cfg.database.user; 209 ensurePermissions = { 210 "${cfg.database.name}.*" = "SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER"; 211 }; 212 } 213 ]; 214 }; 215 216 services.postgresql = mkIf pgsqlLocal { 217 enable = true; 218 ensureDatabases = [ cfg.database.name ]; 219 ensureUsers = [ 220 { name = cfg.database.user; 221 ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; 222 } 223 ]; 224 }; 225 226 services.phpfpm.pools.moodle = { 227 inherit user group; 228 phpPackage = phpExt; 229 phpEnv.MOODLE_CONFIG = "${moodleConfig}"; 230 phpOptions = '' 231 zend_extension = opcache.so 232 opcache.enable = 1 233 ''; 234 settings = { 235 "listen.owner" = config.services.httpd.user; 236 "listen.group" = config.services.httpd.group; 237 } // cfg.poolConfig; 238 }; 239 240 services.httpd = { 241 enable = true; 242 adminAddr = mkDefault cfg.virtualHost.adminAddr; 243 extraModules = [ "proxy_fcgi" ]; 244 virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { 245 documentRoot = mkForce "${cfg.package}/share/moodle"; 246 extraConfig = '' 247 <Directory "${cfg.package}/share/moodle"> 248 <FilesMatch "\.php$"> 249 <If "-f %{REQUEST_FILENAME}"> 250 SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" 251 </If> 252 </FilesMatch> 253 Options -Indexes 254 DirectoryIndex index.php 255 </Directory> 256 ''; 257 } ]; 258 }; 259 260 systemd.tmpfiles.rules = [ 261 "d '${stateDir}' 0750 ${user} ${group} - -" 262 ]; 263 264 systemd.services.moodle-init = { 265 wantedBy = [ "multi-user.target" ]; 266 before = [ "phpfpm-moodle.service" ]; 267 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 268 environment.MOODLE_CONFIG = moodleConfig; 269 script = '' 270 ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$? 271 272 [ "$rc" == 1 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \ 273 --non-interactive \ 274 --allow-unstable 275 276 [ "$rc" == 2 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \ 277 --agree-license \ 278 --adminpass=${cfg.initialPassword} 279 280 true 281 ''; 282 serviceConfig = { 283 User = user; 284 Group = group; 285 Type = "oneshot"; 286 }; 287 }; 288 289 systemd.services.moodle-cron = { 290 description = "Moodle cron service"; 291 after = [ "moodle-init.service" ]; 292 environment.MOODLE_CONFIG = moodleConfig; 293 serviceConfig = { 294 User = user; 295 Group = group; 296 ExecStart = "${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php"; 297 }; 298 }; 299 300 systemd.timers.moodle-cron = { 301 description = "Moodle cron timer"; 302 wantedBy = [ "timers.target" ]; 303 timerConfig = { 304 OnCalendar = "minutely"; 305 }; 306 }; 307 308 systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 309 310 users.users.${user} = { 311 group = group; 312 isSystemUser = true; 313 }; 314 }; 315}