at 21.11-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 literalExample 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 buildInputs = [ 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 "MediaWiki"; 175 176 package = mkOption { 177 type = types.package; 178 default = pkgs.mediawiki; 179 description = "Which MediaWiki package to use."; 180 }; 181 182 name = mkOption { 183 type = types.str; 184 default = "MediaWiki"; 185 example = "Foobar Wiki"; 186 description = "Name of the wiki."; 187 }; 188 189 uploadsDir = mkOption { 190 type = types.nullOr types.path; 191 default = "${stateDir}/uploads"; 192 description = '' 193 This directory is used for uploads of pictures. The directory passed here is automatically 194 created and permissions adjusted as required. 195 ''; 196 }; 197 198 passwordFile = mkOption { 199 type = types.path; 200 description = "A file containing the initial password for the admin user."; 201 example = "/run/keys/mediawiki-password"; 202 }; 203 204 skins = mkOption { 205 default = {}; 206 type = types.attrsOf types.path; 207 description = '' 208 Attribute set of paths whose content is copied to the <filename>skins</filename> 209 subdirectory of the MediaWiki installation in addition to the default skins. 210 ''; 211 }; 212 213 extensions = mkOption { 214 default = {}; 215 type = types.attrsOf (types.nullOr types.path); 216 description = '' 217 Attribute set of paths whose content is copied to the <filename>extensions</filename> 218 subdirectory of the MediaWiki installation and enabled in configuration. 219 220 Use <literal>null</literal> instead of path to enable extensions that are part of MediaWiki. 221 ''; 222 example = literalExample '' 223 { 224 Matomo = pkgs.fetchzip { 225 url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz"; 226 sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b"; 227 }; 228 ParserFunctions = null; 229 } 230 ''; 231 }; 232 233 database = { 234 type = mkOption { 235 type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ]; 236 default = "mysql"; 237 description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers."; 238 }; 239 240 host = mkOption { 241 type = types.str; 242 default = "localhost"; 243 description = "Database host address."; 244 }; 245 246 port = mkOption { 247 type = types.port; 248 default = 3306; 249 description = "Database host port."; 250 }; 251 252 name = mkOption { 253 type = types.str; 254 default = "mediawiki"; 255 description = "Database name."; 256 }; 257 258 user = mkOption { 259 type = types.str; 260 default = "mediawiki"; 261 description = "Database user."; 262 }; 263 264 passwordFile = mkOption { 265 type = types.nullOr types.path; 266 default = null; 267 example = "/run/keys/mediawiki-dbpassword"; 268 description = '' 269 A file containing the password corresponding to 270 <option>database.user</option>. 271 ''; 272 }; 273 274 tablePrefix = mkOption { 275 type = types.nullOr types.str; 276 default = null; 277 description = '' 278 If you only have access to a single database and wish to install more than 279 one version of MediaWiki, or have other applications that also use the 280 database, you can give the table names a unique prefix to stop any naming 281 conflicts or confusion. 282 See <link xlink:href='https://www.mediawiki.org/wiki/Manual:$wgDBprefix'/>. 283 ''; 284 }; 285 286 socket = mkOption { 287 type = types.nullOr types.path; 288 default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null; 289 defaultText = "/run/mysqld/mysqld.sock"; 290 description = "Path to the unix socket file to use for authentication."; 291 }; 292 293 createLocally = mkOption { 294 type = types.bool; 295 default = cfg.database.type == "mysql"; 296 defaultText = "true"; 297 description = '' 298 Create the database and database user locally. 299 This currently only applies if database type "mysql" is selected. 300 ''; 301 }; 302 }; 303 304 virtualHost = mkOption { 305 type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix); 306 example = literalExample '' 307 { 308 hostName = "mediawiki.example.org"; 309 adminAddr = "webmaster@example.org"; 310 forceSSL = true; 311 enableACME = true; 312 } 313 ''; 314 description = '' 315 Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>. 316 See <xref linkend="opt-services.httpd.virtualHosts"/> for further information. 317 ''; 318 }; 319 320 poolConfig = mkOption { 321 type = with types; attrsOf (oneOf [ str int bool ]); 322 default = { 323 "pm" = "dynamic"; 324 "pm.max_children" = 32; 325 "pm.start_servers" = 2; 326 "pm.min_spare_servers" = 2; 327 "pm.max_spare_servers" = 4; 328 "pm.max_requests" = 500; 329 }; 330 description = '' 331 Options for the MediaWiki PHP pool. See the documentation on <literal>php-fpm.conf</literal> 332 for details on configuration directives. 333 ''; 334 }; 335 336 extraConfig = mkOption { 337 type = types.lines; 338 description = '' 339 Any additional text to be appended to MediaWiki's 340 LocalSettings.php configuration file. For configuration 341 settings, see <link xlink:href="https://www.mediawiki.org/wiki/Manual:Configuration_settings"/>. 342 ''; 343 default = ""; 344 example = '' 345 $wgEnableEmail = false; 346 ''; 347 }; 348 349 }; 350 }; 351 352 # implementation 353 config = mkIf cfg.enable { 354 355 assertions = [ 356 { assertion = cfg.database.createLocally -> cfg.database.type == "mysql"; 357 message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'"; 358 } 359 { assertion = cfg.database.createLocally -> cfg.database.user == user; 360 message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true"; 361 } 362 { assertion = cfg.database.createLocally -> cfg.database.socket != null; 363 message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true"; 364 } 365 { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null; 366 message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true"; 367 } 368 ]; 369 370 services.mediawiki.skins = { 371 MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook"; 372 Timeless = "${cfg.package}/share/mediawiki/skins/Timeless"; 373 Vector = "${cfg.package}/share/mediawiki/skins/Vector"; 374 }; 375 376 services.mysql = mkIf cfg.database.createLocally { 377 enable = true; 378 package = mkDefault pkgs.mariadb; 379 ensureDatabases = [ cfg.database.name ]; 380 ensureUsers = [ 381 { name = cfg.database.user; 382 ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; 383 } 384 ]; 385 }; 386 387 services.phpfpm.pools.mediawiki = { 388 inherit user group; 389 phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}"; 390 settings = { 391 "listen.owner" = config.services.httpd.user; 392 "listen.group" = config.services.httpd.group; 393 } // cfg.poolConfig; 394 }; 395 396 services.httpd = { 397 enable = true; 398 extraModules = [ "proxy_fcgi" ]; 399 virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { 400 documentRoot = mkForce "${pkg}/share/mediawiki"; 401 extraConfig = '' 402 <Directory "${pkg}/share/mediawiki"> 403 <FilesMatch "\.php$"> 404 <If "-f %{REQUEST_FILENAME}"> 405 SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/" 406 </If> 407 </FilesMatch> 408 409 Require all granted 410 DirectoryIndex index.php 411 AllowOverride All 412 </Directory> 413 '' + optionalString (cfg.uploadsDir != null) '' 414 Alias "/images" "${cfg.uploadsDir}" 415 <Directory "${cfg.uploadsDir}"> 416 Require all granted 417 </Directory> 418 ''; 419 } ]; 420 }; 421 422 systemd.tmpfiles.rules = [ 423 "d '${stateDir}' 0750 ${user} ${group} - -" 424 "d '${cacheDir}' 0750 ${user} ${group} - -" 425 ] ++ optionals (cfg.uploadsDir != null) [ 426 "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -" 427 "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -" 428 ]; 429 430 systemd.services.mediawiki-init = { 431 wantedBy = [ "multi-user.target" ]; 432 before = [ "phpfpm-mediawiki.service" ]; 433 after = optional cfg.database.createLocally "mysql.service"; 434 script = '' 435 if ! test -e "${stateDir}/secret.key"; then 436 tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key 437 fi 438 439 echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \ 440 ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \ 441 ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \ 442 --confpath /tmp \ 443 --scriptpath / \ 444 --dbserver ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \ 445 --dbport ${toString cfg.database.port} \ 446 --dbname ${cfg.database.name} \ 447 ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \ 448 --dbuser ${cfg.database.user} \ 449 ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \ 450 --passfile ${cfg.passwordFile} \ 451 ${cfg.name} \ 452 admin 453 454 ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick 455 ''; 456 457 serviceConfig = { 458 Type = "oneshot"; 459 User = user; 460 Group = group; 461 PrivateTmp = true; 462 }; 463 }; 464 465 systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service"; 466 467 users.users.${user} = { 468 group = group; 469 isSystemUser = true; 470 }; 471 472 environment.systemPackages = [ mediawikiScripts ]; 473 }; 474}