My golf simulator now hosts local LLMs

The story of redmonitor — or how I turned four computers I wasn't using into a tiny private cloud, mostly because my wife wouldn't let me buy another one.

11 min read

I had started using coding agents to build some fun apps. But I had a problem.

I kept running them on my laptop. They'd be happily doing something useful, I'd close the lid to go somewhere, and… that was it.

So naturally I decided I needed an NVIDIA DGX Spark, remote into it, and — obviously — I would need the GPUs one day anyway.

That got declined instantly. My wife pointed out, correctly, that I have bought a lot of machines over the years and use almost none of them.

You already own a ridiculous number of computers and use almost none of them.

This was annoyingly difficult to rebut.

In my defense, one of those computers runs my golf simulator, which gets plenty of use.

Still: DGX denied.

So I did what any cloud person would do after being told not to buy more hardware. I started pricing cloud VMs.

That lasted about ten minutes. A decent always-on machine was around $290/month. A box with a usable GPU was another $500 on top. Then I remembered I'd also need a Mac to build the iPhone app, and cloud Macs bill a 24-hour minimum.

Somewhere north of $1,300 a month, to run side projects that, technically, did not exist yet.

Then I remembered the Asus nuc sitting in a drawer.

Cloud is mostly me paying someone else to make a computer reliable. I already owned the computers. Could I just add the reliability myself?

Problem #1 — reaching it from everywhere#

