<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://chameth.com/">
    <title>Chameth.com - posts not break-everything-fuzz-testing, debugging-beyond-the-debugger, g15-ram-upgrade, how-i-use-tailscale, reverse-engineering-arctis-pro-wireless-headset, tracking-pbs-without-the-dark-patterns, why-you-should-be-using-https</title>
    <subtitle>Personal homepage of Chris Smith</subtitle>
    <link href="https://chameth.com/feeds/posts/unlike/break-everything-fuzz-testing,debugging-beyond-the-debugger,g15-ram-upgrade,how-i-use-tailscale,reverse-engineering-arctis-pro-wireless-headset,tracking-pbs-without-the-dark-patterns,why-you-should-be-using-https/" rel="self"/>
    <link href="https://chameth.com/"/>
    <icon>https://chameth.com/favicon.png</icon>
    <updated>2026-09-09T00:00:00Z</updated>
    <id>https://chameth.com/</id>
    <author>
        <name>Chris Smith</name>
    </author>
    <entry>
        <title>Lego, ACME DNS-01, and negatively-cached surprises</title>
        <link href="https://chameth.com/lego-acme-dns-01-surprises/"/>
        <updated>2026-09-09T00:00:00Z</updated>
        <id>https://chameth.com/lego-acme-dns-01-surprises/</id>
        <content xml:lang="en" type="html">&lt;p&gt;Recently &lt;a href=&#34;https://www.simonmott.co.uk&#34;&gt;a friend&lt;/a&gt; was deploying &lt;a href=&#34;https://github.com/csmith/centauri&#34;&gt;Centauri&lt;/a&gt;, my reverse proxy service that can obtain TLS certificates
