WikiFarmAlternative

Summary: An efficient way to manage a wiki farm or web server.
Version: 2.1
Maintainer: BenWilson

Last Tested: This recipe was last tested on PmWiki version: pmwiki-2.3.31

Table of Contents

Question

I like the Wiki Farm Step By Step approach, but is there another way?

Abstract

PmWiki is a versatile tool for managing a web site. One under-recognized feature is Pmwiki's ability to manage several different web sites simultaneously from the same code base. This recipe demonstrates how a web administrator can configure PmWiki to service several sites while retaining the ability to smoothly handle upgrades. When implemented, this recipe provides centralized control of PmWiki administration. The final result is a web server that is easily maintained. This recipe can be applied in either a wiki farm or web site context.

Implementation

This recipe seeks to retain security while providing ease of administration and flexibility. For demonstration purposes, we will assume the administrator wants to manage three web sites: example.org, example.net, and example.com. The first step in this process is to build the framework in a non-web accessible directory. Using these sites as an example, the web directories would be:

/var/httpd/hosts/example.org
/var/httpd/hosts/example.net
/var/httpd/hosts/example.com

By way of background, I use the methods of this recipe to manage nearly a dozen wiki fields hosted on four co-located web sites. This recipe has allowed me to introduce seamless upgrades to all these sites while reducing the administrative overhead to running a simple shell script. Some of this recipe indicates my own personal ideocyncracies, such as explicitly setting configuration values rather than trust the PmWiki software to do it. This is because I've encountered some odd situations and this approach has worked in each, whereas I've had difficulties making it work following the PmWiki-way. Additionally, hard-coding these values reduces the unlikely possibility of PmWiki being subverted and those derived values being exploited.

Some of the steps below set variables. As my approach is not entirely consistent with PmWiki, I am trying to rein in this recipe. Therefore, I will provide a sample Attach:WAF_example-farmconfig.php Δ with those settings. (Pending release of $SkinLibDirs implementation.)

Setting up the Centralized Configuration

So, let's use /var/httpd/hosts as the non-web directory. For the remainder of this recipe, we will refer to this as the {core_directory}. The directories you will need to create in the {core_directory} are those listed in red in the example below. Note: This recipe can be in any directory that the web service's user can access (e.g. apached or httpd), not necessarily the {core_directory}. We are using this directory to simplify the documentation.

Core Directory Contents:

/example.org
/example.net
/example.com

/pmwiki-2.3.31 (Current version of PmWiki you are using)
/pmwiki-latest (symlink to /pmwiki-2.3.31)
/pmwiki-share
  /pub
  /cookbook
  /local
The contents of the /pmwiki-share directory will be the parts of PmWiki that would be common to all:

The /local directory allows you to share common configurations. Common configurations could be centralized here. For example, If several sites use the same security configuration, then you could put that configuration in /pmwiki-share/local/security.php.

The /pub directory could be put in the /pmwiki-share directory. This is discussed below in greater detail. Each wiki field must have the pub directory, but if you can use symlinks, then you can maintain that directory in /pmwiki-share and link it to these fields. This allows you to quickly update certain parts of the /pub directory once and affect all fields. For example, if you use the same Skin for several sites or fields, then one change here is immediately effective in all those locations. Additionally, some recipes need to put files in /pub. So, by putting them here, we improve upon the benefit of the central role of /pmwiki-share.

The /cookbook directory should be removed from the web directory for security purposes. This reduces the likelihood of a hacker exploiting a weak recipe. While this may not be an issue, security generally should be approached in a "belt and suspenders" manner (i.e., exercise more care than you think you ought). Also, putting that directory in /pmwiki-share allows the sites and wiki fields to share the same recipes.

I am going to designate a single file as the universal configuration file. I typically call it stdconfig.php, but it is similar to farmconfig.php. I chose to name it stdconfig.php to help it stand out---it could be applied to multiple sites and fields.

As an alternative, /pmwiki-share could be /usr/share/pmwiki/common, with /pmwiki-2.3.31 being located in /usr/share/pmwiki/pmwiki-2.3.31. However, this recipe uses the {core_directory} in all examples for consistency.

Configuring the Fields

In order to take advantage of this recipe, you will need to slightly amend the structure of the fields which use it. As of August 26, 2006, I am revising this recipe to break the steps into more discrete parts. So, where there is a section dedicated to a part you are interested in, please refer to that part.

