The server

One Go program, cmd/soundstorm, sits between every app and every backend. It signs people in, searches every shelf at once, carries every byte of every song, film and photo, files what is dropped onto the window, and remembers the few things that belong to a person rather than to the media.

One program, no dependencies

The server is a single static binary built from the standard library alone: there is no go.sum, because there are no third-party Go modules - not for password hashing (crypto/pbkdf2), not for the ACME client that gets real certificates, not for the FLAC decoder the beat analysis uses. The web app is embedded in the same binary with go:embed, so the image is the program and nothing else. The only third-party code in the whole project is foliate-js, the EPUB renderer, vendored into the web assets.

It runs in a container beside the backends it drives, as an unprivileged user (uid 10001), with no access to the Docker socket. That last point is a choice, not an omission: a server that cannot drive Docker cannot be turned into a way onto the host, which is also why it cannot update itself - updating is running the installer again.

Besides serving, the same binary carries a few subcommands that run beside a stopped or running server: reset-password, backup, restore and train-looks. Any other argument is an error rather than a second server, because a typo once started two writers on one state file.

What happens at start

The order in main is deliberate: everything a person needs to see the app is done before the port opens, and everything that waits on another container happens in the background afterwards.

  1. The library folders. library.Open makes sure music/, movies/, tv/, audiobooks/, ebooks/, documents/ and pictures/ exist, so a fresh install already has somewhere to put things.
  2. The state file. state.Open reads (and migrates) the one file holding backend credentials, accounts, sessions and a handful of decisions. Never anything about the media itself.
  3. The starter library - one public-domain or CC0 item per stocked shelf, about 17MB embedded in the binary - is unpacked into empty folders once, ever. A flag in the state file records that it happened, so somebody who deletes the samples does not get them back at the next restart.
  4. Housekeeping. Half-finished uploads in the hidden staging folder are cleared, the per-person collections folder is opened, and a daily sweep empties the bin of anything deleted more than thirty days ago.
  5. TLS is loaded in whichever mode SOUNDSTORM_TLS names: off, self-signed (a local authority), file, or auto (a real certificate). An unknown value is fatal - quietly serving plain HTTP to somebody who asked for encryption is the worst way to be wrong.
  6. Provisioning starts in the background. Each backend named in the environment is set up, or reconnected with stored credentials, in its own goroutine. A backend can take a minute to boot, and SoundStorm should be showing setup progress meanwhile, not refusing to start. So sources join the registry as they become ready.
  7. The real certificate is fetched in the background too, in auto mode.
  8. A setup code is printed to the log while no account exists - the first sign-up needs it.
  9. Background loops start: automatic read-along syncing, sending plays to ListenBrainz, hearing every song's beats ahead of time, and sorting imported photo downloads.
  10. The listener opens - on one port that speaks both plain HTTP and TLS in auto mode, told apart by the first byte of each connection.
Every goroutine started once and left running is wrapped in recover(). Go's net/http protects only the goroutine serving a request; a panic anywhere else ends the whole process, for everybody. A parser that trips over one strange file on a timed scan would otherwise crash the server on that file, and again on every restart, because the file is still on disk.

Package map

Packages live under internal/. The rule that keeps them apart is normalization at the edge: only an adapter knows its backend's vocabulary, and everything past it speaks media.Item.

PackageWhat it owns
httpapiEvery route, the middleware chain, rate limits, and the handlers that tie the rest together.
authPassword hashing, sessions and their cookies, device tokens, the sign-in throttle, per-kind access.
stateThe one JSON file: backend credentials, accounts, hashed sessions, reading positions, a few owner decisions.
source, source/*The Source interface and its optional extras, the registry that enforces access, and one adapter each for Navidrome, Jellyfin, Audiobookshelf, Immich, Storyteller, AudioMuse-AI, OPDS and the local book folders.
federateAsking every source at once with a deadline each, and merging the answers into one ordered page.
mediamedia.Item, the one comparator (media.Less) and the shelf cache.
streamProxying media and art: ranges, picture-free song headers, pacing, cover shrinking, active-content guards.
provisionSetting up each backend with nobody logging in, reconnecting after restarts, per-member backend accounts.
libraryThe folders, where a dropped file goes, staging and atomic placement, the bin, free space.
collectionsPer-person favorites, playlists, listening history, the listen log, custom covers, preferences.
servetls, acme, names, portmapCertificates, the in-house ACME client, the name service and its client, router port mapping.
beats, flacHearing each song once on the server for the visualizers, through a FLAC decoder of its own.
tags, epub, pdf, photoimportSmall readers that answer one question each - where to file a file, what a book is called, when a photo was taken.
lyrics, discover, scrobble, plexThe opt-in outside services: LRCLIB, MusicBrainz and friends, ListenBrainz, and Plex playlist import.
starter, webuiThe embedded starter library and web app.

The parts, one page each

HTTP APIRoute groups, the guarded and owner muxes, middleware, rate limits and errors. Accounts and sign-inOwner and member, the setup code, sessions, the throttle and per-shelf access. Search and browsingFederated search, merged paging, Home, Continue, genres, authors and series. StreamingWhy every byte passes through, ranges, slim headers, pacing, covers, HLS. ProvisioningEach backend's first run with nobody logging in, and surviving restarts. The library and dropping files inShelves, where things are filed, staging, duplicates and the bin. Favorites, playlists, historyWhat is kept per person, and why not in the backends. Music: radio, moods, beatsStations, moods from AudioMuse-AI, and hearing every song ahead of time. Photos: folders, backup, importsEveryone's own photos, phone backup, and bringing a library in from Google or Apple. Books and read-alongEbooks SoundStorm reads itself, and pages that turn with the audiobook. Certificates and namesTLS modes, the name service, the ACME client and remote access.