# smolsonic
A tiny, self-contained [Subsonic](http://www.subsonic.org/pages/api.jsp)-compatible
music server written in Rust. Point it at a folder of music, give it a username
and a password in a TOML file, and any Subsonic client can browse and stream
your library.
```
_ _
___ _ __ ___ ___ | |___ ___ _ __ (_) ___
/ __| '_ ` _ \ / _ \| / __|/ _ \| '_ \ | |/ __|
\__ \ | | | | | (_) | \__ \ (_) | | | || | (__
|___/_| |_| |_|\___/|_|___/\___/|_| |_||_|\___|
a tiny Subsonic server in Rust
```
## Features
- One binary, one TOML file, one SQLite database. No external services.
- Built on **actix-web 4** with **sqlx** (SQLite) for storage.
- Library scanner powered by **lofty** — extracts ID3/Vorbis/MP4/etc. tags and
embedded cover art from `mp3`, `flac`, `ogg`, `opus`, `m4a`, `wav`, and more.
- Falls back to `cover.jpg` / `folder.jpg` / `front.jpg` next to the audio file
if there's no embedded picture.
- Stable IDs (`ar-…` / `al-…` / `so-…`) derived from tag content, so re-scans
are idempotent and clients don't lose their bookmarks.
- HTTP `Range` support for proper seeking.
- Subsonic **token auth** (`t = md5(password + salt)`) and plaintext (`p=…`
or `p=enc:<hex>`) both supported.
- CORS is permissive — works directly from web clients.
## Quick start
```sh
# 1. Build
cargo build --release
# 2. Create a config
cp smolsonic.example.toml smolsonic.toml
$EDITOR smolsonic.toml # set music_dir, username, password
# 3. Run
./target/release/smolsonic --config smolsonic.toml
```
On first launch smolsonic scans `music_dir`, creates the SQLite database, and
starts the HTTP server. Point any Subsonic client at
`http://<host>:<port>/rest/…` using the credentials from your TOML file.
## Configuration
`smolsonic.toml` (all keys shown):
```toml
music_dir = "/path/to/your/music" # required
username = "admin" # required
password = "changeme" # required
# Optional — defaults shown
port = 4533
host = "0.0.0.0"
database_path = "smolsonic.db"
covers_dir = "covers"
```
| `music_dir` | Root of your library. Walked recursively. |
| `username` | The single Subsonic user. |
| `password` | Cleartext on disk; used for both token and plaintext auth.|
| `port` | TCP port to bind. |
| `host` | Interface to bind (use `127.0.0.1` to keep it local). |
| `database_path` | Path to the SQLite file. Created if missing. |
| `covers_dir` | Where extracted album art is cached. |
## CLI
```
Usage: smolsonic [OPTIONS]
Options:
-c, --config <CONFIG> Path to the TOML config file [default: smolsonic.toml]
--no-scan Skip the startup library scan
-h, --help Print help
-V, --version Print version
```
Trigger a rescan from a running server with the standard Subsonic endpoint:
```
GET /rest/startScan.view?u=…&t=…&s=…
GET /rest/getScanStatus.view?u=…&t=…&s=…
```
## Supported endpoints
Full navidrome-style endpoint coverage. Both `.view`-suffixed and plain paths
are accepted, on both `GET` and `POST`. Responses are JSON with the Subsonic
envelope (`{"subsonic-response": …}`).
**System** — `ping`, `getUser`, `getMusicFolders`, `getScanStatus`, `startScan`
**Library (ID3 tag browsing)** — `getArtists`, `getArtist`, `getAlbum`,
`getSong`, `getAlbumList2`, `getAlbumList` (alias)
**Library (folder browsing)** — `getIndexes`, `getMusicDirectory`
**Genres** — `getGenres`, `getSongsByGenre`
**Lists** — `getRandomSongs`, `getStarred2`, `getStarred` (alias)
**Playback** — `stream`, `download`, `getCoverArt`, `scrobble`,
`getNowPlaying`, `updateNowPlaying`
**Search** — `search3`, `search2` (alias)
**Playlists** — `getPlaylists`, `getPlaylist`, `createPlaylist`,
`updatePlaylist`, `deletePlaylist`
**Starring** — `star`, `unstar`
**Artist / album info** — `getArtistInfo`, `getArtistInfo2`, `getAlbumInfo`,
`getAlbumInfo2`, `getSimilarSongs`, `getSimilarSongs2`, `getTopSongs`,
`getLyrics`. These return minimal stub shapes (no Last.fm or external lookups).
`GET /` returns a plain-text index of every endpoint and its query params.
## Tested clients
Anything that speaks Subsonic API 1.16.x and prefers tag-based browsing should
work, including Substreamer, play:sub, Symfonium, Tempo, and Sonixd.
## How auth works
Subsonic clients send credentials as query parameters:
- **Token auth** (preferred): `u=<user>&t=<token>&s=<salt>` where
`token = md5(password + salt)`.
- **Plaintext**: `u=<user>&p=<password>` or `p=enc:<hex-encoded password>`.
`smolsonic` accepts both. The single user/password come from the TOML config.
There's no user database.
## Development
```sh
cargo run -- --config smolsonic.toml
RUST_LOG=debug cargo run -- --config smolsonic.toml
```
Project layout:
```
src/
main.rs entry point
cli.rs clap CLI + neon styling + ASCII banner
config.rs TOML loader
db.rs SqlitePool + schema migrations
models.rs Artist / Album / Song row types
scanner.rs walkdir + lofty + cover art extraction
server/
mod.rs actix App + routing
auth.rs Subsonic token / plaintext auth
response.rs JSON envelope helpers
repo.rs sqlx queries
handlers.rs Subsonic endpoint handlers
```
## License
MIT.