Multi-site provisioning with a shared SPIP kernel

The procedures necessary to use a single SPIP kernel for several web sites.

Multi-site provisioning with a shared SPIP kernel has been possible. This involves being able to manage several SPIP sites on a single server or on a single host while only using one instance of the SPIP kernel. From here on, this document will use the term ’kernel sharing’ for the sake of brevity.

This allows for a substantial saving in disk space, as well as being able to update SPIP just once for several sites by updating just one single common kernel.

This article details the procedures for SPIP 1.9.2 running on Apache servers [1] that permit URL rewriting.

There are several ways to get the same results, depending on whether you wish to configure the shared kernel for a shared host or on your own server.

The concept...

Since SPIP 1.9.2 the directories necessary for SPIP kernel functions (ecrire, squelettes-dist, oo), and those used for site activities (config, IMG, tmp, local) have been clearly separated and identified. It’s this separation of directories that enables several autonomous sites to run from a single SPIP kernel.

This autonomy relies on the existence of a set of 4 directories per site, in which SPIP stores the data relevant to each site’s activity. There are 4 such directories, because we need to distinguish between temporary and permanent data on one side, and between HTTP accessible and HTTP inaccessible data on the other side; giving 4 distinct possibilities of data type. The two directories that are HTTP inaccessible are protected with an automatically configured .htaccess file (server administrators can even put these directories beyond the normal HTTP site tree hierarchy if desired).

Before SPIP 1.9.2, these four types of data weren’t clearly separated and were distributed amongst the IMG, CACHE, ecrire and ecrire/data directories. With SPIP 1.9.2. these four directories are now distinct and are identified by the following PHP constants:

define('_NOM_TEMPORAIRES_INACCESSIBLES', "tmp/");
define('_NOM_TEMPORAIRES_ACCESSIBLES', "local/");
define('_NOM_PERMANENTS_INACCESSIBLES', "config/");
define('_NOM_PERMANENTS_ACCESSIBLES', "IMG/");

In a non-shared SPIP installation, these directories are created at the site root when the spip_initialisation function is applied to the four values above. In order to used the kernel sharing of SPIP sources, one only needs to know how to link a specific URL with its set of 4 specific directories, by calling the spip_initialisation function with these 4 alternative values, derived from the URLs themselves.

Creating the right directories

To start kernel sharing, it’s necessary to start with a well-functioning site. In our examples, we’re starting with a site referenced by the URL http://example.org/ which is physically stored in our server directory /home/toto/public_html/.

First up, we create a directory (in our example, called ’sites’) which will contain the directories for each of our member sites, and this directory is created at the distribution site root (at the same level as /ecrire).

-  sharing a directory:
If we intend that each of the addresses http://example.org/first_site/ and http://example.org/second_site/ each call a SPIP web site, we then need to create the sub-directories /first_site and /second_site below the /sites directory, and then create these next 4 directories in each of them: /config, /IMG, /tmp, /local. These 4 directories need to be write accessible. It is also possible to add one of the traditional /squelettes directories AT THE SAME LEVEL as these 4 directories.

-  kernel sharing for a domain and subdomain (some ideas):
If we intended that the addresses http://toto.example.org/, http://example.net/ and http://user.example.com/ each called a SPIP web site, we might decide to create the directories /toto, /example.net, et /example.com/user within the /sites directory.

Note: All URLs should point to the SPIP distribution root, i.e. to /home/toto/public_html/. The key role will be played by a single .htaccess file or by an Apache server configuration file which will be explained later.

Redirecting to the right location !

In order for SPIP to recognise that a site has a shared kernel, it is necessary that the spip.php script (called by index.php) be executed.
This script will determine, thanks to code added into /config/mes_options.php, if the URL called corresponds to a shared kernel site or not. To do that, it is necessary that the address http://example.org/first_site/ or http://example.org/second_site/ be redirected to http://example.org/ in order to then execute index.php...

That is the role to be played by the .htaccess file (or directly specified in the Apache server configuration).

We need to copy and rename the htaccess.txt file in the distribution root directory so that it is named .htaccess, and then we modify it as below:

In order to authorise URL rewriting (otherwise nothing would change):
RewriteEngine On

If the SPIP distribution is in a sub-directory, then you would change the rewritebase (see further below). In our case, the site is at the root, so we have:

RewriteBase /

Finally, with customised rules, add the following code so that the directories /first_site , /second_site and /third_site are to be treated as if from the distribution root:

#Shared provisioning
RewriteRule ^(first_site|second_site|third_site)$ /$1/ [R,L]
RewriteRule ^(first_site|second_site|third_site)/(.*) /$2 [QSA,L]

