{ 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 ];
};
}