Wiki Farm

This document covers how to change from one instance or standalone install covered in documentation to a farm. This example configuration and instructions is how we setup at sophia.host to use a wiki and to use the same engine used in wiki.sophia.host

What config.php change

  • The live wiki is not bare -- $Skin = 'dark', cmsmode.php, langviews.php, ape.php, markdown-output, and the four $WikiStyle boxes are all active. Documented ~/wiki/local/ (the pristine core copy), the live config lives in the mirror at ~/doc/conf/var/www/htdocs/wiki/local/config.php and is the one you pasted.
  • $EnableHTML = 1; is a no-op -- the enablehtml.php recipe is not in cookbook/, so the variable is set but nothing reads it. Leaving it as-is is harmless and matches your "defer EnableHTML" decision.
  • relayd already routes sophia.host and wiki.sophia.host to the same <www> backend and the cert SAN already covers both -- no relayd or ACME changes needed.
  • No farmconfig.php exists, so the live wiki is a standalone install. $FarmD in the config already points at htdocs/wiki, so every include_once("$FarmD/cookbook/...") will keep working after we promote it to a farm home.

File layout

htdocs/wiki/                      <- farm home (already live, keeps wiki.sophia.host)
|-- local/
|   |-- farmconfig.php            <- NEW: shared settings (skin, cookbook, styles)
|   `-- config.php                <- per-wiki: title, ScriptUrl, PubDirUrl, passwords, CMS block
|-- wiki.d/                       <- wiki.sophia.host content (unchanged)
`-- ...

htdocs/www/                       <- field wiki, sophia.host (new)
|-- index.php                     <- include_once('../wiki/pmwiki.php');
|-- favicon.ico                   <- copy of the wiki's favicon (optional)
`-- local/
    `-- config.php                <- per-wiki: title, ScriptUrl, PubDirUrl, passwords, CMS block

Why split config.php at all? Because farmconfig.php is included automatically by pmwiki.php for every wiki in the farm, so shared settings (skin, cookbook includes, WikiStyles, uploads policy, highlight, markdown, langviews) live in one place. Per-wiki config.php keeps only what differs. Without this split you'd duplicate the skin/cookbook block into www/local/config.php and they'd drift.

One subtlety: farmconfig.php is included before PmWiki parses the request, so $action and $pagename are not yet resolved there. Anything gated on $action (APE, RSS feeds) and the $pagename = ResolvePageName($pagename); line must stay in the per-wiki config.php. The cmsmode.php include and its SDVA($CMSRedirGroups, ...) also stay per-wiki, right after the $pagename line, to preserve the current working order.

Proposed contents

htdocs/wiki/local/farmconfig.php -- new file:

<?php if (!defined('PmWiki')) exit();

$FarmPubDirUrl = 'https://wiki.sophia.host/pub';

$Skin = 'dark';
$EnablePathInfo = 1;
$PageLogoUrl = "$FarmPubDirUrl/logo.gif";

$EnableAutoSkinList = 1;
$PageSkinList = array(
    'classic' => 'pmwiki',
    'modern'  => 'adapt',
    'white'   => 'pmwiki-responsive',
    'dark'    => 'dark');
include_once("$FarmD/cookbook/skinlist.php");
$PageSkinList['print'] = -2;
$PageSkinList['pmwiki-responsive'] = -2;
$PageSkinList['pmwiki'] = -2;

include_once("$FarmD/scripts/xlpage-utf-8.php");
XLPage('en','PmWikiEn.XLPage');

$EnableNotSavedWarning = 1;
$EnablePmSyntax = 1;
$PmTOC['Enable'] = 1;

$EnableUpload = 1;
$UploadPermAdd = 0;
$UploadPrefixFmt = '/$Group/$Name';
$UploadDir = "$FarmD/uploads";
$UploadUrlFmt = 'https://wiki.sophia.host/uploads/$1/$2';
$aSize = 1048576;
$UploadExtSize['pub']  = 2048;
$UploadExtSize['jpg']  = $aSize * 2;
$UploadExtSize['jpeg'] = $aSize * 2;
$UploadExtSize['gif']  = $aSize * 2;
$UploadExtSize['png']  = $aSize * 2;
$UploadExtSize['pdf']  = $aSize * 10;
$UploadExtSize['zip']  = $aSize * 50;
$UploadExtSize['gz']   = $aSize * 50;
$UploadExtSize['xz']   = $aSize * 50;
$UploadExtSize['odt']  = $aSize * 50;
$UploadExtSize['apk']  = $aSize * 100;
$UploadExtSize['sh']   = $aSize;
$UploadExtSize['log']  = $aSize * 100;
unset($aSize);

