Well suffice to say that starting a blog has been on my list of things to do for a while. Today I have finally got round to setting one up, thanks in part to a brief discussion this last week on the fediverse (in the aftermath of the birdsite’s recent takeover). Being something of a perfectionist I’ve been reluctant to get started worried both that each post would take hours and without having fully planned out the whole blog I didn’t want to end up with another “half done” project.

However, in the words of Laura Langdon, “Starting a blog is only as hard as you want it to be!” so I decided to stop making it hard for myself and just start with something very basic. Let’s see what happens…

Requirements

To keep things simple, I don’t want a blog which will take much maintenance with security patches, updates, and the like. Ideally for me this means a generated static site. I also want the blog posts to be available via an RSS feed for anyone who wants to consume the content that way.

I am very familiar with markdown, so that seems that would be a nice way to write simple blog posts without excessive formatting options getting in my way.

To summarise:

  • Low maintenance
  • Ideally statically generated
  • Supports RSS
  • Markdown for content
  • Quick and simple to initially setup

Having briefly played with Hugo in the past it was vaguely familiar and seemed to fit all of my criteria, so time to get going.

Making the blog

The Hugo quick start guide was a good place to start and made the process rather quick to achieve. The key instructions are summarised below.

Installing hugo

I am running PopOS, a debian linux derivative, so my notes may not work for you. See the install instructions if you are running a different operating system

  1. sudo apt update
  2. sudo apt install hugo
  3. hugo version to check it has installed correctly. You should see a valid hugo version number.

Creating the site

Complete this from within the directory you want to create the blog inside. The following will create a subdirectory to hold all code and content related to your blog.

  1. hugo new site blog (where blog is the name of your blog, and will also be the name of the directory being created)

Adding a theme

Add a theme, either from themes.hugo.io or making your own.

As I want to keep the initial setup both quick and simple, I’ll use the PaperMod theme for now.

  1. move into your blog directory we created in the last step (cd blog). You should see a set of directories, such as content, themes and public
  2. git init
  3. git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
  4. git submodule update --init --recursive (also needed when you re-clone your repo (submodules may not get cloned automatically))
  5. echo theme = \"PaperMod\" >> config.toml

If you later want to use a different theme, or even one you have created yourself, just add it to the themes/ directory and adjust theme inside config.toml

Site configuration

Open config.toml in a text editor and adjust the site name.

Enable RSS discoverability

RSS didn’t feature in the quick start guide, so I did a bit of googling and it turns out:

If you have a Hugo site, you probably already have a working RSS feed. You just might not know about it!

Hugo - Create RSS Feed for Site - Justin James

What this appears to mean is that Hugo is capable of generating RSS themes using it’s own RSS template. It even appears to do this out of the box, creating XML files such as index.xml. However it doesn’t advertise the available XML feed anywhere on the HTML page, so is undiscoverable.

How we enable RSS discoverability depends on the theme. This makes sense because the HTML is generated from the theme, so it is the theme’s responsibility to advertise the XML RSS feed. Some themes include the necessary <link> tags out of the box, some require it to be turned on, and others don’t have it at all.

Automatic RSS using theme

The PaperMod theme I picked appears to have RSS advertising as an option. Simply add RSS to the output config in config.toml, or add the whole block to the file like I had to. I chose to add JSON as well (because why not) but only HTML and RSS are necessary here to advertise the RSS feed.

[outputs]
  home = [ 'HTML', 'RSS', 'JSON' ]
  posts = [ 'HTML', 'RSS', 'JSON' ]
Manual RSS for custom themes

If you are not using PaperMod, you will need to consult your theme’s documentation. It may require a different configuration to enable RSS discoverability. Ultimately you need to get the following <link> tag inside your the theme’s html <head> tag, regardless of if that is by turning on a configuration option, or by manually adding the following.

<link rel="alternate" type="application/rss+xml" href="{{.Site.BaseURL }}/index.xml" title="{{ .Site.Title }}" />

Add full content to RSS feed

Unfortunately Hugo does not include the full post content in the RSS feed by default. This can be fixed by altering the RSS XML template.

Copy the contents of the default hugo rss template into the file layouts/_default/rss.xml, creating the directory structure where necessary.