using ACME DNS-01 challenges. He asked a seemingly innocent question: “When Centauri is ‘waiting for record propagation’ is there an option to increase the time?”
But that’s a question with a hidden, concerning XY problem. We both use the same DNS provider, and it definitely shouldn’t take more than a minute to see a DNS record
after it’s been created. Plus, I don’t have issues with it timing out!&lt;/p&gt;
&lt;p&gt;Centauri uses the excellent &lt;a href=&#34;https://github.com/go-acme/lego&#34;&gt;lego library&lt;/a&gt; to do the actual ACME dance, and I’ve dug into it before pretty extensively to debug
other DNS issues. When I last looked, it used the local recursive DNS resolver to check for CNAMEs and find the authoritative server, then checked for record
propagation there. So, naturally, I blamed Simon’s local resolver doing something weird. Turns out that was surprisingly close to correct, but not for the reasons
I thought.&lt;/p&gt;
&lt;p&gt;Unfortunately for my snap judgements, Simon had a perfect rejoinder: he’d deployed the exact same VM image before and Centauri worked fine. And also while we’d
been speaking it had managed to get a certificate after waiting 58 seconds for DNS propagation. Simon suggested that maybe it was systemd-resolved caching
something stale, but I dismissed that based on what I knew of how Lego worked.&lt;/p&gt;
&lt;h3 id=&#34;diving-back-into-lego&#34;&gt;Diving back into Lego&lt;/h3&gt;
&lt;p&gt;Of course, after I dismissed the idea, I immediately started doubting myself. Last time I was looking at DNS issues I traced the code all the way across the
Lego codebase. This time I just had a coding agent explore and summarise for me. Turns out it DOES use the recursive resolver to check for the TXT challenge
record.&lt;/p&gt;
&lt;p&gt;The DNS functionality looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CNAME query on the &lt;code&gt;_acme-challenge&lt;/code&gt; record using the recursive resolver.&lt;/li&gt;
&lt;li&gt;Maybe an SOA query on the same, depending on the DNS provider, using the recursive resolver.&lt;/li&gt;
&lt;li&gt;Actually create the challenge record using the DNS provider.&lt;/li&gt;
&lt;li&gt;Query records recursively to discover CNAMEs using the recursive resolver. It doesn’t matter if this fails.&lt;/li&gt;
&lt;li&gt;Query the TXT record on &lt;em&gt;every&lt;/em&gt; configured recursive resolver, blocking until they &lt;em&gt;all&lt;/em&gt; work.&lt;/li&gt;
&lt;li&gt;Query the TXT record on the authoritative nameserver.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So yes, a stale response from the recursive resolver can indeed ruin the whole thing. Simon’s domain had a minimum TTL of 60 seconds, so that links in
nicely with it working at 58 seconds and failing most of the time: the 1 minute timeout is racing the 1 minute TTL, somehow. But my
domains use a much longer TTL and I can see renewals happening in my logs basically immediately. What gives?&lt;/p&gt;
&lt;h3 id=&#34;nxdomain-caching-and-rfc-fun&#34;&gt;NXDOMAIN caching and RFC fun&lt;/h3&gt;
&lt;p&gt;So I had a theory as to what was happening, but it still had some loose ends that I didn’t understand. I did get a pretty big hint when I started doing
lookups from various locations, though. On my desktop if I do a CNAME query then a TXT query for a record that doesn’t exist, I get this:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ drill CNAME nonexistent.chameth.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, rcode: NXDOMAIN, id: 12630
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;chameth.com.	3600	IN	SOA	chameth.com. root.chameth.com. 2026339162 7200 3600 86400 3600
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ drill TXT nonexistent.chameth.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, rcode: NXDOMAIN, id: 56402
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;chameth.com.	3590	IN	SOA	chameth.com. root.chameth.com. 2026339162 7200 3600 86400 3600
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And from my server:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ drill CNAME nonexistent.chameth.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, rcode: NXDOMAIN, id: 7807
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;chameth.com.	3600	IN	SOA	chameth.com. root.chameth.com. 2026339162 7200 3600 86400 3600
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;$ drill TXT nonexistent.chameth.com 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, rcode: NXDOMAIN, id: 5069
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;chameth.com.	3600	IN	SOA	chameth.com. root.chameth.com. 2026339162 7200 3600 86400 3600
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice how the TTL (the first number) decreases locally, but not on the server? That implies my local
resolver is reusing the same result when I do the second query, and the resolver on the server isn’t.
It turns out that on my desktop I use &lt;code&gt;systemd-resolved&lt;/code&gt;, which sends requests to Tailscale, which
passes them off to NextDNS. On the server they go straight to Tailscale and then onwards to NextDNS.
So &lt;code&gt;systemd-resolved&lt;/code&gt; is what’s causing the behaviour. Like Simon guessed right at the start.&lt;/p&gt;
&lt;p&gt;But why is it actually doing that? It’s kind of annoying. Well, it’s doing what it’s meant to.
&lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc2308.html#section-5&#34;&gt;RFC2308 says&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A negative answer that resulted from a name error (NXDOMAIN) should
be cached such that it can be retrieved and returned in response to
another query for the same &amp;lt;QNAME, QCLASS&amp;gt; that resulted in the
cached negative response.&lt;/p&gt;
&lt;p&gt;A negative answer that resulted from a no data error (NODATA) should
be cached such that it can be retrieved and returned in response to
another query for the same &amp;lt;QNAME, QTYPE, QCLASS&amp;gt; that resulted in
the cached negative response.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because the record doesn’t exist at all (there’s no other records for &lt;code&gt;_acme-challenge&lt;/code&gt;: why would
there be?) we get an &lt;code&gt;NXDOMAIN&lt;/code&gt; response, and that “should” be cached for &lt;em&gt;any&lt;/em&gt; query type. So
when Lego queries for the CNAME record, we’re priming the cache with “&lt;code&gt;_acme-challenge&lt;/code&gt; doesn’t
exist” with a potentially huge TTL on that cache entry. When we then try and query the TXT record
we created we get the cached &lt;code&gt;NXDOMAIN&lt;/code&gt; back.&lt;/p&gt;
&lt;p&gt;If the TXT record was created first then the CNAME query would get a &lt;code&gt;NODATA&lt;/code&gt; response instead,
which wouldn’t affect us as that &lt;em&gt;is&lt;/em&gt; understandably cached per query type. But the point in
Lego’s CNAME check is to decide &lt;em&gt;where&lt;/em&gt; to put the TXT record, so it can’t just flip the ordering.&lt;/p&gt;
&lt;p&gt;I apparently dodged this problem entirely on my servers, because nothing in that resolution path
implements this particular caching behaviour.&lt;/p&gt;
&lt;h3 id=&#34;but-why-now&#34;&gt;But why now?&lt;/h3&gt;
&lt;p&gt;But Simon had this working before. It’s possible that his existing deployments are having this issue &lt;em&gt;now&lt;/em&gt;, and are just retrying enough to win the race, but
he would’ve noticed if they failed this much when he set them up.&lt;/p&gt;
&lt;p&gt;So I did some archaeology. Here’s the timeline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/go-acme/lego/commit/b594acbc2a17f1643568bb62d2ccf8250e444219&#34;&gt;February 2016&lt;/a&gt;: CNAME check added to deal with, well, CNAMEs.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/go-acme/lego/commit/06b3802346b4a4c172c66bcde49532e875dfda2d&#34;&gt;February 2016&lt;/a&gt;: Immediate follow-up making a failed CNAME check non-blocking.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/go-acme/lego/pull/2284&#34;&gt;September 2024&lt;/a&gt;: Option added to check propagation using custom recursive resolvers.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/go-acme/lego/pull/2847&#34;&gt;February 2026&lt;/a&gt;: That option now defaults to on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the mystery is solved: Lego v5 enabled the recursive check by default. That also handily explains why
I thought its behaviour was different. It used to be! There were still two things I didn’t understand:
&lt;em&gt;why&lt;/em&gt; was it enabled, and &lt;em&gt;why&lt;/em&gt; didn’t I realise when I upgraded to Lego v5? Looking back, I think this
line of the changelog is meant to cover the behaviour:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[lib,dnsprovider] New approach of the DNS calls&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The migration guide also mentions the flag being renamed, but &lt;em&gt;not&lt;/em&gt; the fact that the
default setting also flipped. Feels like burying the lede a little, but hey ho.&lt;/p&gt;
&lt;p&gt;As for why, there doesn’t seem to be any documented rationale. Given the recursive resolver
check was added to fix a &lt;a href=&#34;https://github.com/go-acme/lego/issues/2276&#34;&gt;fairly niche problem&lt;/a&gt;, it
seems like an odd decision to me. The new defaults mean that any recursive resolver that caches
&lt;code&gt;NXDOMAIN&lt;/code&gt; responses across query types will delay the propagation check, and likely fail it
entirely if the TTL is longer than the timeout. That’s… unfortunate to say the least.
I opened a &lt;a href=&#34;https://github.com/go-acme/lego/discussions/3261&#34;&gt;discussion suggesting the default gets flipped back&lt;/a&gt;
but it doesn’t sound like that will happen before v6.&lt;/p&gt;
&lt;p&gt;For Centauri, at least, I’ve fixed it by unconditionally disabling the recursive resolver check,
taking it back to the Lego v4 behaviour. And after all this, Simon’s original hunch turned out
to be spot on: it &lt;em&gt;was&lt;/em&gt; &lt;code&gt;systemd-resolved&lt;/code&gt; caching, but it was doing the correct thing. Pesky
old-man Lego was the one responsible for dropping a rake on the ground then immediately
stepping on it.&lt;/p&gt;
</content>
    </entry>
    <entry>
        <title>Monthly Meanderings: June 2026</title>
        <link href="https://chameth.com/monthly-meanderings-2026-06/"/>
        <updated>2026-07-01T00:00:00Z</updated>
        <id>https://chameth.com/monthly-meanderings-2026-06/</id>
        <content xml:lang="en" type="html">&lt;p&gt;Writing these monthly posts is making me very aware of how quickly the year is going. I’m not sure I like that at all. June saw some truly horrible hot weather in the UK; it’s almost like murdering the planet with CO₂ emissions was a terrible, terrible idea.&lt;/p&gt;
