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

PathWhat 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.ymlThe 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.ymlAdds build: . back for development.
DockerfileBuilds the server image, cross-compiling on the builder's platform for linux/amd64 and linux/arm64.
install.ps1, SoundStorm-Setup.cmdThe Windows installer (with its own window) and the double-clickable file that starts it.
install.shThe Mac and Linux installer, plain /bin/sh so it runs on dash.
tailscale-serve.jsonThe Tailscale sidecar's proxy config, written on every install because compose bind-mounts it.
.github/workflows/publish.ymlCI: checks, then publishes the image to the GitHub container registry.
CLAUDE.mdThe 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

httpapiSoundStorm's only published surface: every route, its middleware and handlers.
webuiServes the browser UI out of the binary, with its Content-Security-Policy, the service worker and the manifest.
authSign-in, sessions, device cookies and the throttle.
servetlsTLS without anybody running openssl: a local authority, one port for HTTP and TLS, real certificates in auto mode.
acmeGets a certificate from Let's Encrypt over DNS-01 - RFC 8555 for one account and one name.
namesGives every install a real name, so it can have a real certificate (the service's logic).
portmapOpens one inbound port on a home router (UPnP) for remote access, and reads its WAN address.

Backends

sourceThe plug point for a backend: the Source interface, the optional capability interfaces (Streamer, MusicBrowser, Rescanner...), access in the context, and the Registry.
source/subsonicNavidrome, over OpenSubsonic - including the folder view of artists and albums.
source/jellyfinJellyfin, registered twice (films and TV).
source/audiobookshelfAudiobookshelf: books, tracks, chapters, per-person positions.
source/immichImmich, acting as the person asking.
source/localbooksThe ebook and documents shelves SoundStorm reads itself.
source/storytellerStoryteller, for read-along timelines.
source/audiomuseAudioMuse-AI's sound analysis, for moods and "sounds like".
source/opdsAn opt-in Calibre server running elsewhere.
provisionGets SoundStorm credentials on each backend without a human, and per-person accounts where needed.
federateFans a query out across every source and merges the answers.
mediaThe vocabulary every backend is translated into: Item, Kind, Less, ShelfCache.
httpxA small hardened HTTP client shared by adapters: no dot segments, no absolute references, credentials redacted from errors.
streamPipes media bytes from a backend to the device: slim headers, pacing, resized covers, content guards.

Files and formats

libraryOwns the folder layout: creating it, deciding where a dropped file goes, saving uploads, the bin. Not an indexer.
tagsReads just enough of an audio file (ID3, MP4 atoms, Vorbis comments) to know where to file it.
epubReads metadata and resources out of EPUB files, with zip-bomb guards.
pdfReads what metadata a PDF is willing to admit to - XMP, then the Info dictionary, then the file name.
flacDecodes FLAC, just enough to hear a song's beats on the server.
photoimportBrings a photo library in from Google, Apple and others: dates, places, duplicates.
starterShips one item per shelf inside the binary, unpacked once.
qrDraws the QR code a TV shows for signing in from a phone - byte mode, level M, no dependency.

Per person, and music

statePersists the little SoundStorm must remember: credentials, accounts, sessions, decisions.
collectionsEach person's favorites, playlists, history, listens and own covers.
beatsHears a song the way the visualizers want it heard: beats, loudness, hits.
lyricsFinds lyrics a song's files do not have, on LRCLIB (opt-in).
discoverFinds out about an artist from open music services (opt-in).
scrobbleSends plays to a person's own ListenBrainz account.
plexBrings playlists across from a Plex server.
trainingLearns 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.shRuns the ACME client against Pebble, Let's Encrypt's test authority, in CI.
beats-parity.jsRuns the web app's own hearSong under Node on the Go test's samples, to prove the server hears songs identically.
check-images.pyVerifies every committed PNG against its per-chunk CRC32 and decoded size.
fetch-starter-media.shFetches and records how each starter-library file is made.
fetch-test-library.ps1Builds a ~750 MB library of real public-domain and CC media for testing.
make-sample-media.ps1A synthetic library with no downloads.
make-icons.pyDraws every icon from the cloud's three shapes. Change icons here, not in the PNGs.
mkepubGenerates test EPUBs without Calibre.
mobile-check.jsPlaywright: mobile layouts and overflow.
reader-swipe-check.jsPlaywright: swiping the reader turns pages exactly as the arrows do.
calibreweb-initSupport 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.

Every dependency is code that runs with the server's privileges, in a binary that sits on people's home networks and holds admin credentials for their media servers. Each parser here reads only what answers one question - "where should this file go", "what is this book called" - and is deliberately not a general library. The cost is real (the tag reader's history of syncsafe integers and unsynchronization is a list of edge cases found the hard way), but it is paid once, with tests built from real files, instead of carried as supply-chain risk for ever.

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:

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.