Hugo-Workflow auf Hostsharing

Beschreibung meines Hugo-Workflows auf der Plattform des genossenschaftlichen Hosters Hostsharing eG
hugo-hostsharing.png

Seit Anfang des Jahres bin ich Genosse der Hostsharing eG und betreibe alle meine Websites auf der hochverfügbaren Plattform der Hosting-Genossenschaft. Warum ich der Genossenschaft beigetreten bin und keinen eigenen Root-Server mehr betreibe, erkläre ich vielleicht in einem anderen Artikel noch einmal ausführlich.

Hier nur soviel: Hostsharing bietet schon im kleinsten Webpaket Hochverfügbarkeit mit allem Pipapo. Hostsharing ist eine Genossenschaft; ich kann also die Geschäftspolitik als Mitglied mitbestimmen und bin nicht nur Kunde, sondern auch Miteigentümer. Hostsharing ist außerdem schon einige Jahre am Markt. Beinahe wäre ich schon im Gründungsjahr Mitglied geworden. Und last but not least kannte ich zufriedene Genossen und habe die Entwicklung der Genossenschaft über all die Jahre aus der Ferne beobachtet. Hostsharing bietet mir die Möglichkeit mit weniger Aufwand mehr Kontrolle über meine Internetaktivitäten zu haben.

Aber nun zu meinem neuen Workflow.

Disclaimer: Die folgende Anleitung kann Fehler enthalten, ihr benutzt sie auf eigene Gefahr!

Webspace mit dem Online-Verwaltungstool HSAdmin einrichten

Die Einrichtung einer statischen Website auf der Hostsharing-Plattform ist recht einfach. Dazu loggt man sich mit seinen Zugangsdaten in das Verwaltungstool von Hostsharing HSadmin ein. In unserer Anleitung gehen wir davon aus, dass wir ein Webpaket gebucht haben, das XYZ00 heißt. Aus Sicherheitsgründen ist es empfehlenswert, Dienste isoliert zu betreiben, indem man für jeden Dienst einen eigenen Benutzer erstellt, unter dessen Rechte der Dienst läuft. Für den Betrieb einer Website sollte man also einen eigenen Benutzer einrichten. Diesen Benutzer nennt Hostsharing Domain-Admin. In meinem Fall handelt es sich um eine statische Website, die kein größeres Risiko darstellt. Dennoch habe ich einen Benutzer für meine statischen Websites angelegt. Hostsharing sieht eine gewisse Namenskonvention für Benutzer vor. Der Hauptbenutzername, mit dem man sich bei HSAdmin anmeldet, bildet das Präfix neuer Benutzernamen. Wenn der Hauptbenutzername also XYZ00 lautet, kann man den Benutzer für seine Hugo-Website XYZ00-hugo nennen.

Anschließend erstellt man ebenfalls in HSAdmin die Domain, unter der die Website betrieben werden soll, und legt XYZ00-hugo als den Benutzer der Domain fest. Bei der Anlage einer Domain passiert im Hintergrund eine ganze Menge. So wird zum Beispiel das Mailsystem so konfiguriert, dass Postfächer für abuse, postmaster und webmaster angelegt werden. Außerdem wird eine Apache-Konfiguration angelegt, sodass der Server die Website unter der entsprechenden Domain ausliefert.

Statische Website per FTP hochladen

Wenn man sich anschließend per SSH im Webpaket mit dem neuerstellten Benutzernamen einloggt, sieht man, dass HSAdmin eine ganze Ordnerstruktur angelegt hat. Um eine statische Website zu betreiben, reicht es, die HTML-Seiten in das Verzeichnis doms/example.com/subs/www zu kopieren. Man kann also nach dem Aufruf von Hugo, die Dateien im Ordner public einfach per RSYNC oder SFTP in dieses Verzeichnis hochladen.

Dieses Vorgehen hat den Nachteil, dass man immer sehr viele Dateien hochladen muss, da Hugo immer alle Seiten neu baut. Ich habe mich deshalb entschieden, einen anderen Weg zu gehen.

Statische Website mit Git, Git Hooks und Hugo bauen

Ich verwalte meine Websites mit Git und wollte daher folgenden Prozess verwirklichen. Um eine Änderung auf einer Website zu veröffentlichen, soll es ausreichen, den Commit mit Git auf den Server zu pushen. Dort soll Hugo dann die Website automatisch nach jedem Commit neu bauen.

Bei einer Suche im Netz fand ich zwei nahezu gleichlautende Anleitungen, in denen beschrieben wird, wie man das mit Git und Git Hooks erledigen kann.

  1. Anleitung 1
  2. Anleitung 2

