Latest Posts (20 found)

I was wrong about dark mode

Apple invented dark mode again! They’re calling the new feature Adaptive Dark Mode ™ . I’m learning that other browsers already had a similar feature . I assumed people relied on add-ons like Dark Reader (and millions do). Regardless, pushing what is effectively an accessibility option to the forefront goes a long way to help people respect it. My site used to have dark mode but I wasn’t happy with my colour choice and implementation. I redesigned my site back in Feb and removed it. Later I experimented with colour themes which I’ve since adapted for alternate blog banners . In that experiment I said: Personally I prefer reading with a dark theme for long-form content. Dark is not my brand though and I don’t believe every website needs to support colour scheme preference automatically. A good browser has reader mode, I use that all the time. “Just enabled reader mode!” What a load of rubbish. You have to understand that sometimes I just write stuff and I don’t read it back. This also contradicted my previous statement: That said, in [current year] brands with a digital presence — all of them? — should have a colour scheme for both. That’s probably more true. Certainly websites must work for forced dark mode. (Any site forcing itself upon users should go straight to jail.) So either provide a dark mode, or be prepared to test against every adaptive and forced dark mode implementation, or just let your website look broken. Probably easier to design a dark mode then? So that’s what I did, again. Maybe you noticed? If not, there’s a toggle button up top. At the risk of weighing in on the conversation I’ve opted for a two-state single button. I default to system preference. The mystery button flips between light and dark. Want to return to system default? Tough luck! You shouldn’t have clicked it. Delete local storage nerd! Technically I’m using a registered custom property with container style queries because it looks like fancy modern CSS. Three at-rules! Most of my light/dark theming is done by customs properties. The default light colours (A) are set on the root element. A style query applies the dark colours (B) on the element. For edge cases that don’t follow the A/B rule I can use one of two techniques. Either a style query inside a specific component: Or use the function: Note that I set to make aware of the user’s choice that might override their system preference. I wonder if I can do this once: Is that safe? Let me know! It’s not safe to do on the root element because browsers checks that for adaptive dark mode etc. I’ll stick to the verbose style queries for now until I’m sure. There’s a sprinkle of JavaScript to use local storage for the toggle button. It’d be better to use a cookie and a server-side template. My website is hosted on a static CDN and I’m not adding edge worker latency, so I’ll risk Flash of inAccurate coloR Theme (FART) . My site only risks a FART if a user’s choice doesn’t match their system preference. A week into my Noscript November experiment I see most websites are prone to a big FART because they rely entirely on JavaScript to enable dark mode. I’m not happy with my button using a hidden label. Bit of a design failure I need to address later. My original goal was to have multiple themes but I ran out of time. At the risk of soliciting replies to a question I didn’t ask, I would like your feedback on my choice of dark mode colours! Do they look nice? I don’t care whether dark mode makes your eyes bleed (turn it off) or you absolutely must have a 4-dimensional toggle. Stephanie Eckles wrote about the same CSS technique I use. Dave Rupert has explored the with variations. I saw Una Kravets is using with style queries to do something extra fancy. Stu Robson has written in depth on dynamic colour schemes too. If you read this entire post in an RSS reader and never clicked through, what a power move! Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds.

0 views

2026-11-09 09:50: Just another day in Casa Quirk. 🤣

Just another day in Casa Quirk. 🤣 Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment .

0 views

A Small Non-Update

Just wanted to mention why the blog suddenly stopped. It’s been updated on the 12th of every month for a couple of years, after all. Some of you are regular readers! First of all, it will be coming back! Probably not until 2027, but I have more than 100 work in progress posts to wrap up and get out there. Among those, a rework of An Object-Rich World that adds two other broad categories of systemic architectures, including sample code, and turns it into a series of three posts instead of one. I also have a finished post on why generative Ai is a poor match with systemic design and architecture that I really want to get out there. But all of that will have to wait, because I recently started working fulltime on my own project, on my own dime. So I don’t quite have the spare time needed to research and write the kinds of posts I like writing. There are already enough new things to talk about after just a couple of months of doing my own thing, so the writing will definitely resume at some point. Hopefully, you all keep hanging around until there’s more to share! Check out the Blog Guide to see if you missed anything. This blog is still the foundation for a lot of the things I do as well as a wider conversation on systemic design in games. Catch you on the flip side!

0 views

May cause respiratory effects

It's 11pm on a Sunday as I write this. It's Diwali. I'm visiting my parents in Delhi, as I've done every year since I moved away over a decade ago. I'm feeling a certain way right now. I'm writing this post so I don't forget this feeling. The fireworks outside are so loud that it feels like the world is ending. They've been going off for three or four hours now. I'm cowering inside my bedroom, thinking they will stop any moment now. But they just won't stop. There are so many of them. Why won't they stop? Aren't the people of this city tired of setting fire to the world yet? How have they not run out of things to burn? How many more hours of fire and brimstone until their hunger is sated? Our dogs are afraid. They've been trying to burrow into the sofa cushions since the first sporadic sounds of fireworks began at sunset. How must the animals on the streets feel right now? I know from living here for the first twenty-two years of my life that many animals won't make it to tomorrow morning. Many will be badly maimed. Many will perish out of fear. There will be fewer birds in the city for a few days. Oh, and then there's the main event: the smoke. So much smoke . My weather app tells me the AQI in my area is 500. Then it goes on to say, rather matter of factly: May cause respiratory effects even for healthy people and serious health impacts for people with lung/heart diseases. The health impacts may be experienced even during light physical activity. Here's the dataviz, just to drive the point home: This is, somehow, considered just fine around these parts. Normal. Unremarkable. I wore a mask when I went outside during the day. Here's what my mask looked like when I got back in the evening (top), compared to a fresh mask I just opened (bottom): See that gray? There's so much smoke outside that I can't see across the street. The air purifier is barely able to keep the indoor AQI at 250: I've never had any love for Diwali. I only visit my parents during this time because, well, it's Diwali. They want me here, I love them, and I like to see them happy. But as I see the people of Delhi set fire to years of their lives—and the lives of those who did not consent to being part of their so-called celebrations —I can't help but wonder if these trip are worth it anymore. I've had respiratory issues ever since I got COVID in 2020. I've spent lakhs of rupees on doctors, medication, masks, air purifiers, and treatments. I'm always masked up in crowded spaces. I take my flu shots regularly. I've stopped going to gigs and concerts because there are too many smokers at these events. What I mean to say is: I've been careful with my health, but it feels like I'm throwing it all away by choosing to be in Delhi tonight. I don't know how much of my life and health this trip will steal from me. I don't know if I'll fall sick while I'm here, and in what way. I'm afraid of going to sleep, because who knows what state my respiratory system will be in when I wake up tomorrow. My nose is already stuffy. I can already feel the post-nasal drip creeping up on me. My eyes and throat are already burning. In the afternoon I bought a fresh can of pressured saline wash to clean out my nasal cavity, but I left it in the car. The car is outside. I can't go outside. Outside is where the air purifier isn't . I'm afraid of even going to the bathroom, because the air purifier can't clean the air in there. I'm so afraid for my health right now. What's wrong with this city? I don't understand. I was born and raised here. I lived here for twenty-two years. But I just don't get it. What am I missing? Why is this happening? Why are people doing this? I feel so powerless and angry, hiding indoors, waiting out armageddon. There's nothing I can do to stop people from poisoning the air. Worse, there's nothing I can do to save myself. No amount of money or technology or medicine can save me when the air is made entirely of smoke. I just want to go home. I just want to be in Bangalore, where it's safe. I wish I hadn't come to Delhi at all.

0 views

Thunderstruck

