Blog Logo Screenlight Blog

Versioniertes MediaWiki HTML Offline Backup

cover

Vollständiges HTML Offline-Backup einer MediaWiki Installation mit Login und Versionierung

MediaWiki mit der Semantic MediaWiki Extension ist ein sehr mächtiges Tool. Wir verwenden es intern für die Dokumentation von allen möglichen Informationen. Von der Giessanleitung der Bürobepflanzung über Resourcen wie Drucker und Firmenautos, Projektinformationen, Software-Deploys bis hin zu einer vollständigen Netzwerkdokumentation.

Auf Grund der Wichtigkeit der enthaltenen Informationen können wir es uns nicht leisten, dass unser Wiki nicht verfügbar ist. Auch bei regelmässigen Backups bleibt immer noch die Frage, wie wir an essentielle Informationen herankommen, wenn unser lokales Netzwerk nicht verfügbar ist. Denn sämtliche Informationen zum Supprt unseres Netzwerks befinden sich ebenfalls im Wiki. Wir könnten extrem wichtige Informationen zusätzlich auf Papier führen, doch dann müssten wir diese auch doppelt aktualisieren und pflegen.

Aus diesem Grund brauchen wir eine Kopie des gesamten Wiki, die lokal auf einer Workstation und ohne einen Webserver funktioniert. Wir wollen eine regelmässig aktualisierte Offline-Kopie sämtlicher Daten in HTML. Um selbst in der Offline Kopie Änderungen nachvollziehen zu können, versionieren wir die gesamten Dateien mit Git.

HTML Offline Backup

Zum Speichern einer vollständigen HTML Kopie einer MediaWiki Installation gibt es die DumpHTML Extension. Diese Extension hat jedoch einige Nachteile: keine Unterstützung für eigene Skins und Probleme mit anderen Extensions, die den Output manipulieren.

Wir verwenden zum Downloaden der HTML Kopie deshalb wget.

Auf der ersten Blick klingt das relativ einfach. wget folgt mit der --recursive Option allen Links und lädt deren Inhalt ebenfalls herunter:

wget --recursive http://wiki.example.com/

Problem #1: Login

Unser Wiki erfordert, dass sich ein Benutzer, um Inhalte zu lesen, zuerst einloggt.

Um Logins durch externe Formulare zu vermeiden, verwendet MediaWiki ein Login Token verfahren. Dabei wird beim Anfragen des Loginformulars ein Token erzeugt, der beim Einloggen zusammen mit dem Benutzernamen und dem Passwort mitgeschickt werden muss.

Unser Script muss also folgendes tun:

  1. Login Token anfragen
  2. Einloggen
  3. Downloaden

Vorher definieren wir jedoch ein paar Variablen, die wir während dem ganzen Prozess immer wieder brauchen werden:

MW_USER="PeterPan" # Benutzername
MW_PASS="dontgrowup" # Passwort
MW_SERVER="wiki.example.com" # vollständige Domain
MW_URL="http://${MW_SERVER}" # vollständige URL
MW_PATH="w" # Rewrite Pfad (z.B. wiki.example.com/w/Main_Page)
MW_DIR="wiki" # Verzeichnis für nicht umgeschriebenen URLs (z.B. /wiki/index.php?title=...)
MW_LOGIN_PAGE="Special:UserLogin" # Name der Login Page
MW_COOKIE_PATH="cookies.txt" # Cookie Datei, die für alle Requests verwendet wird

Zuerst müssen wir die Login Page anfragen, den Login Token extrahieren und in eine Variable speichern:

MW_LOGIN_TOKEN=$(wget -q -O - 
    --save-cookies "${MW_COOKIE_PATH}" 
    --keep-session-cookies 
    "${MW_URL}/${MW_PATH}/${MW_LOGIN_PAGE}" 
    | grep wpLoginToken 
    | grep -o '[a-z0-9]{32}')

Die Variable MW_LOGIN_TOKEN enthält nun den Token, den wir zum Einloggen verwenden können. Wichtig ist auch, dass wir für alle HTTP requests unsere Cookie Datei laden und speichern, damit MediaWiki uns an Hand der Session ID erkennen kann. Wir verwenden deshalb bei jeder Anfrage die --load-cookies--save-cookies und --keep-session-cookies.

Zum Einloggen setzen wir einen POST Request mit unseren Login Informationen und dem Login Token auf die Ziel-URL des LoginFormulars (“/index.php?title=Special:UserLogin&action=submitlogin&type=login”):

wget -q --load-cookies "${MW_COOKIE_PATH}" 
    --save-cookies "${MW_COOKIE_PATH}" 
    --keep-session-cookies 
    --post-data "wpName=${MW_USER}&wpPassword=${MW_PASS}&wpRemember=1&wpLoginattempt=Log%20in&wpLoginToken=${MW_LOGIN_TOKEN}" 
    "${MW_URL}/${MW_DIR}/index.php?title=${MW_LOGIN_PAGE}&action=submitlogin&type=login"

Nun können wir als eingeloggter Benutzer sämtliche Wiki Pages herunterladen und speichern:

