The Simplest MediaWiki Update Script for Single-Server MediaWiki Site

System requirements:

  • User uploads $wgUploadDirectory are stored offsite
  • Non-Docker MediaWiki with normal setup
  • Composor installed (Can be installed automatically during updating)

Goals:

  • Update MediaWiki with nearly zero downtime
  • Download and install latest tagged MediaWiki from tarball package
  • Update extensions and skins from latest git tagged branch
  • Install extension-specific dependencies during updating

First create a script name it mw-update.sh and make sure the script exits if anything goes wrong:

# Exit the whole script if anything goes wrong
set -e

Create a config file mw-update.conf with the following content:

version=1.42.1
tmp="/tmp"
dest_base="/srv/www/wikiroot"
dest="/public_html"
permissions="33.33"
mode="docker"
docker_container_name="docker-php-1"
docker_dest_base="/app/wikiroot/public_html"
extensions="
BetaFeatures
CheckUser
CommonsMetadata
MobileFrontend
TorBlock
Popups
AntiSpoof
WikiLove
cldr
Flow
ContributionScores
intersection
UploadWizard
RevisionSlider
"
skins="
MinervaNeue
"

Switch back to your script. Add more variables to use later:

# Exit the whole script if anything goes wrong
set -e

MWU_START=$(date +%s)

# Set the current working directory to the directory of this script
# http://stackoverflow.com/a/17744637/412385
cd "${0%/*}"

# Check if config exists
if [ ! -f ./mw-update.conf ]; then
  echo -e "Config not found, run the following command first:"
  echo -e "\n$ cp mw-update.sample.yml mw-update.yml"
  exit 1
fi

# Parse config
if [ -f "mw-update.conf" ]; then
  source "mw-update.conf"
else
  echo "Error: Configuration file not found."
  exit 1
fi

# MediaWiki uses git tag for latest stable version
MW_VER=$version

# Decoration
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Production dir
PROD_BASE="$dest_base"
PROD_DIR="$PROD_BASE$dest"

# `MW_VER_MAIN` is for the stupid download URL
MW_VER_MAIN=$(echo "$MW_VER" | sed -E 's/\.[0-9]+$//g')

# Temp dir prepare for update
TMP_BASE="$tmp/mediawiki-update"
TMP_DIR="$TMP_BASE/mediawiki-$MW_VER"

# Extensions dir
EXT_DIR="$TMP_DIR/extensions"
SKIN_DIR="$TMP_DIR/skins"

# Extensions use git branch for latest stable version
EXT_VER=$(echo "REL$MW_VER_MAIN" | sed -E 's/\./_/g')

# List of custom extensions
EXTENSIONS="$extensions"
SKINS="$skins"

Then print the setup info for confirmation:

# Prompt before executing anything
echo -e "${BLUE}MediaWiki Auto Updater${NC}"
echo -e "Sparanoid ([email protected])\n"
echo -e "     Running mode: ${BLUE}$mode${NC}"
echo -e "     Core version: ${BLUE}$MW_VER${NC}"
echo -e "   Branch version: ${BLUE}$MW_VER_MAIN${NC}"
echo -e "Extension version: ${BLUE}$EXT_VER${NC}"
echo -e "   Temp directory: ${BLUE}$TMP_BASE${NC}"
echo -e " Destination base: ${BLUE}$PROD_BASE${NC}"
echo -e "      Destination: ${BLUE}$PROD_DIR${NC}"
echo -e " Docker Directory: ${BLUE}$docker_dest_base${NC}"
echo -e "   Container name: ${BLUE}$docker_container_name${NC}"
echo -e "      Permissions: ${BLUE}$permissions${NC}"
echo -e "Custom extensions: ${BLUE}$EXTENSIONS${NC}"
echo -e "     Custom skins: ${BLUE}$SKINS${NC}"

Check if PHP Composor installed:

function check_composer() {
  # Allow running Composer with root
  export COMPOSER_ALLOW_SUPERUSER=1

  if command -v composer >/dev/null 2>&1 ; then
    echo -e "${BLUE}PHP Composer found: $(composer --version  | head -n 1)${NC}"
  else
    echo -e "PHP Composer not found, trying to install it..."
    php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
    php composer-setup.php
    php -r "unlink('composer-setup.php');"
    echo -e "Move PHP Composer to /usr/local/bin/composer for global use"
    mv composer.phar /usr/local/bin/composer
  fi
}

if [[ $* == *--clean* ]]; then
  echo -e "\033[0;31mCLEAN MODE: ALL FILES UNDER ${TMP_BASE} WILL BE DELETED!${NC}"
fi

read -p "Press enter to continue..."
echo -e "\n"

if [[ $* == *--clean* ]]; then
  echo -e "${BLUE}Removing update leftovers (--clean)${NC}"
  rm -rf $TMP_BASE
fi

Begin to update MediaWiki cores:

# Update core files
echo -e "${BLUE}Creating essential directories${NC}"
mkdir -p $TMP_BASE

