Skip to content

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 admin

Rerun 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.

CredentialAccepted 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 keyPOST /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 -- --json

The 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:

RequestReturns
GET /api/artists?q=sadMatching artists with track and cluster counts. Omitting q lists the most represented artists.
GET /api/artist-stats?name=SadeDistribution 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.