759 lines
15 KiB
Markdown
759 lines
15 KiB
Markdown
# Subsonic Server Blueprint
|
|
|
|
## Goal
|
|
|
|
Build a self-hosted music server with:
|
|
|
|
- a Subsonic-compatible API for third-party clients
|
|
- a modern web interface inspired by Aonsoku
|
|
- an architecture that is simpler to build than a full Navidrome fork
|
|
- a codebase that can grow into a serious long-term project
|
|
|
|
This project should not try to clone Navidrome feature-for-feature on day one.
|
|
The right approach is:
|
|
|
|
- build a focused backend with strong library and streaming fundamentals
|
|
- build a separate SPA frontend with a polished listening experience
|
|
- support the Subsonic API where it matters
|
|
- keep a separate internal web API for faster frontend development
|
|
|
|
## Reference Direction
|
|
|
|
### What to take from Navidrome
|
|
|
|
- overall product shape: self-hosted music server
|
|
- robust scanning and indexing mindset
|
|
- low-overhead backend architecture
|
|
- Subsonic compatibility strategy
|
|
- cover art, streaming, playlists, favorites, search, multi-user
|
|
|
|
### What to take from Aonsoku
|
|
|
|
- modern visual language
|
|
- frontend stack direction
|
|
- player UX patterns
|
|
- layout ideas for home, album, artist, playlist, queue, lyrics
|
|
- separation between data fetching, UI state, and playback state
|
|
|
|
### What not to copy directly
|
|
|
|
- do not embed the frontend tightly into the backend from day one
|
|
- do not aim for full Subsonic coverage immediately
|
|
- do not duplicate Navidrome's whole surface area before the core works well
|
|
|
|
## Recommended Stack
|
|
|
|
## Backend
|
|
|
|
- Language: Go
|
|
- HTTP router: `chi`
|
|
- Config: `viper` or a smaller env-based config layer
|
|
- Database: SQLite for MVP
|
|
- Migrations: `goose` or `atlas`
|
|
- Tag reading: a mature tag library for MP3/FLAC/M4A/OGG metadata
|
|
- Background jobs: in-process worker loops
|
|
- File watching: `fsnotify` or polling fallback
|
|
- Auth: session + token support
|
|
- Logging: `slog` or `logrus`
|
|
|
|
## Frontend
|
|
|
|
- Framework: React
|
|
- Build tool: Vite
|
|
- Language: TypeScript
|
|
- Routing: React Router
|
|
- Server state: TanStack Query
|
|
- Client state: Zustand
|
|
- UI primitives: Radix UI
|
|
- Styling: Tailwind CSS
|
|
- Forms: React Hook Form + Zod
|
|
- Audio playback: HTMLAudioElement first, optional HLS/transcoding later
|
|
|
|
## Infra
|
|
|
|
- Single binary backend for local/self-hosted use
|
|
- Separate frontend app built into static assets
|
|
- Docker support early
|
|
- Local dev via `docker-compose` or separate dev servers
|
|
|
|
## High-Level Architecture
|
|
|
|
```text
|
|
music files
|
|
-> scanner
|
|
-> metadata extractor
|
|
-> database index
|
|
-> artwork cache
|
|
|
|
clients
|
|
-> web SPA
|
|
-> third-party Subsonic clients
|
|
|
|
backend
|
|
-> auth
|
|
-> library API
|
|
-> Subsonic compatibility API
|
|
-> streaming/transcoding
|
|
-> playlist/favorites/history
|
|
```
|
|
|
|
## Recommended Repository Layout
|
|
|
|
```text
|
|
/
|
|
apps/
|
|
web/
|
|
cmd/
|
|
server/
|
|
internal/
|
|
auth/
|
|
config/
|
|
db/
|
|
httpapi/
|
|
library/
|
|
media/
|
|
playlist/
|
|
scanner/
|
|
subsonic/
|
|
users/
|
|
migrations/
|
|
assets/
|
|
scripts/
|
|
deploy/
|
|
docs/
|
|
```
|
|
|
|
## API Strategy
|
|
|
|
Use two APIs.
|
|
|
|
### 1. Subsonic-compatible API
|
|
|
|
Purpose:
|
|
|
|
- support existing clients
|
|
- preserve compatibility expectations
|
|
- allow easy migration from other Subsonic servers
|
|
|
|
Examples:
|
|
|
|
- `/rest/ping`
|
|
- `/rest/getLicense`
|
|
- `/rest/getArtists`
|
|
- `/rest/getArtist`
|
|
- `/rest/getAlbum`
|
|
- `/rest/getSong`
|
|
- `/rest/stream`
|
|
- `/rest/getCoverArt`
|
|
- `/rest/search3`
|
|
- `/rest/getRandomSongs`
|
|
- `/rest/getStarred2`
|
|
- `/rest/star`
|
|
- `/rest/unstar`
|
|
- `/rest/createPlaylist`
|
|
- `/rest/updatePlaylist`
|
|
- `/rest/getPlaylists`
|
|
- `/rest/getPlaylist`
|
|
- `/rest/scrobble`
|
|
|
|
### 2. Internal web API
|
|
|
|
Purpose:
|
|
|
|
- reduce frontend complexity
|
|
- return cleaner payloads than Subsonic XML/legacy JSON
|
|
- aggregate data for fast UI screens
|
|
|
|
Examples:
|
|
|
|
- `GET /api/me`
|
|
- `GET /api/home`
|
|
- `GET /api/artists`
|
|
- `GET /api/artists/:id`
|
|
- `GET /api/albums/:id`
|
|
- `GET /api/tracks/:id`
|
|
- `GET /api/playlists`
|
|
- `GET /api/playlists/:id`
|
|
- `POST /api/queue/scrobble`
|
|
- `GET /api/search?q=...`
|
|
- `GET /api/browse/recent`
|
|
- `GET /api/browse/random`
|
|
- `POST /api/favorites/:id`
|
|
- `DELETE /api/favorites/:id`
|
|
|
|
## Data Model
|
|
|
|
Core entities:
|
|
|
|
- users
|
|
- artists
|
|
- albums
|
|
- tracks
|
|
- genres
|
|
- album_art
|
|
- playlists
|
|
- playlist_tracks
|
|
- favorites
|
|
- play_history
|
|
- scan_jobs
|
|
- library_roots
|
|
|
|
Important fields:
|
|
|
|
### users
|
|
|
|
- id
|
|
- username
|
|
- password_hash
|
|
- is_admin
|
|
- last_login_at
|
|
- created_at
|
|
|
|
### artists
|
|
|
|
- id
|
|
- name
|
|
- sort_name
|
|
- album_count
|
|
- song_count
|
|
- biography
|
|
- cover_art_id
|
|
|
|
### albums
|
|
|
|
- id
|
|
- artist_id
|
|
- title
|
|
- sort_title
|
|
- year
|
|
- genre
|
|
- cover_art_id
|
|
- track_count
|
|
- duration_seconds
|
|
- album_artist
|
|
- release_type
|
|
|
|
### tracks
|
|
|
|
- id
|
|
- album_id
|
|
- artist_id
|
|
- title
|
|
- track_number
|
|
- disc_number
|
|
- year
|
|
- genre
|
|
- duration_seconds
|
|
- bitrate
|
|
- sample_rate
|
|
- file_path
|
|
- file_size
|
|
- suffix
|
|
- content_type
|
|
- lyrics_embedded
|
|
- cover_art_id
|
|
- created_at
|
|
- updated_at
|
|
|
|
### playlists
|
|
|
|
- id
|
|
- user_id
|
|
- name
|
|
- comment
|
|
- public
|
|
- created_at
|
|
- updated_at
|
|
|
|
## Phased Delivery Plan
|
|
|
|
## Phase 0: Foundation
|
|
|
|
Outcome:
|
|
|
|
- repo initialized
|
|
- dev tooling set up
|
|
- backend and frontend boot independently
|
|
|
|
Deliverables:
|
|
|
|
- monorepo or single repo with `apps/web` and Go backend
|
|
- linting and formatting
|
|
- basic CI
|
|
- environment examples
|
|
- Dockerfile and local compose
|
|
|
|
## Phase 1: Library Core
|
|
|
|
Outcome:
|
|
|
|
- server can scan a music folder and build a usable index
|
|
|
|
Deliverables:
|
|
|
|
- config for music folder paths
|
|
- scanner for recursive file discovery
|
|
- metadata extraction
|
|
- SQLite schema
|
|
- initial scan endpoint/job
|
|
- rescan changed files
|
|
- artist/album/track records
|
|
- cover art extraction or sidecar loading
|
|
|
|
## Phase 2: Playback Core
|
|
|
|
Outcome:
|
|
|
|
- music can be streamed and played in the web app
|
|
|
|
Deliverables:
|
|
|
|
- authenticated stream endpoint
|
|
- range requests
|
|
- MIME/content-type handling
|
|
- artwork endpoint
|
|
- queue and now-playing state in frontend
|
|
- album/artist pages
|
|
- playable track lists
|
|
|
|
## Phase 3: Subsonic Compatibility MVP
|
|
|
|
Outcome:
|
|
|
|
- major Subsonic clients can connect
|
|
|
|
Deliverables:
|
|
|
|
- auth handshake support
|
|
- required response envelope
|
|
- core artist/album/song endpoints
|
|
- stream endpoint compatibility
|
|
- cover art compatibility
|
|
- search support
|
|
- favorites support
|
|
- playlists support
|
|
|
|
## Phase 4: Product UX
|
|
|
|
Outcome:
|
|
|
|
- the web app feels like a polished daily driver
|
|
|
|
Deliverables:
|
|
|
|
- home page
|
|
- recent albums
|
|
- random picks
|
|
- favorites
|
|
- search experience
|
|
- playlists
|
|
- mini player + full player
|
|
- keyboard shortcuts
|
|
- responsive layout
|
|
|
|
## Phase 5: Power Features
|
|
|
|
Outcome:
|
|
|
|
- project becomes meaningfully competitive
|
|
|
|
Deliverables:
|
|
|
|
- on-the-fly transcoding
|
|
- lyrics
|
|
- listening history
|
|
- scrobble
|
|
- share links
|
|
- radio/mixes
|
|
- folder browsing
|
|
- multi-library
|
|
- admin dashboard
|
|
|
|
## Frontend Product Shape
|
|
|
|
## Core Screens
|
|
|
|
- login
|
|
- home
|
|
- artists list
|
|
- artist detail
|
|
- album detail
|
|
- playlist detail
|
|
- search
|
|
- queue
|
|
- settings
|
|
- full-screen player or expanded player panel
|
|
|
|
## UX Goals
|
|
|
|
- art-forward layout
|
|
- fast navigation
|
|
- minimal friction to start playback
|
|
- stable queue behavior
|
|
- good empty/loading/error states
|
|
- pleasant desktop and mobile behavior
|
|
|
|
## Visual Direction
|
|
|
|
- dark-first listening UI is acceptable, but keep theme system flexible
|
|
- strong album art presence
|
|
- large typography for hero sections
|
|
- compact dense lists where appropriate
|
|
- smooth transitions for queue and player states
|
|
- do not overbuild visual effects before playback and browsing feel solid
|
|
|
|
## Backend Module Breakdown
|
|
|
|
## `internal/config`
|
|
|
|
Responsibilities:
|
|
|
|
- env/file config loading
|
|
- defaults
|
|
- path validation
|
|
|
|
## `internal/db`
|
|
|
|
Responsibilities:
|
|
|
|
- DB connection
|
|
- migrations
|
|
- query helpers
|
|
- transactional boundaries
|
|
|
|
## `internal/scanner`
|
|
|
|
Responsibilities:
|
|
|
|
- directory crawl
|
|
- changed file detection
|
|
- deleted file cleanup
|
|
- scheduling rescans
|
|
|
|
## `internal/library`
|
|
|
|
Responsibilities:
|
|
|
|
- domain services for artists/albums/tracks
|
|
- browse/search logic
|
|
- cover art association
|
|
|
|
## `internal/media`
|
|
|
|
Responsibilities:
|
|
|
|
- file streaming
|
|
- range requests
|
|
- transcoding hooks
|
|
- content-type detection
|
|
- artwork serving
|
|
|
|
## `internal/subsonic`
|
|
|
|
Responsibilities:
|
|
|
|
- request parsing
|
|
- auth compatibility
|
|
- response shaping
|
|
- endpoint mapping from internal domain services
|
|
|
|
## `internal/auth`
|
|
|
|
Responsibilities:
|
|
|
|
- password hashing
|
|
- sessions/tokens
|
|
- permissions
|
|
|
|
## `internal/httpapi`
|
|
|
|
Responsibilities:
|
|
|
|
- REST endpoints for the web app
|
|
- JSON response contracts
|
|
- middleware
|
|
|
|
## Detailed MVP Checklist
|
|
|
|
## Project Setup
|
|
|
|
- [x] Choose repository structure and naming
|
|
- [x] Initialize Go module
|
|
- [x] Initialize frontend app in `apps/web`
|
|
- [x] Add root `.editorconfig`
|
|
- [x] Add root `.gitignore`
|
|
- [ ] Add backend formatter/linter commands
|
|
- [ ] Add frontend formatter/linter commands
|
|
- [x] Add shared `Makefile` or task runner
|
|
- [x] Add `.env.example`
|
|
- [x] Add `docker-compose.yml`
|
|
- [x] Add backend `Dockerfile`
|
|
- [ ] Add frontend `Dockerfile` if needed
|
|
- [ ] Add CI workflow for lint/build/test
|
|
|
|
## Backend Bootstrap
|
|
|
|
- [x] Create `cmd/server/main.go`
|
|
- [x] Add config loading
|
|
- [x] Add HTTP server bootstrap
|
|
- [x] Add graceful shutdown
|
|
- [ ] Add structured logging
|
|
- [x] Add health endpoint
|
|
- [x] Add request logging middleware
|
|
- [x] Add panic recovery middleware
|
|
- [x] Add CORS strategy for local dev
|
|
|
|
## Database
|
|
|
|
- [x] Choose migration tool
|
|
- [x] Create initial schema migration
|
|
- [x] Add DB connection setup
|
|
- [x] Add migration runner at startup
|
|
- [x] Add indexes for artist/album/track lookup
|
|
- [ ] Add indexes for search
|
|
- [x] Add repository helpers or query layer
|
|
|
|
## Auth and Users
|
|
|
|
- [x] Create users table
|
|
- [x] Implement password hashing
|
|
- [x] Implement login endpoint
|
|
- [x] Implement session or bearer token issuance
|
|
- [x] Implement auth middleware
|
|
- [x] Implement current user endpoint
|
|
- [x] Implement admin bootstrap user creation
|
|
- [x] Add logout endpoint
|
|
|
|
## Library Scanning
|
|
|
|
- [x] Add library roots table
|
|
- [ ] Add config for one or more music paths
|
|
- [x] Recursively discover supported audio files
|
|
- [x] Ignore unsupported file types
|
|
- [x] Read tags from files
|
|
- [x] Map tags into normalized artist/album/track model
|
|
- [x] Extract embedded artwork when present
|
|
- [x] Load folder sidecar artwork when present
|
|
- [x] Persist scan results transactionally
|
|
- [x] Track deleted files and remove stale DB rows
|
|
- [x] Add initial full scan command
|
|
- [x] Add rescan endpoint or admin action
|
|
- [ ] Add filesystem watch or scheduled scan
|
|
- [ ] Record scan job progress
|
|
- [x] Expose scan status endpoint
|
|
|
|
## Browse API
|
|
|
|
- [x] List artists
|
|
- [x] Artist detail with albums
|
|
- [x] Album detail with tracks
|
|
- [x] Track detail
|
|
- [x] Recent albums
|
|
- [x] Random albums or songs
|
|
- [x] Favorites listing
|
|
- [x] Search endpoint
|
|
- [ ] Pagination support
|
|
- [ ] Sorting support
|
|
|
|
## Streaming
|
|
|
|
- [x] Implement authenticated stream endpoint
|
|
- [x] Support range requests
|
|
- [x] Support HEAD where appropriate
|
|
- [x] Return correct content type
|
|
- [x] Handle missing files gracefully
|
|
- [x] Add cover art endpoint
|
|
- [ ] Add basic download endpoint
|
|
|
|
## Playlists and History
|
|
|
|
- [x] Create playlists table
|
|
- [x] Create playlist tracks table
|
|
- [x] Add create playlist endpoint
|
|
- [x] Add rename playlist endpoint
|
|
- [x] Add delete playlist endpoint
|
|
- [ ] Add reorder tracks endpoint
|
|
- [x] Add add/remove track endpoints
|
|
- [x] Add listening history table
|
|
- [x] Record play/scrobble events
|
|
- [x] Add recently played endpoint
|
|
|
|
## Favorites
|
|
|
|
- [x] Add favorites table
|
|
- [x] Star track
|
|
- [x] Unstar track
|
|
- [x] Star album if desired
|
|
- [x] Unstar album if desired
|
|
- [x] Star artist if desired
|
|
- [x] Unstar artist if desired
|
|
|
|
## Subsonic Compatibility
|
|
|
|
- [x] Implement request auth parsing
|
|
- [x] Support username/password auth where needed
|
|
- [x] Support token/salt auth
|
|
- [x] Add common Subsonic response builder
|
|
- [x] Implement `ping`
|
|
- [x] Implement `getLicense`
|
|
- [x] Implement `getArtists`
|
|
- [x] Implement `getArtist`
|
|
- [x] Implement `getAlbum`
|
|
- [x] Implement `getSong`
|
|
- [x] Implement `stream`
|
|
- [x] Implement `getCoverArt`
|
|
- [x] Implement `search3`
|
|
- [x] Implement `getRandomSongs`
|
|
- [x] Implement `getStarred2`
|
|
- [x] Implement `star`
|
|
- [x] Implement `unstar`
|
|
- [x] Implement playlist endpoints
|
|
- [x] Implement `scrobble`
|
|
- [ ] Test against at least one existing Subsonic client
|
|
|
|
## Frontend Bootstrap
|
|
|
|
- [x] Create Vite React TypeScript app
|
|
- [x] Configure routing
|
|
- [x] Configure Tailwind
|
|
- [ ] Add Radix UI primitives
|
|
- [x] Add TanStack Query client
|
|
- [x] Add Zustand stores
|
|
- [x] Add API client layer
|
|
- [x] Add auth persistence strategy
|
|
- [ ] Add theme tokens and CSS variables
|
|
|
|
## Frontend App Shell
|
|
|
|
- [x] Login page
|
|
- [x] App layout with sidebar/topbar/player area
|
|
- [ ] Responsive navigation
|
|
- [ ] Toast/notification system
|
|
- [ ] Error boundary
|
|
- [x] Query loading/error patterns
|
|
|
|
## Frontend Music Views
|
|
|
|
- [x] Home page
|
|
- [x] Artists grid/list page
|
|
- [x] Artist detail page
|
|
- [x] Album detail page
|
|
- [x] Playlist page
|
|
- [x] Search results page
|
|
- [x] Favorites page
|
|
- [ ] Recently played page
|
|
|
|
## Frontend Player
|
|
|
|
- [x] Global player store
|
|
- [x] Queue model
|
|
- [x] Play/pause
|
|
- [x] Next/previous
|
|
- [x] Seek bar
|
|
- [x] Volume control
|
|
- [x] Repeat modes
|
|
- [x] Shuffle
|
|
- [x] Track switching
|
|
- [x] Keyboard shortcuts
|
|
- [x] Mini player
|
|
- [x] Expanded player
|
|
|
|
## Quality and Testing
|
|
|
|
- [ ] Unit tests for scanner parsing
|
|
- [ ] Unit tests for auth
|
|
- [ ] Unit tests for Subsonic envelope formatting
|
|
- [ ] Backend integration tests for browse endpoints
|
|
- [ ] Backend integration tests for stream endpoint
|
|
- [ ] Frontend component tests for player and key pages
|
|
- [ ] Frontend E2E smoke tests
|
|
- [ ] Test with a realistic sample library
|
|
- [x] Test on Windows paths
|
|
- [ ] Test on Linux paths
|
|
- [ ] Test large album art and missing metadata edge cases
|
|
|
|
## Deployment
|
|
|
|
- [x] Build production frontend assets
|
|
- [x] Serve frontend assets from backend or reverse proxy
|
|
- [x] Docker image for all-in-one deployment
|
|
- [x] Persistent DB volume
|
|
- [x] Persistent cache volume
|
|
- [x] Music folder mount strategy
|
|
- [x] Single app port for web UI and Subsonic clients
|
|
- [x] Reverse proxy example
|
|
- [x] HTTP/reverse proxy deployment notes
|
|
- [x] Backup/restore notes
|
|
|
|
## Nice-to-Have After MVP
|
|
|
|
- [ ] On-the-fly transcoding
|
|
- [ ] Multi-bitrate streaming profiles
|
|
- [x] Lyrics support
|
|
- [ ] Last.fm or ListenBrainz scrobbling
|
|
- [ ] Shareable public links
|
|
- [ ] Smart playlists
|
|
- [ ] Radio or generated mixes
|
|
- [ ] Folder view
|
|
- [ ] Multi-library roots with permissions
|
|
- [ ] Podcast support
|
|
- [ ] Admin analytics page
|
|
- [ ] PWA support
|
|
|
|
## Suggested MVP Cut Line
|
|
|
|
If we want the fastest realistic first release, include only:
|
|
|
|
- auth
|
|
- scanner
|
|
- artists/albums/tracks browse
|
|
- cover art
|
|
- streaming
|
|
- search
|
|
- favorites
|
|
- playlists
|
|
- recent albums
|
|
- Subsonic core endpoints
|
|
- polished web player
|
|
|
|
Delay until later:
|
|
|
|
- transcoding
|
|
- lyrics
|
|
- podcasts
|
|
- radio
|
|
- advanced admin tools
|
|
- recommendations and smart mixes
|
|
|
|
## Recommended First Build Order
|
|
|
|
1. Backend bootstrap and DB
|
|
2. Scanner and normalized schema
|
|
3. Browse endpoints
|
|
4. Stream and cover art endpoints
|
|
5. Web login and app shell
|
|
6. Artist/album pages
|
|
7. Queue and player
|
|
8. Search and favorites
|
|
9. Playlists
|
|
10. Subsonic compatibility layer
|
|
11. Docker and deployment polish
|
|
|
|
## Practical Recommendation
|
|
|
|
The best implementation strategy is:
|
|
|
|
- build your own backend
|
|
- use Navidrome as a behavior reference, not a codebase to fork
|
|
- use Aonsoku as a frontend inspiration and partial architectural reference
|
|
- prioritize a strong MVP over broad feature parity
|
|
|
|
If you want, the next step should be scaffolding the actual repository:
|
|
|
|
- Go backend skeleton
|
|
- React frontend skeleton
|
|
- initial DB schema
|
|
- first endpoints
|
|
- first app shell
|
|
```
|