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