Luke 10:17-18 (KJV), "And the seventy returned again with joy, saying, Lord, even the devils are subject unto us through thy name. And he said unto them, I beheld Satan as lightning fall from heaven ." Even Satanists seem to be aware of this as shown by Anton LaVey, founder of the Church of Satan. He used to wear a inverted pentagram with a lighting bolt inside it. Anton LaVey, founder of the Church of Satan, wearing horns and his personal Sigil. Anton LaVey wearing his Sigil at another moment (source: fandom.com ). This jewel is known as the Church of Satan Sigil of Anton LaVey and can even be bought on Amazon. Amazon sales advertisement image for the Church of Satan Sigil of Anton LaVey (source: Amazon). Together with his Sigil, he was also a big fan of the Sign of the horns, LaVey doing the Sign of the horns (source: RollingStone ). In an interview, LaVey explained that "This is the Sign of the horns. A curse sign. Two fingers extended." LaVey explaining that the Sign of the horns is a curse sign (source: YouTube ). As a side-note, in the same interview he explains that you "have to be bad to be good" and when the interviewer asks "Is it true you teach people how to make voodoo dolls and stick pins in them to hurt people", LaVey answers, "That's right. I believe that hate is necessary in a controlled way just as much as love is necessary." Isaiah 5:20 (KJV), "Woe unto them that call evil good, and good evil; that put darkness for light, and light for darkness; that put bitter for sweet, and sweet for bitter!" In the book The Secret Life of a Satanist (1990) about Anton LaVey written by Blanche Barton, LaVey goes one step further and claims, "In reality, our mainstay influence is just the tip of the iceberg. For every one of those kids you see at rock concerts holding up the Sign of the horns and not knowing anything about it, there are maybe five more kids who have read the The Satanic Bible, know what Satanism is, and are growing up to the stage now where they are gaining positions of power and having a real effect in the world around us. In the motion picture industry, the arts, business, academia, politics — they don’t wear it on their sleeves so you probably wouldn’t even know they were Satanists." Even though Satanists lie often, this statement seems to be true -to hopefully be continued-

0 views

The web used to belong somewhere

There are a couple of related ideas I found myself writing about here and there, either on the IRC server or on Quxnet . Ideas and thoughts about what the web used to be, how it used to feel, about how things have changed, and what can be done to bring at least some of the old web back. We often point fingers at the big social media platforms, with their relentless algorithmic curation as the cause for the awfulness of the modern web. Maybe we blame the SEO folks, or the influencers. There’s a multitude of different characters who each played their role in turning the “old web” into “today’s web”. And I’m not going to pretend they didn’t play a role, because they did. What I am going to argue for, though, is that there’s something else that has contributed a lot more to the changes that we’ve seen over the past few decades, and that’s the fact that the web used to belong somewhere. And I mean that in the physical sense. I’m not the first one to say this; you can find plenty of other posts out there making this exact point. But it’s a point worth repeating. There was a time when in order to go online you had to physically go to a specific place: a desk in the house, an internet cafe, a computer lab. The web wasn’t always with you, and you were not always in it and with it. You had to go somewhere, do what you needed to do, and then leave. BRB? AFK? Acronyms from what feels like a distant past. You are never away from the keyboard today, because there’s a phone following you around everywhere all the time. The internet feels exhausting these days because it’s always there. It’s become what feels like an inescapable presence, for better or for worse. I’ve been particularly unhappy over the past few weeks, for a variety of reasons. And one thing I noticed is that every time I’m in a down phase of life, I can go look at my phone usage and it’s inevitably higher than it should be. At least for me, there’s a 1:1 correlation between how good I feel and how much of a presence the phone is in my life. And the phone is just a proxy for the web because browsing is 95% of what I do on my phone, since I don’t game and I don’t have social apps. And that’s quite ironic considering I make websites for a living. I build for the web, and at the same time, spending more time than necessary on it makes me terribly unhappy. But it feels different when I can leave the web behind, when I can get up from my chair, leave my desk, and the web is no longer with me. That feels healthier. The problem is that most of the world doesn’t work that way anymore. If you leave and come back, you’ll probably have a hard time catching up with everything that has happened while you were away because everybody else is not taking a break. And so even if we managed somehow to get rid of algorithmic curation and infinite scrolls and all those things, the web would still not feel like the “old web”. Because the web used to belong to a place. It sadly doesn’t anymore. Thank you for using and believing in RSS. You're awesome. Send me an email :: Sign my guestbook :: Become a supporter

0 views
James Stanley Yesterday

How to make a CNC plasma cutter with high-frequency start

