at 23.11-pre 20 kB view raw
1{ config, pkgs, lib, ... }: 2 3let 4 5 inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption; 6 inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionals optionalString types; 7 8 cfg = config.services.mediawiki; 9 fpm = config.services.phpfpm.pools.mediawiki; 10 user = "mediawiki"; 11 group = if cfg.webserver == "apache" then "apache" else "mediawiki"; 12 13 cacheDir = "/var/cache/mediawiki"; 14 stateDir = "/var/lib/mediawiki"; 15 16 pkg = pkgs.stdenv.mkDerivation rec { 17 pname = "mediawiki-full"; 18 version = src.version; 19 src = cfg.package; 20 21 installPhase = '' 22 mkdir -p $out 23 cp -r * $out/ 24 25 rm -rf $out/share/mediawiki/skins/* 26 rm -rf $out/share/mediawiki/extensions/* 27 28 ${concatStringsSep "\n" (mapAttrsToList (k: v: '' 29 ln -s ${v} $out/share/mediawiki/skins/${k} 30 '') cfg.skins)} 31 32 ${concatStringsSep "\n" (mapAttrsToList (k: v: '' 33 ln -s ${if v != null then v else "$src/share/mediawiki/extensions/${k}"} $out/share/mediawiki/extensions/${k} 34 '') cfg.extensions)} 35 ''; 36 }; 37 38 mediawikiScripts = pkgs.runCommand "mediawiki-scripts" { 39 nativeBuildInputs = [ pkgs.makeWrapper ]; 40 preferLocalBuild = true; 41 } '' 42 mkdir -p $out/bin 43 for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do 44 makeWrapper ${pkgs.php}/bin/php $out/bin/mediawiki-$(basename $i .php) \ 45 --set MEDIAWIKI_CONFIG ${mediawikiConfig} \ 46 --add-flags ${pkg}/share/mediawiki/maintenance/$i 47 done 48 ''; 49 50 dbAddr = if cfg.database.socket == null then 51 "${cfg.database.host}:${toString cfg.database.port}" 52 else if cfg.database.type == "mysql" then 53 "${cfg.database.host}:${cfg.database.socket}" 54 else if cfg.database.type == "postgres" then 55 "${cfg.database.socket}" 56 else 57 throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}"; 58 59 mediawikiConfig = pkgs.writeText "LocalSettings.php" '' 60 <?php 61 # Protect against web entry 62 if ( !defined( 'MEDIAWIKI' ) ) { 63 exit; 64 } 65 66 $wgSitename = "${cfg.name}"; 67 $wgMetaNamespace = false; 68 69 ## The URL base path to the directory containing the wiki; 70 ## defaults for all runtime URL paths are based off of this. 71 ## For more information on customizing the URLs 72 ## (like /w/index.php/Page_title to /wiki/Page_title) please see: 73 ## https://www.mediawiki.org/wiki/Manual:Short_URL 74 $wgScriptPath = ""; 75 76 ## The protocol and server name to use in fully-qualified URLs 77 $wgServer = "${cfg.url}"; 78 79 ## The URL path to static resources (images, scripts, etc.) 80 $wgResourceBasePath = $wgScriptPath; 81 82 ## The URL path to the logo. Make sure you change this from the default, 83 ## or else you'll overwrite your logo when you upgrade! 84 $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png"; 85 86 ## UPO means: this is also a user preference option 87 88 $wgEnableEmail = true; 89 $wgEnableUserEmail = true; # UPO 90 91 $wgPasswordSender = "${cfg.passwordSender}"; 92 93 $wgEnotifUserTalk = false; # UPO 94 $wgEnotifWatchlist = false; # UPO 95 $wgEmailAuthentication = true; 96 97 ## Database settings 98 $wgDBtype = "${cfg.database.type}"; 99 $wgDBserver = "${dbAddr}"; 100 $wgDBport = "${toString cfg.database.port}"; 101 $wgDBname = "${cfg.database.name}"; 102 $wgDBuser = "${cfg.database.user}"; 103 ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"} 104 105 ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) '' 106 # MySQL specific settings 107 $wgDBprefix = "${cfg.database.tablePrefix}"; 108 ''} 109 110 ${optionalString (cfg.database.type == "mysql") '' 111 # MySQL table options to use during installation or update 112 $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary"; 113 ''} 114 115 ## Shared memory settings 116 $wgMainCacheType = CACHE_NONE; 117 $wgMemCachedServers = []; 118 119 ${optionalString (cfg.uploadsDir != null) '' 120 $wgEnableUploads = true; 121 $wgUploadDirectory = "${cfg.uploadsDir}"; 122 ''} 123 124 $wgUseImageMagick = true; 125 $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert"; 126 127 # InstantCommons allows wiki to use images from https://commons.wikimedia.org 128 $wgUseInstantCommons = false; 129 130 # Periodically send a pingback to https://www.mediawiki.org/ with basic data 131 # about this MediaWiki instance. The Wikimedia Foundation shares this data 132 # with MediaWiki developers to help guide future development efforts. 133 $wgPingback = true; 134 135 ## If you use ImageMagick (or any other shell command) on a 136 ## Linux server, this will need to be set to the name of an 137 ## available UTF-8 locale 138 $wgShellLocale = "C.UTF-8"; 139 140 ## Set $wgCacheDirectory to a writable directory on the web server 141 ## to make your wiki go slightly faster. The directory should not 142 ## be publicly accessible from the web. 143 $wgCacheDirectory = "${cacheDir}"; 144 145 # Site language code, should be one of the list in ./languages/data/Names.php 146 $wgLanguageCode = "en"; 147 148 $wgSecretKey = file_get_contents("${stateDir}/secret.key"); 149 150 # Changing this will log out all existing sessions. 151 $wgAuthenticationTokenVersion = ""; 152 153 ## For attaching licensing metadata to pages, and displaying an 154 ## appropriate copyright notice / icon. GNU Free Documentation 155 ## License and Creative Commons licenses are supported so far. 156 $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright 157 $wgRightsUrl = ""; 158 $wgRightsText = ""; 159 $wgRightsIcon = ""; 160 161 # Path to the GNU diff3 utility. Used for conflict resolution. 162 $wgDiff = "${pkgs.diffutils}/bin/diff"; 163 $wgDiff3 = "${pkgs.diffutils}/bin/diff3"; 164 165 # Enabled skins. 166 ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)} 167 168 # Enabled extensions. 169 ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)} 170 171 172 # End of automatically generated settings. 173 # Add more configuration options below. 174 175 ${cfg.extraConfig} 176 ''; 177 178in 179{ 180 # interface 181 options = { 182 services.mediawiki = { 183 184 enable = mkEnableOption (lib.mdDoc "MediaWiki"); 185 186 package = mkOption { 187 type = types.package; 188 default = pkgs.mediawiki; 189 defaultText = literalExpression "pkgs.mediawiki"; 190 description = lib.mdDoc "Which MediaWiki package to use."; 191 }; 192 193 finalPackage = mkOption { 194 type = types.package; 195 readOnly = true; 196 default = pkg; 197 defaultText = literalExpression "pkg"; 198 description = lib.mdDoc '' 199 The final package used by the module. This is the package that will have extensions and skins installed. 200 ''; 201 }; 202 203 name = mkOption { 204 type = types.str; 205 default = "MediaWiki"; 206 example = "Foobar Wiki"; 207 description = lib.mdDoc "Name of the wiki."; 208 }; 209 210 url = mkOption { 211 type = types.str; 212 default = if cfg.webserver == "apache" then 213 "${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://${cfg.httpd.virtualHost.hostName}" 214 else 215 "http://localhost"; 216 defaultText = literalExpression '' 217 if cfg.webserver == "apache" then 218 "''${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://''${cfg.httpd.virtualHost.hostName}" 219 else 220 "http://localhost"; 221 ''; 222 example = "https://wiki.example.org"; 223 description = lib.mdDoc "URL of the wiki."; 224 }; 225 226 uploadsDir = mkOption { 227 type = types.nullOr types.path; 228 default = "${stateDir}/uploads"; 229 description = lib.mdDoc '' 230 This directory is used for uploads of pictures. The directory passed here is automatically 231 created and permissions adjusted as required. 232 ''; 233 }; 234 235 passwordFile = mkOption { 236 type = types.path; 237 description = lib.mdDoc "A file containing the initial password for the admin user."; 238 example = "/run/keys/mediawiki-password"; 239 }; 240 241 passwordSender = mkOption { 242 type = types.str; 243 default = 244 if cfg.webserver == "apache" then 245 if cfg.httpd.virtualHost.adminAddr != null then 246 cfg.httpd.virtualHost.adminAddr 247 else 248 config.services.httpd.adminAddr else "root@localhost"; 249 defaultText = literalExpression '' 250 if cfg.webserver == "apache" then 251 if cfg.httpd.virtualHost.adminAddr != null then 252 cfg.httpd.virtualHost.adminAddr 253 else 254 config.services.httpd.adminAddr else "root@localhost" 255 ''; 256 description = lib.mdDoc "Contact address for password reset."; 257 }; 258 259 skins = mkOption { 260 default = {}; 261 type = types.attrsOf types.path; 262 description = lib.mdDoc '' 263 Attribute set of paths whose content is copied to the {file}`skins` 264 subdirectory of the MediaWiki installation in addition to the default skins. 265 ''; 266 }; 267 268 extensions = mkOption { 269 default = {}; 270 type = types.attrsOf (types.nullOr types.path); 271 description = lib.mdDoc '' 272 Attribute set of paths whose content is copied to the {file}`extensions` 273 subdirectory of the MediaWiki installation and enabled in configuration. 274 275 Use `null` instead of path to enable extensions that are part of MediaWiki. 276 ''; 277 example = literalExpression '' 278 { 279 Matomo = pkgs.fetchzip { 280 url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz"; 281 sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b"; 282 }; 283 ParserFunctions = null; 284 } 285 ''; 286 }; 287 288 webserver = mkOption { 289 type = types.enum [ "apache" "none" ]; 290 default = "apache"; 291 description = lib.mdDoc "Webserver to use."; 292 }; 293 294 database = { 295 type = mkOption { 296 type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ]; 297 default = "mysql"; 298 description = lib.mdDoc "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers."; 299 }; 300 301 host = mkOption { 302 type = types.str; 303 default = "localhost"; 304 description = lib.mdDoc "Database host address."; 305 }; 306 307 port = mkOption { 308 type = types.port; 309 default = if cfg.database.type == "mysql" then 3306 else 5432; 310 defaultText = literalExpression "3306"; 311 description = lib.mdDoc "Database host port."; 312 }; 313 314 name = mkOption { 315 type = types.str; 316 default = "mediawiki"; 317 description = lib.mdDoc "Database name."; 318 }; 319 320 user = mkOption { 321 type = types.str; 322 default = "mediawiki"; 323 description = lib.mdDoc "Database user."; 324 }; 325 326 passwordFile = mkOption { 327 type = types.nullOr types.path; 328 default = null; 329 example = "/run/keys/mediawiki-dbpassword"; 330 description = lib.mdDoc '' 331 A file containing the password corresponding to 332 {option}`database.user`. 333 ''; 334 }; 335 336 tablePrefix = mkOption { 337 type = types.nullOr types.str; 338 default = null; 339 description = lib.mdDoc '' 340 If you only have access to a single database and wish to install more than 341 one version of MediaWiki, or have other applications that also use the 342 database, you can give the table names a unique prefix to stop any naming 343 conflicts or confusion. 344 See <https://www.mediawiki.org/wiki/Manual:$wgDBprefix>. 345 ''; 346 }; 347 348 socket = mkOption { 349 type = types.nullOr types.path; 350 default = if (cfg.database.type == "mysql" && cfg.database.createLocally) then 351 "/run/mysqld/mysqld.sock" 352 else if (cfg.database.type == "postgres" && cfg.database.createLocally) then 353 "/run/postgresql" 354 else 355 null; 356 defaultText = literalExpression "/run/mysqld/mysqld.sock"; 357 description = lib.mdDoc "Path to the unix socket file to use for authentication."; 358 }; 359 360 createLocally = mkOption { 361 type = types.bool; 362 default = cfg.database.type == "mysql" || cfg.database.type == "postgres"; 363 defaultText = literalExpression "true"; 364 description = lib.mdDoc '' 365 Create the database and database user locally. 366 This currently only applies if database type "mysql" is selected. 367 ''; 368 }; 369 }; 370 371 httpd.virtualHost = mkOption { 372 type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); 373 example = literalExpression '' 374 { 375 hostName = "mediawiki.example.org"; 376 adminAddr = "webmaster@example.org"; 377 forceSSL = true; 378 enableACME = true; 379 } 380 ''; 381 description = lib.mdDoc '' 382 Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`. 383 See [](#opt-services.httpd.virtualHosts) for further information. 384 ''; 385 }; 386 387 poolConfig = mkOption { 388 type = with types; attrsOf (oneOf [ str int bool ]); 389 default = { 390 "pm" = "dynamic"; 391 "pm.max_children" = 32; 392 "pm.start_servers" = 2; 393 "pm.min_spare_servers" = 2; 394 "pm.max_spare_servers" = 4; 395 "pm.max_requests" = 500; 396 }; 397 description = lib.mdDoc '' 398 Options for the MediaWiki PHP pool. See the documentation on `php-fpm.conf` 399 for details on configuration directives. 400 ''; 401 }; 402 403 extraConfig = mkOption { 404 type = types.lines; 405 description = lib.mdDoc '' 406 Any additional text to be appended to MediaWiki's 407 LocalSettings.php configuration file. For configuration 408 settings, see <https://www.mediawiki.org/wiki/Manual:Configuration_settings>. 409 ''; 410 default = ""; 411 example = '' 412 $wgEnableEmail = false; 413 ''; 414 }; 415 416 }; 417 }; 418 419 imports = [ 420 (lib.mkRenamedOptionModule [ "services" "mediawiki" "virtualHost" ] [ "services" "mediawiki" "httpd" "virtualHost" ]) 421 ]; 422 423 # implementation 424 config = mkIf cfg.enable { 425 426 assertions = [ 427 { assertion = cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres"); 428 message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'"; 429 } 430 { assertion = cfg.database.createLocally -> cfg.database.user == user; 431 message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true"; 432 } 433 { assertion = cfg.database.createLocally -> cfg.database.socket != null; 434 message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true"; 435 } 436 { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; 437 message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true"; 438 } 439 ]; 440 441 services.mediawiki.skins = { 442 MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook"; 443 Timeless = "${cfg.package}/share/mediawiki/skins/Timeless"; 444 Vector = "${cfg.package}/share/mediawiki/skins/Vector"; 445 }; 446 447 services.mysql = mkIf (cfg.database.type == "mysql" && cfg.database.createLocally) { 448 enable = true; 449 package = mkDefault pkgs.mariadb; 450 ensureDatabases = [ cfg.database.name ]; 451 ensureUsers = [{ 452 name = cfg.database.user; 453 ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; 454 }]; 455 }; 456 457 services.postgresql = mkIf (cfg.database.type == "postgres" && cfg.database.createLocally) { 458 enable = true; 459 ensureDatabases = [ cfg.database.name ]; 460 ensureUsers = [{ 461 name = cfg.database.user; 462 ensurePermissions = { "DATABASE \"${cfg.database.name}\"" = "ALL PRIVILEGES"; }; 463 }]; 464 }; 465 466 services.phpfpm.pools.mediawiki = { 467 inherit user group; 468 phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}"; 469 settings = (if (cfg.webserver == "apache") then { 470 "listen.owner" = config.services.httpd.user; 471 "listen.group" = config.services.httpd.group; 472 } else { 473 "listen.owner" = user; 474 "listen.group" = group; 475 }) // cfg.poolConfig; 476 }; 477 478 services.httpd = lib.mkIf (cfg.webserver == "apache") { 479 enable = true; 480 extraModules = [ "proxy_fcgi" ]; 481 virtualHosts.${cfg.httpd.virtualHost.hostName} = mkMerge [ 482 cfg.httpd.virtualHost 483 { 484 documentRoot = mkForce "${pkg}/share/mediawiki"; 485 extraConfig = '' 486 <Directory "${pkg}/share/mediawiki"> 487 <FilesMatch "\.php$"> 488 <If "-f %{REQUEST_FILENAME}"> 489 SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" 490 </If> 491 </FilesMatch> 492 493 Require all granted 494 DirectoryIndex index.php 495 AllowOverride All 496 </Directory> 497 '' + optionalString (cfg.uploadsDir != null) '' 498 Alias "/images" "${cfg.uploadsDir}" 499 <Directory "${cfg.uploadsDir}"> 500 Require all granted 501 </Directory> 502 ''; 503 } 504 ]; 505 }; 506 507 systemd.tmpfiles.rules = [ 508 "d '${stateDir}' 0750 ${user} ${group} - -" 509 "d '${cacheDir}' 0750 ${user} ${group} - -" 510 ] ++ optionals (cfg.uploadsDir != null) [ 511 "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -" 512 "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -" 513 ]; 514 515 systemd.services.mediawiki-init = { 516 wantedBy = [ "multi-user.target" ]; 517 before = [ "phpfpm-mediawiki.service" ]; 518 after = optional (cfg.database.type == "mysql" && cfg.database.createLocally) "mysql.service" 519 ++ optional (cfg.database.type == "postgres" && cfg.database.createLocally) "postgresql.service"; 520 script = '' 521 if ! test -e "${stateDir}/secret.key"; then 522 tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key 523 fi 524 525 echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \ 526 ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \ 527 ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \ 528 --confpath /tmp \ 529 --scriptpath / \ 530 --dbserver "${dbAddr}" \ 531 --dbport ${toString cfg.database.port} \ 532 --dbname ${cfg.database.name} \ 533 ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \ 534 --dbuser ${cfg.database.user} \ 535 ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \ 536 --passfile ${cfg.passwordFile} \ 537 --dbtype ${cfg.database.type} \ 538 ${cfg.name} \ 539 admin 540 541 ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick 542 ''; 543 544 serviceConfig = { 545 Type = "oneshot"; 546 User = user; 547 Group = group; 548 PrivateTmp = true; 549 }; 550 }; 551 552 systemd.services.httpd.after = optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service" 553 ++ optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "postgres") "postgresql.service"; 554 555 users.users.${user} = { 556 group = group; 557 isSystemUser = true; 558 }; 559 users.groups.${group} = {}; 560 561 environment.systemPackages = [ mediawikiScripts ]; 562 }; 563}