Page MenuHomePhabricator

Mediawiki 1.31 break symlinked, cause LocalSettings.php failed to load.
Open, LowPublic

Description

Starting from Mediawiki 1.31, Mediawiki will always looking for LocalSettings.php from where the MW code located, instead of the folder containing symlink.

This is not a problem for many single wiki setup. Since their LocalSettings.php are placed right under Mediawiki codes folder.
However, it cause fatal issue to wiki farms where symlink where widely used to set up multiple wikis.

E.G.
We are loading wiki farms in following method:
/www/mediawiki (all MW code, no config file, no web access)
/www/a (web access point, own LocalSettings.php, symlinked to /www/mediawiki)
/www/b (same)
/www/c (...)
/www/d
......
All the wiki failed, due to Mediawiki now only looking for LocalSettings.php under /www/mediawiki folder.

It also break the distribute version of Mediawiki in Debian std. They put MediaWiki core files under /usr/share/mediawiki and then /etc/mediawiki/LocalSettings.php. Mediawiki is loaded via symlink from web-server directory. Therefore, after upgrade to 1.31, Mediawiki will start to load LocalSettings.php under /usr/share/mediawiki, instead of where the symlink LocalSettings.php, which break the site.


This problem seems to be caused by this change: https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/+/0d5f8f6bee296c4e29f03acbd5c07f34d6c8a88a%5E%21/ in WebStart.php.
Where $IP is set to the folder of Mediawiki codes' absolute path in 1.31, instead of the original path.
Therefore, in line 61

define( 'MW_CONFIG_FILE', "$IP/LocalSettings.php" );

will point LocalSettings.php path to MW code folder, instead of the folder where MW was called.

We'd like Mediawiki to add a specify extra check for and only for LocalSettings.php under where the symlink initiated from MW 1.31 to newest master verison. Because it is easier and safer to Debian/Ubuntu users, and judging which folder to load by nginx/Apache is much faster than a giant LocalSettings.php files with hundreds of if statements for wiki farm.

get the calling url if ( strpos( $callingurl, '/wiki1' ) === 0 ) {

       require_once 'LocalSettings_wiki1.php';
} elseif 
......

few more lines of if statement needs to be add to WebStart.php to fix this problem.
such as

// If no LocalSettings file exists, try to display an error page
// (use a callback because it depends on TemplateParser)
$possibleCurrentDirectoryLocalSettings = getcwd().'/LocalSettings.php'; //an extra check for LocalSettings.php under current Directory incase of symlink used.

