Lunar Vaults Radio
Six always-on music stations streamed from a NAS at home, built so family and friends hear the same song at the same second — with requests, votes, and a shared history. Five small containers, no database in the audio path, and curation that lives in the music library instead of the code.
The music server couldn’t do “together.”
The household music server has no synchronized playback and no group sessions — everyone presses play alone. What it does have is a Radio tab that stores nothing but a URL. So the answer was to produce real radio: continuous streams the server treats like any other station, and every account sees.
That one constraint shaped everything after it. The stations had to be genuinely always-on, cheap enough to run forever on a home NAS, and boring to operate — nothing to start, nothing to schedule, nothing anyone has to remember exists.
Curation is data, not code.
Nothing in the repo decides what plays. Heart a track in a phone client and a sidecar folds it into the right station within about fifteen minutes; add a song to a curated playlist and it moves stations the same way. Changing the music never means changing the system.
The same rule protects requests. A requested track is routed by matching the generated playlists themselves — never by re-deriving each station’s rules in a second place, because a second copy of a rule drifts silently the moment the first one changes.
From a heart to everyone’s speakers.
The mix is a handful of numbers.
Every station is a weighted draw over a few sources — libraries and generated pools. The weights live in one env file, the pools rebuild every fifteen minutes, and the panel here reads the same tuning file the engine publishes: change a number on the server and this page changes with the rotation.
Weight-zero sources are omitted from the rotation entirely rather than drawn rarely — the same rule the engine applies — so what renders here is exactly what can reach the air.
Two failures worth keeping.
The play history has a single-writer rule — one process appends, everything else reads. A container recreation once left the history files owned by the wrong user on the old inode: every station froze its history for five days while every visible surface kept working perfectly. The fix mattered less than its ordering — recreate the container first, then repair the files, or the old mounted copy re-breaks them.
The second was subtler. Playlist stations reloaded on a fifteen-minute timer, and every reload restarts the shuffle — so a small station behaved like drawing with replacement: some tracks played over twenty times while others never came up. The repair was to reload only when the playlist actually changes, which restored a true rotation — every track exactly once per cycle.
Idents you can prove.
Every station plays recorded idents on rotation — synthesized from a script rather than sourced, so they need no licence, are reproducible forever, and duck under the first spoken word exactly like the music beds they sit on.
Anything audible is auditioned before it ships: a montage tool renders song → ident → song with the engine’s real crossfade, so a change is judged by ear rather than by argument.
Numbers from the station itself.
Built once, on the air ever since.