Extending Hugo with a microservice
I love Hugo, and it serves as the basis of both my blogs. It’s quite an incredible static site generator, which gives a ton of flexibility and is very lightweight to run, as on a modern machine, serving static HTML files is pretty much free. But, of course, not everything is perfect. For a while now, I’ve been looking for a way to host a bunch of loose thoughts that I’ve been having. “Create a twitter/bluesky/mastodon account”, the keen among you would say. But, here’s the thing: I don’t want a social aspect to this, just a place to dump my thoughts.
Of course, Hugo doesn’t come with any CMS, that’s not the point. Which does make publishing these very short posts somewhat of a challenge: I have to be near a proper computer, go through the git workflow, and push it to my self-hosted git forge which triggers the workflow for building and deploying the generated files. Perfectly fine for long form posts, such as this one. But for short quips? It almost makes me want to not post anything.
But the good thing about this setup is that it’s extensible. In practice, all it takes to post something is to add a file to the right directory and push it to the server. This can be automated, which is the engineer’s dream! So, I built a small submission page for these short thoughts!
Let me present aphorism-server, a microservice for a web interface that allows me to post aphorisms. Those who know me can already guess how I built it: Golang! No external libs, all standard library! Embedded template! HTMX! Less than 500 lines, including both comments and the template for rendering! <1s compile times!
Cool, I know. But also nothing especially noteworthy, mind you. What I think is worthy of note is a small piece of this puzzle: how did I deal with secrets? Because a git-based workflow needs a way to authenticate with the remote server so that the microservice can push to it. Yes, I’m talking about secret management. How can I safely pass the personal access token to the application?
There are a myriad of choices: an environment variable is the simplest one, but that is a can of worms when it comes to access control. There are also things like GPG and SOPS, but the setup is a bit convoluted. And during the search for a good solution, I ended up finding a really neat solution: systemd-creds.
Look, I was already using systemd for managing the application. It’s the right way to do it in a Linux system. So, if I can use it to correctly inject the secret into the runtime without having to worry about anything else, I’m all in. And, I must say, it worked very well.
All it takes is a single directive on the unit file: LoadCredentialEncrypted= under the [Service] section, and it’s there. And with that, it’s as simple as loading the creds when I initialize the server:
credDir, ok := os.LookupEnv("CREDENTIALS_DIRECTORY")
if !ok || credDir == "" {
slog.Error("CREDENTIALS_DIRECTORY needs to be set")
return errors.New("unset CREDENTIALS_DIRECTORY")
}
password, err := os.ReadFile(filepath.Join(credDir, "aphorism-password"))
if err != nil {
slog.Error("Couldn't read password")
return errors.New("error reading password")
} else {
c.password = strings.TrimSpace(string(password))
}
Really neat trick. And with all of this in place, I can properly deploy the app, and muse on the go! I’d say to have a look, but it is, of course, locked down. Here’s a screenshot to prove that it works (resulting in this aphorism):