In each wiki field, you will want:

 index.php
 /pub -> {core_directory}/pmwiki-share/pub (if you can symlink, otherwise you'll have to copy)
 /wiki.d (can be removed see below.)
 /local
    config.php

The structure above is the minimum of what you should have. The index.php file is discussed in greater detail below. However, its purpose is to map the field to the centralized farm information. The /pub directory here is symlinked to the /pmwiki-share/pub sub-directory. The command that would create this (in Unix) is: "ln -sf {core_directory}/pmwiki-share/pub pub". If you are in an FTP environment, or otherwise unable to create this directory, you lose the potential of this arrangement. However, to make this work without symlinking, you should copy the contents of the /pmwiki-share/pub directory.

As suggested above, it is possible to move the wiki.d out of the web-accessible directory. As this directory has wide-open permissions, I believe it is inherently insecure to leave wiki.d in the web-accessible directory. However, if you would prefer to leave this directory alone, then I left it in this listing.

To further map the field to the central directory, you will want to create an index file. There is an example for both symlinkable and non-symlinkable configurations.

in index.php (with symlinks):

 <?php
 $FarmD = '{core_directory}pmwiki-latest';
 $FarmS = '{core_directory}pmwiki-share/';
 $FarmC = '{core_directory}pmwiki-share/cookbook';
 $FarmL = '{core_directory}pmwiki-share/local';
 include_once("$FarmD/pmwiki.php");

Because we can use symlinks, the site administrator can have $FarmD point to a directory which is actually a symlink to the current PmWiki installation. How and why this is done is explained below. I have a variation of this, which coincides with the sans symlinks approach. I create a version.php file, and put all the Farm(X) variables:

 <?php
   include_once('{core_directory}/pmwiki-share/version.php');
   include_once("$FarmD/pmwiki.php");

In this situation, the web administrator cannot use symlinks. To remedy this, the administrator creates a file that tracks the version /pmwiki-share/local/version.php. This allows the web administrator to upgrade all the sites and farms from one centralized file, rather than have to edit each field with the new version number. This is further explained below.

Contents of version.php

 <?php
 $version = 'pmwiki-2.3.31'; # or $version = 'pmwiki_latest'; with symlinks.
 $FarmS = '{core_directory}pmwiki-share';
 $FarmC = '$FarmS/cookbook';
 $FarmD = "{core_directory}/$version";
 $FarmL = '$FarmS/local';

The actual version number would be whatever version you want to use. I move the variables into this common file so it is easy to change the values without having to hunt throughout a wiki plantation.

Note: In both cases, the administrator forces the assignment of $FarmD so there is no chance for PmWiki to get confused. This does not use the '../' approach as it is more secure to put in the full path. Also, this allows me to copy the index.php in sub-directories of a wiki farm and still find the correct version of pmwiki.php. My experience has been that an explicit definition of this variable remedies various problems I've encountered.
Pm suggests this is overkill. However, by explicitly defining a few critical directories here, I have experienced less headache. The only significant problem I've encountered using this approach is the occasional recipe that expects /cookbook to be in $FarmD. In those situations, I usually fix that by substituting $FarmD/cookbook with $FarmC without much problem.

To access all recipes in the centralized /cookbook:

 include_once("$FarmC/{recipe.php}");

Note: you do not need to put the cookbook directory into each field! Just make sure the script knows where to look. I assign the location in index.php that works no matter where the field is. This leverages off of the centralization by allowing multiple fields to benefit from one recipe.

To access commonly shared configurations:

 include_once("$FarmL/standard_configurations.php");

This is what is added to each wiki field's local/config.php file to enable the standard configurations.

Local configurations

You will create the "local/config.php" just like you would for a normal installation. Notice the commonly shared configurations can be site specific. So, if you have two sites, you may have

 /pmwiki-share
   /local
     common_config-example_org.php
     common_config-example_com.php

Then, all fields of "example.org" can access the standard configuration thus:

 include_once("$FarmL/common_config-example_org.php");

If you want both example.com and example.org to share a common set of core configurations (e.g. common security recipes), put the configurations into a super-common configuration (e.g. "common_security.php") and have this included in the "common_config-{site_name}.php".

Each field will include its own set of Cookbook recipes. I tend to run the same basic half dozen, so they are put into farmconfig.php, and each different recipe is then individually included via local.php.

Moving Cookbook Out of the Web Path

Moving the /cookbook out of the web path is potentially the easiest solution that reaps the largest return. However, as recipes vary in how they are created, the potential for headache remains. First, we will address the solution itself, then discuss the trade-offs.

Move the Cookbook. The first step is simple: locate the /cookbook directory in the /pmwiki-share directory, either by the shell mv command, or FTP. Then, you will need to edit your configuration files to point to the new location. Thus, when a recipe says you should refer to it one way, you will need to use another. Below this paragraph I demonstrate what I mean. By way of shorthand, I created a $FarmC variable, which points to {core_directory}/pmwiki-share/cookbook.


# Change from this:
include_once("cookbook/GreatRecipe.php");

# To this:
include_once("$FarmC/GreatRecipe.php"); # or
include_once("{core_directory}/pmwiki-share/cookbook/GreatRecipe.php");

Advantage of Moving /Cookbook Out of the Web Path. This solution offers certain benefits. The greatest benefit is the security improvement. Pm is aggressive about resolving potential security holes in the PmWiki core. However, not all recipe authors can be. Thus, by moving the recipe out of harm's way, the likelihood of a recipe being exploited is greatly reduced. The second advantage is the availability of the recipes to all fields. In the WikiFarms configuration recipes in $FarmD/cookbook are also available to all wikis.

Disadvantage of Moving /Cookbook Out of the Web Path. This solution offers certain tradeoffs. Recipes are authored by different developers. As a consequence, each developer has a different approach to how he refers to files outside of his recipe. Sometimes, the author assumes that the recipe is in the same directory as his is. Othertimes, the author tries to recruit $FarmD to help locate the outside file. By moving /cookbook out of $FarmD (and this entire recipe puts $FarmD away from all local directories), then those recipes fail. The solution is to either correct the error yourself, and/or contact the recipe developer and recommend he adopt an alternative approach (like the one below).

In other words (for non-technical readers) if you use this configuration you will have to edit the code in many recipes to make them work. Peter Bowers June 03, 2015, at 03:04 AM

define('GREATRECIPEPATH', dirname(__File__) . '/'); # or
define('COOKBOOKPATH', dirname(__File__) . '/'); 

Moving wiki.d Out of the Web Path

If you also want to move the wiki.d outside the web area, just remember to set $WorkDir in each field's configuration file. To make this work, create a new directory /pmwiki-share/work_dirs, and copy the /wiki.d from each field. You will want to give every /wiki.d a unique name to prevent overwriting files. Here is how this solution would look:

 {core_directory}
   /pmwiki-share
     /work
        /example_org-wiki.d
        /example_com-wiki.d
        /example_net-wiki.d

Linking to Non-Web Path Wiki.D Before we change PmWiki's behavior, I'd like to briefly mention some relevant variables. There are three variables that are affected: $WorkDir, $WikiDir, and $LastModFile. Typically, the first two point to the same place, but the latter is a PageStore object, and the former is a string. $LastModFile relies on $WorkDir to know where to track the modification on a file. A fourth variable $WikiLibDirs is another variable we have to monkey with to make this work.

In the shared configuration, we are going to rebuild the $WikiLibDirs as shown below. Notice that I explicitly set all variables rather than leave it to chance. We are also creating a new per-farm variable called $WikiD---this variable is set in the farm's local configuration file. For example, if your new wiki.d is example_org-wiki.d, then you would put in your local file "$WikiD = 'example_org-wiki.d';" This becomes {core_directory}/pmwiki-share/work/example_org-wiki.d.

  $WorkDir = "$FarmS/work/$WikiD/";
  $LastModFile = "$WorkDir.lastmod";
  $WikiDir = new PageStore("$WorkDir\$FullName", 1);
  $WikiLibDirs = array(
     &$WikiDir,
     new PageStore("$FarmM/work/share.d/\$FullName"),
     new PageStore("$FarmD/wikilib.d/\$FullName")
  );

Advantage of Moving Wiki.d Out of the Web Path. This solution offers certain benefits. The chief advantage of moving /wiki.d out of the web path is increased security. This is because the /wiki.d directory is world-writable and world-executable, which gives a skilled cracker the chance to put and execute code on your server. While PmWiki seeks to mitigate this, the hazard remains. Moving it out reduces that hazard further. Additionally, this allows the site administrator to put all the /wiki.d directories in a common directory, which helps with archival and other administrative functions. That is, you can archive all sites and fields by tarballing a single path.

Disadvantage of Moving Wiki.d Out of the Web Path. This solution offers certain tradeoffs. The chief disadvantage results from having to configure PmWiki to behave outside its default behavior. Also, by grouping all /wiki.d together, there is an increased chance of accidently wiping out all of them. However, that is why we backup early and often.

Moving /pub Out of the Web Path

This solution presumes Pm will be implementing $SkinLibDirs in a version after 2.1.15. Therefore, exact implementation of $SkinLibDirs is subject to change. There are three steps to this solution: 1) relocating the /pub directory 2) editing the $SkinLibDirs and $SkinDirUrl to instruct PmWiki where to look for skins and 3) editing .htaccess to instruct the web server where to look for /pub. Until $SkinLibDirs is implemented, this recipe will add an alternate step 4, which is to create a symlink. This step would be superceded by the implementation.

