The Devlog

I’ve never kept a devlog before, and I’ll try my best to keep this up as development goes on. I’ve backfilled parts of the journey that I could remember. I got Claude’s help on some of the sequencing. The truth is that I’ve become a little obsessed with this project. It’s crowded out other hobby pastimes for now, but it thoroughly integrates quite a few of my interests at once.

This project will be a long-term pursuit of mine, because it’s currently running my home systems, and thus I must continue pursuing it. It also keeps paying back in utility the more I develop it, which is not true of many of my side projects. It is how I felt about Docket. That has to be a good sign.

Taking the day to establish this devlog and renew a commitment to daily writing feels like a further investment in the project’s long-term health as well.

While there’s still more iteration and polish to do on the media-operator, I can already feel the gravity well of a library browser and management system to rival the venerable Jellyfin. It’s just obvious this is where I’m heading to very soon.

There’s also a wildcard detour I could take with Spotify Soloist to take a swing at music and supplanting Music Assistant. I’m not sure which direction is pulling me more strongly. The wonderful thing about sitting at this vantage point is that I can see so many places to go: whole home media library and playback, a camera-operator to build a K8s-native NVR system, or taking a fresh swing at an autonomous agent built directly into the k3s cluster.

Media playback

(backfilled 2026-08-25)

Sometimes, I just don’t know when to quit. I have a pretty solid stack now up through hardware, and yet my Claude subscription hits the weekly reset and I’ve got all these Fable credits to burn. So I start thinking about that whole-home A/V octopus again.

And I really love Kubernetes, by the way, if you couldn’t tell. It’s one of my favorite pieces of software ever. My top four probably go Linux, Kubernetes, Redis, and PostgreSQL. Tried and true, battle-tested systems with clean abstractions and ruthlessly focused on performance and stability. In a lot of ways, liken is my love letter to Linux and Kubernetes.

After the last week or two, there’s an up-and-comer that’s really got my attention: mpv. What a system! Plays literally anything, and you can script the UI with Lua, and you can drive the play over a domain socket. It’s the perfect foundation for a media system following the Linux and Kubernetes ethos.

The hardware operators

(backfilled 2026-08-25)

I kept pressing Claude to cough up ideas for how we could extend this hardware system more broadly. I expressed a whole-home A/V vision, but what I wasn’t seeing at the time was that we were missing a key layer of abstraction between liken and a multimedia fever dream.

liken sees hardware the same way that the kernel does, because, well, that’s all there are, liken and the kernel. The kernel doesn’t see a pair of Bluetooth headphones, it sees a Bluetooth radio that acts as a gateway to an alien network of other devices. It doesn’t see a monitor, it sees a GPU that has two HDMI ports. It doesn’t see audio channels, it sees a sound card with some PCM sinks. I could easily claim the radios and the GPUs and the cards, but they weren’t really shaped correctly to just hand to a workload. A music player doesn’t want a Bluetooth radio, it wants a socket to stream audio to some speakers.

I noticed a common pattern: userspace services that owned a lower-level liken device (like a Bluetooth radio), handled the bluetoothd/bluez user-space side, and then published back to the cluster resources like a game controller or a Bluetooth speaker. This pattern played out the same way across displays, audio, and bluetooth, so I focused on trying to give them as common a shape as we could.

The results are pretty cool! The audio-operator and display-operator didn’t even need any new CRDs because they just claimed a liken resource and republished the child devices as new ResourceSlices in-cluster. Bluetooth was an interesting exploration and I learned a lot about Bluetooth pairing and protocol negotiation.

I think the real joy came when I realized we could have the bluetooth-operator and audio-operator collaborate on exposing things like Bluetooth speakers and headsets. We still don’t have microphones implemented yet (even now in late August) but the groundwork is definitely all there.

Some new little friends

(backfilled 2026-08-25)

Riding high on the success of the homelab migration, I started thinking about what my original vision had been for the homelab, years ago, in the 2010s. I’d pictured a single brain with digital tentacles snaking through the walls and lighting up every corner of the house as part of one expanded digital brain.

I’d realized much of this with Home Assistant, for sure, and Google Home let us pretend we owned a whole home brain and voice. But there was still something very dull about how the homelab had landed as a rack of computers tucked in a corner, with everyone relying on Google TV dongles and speakers rather than talking with our actual house. Now I had a vocabulary to express that whole house vision, liken with follower nodes of any size, exposing hardware devices over DRA for special cases.

The first target for me was our zigbee2mqtt and NUT devices on those Raspberry Pi 2s. With a couple of very low-end mini PCs, they joined as the first non-leader nodes and the Kubernetes DRA system lets me express exactly why the workloads land on the same machines that the UPS and Zigbee radios are attached to.

That entire operation took less than a couple hours, and I realized that there was really no going back.

Replacing nuc2

(backfilled 2026-08-25)

Due to the sheer humor of the universe, nuc2 (a venerable old beast of 6 years) suffered a power supply failure of some sort, just one week after we rolled the fleet to liken. I ordered a new mini PC to take its place.