&lt;p&gt;My dodgy knee that I mentioned &lt;a href=&#34;https://chameth.com/monthly-meanderings-2026-04/&#34;&gt;way back in the April post&lt;/a&gt; feels like it’s finally back to normal. I had low expectations of the physio service, and I’m glad to find I was utterly wrong. When I self-referred I got an e-mail the same day with some tailored exercise suggestions, and I’ve had two in-person sessions since. I managed to get some longer walks and a jog in this month, when I wasn’t camped in front of a fan to try and keep cool.&lt;/p&gt;
&lt;h3 id=&#34;website-updates&#34;&gt;Website updates&lt;/h3&gt;
&lt;p&gt;Another month with no new posts. I’m just in one of those slumps where nothing quite takes my fancy. It’s a bit upsetting that the three most recent posts are all going to be monthly meanderings, though.&lt;/p&gt;
&lt;p&gt;I did spend some time adding &lt;a href=&#34;https://standard.site/&#34;&gt;standard.site&lt;/a&gt; metadata and records for blog posts. In theory that means it’ll show up a bit nicer when presented on Bluesky, but I’ve not actually tested it in anger yet. As well as pretty embeds, having a standard schema for describing publications and posts feels like it could enable a lot of cool discovery/directory uses.&lt;/p&gt;
&lt;h3 id=&#34;other-projects&#34;&gt;Other projects&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/csmith/centauri&#34;&gt;Centauri&lt;/a&gt;, my reverse proxy, got a whole bunch of new features in response to some requests and problem reports from a friend. Mostly the changes were exposing more options for finer control over how certificates are obtained. Providers other than &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let’s Encrypt&lt;/a&gt; require extra account details, have different rate limits, and so on.&lt;/p&gt;
&lt;p&gt;The only other project of note this month is a new World of Warcraft addon called &lt;a href=&#34;https://www.curseforge.com/wow/addons/whos-got-the-keys&#34;&gt;Who’s Got the Keys?&lt;/a&gt; that shows on-screen who has a keystone for the dungeon you’re currently waiting to start. I really like being able to scratch itches like that.&lt;/p&gt;
&lt;h3 id=&#34;entertainment&#34;&gt;Entertainment&lt;/h3&gt;
&lt;p&gt;I mentioned my wax-and-wane hobby cycle last month. This month apparently films are back in for me:&lt;/p&gt;
&lt;ol class=&#34;watched-films&#34;&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/on-flux-2005/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/256/poster.jpg&#34; alt=&#34;Poster of Æon Flux&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-04&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/annihilation-2018/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/257/poster.jpg&#34; alt=&#34;Poster of Annihilation&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-05&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-half.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Half star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/good-luck-have-fun-don-t-die-2026/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/258/poster.jpg&#34; alt=&#34;Poster of Good Luck, Have Fun, Don&amp;#39;t Die&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-06&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/bodies-bodies-bodies-2022/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/259/poster.jpg&#34; alt=&#34;Poster of Bodies Bodies Bodies&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-11&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/over-your-dead-body-2026/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/260/poster.jpg&#34; alt=&#34;Poster of Over Your Dead Body&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-15&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/ghost-in-the-shell-1995/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/261/poster.jpg&#34; alt=&#34;Poster of Ghost in the Shell&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-20&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/what-we-do-in-the-shadows-2014/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/262/poster.jpg&#34; alt=&#34;Poster of What We Do in the Shadows&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-21&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-half.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Half star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/disclosure-day-2026/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/263/poster.jpg&#34; alt=&#34;Poster of Disclosure Day&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-23&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/stoker-2013/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/264/poster.jpg&#34; alt=&#34;Poster of Stoker&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-24&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/midsommar-2019/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/265/poster.jpg&#34; alt=&#34;Poster of Midsommar&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-27&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-0&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;a class=&#34;plain-link raised-box&#34; href=&#34;https://chameth.com/films/ready-player-one-2018/&#34;&gt;
      &lt;img src=&#34;https://chameth.com/films/266/poster.jpg&#34; alt=&#34;Poster of Ready Player One&#34; loading=&#34;lazy&#34;/&gt;
      &lt;time&gt;2026-06-28&lt;/time&gt;
      &lt;div&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
    &lt;/a&gt;
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There were some big surprises for me in both directions: films I expected to like such as &lt;a href=&#34;https://chameth.com/films/disclosure-day-2026/&#34;&gt;Disclosure Day&lt;/a&gt; and &lt;a href=&#34;https://chameth.com/films/good-luck-have-fun-don-t-die-2026/&#34;&gt;Good Luck, Have Fun, Don’t Die&lt;/a&gt; absolutely missed the mark; meanwhile &lt;a href=&#34;https://chameth.com/films/midsommar-2019/&#34;&gt;Midsommar&lt;/a&gt; I didn’t have any particular expectations of and it ended up stealing the top spot on my &lt;a href=&#34;https://chameth.com/films/lists/ranking/&#34;&gt;watched films ranked&lt;/a&gt; list.&lt;/p&gt;
&lt;p&gt;Most of my other entertainment is still coming from World of Warcraft. I’m now reasonably comfortable with my spec, and my regular group of friends is well on the way to reaching a Mythic+ score of 3400 before the end of the season. Amusingly I’m currently in the top 20 for my spec on my realm, but that says more about the low population of the server than it does about my skill, I think!&lt;/p&gt;
&lt;p&gt;My character is now about as fully geared as they’re going to get this season. There are some minor upgrades I could make but it’s not really worth it with only weeks remaining. I now also don’t get any real benefit from doing the standard open world weekly activities, so I’ve been spending a bit more time farming mounts and transmog in old content. I’ve also levelled a whole bunch of alts, but I’ve failed to find a single DPS spec I actually like. I’m now trying alternative healers just to get some variety when we do runs where we’re not pushing keys.&lt;/p&gt;
&lt;h3 id=&#34;around-the-web&#34;&gt;Around the web&lt;/h3&gt;
&lt;h4 id=&#34;suspicious-discontinuitieshttpsdanluucomdiscontinuities&#34;&gt;&lt;a href=&#34;https://danluu.com/discontinuities/&#34;&gt;Suspicious discontinuities&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;An old article by &lt;a href=&#34;https://danluu.com/&#34;&gt;Dan Luu&lt;/a&gt; that recently resurfaced on Hacker News. It’s an interesting look at various series that have discontinuities and the reasons for those. I’m easily won over by cool graphs, so it’s not a surprise I saved this link.&lt;/p&gt;
&lt;h4 id=&#34;incident-report-cve-2026-lgtmhttpsnesbittio20260626incident-report-cve-2026-lgtmhtml&#34;&gt;&lt;a href=&#34;https://nesbitt.io/2026/06/26/incident-report-cve-2026-lgtm.html&#34;&gt;Incident Report: CVE-2026-LGTM&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A very funny spoof vulnerability disclosure that pokes fun at all the failure modes of LLM security tooling and the possible consequences of that.&lt;/p&gt;
&lt;h4 id=&#34;help-i-accidentally-a-wigglegramhttpslmaocenterblogwiggle-accidents&#34;&gt;&lt;a href=&#34;https://lmao.center/blog/wiggle-accidents/&#34;&gt;Help I accidentally a wigglegram&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Fun article where the author describes how they automatically made a bunch of “wigglegrams” (stereoscopic animated gifs) from their iCloud photo library. It’s interesting how just taking multiple versions of a photo can result in something novel like this.&lt;/p&gt;
&lt;h4 id=&#34;finding-the-best-dog-treat-with-statisticshttpswwwwespisercomposts2026-06-19-best-dog-treathtml&#34;&gt;&lt;a href=&#34;https://www.wespiser.com/posts/2026-06-19-best-dog-treat.html&#34;&gt;Finding the Best Dog Treat with Statistics&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;I’m pretty sure this is actually some devious attempt to make me learn statistics, but it features a dog called Bebop, so I’m going to allow it.&lt;/p&gt;
</content>
    </entry>
    <entry>
        <title>Monthly Meanderings: May 2026</title>
        <link href="https://chameth.com/monthly-meanderings-2026-05/"/>
        <updated>2026-06-01T00:00:00Z</updated>
        <id>https://chameth.com/monthly-meanderings-2026-05/</id>
        <content xml:lang="en" type="html">&lt;p&gt;Another month, another wrap-up post. This is the first one I’ve felt reluctant to write, and I think it’s because I feel like I didn’t really do anything last month. Other than play a lot of World of Warcraft.&lt;/p&gt;
