IoT · home lab · local-first AI · Tampere

SpinStack: scrobbling Sonos to yourself

SpinStack: scrobbling Sonos to yourself

A few weeks ago I put SpinStack out into the world, the Sonos scrobbler I’ve been building for myself for a while.

Quick recap if you missed the announcement post: it watches what’s playing on your Sonos and logs it locally. Your listening history, on your own hardware, no third party holding it.

It stayed a personal tool for a long time. Then I started getting LinkedIn messages from people in the industry who wanted to run it themselves, so I cleaned it up and shipped it.

This post is the other half. Less “what is it” and more “how is it actually built”, for the people who care about that kind of thing.

Why a backend and a frontend, and why Python

SpinStack is two pieces that don’t share much.

A Python backend that talks to Sonos, polls what’s playing, normalizes metadata, and writes history to SQLite. It runs wherever your Sonos speakers are on the network.

A HTML/JS dashboard served by FastAPI itself, Jinja templates for the pages, a sprinkle of vanilla JS for the bits that need to update without a refresh. Stats, history, the views that make the data worth looking at.

I didn’t split them because I love microservices. Sonos’s local control surface is UPnP/SOAP (soco wraps it), not a clean REST API, and Python is just the right tool for talking to fiddly network protocols without losing a weekend to it. FastAPI is fast for “I need auth, routing, and templating before I can show anyone anything.” Boring solved problems so I can spend time on the actual hard parts.

The two halves share a process and the same SQLite databases. Nothing fancy. If I ever need to swap the dashboard out for something heavier, the data layer is already separated.

Why self-hosted only

There’s no hosted version. There won’t be one.

The whole point of SpinStack is owning your data. If I’m running the server, you’re trusting me with your listening history. That’s exactly the thing this is supposed to fix.

The other reason is technical. Sonos devices are local network things. Polling them properly means being on the same LAN as the speakers. Doing that as a hosted multi-tenant service means tunneling into everyone’s home network, doing UPnP discovery through double-NAT, all kinds of architectural pain for very little gain. Not worth it.

So it ships as a zip, hosted on Cloudflare R2, and you run it on whatever you’ve got. Pi, homelab box, the old laptop in the closet. Anything that stays on.

PolyForm Noncommercial, and why not just MIT

I went back and forth on licensing. MIT is the easy default but it lets anyone wrap this in a hosted SaaS and sell it without contributing anything back. GPL has the SaaS loophole everyone knows about. AGPL fixes that but scares off a lot of people who’d otherwise be happy to fork and run it themselves.

PolyForm Noncommercial felt like the right middle ground. Source is open, you can read it, audit it, fork it, run it, modify it, do whatever you want with it for personal or noncommercial use. If someone wants to build a business on top of it, that’s a separate conversation with me. Not a checkbox in a CLA, an actual conversation.

For a side project I want to keep owning and steering, that’s the tradeoff I wanted.

What now

Not much planned specifically. Mostly a function of what people who actually run it come back with. Bugs, edge cases, weird Sonos environments I never saw. That’s the whole reason I put it out in the first place.

If you want to look around, the project lives at spinstack.org. The code’s there too if you want to poke at the parts I glossed over here.

Stay tuned.

← Back to Articles