{ config, pkgs, lib, ... }: let inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption; inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionals optionalString types; cfg = config.services.mediawiki; fpm = config.services.phpfpm.pools.mediawiki; user = "mediawiki"; group = if cfg.webserver == "apache" then config.services.httpd.group else "mediawiki"; cacheDir = "/var/cache/mediawiki"; stateDir = "/var/lib/mediawiki"; pkg = pkgs.stdenv.mkDerivation rec { pname = "mediawiki-full"; version = src.version; src = cfg.package; installPhase = '' mkdir -p $out cp -r * $out/ rm -rf $out/share/mediawiki/skins/* rm -rf $out/share/mediawiki/extensions/* ${concatStringsSep "\n" (mapAttrsToList (k: v: '' ln -s ${v} $out/share/mediawiki/skins/${k} '') cfg.skins)} ${concatStringsSep "\n" (mapAttrsToList (k: v: '' ln -s ${if v != null then v else "$src/share/mediawiki/extensions/${k}"} $out/share/mediawiki/extensions/${k} '') cfg.extensions)} ''; }; mediawikiScripts = pkgs.runCommand "mediawiki-scripts" { nativeBuildInputs = [ pkgs.makeWrapper ]; preferLocalBuild = true; } '' mkdir -p $out/bin for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do makeWrapper ${pkgs.php}/bin/php $out/bin/mediawiki-$(basename $i .php) \ --set MEDIAWIKI_CONFIG ${mediawikiConfig} \ --add-flags ${pkg}/share/mediawiki/maintenance/$i done ''; dbAddr = if cfg.database.socket == null then "${cfg.database.host}:${toString cfg.database.port}" else if cfg.database.type == "mysql" then "${cfg.database.host}:${cfg.database.socket}" else if cfg.database.type == "postgres" then "${cfg.database.socket}" else throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}"; mediawikiConfig = pkgs.writeText "LocalSettings.php" '' . ''; }; socket = mkOption { type = types.nullOr types.path; default = if (cfg.database.type == "mysql" && cfg.database.createLocally) then "/run/mysqld/mysqld.sock" else if (cfg.database.type == "postgres" && cfg.database.createLocally) then "/run/postgresql" else null; defaultText = literalExpression "/run/mysqld/mysqld.sock"; description = lib.mdDoc "Path to the unix socket file to use for authentication."; }; createLocally = mkOption { type = types.bool; default = cfg.database.type == "mysql" || cfg.database.type == "postgres"; defaultText = literalExpression "true"; description = lib.mdDoc '' Create the database and database user locally. This currently only applies if database type "mysql" is selected. ''; }; }; httpd.virtualHost = mkOption { type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); example = literalExpression '' { hostName = "mediawiki.example.org"; adminAddr = "webmaster@example.org"; forceSSL = true; enableACME = true; } ''; description = lib.mdDoc '' Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`. See [](#opt-services.httpd.virtualHosts) for further information. ''; }; poolConfig = mkOption { type = with types; attrsOf (oneOf [ str int bool ]); default = { "pm" = "dynamic"; "pm.max_children" = 32; "pm.start_servers" = 2; "pm.min_spare_servers" = 2; "pm.max_spare_servers" = 4; "pm.max_requests" = 500; }; description = lib.mdDoc '' Options for the MediaWiki PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives. ''; }; extraConfig = mkOption { type = types.lines; description = lib.mdDoc '' Any additional text to be appended to MediaWiki's LocalSettings.php configuration file. For configuration settings, see . ''; default = ""; example = '' $wgEnableEmail = false; ''; }; }; }; imports = [ (lib.mkRenamedOptionModule [ "services" "mediawiki" "virtualHost" ] [ "services" "mediawiki" "httpd" "virtualHost" ]) ]; # implementation config = mkIf cfg.enable { assertions = [ { assertion = cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres"); message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'"; } { assertion = cfg.database.createLocally -> cfg.database.user == user; message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true"; } { assertion = cfg.database.createLocally -> cfg.database.socket != null; message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true"; } { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true"; } ]; services.mediawiki.skins = { MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook"; Timeless = "${cfg.package}/share/mediawiki/skins/Timeless"; Vector = "${cfg.package}/share/mediawiki/skins/Vector"; }; services.mysql = mkIf (cfg.database.type == "mysql" && cfg.database.createLocally) { enable = true; package = mkDefault pkgs.mariadb; ensureDatabases = [ cfg.database.name ]; ensureUsers = [{ name = cfg.database.user; ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; }]; }; services.postgresql = mkIf (cfg.database.type == "postgres" && cfg.database.createLocally) { enable = true; ensureDatabases = [ cfg.database.name ]; ensureUsers = [{ name = cfg.database.user; ensurePermissions = { "DATABASE \"${cfg.database.name}\"" = "ALL PRIVILEGES"; }; }]; }; services.phpfpm.pools.mediawiki = { inherit user group; phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}"; settings = (if (cfg.webserver == "apache") then { "listen.owner" = config.services.httpd.user; "listen.group" = config.services.httpd.group; } else { "listen.owner" = user; "listen.group" = group; }) // cfg.poolConfig; }; services.httpd = lib.mkIf (cfg.webserver == "apache") { enable = true; extraModules = [ "proxy_fcgi" ]; virtualHosts.${cfg.httpd.virtualHost.hostName} = mkMerge [ cfg.httpd.virtualHost { documentRoot = mkForce "${pkg}/share/mediawiki"; extraConfig = '' SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" Require all granted DirectoryIndex index.php AllowOverride All '' + optionalString (cfg.uploadsDir != null) '' Alias "/images" "${cfg.uploadsDir}" Require all granted ''; } ]; }; systemd.tmpfiles.rules = [ "d '${stateDir}' 0750 ${user} ${group} - -" "d '${cacheDir}' 0750 ${user} ${group} - -" ] ++ optionals (cfg.uploadsDir != null) [ "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -" "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -" ]; systemd.services.mediawiki-init = { wantedBy = [ "multi-user.target" ]; before = [ "phpfpm-mediawiki.service" ]; after = optional (cfg.database.type == "mysql" && cfg.database.createLocally) "mysql.service" ++ optional (cfg.database.type == "postgres" && cfg.database.createLocally) "postgresql.service"; script = '' if ! test -e "${stateDir}/secret.key"; then tr -dc A-Za-z0-9 /dev/null | head -c 64 > ${stateDir}/secret.key fi echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \ ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \ ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \ --confpath /tmp \ --scriptpath / \ --dbserver "${dbAddr}" \ --dbport ${toString cfg.database.port} \ --dbname ${cfg.database.name} \ ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \ --dbuser ${cfg.database.user} \ ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \ --passfile ${cfg.passwordFile} \ --dbtype ${cfg.database.type} \ ${cfg.name} \ admin ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick ''; serviceConfig = { Type = "oneshot"; User = user; Group = group; PrivateTmp = true; }; }; systemd.services.httpd.after = optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service" ++ optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "postgres") "postgresql.service"; users.users.${user} = { group = group; isSystemUser = true; }; users.groups.${group} = {}; environment.systemPackages = [ mediawikiScripts ]; }; }