{ 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 = config.services.httpd.group; 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" { buildInputs = [ 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 ''; mediawikiConfig = pkgs.writeText "LocalSettings.php" '' skins subdirectory of the MediaWiki installation in addition to the default skins. ''; }; extensions = mkOption { default = {}; type = types.attrsOf (types.nullOr types.path); description = '' Attribute set of paths whose content is copied to the extensions subdirectory of the MediaWiki installation and enabled in configuration. Use null instead of path to enable extensions that are part of MediaWiki. ''; example = literalExpression '' { Matomo = pkgs.fetchzip { url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz"; sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b"; }; ParserFunctions = null; } ''; }; database = { type = mkOption { type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ]; default = "mysql"; description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers."; }; host = mkOption { type = types.str; default = "localhost"; description = "Database host address."; }; port = mkOption { type = types.port; default = 3306; description = "Database host port."; }; name = mkOption { type = types.str; default = "mediawiki"; description = "Database name."; }; user = mkOption { type = types.str; default = "mediawiki"; description = "Database user."; }; passwordFile = mkOption { type = types.nullOr types.path; default = null; example = "/run/keys/mediawiki-dbpassword"; description = '' A file containing the password corresponding to . ''; }; tablePrefix = mkOption { type = types.nullOr types.str; default = null; description = '' If you only have access to a single database and wish to install more than one version of MediaWiki, or have other applications that also use the database, you can give the table names a unique prefix to stop any naming conflicts or confusion. See . ''; }; socket = mkOption { type = types.nullOr types.path; default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null; defaultText = literalExpression "/run/mysqld/mysqld.sock"; description = "Path to the unix socket file to use for authentication."; }; createLocally = mkOption { type = types.bool; default = cfg.database.type == "mysql"; defaultText = literalExpression "true"; description = '' Create the database and database user locally. This currently only applies if database type "mysql" is selected. ''; }; }; 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 = '' Apache configuration can be done by adapting . See 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 = '' Options for the MediaWiki PHP pool. See the documentation on php-fpm.conf for details on configuration directives. ''; }; extraConfig = mkOption { type = types.lines; description = '' Any additional text to be appended to MediaWiki's LocalSettings.php configuration file. For configuration settings, see . ''; default = ""; example = '' $wgEnableEmail = false; ''; }; }; }; # implementation config = mkIf cfg.enable { assertions = [ { assertion = cfg.database.createLocally -> cfg.database.type == "mysql"; message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'"; } { 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.createLocally { enable = true; package = mkDefault pkgs.mariadb; ensureDatabases = [ cfg.database.name ]; ensureUsers = [ { name = cfg.database.user; ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; } ]; }; services.phpfpm.pools.mediawiki = { inherit user group; phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}"; settings = { "listen.owner" = config.services.httpd.user; "listen.group" = config.services.httpd.group; } // cfg.poolConfig; }; services.httpd = { enable = true; extraModules = [ "proxy_fcgi" ]; virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.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.createLocally "mysql.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 ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \ --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} \ ${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.database.createLocally && cfg.database.type == "mysql") "mysql.service"; users.users.${user} = { group = group; isSystemUser = true; }; environment.systemPackages = [ mediawikiScripts ]; }; }