Getting my blog version-controlled and automating deployment

Today, I’m going to be setting up git for my Hugo blog. (This Blog!) Other than allowing me to track the changes I make to my blog, using github allows me to eventually set up an automated deployment system.

I must admit, adding GitHub functionality to my blog came after I actually got it set up and working.

I had already set up my Hugo folder (in my home directory) on my webserver using their Quickstart Guide. I’d then symlinked my hugo publishing folder to the /var/www/html folder on my server. This meant that I could ssh into my webserver, create a new post, then run hugo to add the new content straight into my page.

Now, this is all well and good, but developing this wasy seems a little bit… manual. What i want to be able to do is create/edit the files I want, commit the changes and push them. I wanted my webserver to handle the rest of the deployment.

Well, in the words of Ian Malcolm:

Life, uh, finds a way.


Using git hooks to deploy a Hugo blog

Automated Deployment Process

As far as I’m aware, the process for a successful deployment is:

  1. Edit blog markdown on Laptop/Desktop/etc.
  2. Check layout/deployment locally using

    hugo -D
    

    The -D part of the command tells Hugo to include pages still in draft.

  3. If happy, push commit to GitHub (to update the origin).

  4. Push commit to your remote repository.

  5. On the remote repository, Github hooks then evaluate what you’ve just pushed. If it’s newer than the code the repo currently has, it will deploy the code to a folder of your choice.

  6. The remote repository then uses

    $ hugo -d </path/to/site/html/folder>
    

    to build the publishable html, thus updating the blog.

Simple, huh?

Initial research

There is A LOT of information on how to run git-hook-powered Hugo builds out there. However, there doesn’t seem to be many guides for those of us who are git-challenged/haven’t had enough caffeine yet.

After some hefty reading, I was going to have to use a git function called bare repos. This, along with making my webserver a remote git repository and some git-hook-fu.

Looking at my current setup, I had:

/home/sean/sean-blog as my Hugo blog folder.

/var/www/html was symlinked to /home/sean/sean-blog/publish

For my specific use case, I would be using my server as both the GitHub remote repository AND the hosting webserver.


So, what did I do to make it work?

I had to make my Hugo project a git project.

This was fairly easy, as I was able to follow the amazing tutorial by Karl Broman. The one change I did make, was to create a .gitignore file containing the publish/ path.

$ echo public > sean-blog/.gitignore

I did this because I didn’t want my generated html files to be pushed to GitHub, or my remote repository.

Once I had made my Hugo site into a github repository and uploaded it for safekeeping, it was time to move on.

Prepare a folder for the remote repository

First of all, I moved my Hugo folder somewhere I wasn’t going to delete it by accident.

$ cp -r ~/sean-blog ~/sean-blog.dev

I then created a bare repository, using the settings of my existing blog repo.

$ git clone --bare sean-blog.dev sean-blog

I chose to use sean-blog in my home folder, as my specific installation of Hugo didn’t like touching files outside of my home directory.

And that’s how I prepared my server to become a GitHub remote repository!

Set up the GitHub post-receive hook

Next, I had to tell my newly minted repository what to do when it was pushed new data. To do this, I had to create an executable file inside the hooks folder in our bare repo.

$ nano ~/sean-blog/hooks/post-receive

I then borrowed the good code from Lemi Orhan Ergin and adapted it for my own purposes.

#!/bin/bash

target_branch="master"
working_tree="build"

while read oldrev newrev refname
do
    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
    if [ -n "$branch" ] && [ "$target_branch" == "$branch" ]; then
    
       GIT_WORK_TREE=$working_tree git checkout $target_branch -f
       NOW=$(date +"%Y%m%d-%H%M")
       git tag release_$NOW $target_branch
    
       echo "   /==============================="
       echo "   | DEPLOYMENT COMPLETED"
       echo "   | Target branch: $target_branch"
       echo "   | Target folder: $working_tree"
       echo "   | Tag name     : release_$NOW"
       echo "   \=============================="
    fi
done
/snap/bin/hugo -s /home/sean/sean-blog/build -d /home/sean/sean-blog/build/publish

If you’re thinking (like me) “That looks complicated, I don’t want to read through all that”, you’re right. The three points to note are:

target_branch="master" 
#If you push anything other than the master branch, this script won't fire.

and

working_tree="build" 
#Place all pushed files into a folder called build. In our case ~/sean-blog/build

and

/snap/bin/hugo -s /home/sean/sean-blog/build -d /home/sean/sean-blog/build/publish
#Run hugo using the build directory as a source and the build/publish folder as a destination

Your remote repository is ready to go!

This is by far the easiest bit! I just ran:

$ ln -s /home/sean/sean-blog/build/publish /var/www/html

As soon as your repository is pushed code, your site will update.


Pushing the code

The final step is to set up your development device’s git capability.

On your dev device, you should clone your blog repo. I’ll assume you know how to do that part.

Next, you sun the following command to add your remote repository. I use the repository name production, purely for workflow aesthetics.

$ git remote add production ssh://sean@my-server:/~/sean-blog

Now that I’ve added my production repo, I can go ahead and push to it.

$ git push production master

You should get an output in your terminal similar to:

Counting objects: 11, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (11/11), 4.14 KiB | 4.14 MiB/s, done.
Total 11 (delta 3), reused 0 (delta 0)
remote: Already on 'master'
remote:    /===============================
remote:    | DEPLOYMENT COMPLETED
remote:    | Target branch: master
remote:    | Target folder: build
remote:    | Tag name     : release_20190124-1619
remote:    \==============================
remote: Building sites …
remote:                    | EN
remote: +------------------+----+
remote:   Pages            | 20
remote:   Paginator pages  |  0
remote:   Non-page files   |  0
remote:   Static files     |  3
remote:   Processed images |  0
remote:   Aliases          |  9
remote:   Sitemaps         |  1
remote:   Cleaned          |  0
remote:
remote: Total in 25 ms
To ssh://sean@my-server:/~/sean-blog
   30915cf..a299f04  master -> master

That’s it!

If you’ve been following through with me, you should be able to visit your web server and see your Hugo blog!

Congrats on getting through it, hopefully this will serve as a useful guide for others (and for me when I inevitably forget how I did it before).

Remember, whenever you add new pages, or make changes, you should run:

$ git add .
$ git commit -m "Commit notes"
$ git push origin master
$ git push production master