Verify this post (I dare you)

Some of you may remember earlier on in the year I went through a phase of wanting to sign my posts with PGP. This plan fell to the wayside fairly quickly and got “placed on the backburner” i.e. I will fix this when I figure out how. Well, after finding myself away from work for a week I have taken the time to figure this out.

Taking advantage of the new feature

Before I get into how I did this I want to clarify how you can take advantage of this new feature. Every post now has an accompanying plaintext page showing the signature. An example of this is the section page for the signing posts project

The reason why I did it this way was to make it easier to curl the file, and pipe that into gpg --verify. This however will only work if you have my public key in your keyring. Please use the following two commands to install my key and verify the post.

# Install my public key
curl | gpg --import

# Verfy the post
curl -sS | gpg --verify -

How I did it

For full details on this, I would seriously recommend looking at build logs. That gives the full details of my thought process as this project was built out, including my various mistakes.

The crux of the way this works is based on three factors

  1. Hugos multiple output formats
  2. Storing posts in a folder inside content called sigs
  3. A custom Python script

Multiple output formats

Hugo supports custom output formats, which can be defined in the hugo.toml file. What this essentially means in layman’s terms is that hugo can output content in not just HTML, but many other options. These include e-books, calendars, and JSON; the most important in this case is text/plain which outputs in in plaintext form. The actual toml code establishing these is shown below

        name = "standard-format"
        baseName = "index"
        mediaType = "text/html"
        path = ""
        permalinkable = true
        weight = 1

        name = "sig-format"
        baseName = "sig"
        mediaType = "text/plain"
        path = ""
        permalinkable = true
        weight = 2

As can be seen, these follow a standard layout.

  • name - This is the name it will be referred by in any YAML frontmatter
  • baseName - This is the name for files that appear i.e. sig.txt
  • isPlainText - Still not 100% sure what this does, I’m yet to delete it however
  • mediaType - This is where you establish what type of media output you want i.e text/html for HTML & text/plain for plaintext
  • path - This is where you establish a subpath to save the files
  • permalinkable - Allows the page to be permalinkable
  • weight - Setting this to a non-zero value will be used as the first sort criteria.

Storing posts in content/sigs

In an ideal world, I would have been able to generate two outputs from the same file, but in practicality, this wasn’t possible. I’ll get into why during the Python script section, but as a bodge I am storing the posts signed inside the content/sigs directory. This means all posts had to have their GPG signatures added and change their YAML front matter to say outputs: sig-format. This, in reality, was quite simple to do but was imperative for the script to work

Python script

The core functionality of the script is to strip out PGP artefacts in the main posts. In an ideal world I could have found a way to get this done without the need to use an activated separately to hugo -t risotto. The issue is I could not find a way to filter things out of the content of a post during the build process; I could have used replaceRe but some of my posts contain PGP-signed messages within them, and RE doesn’t cope well with nested structures.

To see the script id recommendation click here

To simplify its functionality, it does the following

  1. Maps out the directory structure, removing sigs, .git, .gitignore from the list to avoid those being tampered with
  2. Deleted all items in the content directory other than the sigs directory and its contents
  3. Copies all files out of the sigs directory into the content directory
  4. Recursivley edits all files to remove PGP artefacts and updates YAML to output it as the correct format


There are limitations to this method.

One that was brought up to me by a friend is that with pages like build-logs, the links are not part of the signed post. This is mainly due to how the site is built. Within Hugo, there is a template called list.html. This template is used to output pages like build-logs, where it acts as a directory too other posts.

An example of this template is shown below

{{ define "main" }}
    <h1 id="{{ .Title | urlize }}">{{ .Title | markdownify }}</h1>

    {{ .Content }}

    {{ range .Pages }}
        {{ .Render "li" }}
    {{ end }}

{{ end }}

{{define "aside" }}
    {{ if .Params.description }}<p>{{ .Params.description }}</p>{{ end }}
{{ end }}

The part that is signed is the .Content. I would argue that this makes sense, given I didn’t put the links there, a script did but this does deserve to be clarified.

What next

I’m going to go over my plans following this in the accompanying post I’ll do in the build logs section, however in short this has somewhat negatively affected my workflow. I have plans to rectify this, mainly revolving around a few new scripts, and potentially cloning a repo and creating a “writing environment” where I can just write my posts. This has also messed with the formatting of some posts, but again, fairly easy to fix (I hope). Keep an eye out for that post!


A general purpose blog for me to braindump anything I might be thinking about. Please dont hesistate to reach out if you have any questions