aboutsummaryrefslogtreecommitdiff
path: root/modules/apps/mediawiki
diff options
context:
space:
mode:
Diffstat (limited to 'modules/apps/mediawiki')
-rw-r--r--modules/apps/mediawiki/default.nix323
-rw-r--r--modules/apps/mediawiki/localSettings.nix158
2 files changed, 481 insertions, 0 deletions
diff --git a/modules/apps/mediawiki/default.nix b/modules/apps/mediawiki/default.nix
new file mode 100644
index 0000000..584d86a
--- /dev/null
+++ b/modules/apps/mediawiki/default.nix
@@ -0,0 +1,323 @@
+{ config, pkgs, lib, ... }:
+
+let
+
+ inherit (lib)
+ concatMapStrings concatMapStringsSep concatStringsSep
+ filterAttrs genAttrs hasPrefix mapAttrs mapAttrsToList mkDefault
+ mkEnableOption mkIf mkOption optionalAttrs optionalString
+ recursiveUpdate types;
+ inherit (types)
+ attrsOf bool either enum int lines listOf nullOr path str
+ submodule unspecified;
+ inherit (builtins)
+ attrNames elem filter isAttrs isBool isList isString toString;
+
+ cfg = config.nixsap.apps.mediawiki;
+ user = config.nixsap.apps.mediawiki.user;
+
+ defaultPool = {
+ listen.owner = config.nixsap.apps.nginx.user;
+ pm.max_children = 10;
+ pm.max_requests = 1000;
+ pm.max_spare_servers = 5;
+ pm.min_spare_servers = 3;
+ pm.strategy = "dynamic";
+ env.MEDIAWIKI_LOCAL_SETTINGS = "${localSettings}";
+ php_value = optionalAttrs (cfg.maxUploadSize != null) {
+ post_max_size = 2 * cfg.maxUploadSize;
+ upload_max_filesize = cfg.maxUploadSize;
+ };
+ };
+
+ explicit = filterAttrs (n: v: n != "_module" && v != null);
+ concatMapAttrsSep = s: f: attrs: concatStringsSep s (mapAttrsToList f attrs);
+ enabledExtentions = attrNames (filterAttrs (_: enabled: enabled) (explicit cfg.extensions));
+
+ keys = filter (hasPrefix "/run/keys/") (mapAttrsToList (_: o: o.password-file) cfg.users);
+
+ settings =
+ let
+ show = s: n: v:
+ if isBool v then (if v then "TRUE" else "FALSE")
+ else if isString v then "'${v}'"
+ else if isList v then "array(${concatMapStringsSep "," (i: "\n${s}'${toString i}'") v})"
+ else if isAttrs v then "array(${concatMapAttrsSep "," (p: q: "\n${s}'${p}' => ${show "${s} " p q}") (explicit v)})"
+ else toString v;
+ in pkgs.writePHPFile "LocalSettings.inc.php" ''
+ <?php
+ ${concatMapAttrsSep "\n"
+ (n: v: if isAttrs v
+ # XXX This will preserve or replace defaults,
+ # but would give odd result if any element were a list:
+ then "\$${n} = array_replace_recursive (\$${n}, ${show " " n v});"
+ else "\$${n} = ${show " " n v};")
+ (explicit cfg.localSettings)}
+ ?>
+ '';
+
+ localSettings = pkgs.writePHPFile "LocalSettings.php" ''
+ <?php
+ ${concatMapStringsSep "\n " (e:
+ "require_once ('${pkgs.mediawikiExtensions.${e}}/${e}.php');"
+ ) enabledExtentions
+ }
+
+ ${optionalString (elem "GraphViz" enabledExtentions)
+ "$wgGraphVizSettings->execPath = '${pkgs.graphviz}/bin/';"
+ }
+
+ ${optionalString (elem "MathJax" enabledExtentions) ''
+ # MathJax 0.7:
+ MathJax_Parser::$MathJaxJS = '${pkgs.mathJax}/MathJax.js?config=TeX-AMS-MML_HTMLorMML-full';
+ ''}
+
+ $wgDiff = '${pkgs.diffutils}/bin/diff';
+ $wgDiff3 = '${pkgs.diffutils}/bin/diff3';
+ $wgImageMagickConvertCommand = '${pkgs.imagemagick}/bin/convert';
+ ${optionalString (cfg.logo != null) ''
+ $wgLogo = '${cfg.logo}';
+ ''}
+ ${optionalString (cfg.maxUploadSize != null)
+ "$wgMaxUploadSize = ${toString cfg.maxUploadSize};"
+ }
+
+ require_once ('${settings}');
+
+ $wgDirectoryMode = 0750;
+ ?>
+ '';
+
+ mediawiki-db =
+ let
+ psql = pkgs.writeBashScript "mw-psql" ''
+ set -euo pipefail
+ exec ${pkgs.postgresql}/bin/psql -t -w \
+ -v ON_ERROR_STOP=1 \
+ ${optionalString (cfg.localSettings.wgDBserver != "")
+ "-h '${cfg.localSettings.wgDBserver}'"} \
+ -p ${toString cfg.localSettings.wgDBport} \
+ -U ${toString cfg.localSettings.wgDBuser} \
+ -d '${cfg.localSettings.wgDBname}' \
+ "$@"
+ '';
+ mysql = pkgs.writeBashScript "mw-mysql" ''
+ set -euo pipefail
+ exec ${pkgs.mysql}/bin/mysql -N \
+ ${optionalString (cfg.localSettings.wgDBserver != "")
+ "-h '${cfg.localSettings.wgDBserver}'"} \
+ -u ${toString cfg.localSettings.wgDBuser} \
+ -D '${cfg.localSettings.wgDBname}' \
+ "$@"
+ '';
+ in pkgs.writeBashScriptBin "mediawiki-db" ''
+ set -euo pipefail
+ ${if cfg.localSettings.wgDBtype == "postgres" then ''
+ while ! ${psql} -c ';'; do
+ sleep 5s
+ done
+ exist=$(${psql} -c "SELECT COUNT(1) FROM pg_class WHERE relname = 'mwuser';")
+ if [ "''${exist//[[:space:]]/}" -eq 0 ]; then
+ {
+ # XXX this script has BEGIN, but no COMMIT:
+ cat ${pkgs.mediawiki}/maintenance/postgres/tables.sql
+ echo 'COMMIT;'
+ } | ${psql}
+ fi
+ '' else ''
+ while ! ${mysql} -e ';'; do
+ sleep 5s
+ done
+ exist=$(${mysql} -e "SELECT COUNT(1) FROM information_schema.tables
+ WHERE table_schema='${cfg.localSettings.wgDBname}'
+ AND table_name='mwuser'")
+ if [ "''${exist//[[:space:]]/}" -eq 0 ]; then
+ {
+ cat ${pkgs.mediawiki}/maintenance/tables.sql
+ } | ${mysql}
+ fi
+ ''}
+
+ export MEDIAWIKI_LOCAL_SETTINGS='${localSettings}'
+ ${pkgs.php}/bin/php ${pkgs.mediawiki}/maintenance/update.php
+ ${concatMapAttrsSep "" (n: o: ''
+ pw=$(cat '${o.password-file}')
+ if [ -z "$pw" ]; then
+ echo 'WARNING: Using random password, because ${o.password-file} is empty or cannot be read' >&2
+ pw=$(${pkgs.pwgen}/bin/pwgen -1 13)
+ fi
+ ${pkgs.php}/bin/php ${pkgs.mediawiki}/maintenance/createAndPromote.php \
+ --force --${o.role} '${n}' "$pw"
+ '') cfg.users}
+ '';
+
+ mediawiki-upload = pkgs.writeBashScriptBin "mediawiki-upload" ''
+ set -euo pipefail
+ mkdir -v -p '${cfg.localSettings.wgUploadDirectory}'
+
+ ${optionalString (elem "GraphViz" enabledExtentions)
+ # XXX: GraphViz::getUploadSubdir: mkdir(/mediawiki/graphviz/images/, 16872) failed
+ # GraphViz fails to create the directory until you create the first graph.
+ "mkdir -v -p '${cfg.localSettings.wgUploadDirectory}/graphviz'"
+ }
+
+ chmod -Rc u=rwX,g=rX,o= '${cfg.localSettings.wgUploadDirectory}'
+ chown -Rc '${user}:${user}' '${cfg.localSettings.wgUploadDirectory}'
+ '';
+
+ nginx = ''
+ ${cfg.nginxServer}
+
+ ${optionalString (cfg.maxUploadSize != null)
+ "client_max_body_size ${toString cfg.maxUploadSize};"
+ }
+
+ root ${pkgs.mediawiki};
+ index index.php;
+
+ ${optionalString (cfg.logo != null) ''
+ location = ${cfg.logo} {
+ alias ${cfg.logo};
+ }
+ ''}
+
+ ${optionalString
+ (cfg.localSettings.wgEnableUploads
+ && hasPrefix "/" cfg.localSettings.wgUploadPath) ''
+ location ${cfg.localSettings.wgUploadPath} {
+ alias ${cfg.localSettings.wgUploadDirectory};
+ }
+ ''}
+
+ ${concatMapStrings (e: ''
+ location /extensions/${e} {
+ alias ${pkgs.mediawikiExtensions.${e}};
+ }
+ '') enabledExtentions
+ }
+
+ ${optionalString (elem "MathJax" enabledExtentions) ''
+ location ${pkgs.mathJax} {
+ alias ${pkgs.mathJax};
+ }
+ ''}
+
+ location / {
+ try_files $uri $uri/ @rewrite;
+ }
+
+ location @rewrite {
+ rewrite ^/(.*)$ /index.php?title=$1&$args;
+ }
+
+ location ^~ /maintenance/ {
+ return 403;
+ }
+
+ location ~ \.php$ {
+ fastcgi_pass unix:${config.nixsap.apps.php-fpm.mediawiki.pool.listen.socket};
+ include ${pkgs.nginx}/conf/fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ }
+ '';
+
+in {
+
+ options.nixsap.apps.mediawiki = {
+ enable = mkEnableOption "Mediawiki";
+ user = mkOption {
+ description = ''
+ The user the PHP-FPM pool runs as. And the owner of uploaded files.
+ '';
+ default = "mediawiki";
+ type = str;
+ };
+ nginxServer = mkOption {
+ type = lines;
+ default = "";
+ example = ''
+ listen 8080;
+ server_name wiki.example.net;
+ '';
+ };
+ fpmPool = mkOption {
+ description = "Options for the PHP FPM pool";
+ type = attrsOf unspecified;
+ default = {};
+ };
+ logo = mkOption {
+ description = "The site logo (the image displayed in the upper-left corner of the page)";
+ type = nullOr path;
+ default = null;
+ };
+ maxUploadSize = mkOption {
+ description = ''
+ Maximum allowed size for uploaded files (bytes).
+ This affects Mediawiki itself, Nginx and PHP.
+ '';
+ type = nullOr int;
+ default = null;
+ };
+ localSettings = mkOption {
+ description = "Variables in LocalSettings.php";
+ type = submodule (import ./localSettings.nix (explicit cfg.extensions));
+ default = {};
+ };
+ extensions = mkOption {
+ description = "Mediawiki extensions";
+ default = {};
+ type = submodule
+ { options = mapAttrs
+ (e: _:
+ mkOption {
+ description = "Enable the ${e} extension";
+ type = bool;
+ default = false;
+ }) pkgs.mediawikiExtensions;
+ };
+ };
+ users = mkOption {
+ description = "Mediawiki users (only bots or sysops)";
+ default = {};
+ type = attrsOf (submodule { options = {
+ role = mkOption { type = enum [ "bot" "sysop" ]; };
+ password-file = mkOption { type = path; };
+ }; });
+ };
+ };
+
+ config = mkIf cfg.enable {
+ nixsap.apps.php-fpm.mediawiki.pool =
+ recursiveUpdate defaultPool (cfg.fpmPool // { user = cfg.user ;});
+ nixsap.deployment.keyrings.${user} = keys;
+ users.users.${config.nixsap.apps.nginx.user}.extraGroups =
+ mkIf cfg.localSettings.wgEnableUploads [ user ];
+
+ nixsap.apps.nginx.http.servers.mediawiki = nginx;
+
+ systemd.services.mediawiki-db = {
+ description = "configure Mediawiki database";
+ after = [ "network.target" "local-fs.target" "keys.target" ];
+ wants = [ "keys.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ RemainAfterExit = true;
+ Type = "oneshot";
+ User = config.nixsap.apps.php-fpm.mediawiki.pool.user;
+ ExecStart = "${mediawiki-db}/bin/mediawiki-db";
+ };
+ };
+
+ systemd.services.mediawiki-upload = mkIf cfg.localSettings.wgEnableUploads {
+ description = "configure Mediawiki uploads";
+ after = [ "local-fs.target" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ RemainAfterExit = true;
+ Type = "oneshot";
+ ExecStart = "${mediawiki-upload}/bin/mediawiki-upload";
+ };
+ };
+ };
+}
+
diff --git a/modules/apps/mediawiki/localSettings.nix b/modules/apps/mediawiki/localSettings.nix
new file mode 100644
index 0000000..cbacd07
--- /dev/null
+++ b/modules/apps/mediawiki/localSettings.nix
@@ -0,0 +1,158 @@
+extensions:
+
+{ lib, ... }:
+let
+
+ inherit (builtins) elem;
+
+ inherit (lib)
+ concatStringsSep flip genAttrs mergeOneOption mkDefault mkOption
+ mkOptionType optionalAttrs optionals types ;
+ inherit (types)
+ attrsOf bool either enum int listOf nullOr path str submodule ;
+
+ just = t: mkOption { type = t; }; # mergeable defaults
+ default = d: t: mkOption { type = t; default = d; }; # overridable defaults
+ optional = t: mkOption { type = nullOr t; default = null; };
+ set = options: mkOption { type = submodule { inherit options; }; default = {}; };
+
+ # XXX https://github.com/NixOS/nixpkgs/issues/9826
+ enum' = values:
+ let show = v: let t = builtins.typeOf v;
+ in if t == "string" then ''"${v}"''
+ else if t == "int" then builtins.toString v
+ else ''<${t}>'';
+ in mkOptionType {
+ name = "one of ${concatStringsSep ", " (map show values)}";
+ check = flip elem values;
+ merge = mergeOneOption;
+ };
+
+ rights = [
+ "apihighlimits" "applychangetags" "autoconfirmed" "autopatrol"
+ "bigdelete" "block" "blockemail" "bot" "browsearchive"
+ "changetags" "createaccount" "createpage" "createtalk"
+ "delete" "deletedhistory" "deletedtext" "deletelogentry"
+ "deleterevision" "edit" "editinterface" "editmyoptions"
+ "editmyprivateinfo" "editmyusercss" "editmyuserjs"
+ "editmywatchlist" "editprotected" "editsemiprotected"
+ "editusercss" "editusercssjs" "edituserjs" "hideuser" "import"
+ "importupload" "ipblock-exempt" "managechangetags" "markbotedits"
+ "mergehistory" "minoredit" "move" "move-categorypages"
+ "move-rootuserpages" "move-subpages" "movefile" "nominornewtalk"
+ "noratelimit" "override-export-depth" "pagelang" "passwordreset"
+ "patrol" "patrolmarks" "protect" "proxyunbannable" "purge"
+ "read" "reupload" "reupload-own" "reupload-shared" "rollback"
+ "sendemail" "siteadmin" "suppressionlog" "suppressredirect"
+ "suppressrevision" "unblockself" "undelete" "unwatchedpages"
+ "upload" "upload_by_url" "userrights" "userrights-interwiki"
+ "viewmyprivateinfo" "viewmywatchlist" "writeapi"
+ ]
+ ++ optionals extensions.UserPageEditProtection [ "editalluserpages" ]
+ ;
+
+ wgGroupPermissions = set ( genAttrs [
+ "*" "user" "autoconfirmed" "bot" "sysop" "bureaucrat"
+ ] (_:
+ set ( genAttrs rights (_: optional bool) )
+ )
+ );
+
+
+ wgDefaultUserOptions = set (
+ {
+ diffonly = optional bool;
+ disablemail = optional bool;
+ enotifminoredits = optional bool;
+ enotifrevealaddr = optional bool;
+ enotifusertalkpages = optional bool;
+ enotifwatchlistpages = optional bool;
+ fancysig = optional bool;
+ gender = optional (enum [ "female" "male" "unknown" ]);
+ hideminor = optional bool;
+ justify = optional bool;
+ minordefault = optional bool;
+ nickname = optional str;
+ previewontop = optional bool;
+ quickbar = optional (enum' [ 0 1 2 3 4 5 ]);
+ realname = optional str;
+ rememberpassword = optional bool;
+ underline = optional (enum' [0 1 2]);
+ math = optional (enum' [0 1]);
+ usenewrc = optional bool;
+ imagesize = optional int;
+ skin = optional str;
+ } // optionalAttrs extensions.WikiEditor
+ {
+ usebetatoolbar = optional bool;
+ usebetatoolbar-cgd = optional bool;
+ usenavigabletoc = optional bool;
+ wikieditor-preview = optional bool;
+ wikieditor-publish = optional bool;
+ }
+ );
+
+in {
+ options = {
+ inherit wgDefaultUserOptions;
+ inherit wgGroupPermissions;
+ wgAllowCopyUploads = optional bool;
+ wgArticlePath = optional path;
+ wgCheckFileExtensions = optional bool;
+ wgCopyUploadsDomains = default [] (listOf str);
+ wgCopyUploadsFromSpecialUpload = optional bool;
+ wgDBcompress = optional bool;
+ wgDBerrorLog = optional path;
+ wgDBname = default "mediawiki" str;
+ wgDBport = default "3456" int;
+ wgDBserver = default "" str;
+ wgDBssl = optional bool;
+ wgDBtype = default "postgres" (enum ["mysql" "postgres"]);
+ wgDBuser = default "mediawiki" str;
+ wgDebugLogFile = optional path;
+ wgEnableUploads = default false bool;
+ wgFileBlacklist = just (listOf str);
+ wgFileExtensions = just (listOf str);
+ wgLanguageCode = optional str;
+ wgMaxShellMemory = optional int;
+ wgMaxShellTime = optional int;
+ wgMimeTypeBlacklist = just (listOf str);
+ wgScriptPath = optional str;
+ wgServer = optional str;
+ wgShowDBErrorBacktrace = optional bool;
+ wgShowExceptionDetails = optional bool;
+ wgSitename = default "Wiki" str;
+ wgStrictFileExtensions = optional bool;
+ wgStyleDirectory = optional path;
+ wgStylePath = optional path;
+ wgUploadDirectory = default "/mediawiki" path;
+ wgUploadPath = default "/_files" str;
+ wgUrlProtocols = just (listOf str);
+ wgUsePrivateIPs = optional bool;
+ } // optionalAttrs (extensions.UserPageEditProtection)
+ {
+ wgOnlyUserEditUserPage = optional bool;
+ };
+
+ config = {
+ wgUrlProtocols = [
+ "//" "bitcoin:" "ftp://" "ftps://" "geo:" "git://" "gopher://"
+ "http://" "https://" "irc://" "ircs://" "magnet:" "mailto:"
+ "mms://" "news:" "nntp://" "redis://" "sftp://" "sip:"
+ "sips:" "sms:" "ssh://" "svn://" "tel:" "telnet://" "urn:"
+ "worldwind://" "xmpp:" ];
+ wgFileExtensions = [ "gif" "jpeg" "jpg" "png" ];
+ wgFileBlacklist = [
+ "bat" "cgi" "cmd" "com" "cpl" "dll" "exe" "htm" "html" "jhtml"
+ "js" "jsb" "mht" "mhtml" "msi" "php" "php3" "php4" "php5"
+ "phps" "phtml" "pif" "pl" "py" "scr" "shtml" "vbs" "vxd"
+ "xht" "xhtml" ];
+ wgMimeTypeBlacklist = [
+ "application/x-msdownload" "application/x-msmetafile"
+ "application/x-php" "application/x-shellscript" "text/html"
+ "text/javascript" "text/scriptlet" "text/x-bash" "text/x-csh"
+ "text/x-javascript" "text/x-perl" "text/x-php" "text/x-python"
+ "text/x-sh" ];
+ };
+}
+