Vermutlich ist Anleitung 2 von Anleitung 1 abgekupfert.

Nach der Einrichtung der Domains musste auf Hostsharing bloß noch folgende Schritte durchführen:

  1. SSH Key hochladen
  2. Eine Kopie meines Git-Repositories hochladen
  3. Das hochgeladene Repository als Remote in meinem lokalen Repository eintragen.
  4. Hugo installieren
  5. Den Post-Receive-Hook anlegen

SSH Key hochladen

Der SSH Key muss in das Verzeichnis des Benutzers hochgeladen werden, der die Website auf Hostsharing verwaltet. Dies ist der oben mit HSAdmin angelegte Benutzer XYZ00-hugo im Webpaket XYZ00.

$ ssh-copy-id XYZ00-hugo@xyz00.hostsharing.net

Anschließend kann man sich ohne Passwort auf dem Host anmelden.

Bare Repository kopieren

Zunächst muss man von dem lokale Repository, in dem man seine Hugo-Website pflegt, ein sogenanntes Bare-Repository anlegen. Ein Bare-Repository enthält kein Arbeitsverzeichnis, sondern nur alle Commits im .git-Verzeichnis. Da dieses Repository hochgeladen und dann auf dem Heimrechner nicht mehr benötigt wird, kann man es ins /tmp-Verzeichnis schreiben, wo es beim nächsten Neustart des lokalen Rechners automatisch gelöscht wird.

$ git clone --bare ~/hugo_website /tmp/hugo_website.git

Dann lädt man das Repository auf den Produktionsserver. In unserem Fall in den Account XYZ00-hugo im Webpaket XYZ00 bei Hostsharing.

$ scp -r /tmp/hugo_website.git XYZ00-hugo@xyz00.hostsharing.net:

Der Doppelpunkt am Schluss des Ziel-Pfades ist wichtig. In der Konsole kann man verfolgen, wie jeder Commit einzeln hochgeladen wird.

Anschließed kann man sich zur Kontrolle auf dem Hostsharing-Server anmelden und kontrollieren, ob das Repository direkt im Heimverzeichnis des Benutzers XYZ00-hugo angelegt wurde.

Produktions-Repository als Remote eintragen

Das hochgeladene Produktions-Repository wird dann als Remote in das lokale Arbeits-Repository eingetragen.

$ cd hugo_website
$ git remote add prod XYZ00-hugo@xyz00.hostsharing.net:hugo_website.git

Hugo installieren

Leider unterstützt Hostsharing Hugo noch nicht direkt, sodass es auf dem Server nicht vorinstalliert ist. Deshalb müssen wir es mit der Hand installieren. Dazu melden wir uns auf dem Server mit dem Benutzernamen XYZ00-hugo an, laden das Installationspaket herunter, packen es aus und erstellen einen Link zur ausführbaren Datei im Ordner ~/bin.

$ wget https://github.com/spf13/hugo/releases/download/v0.18/hugo_0.18_Linux-32bit.tar.gz
$ tar -xvf hugo_0.18_Linux-32bit.tar.gz
$ ln -s hugo_0.18_linux_386/hugo_0.18_linux_386 ~/bin/hugo

Wenn eine neue Version von Hugo veröffentlicht wird, laden wir einfach den neuen Tarball herunter, entpacken ihn und tauschen den Link aus. Sollte mit der neuen Version etwas nicht funktionieren, setzen wir einfach wieder den Link auf die alte Version.

Da ich hugo aus dem Commit-Hook mit vollem Pfad aufrufen werde, habe ich darauf verzichtet, das bin-Verzeichnis in meinen Pfad ausführbarer Programme aufzunehmen.

Anschließend testet man, ob Hugo funktioniert:

$ ~/bin/hugo version
Hugo Static Site Generator v0.18 BuildDate: 2016-12-19T15:42:18+01:00

Der Befehl sollte je nach Version eine entsprechende Informationszeile über die Hugo-Version und das BuildDate zurückgeben.

Git Hook einrichten

Nun kam die eigentlich interessante Arbeit, denn der Post-Receive-Hook musste eingerichtet werden. Bei den Git-Hooks handelt es sich um einfache Shell-Skripte, die im hooks-Verzeichnis eines Bare-Repositories liegen.

Man meldet sich als Domain-Admin (XYZ00-hugo) auf dem Hostsharing Webspace an und wechselt in das hooks-Verzeichnis, um dort den Hook anzulegen.