Relocating /pub directory. The first step is to relocate the /pub directory. This is perhaps the more straight-forward step, as it only necessitates moving the contents. This is a simple mv pub {core_directory}/pmwiki-share/. command. In FTP environments, just FTP the contents to the new location and then delete them from the default location.

Configure Skin Location. Not implemented $SkinLibDirs is an array of directories that Pmwiki uses to search for the skin directory. The 2.1.15 and earlier method checked the script's directory and $FarmD. This array allows the site administrator to add a directory. In the farmconfig.php, add the following line of code. This will instruct PmWiki where to find the skin directory, which is essential for PmWiki to use the skin.

$SkinLibDirs = array('{core_directory}/pmwiki-share/pub');

Configure /pub Location for Web Server. Not implemented Once /pub has been removed from the web-path, the web server must be instructed where to find its contents. In Apache, this can be accomplished via editing the .htaccess file. If you are using Cookbook:CleanUrls, then you may already be familiar with this file. In the .htaccess file, after all the other RewriteRules, add the code line below. This line tells Apache to look in {core_directory}/pmwiki-share/pub to satisfy any reference to /pub. This means that a browser request for a file, say /pub/skins/pmwiki/screen.css will cause Apache to deliver the file located at {core_directory}/pmwiki-share/pub/skins/pmwiki/screen.css. I consider this a neat piece of voodoo. By the time you have reached this step, you should be able to view the Pmwiki site with your chosen skin without needing to have /pub in every field.