The issue with high-frequency start ("HF start") on plasma cutters is that igniting the pilot arc creates an enormous amount of electromagnetic interference which causes problems for nearby electronics, in particular tending to disconnect the USB connection to your CNC motion controller. You can solve this by talking to the motion controller over WiFi. This is my machine in its temporary location on the workbench: If you still need to acquire a plasma cutter then I obviously recommend trying to get one with "blowback start" instead of "HF start" so that you don't have to worry about it. In acquiring a machine with HF start I made an error. But I got it to work anyway, so if you already have an HF start machine and you want to use it for CNC, you might be able to do the same thing. I kind of wanted a CNC plasma cutter because I have recently spent lots of time and end mills making planar parts like this for my cyclecar at excruciatingly low feed rates and depths of cut: So I started placing some bids on eBay and accidentally won a Parkside PPS 30 B2 for £80: The seller stated that he bought it from Lidl on a whim but then realised it needed a compressor and he didn't have one. If I had done my research I would not have bid on a machine without blowback start, but here we are. So when it arrived I set it up on the garage floor and tried to run my 6040 CNC router while lighting the plasma arc. As you might predict, I couldn't get it to work. Most of the time lighting the arc would disconnect UGS from Grbl. But serendipitously I was running a test program that had a small number of very long and very slow movements, which meant I had several minutes of motion left in Grbl's buffer, and I noticed that even though UGS got disconnected from Grbl and was totally broken... Grbl was still moving the machine! It was still following my gcode perfectly. So the issue isn't that the HF start actually prevents Grbl from working, it's just that it disconnects the USB serial connection . Solution: use FluidNC on an ESP32 board and talk to it over WiFi. FluidNC is Grbl-compatible but suitable for more modern microcontrollers. Charlie suggested talking to the controller over fibre optic, which would probably work even better than WiFi but is more faff to set up. Annoyingly, ESP32 outputs are 3.3v instead of 5v. I made up a breakout board for an Arduino Nano ESP32 on a piece of stripboard with a 74HCT245 abused as a level-shifter on the outputs and this was sufficient to provide 5v output from 3.3v input, unsure how legit this is. You can see my custom breakout board in here: I initially experimented with some small stepper motors with A4988 drivers, left over from when I dismantled my i3 clone 3d printer , and I think these would have been fine. But I also had some spare Leadshine easy servos from the unfinished CNC mini mill project I bought years ago. I didn't think the Leadshine servos would work because I thought the interference would confuse the encoder input, but I tested them and they seemed fine. And they're more powerful and better so I used them. But for a plasma cutter I really don't think fancy servos are necessary, the cutting load is basically zero, it's just a glorified pen plotter. The Leadshine documentation says they need at least 18v, but I only had a 12v power supply lying around so I tried it and it seems fine. I did subsequently connect the alarm output from the servo to an input on FluidNC and it kept falsely triggering every time the arc started (sigh) so I am running the servos without the alarm input. That's no worse a position than I would have been in using open-loop stepper motors anyway. My motion platform is made out of box section steel and 3d printed parts: With GT2 belts left over from the same 3d printer, and printed pulleys. The bigger of the two orange parts was a 36-hour print and used 700g of material, that's my biggest ever 3d print. All printed in ABS-GF on Bambu Studio "strength" setting. The "linear rails" consist of 608 bearings riding directly on the steel box section. I didn't make any of them adjustable but I should have done because it has slightly more play in it than I would prefer. Again this would be nowhere near adequate for a router but for a plasma cutter I think it should be fine. (The aluminium foil wrapped around the torch lead was a shielding experiment from before I was using FluidNC, it didn't help, I just haven't taken it off yet). I need to get a larger water tray. To fire the arc on the plasma cutter, it has a 2-pin GX16 connector which you normally connect to the torch trigger. I connected it to a relay operated by FluidNC so that I can turn it on/off with M3/M5. (But note you need to give FluidNC a non-zero "spindle speed" else M3 won't do anything, so get used to "M3 S1"). So at this point I have a working CNC motion system, with an HF start plasma cutter that I can fire from CNC control. Time to cut something out? Unfortunately I have no plasma cutting experience and (story of my life) it's harder than it looks. I messed around with the air pressure, current setting, and feed rate, and I just couldn't get a clean cut in 4mm steel. That's disappointing because the machine documentation says it can cut through up to 12mm steel. But I tried a piece of 3mm steel and my first test cuts worked a lot better, so I tried to cut out a heart shape. (I got ChatGPT to write the gcode for this). It blew the fuse in the extension lead 3/4 of the way through because I foolishly had the compressor and plasma cutter plugged into the same one. D'oh! I moved the compressor to a different extension lead and then it worked first time, albeit with a lot of slag on the underside of the cut: I cleaned this off with the angle grinder and you can see that along one axis my machine is cutting nice straight sides but on the other axis it is quite badly bevelled: I am going to replace the consumables in the torch when they arrive, and failing that I am going to make the torch clamp angle adjustable, and I expect I will be able to get rid of the bevel, but I don't know if the combination of the excessive bevel and the poor cutting performance are evidence that my machine is actually faulty. This may be the real reason it was sold on eBay after hardly any use. I have subsequently had my 16 amp breaker trip a few times while cutting so I may want to either find a way to run the compressor on a different circuit or else upgrade the circuit I have. These are the switches and lights on my control cabinet: Though the "alarm" light is now surplus as the servo drives can no longer trigger it. The "torch" light comes on when FluidNC is asking for the relay to turn on, but the relay doesn't actually turn on unless the switch is set to "enable". This is so I can ensure that even when I'm fumbling the gcode the torch can't light unexpectedly. And btw I obviously don't have an emergency stop button on this machine, but I can still toggle the torch off, or switch the power off, and there's no world in which I'm running a plasma cutter unattended, so I'm not very worried about it. I also don't have limit switches. I don't really understand why people bother putting limit switches on DIY machines (unless they have an automatic tool changer or whatever). What's the benefit to having a global coordinate system? You need to set up work coordinates for every job anyway. I don't have limit switches on any of my machines, but on this one in particular I expect limit switches would falsely trigger whenever the HF arc starts. So what have we learnt about making CNC plasma cutters? you can use a HF start machine if you talk to the motion controller over WiFi you don't want any inputs to the microcontroller, including limit switches, emergency stop, alarm inputs, USB serial if you make linear rails out of box section and ball bearings then make some of them adjustable so that you can take out the play GT2 belts are adequate 3d-printed structure is adequate getting correct cutting parameters is not any easier than it is with milling unfortunately, so still lots to learn Leadshine easy servos appear to work with 12v supply even though the documentation says they need at least 18v I made a custom gcode sender to talk to FluidNC because I wanted it to work differently to any existing ones and coding agents make software cheap, but I'm pretty sure UGS can talk over WiFi so it is not necessary to make your own sender. If you make a CNC plasma cutter using information from this post then please email me and show me, I'd love to hear from you. you can use a HF start machine if you talk to the motion controller over WiFi you don't want any inputs to the microcontroller, including limit switches, emergency stop, alarm inputs, USB serial if you make linear rails out of box section and ball bearings then make some of them adjustable so that you can take out the play GT2 belts are adequate 3d-printed structure is adequate getting correct cutting parameters is not any easier than it is with milling unfortunately, so still lots to learn Leadshine easy servos appear to work with 12v supply even though the documentation says they need at least 18v

0 views
Kev Quirk Yesterday

Does Money Buy Happiness?

by Tuesdays Child Tuesdays Child talks about how they think money = happiness. It's an interesting debate with people on both sides of the fence. Read post ➡ I found this post via this reply on Bubbles. Both are interesting takes, for different reasons. For me, money doesn't buy happiness - it buys the removal of financial stress. What actually makes you happy is the people around you and the decisions you make with whatever you have. I've kinda talked about this before in my when was I happiest post. I grew up in a very poor home. After my mum and dad split up, our house was repossessed and we ended up living in a rough area surrounded by gangs, drug addicts, and prostitutes. My mum worked every hour god sends as a cleaner just to make ends meet, while I looked after the younger kids. We didn't know if the bills would be paid and were stuck in a cycle of weekly payday loans for years . On paper, that doesn't sound like a happy existence, right? But we were happy. As we settled in, we became part of the community, and watching all the street drama - like the regular fights and arrests that are common in rough areas - just became part of the entertainment, like watching a live episode of Jerry Springer . It wasn't all sunshine and roses, though. I remember one time when the addict that lived opposite us came busting into our flat screaming that her baby had died and she needed to call an ambulance. Back then there were no mobile phones, and our landline was in mum's bedroom. So mum obviously let her go call an ambulance. Turns out her baby was fine, but she knew our phone was in mum's bedroom and she took the opportunity to steal all mum's jewellery for drug money. Still, most of my memories from back then are good ones. I fondly remember sitting with my mum on a Friday night watching a stream of comedy shows like Cheers and Frasier . Once in a blue moon she'd say, "do you fancy a pizza?" Obviously I did! I'd jump on my bike, cycle to the local takeaway, and get us a 10" pizza to share because that's all we could afford. My god, were we happy. We were together, money be damned. Today, my life is the polar opposite of how I grew up. We live in a lovely quaint village in the Welsh countryside and I earn a very good living. Having money solves certain problems - like not having to worry about paying the bills or putting food on the table - which certainly makes life easier, even if it doesn't automatically make you happy. It still comes down to the choices you make along the way. Four years ago, we bought our dream home in the country. It's an old place that needs a ton of renovation work, which comes with big bills of its own. Had we stayed in our old house, we could have been mortgage-free by now. Would having no mortgage have made us happier? Probably not. That house was too small for us, right in the middle of a large town, and it felt claustrophobic. Taking on the bigger financial commitment gave us the lifestyle we actually wanted. I am happy today, but not because of what I earn. I'm happy because I'm surrounded by the people I love, living the life we want to live. On the other end of the scale from my childhood, you could have a millionaire who's completely alone and tries to fill that void with things - nice watches, nice cars, a nice home. With no-one to share it with, it's all just meaningless stuff. I think of it like a see-saw. The sweet spot is somewhere in the middle: having enough money that you don't have to worry about the basics, without spending your whole life climbing the ladder and missing out on time with the people who matter. That's a big part of why I stepped down last year . Money doesn't necessarily mean happiness. It can certainly help, though. Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment .

0 views

Musically Horizontal Monads