if ( !defined( 'MW_CONFIG_CALLBACK' ) ) {
	if ( !defined( 'MW_CONFIG_FILE' ) ) {
		if (  is_readable($possibleCurrentDirectoryLocalSettings) ) {
			define( 'MW_CONFIG_FILE', $possibleCurrentDirectoryLocalSettings );
		}else{
			define( 'MW_CONFIG_FILE', "$IP/LocalSettings.php" );
		}
	}

We tested it, and could confirm that by modify WebStart.php like so, we get the wiki farm run again as well as the Debian distribution MW in version 1.31.

It seems that by setting $IP to the actual MW code path, Mediawiki get slightly better performance due to file path unify in apc. But not much. Therefore, switch back to the way Mediawiki 1.30 used to use(relative path) could be a choice too.

Event Timeline

I guess the web server can pass MW_CONFIG_FILE (custom profile path) environment variable defined by the web server to PHP, and then MW can use it to load LocalSettings.php.

We are loading wiki farms in following method:

  • /www/mediawiki (all MW code, no config file, no web access)
  • /www/a (web access point, own LocalSettings.php, symlinked to /www/mediawiki)

Can you add more details about this structure? I do not understand it currently.

If /www/a is not a directory but a sylink to /www/mediawiki, then it cannot have files. Where is the LocalSettings.php file?

www.a.com point to /www/a.
/www/a contain LocalSettings.php and symlink to each file/folder under /www/mediawiki.

Such structure were documented https://www.mediawiki.org/wiki/Manual:Wiki_family

www.a.com point to /www/a.
/www/a contain LocalSettings.php and symlink to each file/folder under /www/mediawiki.

Such structure were documented https://www.mediawiki.org/wiki/Manual:Wiki_family

I'm not sure such structure was officially supported. (The documentation is freely editable). But, I think we can support this structure.

The only requirement is for MW_INSTALL_PATH to be set as an environment variable to the imitation directory for the current site. Everything will automatically fall into place after that. We can add that to the 1.31 release/upgrade notes.

Krinkle triaged this task as High priority.Aug 31 2018, 7:54 PM
Krinkle added a project: MediaWiki-Installer.

I'm not sure such structure was officially supported. (The documentation is freely editable). But, I think we can support this structure.

The only requirement is for MW_INSTALL_PATH to be set as an environment variable to the imitation directory for the current site. Everything will automatically fall into place after that. We can add that to the 1.31 release/upgrade notes.

Cool!
In our further test, set MW_INSTALL_PATH to the real file path is slightly faster & much more stable under heavy load, which is good compare to the original method used by 1.30. Therefore, it's better to keep MW_INSTALL_PATH as a variable set to the real file path.
But only detect LocalSettings.php from both the symlink directory as well as the real directory. What I mean is similar to the /maintenance schema. Those schema could mark LocalSettings.php location with --conf parameters, as documented here: https://www.mediawiki.org/wiki/Manual:Update.php#Script_specific_parameters . This is useful when LocalSettings.php was placed under other directory, or when they have different name for wiki farm.

I'm not sure such structure was officially supported. (The documentation is freely editable). But, I think we can support this structure.

The only requirement is for MW_INSTALL_PATH to be set as an environment variable to the imitation directory for the current site. Everything will automatically fall into place after that. We can add that to the 1.31 release/upgrade notes.

Cool!
In our further test, set MW_INSTALL_PATH to the real file path is slightly faster & much more stable under heavy load, which is good compare to the original method used by 1.30. Therefore, it's better to keep MW_INSTALL_PATH as a variable set to the real file path.
But only detect LocalSettings.php from both the symlink directory as well as the real directory. What I mean is similar to the /maintenance schema. Those schema could mark LocalSettings.php location with --conf parameters, as documented here: https://www.mediawiki.org/wiki/Manual:Update.php#Script_specific_parameters . This is useful when LocalSettings.php was placed under other directory, or when they have different name for wiki farm.

It is good to hear you care about performance. Me too! Optimising for performance is easier when there is only 1 method of doing something. Right now, MediaWiki supports multiple methods.

  1. One imitation directory for every wiki, is supported. This must set MW_INSTALL_PATH to the imitation directory. This still works the same as in 1.30 and earlier versions. This is the current method you describe.
  2. One installation path for all wikis, is supported. The configuration file can be set in two ways:
    • A) Dynamically load the wiki-specific LocalSettings.php file from a central LocalSettings.php file. For example, by loading it based on a custom environment variable set from the web server, or by comparing the virtual hostname in PHP. This is what Wikipedia does today.
    • B) Dynamically set MW_CONFIG_FILE or MW_CONFIG_CALLBACK from an entry point wrapper.

These are all compatible, and supported. They work. But, they are not all the fastest. The difference in performance is small, but if you want the best performance, I recommend you use the same method of installation as Wikipedia.

If I understand correctly, you have more than just settings different per-wiki, but also uploads and cache, and these are not currently explicitly assigned in configuration, but happen automatically by $IP? If you follow method 2A above, these will need to become explicit. For example:

Directories
* private/shared/mediawiki/
* public/www.foo.example/
** uploads/
** LocalSettings.php
** .. symlinks
* public/www.bar.example/
** uploads/
** LocalSettings.php
** .. symlinks

Then, you could do:

Apache
<VirtualHost www.foo.example>
  DocumentRoot /public/www.foo.example/

 SetEnv MYFARM_WIKIDIR=/public/www.foo.example/
</VirtualHost>
private/shared/mediawiki/LocalSettings.php
# (default, real path) $IP = '/private/shared/mediawiki'
$myFarmWikiDir = getenv( 'MYFARM_WIKIDIR' ) ?: '/dev/null';

