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
- Main page - https://eddiequinn.xyz/build-logs/eddiequinn.xyz/signing-posts/
- Sig page - https://eddiequinn.xyz/sigs/build-logs/eddiequinn.xyz/signing-posts/sig.txt
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 https://keybase.io/eddiequinn/pgp_keys.asc | gpg --import
# Verfy the post
curl -sS https://eddiequinn.xyz/sigs/build-logs/eddiequinn.xyz/signing-posts/sig.txt | 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
- Hugos multiple output formats
- Storing posts in a folder inside
content
calledsigs
- 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
[outputFormats]
[outputFormats.standardFormat]
name = "standard-format"
baseName = "index"
mediaType = "text/html"
path = ""
permalinkable = true
weight = 1
[outputFormats.sigFormat]
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
forHTML
&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
- Maps out the directory structure, removing
sigs
,.git
,.gitignore
from the list to avoid those being tampered with - Deleted all items in the
content
directory other than thesigs
directory and its contents - Copies all files out of the
sigs
directory into the content directory - Recursivley edits all files to remove PGP artefacts and updates YAML to output it as the correct format
Clarifications
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 }}
<ul>
{{ range .Pages }}
{{ .Render "li" }}
{{ end }}
</ul>
{{ 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!