Luther Monson, Staff Software Engineer at SUSE

Luther Monson

Go by way of PHP · Containers · VMs · Windows · Kubernetes

Escaping WSL: Booting My Own Linux VM Through Hyper-V's HCS API

WSL2 refuses to run under LocalSystem, so ephemerd boots an Alpine kernel directly through vmcompute.dll instead.

9-Minute Read

Diagram of ephemerd booting a Linux VM through HCS and reading its IP from the serial console

I’ve been building ephemerd, a daemon that runs self-hosted GitHub Actions runners with a fresh environment per job. On a Windows host, Windows jobs run in Hyper-V isolated containers. Linux jobs still need a Linux kernel from somewhere. The obvious answer on Windows in 2026 is WSL2, and that’s what I used first. It worked great from my terminal and fell over the moment ephemerd became a real Windows service.

Your Zero Value Just Turned Off My TLS Verification: omitempty, Go Defaults and Proxmox

A plain Go field can't tell unset from false. In go-proxmox the fix for one omitempty bug created the opposite bug, until an audit against the Proxmox schema found 75 fields and v0.7.0 moved them to pointers.

7-Minute Read

Table showing how verify-certificates was sent by go-proxmox before #210, between v0.2.3 and v0.6.x, and in v0.7.0

Every Go developer knows omitempty drops zero values. What takes longer to sink in is that a plain bool only has two states. A caller filling in an options struct means one of three:

From 52% to 100%: Measuring a Go Client Against the Proxmox API Schema

I built a stdlib-only mage target that pulls Proxmox's own API schema and regex-scans go-proxmox for HTTP call sites to report coverage. The tool was wrong more often than the code was.

8-Minute Read

Line chart of go-proxmox endpoint coverage from 4% in 2021 to 100% in May 2026

For five years the honest answer to “how much of the Proxmox API does go-proxmox cover?” was “a lot of it? the parts people asked for?”. Issues would come in for endpoints that didn’t exist in the client. I’d have no idea how many more were missing. At the end of May I finally built the thing that answers the question. Three days after the first run the answer was 100%. The more interesting part is how many times the measuring stick was wrong on the way there.

Cloud-Init ISOs in Pure Go with go-proxmox

Building nocloud cloud-init ISOs in memory with go-diskfs, uploading them to Proxmox and mounting them on a VM in one call, with no genisoimage.

8-Minute Read

Flow: makeCloudInitISO, upload to Proxmox storage, mount as CD-ROM, VM boots and cloud-init reads the cidata volume, cleanup

Proxmox’s built-in cloud-init support is fine for the basics: a user, an SSH key, an IP config. It falls apart the second you want your own user-data, vendor-data or a real network-config. The GUI and the ci* options only expose a handful of fields. The escape hatch is cicustom pointing at snippet files, and that means getting files onto the Proxmox host’s filesystem first. The usual alternative is building a nocloud ISO yourself with genisoimage or mkisofs. That means a system…

Staying Logged In to Proxmox: Ticket Renewal, Expiry and Where API Tokens Stop

Proxmox tickets die after two hours. How go-proxmox went from never re-authenticating to renewing tickets in place, plus the one thing API tokens can't do.

7-Minute Read

Timeline of a Proxmox ticket: valid for two hours server side, considered stale by the client after one

Back in 2023 I wrote up how go-proxmox logs in and ended it with a shrug. Once a session existed the client never logged in again. A ticket-based client died at the two hour mark. My advice was “use an API token for anything long running”. It took until this month to fix that properly. It took three PRs from two of us to get there. Here’s the whole arc, plus the one place where an API token isn’t an option at all.

go-diskfs: Getting Rock Ridge Working for Joliet on Windows

I fixed Rock Ridge and added Joliet to go-diskfs so a Go-built cloud-init ISO works for cloudbase-init. Then I found out what Windows actually wanted.

10-Minute Read

Sector map of a cidata ISO built by go-diskfs with a Rock Ridge primary tree and a Joliet supplementary tree sharing the same file extents

go-proxmox has had a CloudInit() helper since 2023. It builds a small nocloud ISO in Go, uploads it to Proxmox and mounts it on the VM as a CD-ROM. It’s always worked fine for Linux guests. It has never worked for Windows guests running cloudbase-init. The reason is a filesystem extension from 1995 called Joliet.

Why Rust for PHP Embedding (Not Go)

I'm a Go developer building a PHP application server in Rust. Here's the FFI reasoning, the C wrapper it forced on me, and what cgo actually costs when you measure it.

11-Minute Read

Diagram of a request flowing from hyper/tokio through a Rust blocking thread, a C wrapper with setjmp, and libphp, with SAPI callbacks writing into thread-local C buffers

Go is my primary language and has been for a long time. When I started building ePHPm a few weeks ago the obvious move was to write it in Go. ePHPm is a PHP application server. It’s one binary that embeds the real PHP interpreter (libphp, the Zend engine) and puts an HTTP server, TLS, a KV cache and a database proxy around it. A PHP app doesn’t need nginx + php-fpm + Redis to run. FrankenPHP already proved the “PHP inside a Go web server” idea works, and it works well. I…

Maintaining a Go Tool That Edits /etc/hosts on Every OS

Notes from maintaining goodhosts, the hosts file CLI and Go library behind vagrant-goodhosts and ddev. It covers Windows quirks, a HostsPerLine bug, and making the CLI testable.

8-Minute Read

hosts file output from hostsfile v0.1.4, v0.1.5 and v0.1.6 for the same sequence of adds

Editing the hosts file sounds like the most boring problem in software. Read a text file, add a line, write it back. Then you find out your binary is being shelled out to from a Ruby Vagrant plugin on a Windows laptop with UAC prompts. Your library is being called in a loop by ddev. And every hosts file you parse was hand-edited by somebody at some point. I’ve been maintaining goodhosts for a few years now. I just spent a chunk of the fall and the holidays on it, so here’s what that…

Proxmox Doesn't Know What Type a Number Is

The Proxmox VE API returns the same field as 1234 in one place and "1234" in another. This is why go-proxmox has StringOrInt, StringOrUint64, StringOrFloat64 and IntOrBool instead of plain Go types.

5-Minute Read

Proxmox VE Logo

Every couple of months somebody opens an issue on go-proxmox with a title that starts with json: cannot unmarshal string into Go struct field. The most recent one was fixed last week. It’s the same root cause every time. The Proxmox VE API is backed by Perl, and Perl doesn’t really care whether a scalar is a number or a string. The JSON encoder on the other end writes whatever the scalar happens to be at the time. So a field that is documented as an integer comes back as 2048 from…

Recent Posts

Categories

About

Luther is a 20+ year software veteran specializing in backend APIs and architecting products. Employed at SUSE as the Team Lead for the Containers on Windows Team working on Rancher + Kubernetes