The first rewrite rule redirects addresses like
http://example.org/premier_site to http://example.org/premier_site{{/, that is, it adds the final blackslash.
The second rewrite rule redirects anything below /first_site/ back to the root directory, for example: http://example.org/premier_site/article112 is redirected to http://example.org/article112. (However, this redirection is transparent, and the URL name does not change, it remains as http://example.org/premier_site/article112, even and especially for the PHP scripts that will soon use it !)

What if SPIP is in a sub-directory?
In this case, the SPIP files are located in /home/toto/public_html/spip/ , SPIP is called at http://example.org/spip/, and the shared-kernel sites at http://example.org/spip/first_site/ and http://example.org/spip/second_site/.

In this case it is therefore necessary to include this code in the .htaccess file:

RewriteEngine On
RewriteBase /spip/

#Kernel sharing rewrites
RewriteRule ^(first_site|second_site|third_site)$ /spip/$1/ [R,L]
RewriteRule ^(first_site|second_site|third_site)/(.*) /spip/$2 [QSA,L]

And how to redirect all the appropriate directories as for kernel sharing sites?

-  if SPIP is at the root:

RewriteCond %{REQUEST_URI} !^/(config|squelettes-dist|ecrire|IMG|oo|plugins|sites|squelettes|tmp|local)/(.*)
RewriteRule ^[^/]+/(.*) /$1 [QSA,L]

-  or if it’s in the /spip directory:

RewriteCond %{REQUEST_URI} !^/spip/(config|squelettes-dist|ecrire|IMG|oo|plugins|sites|squelettes|tmp|local)/(.*)
RewriteRule ^[^/]+/(.*) /spip/$1 [QSA,L]

And what about domains and subdomains ?
By simple good luck you don’t need to do anything in this case, as everything already points by default to the SPIP root distribution directory...

Kernel sharing depending on the URL thanks to mes_options.php

It is the /config/mes_options.php file in the root directory that is going to do most of the hard work: it will check if a URL is for a shared provisioning site or not, and call SPIP accordingly.

There are several possible cases that can occur, between the directories, domains and subdomains. PHP can use two variables to check the URLs which call a particular script:

$_SERVER['REQUEST_URI']; // contains everything in the URL that follows the domain name, e.g. /first_site/article112 
$_SERVER['SERVER_NAME']; // contains the domain and subdomain name, e.g. user.example.org

It is these two variables that will be compared with regular expressions in order to extract the name of the directory that contains the shared provisioning resource.

One simple method to enable kernel sharing for the directories is to use the following code in mes_options.php:

if ( preg_match(',/([a-zA-Z0-9_-]+)/?,',$_SERVER['REQUEST_URI'],$r)) {

    if (is_dir($e = _DIR_RACINE . 'sites/' . $r[1]. '/')) {

        $cookie_prefix = $table_prefix = $r[1]; 

        define('_SPIP_PATH',
            $e . ':' .
            _DIR_RACINE .':' .
            _DIR_RACINE .'squelettes-dist/:' .
            _DIR_RACINE.'prive/:'.
            _DIR_RESTREINT);

        spip_initialisation(
            ($e . _NOM_PERMANENTS_INACCESSIBLES),
            ($e . _NOM_PERMANENTS_ACCESSIBLES),
            ($e . _NOM_TEMPORAIRES_INACCESSIBLES),
            ($e . _NOM_TEMPORAIRES_ACCESSIBLES)
            );

       $GLOBALS['dossier_squelettes'] = $e.'squelettes';

        if (is_readable($f = $e._NOM_PERMANENTS_INACCESSIBLES._NOM_CONFIG.'.php')) include($f);
    }
} 

The preg_match line recovers the folder name $r from the URL hierarchy, i.e. ’first_site’ from http://example.org/first_site/article112 ...
Then the script checks if the directory /sites/first_site/ exists and attempts to call the shared provisioning resource.

If SPIP were in the /spip directory, then change the first line in the above code to read:

if ( preg_match(',/spip/([a-zA-Z0-9_-]+)/?,',$_SERVER['REQUEST_URI'],$r)) {

We need to tell SPIP what is the prefix that has been used for tables in the database, which is why we have that $cookie_prefix = $table_prefix = $r[1];
This line will prefix the MySQL tables with the name of the directory that contains the site: first_site_article instead of spip_article for example. That enables the hosting of all the sites on the one single database, without confusing tables for one site with those of another. You can keep the default prefix (spip) if you wish, but then you’ll need to think about maintaining several databases, one for each separate site.
If that is the case, replace the "cookie" line with:

$cookie_prefix = $r[1]; 
$table_prefix='spip';

The spip_initialisation function accepts four parameters that are the addresses of each of the directories required for a shared-kernel site to run.

$GLOBALS['dossier_squelettes'] = $e.'squelettes'; defines the location of the squelette templates directory for each of the shared-kernel sites.

Last, but not least, the script above checks for the possible presence of sites/first_site/config/mes_options.php.

Information:

Any and all changes made to the config/mes_options.php file of the SPIP kernel will affect all the options for all the shared-kernel sites.

For example, including the following code in the file:
$type_urls = ’propres’ ;

will assign these types of URL to all the sites... but then each of these sites can override this with their own options, e.g. by using /sites/first_site/config/mes_options.php.

Configuring Apache for domains and subdomains

For kernel sharing on the server side of things, management of subdomains and domains requires only simple changes, but it requires the redirection of URLs in the Apache server configuration to take account of these sites.

Here, for example, is the configuration for a server called ’example.net’ (a shared-kernel SPIP site) using subdomains (SPIP called by http://user.example.org/spip/).

The SPIP kernel is in ’/home/toto/public_html/spip/’.

It is therefore necessary to create the directories /sites/example.tld/ , /sites/example.tld/user/.

The configuration file in Apache 2/linux is located in /etc/apache2/sites_availables/default (or you could also create a new file in the sites_availables folder and activate it).

# SERVEUR example.tld
# SPIP for a subdomain...
<VirtualHost *>
        ServerName example.tld
	ServerAlias *.example.tld

        # Redirection to the SPIP kernel
	DocumentRoot "/home/toto/public_html"
	<Directory "/home/toto/public_html/">
		AllowOverride All
		Order allow,deny
		Allow from all
	</Directory>

	# Only addresses like http://utilisateur.example.tld/spip/* should be redirected

	# (user.example.tld/spip/* -> /home/toto/public_html/*)
	RewriteCond %{SERVER_NAME} (www\.)?([^.]+)\.example\.net$
	RewriteRule ^/spip/(.*) /home/toto/public_html/spip/$1 [QSA,L]

	# (user.example.net/* -> /home/toto/public_html/sites/example.tld/user/*)
	RewriteCond %{SERVER_NAME} (www\.)?([^.]+)\.example\.net$
	RewriteRule (.*) /home/toto/public_html/sites/example.tld/%1/$1 [QSA,L]

</VirtualHost>

It is, however, necessary to test for this kind of kernel sharing within the /config/mes_options.php file:

<?php
# for user.example.tld/spip/ 
if ( preg_match(',(.*)\.example\.tld/spip/?,',$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'],$r)) {

    if (is_dir($e = _DIR_RACINE . 'sites/example.tld/' . $r[1]. '/')) {

       $cookie_prefix = $table_prefix = $r[1]; 

       define('_SPIP_PATH',
            $e . ':' .
            _DIR_RACINE .':' .
            _DIR_RACINE .'squelettes-dist/:' .
            _DIR_RACINE.'prive/:'.
            _DIR_RESTREINT);

        spip_initialisation(
            ($e . _NOM_PERMANENTS_INACCESSIBLES),
            ($e . _NOM_PERMANENTS_ACCESSIBLES),
            ($e . _NOM_TEMPORAIRES_INACCESSIBLES),
            ($e . _NOM_TEMPORAIRES_ACCESSIBLES)
            );

       $GLOBALS['dossier_squelettes'] = $e.'squelettes';

        if (is_readable($f = $e._NOM_PERMANENTS_INACCESSIBLES._NOM_CONFIG.'.php')) include($f);
    }
} 
?>

Note about backups and restores

Each site copies its backup files into it’s own directory, e.g.
/sites/first_site/tmp/dump (or /sites/first_site/tmp/upload/login for backups by a "restricted administrator").
Restores are retrieved from the same directories.

Attention:

At the moment, SPIP stores in its database, as regards the /IMG folders of shared-kernel sites, entries for sites/first_site/IMG/* instead of for IMG/* as with a single standalone SPIP site.

So a backup restore will only correctly work if it is restored by the same site that created the backup.

The trick to restore a backup from a different site entails editing the dump.xml file and replacing all occurrences (excepting those that declare dir_img and dir_logo in the opening SPIP tags):
-  sites/first_site/IMG/

  • with (standalone SPIP site): IMG/
  • or with (shared-kernel SPIP site): sites/second_site/IMG/

Conversely, to load a standalone SPIP site into a shared-kernel directory, you would need to replace all occurrences of:
-  IMG/
-  with: sites/first_site/IMG/

(This hurdle will be addressed in the next version of SPIP.)

Footnotes

[1FYI: these procedures have been tested with Apache 2.0.55 et PHP 5.1.2 on Ubuntu Dapper Drake as well as on PHP 5.1.6 on Ubuntu Edgy Eft

Author Mark Published : Updated : 14/07/23

Translations : عربي, català, English, Español, français, italiano, Türkçe