1 { config, lib, pkgs, ... }:
4 inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption types;
5 inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionalString;
7 cfg = config.services.moodle;
8 fpm = config.services.phpfpm.pools.moodle;
11 group = config.services.httpd.group;
12 stateDir = "/var/lib/moodle";
14 moodleConfig = pkgs.writeText "config.php" ''
15 <?php // Moodle configuration file
19 $CFG = new stdClass();
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 (
30 'dbport' => '${toString cfg.database.port}',
31 ${optionalString (cfg.database.socket != null) "'dbsocket' => '${cfg.database.socket}',"}
32 'dbcollation' => 'utf8mb4_unicode_ci',
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';
39 $CFG->directorypermissions = 02777;
40 $CFG->disableupdateautodeploy = true;
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';
50 require_once('${cfg.package}/share/moodle/lib/setup.php');
52 // There is no php closing tag in this file,
53 // it is intentional because it prevents trailing whitespace problems!
56 mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
57 pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
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";
66 options.services.moodle = {
67 enable = mkEnableOption "Moodle web application";
69 package = mkPackageOption pkgs "moodle" { };
71 initialPassword = mkOption {
73 example = "correcthorsebatterystaple";
75 Specifies the initial password for the admin, i.e. the password assigned if the user does not already exist.
76 The password specified here is world-readable in the Nix store, so it should be changed promptly.
82 type = types.enum [ "mysql" "pgsql" ];
84 description = "Database engine to use.";
89 default = "localhost";
90 description = "Database host address.";
95 description = "Database host port.";
99 }.${cfg.database.type};
100 defaultText = literalExpression "3306";
106 description = "Database name.";
112 description = "Database user.";
115 passwordFile = mkOption {
116 type = types.nullOr types.path;
118 example = "/run/keys/moodle-dbpassword";
120 A file containing the password corresponding to
121 {option}`database.user`.
126 type = types.nullOr types.path;
128 if mysqlLocal then "/run/mysqld/mysqld.sock"
129 else if pgsqlLocal then "/run/postgresql"
131 defaultText = literalExpression "/run/mysqld/mysqld.sock";
132 description = "Path to the unix socket file to use for authentication.";
135 createLocally = mkOption {
138 description = "Create the database and database user locally.";
142 virtualHost = mkOption {
143 type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
144 example = literalExpression ''
146 hostName = "moodle.example.org";
147 adminAddr = "webmaster@example.org";
153 Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
154 See [](#opt-services.httpd.virtualHosts) for further information.
158 poolConfig = mkOption {
159 type = with types; attrsOf (oneOf [ str int bool ]);
162 "pm.max_children" = 32;
163 "pm.start_servers" = 2;
164 "pm.min_spare_servers" = 2;
165 "pm.max_spare_servers" = 4;
166 "pm.max_requests" = 500;
169 Options for the Moodle PHP pool. See the documentation on `php-fpm.conf`
170 for details on configuration directives.
174 extraConfig = mkOption {
178 Any additional text to be appended to the config.php
179 configuration file. This is a PHP script. For configuration
180 details, see <https://docs.moodle.org/37/en/Configuration_file>.
183 $CFG->disableupdatenotifications = true;
189 config = mkIf cfg.enable {
192 { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
193 message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true";
195 { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
196 message = "a password cannot be specified if services.moodle.database.createLocally is set to true";
200 services.mysql = mkIf mysqlLocal {
202 package = mkDefault pkgs.mariadb;
203 ensureDatabases = [ cfg.database.name ];
205 { name = cfg.database.user;
206 ensurePermissions = {
207 "${cfg.database.name}.*" = "SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER";
213 services.postgresql = mkIf pgsqlLocal {
215 ensureDatabases = [ cfg.database.name ];
217 { name = cfg.database.user;
218 ensureDBOwnership = true;
223 services.phpfpm.pools.moodle = {
226 phpEnv.MOODLE_CONFIG = "${moodleConfig}";
228 zend_extension = opcache.so
230 max_input_vars = 5000
233 "listen.owner" = config.services.httpd.user;
234 "listen.group" = config.services.httpd.group;
240 adminAddr = mkDefault cfg.virtualHost.adminAddr;
241 extraModules = [ "proxy_fcgi" ];
242 virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
243 documentRoot = mkForce "${cfg.package}/share/moodle";
245 <Directory "${cfg.package}/share/moodle">
246 <FilesMatch "\.php$">
247 <If "-f %{REQUEST_FILENAME}">
248 SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
252 DirectoryIndex index.php
258 systemd.tmpfiles.settings."10-moodle".${stateDir}.d = {
263 systemd.services.moodle-init = {
264 wantedBy = [ "multi-user.target" ];
265 before = [ "phpfpm-moodle.service" ];
266 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
267 environment.MOODLE_CONFIG = moodleConfig;
269 ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$?
271 [ "$rc" == 1 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \
275 [ "$rc" == 2 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \
277 --adminpass=${cfg.initialPassword}
288 systemd.services.moodle-cron = {
289 description = "Moodle cron service";
290 after = [ "moodle-init.service" ];
291 environment.MOODLE_CONFIG = moodleConfig;
295 ExecStart = "${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php";
299 systemd.timers.moodle-cron = {
300 description = "Moodle cron timer";
301 wantedBy = [ "timers.target" ];
303 OnCalendar = "minutely";
307 systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
309 users.users.${user} = {