wget 
    --load-cookies "${MW_COOKIE_PATH}" 
    --save-cookies "${MW_COOKIE_PATH}" 
    --keep-session-cookies 
    --recursive 
    "${MW_URL}"

Problem #2: Unerwartet ausgeloggt

wget wird mit der --recursive Option früher oder speater auch den Logout Link (“/wiki/index.php?title=Special:UserLogout”) aufrufen, was zu Folge hat, dass unsere Session invalidiert wird und alle darauffolgenden Requests nur noch das Loginformular herunterladen werden.

Mit der --reject Option können wir Muster von Links angeben, die wget nicht folgen soll. Wir nutzen diese Gelegenheit, um auch noch andere Seiten auszuschliessen, die wir nicht herunterladen wollen:

wget 
    --reject "*index.php*, *Help*, *Special*" 
    --load-cookies "${MW_COOKIE_PATH}" 
    --save-cookies "${MW_COOKIE_PATH}" 
    --keep-session-cookies 
    --recursive 
    "${MW_URL}"

Hier schliessen wir alle Seiten aus, die “index.php” beinhalten. Wir verhindern dadurch, dass unser Script z.B. auf die “Edit” Page geht oder sich selber ausloggt. Zudem verhindern wir den Download aller Hilfe- und Spezialseiten. Die Hilfeseiten brauchen wir nicht und die Spezialseiten könnten unerwünschte Aktionen auslösen.

Weitere Optionen

Um unseren Download weiter zu optimieren, fügen wir ein paar weitere Optionen hinzu:

MW_DOMAIN="example.com"

wget 
    --reject "*index.php*, *Help*, *Special*" 
    --load-cookies "${MW_COOKIE_PATH}" 
    --save-cookies "${MW_COOKIE_PATH}" 
    --keep-session-cookies 
    --recursive 
    --quiet 
    --page-requisites 
    --adjust-extension 
    --convert-links 
    --restrict-file-names=ascii 
    --domains="${MW_DOMAIN}" 
    --no-parent 
    "${MW_URL}"
rm ./index.php*
rm $MW_COOKIE_PATH

Banner

Wiki mit "OFFLINE COPY" Banner und roten externen Links

Wiki mit “OFFLINE COPY” Banner und roten externen Links

Unser Script kann unser gesamtes Wiki herunterladen und speichern. Alle Dateien befinden sich in einem Unterverzeichnis, das gleich heisst wie die Domain und unserer Variable MW_SERVER entspricht.

Damit die Offline-Kopie nicht mit der Online-Version verwechselt wird, fügen wir auf allen Seiten per CSS ein Banner ein, das darauf hinweist, dass es sich um eine Offline-version handelt, und machen alle Links rot, die auf Seiten zeigen, die nicht offline verfügbar sind. Wir hängen dafür die nötigen Stildefinitionen einfach der CSS Datei unseres Skins an:

MW_SKIN="Screenlight"

echo "#header:after { content: 'OFFLINE COPY'; position: absolute; background-color: rgba(255, 85, 85, 0.8); text-align: center; color: #fff; font-weight: bold; top: 0; left: 0; width: 100%; height: 27px; line-height: 27px; font-size: 18px; }" 
>> "${MW_SERVER}/${MW_DIR}/skins/${MW_SKIN}/main.css?303.css"

echo 'a[href^="http://"] { color: rgb(255, 85, 85) !important; }' 
>> "${MW_SERVER}/${MW_DIR}/skins/${MW_SKIN}/main.css?303.css"

Versionieren mit Git

Damit wir Änderungen im Wiki auch in der Offline-Version nachvollziehen können, versionieren wir alle Dateien mit Git. Allerdings nur dann, wenn Git auch installiert ist:

git --version 2>&1 >/dev/null
GIT_IS_AVAILABLE=$?

if [ $GIT_IS_AVAILABLE -eq 0 ]; then
...

Danach müssen wir zuerst überprüfen, ob unsere Offline-kopie bereits ein Git Repository ist. Falls nicht, initialisieren wir es mit git init. Anschliessend stagen wir mit git add alle Dateien und setzen mit git commit einen Commit ab.

Die Heruntergaladenen Dateien befinden sich in einem Unterverzeichnis, dessen Name unserer MW_SERVER Variable entspricht. Deshalb wechseln wir zuesrt in das Verzeichnis:

cd ${MW_SERVER}
git rev-parse --resolve-git-dir .git 2>&1 >/dev/null
IS_REPO=$?

if [ $IS_REPO -ne 0 ]
then
    git init
fi

git add .
git commit -m "update $(date)"
cd ..

Problem: Fast alle Dateien sind als “modified” markiert

Nachdem wir unser Script zum zweiten Mal ausgeführt haben, weisen fast alle Dateien Änderungen auf, obwohl nur eine einzige Wiki Page verändert wurde.

Der Grund dafür liegt daran, dass MediaWiki in der HTML Ausgabe Statistiken und andere Informationen in Form von HTML- und JavaScript-Kommentaren ausgibt, die sich bei jedem Seitenaufruf ändern:

