The repository
One repository, one long-lived branch, holding the server, the web app, the Android, iPhone and Apple TV apps, the installers and this site. This page lists every top-level folder and every server package, each in a line, so you know where to look.
Top level
| Path | What it is |
|---|---|
cmd/soundstorm/ | The server's main, plus its subcommands: backup, restore, reset-password, train-looks. Any argument it does not know is an error, never a second server. |
cmd/soundstorm-names/ | The names service: gives each install a name under soundstorm.dev and publishes ACME challenges. Deployed separately, with its own Dockerfile. |
internal/ | Every server package (below). |
android/ | The Android app (Kotlin): phones, Google TV, Android TV and Fire TV. |
ios/ | The Xcode project: the iPhone app, the Apple TV app (SoundStormTV/), shared code and UI tests. |
library/ | The folder skeleton with a README.txt per shelf, so a fresh clone has somewhere to put media. Contents are ignored by git. |
scripts/ | Development tools (below). |
docs/ | Older Markdown guides and the README's screenshots. Some of it predates later changes; the working notes and the code are the reference. |
site/ | This documentation site: plain HTML, one stylesheet, one script, no build step. |
docker-compose.yml | The install: SoundStorm and every backend, naming a published image and containing no build: key, so it works alone in an empty folder. |
docker-compose.dev.yml | Adds build: . back for development. |
Dockerfile | Builds the server image, cross-compiling on the builder's platform for linux/amd64 and linux/arm64. |
install.ps1, SoundStorm-Setup.cmd | The Windows installer (with its own window) and the double-clickable file that starts it. |
install.sh | The Mac and Linux installer, plain /bin/sh so it runs on dash. |
tailscale-serve.json | The Tailscale sidecar's proxy config, written on every install because compose bind-mounts it. |
.github/workflows/publish.yml | CI: checks, then publishes the image to the GitHub container registry. |
CLAUDE.md | The project's working notes: every decision, reversal and trap, in detail. The main source for this site. |
Server packages
Grouped by role. Each line is the package's own opening sentence, lightly shortened.
The surface
httpapi | SoundStorm's only published surface: every route, its middleware and handlers. |
webui | Serves the browser UI out of the binary, with its Content-Security-Policy, the service worker and the manifest. |
auth | Sign-in, sessions, device cookies and the throttle. |
servetls | TLS without anybody running openssl: a local authority, one port for HTTP and TLS, real certificates in auto mode. |
acme | Gets a certificate from Let's Encrypt over DNS-01 - RFC 8555 for one account and one name. |
names | Gives every install a real name, so it can have a real certificate (the service's logic). |
portmap | Opens one inbound port on a home router (UPnP) for remote access, and reads its WAN address. |
Backends
source | The plug point for a backend: the Source interface, the optional capability interfaces (Streamer, MusicBrowser, Rescanner...), access in the context, and the Registry. |
source/subsonic | Navidrome, over OpenSubsonic - including the folder view of artists and albums. |
source/jellyfin | Jellyfin, registered twice (films and TV). |
source/audiobookshelf | Audiobookshelf: books, tracks, chapters, per-person positions. |
source/immich | Immich, acting as the person asking. |
source/localbooks | The ebook and documents shelves SoundStorm reads itself. |
source/storyteller | Storyteller, for read-along timelines. |
source/audiomuse | AudioMuse-AI's sound analysis, for moods and "sounds like". |
source/opds | An opt-in Calibre server running elsewhere. |
provision | Gets SoundStorm credentials on each backend without a human, and per-person accounts where needed. |
federate | Fans a query out across every source and merges the answers. |
media | The vocabulary every backend is translated into: Item, Kind, Less, ShelfCache. |
httpx | A small hardened HTTP client shared by adapters: no dot segments, no absolute references, credentials redacted from errors. |
stream | Pipes media bytes from a backend to the device: slim headers, pacing, resized covers, content guards. |
Files and formats
library | Owns the folder layout: creating it, deciding where a dropped file goes, saving uploads, the bin. Not an indexer. |
tags | Reads just enough of an audio file (ID3, MP4 atoms, Vorbis comments) to know where to file it. |
epub | Reads metadata and resources out of EPUB files, with zip-bomb guards. |
pdf | Reads what metadata a PDF is willing to admit to - XMP, then the Info dictionary, then the file name. |
flac | Decodes FLAC, just enough to hear a song's beats on the server. |
photoimport | Brings a photo library in from Google, Apple and others: dates, places, duplicates. |
starter | Ships one item per shelf inside the binary, unpacked once. |
qr | Draws the QR code a TV shows for signing in from a phone - byte mode, level M, no dependency. |
Per person, and music
state | Persists the little SoundStorm must remember: credentials, accounts, sessions, decisions. |
collections | Each person's favorites, playlists, history, listens and own covers. |
beats | Hears a song the way the visualizers want it heard: beats, loudness, hits. |
lyrics | Finds lyrics a song's files do not have, on LRCLIB (opt-in). |
discover | Finds out about an artist from open music services (opt-in). |
scrobble | Sends plays to a person's own ListenBrainz account. |
plex | Brings playlists across from a Plex server. |
training | Learns the looks from the developer's own taps (one install only). |
The apps' code
The web app is in internal/webui/assets: index.html,
app.js (the whole product, every media type), style.css,
reader.js, sw.js and sw-register.js (kept in a file because
the CSP forbids inline script), and vendor/. It is embedded in the Go binary with
go:embed.
Android (android/app/src/main/java): MainActivity (the
web view, system bars, back), PageScript (injected before the page's own scripts),
NativeAudio and AudioService (songs through Media3 ExoPlayer),
PlaybackService and MediaBridge (media session),
PhotoBackup (WorkManager jobs), ServerAddress,
WebCookies.
iOS (ios/): SoundStorm/ is the iPhone shell
(ConnectViewController, WebViewController); SoundStormTV/ the
native Apple TV app (player, reader, photo viewer, all twelve visualizers, song analysis);
Shared/ what both use; SoundStormUITests/ the XCUITest that drives the
phone app.
Scripts
acme-rehearsal.sh | Runs the ACME client against Pebble, Let's Encrypt's test authority, in CI. |
beats-parity.js | Runs the web app's own hearSong under Node on the Go test's samples, to prove the server hears songs identically. |
check-images.py | Verifies every committed PNG against its per-chunk CRC32 and decoded size. |
fetch-starter-media.sh | Fetches and records how each starter-library file is made. |
fetch-test-library.ps1 | Builds a ~750 MB library of real public-domain and CC media for testing. |
make-sample-media.ps1 | A synthetic library with no downloads. |
make-icons.py | Draws every icon from the cloud's three shapes. Change icons here, not in the PNGs. |
mkepub | Generates test EPUBs without Calibre. |
mobile-check.js | Playwright: mobile layouts and overflow. |
reader-swipe-check.js | Playwright: swiping the reader turns pages exactly as the arrows do. |
calibreweb-init | Support for the opt-in external Calibre server. |
The zero-dependency rule
The Go module has no third-party dependencies and therefore no
go.sum. Password hashing is crypto/pbkdf2 from the standard library; the
ACME client, the FLAC decoder, the tag reader, the EPUB and PDF readers and the HEIC-free image
shrinking are all small and the project's own.
Vendored browser code
Two third-party libraries are checked in under internal/webui/assets/vendor/,
pinned by content, embedded with go:embed, and fetched at no point during a build:
- foliate-js (MIT) renders EPUBs. Writing it was considered and rejected for
the same reason as transcoding:
paginator.js(44 KB) andepubcfi.js(13 KB) are the two genuinely hard parts of a reader, and reflowable text needs content-anchored positions (EPUB CFIs), not page numbers. SoundStorm unzips books on the server, so no zip library runs in the browser. - hls.js (Apache 2.0) plays Jellyfin's HLS where a browser cannot natively. It is loaded lazily and only when needed.
Some app behaviour depends on foliate's internals - for example that its paginator handles
touch swipes and its fixed-layout renderer does not - so reader-swipe-check.js must
be re-run whenever foliate-js is updated.