Sorry for the delay! My household has had a couple of chaotic days of everyone-being-very-very-sick and house guests and 4:30am airport drop-offs. But we’re back! This blog post got written in several parts over several days, so it might be a little disjointed. Apologies. In the last post we discussed a way of attaching durations to instantaneous musical events: just wrap them with a duration, stupid! Then we wrote a function which takes an event stream of stopping points, as well as an event stream of starting points 1 . It then spans each starting event until the next stop or start. The stops are there so that we can add silence if necessary. Having a function like around is cool because it means we can just emit starting and stopping events from whatever upstream process we might like, and it will automatically turn them into “rhythm.” And of course, that upstream process is welcome to derive the stop stream from the start stream (or vice versa), if you don’t want to have to think about it. Anyway, that’s all and good. But musicians have a lot of intellectual moment behind not thinking about music that way. Instead they usually just think of having durations intrinsically attached to notes — that is to say, they conceive of the note and the duration at the same time. Since we have an explicit desideratum here of making it easy to get good results, we ought to provide some more conventional ways of thinking about writing lines. Writing lines is usually done as “pick a note and a duration, then after that duration, pick another note and another duration. Rinse and repeat.” Of course, there’s some wiggle room where you can instead choose to not select a note and leave silence in its place. Depending on how functional-programming-pilled you are, this either sounds like a monoid or a monad. Let’s explore both ideas, starting with the monoid. A horizontal monoid seems like it ought to sequence one piece of music before another, in a temporal-sense. That is, should play , and then once is done, it should play . Recall that already has instances, but looking at the type, they necessarily can’t be the right thing: If you’re unfamiliar with the newtype, it lifts the and instances over the structure. That is, we’ve effectively derived the following instances: The clue that this is doing the wrong thing for our purposes is the constraint. If we were putting one after another in time , we’d never need to combine s, and thus we would have no need for a instance. Thus, this instance must be doing a point-wise zip of times. Which is all to say that this is no good to us. What we instead must do is build a wrapper type which provides the correct temporal-sequencing monoid. Let’s take a moment to pour over our primitives and see if any of them are feasible for actually making that happen. The relevant primitive happens to be : which long-time readers might recall has seen some action on this blog in the past. The idea is that we will run the first until the first occurrence of the , then use that in order to switch to the given continuation. So gives us the “do one thing after another” semantics we want (notice its lack of a constraint!) This happens to be most of the work done, now it’s just a matter of accumulating the rest of the bits we need: the actual notes we’re playing, the start points, and the stop points. Let’s package all of that up into a little datatype: A horizontal structure then is some sort of newtype over . As it happens, writing the monadic horizontal structure is going to be a little more informative at this point, since we can lift the monoidal version out directly. But we can immediately put to work! In the exact style of Yampa Switching , we will use the inuation monad , which allows us to bind over the in some function — in our case, this will be the that curries out of . This is already well-treaded territory on this blog, so you can go read the Yampa Switching post if you’re curious about the exact details here. As a first approximation, wrapping this up gives us the type: However, this is somewhat unsatisfactory. s are covariant in their output (here ), but is invariant in that parameter. Blinding moving forward with as written would mean we wouldn’t have an operation which in practice turns out to be quite a limitation indeed! Instead we can do a little bit of a Yoneda -style encoding, where we hold onto a function that maps from into the eventual result of the invariant parameter. Check this thing out: The idea is that we delay building up our until you hand us a way of mapping from into whatever type you eventually end up wanting. We usually use a trick like this to get a free functor , but here we’re using it to get a free bifunctor : The and instances also come already-paid-for, but we must write them out ourselves: This concludes our diversion into writing . Popping the stack: recall we’re still trying to write a monadic horizontal structure. Which we can now write out as (with being left intentionally unmotivated for the time being), getting all our relevant instances for free: Take a moment to appreciate how amazing GHC is here; each of those deriving vias is lifting an unrelated type through a polytype and through a , the latter of which doesn’t have most instances we’re actually intersted in! 2 Our primary introduction form for is , which acts as one for some amount of time, before rendering a continuation of type : Note that we issue a start event at time 0, and both a stop event and switch event at time . From this we can derive and , both of which use a constant value across their , differing only in what they return as a monadic continuation: And of course, the function we had to invent the machinery in order to provide: It’s probably worth taking a moment to discuss what exactly the hell is going on with all of this crazy yoneda continuation monad stuff. What does it buy us? Lets write a little motif: notice that this emits notes relative to its input. In somewhat-standard notation, looks like this 3 : using this triangle symbol to indicate the continuation. But now check this out. We can bind the result in order to build a musical sequence; looks like this: This happens to be the underlying machinery in Tymoczko’s work on sequences (chapter 4 of Tonality ), though it is not presented as such (and I suspect he didn’t have monads in mind when writing it.) Revitalized with a real connection to real musical ideas, we press on through the mires of abstract nonsense. The most pressing problem remaining is how do we eliminate ? By means of : which isn’t actually unsafe, but will be due to a few additions to our API in a second. But a nicer eliminator is , which as originally promised, calls on our behalf: Here we use and pass it a dummy continuation, since we know we’re never going to actually sample it past the duration of our . Then we take the output , sample only at the start points , and pass the stop points through unmolested. It can also be useful to turn a directly into an event stream, ignoring its spans: The final two mysteries remaining are the unmotivated parameter in , and why is unsafe. Recall that musicians often think about periods of silence, or rests. While we could model rests as a value in a -typed , this would get rather in the way of our nice combinators! In particular, nothing says we must interpret a as a discrete function; if it’s continuous, then the value would make sense. But if we’re exporting discrete function, then the s never actually show up and the s are unnecessary. So here’s the trick; this phantom type in corresponds to “are there any rests in this value?” We can use a to get ourselves a type-level value to play with: Our new combinator is the only thing that uses the parameter: Notice that sets a stop point at t=0, and doesn’t create any new start points. Furthermore, the actual value of type here is an value. It claims to be “impossible,” which we will enforce momentarily. Something peculiar here is that we only have a single type of kind , namely . How can we differentiate s that use from those that don’t? Our old friend the ST trick comes knocking, requiring that remain fully polymorphic: Now can give us a continuous function only when the doesn’t contain any s! Usage of makes no such guarantees, and thus will crash with the impossible error when used on a . Note that has no such compunctions; it samples its function only at the designated start points, which doesn’t provide. And since our semigroup/applicative instances are all temporally end-to-end, nothing can introduce a start point inside of a . We began all of this looking for a temporal monoid whose multiplication would glue horizontal structures end-to-end. We then switched over to making a monad that did the same thing, but we don’t need to give up on our dreams of monoids! In fact, is already the monoid we want, with a minor niggle in that it wants a semigroup/monoid over the continuation value. If we tie that to be everything falls out for free: I don’t actually think this type is worth using, since it means our introduction forms need to be lifted into this newtype. But it’s nice to know that we could do such a thing if we wanted to! That’s all I have to say about rhythm for now. I don’t consider this to be the last word, but it’s as far as I’ve figured anything out. I’ve been reading a ton of textbooks on the topic, and the author who seems to be the least confused about it is Peter Westergaard . His formulation is in terms of “if you want a listener to mentally parse a hierarchical structure out of your notes, what properties would make that parsing easy?” That’s a thing I’d like to explore further, but I’m on a publishing deadline here, so I can’t take too much time to do research in between blog posts. Next time we’ll discuss Tymoczko’s theory of harmony , which will dramatically power up our ability to make something that sounds like music. Until then! Writing that sentence out makes it clear that I got the arguments backwards. Oops! ↩︎ As a little aside, if you’re not 90%+ of your instances, you should start. Good places to look for opportunities involve the wrappers in and . ↩︎ In glorious SVG. Sorry for the shitty image quality in previous posts! ↩︎ Writing that sentence out makes it clear that I got the arguments backwards. Oops! ↩︎ As a little aside, if you’re not 90%+ of your instances, you should start. Good places to look for opportunities involve the wrappers in and . ↩︎ In glorious SVG. Sorry for the shitty image quality in previous posts! ↩︎