mkdir -p layouts/_default
curl https://raw.githubusercontent.com/gohugoio/hugo/master/tpl/tplimpl/embedded/templates/_default/rss.xml > layouts/_default/rss.xml

Then change the following line:

<description>{{ .Summary | html }}</description>

into

<description>{{ .Content | html }}</description>

Running hugo

  • hugo serve -D (where -D will render any draft posts)
  • hugo serve --bind=10.22.0.14 --baseURL=http://10.22.0.14 -D you can also optionally bind to an ip address, instead of localhost, if you want to preview from another machine

Save what we’ve got so far

It’s a good practice to regularly commit (save) to git, so let’s select everything and commit our empty site before we go any further.

  • git add .
  • git commit -m "Empty blog with theme added"

Let’s also add some configuration to git to prevent it saving files which are generated. Create a file called .gitignore in your blog directory containing the following content.

*.lock
public/
  • git add .gitignore
  • git commit -m "Ignore built files from git"

Adding content

Now that the site is created, we can start adding our blog posts.

URL structure

As with any custom site, you get to choose your own url structure. For example, the following are all valid options:

  • https://blog.example.com/2022/my-first-post
  • https://example.com/blog/2022/11/my-first-post
  • https://example.com/posts/my-first-post
  • https://example.com/my-first-post

It’s a good idea to have the year in the url of blog posts as they generally contain or make reference to content which ages over time. The year both makes it clear how old the content is and also provides a level of hierarchy to the site. Including the month as well as the year can be a useful subcategorisation if you post a lot. I don’t think that I will be posting particularly often so I plan on keeping my posts grouped only by year.

How do we achieve this?

Hugo assumes the subdirectories directly inside /content denote the page layout you want to use to render the pages. This is called the content type. So all blog posts should live inside /content/posts. If you end up needing different kinds of content, such as author biographies, then these would each be a different content type and live in a different directory within /content, such as /content/authors. Each content type can have a different visual design and layout on the page. By default the content type name also gets added to the start of the page url, such as https://example.com/posts/my-first-post.

With the correct configuration each content type can automatically have a unique URL structure. Using this to also add the year, month and other fields from each post’s front matter prevents us needing to store the posts in year based subdirectories within /content/posts. This can be configured in a few places but it makes most sense to apply it in the main config file.

Add the following to the bottom of config.toml to automatically remove the prefix /posts, and add both the year and month to the url for the posts content type. More options and examples in the Hugo URL documentation.

[permalinks]
  posts = '/:year/:month/:slugorfilename/'

Note: At the time of writing :slugorfilename is relatively new and doesn’t work on older Hugo versions. If you get errors, you can use a different variable or update to the latest Hugo version.

Creating a post

Creating a post is as simple as either running a command, or creating a new text file in the correct content directory. Keep in mind your url structure when creating posts. In general file path inside your content directory should be the same as the url path you would like, however any permalink configuration, like the ones we have just made, will take prescience over the directory structure.

So to create a post at https://blog.example.com/2022/my-first-post you could either:

If you have added the permalink config:

  • run hugo new posts/my-first-post.md
  • or manually create content/posts/my-first-post.md with the correct front matter

If you do not added have the permalink config:

  • run hugo new posts/2012/my-first-post.md
  • or manually create content/posts/2022/my-first-post.md with the correct front matter

Writing the posts

This is where you get creative and write the blog post. You can include text paragraphs, subheading, images, video embeds and the like. If you haven’t used Markdown before, then you probably want to read the instructions or complete the 10 minute tutorial for the Commonmark version of Markdown which Hugo uses.

Remember to spell check your work. Depending on what application you are using to write your posts, it may already automatically spell check for you, or you may be able to install a spell checking extension/plugin.

Save posts into git

Once the content is added, let’s save it again. This time let’s just save the posts one at a time.

  • git add content/posts/my-first-post.md
  • git commit -m "Add first post"

If writing posts takes more than one sitting it may be a good idea to commit each draft, allowing you to see the changes you are making and easily undo an edit if you are unhappy with it.

