After over 5 years using DotNetBlogEngine I thought it was time to move on to something different. I was interested in a modern code-base that could run on linux. After a bit of searching around I settled on Hugo. I was initially tempted by Ghost, but wanted to try out life in the Go ecosystem for a little while, so Hugo fit the bill.
Migrating my content was fairly straight-forward. Hugo is a static-site generator similar to Jekyll, but written in Go instead of Ruby. After downloading Hugo and creating a new site (some files and a directory structure on the file system) I started by tackling the task of migrating my blog post content, which goes all the way back to 2003. One issue with previous blog migrations was that I’d broken links to my site. As those hipsters over at the W3C say, “cool URLs don’t change”. I wanted to avoid changing URIs as possible with my latest move, so my migrated content needed to reflect the current URI structure. Hugo stores posts as Markdown files on the file system with a little bit of meta-data added to them, which it then transforms into pages (which are stored on and served from the file system) on build. I wrote a little C# console app to dump out my blog posts as .md files in the appropriate hierarchy to match the current URI structure , fix up the URLs to content which in the DotNetBlogEngine world was being served by a HTTP Handler (.ashx). Time to slap down $5 and spin up a digital ocean droplet, I thought.
Migration ‘Opportunity’ No.1 - TimeZones
One of the first probl..err ‘opportunities’ I came across was a minor one - the date an item was posted forms part of the URL for example /2007/04/15/WPFe-re-named-Silverlight.aspx. DateCreated stored in the database was the local time (UTC+10) I wrote the post, however the date time in the URL was UTC time, so a number of posts ‘shifted’ by a day, and thus broke links to them. No problem - delete all the generated pages and re-run, correcting for timezone offsets and it’s droplet time?
Migration ‘Opportunity’ No.2 - Case-sensitive File Paths
The second ‘opportunity’ I had to do some coding was to fix a problem with case-sensitive file paths. Windows (and IIS) treat URLs as being case-insensitive, reflecting Windows case-insensitive file system heritage. Unix on the other hand was having none of that. A number of paths to content like images were broken due to incorrect casing. To address this I built a list of correctly-cased image names, and programmatically fixed up the URLs while I was migrating the content.
Migration ‘Opportunity’ No.3 - Serving .aspx file extension
The third ‘opportunity’ was once again to do with retaining compatible URLs. It seemed as though even I was moving to Unix I wasn’t quite done with the .aspx file extension. Fortunately hugo had a handy switch to specify what the ‘default’ file extension should be, unfortunately the built-in Hugo server didn’t set the MIME type correctly for some file types, like .aspx, so even though it served the file the MIME type it served (application/xml) prevented the browser displaying it correctly. Being fairly new to Go I wasn’t quite ready to chip in to help fix this one. It did raise an interesting question of what was I going to actually RUN this new blog on once I’d finished the migration? Almost any *nix web server would do (well, one that could serve .aspx files with a MIME type of text/html). So what to choose?
Bonus Points - let’s write our own web server and migrate 2 blogs
In the end I decided to further consolidate things by migrating both jcooney.net AND learnwpf.com to Hugo. Both were running on slightly different versions of DotNetBlogEngine. I wrote a simple server in Go that could handle serving both domains at once, could fix up the .aspx MIME type issue, and could also serve up a custom 404 page. Fortunately Go has a lot of primitives for writing HTTP servers, and the total amount of code was about 80 lines of code.
Comments was the last thing I needed to take into account. I wanted to use Disqus in the future (now), but didn’t want to try to tackle migrating the comments from many years into Disqus (I didn’t even explore if this was possible) so I ended up including them as ‘static’ content in the body of the page, as shown in the screen-shot below from this page.
The Road Not Taken
Another option instead of rolling my own little web-server in Go would have been to use some combination of Nginx and maybe Apache to serve up those files. I’m keen to learn more about Nginx, but kicking the tyres in a more meaningful way with Go was also a good learning experience. If this wasn’t a personal project I would have gone for Nginx. There were also some good learning opportunities setting up my hand-rolled server to restart correctly under Linux which I might have missed out on if I’d gone down the more well-trod path. I suppose that is the beauty of ‘fun’ projects - you’re not accountable for anyone else for decisions like this, and that can make all the difference.