0 views
neilzone Yesterday

Automating renewal of Let's Encrypt certificates with Home Assistant OS

I had an alert from my monitoring tool that the Let’s Encrypt TLS certificate for my Home Assistant OS installation had expired. The answer lay in the documentation for the Let’s Encrypt app : Certificates are not renewed automatically by the plugin. The app has to be started again to renew certificates. If the app is started again, it checks if the certificates are due for renewal. This is usually the case 30 days before the certificates’ due date. If the certificates are not due for renewal, the app terminates without changes. If the certificates are due for renewal, new certificates will be created. There are multiple ways how the app can be started to check/renew the certificates. One way to automate the certificate renewal is to configure a Home Assistant automation to restart this app every night via the Supervisor app restart action. My automation looks like this: I can see in the log for the Let’s Encrypt app, and the log for the automation, that it ran as expected so, fingers crossed, no more TLS certificate issues…

0 views
Heather Burns Yesterday

Oh sit down, oh sit down, oh sit down, sit down next to me

In a week of change and closure and moral arcs bending, I spotted something I've never seen before, and that was our Alan all alone.

0 views

The great software IP writedown

Around a year ago I asked whether the cost of building software had just dropped 90% . A lot has happened since then, and I've become more interested in the knock-on question: if building software is that much cheaper, what is the software itself actually worth? One thing that has become obvious to me researching this topic is that software is very much an outlier in terms of ability to extract outsized profit margins, and I think this fact underlines why the industry faces such existential challenges presently. Typically in history you had three main ways of making "outsized" profits: Firstly, you had monopolistic behaviour, with Standard Oil being the textbook example. While there are no doubt significant market competition concerns in software and SaaS, I would argue most software businesses, at least by number if not revenue, do not operate in a monopoly. Secondly, you had network effects, restricting new market entrants. Again, the tech giants clearly do have huge network effect benefits, but many smaller players do not, yet still make very healthy profit margins. Newspapers (used to be) a good example of this, advertisers would want a readership base to advertise to, and if you were starting out from scratch with no readership base, you'd very much struggle to make the commercial model work without willingness to take heavy losses while you built up your readership. Thirdly you had corruption or using violence to extract high profits out of your customers. Protection money from the mafia is the obvious example historically (though still persists in some geographies). Again, there is an argument that many software companies do achieve their market power via regulatory capture, especially in government contracting. And many would argue regulatory capture is corruption with rounded corners. But again, many software companies don't have a lobbying firm working for them, yet still do very well. While you can argue that some of these factors are in play for certain tech companies, I struggle to see that the average small or mid-tier SaaS or software company takes advantage of this, yet posts usually extremely high gross (and often net) margins. So why is this? I posit instead that these margins weren't driven by these mechanisms, but instead by simply not enough supply of people able to build software for the demand. And while the number of people able to develop software has exploded over the past 20 or 30 years, the amount of software out there has equally exploded. So in essence, market actors (whether that would be software engineers or companies) could derive a large amount of value for their work, because there is inherently a (fairly) small pool of people able to build said software. You can see the issue though now AI agents can do a significant proportion of the engineering. That scarcity is evaporating, which in this model therefore drives the value down sharply. The market always beats margins out of you when there is a shift in supply. Of course, it's not quite as simple as AI agents can do coding, therefore the equilibrium between supply and demand changes. Jevons paradox tells us that when something becomes cheaper and more efficient to use, we often end up using more of it, not less. Jevons' original observation was that more efficient steam engines led to Britain burning more coal, not less. So surely with a huge reduction in cost of bespoke software, we'll see far more demand. This is true - we're seeing an absolute explosion in the amount of software being created. But I'd argue this software is intrinsically different to what made SaaS (especially) so valuable. The rough idea with SaaS was that you spent some money on development, and then could sell the same software to thousands or millions of customers. Your marginal cost of a new customer was close to zero, at least in terms of software costs. But with the Jevons paradox-induced software we have now, it is incredibly bespoke for the most part. Why use a generic CRM when you could build (or purchase) one designed for your exact domain? I think this dynamic explains the real issue here. Yes , there will be software products in the future. But I suspect these software products will be for the most part far smaller in addressable market size. Of course, an argument could be that future software companies operate hundreds or thousands of distinct products, across a whole bunch of markets. There is no doubt with agents this could be from a technology point of view at least more possible than before. But I think the flaw with this strategy is Baumol's cost disease . When productivity shoots up in one part of the economy, the parts that can't speed up end up getting relatively more expensive. The classic example is a string quartet, which needs the same four musicians and the same amount of time as it did in 1800. Apply that to a software business and the bottleneck moves very quickly from building the product to selling it. Already I'm seeing that while building software has got 10x quicker, sales and marketing is as slow as it always was, at least for most B2B deals. So I think this explains the flaw with the "software factory" concept that many have been pushing. Yes, you can build hundreds of line-of-business applications in a fraction of the time, if you really want to. But selling these to actual humans is just as hard in my opinion, despite the claims of many that they've managed to automate go to market - any (questionable) efficiencies of these approaches are outweighed by the fact that each buyer has far more marketing directed at them. I therefore think that there is certainly going to be a market for SaaS in the future - not everyone is going to vibe code their own apps, though some will. But my gut tells me that these are going to be far smaller in scale - and therefore the underlying VC model of throw loads of cash at a SaaS business to scale as fast as possible is even more broken than it was before. The final thing that is really nagging at me is just how easy it is to copy software now. You can take any agent, give it some screenshots (or if you are even more lazy, a browser tab for it to control) and tell it to clone software you think you like. We've already seen this on an industrial scale with the ArtCraft suite of clones of everything from Excel to Photoshop. While I'm sure they are not quite as good or stable as the "genuine" product, this has been the biggest shock to me. Arguably already their Photoshop clone has more features than the unfortunately named GIMP open source clone of Photoshop, which has had 30 years of human effort aimed at it. So this again makes me wonder where the value in software IP lives. Certainly agents aren't (yet) capable of coming up with good software products to build a business around, so there is definitely value in product taste and choosing the right product to build. But if you do find a successful niche for your product, it is now trivial for anyone to copy that. So this makes me question exactly where the value is going to lie in future. One other thing I've noticed is that it's much easier to move between software providers now. Agents can export your data, transform it and import it into a new tool very easily. Even if the provider doesn't offer a nice export, agents are very capable of reverse engineering the web app and doing the export for you anyway. Switching costs have always been one of the quieter moats in SaaS, and that moat is draining too. This is where I think it gets uncomfortable for the finance side. For the past decade or so, the standard defence of loss-making software companies has been that R&D isn't really an expense. It's an investment. You spend heavily on engineering today, and you get a long-lived asset - the product - that keeps paying out for years. That logic only holds if the R&D actually builds something durable. If a competitor - or your own customer - can rebuild what you spent five years on in a few weeks with agents, then that spend isn't building an asset at all. It's much closer to a running cost you have to keep paying just to stand still. Which means the underlying economics of a lot of software companies are quite a bit worse than the adjusted numbers suggest. You can see the same thing from the M&A side. A lot of tech acquisitions were really build-vs-buy decisions. Paying 10x revenue for a startup made sense when building the equivalent in-house would take two years and 30 engineers. But if your own team, with agents, can build it in a matter of weeks, the premium for the technology itself drops to close to zero. You're only paying for the customers and the distribution - and if the product has no real traction, the IP on its own is worth roughly what it would cost to rebuild. Which is not very much anymore. A year ago I was asking whether the cost of building software had dropped 90%. I think the more uncomfortable question now is whether the value of what's already been built has dropped by a similar amount.