<!-- Served in 1.518 secs. -->
/* cache key: companywiki:resourceloader:filter:minify-css:7:0d2a291211d507cdeebf3936328645c9 */

Bevor wir mit der Git-Versionierung fortfahren können, müssen wir deshalb alle Kommentare entfernen. Wir verwenden dafürfind und sed:

find "./${MW_SERVER}" -type f -name "*.html" -exec sed -i '' -e :a -re 's/<!--.*?-->//g;/<!--/N;//ba' {} +

find "./${MW_SERVER}" -type f -exec sed -i -e '/^/* cache key/d' {} +

An ein Remote Repository pushen

Jetzt wo alles funktioniert, möchten wir unser lokales Repository an ein zentrales Repository pushen. Dazu fügen wir nach dem Initialisieren unser Remote Repository hinzu und pushen nach dem Commit an unser zentrales Repository:

MW_GIT_REMOTE="git@code.example.com:wiki-html.git"

cd ${MW_SERVER}
git rev-parse --resolve-git-dir .git 2>&1 >/dev/null
IS_REPO=$?

if [ $IS_REPO -ne 0 ]
then
    git init
    git remote add origin $MW_GIT_REMOTE
fi

git add .
git commit -m "update $(date)"
git push -u origin master
cd ..

Alles zusammen

Das vollständige Script:

#!/bin/bash

#
# Define variables
#

MW_USER="PeterPan" # insert username here
MW_PASS="nevergrowup" #insert password here

MW_DOMAINS="example.com"
MW_SERVER="wiki.example.com"
MW_URL="http://${MW_SERVER}"
MW_PATH="w"
MW_DIR="wiki"
MW_LOGIN_PAGE="Special:UserLogin"
MW_COOKIE_PATH="cookies.txt"
MW_SKIN="Screenlight"
MW_GIT_REMOTE="git@code.example.com:wiki/wiki-html.git"

#
# Fetch login token
#

MW_LOGIN_TOKEN=$(wget -q -O - 
    --save-cookies "${MW_COOKIE_PATH}" 
    --keep-session-cookies 
    "${MW_URL}/${MW_PATH}/${MW_LOGIN_PAGE}" 
    | grep wpLoginToken 
    | grep -o '[a-z0-9]{32}')

#
# Log in
#

wget -q --load-cookies "${MW_COOKIE_PATH}" 
    --save-cookies "${MW_COOKIE_PATH}" 
    --keep-session-cookies 
    --post-data "wpName=${MW_USER}&wpPassword=${MW_PASS}&wpRemember=1&wpLoginattempt=Log%20in&wpLoginToken=${MW_LOGIN_TOKEN}" 
    "${MW_URL}/${MW_DIR}/index.php?title=${MW_LOGIN_PAGE}&action=submitlogin&type=login"

#
# Fetch wiki contents
#

wget 
    --quiet 
    --reject "*index.php*, *Help*, *Special*" 
    --load-cookies "${MW_COOKIE_PATH}" 
    --save-cookies "${MW_COOKIE_PATH}" 
    --keep-session-cookies 
    --recursive 
    --page-requisites 
    --adjust-extension 
    --convert-links 
    --restrict-file-names=ascii 
    --domains="${MW_DOMAIN}" 
    --no-parent 
    "${MW_URL}"

#
# Clean up
#

rm ./index.php*
rm $MW_COOKIE_PATH

#
# Insert banner
#

echo "#header:after { content: 'OFFLINE COPY'; position: absolute; background-color: rgba(255, 85, 85, 0.8); text-align: center; color: #fff; font-weight: bold; top: 0; left: 0; width: 100%; height: 27px; line-height: 27px; font-size: 18px; }" 
    >> "${MW_SERVER}/${MW_DIR}/skins/${MW_SKIN}/main.css?303.css"

echo 'a[href^="http://"] { color: rgb(255, 85, 85) !important; }' 
    >> "${MW_SERVER}/${MW_DIR}/skins/${MW_SKIN}/main.css?303.css"

#
# Replace unwanted HTML and JavaScript comments
#

find "./${MW_SERVER}" -type f -name "*.html" -exec sed -i '' -e :a -re 's/<!--.*?-->//g;/<!--/N;//ba' {} +
find "./${MW_SERVER}" -type f -exec sed -i -e '/^/* cache key/d' {} + # # Version control with Git # git --version 2>&1 >/dev/null
GIT_IS_AVAILABLE=$?

if [ $GIT_IS_AVAILABLE -eq 0 ];
then
    cd ${MW_SERVER}
    git rev-parse --resolve-git-dir .git 2>&1 >/dev/null
    IS_REPO=$?

    if [ $IS_REPO -ne 0 ]
    then
        git init
        git remote add origin $MW_GIT_REMOTE
    fi

    git add .
    git commit -m "update $(date)"
    git push -u origin master
    cd ..
fi

Leave a Response

One Response to “Versioniertes MediaWiki HTML Offline Backup”

MG
October 1st, 2013 at 1:04 pm

Was waere die Welt ohne Max? Danke fuer die ausfuehrliche Erklaerung und die Code-Erklaerungen dazu.