$wgUploadDirectory = "$myFarmWikiDir/uploads/";
require_once "$myFarmWikiDir/LocalSettings.php";

This means that for the wiki directories, nothing changes. And it means that the installation will use a method that is officially supported, by using configuration variables explicitly.

It also break the distribute version of Mediawiki in Debian std. They put MediaWiki core files under /usr/share/mediawiki and then /etc/mediawiki/LocalSettings.php. Mediawiki is loaded via symlink from web-server directory. Therefore, after upgrade to 1.31, Mediawiki will start to load LocalSettings.php under /usr/share/mediawiki, instead of where the symlink LocalSettings.php, which break the site.

I'm unable to reproduce any breakage using the Debian filesystem symlink layout under MediaWiki 1.31 - it seems to work just fine.

It also break the distribute version of Mediawiki in Debian std. They put MediaWiki core files under /usr/share/mediawiki and then /etc/mediawiki/LocalSettings.php. Mediawiki is loaded via symlink from web-server directory. Therefore, after upgrade to 1.31, Mediawiki will start to load LocalSettings.php under /usr/share/mediawiki, instead of where the symlink LocalSettings.php, which break the site.

I'm unable to reproduce any breakage using the Debian filesystem symlink layout under MediaWiki 1.31 - it seems to work just fine.

Debian changes to MediaWiki
We change a few things about MediaWiki to fit in with what Debian expects. Files can be found in the following places:
/usr/share/mediawiki - MediaWiki core files
/etc/mediawiki/LocalSettings.php - expected location of ?LocalSettings.php. You can also put other configuration files in this directory.
/var/lib/mediawiki - Main directory that the webserver will read from. Most core files from /usr/share/mediawiki are symlinked into this directory, and the ?LocalSettings.php file as well.

from https://wiki.debian.org/MediaWiki

Can you load /etc/mediawiki/LocalSettings.php when the site were point to /var/lib/mediawiki with Mediawiki 1.31?

Liuxinyu970226 subscribed.

as we know that this is an Installer problem

@Krinkle
I tired your method of "SetEnv" in nginx. It does not work due to nginx remove any environment variable. Moreover, env MW_CONFIG_FILE=value; will set a single value to all wikis, which is not ideal for wiki farm.

It does not work with fastcgi_params as well. nginx treat param as $_SERVER like "$_SERVER['MW_CONFIG_FILE'] /mediawiki-setting/LocalSettings-example.com.php". It seems that mediawiki ignored $_SERVER.

I found a bug report in 2015 T115432, which going to load MW_CONFIG_FILE from environment variable. However it not been implemented.

What's the effective way to do this with nginx+php-fpm?


This bug is nothing related to installer.

@Krinkle
I tired your method of "SetEnv" in nginx. It does not work due to nginx remove any environment variable. Moreover, env MW_CONFIG_FILE=value; will set a single value to all wikis, which is not ideal for wiki farm.

It does not work with fastcgi_params as well. nginx treat param as $_SERVER [..]

Somewhere in your web server, there is configuration per wiki, right? Otherwise every website would be identical. There is a variable somewhere somehow for the current wiki and it associates it with a directory on disk, is that right?

If you have that, then that is presumably the place where you tried fastcgi_params. That does not work, I understand. But can you use SetEnv from that same location, instead of from the global location?

Alternatively, if you have a variable for the current website, but not a conditional/location block, then you can still use that variable. For example if you have somewhere DocumentRoot /var/www/$mysite you can use SetEnv MYFARM_SITE=$mysite, and access it from PHP and correlate it to a LocalSettings file.

private/shared/mediawiki/LocalSettings.php
$settingsDir = getenv( 'MYFARM_SITE' ) ? '/private/wiki-settings/' . getenv( 'MYFARM_SITE' ) : '/dev/null';
require_once "$settingsDir/LocalSettings.php";

Or if you want it simpler (but more fragile, in my opinion), you can even use DOCUMENT_ROOT directly, if the settings file is there

private/shared/mediawiki/LocalSettings.php
$documentRoot = $_SERVER['DOCUMENT_ROOT'];
require_once "$documentRoot/LocalSettings.php";