&lt;h3 id=&#34;website-updates&#34;&gt;Website updates&lt;/h3&gt;
&lt;p&gt;No new posts this month, but I finished off the refactoring &lt;a href=&#34;https://chameth.com/monthly-meanderings-2026-04/#website-updates&#34;&gt;I mentioned in the last update&lt;/a&gt;. It’s been a very satisfying process, and working on the site feels a lot less like a chore now. Features are now entirely self-contained, instead of having bits and pieces scattered everywhere, and everything is wired up together with some automatically generated code. I’ll probably write a longer post about the architecture and the refactoring process at some point.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&#34;https://chameth.com/wow/&#34;&gt;World of Warcraft page&lt;/a&gt; got some updates, now showing achievements and stats about Mythic+ runs. It’s always nice to take a break from the game… and code things relating to it instead.&lt;/p&gt;
&lt;h3 id=&#34;other-projects&#34;&gt;Other projects&lt;/h3&gt;
&lt;p&gt;I finished off &lt;a href=&#34;https://github.com/csmith/glauncher&#34;&gt;glauncher&lt;/a&gt;, my native alternative to &lt;a href=&#34;https://ulauncher.io/&#34;&gt;ulauncher&lt;/a&gt; and am now using it day-to-day, which is nice. I’ve never really liked making native user interfaces, but having an LLM generate code for &lt;a href=&#34;https://gioui.org&#34;&gt;Gio UI&lt;/a&gt; was pretty much pain-free. I’m now toying with replacing a few other simple apps I currently rely on that use Electron or Webkit with Go versions.&lt;/p&gt;
&lt;h3 id=&#34;entertainment&#34;&gt;Entertainment&lt;/h3&gt;
&lt;p&gt;Let’s start with the non-World of Warcraft updates. I seem to be on a one-film-a-month cadence recently. This month’s was fine but not earth shattering:&lt;/p&gt;
&lt;div class=&#34;film-review-parent&#34;&gt;
  &lt;section class=&#34;film-review raised-box&#34;&gt;
    &lt;img src=&#34;https://chameth.com/films/255/poster.jpg&#34; alt=&#34;Poster for This Is Spinal Tap&#34; loading=&#34;lazy&#34;/&gt;
    &lt;header&gt;
      &lt;h3 class=&#34;plain-header&#34;&gt;&lt;a href=&#34;https://chameth.com/films/this-is-spinal-tap-1984/&#34;&gt;This Is Spinal Tap&lt;/a&gt;&lt;/h3&gt;
      &lt;div&gt;&lt;/div&gt;
      &lt;div title=&#34;5/10&#34;&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-half.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Half star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-empty.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Empty star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
      &lt;time&gt;2026-05-30&lt;/time&gt;
    &lt;/header&gt;
    &lt;div&gt;&lt;p&gt;Quite funny, and it’s interesting to watch something that’s become such a cultural touchstone, but coming to it 40 years on I find it a bit slow and meandering.&lt;/p&gt;
&lt;/div&gt;
  &lt;/section&gt;
&lt;/div&gt;
&lt;p&gt;SNL UK wrapped up this month, but has thankfully been picked up for another season. I really enjoyed it, despite my initial misgivings about it. I’m now back down to exactly zero TV programmes I care about.&lt;/p&gt;
&lt;p&gt;A few board game plays this month, but overall still in the “wane” phase of my wax-and-wane hobby cycle:&lt;/p&gt;
&lt;ul class=&#34;played-boardgames&#34;&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Cartographers (2019)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/263918/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/263918/image.jpg&#34; alt=&#34;Box art of Cartographers&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;4 plays&lt;/p&gt;
  &lt;/li&gt;
  &lt;li class=&#34;raised-box&#34;&gt;
    &lt;div class=&#34;image-container&#34; title=&#34;Terraforming Mars (2016)&#34;&gt;
      &lt;img class=&#34;background&#34; src=&#34;https://chameth.com/boardgames/167791/image.jpg&#34; aria-hidden=&#34;true&#34; loading=&#34;lazy&#34;/&gt;
      &lt;img class=&#34;foreground&#34; src=&#34;https://chameth.com/boardgames/167791/image.jpg&#34; alt=&#34;Box art of Terraforming Mars&#34; loading=&#34;lazy&#34;/&gt;
    &lt;/div&gt;
    &lt;p&gt;3 plays&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, finally, there’s World of Warcraft. I started playing a new class:&lt;/p&gt;
