It seems quite appropriate that the first real page on this site is about how this site is running. There are lots of ways to self-host a low-traffic website, but I wanted a very simple and low fuss static website that is automatically published when I update it.

Software Installed Link to heading

  • apache 2.4.58
  • git 2.43.0
  • hugo 0.120.3 <- warning: be careful of older versions
  • rsync 3.2.7
  • webhook 2.8.0

on a FreeBSD 14.0-RELEASE jail


I will include version numbers when I am talking about software because it is important to be able to check that the instructions that you are about to read are still relevant.

For example, I found out the hard way that there have been significant changes in how hugo looks for configuration files. A linux desktop installed hugo version 0.101.0 from its repository. This version of hugo used config.toml as its configuration file. A couple of themes I tried also referred to config.toml as the configuration file. The site worked fine on that computer.

But when I cloned the repository into a FreeBSD jail, the site refused to build. FreeBSD provided hugo version 0.120.3

It turns out version 0.110 switched from config.toml to hugo.toml.


Workflow Link to heading

The workflow is very simple: webhook -> git -> hugo -> rsync -> apache

I use a Gitea server to store and update the website. When changes are pushed to the repository, Gitea uses something called a ‘webhook’ to send a notification to the jail that is hosting the static site. I will write about the gitea server later.

Overall Link to heading

I use a simple ‘webhook’ system to tell the static site jail that there is an update that needs to be retrieved and published. This system listens for a request to http://<ip-address-of-static-site-jail>/hooks/test-webhook. A script is run once a request is received. This script calls git to retrieve the latest copy of the site. The static pages are then generated using hugo. Finally, the static pages are copied into place for apache to serve.

I won’t write a lot about git, hugo, rsync, or apache because there was no unique configuration needed after installing them. All of them have been well documented elsewhere. In fact, apache could be swapped out for any other webserver (nginx, caddy, lighttpd). Each of these applications worked out of the box.

What I will write about is how I set up webhook because I found a lack of information here, and how the programs all fit together.

Continuous Deployment using webhook Link to heading

I use a very simple system for deploying the website. The webhook is from the FreeBSD packages: webhook. This is an incredibly simple system that is currently using up 11MB of RAM and does exactly what I need it to do. (Compare this to other monsters of CI/CD systems).

The webhook service requires three parts:

  • a daemon file (to let webhook start and stay in the background of the jail),
  • a hooks file (to tell webhook what to listen for and what to do),
  • a script with what to do when the webhook is received.

Webhook daemon Link to heading

The installation of webhook comes with a daemon file, but I couldn’t get it working and couldn’t work out why it wasn’t working. So I wrote a very simple daemon file taking inspiration from the insane.engineer. One day, I’ll get around to setting up logging and other advanced features, but it works for now. This file has been updated in a later post to include more robust logging that is beyond the scope of this post. This daemon file needs to go into /usr/local/etc/rc.d/staticsitelistener.

#!/bin/sh
# PROVIDE: staticsitelistener
# REQUIRE: networking
# KEYWORD:

. /etc/rc.subr

name="staticsitelistener"
rcvar="staticsitelistener_enable"
staticsitelistener_user="root"
staticsitelistener_command="/usr/local/sbin/webhook -hooks /root/hooks.json"
pidfile="/var/run/${name}.pid"
command="/usr/sbin/daemon"
command_args="-P ${pidfile} -r ${staticsitelistener_command}"

load_rc_config $name
: ${staticsitelistener_enable:=no}

run_rc_command "$1"

You need to add staticsitelistener="YES" to /etc/rc.conf to activate it when you restart the jail.

Webhook file Link to heading

The hooks are defined in /root/hooks.json. If you put the hooks file in another location, make sure you update the location in staticsitelistener_command="/usr/local/sbin/webhook -hooks /root/hooks.json" in /usr/local/etc/rc.d/staticsitelistener. The hooks file is very simple. It listens for a request sent to test-webhook (this can be named anything you like).

[
    {
        "id": "test-webhook",
        "execute-command": "/root/script.sh"
    }
]

The script Link to heading

Once the webhook request is received, the execute-command is activated. This calls the following very simple script:

#! /bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/root/bin

DIR="/root/static-site/"

REPO="https://gitea.brendans-bits.com/brendan/static-site.git"

git clone --recurse-submodules ${REPO} ${DIR}

hugo --source ${DIR}

rsync -acr ${DIR}/public/ /usr/local/www/apache24/data/

chown -R www:www /usr/local/www/apache24/data

rm -rf ${DIR}

I have updated the script from the original below. The new script can be put in place and run straight away. Originally, I had to manually clone and then retrieve the theme submodule before I could use the script. The new script also lets me ensure that hugo can call on git so that I can do things like automatically add the date that I update any pages based on the git commit date.

For reference, the original script looked like this:

#! /bin/sh
#
REPO="/root/static-site/"
now=$(date +"%F-%T")
touch /root/test_$now
/usr/local/bin/git -C ${REPO} fetch
/usr/local/bin/git -C ${REPO} rebase
/usr/local/bin/git -C ${REPO} submodule update 
/usr/local/bin/hugo --source ${REPO}
/usr/local/bin/rsync -avr ${REPO}/public/ /usr/local/www/apache24/data/
chown -R www:www /usr/local/www/apache24/data

Using this script, when I first deployed the site, I had to manually clone the repository, update the theme, and generate the static files just to make sure that everything was working:

git clone <repository> # to get a copy of the repository
cd <repository that git updated>
git submodule update # the repository has a theme installed as a git submodule
hugo # to generate the static files
rsync -avr public/ /usr/local/www/apache24/data # move the files across
chown -R www:www /usr/local/www/apache24/data # change the ownership to the apache user

The new script can be put into the jail and run.

Activating the webhook Link to heading

Once all the pieces are installed, you can run service staticsitelistener start.

Git, Hugo, Rsync, and Apache Link to heading

There was no additional configuration of hugo after it was installed.

The first time I deployed the site, I manually ran through cloning the repository, updating the theme, and generating the static files just to make sure that everything was working:

git clone <repository> # to get a copy of the repository

cd <repository that git updated>

git submodule update # the repository has a theme installed as a git submodule

hugo # to generate the static files

rsync -avr public/ /usr/local/www/apache24/data # move the files across

chown -R www:www /usr/local/www/apache24/data # change the ownership to the apache user

What did I learn? Link to heading

All of this is a bit more effort compared to paying a few dollars a month to host this site on a wordpress instance somewhere else. I could have done this with something like Jenkins or another CD tool on docker, and perhaps used a systemd unit to monitor for changes to a git repo. But doing it this way meant that I learned about a very simple continuous deployment tool and I also learned a bit about how to run something in the background as a daemon on FreeBSD.