0 views
Sean Goedecke Yesterday

LLMs are terrible at designing AI systems

LLMs don’t know how to design software that uses LLMs. They’re comfortable writing normal programs, but if you throw an AI-shaped problem at them, they will usually screw it up. Why is that? For example, this post describes an attempt to vibe-code a memory tool that would flag when one memory contradicted another 1 . This is a great instance of a task that was virtually impossible five years ago but is now relatively trivial due to LLMs. You can simply task a LLM with scanning for contradictions, either in a big batch or pairwise 2 . However, when asked for the “state of the art” approach 3 , the AI agent in this case did not recommend LLMs. It started building a bewilderingly complex symbolic system to convert the memory items into structured logical atoms , then mechanically compare them with a solver. This, of course, did not and could not work, because there is no analytic way of determining whether any two natural-language sentences express the same proposition. The LLM knew this was impossible! When the user eventually lost patience and said “hey, why isn’t this working”, Claude immediately explained that this was a known 130-year-old problem with no solution. And yet it happily went around and around for weeks iterating on a dead end. I’ve run into this myself. When I was building Autodeck (AI-generated spaced repetition cards) my agents would routinely try to categorize cards by regex or keyword search instead of just making another LLM call. Why would this be happening? I have two obvious explanations and one deeper one. The first obvious explanation is that most training code is pre-LLMs . Assuming the labs aren’t feeding the current avalance of slop repositories directly into the models 4 , the vast majority of the code that models learn from was written before LLMs were an option. So it would make sense that they’d reach for non-LLM approaches, in the same way that they’d reach for jQuery over modern vanilla JS, or Backbone.js over React, right? Wrong, actually! LLMs don’t choose jQuery and Backbone. Whatever part of the training process pushes them towards up-to-date knowledge in other areas isn’t successfully pushing them towards LLMs, so this can’t be the whole story. The second obvious explanation is that RLVR is not a good environment to write code that calls LLMs . Much of the time LLMs spend training is spent in reinforcement learning environments where an automatic grader determines their performance (for instance, by running some code they wrote and checking the outputs). I don’t know this for a fact, but I would be surprised if these environments contained the credentials and API access for a LLM to call other LLMs 5 . So even if the pretrained LLM was comfortable writing that kind of code, the reinforcement learning process might discourage 6 it from doing so. The third explanation, which I like the most, is that current AI agents are just not very good at software design . On this explanation, picking a doomed symbolic approach instead of simply calling a LLM is not a weird aberration but a normal instance of AI agents picking the wrong tool for the job. I’ve seen this behavior from LLMs in other contexts too: trying to build complex (and doomed) consensus algorithms instead of tolerating stale data, introducing synchronous checks in event-driven systems, and so on. In the past I’ve chalked these up to alignment , but I’m open to the idea that they’re just ordinary errors of software design. One lesson I learn over and over again is that I can’t judge a LLM by the standards of a human. Any human programmer who was capable of implementing a sophisticated symbolic solver would know that it wouldn’t work, and so if they suggested that approach I’d assume 7 they had a good reason. Only a LLM could be both smart enough and dumb enough to make this mistake. I write a lot about the skills that are involved in working effectively with LLMs. I still stand by what I wrote in February 2025 : Using LLMs well involves extracting value from what they’re good at and avoiding wasting time at what they’re bad at. This is surprisingly hard, because of the point above. LLMs are good and bad seemingly at random: they will work hard for hours but sometimes be shockingly low-effort , they will draw on inhumanly broad expertise but fail to apply the most basic common-sense, they can successfully rewrite enormously complex programs but will often make very silly errors in design. If you can predict where they will do well or badly ahead of time, you can get enormous value from LLMs; if you can’t, you will likely waste a lot of time and tokens going down hopeless approaches. Here’s another example where the user “implied” that Opus 5.5 should use a judge model and it instead constructed a series of regex checks. Here I’d probably reach for a decision model . In hindsight, this was probably the key mistake, though I don’t know how you’d turn that into advice for non-technical vibe-coders. Never asking the agent about the state of the field is a mistake too. Put one more notch on the LLMs reward expertise belt. A fair assumption, I think: even though training on low-quality synthetic data clearly isn’t catastrophic for LLMs, it can’t be great . It would be expensive, slow, and risky. This theory also explains why LLMs are great at prompting other LLMs as subagents, or on training LLMs from scratch: they’re likely directly RL-ed and evaluated on that, unlike on writing code that calls LLMs. Well, okay, I wouldn’t — I’m arrogant enough to ask anyway — but I’d at least expect a good reason. Here’s another example where the user “implied” that Opus 5.5 should use a judge model and it instead constructed a series of regex checks. ↩ Here I’d probably reach for a decision model . ↩ In hindsight, this was probably the key mistake, though I don’t know how you’d turn that into advice for non-technical vibe-coders. Never asking the agent about the state of the field is a mistake too. Put one more notch on the LLMs reward expertise belt. ↩ A fair assumption, I think: even though training on low-quality synthetic data clearly isn’t catastrophic for LLMs, it can’t be great . ↩ It would be expensive, slow, and risky. ↩ This theory also explains why LLMs are great at prompting other LLMs as subagents, or on training LLMs from scratch: they’re likely directly RL-ed and evaluated on that, unlike on writing code that calls LLMs. ↩ Well, okay, I wouldn’t — I’m arrogant enough to ask anyway — but I’d at least expect a good reason. ↩

0 views
neilzone Yesterday

Upgrading my Airsoft rifle

I spent a pleasant morning at Red Alert, making a couple of small (but hopefully useful) upgrades to my main Airsoft rifle (MTW). I changed the routing of the air line for the HPA engine, routing it out the back of the rifle, under the buffer tube, moving it from down the hand grip. I also replaced the stock grip with a grip containing batteries and switch, so that I can power the HPA engine with that, rather than needing to install a Li-Po battery each time. These were reasonably straightforward upgrades, and I was keen to do them myself - to get the benefit of the learning experience - but I was also very keen to do it around people who know what they are doing. I also wanted to borrow an armourer’s wrench, as I do not have one, and I so rarely need one that buying my own seems a bit pointless. Luck was on my side, as the upgrades went smoothly. I dropped a small spring at one point, and I did not know from whence it came. It turns out that it was a spring which retains the small pin which keeps the large rear metal pin in place - it is inserted from near the stock at the back of the rifle, through a small hole, which I uncovered when I replaced some parts to re-route the air line. Fitting the upper and lower receiver back together, with the new, shorter, air line in place was a bit of a pain, but hopefully I am not going to need to open up the weapon too often, having made these changes. Perhaps the next time that I need to change the hop rubber? But not each week as I did before, to insert and remove the battery. I took the opportunity to make a slight adjustment to my HPA regulator’s output, giving me an increase of about 10 feet per second, while hopefully keeping me sufficiently clear of the upper limit for this weapon that I don’t need to tweak it down again when I use someone else’s chronograph when I next play. Lastly, I re-zeroed my red dot, as it was slightly out. I was doing pretty well at the end, hitting with reasonable accuracy out to 70m, so I look forward to giving this a go in a game very soon.

0 views
Unsung Yesterday

“Over the years, countless amounts of speculation has come and gone.”