&lt;div class=&#34;wow-char-grid&#34;&gt;
&lt;div class=&#34;wow-char raised-box&#34; data-title=&#34;World of Warcraft Character Data&#34;&gt;
&lt;div class=&#34;overview&#34;&gt;
&lt;img src=&#34;https://chameth.com/wow/characters/Miraceth.png&#34; alt=&#34;Miraceth&#34; loading=&#34;lazy&#34;/&gt;
&lt;p class=&#34;detail&#34;&gt;Level 90&lt;/p&gt;
&lt;p class=&#34;detail&#34;&gt;Female Night Elf&lt;/p&gt;
&lt;p class=&#34;detail&#34;&gt;&lt;span class=&#34;wow-class-monk&#34;&gt;Mistweaver Monk&lt;/span&gt;&lt;/p&gt;
&lt;p class=&#34;detail&#34;&gt;312 average item level&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;info&#34;&gt;
&lt;h3 class=&#34;plain-header&#34;&gt;Miraceth&lt;span class=&#34;realm&#34;&gt;-Terenas&lt;/span&gt;&lt;/h3&gt;
&lt;h4 class=&#34;plain-header&#34;&gt;Professions&lt;/h4&gt;
&lt;table class=&#34;plain-table professions&#34;&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Midnight Leatherworking&lt;/td&gt;&lt;td&gt;&lt;progress class=&#34;tier-progress&#34; value=&#34;100&#34; max=&#34;100&#34;&gt;&lt;/progress&gt;&lt;/td&gt;&lt;td class=&#34;tier-count&#34;&gt;100/100&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Midnight Skinning&lt;/td&gt;&lt;td&gt;&lt;progress class=&#34;tier-progress&#34; value=&#34;100&#34; max=&#34;100&#34;&gt;&lt;/progress&gt;&lt;/td&gt;&lt;td class=&#34;tier-count&#34;&gt;100/100&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Midnight Cooking&lt;/td&gt;&lt;td&gt;&lt;progress class=&#34;tier-progress&#34; value=&#34;100&#34; max=&#34;100&#34;&gt;&lt;/progress&gt;&lt;/td&gt;&lt;td class=&#34;tier-count&#34;&gt;100/100&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Midnight Fishing&lt;/td&gt;&lt;td&gt;&lt;progress class=&#34;tier-progress&#34; value=&#34;110&#34; max=&#34;300&#34;&gt;&lt;/progress&gt;&lt;/td&gt;&lt;td class=&#34;tier-count&#34;&gt;110/300&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h4 class=&#34;plain-header&#34;&gt;Mythic+&lt;/h4&gt;
&lt;table class=&#34;plain-table mythic-plus&#34;&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Altar of Fangs&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+9&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;&lt;s&gt;31:31&lt;/s&gt;&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;273&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Den of Nalorakk&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;27:22&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;325&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Kings&amp;#39; Rest&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+8&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;25:34&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;283&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Murder Row&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;22:37&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;333&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Ruby Life Pools&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;26:39&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;322&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Temple of Sethraliss&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;27:08&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;326&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;The Blinding Vale&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;23:56&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;328&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Voidscar Arena&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;24:28&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;327&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;total&#34;&gt;
&lt;th colspan=&#34;3&#34; class=&#34;plain-header&#34;&gt;Total M+ rating&lt;/th&gt;
&lt;td class=&#34;rating&#34;&gt;2516&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&#34;links&#34;&gt;
&lt;a href=&#34;https://worldofwarcraft.blizzard.com/en-gb/character/eu/terenas/miraceth&#34;&gt;View on blizzard.com&lt;/a&gt;
&lt;a href=&#34;https://raider.io/characters/eu/terenas/Miraceth&#34;&gt;View on raider.io&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I’m still mostly playing as a healer, but this one is a melee fighter who heals while doing damage. I previously shied away from melee classes as being right on top of the enemies always felt too chaotic compared to parking at the back of the room, but I’m actually really liking it. Once you get used to it, it’s a lot easier to keep track of the fight when you’re in the middle of things instead of hanging out at the back. This spec is currently one of the flavours of the month, it’ll be interesting to see if I still enjoy it as much when it falls out of favour.&lt;/p&gt;
&lt;h3 id=&#34;around-the-web&#34;&gt;Around the web&lt;/h3&gt;
&lt;h4 id=&#34;making-an-original-jubilee-line-door-button-into-a-hue-light-switchhttpslocalghostdevblogmaking-an-original-jubilee-line-door-button-into-a-hue-light-switch&#34;&gt;&lt;a href=&#34;https://localghost.dev/blog/making-an-original-jubilee-line-door-button-into-a-hue-light-switch/&#34;&gt;Making an original Jubilee line door button into a Hue light switch&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A really cool electronics project, that does exactly what it says in the title. The finished product looks stunning, though.&lt;/p&gt;
&lt;h4 id=&#34;llamattfhttpsfugledegithubiollamattf&#34;&gt;&lt;a href=&#34;https://fuglede.github.io/llama.ttf/&#34;&gt;llama.ttf&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;“llama.ttf is a font file which is also a large language model and an inference engine for that model.”&lt;/p&gt;
&lt;h4 id=&#34;departure-monohttpsdeparturemonocom&#34;&gt;&lt;a href=&#34;https://departuremono.com/&#34;&gt;Departure mono&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;This is a really nice looking pixel font, and the web page demonstrating it is well done as well. Definitely going to be using it next time I need to render text in a small space.&lt;/p&gt;
&lt;h4 id=&#34;mitigating-floods-of-posts-in-artemishttpsjamesgblog20260530mitigating-floods-of-posts-in-artemis&#34;&gt;&lt;a href=&#34;https://jamesg.blog/2026/05/30/mitigating-floods-of-posts-in-artemis&#34;&gt;Mitigating floods of posts in Artemis&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;James has been &lt;a href=&#34;https://jamesg.blog/artemis&#34;&gt;documenting his development of Artemis&lt;/a&gt;, a ‘calm web reader’, and I’ve been enjoying reading along. This particular post is interesting to me as it deals with a common problem with RSS readers (“the website has altered their feed in a bad way, now there are 50 ’new’ posts that you’ve already seen”) in a novel way that just seems so obvious in hindsight.&lt;/p&gt;
</content>
    </entry>
    <entry>
        <title>Monthly Meanderings: April 2026</title>
        <link href="https://chameth.com/monthly-meanderings-2026-04/"/>
        <updated>2026-05-01T00:00:00Z</updated>
        <id>https://chameth.com/monthly-meanderings-2026-04/</id>
        <content xml:lang="en" type="html">&lt;p&gt;It’s apparently been a whole month since &lt;a href=&#34;https://chameth.com/monthly-meanderings-2026-03/&#34;&gt;the last edition of Monthly Meanderings&lt;/a&gt;. Not sure when that happened. This month I’ve been to a LAN party, had an MRI, and have been trying unsuccessfully to get my doctor to change a prescription. The latter two are not doing much to help shake that middle-aged feeling I mentioned last month…&lt;/p&gt;
