Jack Atkinson

Scientist, Archer

(Hugo) Website deployment using git (hooks)

7 minutes
March 10, 2020
computing,  website,  git,  server, 

When I set up my website I wanted to have it stored as a git repository for obvious reasons (read for when I inevitably mess something up). However, you do not wish for the git repo to be the website itself and neither do you wish to go in to your server after each push in order to re-build.

After much effort I finally found a solution that worked for me. This post consolidates what I learnt and did so that if anyone tries this in future they attempt this method (and I can replicate it when I inevitably mess something up).

Why use git, and what are hooks?

I suspect most people here are already aware of this, but git is a version control software. This means that rather than replacing files when you make changes, the files are updated and the changes logged. This means that if something goes wrong you can recover a previous version of a file and restore the version that did work. The most relateable thing in to compare it to might be apple’s time machine software, though git is far more efficient and versatile. git also allows branching and collaborative work etc. etc. beyond the scope of this discussion. Hopefully it is clear why this is useful when building a website.

However, with all of this extra stuff your git repository shouldn’t be your front facing website! This is true for several reasons including security. A big factor in this, however, is that for websites built using Hugo (such as this one) you have to compile the filesystem using the hugo command. This generates a /public_html-type folder that contains the built website.

However, if we are using git regularly to track all changes we don’t want to have to log in to our server every time we push changes to re-compile the site and move it to the public-facing location. If only there was a way of automating the recompile process on the server so that every time we pushed from our local machine everything else was taken care of…

The answer is in git hooks!
git hooks are snippets of code that lie dormant in the git repository until someting happens that calls them to be executed. You can have pre-commit, post-update, and update hooks among others, hopefully fairly self explanatory. The hook we are interested in, however, will be the post-recieve hook. This will be executed following any commits we make to the server. By this point it is hopefully clear that the game plan is going to be commit, then execute a post-recieve hook that re-compiles the website and replaces it in the public facing folder. This is how I did it.

1. Set up a git repository for the website

The first thing to do is set up a git repository somewhere for your website. Note that as discussed above, this is not the /public_html location of your website. I did this using:

    mkdir mywebsite.git
    cd mywebsite.git/
    git init --bare

to initialise a bare git repository for mywebsite in the location I chose to use.

I then cloned this repository to my local machine where I would be working on the website using:

    git clone /location/of/git/repo/ /local/destination

2. Add content

At this point I began building the skeleton of my simple hugo website in the local working copy of my git repo. This was tested throughout using the hugo server command to view on a localhost. Once I was happy with a basic working site it was time to move it to the server. Before that, however, we need to put our post-receive hook in place.

3. Add a post-receive hook

This article assumes that there is an outwards facing location on your server that is already set up to serve a website from a /public_html folder for a domain that you own. For me this is in the fairly standard location of /var/www/mywebsite/ on my server. If this differs for you, appropriate changes may be required to the script below.

The script below also works because the server where the git repo is is also the server where the website is. If this is not the case for you, you may need to modify it slightly to ensure that you can execute the commands on the website server (or it may not be possible at all and you may need an entirely different workaround… ¯\_(ツ)_/¯ ).

The following script needs saving as post-receive in /location/of/git/repo/mywebsite.git/hooks/.

#!/bin/bash

GIT_REPO=/location/of/git/repo/
TEMP_DIRECTORY=/tmp/temp
TARGET=/var/www/mywebsite/public_html

echo "    /==============================="
echo "     Post receive file active"

echo "     Making temporary directory."
mkdir $TEMP_DIRECTORY

echo "     Pulling into temporary directory."
git clone $GIT_REPO $TEMP_DIRECTORY

echo "     Removing existing site."
rm -rf $TARGET

echo "Building new site."
hugo -s $TEMP_DIRECTORY -d $TARGET

echo "     Removing temporary directory."
rm -rf $TEMP_DIRECTORY

Breaking this down line by line, we first initialise three variables, the location of our git repository, a temporary directory that we will compile into, and the location of our publicly facing website.

Following this we provide some feedback to the user who has just committed to tell them that the post-receive has been triggered.

We then make our temporary directory. The reason for doing this, as discussed before, is that the git repository is not simply a one-to-one backup of our files. It is more complicated than that. Therefore to get a up-to-date set of files that we can compile into a website we are going to need to clone the repo into a temporary location. Indeed, this is what the next command does.

Note that we are providing feedback to the user who committed throughout this process - commenting is good people!

Following this we remove the current incarnation of the website. On writing this article I am not sure this is ideal, and wonder if there is perhaps there is a better workaround. The beauty of git does mean that we can always go back to the previous working version if something goes wrong however. This is especially true given that the website is housed entirely in the git repo, not /public_html, so the removal of $TARGET is low risk.

The key stage - we build our website using the hugo command. Note the use of the -s and -d flags that allow us to specify the source and destination. We will build it from the clone in our temporary file, and send it direct to /public_html/!

Finally there is some cleaning up to do in removing the temporary file, and we’re all done. The changes that were pushed from a local machine should now be live on the website!

4. Write!

Once the post-receive hook is in place we can perform a commit to our git repository as usual and then push our changes. If all is well the repository should be updated, the post-receive hook should be activated, and the website should go live!

The output you see in terminal following a push of any staged commits should be as follows:

MyComputer:localwebsitedir user$ git push origin master
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 4 threads
Compressing objects: 100% (14/14), done.
Writing objects: 100% (14/14), 1.43 MiB | 12.39 MiB/s, done.
Total 14 (delta 4), reused 0 (delta 0)
remote:     /===============================
remote:      Post receive file active
remote:      Making temporary directory.
remote:      Pulling into temporary directory.
remote: Cloning into '/tmp/temp'...
remote: done.
remote: Checking out files: 100% (134/134), done.
remote:      Removing existing site.
remote: Building new site.
remote: Building sites … 
remote:                    | EN  
remote: +------------------+----+
remote:   Pages            | 33  
remote:   Paginator pages  |  0  
remote:   Non-page files   | 28  
remote:   Static files     |  8  
remote:   Processed images |  0  
remote:   Aliases          |  0  
remote:   Sitemaps         |  1  
remote:   Cleaned          |  0  
remote: 
remote: Total in 405 ms
remote:      Removing temporary directory.

Closing

The only issue I have had with this process was when I tried to update the sass formatting on the website. I think I solved this by going into the server and adding the -disableFastRender flag to the compilation command, but I am not 100% sure. Either way, the changes somehow ended up on there…

If you have any comments or suggestions please do get in touch. I am by no means an expert on this and am always keen to learn more.