A video from Leon Massey about the community trying to figure out a secret in the 2012 videogame Fez: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/over-the-years-countless-amounts-of-speculation-has-come-and-gone/yt1-play.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/over-the-years-countless-amounts-of-speculation-has-come-and-gone/yt1-play.1600w.avif" type="image/avif"> This was an interesting video because the secret was figured out, but it was figured out in an interesting way – with slight help from the developer, followed by brute forcing the solution. We previously talked about some similar approaches. I once used a random number generator to stumble upon a solution rather than calculating it mathematically , and just recently we talked about a Zelda game and a concerted trial-and-error effort to see a very rare condition inside it . But in the case of Fez, the outcome ended up harder to get excited about: knowing the solution tainted the journey and made the whole outcome rather unsatisfying. To me, it echoed the current encounters with AI and the tradeoffs there. Seeking out help from an llm might get you to an answer faster – sometimes much faster! – but at the expense of understanding how the answer came to be, or having been through the experience of searching for that answer.

0 views
Unsung Yesterday

An unexpected command palette

When I linked to the troubled New York City/Big Apple Game , at the very bottom I mentioned a web emulator of Atari I just discovered, called A8 Web . What was particularly interesting about this emulator that I haven’t seen before, is that it came equipped with a ⌘K command palette: I didn’t expect this kind of a professional UI in such a small piece of software, but I immediately embraced it – it not only gave access to various expected commands like “mute audio” or “resume emulation,” but also allowed to simulate all of the inputs. One of the challenges with emulation is that keyboards never match, special buttons like Start or Select don’t exist, and joysticks might not be connected. That might leave you truly stuck when the key absolutely need to keep going simply isn’t something you can press. Here, the creator thoughtfully adding all of those inputs to the command palette got me unstuck quickly. I am generally a big fan of command palettes as a UI layer underneath more visible layers. They are a nice way to enable flow, an interesting vector of discovery (just browsing the list above taught me a lot about what’s possible), and a good place to stash lesser-used commands that do not deserve more prominent places like menus or on-screen buttons. Done well, they’re useful with the keyboard and with the mouse. Some old professional apps like Photoshop that didn’t bother to update their core interface feel extra obsolete without a command palette today. A8 Web’s implementation isn’t perfect – it should probably be floating atop the emulated screen rather than sliding in from the side, and I would make the input commands not auto-close the sidebar – but it was such a positive surprise that made me rethink some things I thought I knew about command palettes.

0 views
Blog System/5 2 days ago

Unattended reboots with encrypted disks

Do you run a small home server or NAS holding your precious photos and sensitive documents? If so, I suppose the drives are encrypted, right? But then… can you reboot the machine without manually entering a passphrase before being able to access your files again? If you answered yes to the questions above—which is common for convenience—that implies the encryption keys live in the machine which… kinda defeats the purpose of disk encryption, doesn’t it? A thief can break into your house, lift the whole box (they are small these days, huh), plug it in elsewhere, and gain free rein over your files. So, which one is it? Simplicity and reliability by allowing automatic reboots or data protection by keeping the key in your head? With ZKeys—a little new project I’m launching today—you can have your cake and eat it too. TL;DR ZKeys is a managed service that stores encryption passphrases off-site. Client machines fetch the encryption keys from the service and keep those keys alive (unlocked) via periodic keepalive messages. If the server stops receiving those messages for a long period of time (e.g. because it has been unplugged as part of theft), access to the key is automatically locked. You get automatic, passwordless reboots while keeping your data protected against real incidents. Did you know that, by subscribing to Blog System/5, you support the creation of this content and this software? About a year ago, I was reinstalling my FreeBSD home server to act as my NAS after a short stint with a Synology box . During that process, I decided to enable encryption for the ZFS data pool. But as I did that, I faced the question: how do I allow the machine to automatically serve my data after a reboot? I could put the key in a local file, which FreeBSD supports via the ZFS option, but that would render disk encryption pretty much useless. So I had an idea: what if I put the key on a different machine away from my house? I could then make the NAS fetch the key to mount the drives. And if someone stole the server, I could wipe the remote key and make the data unreadable before they could plug it back in. I did post this to Twitter and there was some interest in the idea: But… did you notice the problem with what I said above? Wiping the key by hand after noticing something is wrong is problematic because the gap between those two events may be large. What if I’m on a long vacation and I have no means of noticing that the machine has been stolen? Game over. So I refined the idea: what if I had a service that stored the key remotely and required the consuming machine to send it frequent pings to keep the key unlocked? This way, if the machine were stolen, it’d likely be offline for more than a few minutes and the service could automatically determine that something is wrong and lock the key from future accesses. And that’s precisely what ZKeys does. Before getting into details, it’s important to get something out of the way early on: ZKeys is designed so that a compromise of the server is insufficient to retrieve or reconstruct your keys. This is done in two ways: Split keys: The ZKeys server only stores half of the passphrase used to unlock a volume. The other half remains on the client machine. The client program combines both halves to produce the whole passphrase. Encryption at rest: The keys are encrypted at rest with a password that the client must supply for retrieval and which is securely transmitted over HTTPS. The password only lives in the server’s memory for a brief period of time. The tradeoff is, simply, that you MUST back up the full encryption keys in a third location of your own (a paper in a safe, a password manager, you name it). If ZKeys goes down or you lose your remote key password, there is absolutely nothing I can do to help you. You have been warned. The setup instructions later on will instruct you how to back the key up. The ZKeys service is available at https://zkeys.jmmv.dev/ and the open-source client—along with system integrations for FreeBSD and Fedora—is in my jmmv/zkeys repository. Disclaimer: ZKeys is offered to you for free. I make no promises about its reliability, availability, or anything else. I am giving you access because I expect that supporting a handful of extra users won’t be a big issue. But if support becomes a burden, or if load becomes unmanageable, I’ll have to re-evaluate my choices here. Which means that, for now, you have to use the limited code to get started! The basic idea to set ZKeys up is the following: Install the client CLI tool. Create a service account. Create a new key (or onboard an existing key). Configure the keep-alive service on the target machine. Change or add the filesystem encryption key to be the one stored in ZKeys. Configure the auto-mount service on the target machine. That’s all it takes, but the steps are quite manual at the moment because no upstream OSes provide packages. The service documentation covers several scenarios in detail for FreeBSD and Fedora, so I’m not going to repeat them here. Demo of the integration between ZKeys and Fedora 44 with a fully encrypted root volume: the video starts by showing a fully unattended boot, followed by a manual lock of a key that causes Fedora to refuse to reboot on its own, and finishes with another automatic reboot after the key has been unlocked again. Now for the nerdy details, because that’s what you’re here for: The CLI is Rust, of course, but it has to come with a bunch of shell scripts to integrate with FreeBSD’s rc.d system and Fedora’s systemd and dracut. These integration points are the trickiest part of the client package and the hardest to validate automatically, which is why I only provide support for the two systems I use on a daily basis. It should be possible to integrate ZKeys with any other Unix-like OS, though. The CLI package is a frankenmonster made up of Cargo and GNU Automake/Autoconf because of the above. I believe system-level tools should abide by de facto standard tooling even if such standard tooling is deficient, so GNU Autotools it is. I run two non-replicated instances of the service: one in US West and one in EU West, because I have machines in both locations and both rely on encryption. I strongly suggest you use the US instance as I’m seeing frequent power hiccups in my EU deployment. The frontend uses Bootstrap, straight from the 2010s. Does it look ugly? Maybe, but I kinda like it and, most importantly, it’s functional . Of course I could have asked AI to produce yet-another-dull-site full of gradients and slop, but I did not want to do that, so I stuck to what I already knew how to do by hand from previous services. The admin page is an SPA, yes. This is an intentional design choice that forces a very clear separation between the backend APIs and the frontend. Whatever the current web frontend can do any other client can do as well. For example: the current CLI tool does not offer administrative operations but it could trivially be made to support them, because the APIs are right there. The backend is built on my own III-IV framework which I’ve been building up over the last few years while implementing a couple of other services. I’ve had to add a lot more features to it to make this possible though. Oops, I know that’s the name of a different effort , but you really need to encrypt your local drives. With computers becoming smaller and lighter every day, it’s easier than ever to steal them, and unfortunately home break-ins are common in some parts of the world. Block-level encryption is cheap in CPU terms, so do it. ZKeys just makes this process less cumbersome for headless servers. Once again, head to: https://zkeys.jmmv.dev/ to get started and use code to create an account! And if you are interested in helping out, I’d appreciate the creation of packages for FreeBSD and Fedora, and integration scripts for other systems. TL;DR ZKeys is a managed service that stores encryption passphrases off-site. Client machines fetch the encryption keys from the service and keep those keys alive (unlocked) via periodic keepalive messages. If the server stops receiving those messages for a long period of time (e.g. because it has been unplugged as part of theft), access to the key is automatically locked. You get automatic, passwordless reboots while keeping your data protected against real incidents. Did you know that, by subscribing to Blog System/5, you support the creation of this content and this software? Storytime About a year ago, I was reinstalling my FreeBSD home server to act as my NAS after a short stint with a Synology box . During that process, I decided to enable encryption for the ZFS data pool. But as I did that, I faced the question: how do I allow the machine to automatically serve my data after a reboot? I could put the key in a local file, which FreeBSD supports via the ZFS option, but that would render disk encryption pretty much useless. So I had an idea: what if I put the key on a different machine away from my house? I could then make the NAS fetch the key to mount the drives. And if someone stole the server, I could wipe the remote key and make the data unreadable before they could plug it back in. I did post this to Twitter and there was some interest in the idea: But… did you notice the problem with what I said above? Wiping the key by hand after noticing something is wrong is problematic because the gap between those two events may be large. What if I’m on a long vacation and I have no means of noticing that the machine has been stolen? Game over. So I refined the idea: what if I had a service that stored the key remotely and required the consuming machine to send it frequent pings to keep the key unlocked? This way, if the machine were stolen, it’d likely be offline for more than a few minutes and the service could automatically determine that something is wrong and lock the key from future accesses. And that’s precisely what ZKeys does. Zero-knowledge protection Before getting into details, it’s important to get something out of the way early on: ZKeys is designed so that a compromise of the server is insufficient to retrieve or reconstruct your keys. This is done in two ways: Split keys: The ZKeys server only stores half of the passphrase used to unlock a volume. The other half remains on the client machine. The client program combines both halves to produce the whole passphrase. Encryption at rest: The keys are encrypted at rest with a password that the client must supply for retrieval and which is securely transmitted over HTTPS. The password only lives in the server’s memory for a brief period of time. Install the client CLI tool. Create a service account. Create a new key (or onboard an existing key). Configure the keep-alive service on the target machine. Change or add the filesystem encryption key to be the one stored in ZKeys. Configure the auto-mount service on the target machine. The CLI is Rust, of course, but it has to come with a bunch of shell scripts to integrate with FreeBSD’s rc.d system and Fedora’s systemd and dracut. These integration points are the trickiest part of the client package and the hardest to validate automatically, which is why I only provide support for the two systems I use on a daily basis. It should be possible to integrate ZKeys with any other Unix-like OS, though. The CLI package is a frankenmonster made up of Cargo and GNU Automake/Autoconf because of the above. I believe system-level tools should abide by de facto standard tooling even if such standard tooling is deficient, so GNU Autotools it is. I run two non-replicated instances of the service: one in US West and one in EU West, because I have machines in both locations and both rely on encryption. I strongly suggest you use the US instance as I’m seeing frequent power hiccups in my EU deployment. The frontend uses Bootstrap, straight from the 2010s. Does it look ugly? Maybe, but I kinda like it and, most importantly, it’s functional . Of course I could have asked AI to produce yet-another-dull-site full of gradients and slop, but I did not want to do that, so I stuck to what I already knew how to do by hand from previous services. The admin page is an SPA, yes. This is an intentional design choice that forces a very clear separation between the backend APIs and the frontend. Whatever the current web frontend can do any other client can do as well. For example: the current CLI tool does not offer administrative operations but it could trivially be made to support them, because the APIs are right there. The backend is built on my own III-IV framework which I’ve been building up over the last few years while implementing a couple of other services. I’ve had to add a lot more features to it to make this possible though.