&lt;h3 id=&#34;website-updates&#34;&gt;Website updates&lt;/h3&gt;
&lt;p&gt;Another pair of blog posts for this month: &lt;a href=&#34;https://chameth.com/the-case-of-the-unchanging-config/&#34;&gt;The Case of the Unchanging Config&lt;/a&gt; about a problem I had with a bind-mounted config in a Docker container, and &lt;a href=&#34;https://chameth.com/migrating-from-github-to-forgejo/&#34;&gt;Migrating from GitHub to Forgejo&lt;/a&gt;, a write-up of… well… migrating from GitHub to Forgejo. That one’s been on my to-do list for a while, so it’s nice to finally get it written up.&lt;/p&gt;
&lt;p&gt;It’s not visible when you look at the site, but I’ve been doing a lot of refactoring this month. The existing design had a package for database operations, a package for the stylesheet, a package for shortcodes, and so on. As I bolt more and more things on this gets less and less useful: those packages get bigger and bigger, and trying to understand how, say, the music import functionality works means hopping all over the place. Instead, I’m moving towards having each feature contained in its own package. It defines its own stylesheet, database operations, HTTP handlers, and so on. They’re then all wired together at the top layer. There’s still some more to do on that front, but it’s progressing in a good direction, and I’m happier with the code.&lt;/p&gt;
&lt;p&gt;The only actual visible change is the addition of &lt;a href=&#34;https://chameth.com/wow/&#34;&gt;a page dedicated to World of Warcraft&lt;/a&gt; that automatically imports some data from the game’s API. We’ll come back to that later!&lt;/p&gt;
&lt;h3 id=&#34;other-projects&#34;&gt;Other projects&lt;/h3&gt;
&lt;p&gt;I made a trio of new tiny projects: &lt;a href=&#34;https://github.com/csmith/pdsps&#34;&gt;pdsps&lt;/a&gt;, a reverse proxy for a Bluesky/ATProto PDS that overrides the age verification response so you don’t need to hand over ID; &lt;a href=&#34;https://github.com/csmith/irc-adze&#34;&gt;irc-adze&lt;/a&gt;, a plugin for &lt;a href=&#34;https://github.com/greboid/irc-bot&#34;&gt;irc-bot&lt;/a&gt; that receives and announces &lt;a href=&#34;https://github.com/greboid/adze&#34;&gt;adze&lt;/a&gt; webhooks; and &lt;a href=&#34;https://github.com/csmith/wow-spec-switch&#34;&gt;wow-spec-switch&lt;/a&gt;, a super-simple addon for World of Warcraft that lets you switch specialisation with a command.&lt;/p&gt;
&lt;p&gt;I’ve also been working on a custom app launcher in the style of &lt;a href=&#34;https://ulauncher.io/&#34;&gt;ulauncher&lt;/a&gt; but without requiring WebKit to render things. I’ve not yet polished it up enough to open source it, but it’s not far off.&lt;/p&gt;
&lt;h3 id=&#34;entertainment&#34;&gt;Entertainment&lt;/h3&gt;
&lt;p&gt;Another single film month for me. It was a great one, though:&lt;/p&gt;
&lt;div class=&#34;film-review-parent&#34;&gt;
  &lt;section class=&#34;film-review raised-box&#34;&gt;
    &lt;img src=&#34;https://chameth.com/films/254/poster.jpg&#34; alt=&#34;Poster for Project Hail Mary&#34; loading=&#34;lazy&#34;/&gt;
    &lt;header&gt;
      &lt;h3 class=&#34;plain-header&#34;&gt;&lt;a href=&#34;https://chameth.com/films/project-hail-mary-2026/&#34;&gt;Project Hail Mary&lt;/a&gt;&lt;/h3&gt;
      &lt;div&gt;&lt;/div&gt;
      &lt;div title=&#34;9/10&#34;&gt;
&lt;span class=&#34;star-rating&#34;&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-1&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-flat.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Full star&#34; class=&#34;rot-2&#34;/&gt;&lt;img src=&#34;https://chameth.com/star-half.png&#34; width=&#34;28&#34; height=&#34;28&#34; alt=&#34;Half star&#34;/&gt;&lt;/span&gt;
&lt;/div&gt;
      &lt;time&gt;2026-04-01&lt;/time&gt;
    &lt;/header&gt;
    &lt;div&gt;&lt;p&gt;A film adaptation of a book I loved that’s actually good. Huh!&lt;/p&gt;
&lt;p&gt;Ryan Gosling does great in what is often a solo act. Early on I thought the film was trying a bit too hard to be funny, but at some point it started working and I enjoyed it.&lt;/p&gt;
&lt;p&gt;There are lots of parallels to The Martian, Arrival, and so on, but it’s a sufficiently different mash-up of the concepts that it’s not a problem.&lt;/p&gt;
&lt;/div&gt;
  &lt;/section&gt;