I am closing this issue now, because the use case of storing LocalSettings.php is supported by MediaWiki it. This is done by mediawiki-debian in one way, and is done by Wikimedia Foundation in another way, and there are many other ways to do it. I have shown several ways as examples.

For further help with MediaWiki, I recommend https://www.mediawiki.org/wiki/Project:Support_desk, or https://discourse-mediawiki.wmflabs.org/. For help with how to configure Nginx, I recommend https://nginx.org/en/support.html or https://serverfault.com/questions/ask?tags=nginx.

Krinkle claimed this task.

I noticed this in the backscroll of #mediawiki from today:

[11:20:56] <myier> Hi, I am struggling to install mediawiki 1.31 the debian way, with symlinks in /var/lib/mediawiki/* for
[11:20:59] <myier> multi-site install, it always looks for LocalSettings.php in /usr/share/mediawiki where the files are, has
[11:21:02] <myier> this become impossible in 1.31? thanks
[06:55:05] <TheSin> hey guys debian finally updated to 1.31 but now I can't get it to find the LocalSettings.php that was working, it's a central install, as in I have multiple instances of mediawiki running where each instance have symlinks to shared code
[06:55:36] <TheSin> I assume it's trying to find /etc/mediawiki/LocalSettings.php but it's should be looking for <webroot>/LocalSettings.php
[06:55:44] <TheSin> did somethign change form 1.30 to 1.31?

I think we need to reconsider this behavior change given the amount of breakage it seems to be causing.

Yeah, let's revisit before release. I'm still not convinced that we need to restore previous behaviour as-is because I also believe it fixed various things.

But, I recognise the use case. At the very least we should make it really easy to upgrade for these users and maybe at most require them to change one thing (whatever that one thing might be), I think we can afford to require one change on their part for such relatively unusual set up.

just broke my wikis the same way while trying to upgrade to 1.31.1

I have a central directory with MediaWiki files in it.
In the document root I just keep cache, images, LocalSettings.php and mw-config. Everything else is symlinked to the central directory. It used to work fine, now as described above the LocalSettings.php is expected to reside in the central MediaWiki directory instead of the local directory.

Change 466865 had a related patch set uploaded (by Krinkle; owner: Alangi Derick):
[mediawiki/core@master] Fix $IP value when directory is a symlink

https://gerrit.wikimedia.org/r/466865

I think it's fine to add support this. But, if possible, we should do it in a way that doesn't re-introduce the problem described at T153882.

Is anyone committing to implementing this? It's in the backlog for the release, but in no team's backlog, and not assigned to anyone. If nobody committed to doing this, it probably should not block the release.

@CCicalese_WMF this seems like it's down your alley. What do you think, should CPT pick this up? The code should be minimal/trivial, the tricky bit is to not break some other way of installing MediaWiki in the process.

Larrystrickland raised the priority of this task from High to Needs Triage.Jan 16 2019, 2:11 AM
Larrystrickland updated the task description. (Show Details)
Aklapper updated the task description. (Show Details)

Change 466865 abandoned by D3r1ck01:
[NOT TESTED] Fix $IP value when directory is a symlink

Reason:
No longer working on it, seems I've not understood the scope of things here.

https://gerrit.wikimedia.org/r/466865

Since there is not an insignificant number of installations done using symlinks, this should be fixed.

WDoranWMF lowered the priority of this task from High to Medium.Jul 17 2019, 8:39 PM

the bug isn't fixed in 1.31.3

I myself found a workaround: in line 53 of includes/WebStart.php replace

if ( $IP === false ) {
	$IP = dirname( __DIR__ );
}

with

if ( $IP === false ) {
		// T206575: Check if directory is a symlink else default
	//          to original directory name.
	if ( is_link( dirname( $_SERVER['SCRIPT_FILENAME'] ) ) ) {
		$IP = dirname( $_SERVER['SCRIPT_FILENAME'] );
	} else {
		$IP = dirname( __DIR__ );
	}
}

Using wiki farms with a shared code base (like WMF) based on symlinks per website (unlike WMF) is not very well-documented, and often leads to fragile, ambiguous or confusing code. I've tried multiple times to make it work, and in some cases it worked, others didn't. The change that broke this kind of setup for some of you, is also what finally made it work for others (T221613).

Unfortunately, I don't think it is possible to make everthing work as-is (without requiring any changes on the administrator side) for both MW 1.30 (and earlier) installs and for MW 1.31+ installs.

Having said that, I do think both kinds of directory layouts can and should be supported without requiring site admins to change their directory layout. But, we would need to require a minor change in site configuration, I think that's fair?

My recommendation would be to compare the reports we have and determine whether they all have the exact same setup or whether there are differences. For these setups, document how to make them work (with minimal effort for the admins) on MW 1.31+. Afaik these can all be supported without requiring changes to MediaWiki core WebStart. It only requires a config change on the site admin end - which we should document.

If we do find there are directory layouts that previously worked but now can't, and can't be made to work through configuration alone, then we may need to patch WebStart so those are possible (and backport accordingly), although it will be tricky and would ideally be avoided.

This comment was removed by YOUR1.

I've written some patches for 1.31 which enables you to use symlinks, but we're also using the same strategy for 1.27 and so on.

We've symlinked every file in the core to folderA.
folderA contains;

  • non symlinked folder 'vendor'
  • non symlinked folder 'cache'
  • non symlinked file 'LocalSettings.php'
  • non symlinked file 'composer.local.json'

Written patches:
includes/WebStart.php

iff --git a/includes/WebStart.php b/includes/WebStart.php
index 6f3aa716..e1c55113 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -51,7 +51,13 @@ define( 'MEDIAWIKI', true );
 # Full path to the installation directory.
 $IP = getenv( 'MW_INSTALL_PATH' );
 if ( $IP === false ) {
-	$IP = dirname( __DIR__ );
+    // T206575: Check if directory is a symlink else default
+    //  to original directory name.
+    if ( is_link( dirname( $_SERVER['SCRIPT_FILENAME'] ) ) ) {
+        $IP = dirname( $_SERVER['SCRIPT_FILENAME'] );
+    } else {
+        $IP = dirname( __DIR__ );
+    }
 }
 
 // If no LocalSettings file exists, try to display an error page

includes/PHPVersionCheck.php

diff --git a/includes/PHPVersionCheck.php b/includes/PHPVersionCheck.php
index 609b75be..868ca909 100644
--- a/includes/PHPVersionCheck.php
+++ b/includes/PHPVersionCheck.php
@@ -151,7 +151,7 @@ HTML;
 	 * Displays an error, if the vendor/autoload.php file could not be found.
 	 */
 	function checkVendorExistence() {
-		if ( !file_exists( dirname( __FILE__ ) . '/../vendor/autoload.php' ) ) {
+		if ( !file_exists( getcwd() . '/vendor/autoload.php' ) ) {
 			$shortText = "Installing some external dependencies (e.g. via composer) is required.";
 
 			$longText = "Error: You are missing some external dependencies. \n"

maintenance/Maintenance.php

iff --git a/maintenance/Maintenance.php b/maintenance/Maintenance.php
index 13fee9c6..84d784db 100644
--- a/maintenance/Maintenance.php
+++ b/maintenance/Maintenance.php
@@ -161,7 +161,7 @@ abstract class Maintenance {
 		global $IP;
 		$IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
 			? getenv( 'MW_INSTALL_PATH' )
-			: realpath( __DIR__ . '/..' );
+			: getcwd();
 
 		$this->addDefaultParams();
 		register_shutdown_function( [ $this, 'outputChanneled' ], false );

We haven't encountered any issues so far. Jobs are being ran from the wiki directory it self.

Note; this fix will probally only work for *nix hosts, I havent tested it on Windows.

Aklapper lowered the priority of this task from Medium to Low.Jan 30 2022, 12:33 AM

No updates for more than two years; obviously not "medium" priority.

To bad nobody did anything with the patches I wrote above.. This seems like a useful solution..

@YOUR1: Thanks for taking a look at the code. You are very welcome to use developer access to submit the proposed code changes as a Git branch directly into Gerrit which makes it easier to review and provide feedback. If you don't want to set up Git/Gerrit, you can also use the Gerrit Patch Uploader.