$ cd hugo_website.git/hooks/
$ vim post-receive

Der Inhalt der Datei sieht so aus:

#!/bin/bash

GIT_REPO=$HOME/hugo_website.git
WORKING_DIRECTORY=$HOME/hugo-website-working
PUBLIC_WWW=$HOME/doms/example.com/subs/www
BACKUP_WWW=$HOME/doms/example.com/backup_html
MY_DOMAIN=www.example.com

set -e

rm -rf $WORKING_DIRECTORY
rsync -aqz $PUBLIC_WWW/ $BACKUP_WWW
trap "echo 'An issue has been occurred.  Reverting to backup.'; rsync -aqz --del $BACKUP_WWW/ $PUBLIC_WWW; rm -rf $WORKING_DIRECTORY" EXIT

git clone $GIT_REPO $WORKING_DIRECTORY
rm -rf $PUBLIC_WWW/*
$HOME/bin/hugo -s $WORKING_DIRECTORY -d $PUBLIC_WWW -b "http://${MY_DOMAIN}"
rm -rf $WORKING_DIRECTORY
trap - EXIT

Wir arbeiten mit einem Arbeitsverzeichnis (WORKING_DIRECTORY), in das das Bare-Repository geklont wird. Es gibt ein Backup-Verzeichnis, in das wir die jeweils aktuelle Website kopieren, bevor wir versuchen, die neue Website zu bauen. Im Ordner doms/example.com legen wir deshalb mit mkdir das Backup-Verzeichnis an.

Die Variable MY_DOMAIN habe ich aus der Anleitung übernommen, ich bin aber der Meinung, dass sie überflüssig ist, wenn man, wie es üblich ist, in der Hugo-Konfigurationsdatei das Basisverzeichnis gesetzt hat.

In Zeile 9 teilen wir bash mit, das Skript sofort zu beenden, wenn ein Fehler auftritt: set -e.

Das Skript macht Folgendes:

  1. Es löscht ein eventuell noch vorhandenes Arbeitszeichnis: rm -rf $WORKING_DIRECTORY.
  2. Es erstellt ein Backup unserer Website: rsync -aqz $PUBLIC_WWW/ $BACKUP_WWW
  3. Falls ein Fehler passiert, spielt es das Backup zurück und beendet sich selbst: trap "echo 'An issue has been occurred. Reverting to backup.'; rsync -aqz --del $BACKUP_WWW/ $PUBLIC_WWW; rm -rf $WORKING_DIRECTORY" EXIT
  4. Um die Website zu bauen, klont das Skript das Bare-Repository in unser Arbeits-Repository: git clone $GIT_REPO $WORKING_DIRECTORY
  5. Es löscht die alte Website: rm -rf $PUBLIC_WWW/*
  6. Es ruft Hugo auf, um die neue Website zu bauen: $HOME/bin/hugo -s $WORKING_DIRECTORY -d $PUBLIC_WWW -b "http://${MY_DOMAIN}"
  7. Zum Abschluss löscht das Skript das Arbeitsverzeichnis wieder: rm -rf $WORKING_DIRECTORY
  8. Es setzt trap zurück. Achtung, in Anleitung 2 gibt es an dieser Stelle einen Tippfehler. Im Skript steht ein Gedankenstrich statt eines normalen Trennstriches. Das führt zu einem Fehler, wenn das Skript ausgeführt wird.

Da ich kein Experte für Shell-Skripts bin, könnte die Erklärung des Skripts Fehler enthalten. Seid nachsichtig und korrigiert mich bitte per E-Mail.

Man kann das Skript testen, indem man es ausführbar macht und aufruft:

$ chmod +x post-receive
$ bash post-receive

Anschließend sollte sich im Verzeichnis $HOME/doms/example.com/subs/www die neue Website befinden.

Nun kann man in seinem lokalen Repository zur Probe eine Änderung machen, committen und zum Produktionsserver pushen.

$ cd hugo_website
$ hugo new post/Deployment-Test.md
$ git add .
$ git commit -m "Deployment-Test"
$ git push prod master

Die Meldungen in der Shell informieren uns über den Erfolg.

Diese Anleitung funktioniert nur, wenn man das Theme zusammen mit den Inhalten der Website im gleichen Repository verwaltet.

Ich pflege aber auch einige generische Themes in separaten Repositories, sodass man sie für verschiedene Hugo-Websites verwenden kann.

Um beides zusammenzubringen, Inhalte und Theme, muss man in Git mit Submodulen arbeiten.

Repository mit Submodul einrichten

Um ein Theme in Form eines Submoduls zu nutzen, geht man folgendermaßen vor:

$ hugo new site hugo_website
$ cd hugo_website
$ git init
$ git add .
$ git commit -m "Erster Commit"
$ git submodule add https://github.com/juh2/hugo-phantom.git themes/hugo-phantom
$ git submodule init
$ git add .
$ git commit -m "Theme als Submodul hinzugefügt"

Wir erzeugen eine neue Website mit Hugo, initialisieren das Git-Repository und fügen ein Theme, das in einem anderen Git-Repository gepflegt wird, als Submodul hinzu. Von diesem Repository machen wir nun eine Bare-Kopie wie oben beschrieben und laden sie auf den Hostsharing-Server.

Das Submodul mit dem Theme muss auf einem öffentlichen Repository verfügbar sein, damit das Git auf dem Hostsharing-Server darauf zugreifen kann. Um Probleme mit fehlenden SSH-Keys zu vermeiden, sollte das Submodul mit HTTPS und nicht mit SSH eingebunden werden.

An unserem Hook-Skript müssen wir eine Änderung vornehmen, wenn wir mit einem Submodul arbeiten

#!/bin/bash

GIT_REPO=$HOME/hugo_website.git
WORKING_DIRECTORY=$HOME/hugo-website-working
PUBLIC_WWW=$HOME/doms/example.com/subs/www
BACKUP_WWW=$HOME/doms/example.com/backup_html
MY_DOMAIN=www.example.com

set -e

rm -rf $WORKING_DIRECTORY
rsync -aqz $PUBLIC_WWW/ $BACKUP_WWW
trap "echo 'An issue has been occurred.  Reverting to backup.'; rsync -aqz --del $BACKUP_WWW/ $PUBLIC_WWW; rm -rf $WORKING_DIRECTORY" EXIT

git clone $GIT_REPO $WORKING_DIRECTORY
cd $WORKING_DIRECTORY
unset GIT_DIR
git submodule init
git submodule update
cd $HOME

rm -rf $PUBLIC_WWW/*
$HOME/bin/hugo -s $WORKING_DIRECTORY -d $PUBLIC_WWW -b "http://${MY_DOMAIN}"
rm -rf $WORKING_DIRECTORY
trap - EXIT

Nach dem Klonen in unser Arbeitsverzeichnis müssen wir die Submodule initialisieren und aktualisieren, da sie sonst nicht ausgecheckt werden. Git muss die Submodule dazu von Github oder einem anderen öffentlichen Server herunterladen, deshalb muss das Submodul auf einem öffentlichen Server über HTTPS zugänglich sein. Ich habe auf die Schnelle keinen Weg gefunden, ein Repository zu benutzen, dass sich ebenfalls auf dem Hostsharing-Server befindet. Das Submodul muss sowohl auf dem lokalen Rechner als auch auf dem Hostsharing-Server unter der gleichen Adresse verfügbar sein muss.

Ohne die Zeile unset GIT_DIR funktioniert dieses Skript nicht, wenn man es remote ausführt. Das Git auf dem lokalen Rechner meldet dann, dass es kein Git-Repository finden kann. Nach einigem Suchen fand ich auf Stackoverflow den Hinweis, dass die Variable GIT_DIR entfernt werden muss, da sie wohl auf das lokale GIT-Repository zeigt und nicht auf das Repository des Arbeitsverzeichnisses auf dem Server.

Man kann das Hook-Skript lokal testen, indem man es aufruft. Die Ausführung des Skripts dauert etwas länger, da jedesmal das Submodul von Github (oder einem anderen öffentlichen Repository) geholt und initialisiert werden muss.

Workflow lokal

So sieht mein Workflow in der Übersicht aus, wenn ich einen neuen Artikel in meinem Blog veröffentlichen will.

$ cd hugo_website
$ hugo new post/neuer-post.md
$ subl content/post/neuer-post.md
$ hugo server -w

Wenn die Website mit dem letzten Befehl fehlerfrei gebaut wird und ich mit dem Artikel redaktionell durch bin, übergebe ich die Änderungen an Git.

$ git add .
$ git commit -m "Toller neuer Blogartikel"
$ git push
$ git push prod

Der Befehl git push schickt die Änderungen in einen Gogs-Server, der bei mir lokal auf einem Raspberry Pi läuft. Der Befehl git push prod schickt die Änderungen ins Repository auf den Hostsharing-Server, wo die Website neu gebaut wird.

Mehr über Hugo erfahrt ihr auf der Homepage von Hugo und in diesem Blogartikel