&lt;/div&gt;
&lt;p&gt;I’m still enjoying &lt;a href=&#34;https://www.themoviedb.org/tv/288670-saturday-night-live-uk&#34;&gt;SNL UK&lt;/a&gt; a lot more than I expected to, although I keep forgetting what day it airs. They should put it in the name or something.&lt;/p&gt;
&lt;p&gt;The rest of my “entertainment” time has been spent playing World of Warcraft. I played an awful lot about 6-7 years ago, and have dipped in and out occasionally since. I got an MMORPG itch again recently and jumped back in, and I’m enjoying it a lot once again. I’ve been playing a healer properly for the first time (after enjoying healing in Final Fantasy XIV):&lt;/p&gt;
&lt;div class=&#34;wow-char-grid&#34;&gt;
&lt;div class=&#34;wow-char raised-box&#34; data-title=&#34;World of Warcraft Character Data&#34;&gt;
&lt;div class=&#34;overview&#34;&gt;
&lt;img src=&#34;https://chameth.com/wow/characters/Methrica.png&#34; alt=&#34;Methrica&#34; loading=&#34;lazy&#34;/&gt;
&lt;p class=&#34;detail&#34;&gt;Level 90&lt;/p&gt;
&lt;p class=&#34;detail&#34;&gt;Female Void Elf&lt;/p&gt;
&lt;p class=&#34;detail&#34;&gt;&lt;span class=&#34;wow-class-priest&#34;&gt;Holy Priest&lt;/span&gt;&lt;/p&gt;
&lt;p class=&#34;detail&#34;&gt;280 average item level&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&#34;info&#34;&gt;
&lt;h3 class=&#34;plain-header&#34;&gt;Methrica&lt;span class=&#34;realm&#34;&gt;-Terenas&lt;/span&gt;&lt;/h3&gt;
&lt;h4 class=&#34;plain-header&#34;&gt;Professions&lt;/h4&gt;
&lt;table class=&#34;plain-table professions&#34;&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Midnight Alchemy&lt;/td&gt;&lt;td&gt;&lt;progress class=&#34;tier-progress&#34; value=&#34;100&#34; max=&#34;100&#34;&gt;&lt;/progress&gt;&lt;/td&gt;&lt;td class=&#34;tier-count&#34;&gt;100/100&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Midnight Engineering&lt;/td&gt;&lt;td&gt;&lt;progress class=&#34;tier-progress&#34; value=&#34;92&#34; max=&#34;100&#34;&gt;&lt;/progress&gt;&lt;/td&gt;&lt;td class=&#34;tier-count&#34;&gt;92/100&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Midnight Cooking&lt;/td&gt;&lt;td&gt;&lt;progress class=&#34;tier-progress&#34; value=&#34;1&#34; max=&#34;100&#34;&gt;&lt;/progress&gt;&lt;/td&gt;&lt;td class=&#34;tier-count&#34;&gt;1/100&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Midnight Fishing&lt;/td&gt;&lt;td&gt;&lt;progress class=&#34;tier-progress&#34; value=&#34;4&#34; max=&#34;300&#34;&gt;&lt;/progress&gt;&lt;/td&gt;&lt;td class=&#34;tier-count&#34;&gt;4/300&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h4 class=&#34;plain-header&#34;&gt;Mythic+&lt;/h4&gt;
&lt;table class=&#34;plain-table mythic-plus&#34;&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Algeth&amp;#39;ar Academy&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;20:41&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;332&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Magisters&amp;#39; Terrace&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;24:58&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;330&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Maisara Caverns&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+11&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;29:31&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;339&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Nexus-Point Xenas&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;25:08&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;326&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Pit of Saron&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+11&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;29:32&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;336&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Seat of the Triumvirate&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+11&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;31:12&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;338&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Skyreach&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+10&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;19:21&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;332&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&#34;dungeon&#34;&gt;Windrunner Spire&lt;/td&gt;
&lt;td class=&#34;level&#34;&gt;+12&lt;/td&gt;
&lt;td class=&#34;duration&#34;&gt;19:09&lt;/td&gt;
&lt;td class=&#34;rating&#34;&gt;380&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;total&#34;&gt;
&lt;th colspan=&#34;3&#34; class=&#34;plain-header&#34;&gt;Total M+ rating&lt;/th&gt;
&lt;td class=&#34;rating&#34;&gt;2713&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&#34;links&#34;&gt;
&lt;a href=&#34;https://worldofwarcraft.blizzard.com/en-gb/character/eu/terenas/methrica&#34;&gt;View on blizzard.com&lt;/a&gt;
&lt;a href=&#34;https://raider.io/characters/eu/terenas/Methrica&#34;&gt;View on raider.io&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;around-the-web&#34;&gt;Around the web&lt;/h3&gt;
&lt;h4 id=&#34;git-history-documentationhttpsgit-scmcomdocsgit-history&#34;&gt;&lt;a href=&#34;https://git-scm.com/docs/git-history&#34;&gt;git-history documentation&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The latest release of git added this handy-seeming history subcommand. It allows you to reword a commit, or split a commit up interactively, without having to go through the interactive rebase song and dance.&lt;/p&gt;
&lt;p&gt;Git has acquired a bunch of new commands in recent releases, but I tend to ignore them in favour of the old ones I know well. This one will definitely get use from me, though.&lt;/p&gt;
&lt;h4 id=&#34;git-koans-by-steve-loshhttpssteveloshcomblog201304git-koans&#34;&gt;&lt;a href=&#34;https://stevelosh.com/blog/2013/04/git-koans/&#34;&gt;Git Koans by Steve Losh&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Sticking with the same theme, this series of koans about Git and its various quirks had me laughing. If you’re reading this blog, you’re probably part of the target audience for it.&lt;/p&gt;
&lt;h4 id=&#34;sqlite-prefixes-its-temp-files-with-etilqs-httpsaviimblag2026etilqs&#34;&gt;&lt;a href=&#34;https://avi.im/blag/2026/etilqs/&#34;&gt;SQLite prefixes its temp files with etilqs_&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;I think I’d come across this before, but it’s still a fun anecdote. It reminds me of &lt;a href=&#34;https://daniel.haxx.se/email/&#34;&gt;Daniel Stenberg’s e-mail collection&lt;/a&gt;; as the author of curl his name is listed on basically everything with the inevitable influx of people contacting him.&lt;/p&gt;
&lt;h4 id=&#34;a-dot-a-day-keeps-the-clutter-awayhttpsscottlawsonbccompostdot-system&#34;&gt;&lt;a href=&#34;https://scottlawsonbc.com/post/dot-system&#34;&gt;A dot a day keeps the clutter away&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;I love this approach to tracking how frequently things are used, and the weird sort of archaeological record you get from rotating the colours of the stickers used.&lt;/p&gt;
</content>
    </entry>
    <entry>
        <title>The Case of the Unchanging Config</title>
        <link href="https://chameth.com/the-case-of-the-unchanging-config/"/>
        <updated>2026-04-09T00:00:00Z</updated>
        <id>https://chameth.com/the-case-of-the-unchanging-config/</id>
        <content xml:lang="en" type="html">&lt;p&gt;Last week I was attempting to make it so I could share pictures on IRC directly from my client. This sounds simple, but it involves a bouncer&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that proxies the request to a standalone image hosting service that I had to modify to be compatible. At one point my testing loop was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make a change to the hosting service&lt;/li&gt;
