Skip to main content

Configuration

Immerle splits configuration in two:

  • Bootstrap settings — a handful of values read from the environment (or a .env file) at startup. Changing them needs a restart.
  • Runtime settings — everything else (providers, scan cadence, transcoding, CORS, device-token TTL, federation…), managed by an admin via the API and stored in data/configuration.yaml. No restart needed.

Bootstrap (.env)

Copy .env.example to .env; real environment variables take precedence.

# --- HTTP server ---
PORT=4533

# --- Auth ---
# If unset, a random secret is generated at startup and persisted.
# AUTH_SECRET=
AUTH_REQUIRE_SETUP_TOKEN=false # gate first-run admin behind a startup token (see note below)
# Optional: create the first admin from these instead of the setup UI (see note
# below). Both must be set together, or neither.
# ADMIN_USERNAME=
# ADMIN_PASSWORD=

# --- Database ---
DATABASE_DRIVER=sqlite
DATABASE_DSN=immerle.db
# For Postgres:
# DATABASE_DRIVER=postgres
# DATABASE_DSN=postgres://immerle:immerle@localhost:5432/immerle?sslmode=disable

# --- Logging ---
LOG_LEVEL=info # debug | info | warn | error
# Output is always structured JSON, streamable live from the admin UI.

# --- Library ---
LIBRARY_PATHS=/music
LIBRARY_DATA_DIR=data
First-run admin setup

AUTH_REQUIRE_SETUP_TOKEN defaults to false on purpose. The first time the server starts with no users, POST /api/v1/setup lets you create the admin account straight from the web UI — no token to copy out of the logs. This keeps onboarding simple for non-technical, self-hosting users.

The setup endpoint self-locks the moment any user exists, so it can only be used once. The only exposure window is between the instance first becoming reachable on the network and you finishing setup: if someone reaches it before you do, they could claim the admin account.

If your instance is exposed to the public internet before you've initialized it, either set AUTH_REQUIRE_SETUP_TOKEN=true (the server then prints a one-time token you must supply to create the admin) or keep the instance off the public network until setup is complete.

For fully automated deployments (Docker, IaC) with no interactive setup step, set ADMIN_USERNAME/ADMIN_PASSWORD instead: the server creates that admin account at startup, before serving traffic, and skips the setup UI/token entirely. Like the setup endpoint, this only ever applies while the server has no users — safe to leave set permanently, it's a no-op on every later restart.

Concert discovery

An optional, runtime-configured feature (Admin → Settings → Concert discovery) that matches each user's top-listened artists against upcoming shows and surfaces the nearest one as a closable Home banner, with a toast when a new match is found. Disabled by default — like other API-key-gated features (Jamendo, Spotify charts), it needs configuration to be useful.

FieldMeaning
EnableMaster switch
CountryThe single country concert discovery searches near — instance-wide, not per user (a self-hosted instance is usually one household/group, not a global audience)
Ticketmaster API keyOptional — only shown when Ticketmaster covers the selected country
Skiddle API keyOptional — only shown when Skiddle covers the selected country

How it works:

  • Once a day (plus once immediately after enabling, and again whenever the country changes), every user's top 10 artists over the last ~180 days are searched against every source that covers the selected country — not an ordered fallback that stops at the first hit, so a source with thin coverage doesn't get starved by one that happened to find something unrelated.
  • A match is kept per (user, source, event) — dismissing one is permanent, even after later syncs re-find it. The Home banner only ever shows the single nearest upcoming match.

Sources

SourceNeeds a keyCovers
TicketmasterYesUS, GB, DE, ES, IT, NL, BE, IE, CA, AU, NZ, SE, NO, DK, FI, PL, AT, CH, MX, BR, TR, CZ
SkiddleYesGB, IE, ES, GR, PT
EventimNoFR only

Coverage is a fixed list per source, not "try everywhere" — checked directly against each API: outside these countries, a source's real catalog is thin to nonexistent (Ticketmaster's Discovery API, for instance, has essentially no French listings, which is why Eventim exists specifically for France). A country the admin dropdown offers is always covered by at least one source; searching a country no source covers would silently sync nothing forever, so that combination isn't offered. The admin UI shows which sources apply to the currently selected country and hides API key fields for sources that don't.

Why not per-user location?

Concert discovery originally used a per-user city field. In practice this didn't fit a self-hosted instance (usually one household, not users spread across cities) and free-text city names are ambiguous input for these APIs — a single admin-configured country, matched against each provider's structured country filter, is both simpler and more reliable.

Runtime (admin API)

Runtime settings are managed via the admin API and persisted in data/configuration.yaml:

AreaEndpoint
SettingsGET/POST /admin/settings
ProvidersGET/POST /admin/providers
CleanupGET/POST /admin/cleanup

Providers (including built-ins like Jamendo and their credentials) are not set in .env. Jamendo, for instance, is seeded disabled with a {"params":{"client_id":"<token>"}} config to fill in and enable from the admin UI. See On-demand catalog for how providers work, or Building a custom content provider for the config schema and the /capabilities contract used to add an HTTP provider.

LDAP authentication

LDAP is an optional, runtime-configured login path managed from the admin UI (Settings → LDAP). It uses a direct simple bind — no service account, no search:

FieldMeaning
Enable LDAPMaster switch (off = local accounts only)
Server URLDirectory endpoint, e.g. ldaps://ldap.example.com:636
Bind DN templateDN built from the username via a single %s, e.g. uid=%s,ou=people,dc=example,dc=com

How it works:

  • Local accounts are checked first, then LDAP. So a local admin always works even if the directory is down.
  • On the first successful bind, the LDAP user is provisioned a local account automatically (needed for playlists, scrobbles and devices). It has no usable local password — it can only ever authenticate through LDAP.
  • A successful bind is cached in memory for 5 minutes, so chatty clients (notably Subsonic) don't hit the directory on every request. Disabling LDAP in the UI takes effect immediately; a password change is honored within 5 minutes.

:::warning Subsonic clients must use password auth with LDAP

LDAP only works with credential logins that carry the password — the Immerle REST login (which then issues a device JWT) and Subsonic's password mode (p= / p=enc:).

It cannot work with Subsonic token auth (t=md5(password+salt) + s=), which is the default for most clients. Token auth requires the server to recompute the hash from the stored plaintext password, but LDAP never exposes a password — the directory validates it during the bind. This is an inherent LDAP limitation, not specific to Immerle.

If you use LDAP, configure your Subsonic clients to send the password (often labeled "plain password", "legacy auth", or "disable token auth"). Only do this over HTTPS, since the password is sent on every request.

:::