echo -e "${BLUE}Updating MediaWiki core files${NC}"
cd $TMP_BASE
wget -c "https://releases.wikimedia.org/mediawiki/$MW_VER_MAIN/mediawiki-$MW_VER.tar.gz"
tar -zxf mediawiki-$MW_VER.tar.gz

echo -e "${BLUE}Backing up LocalSettings.php${NC}"
cd $TMP_DIR
cp $PROD_DIR/LocalSettings.php $TMP_DIR/

Update extensions:

# Update extensions
for extension in $EXTENSIONS
do
  echo -e "${BLUE}Updating extension $extension...${NC}"
  cd $EXT_DIR
  if [ ! -d "$EXT_DIR/$extension" ]; then
    echo -e "${EXT_DIR}/${extension} git repo does not exists!"
    git clone "https://gerrit.wikimedia.org/r/mediawiki/extensions/$extension.git"
  fi
  cd "$EXT_DIR/$extension"
  git reset --hard
  git clean -f -d
  git pull
  # shellcheck disable=SC2086
  git checkout $EXT_VER
  echo ""
done

Some extensions need special setup process like update git submodules or install dependencies with Composor:

# Extension specific process
# No longer required. VisualEditor is now bundled with MediaWiki 1.35 and above
if [[ ${extensions} =~ "VisualEditor" ]]; then
  echo -e "${BLUE}Updating submodules for VisualEditor...${NC}"
  cd $EXT_DIR/VisualEditor
  git submodule update --init
  echo ""
fi

if [[ ${extensions} =~ "Flow" ]]; then
  echo -e "${BLUE}Updating composer for Flow (StructuredDiscussions)...${NC}"
  cd $EXT_DIR/Flow
  if [[ ${mode} == "docker" ]]; then
    docker run --rm -it -v "$PWD":/app composer update --no-dev
  fi

  if [[ ${mode} == "local" ]]; then
    check_composer
    composer update --no-dev
  fi
  echo ""
fi

if [[ ${extensions} =~ "AntiSpoof" ]]; then
  echo -e "${BLUE}Updating composer for AntiSpoof...${NC}"
  cd $EXT_DIR/AntiSpoof
  if [[ ${mode} == "docker" ]]; then
    docker run --rm -it -v "$PWD":/app composer update --no-dev
  fi

  if [[ ${mode} == "local" ]]; then
    check_composer
    composer update --no-dev
  fi
  echo ""
fi

Update skins:

<span class="hljs-meta prompt_"># </span><span class="language-bash">Update skinsfor skin <span class="hljs-keyword">in</span> <span class="hljs-variable">$SKINSdo</span>  <span class="hljs-built_in">echo</span> -e <span class="hljs-string">"<span class="hljs-variable">${BLUE}</span>Updating skin <span class="hljs-variable">$skin</span>...<span class="hljs-variable">${NC}</span>"</span>  <span class="hljs-built_in">cd</span> <span class="hljs-variable">$SKIN_DIR</span>  <span class="hljs-keyword">if</span> [ ! -d <span class="hljs-string">"<span class="hljs-variable">$SKIN_DIR</span>/<span class="hljs-variable">$skin</span>"</span> ]; <span class="hljs-keyword">then</span>    git <span class="hljs-built_in">clone</span> https://gerrit.wikimedia.org/r/mediawiki/skins/<span class="hljs-variable">$skin</span>.git  <span class="hljs-keyword">fi</span>  <span class="hljs-built_in">cd</span> <span class="hljs-variable">$SKIN_DIR</span>/<span class="hljs-variable">$skin</span>  git reset --hard  git clean -f -d  git pull  git checkout <span class="hljs-variable">$EXT_VER</span>  <span class="hljs-built_in">echo</span> <span class="hljs-string">""</span><span class="hljs-keyword">done</span></span>

Please note that all the above process are done in $TMP_BASE directory. The website is still untouched at the moment. Now lets go into $TMP_BASE to finalize the prepare process:

cd $TMP_BASE

echo -e "${BLUE}Backing up old files...${NC}"
# tar mediawiki-$MW_VER-update-backup.tar.gz -C $PROD_DIR .
mv $PROD_DIR "$PROD_BASE/mediawiki-$MW_VER-backup-$(date +%F-%H:%M)"

echo -e "${BLUE}Moving updated files to production...${NC}"
cp -R $TMP_DIR $PROD_DIR

echo -e "${BLUE}Updating directory permissions ($permissions)...${NC}"
chown -R $permissions $PROD_DIR

echo -e "${BLUE}Running MediaWiki maintenance ($mode mode)...${NC}"
if [[ ${mode} == "docker" ]]; then
  docker exec $docker_container_name php $docker_dest_base/maintenance/update.php --quick
fi

if [[ ${mode} == "local" ]]; then
  php $PROD_DIR/maintenance/update.php --quick
fi

MWU_END=$(date +%s)
MWU_RUNTIME=$((MWU_END - MWU_START))

echo -e "${BLUE}Done! Time took: ${MWU_RUNTIME}s${NC}"