Static Site Generator

This site was built with Hugo.

Version: v0.124.0-629f84e8edfb0b1b743c3942cd039da1d99812b0+extended.

It was last built at 2024-06-24T18:10:46Z.


This site began with the hello-friend-ng Hugo theme. Rather than install it via a submodule, though, I copied it directly in to my repo so that I could start making it exactly what I wanted it to be. I’ll lose the ability to update if the theme ever updates, but I mostly just wanted the theme as a place to base my own creations from.

Theme Switcher

The original hello-friend-ng theme, at least at the time that I copied it, doesn’t support media query CSS themes, which meant that the only theme switching capability relied on Javascript. Since I didn’t want to rely on Javascript, and I wanted to support system defaults for new visitors, I had to make some tweaks to the theme switcher.

What I ultimately ended up with was rewriting the way color schemes worked in hello-friend-ng, adopting code from Bryce Wray’s “It’s tri-state switch time” and Aleksandr Hovhannisyan’s “The Perfect Theme Switch Component”. It turns out this is a reasonably complex problem to solve if you want to avoid flashes of unstyled content, which I absolutely did.

This means that if your system is in dark mode and you visit my site for the first time, it’s automatically in dark mode. If you visit in light mode, it’s automatically in light mode. You can then optionally override this for the site by using the theme switcher at the top.

Deploying to Tor

In order to deploy my site to both and my onion site, I had to learn how configurations cascade in Hugo.

Instead of having a single Hugo config file, I have a config folder in the root of my repository. Inside this folder, I have 3 additional folders – _default, production, and tor. The _default captures the majority of my configuration settings – anything that doesn’t change between my tor and clearnet deployments goes here. This captures about 99% of my configuration.

├── _default
│   ├── config.toml
│   ├── menus.toml
│   └── params.toml
├── production
│   ├── config.toml
│   └── params.toml
└── tor
    ├── config.toml
    └── params.toml

3 directories, 7 files

In the production and tor folders, I have config.toml and params.toml files to override specific sections of the config. Basically, in production we set the base URL and list an alternate site as tor, and in tor we set the base URL to the onion address and set an alternate site of the clearnet version. The contents of these files are really simple:

Configuration Differences

# production/config.toml
baseURL = ""
# production/params.toml
    name = "tor"
    class = "pink"
    url = "http://dadehacks5p4qrui2wy2bcfp37wgtycysqhxuwa2o7k2t34rryrzhdqd.onion"
# tor/config.toml
baseURL = "http://dadehacks5p4qrui2wy2bcfp37wgtycysqhxuwa2o7k2t34rryrzhdqd.onion/"
# tor/params.toml
    name = "clearnet"
    class = "pink"
    url = ""

Beyond these configuration files, I also added the Onion-Location header to my nginx config, so that browsing to it in the Tor Browser will automatically redirect you to the onion site.

Deployment Script

In order to build both variations of my site, so that each environment is self-contained and doesn’t leak over to the other, I use a simple deployment script that uses hugo’s --environment flag to specify which site to build. It then copies the site into the corresponding web directory.


# Perform git pull and capture the output
pull_output=$(git pull)