RewriteRule ^/pub/?(.*) {core_directory}/pmwiki-share/pub/$1 [L,qsa]

Alternate: Symlink to /pub. If you are unable to implement the $SkinLibDirs and .htaccess methods, described above, then the alternative is to create a symlink to /pmwiki-share/pub to the wiki field. To do this, ensure that your current working directory is where you want the symlink (pwd). Then, execute this shell command: ln -sf {core_directory}/pmwiki-share/pub pub. You now have a linked /pub directory.

Advantage of Moving /pub Out of the Web Path. Of course, with every solution there needs to be a reason. The advantage to moving this directory out of the web path is largely administrative. When you manage several wiki fields that share the same skins, javascripts or CSS files, then you may prefer to update that file one time and have that update effective on all fields simultaneously. This solution provides that advantage. Additionally, not having multiple copies of the /pub directory reduces the amount of disk space used by your implementation. While the amount of space used is probably trivial for many administrators, for some administrators disk space remains a concern.

Disadvantage of Moving /pub Out of the Web Path. All solutions offer some trade off. The disadvantage to this solution is that there is some overhead involved by editing files and some potential debuggery of the .htaccess or $SkinLibDirs code. Additionally, some web servers prohibit symlinks or path redirection. My opinion on restrictive web hosts is to consider an alternative, as web hosting is a commodity market.

Advantage: Upgrades

The real advantage with this recipe lies in the ability to upgrade. (Upgrades are even simpler using the standardized configuration found in WikiFarms - you will do the first 2 steps below and then you will be done.) When you are in a symlink situation, here is what you need to do:

  1. Download newest version of PmWiki
  2. Untar the version in the {core_directory}
  3. Remove the /pmwiki-latest symlink
  4. Add symlink from /pmwiki-2.3.31 to /pmwiki-latest
Code:
  wget -c http://www.pmwiki.org/pub/pmwiki/pmwiki-latest.tgz
tar xvfz pmwiki-latest.tgz
  rm pmwiki-latest
  ln -sf pmwiki-2.3.31 pmwiki-latest

Without symlinking, you still follow the first two steps. Instead of steps three and four, you would instead edit /pmwiki-share/local/version.php and edit in the new version number.

In both cases, if the new installation does not work, just reverse the version assignment (i.e, revert the symlink change, or edit back the older, stable version number).

Releases

August 26, 2006. BenWilson August 26, 2006, at 03:33 PM

August 13, 2006. Added solution for moving wiki.d directories out of the web-accessible directory. BenWilson August 13, 2006, at 03:33 PM

July 2, 2006. I am trying a new rewrite to help clarify some confusion raised. BenWilson July 02, 2006, at 03:33 PM

February 10, 2006. I have used this to host multiple sites on the same server-where each site has its own Farm. I am making this available to other administrators. BenWilson February 10, 2006, at 03:33 PM

Comments

Comments to this recipe are available on its discussion page. Past discussions are also archived there.

See Also

Contributors

BenWilson


User notes? : If you use, used or reviewed this recipe, you can add your name. These statistics appear in the Cookbook listings and will help newcomers browsing through the wiki.