The cluster carried on with four machines while the replacement shipped. nuc2’s Machine record read Lost, its scrape targets came out, and the CI runner fleet dropped from five to four.

The replacement, a little Bmax, arrived two days later. Replacing a leader was: boot the stick, read the hardware report, write the new nuc2 manifest, and install it as nuc2. It rejoined the leader rotation the same afternoon. From a dead power supply to a fully replaced leader in two days, and most of that was shipping.

Homelab cutover

(backfilled 2026-08-25)

Over the week leading up to the homelab cutover, I spent a good bit of time moving anything that wasn’t bolted down into Kubernetes. It wasn’t that much, just WireGuard (for my homelab’s bootleg Tailscale wannabe system), DNS, NTP, and some metrics things.

I also happened to have a spare mini PC from my days tinkering with an autonomous agent. Yes, I ran openclaw for a week. Yes I bought a dedicated machine for it. Yes I thought I’d build an alternative myself. No I don’t still run any of them today. That mini PC was just begging for a workload, and it became liken-1, the first official physical machine that ever booted to liken. It has still only booted from the USB stick image once, and has only taken liken’s “over-the-air” updates since then.

I resolved that I’d just rebuild the whole fleet on liken.

And I did! By about 11pm that night, the whole thing was done and everything was completely smooth sailing.

And it was…anticlimactic!

LOL nope that’s a total lie. Somewhere in the 7pm hour that evening, when I imaged the first node, it woke up, decided that it owned flux, saw that I had turned the flux addon off on purpose, and proceeded to tear down flux on my entire homelab cluster. This included all the PVs for all the shared files and databases. I panicked and raced to power off that first node but the damage was done. I was heartbroken, assuming I’d be restoring things from backups for hours to come.

Claude, though, Claude said something like, “This is why I took a backup of the cluster’s etcd database before we started”. You did WHAT?

Yep, Claude had quietly taken a snapshot of etcd and put it on my laptop for safe-keeping before we took down the first node. That etcd snapshot had all of the Flux objects and PVCs and PV definitions wiring everything to the right places on our NAS. Claude restored the backup with some cluster surgery, then did a rolling restart and at some point said something like “Okay, now go put the stick in the next machine and hit wipe and reinstall as nuc4. I’ll wait.”

What a champ, and I really wish I still had the transcript from that evening. We really didn’t skip a beat and we really did get done with the full rollout by like 11pm, and I slept like a baby.

Okay, this thing might be real

(backfilled 2026-08-25)

After a crazy week of hacking, my conviction really started to sharpen that I had something really interesting on my hands. It was the server Linux distribution I’d always wanted: Kubernetes. We’d made something that I wanted, so it might be something someone else wanted too.

I was still in a mode where I was laser-focused on replacing the distro of my homelab, but I took a beat to make sure the project was real. I made a GitHub org, I gave it a website. I even started the first version of that website on a little 1GB Linode.

Deployed my blog on Kubernetes

Needless to say, I’ve since rethought that deployment architecture. At the time I felt that I should have at least one place to dogfood it for “real”. A cloud provider instance did serve well as a real persistent deployment, but the true endgame was always physical hardware, which would take another couple of weeks.

Hatching liken

(backfilled 2026-08-25)

Heading into a 10-day stretch of relaxation and lake time, I started thinking about catching up on some maintenance on my homelab. My homelab has never been in better shape since the advent of AI coding agents. It has fully automated image updates with flux; the machines themselves were fairly well-maintained with Ansible. It was a shining beacon of Infrastructure-as-Code (perhaps even too much, given some serious circularity in the fact that my internal gitea instance runs on the same cluster it manages, but alas).

But not everything smelled like fresh cut grass here. The five mini PCs backing the k3s cluster were all running Ubuntu 22.04 and had a gajillion pending updates requiring reboots. I was 4 minor versions behind on k3s. My zigbee and UPS controllers were running on Raspberry Pi 2s (yes, 2s) and were literally still running Raspbian, which hasn’t even existed since 2020. I was drowning in updates, and there’s really no good time to take the cluster down to apply them.

I started thinking very hard about why I even had Ubuntu and Raspbian in the mix at all. Just about everything was already running in K8s, except a few oddballs, like WireGuard and bind9 for DNS. Surely any service that can run on a host OS can also run under K8s, right? Why do I even need SSH on these machines? Can’t I just boot straight from Linux to k3s?

It turns out that, yes, yes we can.

Almost.

So the gears start turning: k3s probably doesn’t actually expect itself to be pid 1 and it would be thoroughly unreasonable for Rancher to give it support for that. So maybe we’d just need a tiny little init shim. How does Linux even actually boot up anyway? And how on earth would we give a pod access to a device like a GPU or a zigbee dongle if there’s no udev running? Do there need to be users? What does systemd actually do?

So I started pulling threads to see what I could learn here. And it didn’t take long before Claude and I had a very small proof-of-concept booting up on QEMU. I’m not even sure when I could first run kubectl against it, but from that moment, I knew for sure I was going to take this to its logical conclusion.

(And yes, I did still spend plenty of time with my family by the lake!)