Migrate from ghost to jekyll

I haven’t blogged regulary ever in my life so I decided now it’s time for change and blog frequently about technology. This blog was using ghost cms deployed on docker to do it and I decided it would be nicer to have static pages that I can commit using git and deploy directly to webserver without any middleware.

At first I thought it would be fun to build such thing from scratch but then I remember how complicated life become in terms of making blog. For complete working solution you need to add rss / seo / social media metadata / site map to name a few. And most painfull, you need to maintain it.

I got myself reminded about a great tool for generating web pages from markdown jekyll, after quick reading about features it turned out it have all of above and more so I decided to go for it.

At first I needed to install ruby on OSX Mojave

brew install rbenv ruby-build
echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile
source ~/.bash_profile

# Install Ruby

rbenv install 2.6.3
rbenv global 2.6.3
ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]

Then I can proceed with jekyll and migration of ghost blog

gem install bundler jekyll
jekyll new vane.pl
cd vane.pl

#migrate
gem install jekyll-import
ruby -r rubygems -e 'require "jekyll-import";
    JekyllImport::Importers::Ghost.run({
      "dbfile"   => "ghost.db"
    })'

After migration two directories will appear _drafts and _posts and all pages will be in main directory.
Unfortunately dates were not migrated properly so my posts directory looked something like that

15:54 1970-01-18-why-serverless-is-a-next-big-deal.markdown
15:54 1970-01-19-understand-how-to-theme-material-ui.markdown
15:54 1970-01-19-world-of-machines.markdown
15:51 2019-06-25-welcome-to-jekyll.markdown

So I needed to migrate those dates manually and also change date inside those files. I also wanted to get tags because those were also not migrated so I created custom sql script that gets all the tags, slug, title and formatted date so I can migrate those posts.

select 
  p.id, p.title, p.slug, group_concat(t.name), strftime('%Y-%m-%d %H:%M:%S',datetime(p.published_at)) as published
from posts as p
inner join posts_tags as pt on pt.post_id = p.id
inner join tags as t on t.id = pt.tag_id
group by p.id
order by p.published_at desc

I exported results as csv file and I could build a script that will merge csv file with those files created by jekyll migration script.

After hour later it was done using this a little ugly python script that is looking for file matching slug in _posts directory and replacing it’s date with correct one adding tags and finally saving it as file with proper date in front. You can download it from this location

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import csv
import os


class Constraints:
  CSV_FILE = 'blog_tags.csv'
  POSTS_DIR = '_posts'
  TIMEZONE = '+02:00'


def write_post(date, tags, date2):
  with open('{}/{}'.format(Constraints.POSTS_DIR, p)) as post:
    post_array = post.readlines()
    new_date = 'date: {}.000000000 {}\n'.format(date, Constraints.TIMEZONE)
    for i, line in enumerate(post_array[:]):
      if line.startswith('date:'):
        post_array[i] = new_date
        post_array.insert(i, 'tags : {}\n'.format(tags))
        break
    # write new file
    new_fname = '{}/{}-{}'.format(Constraints.POSTS_DIR, date2, p[idx:])
    with open(new_fname, 'w+') as newpost:
      newpost.writelines(post_array)


if __name__ == '__main__':
  posts = os.listdir('_posts')
  with open(Constraints.CSV_FILE) as f:
    data = csv.DictReader(f)
    for p in posts:
      f.seek(0)
      for row in data:
        idx = p.find(row.get('slug'))
        # we found file named as slug so we can process it
        if idx > 0:
          date = row.get('published')
          tags = row.get('tag_names').split(',')
          date2 = date.split(' ')[0]
          # read, replace and write post file
          write_post(date, tags, date2)

After I removed old posts and run jekyll

rm -rf _posts/1970-01-1*
bundle exec jekyll serve

I was able to see results under http://127.0.0.1:4000

I wanted to see my blog posts without /date/slug format so I modified _config.yml by adding this line:

permalink: :title/

Also I needed to copy assets from ghost to assets directory inside jekyll project.

Later I also added lines to comprese sass files, seo plugin and syntax highlighting.

highlighter: rouge
sass:
  style: compressed
plugins:
  - jekyll-feed
  - jekyll-sitemap
  - jekyll-seo-tag

There is a great post from Arthur Gareginyan about syntax highlighting configuration. Basically you need the css file with highlighting style and knowledge how to color things.

It was easy so I decided to make my own skin. I wanted to make it dark so I started reading documentation, study blank_template from repository and default theme.

So here is my basic description how it works.

First we create assets/css/main.scss directory with

@import "main";

and _sass directory with main.scss file where we include our css with syntax, mine is native. Also we add all style configuration. I used Better motherfucking website as a base to keep my page smooth.

Then you need to create _layouts directory and put files in there. File names are corresponding to names in markdown files with variable layout

---
layout: post
---

The default template name is located in main project directory in file index.md mine was named default so I created _layouts/default.html

There at least two scopes of variables site and page. You can get for example title by typing

<title>{{ site.title }}</title>

And acces some feed and seo plugins by adding variables in head

{%- seo -%}
{%- feed_meta -%}

To list all posts you can write:

{% for post  in site.posts %}
	<span>{{ post.title }}</span>
	<span>{{post.date | date: "%Y-%m-%d %H:%M:%S"}}</span>
	<span>{{post.content | truncatewords: 35}}</span>
{% endfor %}

There are many filters available in jekyll trough Liquid template language so you can build tempaltes fast.

You can even add pagination but I skipped it for now.

To format posts you need to create _layouts/post.html.
There you can ex. list all tags inside posts and use it as keywords in meta like this:

<meta name="keywords" content="{{page.tags| join: ', '}}" />

After a few hours of expreriments I had dark mode blog with all I needed and I could test it on my devices.
To serve jekyll with drafts on my local machine I can use. Then I can test if everything is working looking fine on my phone.

bundle exec jekyll serve --drafts --host 0.0.0.0

To compile build you should add your domain name and base name into _config.yml mine is looking like that :

baseurl: "" # the subpath of your site, e.g. /blog
url: "https://vane.pl"

and then compile it using this command

jekyll build

Results are in _site directory and you can copy them wherever you want on some cloud server or even configure github pages and serve your site directly from github.

Have fun.