Appearance
Authentication and configuration
Edit .env, then run docker compose up -d to apply container settings. Commands assume a source checkout unless they mention the starter stack.
Authentication
There is no default account. Follow Local install to create the first Docker account.
From a source checkout, run:
sh
bun run auth:create-user --username adminRerun the helper with the same username to reset its password. It revokes that account's sessions (account helper).
MAX_USERS defaults to 1. Raise it before creating additional accounts. Self-registration is disabled by default. Set ALLOW_REGISTRATION=true to allow sign-ups within that limit. Restart the web service after changing it.
API keys
Create scoped keys under API keys. Secrets appear once and are stored hashed. See the key code.
| Credential | Accepted routes |
|---|---|
WORKER_TOKEN | /api/worker/*, plus POST /api/analyze and /api/sync-favorites |
UI-created worker key | /api/worker/* |
PLAYBACK_API_KEY or UI-created playback key | POST /api/play-vibe |
The route guard limits service credentials to those routes.
See the Playback API for mix selection, schedules, and device overrides, or Home Assistant buttons for a complete dashboard configuration.
sh
curl -X POST https://sole.example.com/api/play-vibe \
-H "Authorization: Bearer $PLAYBACK_API_KEY" \
-H 'Content-Type: application/json' \
-d '{}'Embedding settings
Manage → Embedding settings stores the sampling recipe in PostgreSQL. New installs use one window every 0.5 seconds, with at most 90 seconds of audio. Choose the recipe before uploading embeddings. A populated library keeps its existing recipe. The API worker fetches the recipe before inference.
See Embedding workers for remote workers and notebooks. See the Python reference for dependency installation and command options.
Database commands
sh
bun run db:migrate
bun run db:ensure-centered
bun run doctor -- --jsonThe doctor script checks configuration, database access, pgvector, required schema, and the host audio path.
For a legacy database without a Drizzle migration journal, inspect its schema first. Run bun run db:ensure-user-api-keys only after user and session tables exist. See migration troubleshooting before replaying migrations.
Cluster names
Setup → Auto-name missing vibes uses each cluster's three most frequent artists. It leaves existing names unchanged. Click a Vibe → Browse tile to edit its name. Names belong to clustering generations. Rollback restores the earlier generation's names. See the naming code.
Artist distribution
Vibe → Artists searches the library by artist name and shows where that act actually lands: one bar per vibe with its track count, plus tracks, albums, vibes touched, focus (share in the biggest cluster), spread (effective number of vibes) and evenness. Each bar links to the matching sleeve in Vibe → Browse, and the co-credited artists are clickable, so a duet leads to the other half.
Pruned duplicates are excluded by default so the numbers describe what is playable; a checkbox counts them back in. Encoded as:
| Request | Returns |
|---|---|
GET /api/artists?q=sad | Matching artists with track and cluster counts. Omitting q lists the most represented artists. |
GET /api/artist-stats?name=Sade | Distribution plus the stats above. Add includeSkipped=1 to count pruned duplicates. |
Both routes use the session like the rest of the UI. Matching folds case, so the beatles and The Beatles are one artist.
Duplicates
Manage → Duplicates groups tracks by normalised title, first artist, and album. Missing or inconsistent metadata can group different recordings. Review each group before pruning. Title suffixes such as Live at… separate entries when present.
Pruning prefers a copy with an embedding. It averages available embeddings into the keeper and marks other copies skipped. See the duplicate code. The centering trigger recomputes the centered embedding after that write.
History: the incomplete scan
Scanning more rows costs more database work. I first limited the scan to clustered tracks and missed about two thirds of duplicates. The scan now includes clustered and unclustered tracks. Skipping an unembedded copy also removes it from pending worker tracks.
A skipped track keeps its cluster_id. Pruning affects recommendations and future clustering. Use the Rust CLI to recompute assignments. Indexing preserves track.skip. The Duplicates page can restore skipped tracks.
Recommendation queries also check skipped_songs, the listener's exclusion list. That list is separate from the maintenance flag track.skip.
Runtime and source requirements
The web process serves snippets using ffmpeg and a read-only music mount. Scheduled maintenance runs in the web process through Bun.cron. See the scheduler. Each web replica starts its own scheduler. Run one replica to avoid duplicate jobs.
The repository deploy.sh fetches the configured branch and rebuilds its local stack. Use the Compose commands in Public deployment for a new host.
