grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / web-apps / moodle.nix
blob7e2d59d3c3e7abd26c91e138fb14c49c89512c6b
1 { config, lib, pkgs, ... }:
3 let
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;
10   user = "moodle";
11   group = config.services.httpd.group;
12   stateDir = "/var/lib/moodle";
14   moodleConfig = pkgs.writeText "config.php" ''
15   <?php  // Moodle configuration file
17   unset($CFG);
18   global $CFG;
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 (
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   );
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';
48   ${cfg.extraConfig}
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!
54   '';
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";
62   };
65   # interface
66   options.services.moodle = {
67     enable = mkEnableOption "Moodle web application";
69     package = mkPackageOption pkgs "moodle" { };
71     initialPassword = mkOption {
72       type = types.str;
73       example = "correcthorsebatterystaple";
74       description = ''
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.
77       '';
78     };
80     database = {
81       type = mkOption {
82         type = types.enum [ "mysql" "pgsql" ];
83         default = "mysql";
84         description = "Database engine to use.";
85       };
87       host = mkOption {
88         type = types.str;
89         default = "localhost";
90         description = "Database host address.";
91       };
93       port = mkOption {
94         type = types.port;
95         description = "Database host port.";
96         default = {
97           mysql = 3306;
98           pgsql = 5432;
99         }.${cfg.database.type};
100         defaultText = literalExpression "3306";
101       };
103       name = mkOption {
104         type = types.str;
105         default = "moodle";
106         description = "Database name.";
107       };
109       user = mkOption {
110         type = types.str;
111         default = "moodle";
112         description = "Database user.";
113       };
115       passwordFile = mkOption {
116         type = types.nullOr types.path;
117         default = null;
118         example = "/run/keys/moodle-dbpassword";
119         description = ''
120           A file containing the password corresponding to
121           {option}`database.user`.
122         '';
123       };
125       socket = mkOption {
126         type = types.nullOr types.path;
127         default =
128           if mysqlLocal then "/run/mysqld/mysqld.sock"
129           else if pgsqlLocal then "/run/postgresql"
130           else null;
131         defaultText = literalExpression "/run/mysqld/mysqld.sock";
132         description = "Path to the unix socket file to use for authentication.";
133       };
135       createLocally = mkOption {
136         type = types.bool;
137         default = true;
138         description = "Create the database and database user locally.";
139       };
140     };
142     virtualHost = mkOption {
143       type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
144       example = literalExpression ''
145         {
146           hostName = "moodle.example.org";
147           adminAddr = "webmaster@example.org";
148           forceSSL = true;
149           enableACME = true;
150         }
151       '';
152       description = ''
153         Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
154         See [](#opt-services.httpd.virtualHosts) for further information.
155       '';
156     };
158     poolConfig = mkOption {
159       type = with types; attrsOf (oneOf [ str int bool ]);
160       default = {
161         "pm" = "dynamic";
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;
167       };
168       description = ''
169         Options for the Moodle PHP pool. See the documentation on `php-fpm.conf`
170         for details on configuration directives.
171       '';
172     };
174     extraConfig = mkOption {
175       type = types.lines;
176       default = "";
177       description = ''
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>.
181       '';
182       example = ''
183         $CFG->disableupdatenotifications = true;
184       '';
185     };
186   };
188   # implementation
189   config = mkIf cfg.enable {
191     assertions = [
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";
194       }
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";
197       }
198     ];
200     services.mysql = mkIf mysqlLocal {
201       enable = true;
202       package = mkDefault pkgs.mariadb;
203       ensureDatabases = [ cfg.database.name ];
204       ensureUsers = [
205         { name = cfg.database.user;
206           ensurePermissions = {
207             "${cfg.database.name}.*" = "SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER";
208           };
209         }
210       ];
211     };
213     services.postgresql = mkIf pgsqlLocal {
214       enable = true;
215       ensureDatabases = [ cfg.database.name ];
216       ensureUsers = [
217         { name = cfg.database.user;
218           ensureDBOwnership = true;
219         }
220       ];
221     };
223     services.phpfpm.pools.moodle = {
224       inherit user group;
225       phpPackage = phpExt;
226       phpEnv.MOODLE_CONFIG = "${moodleConfig}";
227       phpOptions = ''
228         zend_extension = opcache.so
229         opcache.enable = 1
230         max_input_vars = 5000
231       '';
232       settings = {
233         "listen.owner" = config.services.httpd.user;
234         "listen.group" = config.services.httpd.group;
235       } // cfg.poolConfig;
236     };
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 "${cfg.package}/share/moodle";
244         extraConfig = ''
245           <Directory "${cfg.package}/share/moodle">
246             <FilesMatch "\.php$">
247               <If "-f %{REQUEST_FILENAME}">
248                 SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
249               </If>
250             </FilesMatch>
251             Options -Indexes
252             DirectoryIndex index.php
253           </Directory>
254         '';
255       } ];
256     };
258     systemd.tmpfiles.settings."10-moodle".${stateDir}.d = {
259       inherit user group;
260       mode = "0750";
261     };
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;
268       script = ''
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 \
272           --non-interactive \
273           --allow-unstable
275         [ "$rc" == 2 ] && ${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \
276           --agree-license \
277           --adminpass=${cfg.initialPassword}
279         true
280       '';
281       serviceConfig = {
282         User = user;
283         Group = group;
284         Type = "oneshot";
285       };
286     };
288     systemd.services.moodle-cron = {
289       description = "Moodle cron service";
290       after = [ "moodle-init.service" ];
291       environment.MOODLE_CONFIG = moodleConfig;
292       serviceConfig = {
293         User = user;
294         Group = group;
295         ExecStart = "${phpExt}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php";
296       };
297     };
299     systemd.timers.moodle-cron = {
300       description = "Moodle cron timer";
301       wantedBy = [ "timers.target" ];
302       timerConfig = {
303         OnCalendar = "minutely";
304       };
305     };
307     systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
309     users.users.${user} = {
310       group = group;
311       isSystemUser = true;
312     };
313   };