Let’s also take a moment to consider if we want to push our git repo to a remote server. Since your blog posts are about to be published freely on the internet, it generally isn’t a problem having the whole blog in a public git repo. However be aware that if you do keep your git repo public then any draft posts will be visible in git before being published and also leave a public history of any edits you make to any posts, both draft and published posts.

Deploying

Hosting a static side can be done with pretty much any commercial web hosting company, or one of the following.

Since these three all simple options, I’ll cover each of these three quickly.

Sourcehut pages

The following instructions are based on the official sourcehut pages documentation.

You can use the following commands to build the site, zip it into an archive file and deploy straight to sourcehut pages. If you have your own domain, you can use it instead of username.srht.site by following the custom domain instructions to configure DNS. Then use your own domain in place of username.srht.site when publishing with either the curl request or hut command.

Manual deployment

In the commands block, replace the following:

  • username with your actual username, or username.srht.site with your custom domain
  • access token with the access token you generate (limit scope to the grant string pages.sr.ht/PAGES:RW).
hugo
tar -C public -cvz . > site.tar.gz
curl --oauth2-bearer "access token" \
    -Fcontent=@site.tar.gz \
    https://pages.sr.ht/publish/username.srht.site

Automated deployment

You can also automate deployment with a Sourcehut build script by:

  • creating a new git repo on sourcehut
  • creating a file called .build.yml in your repo with following contents (changing username.srht.site to your blog url).
image: alpine/latest
packages:
  - hut
  - hugo
oauth: pages.sr.ht/PAGES:RW
environment:
  site: username.srht.site
tasks:
- package: |
    cd $site
    hugo
    tar -C public -cvz . > ../site.tar.gz    
- upload: |
    hut pages publish -d $site site.tar.gz    

Now, each time you make a change and push it to your sourcehut repo it will get automatically built and deployed to your blog.

Github pages

Github pages is similar to Sourcehut pages but works slightly differently. Whereas in Sourcehut we can build and deploy the site as a tarball (.tar.gz file) for Github the site’s contents needs to be committed into a git repo. This means that, while the manual deployment method is still possible, it is more convoluted if we also want to use Git to track our source code changes. (If I, however, am mistaken then please let me know)

The following instructions summarised from Hugo’s documentation for GitHub Actions. This will set things up so that each time you make a change and put it into your github repo, it will build and deploy the changes to your blog.

Self hosted on existing web server

If you are running a webserver then I will assume that you know how to know how to deploy html, css and js files to it as the steps will depend on exactly how your server is setup.

The key step is to run hugo during each deployment of the site. For example, I’ll be using sourcehut builds to both build the site and also rsync onto the web server, though GitHub Actions, GitLab CI or just a simple bash script should work fine too.

rrsync

For example on a Linux server you can use the rrsync script forced in authorized_keys to further limit a specific public ssh key:

command="rrsync /var/www/blog.example.com/public/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAAAB1NzaC1qc2...qa== sourcehut

Sourcehut builds

The following .build.yml file will download the git repo, run hugo in teh package section then deploy it with rsync to the web server.

image: alpine/latest
packages:
  - hugo
  - rsync
sources:
  - https://git.sr.ht/~techwilk/blog
secrets:
  - xxxxxxxxxx
  - xxxxxxxxxx
environment:
  site: blog
tasks:
- package: |
    cd $site
    hugo    
- deploy: |
    cd $site
    echo "StrictHostKeyChecking=no" >> ~/.ssh/config
    rsync -avpP public/ blog-user@webserver.example.com:/ --delete    
triggers:
  - action: email
    condition: always
    to: "Your Name" <your-public-email-address@example.com>

The two secrets contain

  • the private ssh key for the blog user on your webserver
  • an ssh config file to hold any ssh ports and

Summary

You should now have a blog site live on the internet, well done!

There are a few things we haven’t setup yet, including the sending and receiving of webmentions. However, those are a tasks for another day. Once I get them working I’ll write another post and link it here.

To make the most of both Hugo and your blog, it’s worth planning to go back and read a few bits of their documentation to become more familiar with the tools and tweak some more configurations. I’ve found it helpful to read the following.

Further reading

For your theme

Note that if you’ve made your own theme I still recommend reading though at least one published theme’s documentation for inspiration.

For understanding configurations in config.toml

For understanding blog posts, front matter and urls