If the Asus nuc was going to replace my laptop, I needed to reach it from basically anywhere — the office, a coffee shop, an airport, or the Alaska shuttle from PAE to SFO (thanks to Starlink, Elon's greatest gift to travellers).

My first solution was exactly what you'd expect from someone who knew just enough networking to be dangerous: open an SSH port on my router.

It worked! Until Ziply rebooted the router and my public IP changed.

I tried Dynamic DNS. I could never get it to update reliably.

Then it happened again. And again. For a while my remote-access architecture was basically "I think this is my IP address?"

That's when I finally tried Tailscale. I'd actually spoken to Avery, their founder, a while back, but had never had a reason to use it. Turns out this is exactly the reason.

Every machine gets a stable address on my own private network. No exposed SSH. No port forwarding. No reverse proxy. No bastion host. No dynamic DNS nonsense.

Great. Except I still had another problem.

I'd SSH into the nuc, start Claude Code on something, close my laptop… and kill the session. The work was remote. The session was not.

So I had successfully rebuilt the exact problem I was trying to solve, except now there was a nuc involved.

That led me to zellij. Now I can start something, detach, close the laptop, get on a plane, open another machine an hour later and pick up exactly where I left off.

This felt magical for approximately two days. Then I discovered the next problem.

Problem #2 — everything is out of date#

Every time I SSH'd into one of these machines, I did the same stupid ritual.

Update Claude. Update Codex. npm update -g. apt update. apt upgrade. Something else had its own installer. Something else had changed how its installer worked.

Every machine was out of date in its own unique and deeply personal way.

A zellij session with a tab called agents, split into five panes: Claude Code, Antigravity CLI running Gemini, polly the orchestrator for omnigent, OpenAI Codex, and OpenCode — all open at once in one terminal window.
The agents tab, as it actually sits on my machine. Five of them, one window. Every one of these ships something new most weeks — which is the whole problem.

I was spending more time maintaining my AI tools than asking them to do anything.

What I wanted was simple: every machine updates itself, every night, and I never think about it.

Small issue: I had never written a Linux service.

So I asked Claude to help me write one. It worked.

And this was probably the moment the project went off the rails, because I realized something important about coding with AI. The distance between "I wish this existed" and "oh, it exists now" has become dangerously small.

Problem #3 — now I want an iPhone app#

By this point I was building random things for myself. And, naturally, several things for my house that nobody else in my family has ever used.

One of them really wanted to be an iPhone app. And you can't build an iPhone app on a nuc — you need a Mac.

This seemed like an excellent opportunity to revisit the new-computer discussion.

It was not.

So I resurrected an old Mac desktop instead — the one I'd stopped using the day I could no longer check work email from it.

And now the Mac needed all of this setup again. Tailscale. zellij. The agents. The update service. Every tool, the whole shell, all of it. Arrrgghhh.

Doing this once is fun. Doing it twice is irritating. Doing it three times means you've accidentally invented infrastructure.

So I asked my AI — by this point genuinely a buddy — how normal people solve this. That's how I discovered mise, chezmoi and the entire universe of dotfiles.

And this is one of my favorite things about using AI for coding. Yes, it writes code. But increasingly the useful part is: "Don't write that. Someone already solved it. Use this." I would never have found half these tools on my own.

Now the machine sets itself up. Tool versions live in mise. Config lives in dotfiles. Adding a CLI to every machine is basically one line.

Which of course meant I now wanted more machines.

Problem #4 — SSH is great until you need a mouse#

SSH handles 95% of what I need. The other 5% is always something incredibly stupid.

Approve this dialog. Log into this website. Click this button. Convince macOS that yes, I really do want to run the thing I just told it to run.

I tried Moonlight. I tried Sunshine. Eventually I landed on RustDesk.

Now, from my phone, I can get an actual screen on any of these machines when I need one. I use it maybe a few times a month, which is exactly how often you want to use remote desktop.

Problem #5 — wait, is the code even on this machine?#

Once I had multiple machines, another very dumb problem appeared. I'd connect to one and realize the repo I wanted wasn't there. Or worse: it was there, but three weeks old.

An AI coding agent is substantially less impressive when it is confidently editing the wrong version of your code.

So repos became fleet-level configuration too. The system knows which repos belong on which machines. Missing? Clone it. Clean? Pull it. Dirty? Leave it alone and yell at me.

That last one is important. I am perfectly capable of destroying my own work. I don't need my infrastructure helping.

Problem #6 — obviously we need local LLMs#

At some point I decided I wanted to run models locally. Mostly because I was curious. Partly because inference costs money. And partly because, deep down, I was still trying to justify buying the DGX.

By now, however, I knew exactly how that conversation was going to go.

Then I remembered something. My golf simulator has GPUs. Pretty good GPUs. And unless I am playing golf, they are doing absolutely nothing.

So now my golf simulator runs vLLM.

I find this disproportionately entertaining. During the day it runs local models. At night I can go hit terrible drives into a screen. This may be the most efficiently utilized computer I own.

The DGX can wait. For now.

Problem #7 — I have accidentally created a fleet#

At this point I had four machines. They were updating themselves. Syncing configuration. Pulling repos. Running agents. Running models.

And I realized I had absolutely no idea whether any of it was actually working.

I had created a tiny datacenter in my house and then implemented the classic datacenter monitoring strategy: hope.

So I needed monitoring. That became redmonitor.

But I don't actually want monitoring. I don't want another dashboard to stare at. I want to completely forget these machines exist — and then, only when forgetting them becomes unsafe, I want them to bother me.

That distinction ended up shaping the whole thing.

Each machine runs a tiny Go agent. It sends lightweight state every minute and heavier state every ten minutes into Postgres on Supabase. Then I did something that would probably offend someone who builds monitoring systems professionally.

I made Postgres the backend.

A layered architecture diagram. Top: the iPhone app, web dashboard and redmonitor CLI. Middle: Postgres on Supabase holding hosts, alerts and commands. Bottom: a Tailscale tailnet containing the red cow home network on Ziply Fiber and a UniFi router, with the golf simulator, asus nuc and desktop inside it, and the roaming laptop outside. Four arrows push state up every 60 seconds; one dashed arrow carries commands back down. Underneath, one shared list of everything installed on every machine, grouped into coding agents, redmonitor, fleet plumbing, terminal and cloud CLIs.A layered architecture diagram. Top: the iPhone app, web dashboard and redmonitor CLI. Middle: Postgres on Supabase holding hosts, alerts and commands. Bottom: a Tailscale tailnet containing the red cow home network on Ziply Fiber and a UniFi router, with the golf simulator, asus nuc and desktop inside it, and the roaming laptop outside. Four arrows push state up every 60 seconds; one dashed arrow carries commands back down. Underneath, one shared list of everything installed on every machine, grouped into coding agents, redmonitor, fleet plumbing, terminal and cloud CLIs.
The whole thing. Four machines on one private network push up, three clients read down, and commands travel back the other way.

Alerts? SQL and pg_cron. Email? Postgres to pg_net to Resend. Scheduled jobs? Rows. History? A table. Commands? Also rows.

No API server. No job service. No separate alerting system. For four machines, Postgres is plenty.

The iPhone app, web dashboard and CLI are basically just different views over the same state.

And the system knows that machines are different. My laptop being asleep is normal. The nuc disappearing is not. Half of good monitoring, I've discovered, is deciding what not to wake me up about.

And when something does need me, it finds me. I use Resend to send the email — and there is no mail service in between. A Postgres function calls pg_net, pg_net calls Resend, and the alert is in my inbox about a second after the row changed.

A RedMonitor alert email in Gmail from alerts@galvyn.xyz. Subject: resolved, red-studio load 16.8 on 10 cores, five-minute average. It lists host, kind, severity and the resolved timestamp, with an Open alerts button.
A real one. This fired and resolved itself while I was writing the post.

Monitoring wasn't enough#

There was one thing I hated about every monitoring system I looked at. They tell you what's wrong. Then you have to SSH somewhere to fix it.

So I made redmonitor work both ways.

I tap something on my phone. A command gets written into Postgres. The right machine claims it, runs it, streams the logs back, and marks it complete.

One command, end to end. Nothing here is polling a machine — the host picks the row up itself, and the lease expires if it dies mid-job.

So from bed I can update every agent, pull every repo, clone something missing, restart things, or kick off maintenance.

And yes, bed is where I notice most infrastructure problems.

Here is what it actually looks like#

redmonitor.galvyn.xyz
The redmonitor fleet dashboard showing four machines as cards, each with CPU, memory and disk bars, agent version, and running agent sessions.
All four, live. The golf simulator reports its GPU load and temperature; two machines show the coding agent sessions currently running on them.
The same fleet view on a phone, with the laptop showing as asleep on 51% battery.
The same fleet on my phone — scroll it. Note the laptop at the bottom: asleep, on battery, and deliberately not an alert.

The state of the fleet, right now#

Since this is a post about knowing whether four computers are fine, here is what they were doing when I published it. Names redacted, everything else real.

All four reporting · agent 0.11.0
The golf simulatorup 6dLinux (WSL) · 24 cores · 1 GPUcpu 0.4% mem 4.9% of 63 GB disk 7.9% of 1.0 TB
The Asus nucup 6dLinux · 18 corescpu 1.9% mem 21% of 31 GB disk 24% of 0.5 TB
The desktopup 18dmacOS · 10 corescpu 9.1% mem 69% of 64 GB disk 1.3% of 0.9 TB
The laptopup 6dmacOS · 16 corescpu 2.7% mem 51% of 64 GB disk 1.3% of 0.9 TB
Pulled from the same Postgres rows the dashboard reads. The desktop has been up 18 days; everything else restarted for an agent release.

The stupid part: it costs about $14/month#

The four machines together average around 150 watts. That works out to roughly $14/month in electricity at Seattle-area rates.

The rough cloud equivalent, always-on, is around $1,300/month — $294 for an 8 vCPU box, $40 of storage, $470 for a Mac to build the app on, and $516 for a single-L4 GPU instance.

$14 electricityversus$1,300 rented

Now, before every cloud economist on the internet emails me: yes. I know. I wouldn't actually leave four cloud machines running 24/7. I could spin them down. I could schedule them. I could use spot instances.

And then I would be spending my evenings managing cloud instance lifecycle for the side projects I supposedly built all this infrastructure to work on.

That's the actual comparison.

My workloads are ridiculously bursty. Claude Code works furiously for twenty minutes. Then I stare at its diff for an hour wondering what it did. A VM bills for both. A computer I already own doesn't care.

My favourite line item: cloud Macs bill a 24-hour minimum, because of Apple's licence terms. An impressive billing model for a machine I need for a 20-minute iPhone build.

The weirdest part is how I built it#

I built essentially all of this with coding agents — mostly claude, but occasionally codex, omnigent, antigravity and opencode. This post too. The ideas, questionable architectural decisions, unnecessary complexity and bugs are mine. They just made them happen much faster.

And that is probably the bigger story for me.

When I started, I didn't know how to write a Linux service. If I'd had to stop and properly learn systemd, Go, SwiftUI, Postgres scheduling, remote execution and all the other pieces first, I simply would not have built this.

But when the cost of trying something drops low enough, you stop asking "Do I know how to build this?" and start asking "What would the right solution look like?"

That is a much more interesting way to build.

And yes, I screwed it up#

For weeks, three machines reported services as down. None of them were down.

One service had been renamed. One didn't exist on any machine. One was a macOS helper for a GUI app I don't even run.

My personal favorite: at one point the redmonitor agent reported itself as down. The report telling me the agent wasn't running had, of course, been generated and uploaded by… the agent.

Very useful.

But the real bug wasn't the bad probe. It was that I'd gotten used to seeing the false alarm. I stopped noticing it.

A permanent false alarm is just training for ignoring a real one.

So now a probe can't exist unless the thing it's probing actually exists on that machine.

There is also one architectural flaw I cannot Claude my way around: my house. If my internet dies, power dies, or something sufficiently exciting happens to the house, the whole "private cloud" disappears. AWS has me there.

The actual test#

Eventually I realized the test wasn't whether I could make four random computers behave. The test was: what happens when I add machine #5?

The answer now is one command. It joins the private network. Gets the same tools. Gets my shell. Gets the repos it needs. Starts updating itself. Starts reporting into redmonitor.

And becomes boring.

Which is actually what I wanted all along.

I didn't want a homelab. I didn't particularly want a monitoring dashboard. I definitely didn't want to become the sysadmin for four computers in my house.

I wanted to be able to plug in a computer and have it immediately become one of mine.

Although, if anyone has a compelling argument for why machine #5 should be a DGX Spark, please send it to my wife.

AMA

Ask me anything about this post. I'm answering in the thread on X, over here. How it's wired, what I'd do differently, or why I didn't just rent the VM. Ask about the ones I abandoned, too.