$WikiStyle['greenbox']['class'] = 'rframe';
$WikiStyle['greenbox']['background-color'] = '#bdf4cb17';
$WikiStyle['greenbox']['border'] = '2px solid #4ae371';
$WikiStyle['greenbox']['color'] = 'white';
$WikiStyle['orangebox']['background-color'] = '#bdf4cb17';
$WikiStyle['orangebox']['border'] = '2px solid #ff8300';
$WikiStyle['orangebox']['color'] = 'white';
$WikiStyle['yellowbox']['background-color'] = '#bdf4cb17';
$WikiStyle['yellowbox']['border'] = '2px solid #ffff00d6';
$WikiStyle['yellowbox']['color'] = 'white';
$WikiStyle['redbox']['background-color'] = '#bdf4cb17';
$WikiStyle['redbox']['border'] = '2px solid #f00';
$WikiStyle['redbox']['color'] = 'white';

$EnableHighlight = 1;
$HTMLHeaderFmt['Highlight'] = '
<link rel="stylesheet" href="' . $FarmPubDirUrl . '/highlight/styles/atom-one-dark.min.css" />
<script src="' . $FarmPubDirUrl . '/highlight/highlight.min.js"></script>';

$EnableHTML = 1;   // inert without cookbook/enablehtml.php -- harmless, kept as-is

include_once("$FarmD/cookbook/markdown-output/markdown-output.php");
$MarkdownExtra = TRUE;

$MLVList = array(
    'en' => 'English', 'fr' => 'Francais', 'es' => 'Espanol',
    'de' => 'Deutsch', 'it' => 'Italiano', 'pt' => 'Portugues',
    'sa' => 'Arabic',  'ma' => 'Darija'
);
include_once("$FarmD/cookbook/langviews.php");

Note: $HTMLHeaderFmt['Highlight'] is now built from $FarmPubDirUrl rather than a hard-coded /pub/... . That fixes a latent bug -- the old /pub/... only works on wiki.sophia.host; on sophia.host it would 404.

htdocs/wiki/local/config.php -- replace with the per-wiki remainder:

<?php if (!defined('PmWiki')) exit();

$WikiTitle = 'Sophia Host -- Wiki';
$ScriptUrl = 'https://wiki.sophia.host';
$PubDirUrl = 'https://wiki.sophia.host/pub';

$DefaultPasswords['admin']  = '<same hash as live>';
$DefaultPasswords['attr']   = '<same hash as live>';
$DefaultPasswords['edit']   = '<same hash as live>';
$DefaultPasswords['upload'] = '<same hash as live>';

$pagename = ResolvePageName($pagename);

SDVA($CMSRedirGroups, array('SiteAdmin', 'Site'));
$CMSRedirGroups['Profiles'] = '';
SDVA($CMSExclSearchGroups, array(
    'PmWikiAr','PmWikiBg','PmWikiCa','PmWikiCs','PmWikiDa','PmWikiDe',
    'PmWikiEn','PmWikiEo','PmWikiEs','PmWikiFa','PmWikiFi','PmWikiFr',
    'PmWikiEl','PmWikiHu','PmWikiId','PmWikiIt','PmWikiJa','PmWikiKr',
    'PmWikiLt','PmWikiLv','PmWikiMk','PmWikiNl','PmWikiNo','PmWikiPl',
    'PmWikiPt','PmWikiBr','PmWikiPtBr','PmWikiRo','PmWikiRu','PmWikiSi',
    'PmWikiSk','PmWikiSq','PmWikiSv','PmWikiTa','PmWikiTr','PmWikiUk',
    'PmWikiVi','PmWikiZhCn','PmWikiZhTw'
));
$CMSAllowedActions[] = 'logout';
$CMSAllowedActions[] = 'crypt';
include_once("$FarmD/cookbook/cmsmode.php");

if ($action == 'browse' || !empty(@$_REQUEST['preview'])) {
    include_once("$FarmD/cookbook/ape.php");
}

if ($action == 'rss') include_once("scripts/feeds.php");

htdocs/www/index.php -- new file:

<?php include_once(dirname(__DIR__) . '/wiki/pmwiki.php');

htdocs/www/local/config.php -- new file:

<?php if (!defined('PmWiki')) exit();

$WikiTitle = 'Sophia Host';
$ScriptUrl = 'https://sophia.host';
$PubDirUrl = 'https://wiki.sophia.host/pub';

$DefaultPasswords['admin']  = '<same admin hash>';
$DefaultPasswords['attr']   = '<same attr hash>';
$DefaultPasswords['edit']   = '<same edit hash>';
$DefaultPasswords['upload'] = '<same upload hash>';

$pagename = ResolvePageName($pagename);

