There and back again

A tale of switching blogging platforms


I started this blog when i got a “free” Raspberry Pi hosted by the nice people at PCExtreme. Originally this blog was using Octopress because of the limited power provided by the Raspberry Pi and while it was never meant to be a high volume blog, i felt that static pages were the least CPU intensive.

As time went by, i became increasingly more and more annoyed with the way Octopress was “a part” of my blog, and updating it involved pulling things from git and rebasing. I’m a developer by trade, but that doesn’t mean i like to make things difficult. I prefer things to not get in my way, and Octopress was beginning to do just that. Apparently the Octopress guys feel the same way

Fast forward a couple of years, and an exciting new blogging platform called Ghost was seeking funding. It was based on Node.js so what could go wrong ? The Kickstarter campaign made many promises, so i backed it, and a year or so later, a somewhat halfbaked blogging platform saw the light of day. While it wasn’t perfect, it was at least better than what i had, so i migrated.

In the meantime i played around with other Node.js projects, Node-RED to name one, and the beast that is deploying and upgrading Node dependencies showed it’s ugly head.

While Ghost remains somewhat easier to upgrade, it still involves a good 30-60 minutes of manual monitoring how things go, and testing the “live” site afterwards. A regular exercise after upgrading became adding Disqus support back into the theme. Another pain i had with Ghost was backing up the data. While the Ghost admin console offers an “Export” function, it only generates a JSON file containing your posts, but images etc. are not backed up. Another option is creating a tar file of the entire installation, risking a thrashed database in the process.

Evaluating my choices

A while ago i decided to give static content generators another try. I briefly considered going back to Octopress, but if i was going to switch platforms again, i wanted to survey the landscape, and see what had happened in the couple of years i was using Ghost.

I don’t require much in the way of extensibility. A nice theme and syntax highlighting, coupled with the usual social links, etc. So most of the “bells and whistles” are wasted on me. I prefer things working out of the box.

I tried out Pelican, and Jekyll, and while it appears to come with all the bells and whistles, it still seemed like “a lot” of manual work to configure and upgrade. Each system also has their own pile of dependencies, usually installed with a language specific package manager. I also tried out Octopress 3, and while it is now a much cleaner setup that Octopress 2, and will certainly be a great fallback plan if something fails, i figured i would keep looking.

I ended up with Hugo, which seems to offer the perfect (for me) mix of configurability, along with a simple content generator, which is not stored along with the site source. Hugo is statically compiled, and doesn’t directly support plugins. Upgrading is simply a matter of replacing the executable with the new one, and regenerating the blog.

Converting the data

As for porting my data from Ghost to Hugo, i wrote a small python script to extract the data from the exported JSON Github link

#!/usr/bin/env python3

import json
import sys
import datetime
import os.path

with open(sys.argv[1]) as f:
	data = json.load(f)

	output_dir = './output'
	if os.path.exists(output_dir) == False:

	for post in data['db'][0]['data']['posts']:
		created_at = datetime.datetime.fromtimestamp(post["created_at"]/1000).isoformat() + "Z"
		title = post['title'].replace('\\','\\\\').replace('"','\"')
		slug = post["slug"]
		markdown = post["markdown"]
		draft = 'true' if post["published_at"] == None else 'false'
		published_at = created_at if post['published_at'] is None else datetime.datetime.fromtimestamp(post['published_at']/1000).isoformat() + 'Z'

		output = '+++\n'
		for k,v in (('date',published_at),('title',title),('slug',slug),('draft',draft)):
			output += "{0} = \"{1}\"\n".format(k,v)
		output += '+++\n'
		output += markdown

		outfile = os.path.join(output_dir,"{0}.md".format(slug))
		with open(outfile,'w') as o:

This converts the posts to individual files in $PWD/output, and because i don’t have a lot of posts, i simply went through each post and manually edited the urls/image links to match my new setup. Optimally the script could have fixed up the markdown as well, but i estimated the task of writing the code to take longer than just search/replace through the posts.

The script doesn’t pull tags from the Ghost data, and pulling and correlating them from the JSON source seemed like it was more work than just editing the files manually.

Wrapping it up

So, after a couple of hours “work”, this blog is once again back where it started, on a static content generator. The original Raspberry Pi has long ago been retired, and the blog is currently being hosted on a VPS, also hosted at PCExtreme, so performance is not really a first class issue anymore, but with 2 kids, a dog, a full time job, and lot of different hobby projects, managing and upgrading blogging platforms isn’t my favorite cup of tea.

With a static site, i’m down to upgrading the software on the VPS, which is only a minimal setup with SSH and Nginx

Starting Nmap 7.12 ( ) at 2016-04-21 08:10 CEST
Nmap scan report for (
Host is up (0.048s latency).
Not shown: 997 filtered ports
22/tcp  open  tcpwrapped
80/tcp  open  http       nginx 1.6.2
443/tcp open  ssl/http   nginx 1.6.2
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Nmap done: 1 IP address (1 host up) scanned in 22.50 seconds

Since there is no scripting framework on the server end, this severely limits the potential threat of becoming the victim of a “drive-by”.

Switching back to a static content generator also allows me to follow up on a “pet project” of mine. I love the excellent Editorial editor for iOS. It’s great for writing markdown on the go, but with Ghost it has been a hazzle to get the markdown to the actual site, so i’ve mainly used it for writing new posts. To utilize the full potential of Editorial though, i must first complete my auto deploy setup, more on that later.

See also