Blog

More than you expected?

You're reading everything on my blog - including, among other things:

  • notes (short, "Tweet-like" messages),
  • reposts (links to other people's work, sometimes with commentary),
  • checkins (records of places I've visited, often while geo*ing), and
  • videos ("vloggy" content).

That might be more than you wanted to see, if you're only interested in articles (traditional long-form blog posts). Click a link to filter.

Dynamically-Deployed Static Site Subdomains on Caddy

I’ve recently been experimenting with where I host my small and open-source static sites. In my latest experiment, I wanted to try a low-maintenance selfhosting solution1. Here’s what I wanted:

  • Pushing to the main branch of my GitHub/Codeberg/wherever repo would send a webook to my server.
  • Upon receiving the webhook, my server would pull the latest changes2.
  • Using a wildcard certificate, my webserver automatically mounts each project at a subdomain matching its project name3.

Here’s what I came up with:

Step 1: webhook handler

I’m using Caddy as my webserver, because despite its considerable power and versatility it’s a breeze to set up. To sort wildcard DNS later I’ll want to swap in a custom build, but to get started I just ran apt install caddy. Then I used apt install webhook to install Adnan Hajdarević’s webhook endpoint, and tied the two together in my Caddyfile:

webhook.duckling.danq.me {
  reverse_proxy localhost:9000
}
My static server’s called duckling.danq.me, so you’ll see that turn up a lot in these configs.

Then I created a webhook in a GitHub repository:

GitHub webhook pointing to https://webhook.duckling.danq.me/hooks/github-push, with a secret set and SSL verification enabled, triggered by a push event.
I generated a long random string to use as the secret, and kept a copy for later.

When you create a webhook in GitHub it immediately sends a test event, but it doesn’t quite look like a real push event so I pushed an inconsequential change to the repo to trigger another. Once you’ve got a “real” one sent, you can re-send it via the “Recent Deliveries” tab as many times as you like, to help with testing.

Then, on the server, I checked-out a copy of the code (anonymously: this is a public repository so I don’t need keys to read from it anyway) and set up my /etc/webhook.conf to expect these calls:

[
    {
      "id": "github-push",
      "execute-command": "/var/www/github-push/webhook.sh",
      "command-working-directory": "/var/www/github-push/",
      "pass-arguments-to-command": [
        {
          "source": "payload",
          "name": "repository.name"
        }
      ],
      "trigger-rule": {
        "and": [
          {
            "match": {
              "type": "payload-hash-sha256",
              "secret": "[MY SECRET KEY HERE]",
              "parameter": {
                "source": "header",
                "name": "X-Hub-Signature-256"
              }
            }
          },
          {
            "match": {
              "type": "value",
              "value": "refs/heads/main",
              "parameter": {
                "source": "payload",
                "name": "ref"
              }
            }
          }
        ]
      }
    }
  ]
  
The trigger-rule directives ensure that (a) the secret key is correct (it uses a HMAC hash across the entire JSON request, so it prevents payload tampering too) and (b) the event only triggers on pushes to the main branch. The execute-command specifies the Bash script I want to run when the webhook is triggered. The pass-arguments-to-command configuration says to send the repo name on to that script.

Now all I needed to do was write the /var/www/github-push/webhook.sh Bash script so that it pulled the latest copy of the code when triggered:

#!/bin/bash
cd /var/www/github-push/$1 && git pull

I was able to test this by pushing inconsequential changes to my codebase and watching them get replicated down to my webserver. Neat!

Step 2: low-maintenance webserver

After pointing the DNS for *.static.duckling.danq.me at my static server, I set about configuring Caddy to be able to use DNS-01 challenges to get itself wildcard SSL certificates4. Caddy can’t do DNS-01 challenges out of the box, so you either need to write your own renewal script or compile Caddy with plugins corresponding to your DNS provider. My domains’ DNS are managed by a mixture of AWS Route 53, Gandi, and Namecheap, so my xcaddy build step looked like this:

xcaddy build \
  --with github.com/caddy-dns/route53 \
  --with github.com/caddy-dns/gandi \
  --with github.com/caddy-dns/namecheap

Of course, if I’d have preferred somebody else build it for me, CaddyServer’s download configurator would have done it for me on-demand.

For Gandi and Namecheap I just need a personal access token or API key, respectively, but Route 53’s configuration is slightly more-involved: I needed to create a new user via IAM and give it permission to write DNS TXT records for the appropriate hosted zone. Fortunately the guide for the caddy-dns/route53 repo had an almost copy-pastable example.

I added the AWS access key and secret key as environment variables (like this!) into my /etc/systemd/system/multi-user.target.wants/caddy.service service definition, and then told my Caddyfile to make use of them when renewing the wildcard certificate:

*.static.duckling.danq.me {
    tls {
        dns route53 {
          access_key_id {env.AWS_ACCESS_KEY_ID}
          secret_access_key {env.AWS_SECRET_ACCESS_KEY}
        }
      }
      root * /var/www/github-push/{http.request.host.labels.4}
      file_server
    }
}
The {http.request.host.labels.4} refers to the fourth part of the domain name, when separated at the dots and counted from the right, so 0 = me, 1 = danq, 2 = duckling, 3 = static, and 4 = the part that we’re interested in. So long as I don’t store any other directories in the /var/www/github-push/ directory then this will simply map each subdomain onto its git repository name and return a 404 for any other request.

DNS-01 challenges are necessarily slower than HTTP-01/ALPN challenges, because they’re limited by DNS propogation, so it took a while before the certificate was issued. I ran Caddy in the foreground to watch the logs while it did so:

Caddy webserver logs, with a highlighted section showing a DNS-01 challenge for *.static.duckling.danq.me repeatedly fail and then eventually succeed, then a certificate chain being installed.

You can see the whole thing working (for now at least; I don’t know if I’m keeping this approach!) by going to e.g. embed-html.static.duckling.danq.me, which dynamically tracks the main branch of the embed-html repo on GitHub.

I don’t yet know if this is going to be the future forever-home of my many static site side projects, but it’s certainly been the most-satisfying experiment to run so-far.

Footnotes

1 I’ve drifted away from selfhosting simple static sites lately because I’ve accidentally broken them with configuration changes too many times! But I figured I’d be open to in-housing them again if I had a single simple architecture for them all, so I spun up a VPS and gave it a go

2 Running a build script or some other static site generation tool is out of scope for now, but I want to be able to confirm that it would be possible in the future.

3 It also needs to be possible for me to map other domain names to it, but that’s a triviality.

4 It’s absolutely possible to use tls { on_demand } to do this, but it’s better to use a wildcard certificate which can be pre-generated and doesn’t let people trick your server into making ludicrous numbers of certificate requests by hammering random subdomain names.

× ×

Thames Path 7

This is a repost promoting content originally published elsewhere. See more things Dan's reposted.

New friends – obscure sights – the group divides – clear and present danger – an accident of geography – interest in bridges

2026 has not been an easy one so far. Work challenges, family challenges and my frickin’ house flooding have combined to make everything a bit overwhelming and hard to cope with.

So when we got a sunny Sunday, on a weekend in late April when (thanks to having found a long-term rental) we didn’t have to move between short-term lets, I cajoled Dan into once again acting as my support driver so I could walk some more of the Thames Path.

Dan and the smaller child joined me for the first couple of miles from Abingdon, which was nice.

My partner Ruth’s mission to walk the entire length of the Thames Path1 continued recently, and I still love “going on on” her journey – even the parts I wasn’t present for – through her blog posts.

If you too might enjoy blog-spectating this slowest-possible-walk along the length of the River Thames, you can catch-up on the backlog and subscribe for the next one, whenever that happens!

Footnotes

1 She’s doing the walk in many, tiny, and disparate instalments. By her own estimates she’s achieving about 50 metres per day, when averaged over her entire effort. This makes her only marginally faster than the 40 metres per day of the faster parts of the Greenland Ice Sheet, which I guess means that her progress is literally glacial in its speed.

×

Geohashing expedition 2026-05-06 51 -1

This checkin to geohash 2026-05-06 51 -1 reflects a geohashing expedition. See more of Dan's hash logs.

Location

Large field near Tawny’s Farm, East of Stanton Harcourt.

Participants

Plans

Between dropping the kids off at school and going to the dentist I had a small window of time in the vicinity of this hashpoint. Could I make it? I figured I’d rush out there and find out. It’d all depend on whether the field was actively in-use (I’m not gonna go tramping through crops!).

Expedition

I’ve been sort-of distracted from geohashing after my house flooded and I had to evacuate earlier this year, so I’ve not had much opportunity to get out and participate. But today, that changed!

A paved lane leading past a farmhouse.
The road stops here and becomes a footpath.

After dropping the kids at their schools I drove to Manor Cottages at 51.746, -1.396 where I knew I’d be able to park, then set off at a jog down the rest of Steadys Lane and onto the permissive footpath beyond. I waved to a solitary walker coming the other way, and pressed on until I was close to the field.

A field, turned over but left fallow.
Entering the first field.

Satellite photography suggested that I wouldn’t be able to enter the field directly, so I hopped a gate into the field West of it and was pleased to discover that neither field was currently cultivated.

A large empty field.
The second field: the hashpoint’s out there!

The hashpoint turned out to be about 700 metres into the field, and it felt sort of open and exposed to go tramping right across the middle of it, but sure enough that’s where I reached the circle of uncertainty, at 08:53.

A white man wearing a yellow 'Slamilton' t-shirt stands, smiling, in the middle of an empty field.
Silly grin!
Ultrawide 360 degree panoramic view of the empty field, with trees and hedges in the distance.
Panoramic view from the hashpoint.

Coming back, I spotted a disused and rusty old gate that provided easier access to the field, which provided a quicker way back to the footpath.

A rusted metal gate, slightly overgrown, at the egde of a field.
A better-placed gate.

Tracklog

No tracklog today. 😢 I’m not sure if I’ve seen my GPSr since the flood, so I was just working off my my phone.

× × × × × ×

A Postcard from Norway!

I got another postcard, and another new country for the senders list.

Dan, a white pan with a blue ponytail, holds a postcard in front of his face.
It’s always an exciting surprise to receive a Postcard From The Internet(!).1

I’ve added it to my gallery. This one’s from grubbyfox, whom I’ve crossed paths with on a Web forum2.

It remains a huge pleasure to receive a postcard from an Internet stranger (or even somebody I already know). It’s so much more tactile, and thoughtful, and human, and real than most of the other feedback I get.3

I guess there’s sort-of a scale of effort that it takes to react to content online. At the lowest end of the scale, barely above “doing nothing”, is “clicking a reaction button” (hey, did you see that you can click one below this?). It takes more effort to fill in a contact form. More still to send an individual email or ping me on Mastodon. But yet more still to write a postcard, find a stamp, go to the postbox… And I really appreciate it when somebody makes the effort.

Footnotes

1 The surprise is dampened only slightly by the fact that my PO Box provider emails me in advance to tell me I’ve received something.

2 Remember Web forums? They’re still very much around, and there are some cool communities if you can find the right one for you.

3 If you want to send my a postcard, my PO Box address is over here

×

Note #29064

How kind of the humans who constructed this sofa to leave a perfect dog-shaped nest in-between the cushions. Our pupper is appreciative.

A satisfied but sleepy-looking fawn-coloured French Bulldog lies on a blanket that's slipping into the crack between two sofa seat cushions, forming a nest around her torso. Her legs hang, crossed, off the front of the sofa.

×

Moving a static site from GitHub to Codeberg Pages

Late to the party,1 I finally got around to experimentally moving a GitHub Pages-hosted static site to Codeberg. I wanted a low-risk site to try first, so I moved Beige Buttons, the site hosting my “90s PC turbo button simulator” web component.

Ê

Mostly for my own benefit later, here’s the steps I took and the things I learned along the way:

  1. You can migrate a repository across in about two clicks. Easy!
  2. Codeberg Pages is deployed from the pages branch. If there’s no build step to the static site, all you need to do is rename the main branch to pages (and probably make it the default branch).2
  3. The default URL is https://username.codeberg.page/repository.
  4. You can use a custom domain by adding a .domains file that lists domains; if migrating from GitHub Pages you can just rename your CNAME file to .domains.
  5. You’ll need to tweak your DNS CNAME, ALIAS (or, worst-case, A/AAAA) record to point at Codeberg Pages.3

Change propogation feels slightly slower than GitHub, but perfectly tolerable.

The one thing that’s causing me trouble is that Codeberg Pages’ CORS headers prevent people from hotlinking the Beige Buttons JS, so there are some projects for which this wouldn’t be a suitable migration (issues are raised). But for most static sites, it’d probably Just Work and seems to be a great alternative.

Footnotes

1 With thanks to Kev for reminding me I’d had this on my list.

2 There are other ways to deploy but they don’t support custom domains yet.

3 Like GitHub Pages, Codeberg Pages uses LetsEncrypt for certificate provision, so you don’t need to change any CAA records.

Reply to: Who knows that you blog?

This is a reply to a post published elsewhere. Its content might be duplicated as a traditional comment at the original source.

David (Forking Mad+) said:

Question for the audience: Do you tell people you blog?

I was reflecting on this recently, after I inadvertently mentioned to a colleague that I had blogged about a topic a few days earlier. That, of course, started the questioning from them: Oh, what’s it called? What do you write about? Can I see it?

This one’s been doing the rounds recently, and I’ve enjoyed seeing responses from RNotte, Kev, Alex, and others (there’s a longer list on David’s post).

I feel like my answer is different from those of many other participants: I’ve traditionally made no effort to keep my cyberspace and meatspace separate: probably everybody in my “offline” day-to-day life knows that I write here, and my “online” identity centres around the same central point too.

But lately, I’ve leaned harder on talking about it online. Most of my real-world friends had blogs at some point, but virtually all of those have also long-ago abandoned them1, and along with that shift has come a disinterest in blogging and what it is (or can be) as well. Based on WhatsApp groups we share, most are happier reposting Facebook memes than sharing their own original thoughts in any kind of public forum.

(That said, it made my day when my friend Ele commented a couple of months ago. Hi, Ele!)

Anyway: I think that few would disagree that within popular culture, blogging has “had it’s day”. That’s not to say that there aren’t a great (and growing!) number of blogs out there: just that it’s not the popular touchstone that it was twenty years ago: data-harvesting social media silos have tragically become the “norm”, and for a blogger whose interest was targetting a specific audience… they need to go where the audience went.

Which I guess is something Kev was adjacent to when he said:

I tend to say “I enjoy writing” rather than “I have a blog.” And I think that’s because of the negative connotations blogging has with the general public.

I think some people tend to put “bloggers” in the same bucket as influencers, or podcasters. Which isn’t the case – many of us bloggers have no aspirations of influencing anything, we just like to share out thoughts on a medium we control.

This is the essence of it, I think. Blogging began as a way for people to write about… whatever they liked. But before long it also became a vehicle for marketing: “influencer bloggers” appeared and, let’s face it, made a bad name of it by making the popular expectation be that blogging is something you would do to make a living, rather than for the love of it. I’m not entirely surprised that so many people dropped blogging as a hobby rather than be clumped-in with them.2

But those same people had to jump platforms when social media silos became “the way” to advertise to a large audience, so now they’re all on Instagram, TikTok, and the like. The real personal bloggers – the ones who do it because blogging itself is the right medium for what they want to do – are still here. And, indeed, it feels like they’re in resurgence. Blogs are coming back, baby!3

The question was, though: who do I tell that I blog? And the answer is: absolutely anybody and everybody. But I mostly only make “subscribers” (a term I don’t terribly-much like, because it feels like it implies that increasing that number is some kind of “goal”) out of cyberspace friends, not meatspace friends, these days, I think.

But just sometimes, the worlds collide and somebody I see on the school run will talk about finding something of mine online, or somebody I meet through work will say that they read something I wrote, once. I enjoy these strange organic connections, but it can feel a little weird and awkward to be “recognised” unexpectedly.4

Footnotes

1 I’m pretty sad about it, honestly! I miss seeing blog posts from people I know in “the real world”; I see some, but not many.

2 It’s still a thing, of course, and I get countless requests for paid guest posts, ad placements, participation in link farming, and the like. And I’ve even had people ask me in person how I “monetise” my blog, as if making money from it was ever the goal!

3 Although probably never to the point where people will write about it as an up-and-coming phenomenon in a national newspaper, again. I think we’re past that stage.

4 Unexpected recognition also happens as a result of my work on FreeDeedPoll.org.uk and Three Rings,  but these tend to be slightly easier to predict depending on the space I’m in. There are still surprise moments, though, and they’re still sometimes awkward at first!

Spoon bucket

This is a repost promoting content originally published elsewhere. See more things Dan's reposted.

I got a spoon bucket for my birthday! 🥄

2 gallon white plastic bucket, labeled: - Flatware Circus - Spoon Bucket - Discover the wonder of spoons with this randomized collection contained within a convenient bucket - 30 miscellaneous spoons inside one bucket

Wait, this is a real thing?

OMG it is.

New life goal unlocked!

Evaluation: wild success, a very pleasing range of spoons (and two tiny forks)

11 small piles of stainless steel flatware sorted by type, with 1-7 items per pile. 10 types are spoons, 1 is tiny forks.

The same guy behind these also does buckets of forks. And vlogs about silverware in general.

Everything about that is excellent.

Oh, and happy birthday Jamie, I guess!

× ×

Dan Q found GCBMAMH #22 Northmoor Loop

This checkin to GCBMAMH #22 Northmoor Loop reflects a geocaching.com log entry. See more of Dan's cache logs.

For a while I was looking way too high, but in the end I caught a glimpse of this surprisingly well-concealed container.

And that’s the loop complete! Big thanks to the CO for setting this excellent trail spanning so many gorgeous footpaths, many of them new to me despite being relatively local!

Dan, a white man with a blue ponytail and a goatee beard, wearing a purple t-shirt, crouches in a quiet rural lane alongside a panting French Bulldog, whose lead he's holding, as he smiles at the camera.

Thanks for all the caches, and for a delightful Spring morning’s walk!

×