Blogging auf Hugo

2017-03-23 // Tags:

Den statischen Webseiten Generator Hugo hatte ich bereits vorgestellt. Und eigentlich hatte ich zu dem Zeitpunkt noch kein Interesse daran, meinen Blog auf Hugo umzustellen. Dennoch habe ich nun alles auf statische Webseiten umgestellt.

Warum der Umstieg auf Hugo?

Nicht falsch verstehen, ich mag Ghost. Der Ansatz ist wirklich sehr nett. Eine voll funktionsfähige Software, die einen eigenen Webserver mitbringt, schnell läuft und das Schreiben im schön flachen Markdown-Format erlaubt.

Doch habe ich dadurch auf meinem Server auch mehr Software laufen, die gewartet werden will. Die Laufzeitumgebung NodeJS will aktuell gehalten werden und natürlich auch Ghost selber. Durchaus machbar, wenngleich der Update-Prozess von Ghost etwas mehr manuellen eingriff benötigt, als ein apt upgrade auf dem Server oder der eine Klick im Admin-Bereich von Wordpress. Bei Hugo muss ich keine separate Software auf dem Server aktuell halten. Einfach die fertigen HTML-Seiten hinlegen und gut ist.

Da auf meinem vServer eh ein Gitea läuft, kann ich diesen Prozess sogar schnell und einfach automatisieren.

Migration von Ghost zu Hugo

Großer Vorteil: Ghost und Hugo unterstützen beide Markdown. Dennoch muss man die Daten migrieren. Dazu exportiert man zunächst die Einträge aus Ghost. Das geht im Backend von Ghost unter dem Menüpunkt Labs und dann Export. Das Umwandeln in Hugo-Dateien mit Front-Matter ist allerdings nicht so trivial. Ich habe zwar zwei Anleitungen gefunden, beide hatten aber ihre Defizite.

Koz.io beschreibt das Vorgehen eigentlich ganz gut. Nur leider funktioniert das Tool ghostToHugo in einem Detail nicht so gut: Alle konvertierten Beiträge waren auf den 01.01.1970 datiert. Caveconfessions hat ein halbwegs funktionierendes Python-Script geschrieben. Da musste ich ein paar Änderungen vornehmen, weil auch hier wieder Probleme mit dem Datumsformat aufgetreten sind; Scheinbar hat Ghost im Hintergrund das Format geändert. Meine Änderungen sahen schlussendlich so aus:

#!/usr/bin/env python

import json
import datetime

with open('GhostData.json', 'rb') as data_file:
    ghost_data = json.load(data_file)

posts = ghost_data['db'][0]['data']['posts']

for post in posts:
    created_at = post['created_at']
    title = post['title']
    slug = post['slug']
    markdown = post['markdown']
    draft = 'false' if post['published_at'] else 'true'
    published_at = post['published_at']
        if post['published_at']
        else post['created_at']
    )

    with open('output/%s.md' % slug, 'w') as post_file:
        post_file.write('+++\n')
        post_file.write('date = "%s"\n' %
                        published_at.encode('utf8'))
        post_file.write('draft = "%s"\n' % draft.encode('utf8'))
        post_file.write('title = "%s"\n' % title.encode('utf8'))
        post_file.write('slug = "%s"\n\n' % slug.encode('utf8'))
        post_file.write('+++\n\n')
        post_file.write(markdown.encode('utf8'))

Leider konvertiert das Python-Script nicht die Tags mit. Die musste ich also manuell nachpflegen. Eigentlich eine unschöne Arbeit, die wohl für die meisten nicht applikabel ist. Ich nutzte aber mal die Gelegenheit, um das Ganze mal etwas aufzuräumen.

Mich persönlich hat nun aber noch gestört, dass alle Beiträge nur nach Titel sortiert unter content/post liegen. Deswegen habe ich mir ein Bash-Script geschrieben, was dem Dateinamen das Veröffentlichungsdatum als Prefix anhängt:

#!/bin/bash
for f in *.md; do
	prefix=$(grep "date = " $f | sed -r "s/.+\"(.+) .+\"/\1/g")
	mv "${f}" "${prefix}-${f}"";
done;

Und ich musste in der config.toml noch die Feed-URI anpassen. Ghost publiziert schlicht nach domain.tld/rss, derweil Hugo den Feed nach domain.tld/index.xml schreibt. Das kann man mit einer einfachen Zeile ändern:

rssuri = "rss"

Und schon liegt der Feed wieder an der zuvor bekannten Stelle.

Automatisierung mit Git(ea)

Zunächst habe ich natürlich ein neues Projekt in Gitea angelegt. Damit neue Änderungen auch direkt als Webseite publiziert werden, habe ich mir einen post-receive-Hook erstellt.

Dazu geht man im neu angelegten Projekt in die Einstellungen und dort in den Punkt Git-Hooks. Dort habe ich dann einen Script-Aufruf hinterlegt:

#!/bin/bash
WD=/home/git/hugo/test
bash -x <pfad-zum-script>/build.sh

Das Script selber liegt bei mir im Arbeitsverzeichnis von Hugo und Git und macht die eigentliche Arbeit:

#!/bin/bash

WD=<arbeitsverzeichnis>
HUGO=<pfad-zu-hugo>

unset GIT_INDEX_FILE
git --work-tree=$WD --git-dir=$WD/.git checkout -f
git --work-tree=$WD --git-dir=$WD/.git pull -f

cd $WD
exec $HUGO

Nun wird jedes Mal, wenn ich meine Änderungen an meinem Blog in mein Git-Repository pushe alles automatisch ausgecheckt und mit Hugo gebaut.

Damit die Webseite auch erreichbar ist, musste ich dem Apache sagen, dass meine Subdomain blog.christhulhu.de auf das $HUGO_WORKING_DIR/public zeigt. Das war auf meinem vServer etwas anspruchsvoller. Gitea läuft unter einem eigenen Git-Benutzer, der Blog als Subdomain unter dem Webspace-Benutzer.

Damit der Apache, der nur in das Unterverzeichnis des Webspace-Benutzers schaut, auch auf die Dateien zugreifen kann, habe ich unter /var/www/vhost/<domain>/<subdomain> ein Verzeichnis angelegt, wo Hugo arbeiten soll. Anschließend habe ich den Git-User in die Gruppe des Webspace-Benutzers gelegt und die Ordnerberechtigungen entsprechend gesetzt, damit Gitea auch auf das Verzeichnis zugreifen kann. Ganz zum Schluss habe ich das eigentliche Arbeitsverzeichnis als Sym-Link in das Home-Verzeichnis von Gitea gelegt, und arbeite in diesem gelinkten Verzeichnis.