# Check if there were changes
if [[ $pull_output == *"Already up to date."* ]]; then
    # If there were no changes, exit without continuing
    echo "No changes were pulled from the remote repository."
    exit 0
    # If there were changes, proceed with the rest of the script
    hugo && \
    cp -r public/* /var/www/ && \
    hugo --environment tor && \
    cp -r public/* /var/www/dade-onion/

Tor Vanity Domain

I used mkp224o to generate my vanity onion v3 address. I don’t remember the specifics of how long it took, but I think it was a couple days of generating on the vm that my site runs on.

Deployment Methodology

I’m a little annoyed at how this works, so I might change it soon. Basically, my server only allows SSH and other administrative connections from a bastion host. As such, I can’t easily use a Github workflow to SSH into the server to update it. I would have to decide either to allow all Github actions to SSH into my server or setup an action that tunnels the connection through my bastion host – neither of these seem ideal.

Instead, I use a manual pull process that involves logging into the server and running my update script. It uses a deploy key that can only pull from the repo, so it isn’t password protected or anything. I’ve thought about automating the pull script to run every few minutes, that way it would be like a real time update, without having to login and do it myself. But my attempts to do so with cron have not yielded success so far.

An alternate approach I have been considering is building a super simple deployment controller application that can receive webhooks from Github and then run a script to update that repository when a push to main happens. This would give me a responsive deployment that doesn’t rely on arbitrarily polling Github, and I could keep my ssh ports restricted to my bastion. But then I have to build and maintain a little deploy controller.

Images / Static Resources

Recently I switched to using a version of the hugomods images module to manage my images. It automatically sets lazy loading on all my images, converts things to webp, hashes image URLs, etc. This means my images are content-addressable, which makes them permanently cacheable.

Before this, I was basically just uploading images to imgur and referencing them in markdown. The one thing I liked about this solution was that I didn’t have to upload images to my git repository. But it meant that visitors to my site were being forced to load images from imgur, even if they were connecting via the tor hidden service. I didn’t like that.

I guess technically I can improve this by using git LFS to track the image files, that way they aren’t being tracked the same way as my text-based files. But I haven’t looked closely into this, and am not sure what the LFS limits are for the free tier of Github. It also seems to complicate things, and I don’t use images frequently enough to warrant it, probably?


I make liberal use of icons for my homepage as well as in my header and footer. These are all SVG icons in an SVG partial that takes the name of an icon as a parameter and then renders the corresponding SVG. I get most of my icons from where possible, and then typically make some changes to them to make sure the SVG will take take the color of whatever css style I put on it, make sure it has a title, etc.

Alternate Formats

I publish section and taxonomy pages as HTML files for browsing on the web, but also as RSS and JSON Feed for consumption in various feed readers. I’ll be honest, I don’t actually understand the difference between RSS and Atom, so I might be publishing an Atom feed instead of an RSS feed. But I think anything that supports one tends to support the other, so I haven’t spent the time to learn the distinction yet.

My JSON feed output is almost identically copied from this very useful post titled “Make Hugo generate a JSON search index and JSON Feed”. I’d like to also explore the search index output that it recommends, but I want to combine it with a command k bar like experience, which I’ve been finding is slowly dominating my life – I want command k bars for everything. I’m mildly annoyed that in Obsidian (which I’m writing this in), my command palette is on cmd-p (or ctrl-p). I still appreciate having the command palette, and I don’t find it annoying enough that I’m going to remap the hot keys, but I do wish there was a universal standard that everyone used. Unfortunately several sites use cmd-k to insert a link at the cursor position, and that has probably been deeply ingrained in a lot of users.

I want to toy with providing alternate formats of my actual content pages through Hugo’s alternate output configuration. I think it would be cool if I could publish all of my pages as text or markdown files that can easily be read in the terminal or by a user agent that specifically requests something other than html, for instance. But I’m not sure if there is meaningful value in this, or if it’s just a novel idea to explore.


I also quite like the IndieWeb movement, I think it is a noble movement that seeks to reclaim the internet experience from the massive corporations that run everything these days. I’ve taken steps to get my site IndieWeb compatible, mostly through following the process.

Writing Experience

I write all my draft posts in my Obsidian vault, which I find makes it super easy to find related topics that I’ve already taken notes on, find related links that I wouldn’t remember how to find online but know I captured in my notes, etc.

But when it’s time to publish, I haven’t quite figured out a good obsidian->hugo->publish pipeline yet. I think I can do something with Templater and system commands to automatically copy a post from my Obsidian vault to my hugo site, convert links to match what hugo expects, and then stage a commit for me. Ideally it would actually commit and push the changes and trigger the build, too. But given that I haven’t solved for the push-based deploys yet, and I’m an insane guy who has to type my ssh key password every single time I use it, this isn’t likely to work very well. I did see someone who has done similar work to this last year, from Conundrum – it looks really useful as a basis point, but I haven’t invested in perfecting it for me yet.

When modifying existing posts, I use VS Code with the Frontmatter CMS extension. What I find particularly valuable about this is that it has an option to automatically update the lastmod frontmatter. I wanted my lastmod to automatically accurately reflect the last time I modified a file, but there were a few caveats – if I just moved files around in my repo but the contents/URL didn’t change, I didn’t want the date to change. Additionally, when I build and publish my site, I had :fileModTime as a fallback for the lastmod time, and basically every file was changing on every build, which made the lastmod very useless.

The Frontmatter approach of updating lastmod only when I save the file in VS Code is basically perfect – I don’t care about lastmod for unpublished files in Obisidian, and I can move files to my heart’s content without the lastmod date changing.

Security Measures

This is a statically generated site, so there aren’t a ton of drastic security measures required. I do a couple things in my nginx config.

HTTP Strict Transport Security

I’m a huge fan of TLS, as we all should be. Let’s Encrypt changed the game when it came to TLS certificates, and since they are easier than ever to obtain, it made sense to also include my site in the HSTS Preload list. This means that browsers will automatically connect to my website, and all subdomains of my domain, with HTTPS without trying HTTP.

You can check the status of my site in the HSTS Preload List.

Content Security Policy

Because my website provides all of its own assets, my Content Security Policy is able to be quite restrictive and only allow content from self. I do allow unsafe-inline for style attributes, mostly because I’m a bad developer and sometimes I just like overriding a style directly on an element instead of going to deal with a new CSS class.

Other Security Headers

This site scores an A+ on It makes use of HSTS, CSP, X-Content-Type-Options, Referrer-Policy and Permissions-Policy.

TLS Configuration

This site uses a “modern” configuration from Mozilla’s SSL Configuration Generator. It does not, however, use any TLS when visited via the Tor Hidden Service. This is because all traffic to hidden services is already encrypted, and not a lot of CAs will issue .onion certificates.


This site uses a self-hosted instance to collect analytics from visitors. It is designed to be privacy-preserving and effectively it serves as a glorified hit counter.

Potential future work

Some things I’d love to explore include:

  • Alternate output options like .txt or .md
  • A way to proxy twitter embeds, youtube embeds, that sort of thing – I want to start publishing talk scripts/slides/videos on my blog this year, but don’t want to host the video myself, but also don’t want to embed youtube on my site and wreck the privacy-centric vibe I’ve got going on.
  • Setting up webmentions maybe? I like the idea of it, but I remember pingbacks in my wordpress days and my god they were annoying.
  • Look into auto-POSSE type behavior to publish on my site and syndicate to twitter, mastodon, etc. I like a post by Cory Doctorow where he describes his approach to POSSE, but it seems like a lot of manual work. I wonder if there’s a good way I can automate parts of it. Twitter eliminating it’s free tier API also kinda ruins doing it automatically there.
  • Introduce a mailing list to my site – I don’t know if I want to do a mailing list that gets everything that gets published as individual articles, or if I want to do a weekly digest type newsletter. The former puts a lot of pressure on all my posts being relevant to everyone who subscribes, whereas the latter lets me create a consistent experience at the cost of having to write a specific newsletter post on some cadence.
  • I really like Xe’s use of characters to help move a post along in a conversational way. This is briefly touched on in the “How My Website Works” talk, which is also incredible. I experimented a little with this in Anatomy of a Hash but haven’t really fleshed it out much.
  • I also love Maggie Appleton’s “A Brief History & Ethos of the Digital Garden”. I especially like the way categories and tags work together to produce an interesting navigation experience, as well as the concept of “evergreen” notes versus “seedling” notes – a way to indicate how much effort/thinking you’ve put into posts. I’d love to explore ways to move my site beyond the typical chronological blog. I think there is still value in the chronological feed, but maybe it doesn’t need to be the foremost way to interact with the site.
  • I would love a good mechanism to keep only very specific notes in my obsidian vault in sync with my hugo site. But I don’t want my whole hugo site in my vault. Otherwise tweaks to my published site will quickly start to get away from their representation in my obsidian vault.