0 views
Ankur Sethi 2 days ago

A few assorted thoughts about LLMs in November 2026

LLMs are useful tools. They're also the products of the greatest theft of intellectual labor in human history. Both of these things can be true at the same time. In the right hands, LLMs can produce good, reliable software in shorter timeframes than ever before. Most hands are not the right hands. Most organizations using LLMs to write code are producing slop, no matter what they tell you in their press releases. If you've never built serious software using an LLM, I urge you to make an honest attempt before you complain about them. You might be surprised. If you're not using LLMs for code reviews—especially when it comes to security—you're not doing your job right. LLMs over-engineer everything. Constantly pushing back against their eagerness to write code is exhausting and frustrating. Software built entirely or partially using LLM assistance is fine, actually. If an LLM-generated codebase has a security breach, the fault lies completely and fully with the human who generated and shipped that code. We must not allow AI to become yet another accountability sink. If you use LLMs for writing—emails, text messages, blog posts, anything at all—please don't talk to me. Ideally, don't even sit next to me on the train. I don't want to associate with people who can't use their own words to communicate. We ought to pass regulations that force the big AI labs to make all their training data public. This should include post-training data, too. Since most of the data used for training LLMs has been acquired unethically by ravaging our digital commons, it stands to reason that it should be released back into the commons. The AI bubble does’t feel very bubbly to me. The big labs seem to be on the path to profitability. They might stop growing as fast as they're growing, but I don't expect a complete collapse. Source: vibes. Models need to get faster. Like, 10-20x faster than they are currently. That's going to be the next inflection point for productivity. I don't want a bigger, smarter model. I want today's model to go brrrrr. Bonus points if it can be cheaper, too. Has somebody figured out continuous learning yet? Is somebody working on it? Open models will not eat Anthropic/OpenAI's lunch. The big labs will always have one thing your M9 MacBook Pro Max with 256GB RAM won't: infinite compute. That means they can always serve larger models than you can run at home. They can also serve small models at stupid fast speeds. There will always be workloads that require the largest models, and workloads that require smaller models burning more tokens faster. For example, certain long-horizon tasks like the Millennium Prize problems or finding holes in the Linux kernels both respond better to larger models and more token use. The big labs are in the business of selling compute, not intelligence. Dario and Sam do not have your best interests at heart. Be careful. You should keep writing code by hand. You should keep learning computer science. You should have projects where you do not allow yourself to use any LLM assistance. Why? For the love of it, of course. But also to stay sharp, to understand how the sausage is made, to get close to the machine, and to make sure you're never fully dependent on silicon for your cognitive needs.

0 views
ava's blog 2 days ago

chronic illness memes

I’m switching off of adalimumab and starting upadacitinib (Rinvoq)! Finally, no more weekly injections for now, but daily pills. Despite everything, I am currently pretty happy and I’m turning this crap year around. I feel accomplished at work and have been getting a lot of praise for my (new-ish) role as data protection coordinator of our department. I study a lot for my degree and feel pretty competent most of the time. I make plans for more certifications and classes in 2027, too. I’m back to volunteering more for noyb, and I’m looking for other part time opportunities. I have fun playing FFXIV, and I’m writing some blog posts again. I’m slowly digging through my emails and will hopefully reply soon. I’m currently at a friend’s place :) While I write some more drafts… have some memes I found online. Published 07 Nov, 2026

0 views