SDVA($CMSRedirGroups, array('SiteAdmin', 'Site'));
$CMSRedirGroups['Profiles'] = '';
SDVA($CMSExclSearchGroups, array(
    'PmWikiAr','PmWikiBg','PmWikiCa','PmWikiCs','PmWikiDa','PmWikiDe',
    'PmWikiEn','PmWikiEo','PmWikiEs','PmWikiFa','PmWikiFi','PmWikiFr',
    'PmWikiEl','PmWikiHu','PmWikiId','PmWikiIt','PmWikiJa','PmWikiKr',
    'PmWikiLt','PmWikiLv','PmWikiMk','PmWikiNl','PmWikiNo','PmWikiPl',
    'PmWikiPt','PmWikiBr','PmWikiPtBr','PmWikiRo','PmWikiRu','PmWikiSi',
    'PmWikiSk','PmWikiSq','PmWikiSv','PmWikiTa','PmWikiTr','PmWikiUk',
    'PmWikiVi','PmWikiZhCn','PmWikiZhTw'
));
$CMSAllowedActions[] = 'logout';
$CMSAllowedActions[] = 'crypt';
include_once("$FarmD/cookbook/cmsmode.php");

if ($action == 'browse' || !empty(@$_REQUEST['preview'])) {
    include_once("$FarmD/cookbook/ape.php");
}

if ($action == 'rss') include_once("scripts/feeds.php");

htdocs/www/.htaccess -- defence in depth (httpd already blocks /local/, but this catches misconfig):

<IfModule mod_authz_core.c>
    Require all denied
</IfModule>

httpd.conf diff

Delete these two location blocks from server "sophia.host":

location "/pub" {
    block return 301 "https://$HTTP_HOST$REQUEST_URI/"
}
location "/pub/*" {
    root "/htdocs/pub"
    request strip 1
    directory auto index
    no fastcgi
}

Change the index:

directory { index "index.html" }     ->     directory { index "index.php" }

Add, immediately after root "/htdocs/www":

connection max request body 104857600

location match "/local/(.*)" {
    block drop
}

location match "/favicon.(.*)" {
    request rewrite "/favicon.%1"
    no fastcgi
}

location match "/(.+)/(.+)" {
    request rewrite "/index.php?n=%1.%2&$QUERY_STRING"
    fastcgi socket "/run/php-fpm.sock"
}

location match "/(.+)" {
    request rewrite "/index.php?n=%1&$QUERY_STRING"
    fastcgi socket "/run/php-fpm.sock"
}

location "*.php*" {
    fastcgi socket "/run/php-fpm.sock"
}

No /pub/, /cookbook/, /uploads/ handlers on sophia.host -- those URLs resolve on wiki.sophia.host because $PubDirUrl and $UploadUrlFmt point there. Keep the existing wiki.sophia.host block unchanged (it already has /pub/, /cookbook/, /uploads/, /favicon handlers).

No relayd, no acme-client, no pf changes.

Deploy sequence (manual copy, per your workflow)

  1. Back up first: on the live box, cp htdocs/wiki/local/config.php /root/config.php.wiki.bak and cp htdocs/www/index.html /root/index.html.bak (if it exists).
  2. On caravela, place farmconfig.php and the new config.php at ~/wiki/local/, and create ~/www/index.php, ~/www/local/config.php, ~/www/.htaccess. Copy ~/wiki/favicon.ico to ~/www/favicon.ico.
  3. Copy ~/wiki/local/ -> live htdocs/wiki/local/ (leaving wiki.d/ and uploads/ on the live side untouched -- your normal workflow already does this).
  4. Copy ~/www/ -> live htdocs/www/.
  5. Edit live etc/httpd.conf with the diff above, httpd -n, rcctl restart httpd.
  6. Hit https://wiki.sophia.host/ first to confirm the home wiki still renders (skin, CMS redirect, highlight CSS all load).
  7. Hit https://sophia.host/ -- you should get the PmWiki default Main/HomePage rendering under the dark skin, and ?action=edit should prompt for the shared admin password. Populate Main/HomePage and Main/ pages from there.

Before I finalize -- three small confirmations

  1. Passwords: same hashes on both wikis (shared admin/edit/upload) as I've laid out -- correct, or different per wiki?
  2. htdocs/www/uploads/: with $UploadDir = "$FarmD/uploads" in farmconfig, both wikis share htdocs/wiki/uploads/. That means the field wiki's uploads are served from wiki.sophia.host/uploads/. Acceptable, or do you want a separate www/uploads/?
  3. Front-page edit policy: keep the same edit password on sophia.host, or open it ($DefaultPasswords['edit'] = '') so the tilde community can post news directly?

Once you confirm these three, I'll produce the final file contents with the real password hashes filled in, so you can paste them straight into ~/wiki/local/ and ~/www/local/ and copy up.