&lt;li&gt;Commit it&lt;/li&gt;
&lt;li&gt;Tag a new release&lt;/li&gt;
&lt;li&gt;Wait for it to build&lt;/li&gt;
&lt;li&gt;Update the version I’m running on my server&lt;/li&gt;
&lt;li&gt;Reconfigure the bouncer to pass the new parameter or change the URL or whatever&lt;/li&gt;
&lt;li&gt;Try uploading a photo from my phone&lt;/li&gt;
&lt;li&gt;Realise I’ve overlooked something and go back to step one&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I &lt;em&gt;could&lt;/em&gt; have set up a local copy of everything and tested it sensibly. I &lt;em&gt;should&lt;/em&gt; have set up a local copy of everything and tested it sensibly. But it seemed like such a trivial change, and setting up the whole environment seemed like such a pain. After the third or so iteration of failure I was pretty annoyed with myself, computers, and basically everything.&lt;/p&gt;
&lt;p&gt;My biggest annoyance was that my bouncer would not pick up the new URL from the config when I changed it. It’s meant to reload the config when it receives a &lt;code&gt;SIGHUP&lt;/code&gt;, and it claimed to in the logs, but I could clearly see it was still hitting the old URL. Restarting the bouncer to update the config is a pain, as it disconnects me from all the IRC networks, and has to reconnect to them all, reauthenticate, etc. It also mildly spams everyone who shares a channel with me&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;When I finally got everything working I had a look at the bouncer source, and thought I’d spotted the issue. I raised a bug report, ending in this remark:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It looks like the config is reloaded properly, but the handler for uploads is created once at startup and has its own copy of the uploader, so effectively snapshots the config to whatever it is at startup:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nx&#34;&gt;fileUploadHandler&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;HandlerFunc&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;ResponseWriter&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Request&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;srv&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;Config&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;fileupload&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Handler&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Uploader&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;FileUploader&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;DB&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Auth&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;Auth&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;HTTPOrigins&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;HTTPOrigins&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;chroma-nf&#34;&gt;ServeHTTP&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;chroma-nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;chroma-w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I’m not quite sure what I thought I saw there, but that’s not right. I want to call it a hallucination, but that feels like a very overloaded word these days. I guess I was just seeing what I wanted to see, and that was a good excuse to stop investigating. Still, even if I misidentified the cause, the bug was still present, right? … Right?&lt;/p&gt;
&lt;h3 id=&#34;the-science-of-deduction&#34;&gt;The Science of Deduction&lt;/h3&gt;
&lt;p&gt;When the developer got back and said he couldn’t reproduce my issue, I went back to my install and immediately reproduced it. With a calmer head, I figured it was probably something with my particular set up. First thing to check: am I actually running the version I think I am?&lt;/p&gt;
&lt;p&gt;Unfortunately the bouncer doesn’t actually seem to expose the version anywhere that I can see. It’s not in the logs, it doesn’t have a &lt;code&gt;-version&lt;/code&gt; flag, and none of the IRC-based status commands seem to include it. But I know it’s a Go app, and I know Go embeds the version information. &lt;code&gt;go version -m &amp;lt;binary&amp;gt;&lt;/code&gt; will dump it all out, but the binary is inside a Docker image, and the Docker image is one of my &lt;a href=&#34;https://chameth.com/artisanal-docker-images/&#34;&gt;nice, minimal, artisanal ones&lt;/a&gt; so doesn’t ship a &lt;code&gt;go&lt;/code&gt; binary. No problem, &lt;code&gt;docker compose cp bouncer:/bnc ./bnc&lt;/code&gt; yoinks the binary out of the container, and then dumping the version shows that, yes, I am running the version I thought I was. Hmm.&lt;/p&gt;
&lt;p&gt;The next tool I reached for in &lt;a href=&#34;https://chameth.com/debugging-beyond-the-debugger/&#34;&gt;my toolbox&lt;/a&gt; was &lt;code&gt;strace&lt;/code&gt;. Maybe it’s not actually reading the file for some reason? I immediately executed &lt;code&gt;strace -p &amp;lt;pid&amp;gt; -e trace=openat,open,read,pread64 -f&lt;/code&gt; without having to look any part of that up. Yep. Definitely. Then I edited the config, &lt;code&gt;HUP&lt;/code&gt;’d the bouncer, and saw that it was… reading the config file. As it’s meant to. By default &lt;code&gt;strace&lt;/code&gt; truncates strings to 32 bytes, so I couldn’t actually see the line I’d changed. Some more definitely-not-RTFMing later, and rerunning it with an extra &lt;code&gt;-s 65536&lt;/code&gt; let me see the full config. Surprise! The config hadn’t changed!&lt;/p&gt;
&lt;p&gt;To confirm my findings, I used &lt;code&gt;docker compose cp&lt;/code&gt; again, this time yoinking the config file from inside the container. The inside config file was definitely different to the outside config file. What? My hypothesis at this point was “something something Docker nonsense”. I mount the config as read-only, and was wondering if that meant that Docker was doing something &lt;em&gt;weird&lt;/em&gt; instead of just bind mounting it. A quick trip to &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/mounts&lt;/code&gt; showed that it was, in fact, not doing anything weird, and was just bind mounting it.&lt;/p&gt;
&lt;p&gt;If the file is bind mounted, then surely it’s the same file? I ran &lt;code&gt;stat&lt;/code&gt; on the file on the host, noted the inode number, then pondered how to actually run &lt;code&gt;stat&lt;/code&gt; on the file inside the container, given the aforementioned awkwardly minimal image. The solution was easy: access it via &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/root/&lt;/code&gt;. I could’ve saved myself a bunch of &lt;code&gt;docker compose cp&lt;/code&gt; if I’d thought about that earlier. Oh well. The inode of that file was different. What?&lt;/p&gt;
&lt;p&gt;The answer was DNS. It’s always DNS. Oh, sorry, force of habit. I meant the answer was &lt;em&gt;vim&lt;/em&gt;. I was editing the config in vim, and when it saves files, by default, it writes the new content to a temporary file and does an atomic rename. That’s normally a good thing: it prevents corruption if the write fails midway through&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. In this case, though, that means the new file has a new inode. Bind mounting a file binds to the inode, so the container just keeps clutching onto the original config from when it was started, blissfully unaware that the party has relocated down the street.&lt;/p&gt;
&lt;p&gt;This problem is likely to happen whenever you bind mount a file into a container. When I mentioned this to a friend, he immediately responded “oh yeah, never do that”, and went on to describe the horrible hacks he’s had to add to Ansible to sidestep the issue. The nicer solution is to just bind mount an entire directory if you can, as then it doesn’t matter what happens to the files within it. I really like having the config files sat alongside the Docker compose files, though; having to create a directory just to work around some bind mount weirdness upsets me.&lt;/p&gt;
&lt;p&gt;Now I knew what the problem was, I found there was an issue &lt;a href=&#34;https://github.com/moby/moby/issues/6011&#34;&gt;raised against Docker&lt;/a&gt; twelve years ago&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. The first response was “that’s expected”, and I fully agree with the author: “respectfully, that might be expected by you, but it was not expected by me”!&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:5&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; As a result of the issue they &lt;a href=&#34;https://github.com/moby/moby/pull/6854/changes&#34;&gt;added a nice note to the docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;
Many tools used to edit files including &lt;code&gt;vi&lt;/code&gt; and &lt;code&gt;sed --in-place&lt;/code&gt; may result
in an inode change. Since Docker v1.1.0, this will produce an error such as
“&lt;em&gt;sed: cannot rename ./sedKdJ9Dy: Device or resource busy&lt;/em&gt;”. In the case where
you want to edit the mounted file, it is often easiest to instead mount the
parent directory.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But in the intervening twelve years, both the note and the functionality described have gone missing. Ho-hum.&lt;/p&gt;
&lt;p&gt;I found it interesting how I’ve only just hit this problem, given how long I’ve used Docker. But I realised that almost everything I run I’m happy to just restart. Cattle, not pets, and so on. My IRC bouncer is one of the few exceptions to that. The only other thing I regularly hot reloaded was &lt;a href=&#34;https://github.com/csmith/centauri&#34;&gt;Centauri&lt;/a&gt;, my reverse proxy, but that had a whole config directory mounted because it was shared between containers, so nicely sidestepped the foot-gun.&lt;/p&gt;
&lt;p&gt;So lesson learnt: check for weird bind mount issues before raising issues about config hot reloading. It’ll join the esteemed company of “maybe the drive is full and causing completely unrelated problems?”, “perhaps everything is dog slow because the kernel ran out of entropy?”, and “did systemd sneakily take over that functionality while you weren’t looking?” in the troubleshooting checklist.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;An IRC bouncer is basically an always-on proxy. It connects to the IRC networks for you, then your clients connect to your bouncer. The bouncer can then send incoming messages to all your different clients, cache them when you’re offline and replay them later, and lots of other nice things people take for granted in their chat apps these days. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Join/part/quit spam is part of IRC, and clients have ways of handling it, but I still &lt;em&gt;feel&lt;/em&gt; bad about doing it excessively. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;You can disable this in vim by setting &lt;code&gt;backupcopy=yes&lt;/code&gt;. Good luck redoing all the debugging if you ever accidentally remove that from your &lt;code&gt;vimrc&lt;/code&gt;, though! &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;Which is weird, because Docker can’t possibly be that old. That would make me much older than I’m prepared to accept. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;If you read the thread they didn’t actually mean to sound so dismissive, but it’s still pretty funny. “Yes, it’s expected that the foot-gun causes your foot to hurt. Duh. What did you expect?” &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:5&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
</feed>
