Repository navigation
Configuration
All configuration is via OBELO_* environment variables. There is no
config file. Everything has a working default except the things that cannot
have one (your TMDB key, your domain name).
This page is the complete reference — every variable the server reads. The
authoritative source is
internal/config/config.go,
which documents each one at length.
Three groups of settings — metadata providers, enrichment consent, and Tailnet remote access — are managed from the admin UI at runtime. Their environment variables exist so a headless deploy can come up already configured, and they seed the database on first boot only. After that the database is authoritative and the environment values are ignored.
This trips people up, so to be explicit: on an existing install, changing
OBELO_TMDB_API_KEY or OBELO_TAILSCALE_HOSTNAME and restarting does
nothing. The symptom is an operator restarting the server repeatedly
wondering why the new value never takes. Change it in the web UI instead — where
it also takes effect without a restart.
Every variable marked first boot below behaves this way. Everything else is read on every start.
| Variable | Default | Purpose |
|---|---|---|
OBELO_LISTEN_ADDR |
:8080 |
host:port the plain-HTTP listener binds to. |
OBELO_DATA_DIR |
./data (/data in the image) |
The single writable directory: SQLite DB, caches, transcode scratch, TLS material. |
OBELO_SERVER_NAME |
derived | The server's human-facing display name, shown in clients and advertised over mDNS. Empty derives one from the host's name. Purely cosmetic — nothing keys on it, and renaming never invalidates a device token. |
OBELO_SCAN_INTERVAL |
1h |
Scheduled incremental scan cadence. 0 disables scheduled scans (manual scans still work). Scans are incremental, so an unchanged library costs only a directory walk. |
OBELO_SESSION_IDLE_TIMEOUT |
90s |
How long an idle playback session lives before the reaper ends it. Clients report progress every ~10–15s, so this tolerates a network hiccup without killing an active stream. Not a login timeout. |
OBELO_MAX_CONCURRENT_TRANSCODES |
3 |
Cap on simultaneous transcodes. 0 is unlimited. Direct play and remux are unmetered — this bounds only the expensive tier. |
OBELO_HARDWARE_ACCEL |
off |
off / auto / nvenc / vaapi / qsv / videotoolbox. A preference, validated at startup — see Hardware Transcoding. |
See LAN Discovery for what these fix and how to tell you need them.
| Variable | Default | Purpose |
|---|---|---|
OBELO_ADVERTISE_IP |
auto | Comma-separated IPs to publish in the mDNS records. Set this when the boot log advertises a Docker bridge address instead of your LAN address. |
OBELO_MDNS_INTERFACE |
auto | Interface the mDNS responder listens on, e.g. eth0. Set this when the address is right but nothing discovers the server. |
See HTTPS and Reverse Proxy. HTTPS is always
an addition — the plain-HTTP listener keeps serving OBELO_LISTEN_ADDR.
| Variable | Default | Purpose |
|---|---|---|
OBELO_TLS_MODE |
off |
off / files / acme. Either non-off value adds an HTTPS listener. |
OBELO_TLS_LISTEN_ADDR |
:8443 |
host:port for HTTPS. Must differ from OBELO_LISTEN_ADDR (the server refuses to start otherwise). |
OBELO_TLS_CERT |
— | Absolute path to the PEM certificate chain. Required in files mode. |
OBELO_TLS_KEY |
— | Absolute path to the PEM private key. Required in files mode. |
OBELO_TLS_DOMAINS |
— | Comma-separated public DNS names. Required in acme mode, and it is an exact list — no wildcards, and www. counts as a separate name. |
OBELO_ACME_EMAIL |
— | Optional contact address for the CA's expiry notices. |
OBELO_ACME_DIRECTORY |
Let's Encrypt production | ACME directory URL. Point it at https://acme-staging-v02.api.letsencrypt.org/directory while setting up. |
OBELO_TRUSTED_PROXIES |
— (empty) | CIDRs or bare addresses whose X-Forwarded-* headers are believed. Empty means none are, from anybody. Only list a proxy you control. |
Managed from Settings → Remote access in the web UI, which takes effect with no restart. These four seed it. See Remote Access with Tailscale.
| Variable | Default | Purpose |
|---|---|---|
OBELO_TAILSCALE_ENABLED |
false |
Join a tailnet at boot. Off means no node, no goroutine, no state directory, and zero contact with any coordination server. |
OBELO_TAILSCALE_HOSTNAME |
derived | The MagicDNS label to join under — a single DNS label. Empty derives one from the server name, falling back to obelo. Resolved once at seed time and then stored. |
OBELO_TAILSCALE_CONTROL_URL |
Tailscale's | Coordination server. Set it to your own Headscale if you would rather not depend on Tailscale's. |
OBELO_TAILSCALE_HTTPS |
false |
Also serve HTTPS on the tailnet. Needs two prerequisites in the Tailscale console — and you want this on; see the page. |
OBELO_TAILSCALE_AUTHKEY |
A pre-authorized join key for a headless first join, so nobody has to click a login link. It is consumed at join and never stored — deliberately kept out of the config struct so it cannot be persisted or echoed back by an API. |
Requires the release build. The embedded node is compiled in only under
-tags tailscale, which the Docker image and make build-release carry and a
plain make build does not.
How often a linked server's libraries are pulled again. Everything else about a link — the credential, the addresses, the grants — lives in the database and is managed from Settings → Linked servers.
| Variable | Default | Purpose |
|---|---|---|
OBELO_LINK_SYNC_INTERVAL |
1h |
Fallback refresh cadence for every link. The mirror is normally nudged the moment the sharing server's library changes, so this is the safety net. 0 turns the background half off entirely — no timer, no retry backoff, and no change subscription. Manual Sync now, and the pull that follows a link or a re-key, still work. |
Enrichment is off until you turn it on: until enrichment consent is granted, Obelo makes zero outbound calls and every title simply shows as un-enriched. Official builds and the published Docker image carry default TMDB and fanart.tv keys, so once consent is granted they enrich Movies and TV with no key typed; your own key (BYOK) overrides the default. A build from source carries no default keys, so it enriches only what you configure. After first boot, manage all of this in the admin settings UI.
| Variable | Default | Purpose |
|---|---|---|
OBELO_TMDB_API_KEY |
bundled default (official builds); — from source | Your own TMDB key for Movie and TV enrichment. Video enrichment is on exactly when a key is present — yours, or the bundled default. |
OBELO_MUSICBRAINZ_ENABLED |
false |
Turns on Music enrichment. Needs no key. |
OBELO_FANART_TV_API_KEY |
bundled default (official builds); — from source | Your own fanart.tv key for artist and fan imagery. |
OBELO_THEAUDIODB_API_KEY |
— | Adds artist images and biographies from TheAudioDB. |
OBELO_OPENSUBTITLES_API_KEY |
— | Enables on-demand subtitle fetching. |
OBELO_SUBTITLE_AUTO_FETCH_LANG |
— | Language to auto-fetch subtitles in. |
OBELO_METADATA_LANGUAGE |
en-US |
Preferred language/region tag for fetched metadata. |
OBELO_AUTO_ENRICH |
true |
Enrich new titles immediately after a scan adds them. |
OBELO_ENRICH_INTERVAL |
6h |
Safety-net sweep that backfills anything still pending (e.g. added while a provider was down). |
OBELO_ARTWORK_CANDIDATE_CACHE_TTL |
2m |
How long a provider candidate-list result is cached, so toggling the artwork tab does not re-hit the provider. |
OBELO_MUSICBRAINZ_RATE_LIMIT |
1s |
Minimum interval between MusicBrainz requests, matching the public host's ~1 req/sec policy (it answers 503 above that). 0 disables throttling — only sensible against a mirror. |
OBELO_ENRICHMENT_CONSENT |
unset | Pre-answers the first-run consent prompt for a headless deploy. granted/true/1 grants, declined/false/0 declines, unset leaves it undecided — and an undecided server makes no outbound enrichment calls until someone answers. |
Rarely needed — they exist so tests can point enrichment at a local server, and so you can use a mirror.
| Variable | Default |
|---|---|
OBELO_TMDB_BASE_URL |
https://api.themoviedb.org/3 |
OBELO_TMDB_IMAGE_BASE_URL |
https://image.tmdb.org/t/p/original |
OBELO_MUSICBRAINZ_BASE_URL |
https://musicbrainz.org/ws/2 |
OBELO_COVERART_BASE_URL |
https://coverartarchive.org |
OBELO_FANART_TV_BASE_URL |
https://webservice.fanart.tv/v3 |
OBELO_THEAUDIODB_BASE_URL |
https://www.theaudiodb.com/api/v1/json |
OBELO_OPENSUBTITLES_BASE_URL |
the public OpenSubtitles API |
An optional channel that lets official builds pick up replacement default provider keys without you updating anything.
A build from source never contacts it — the endpoint URL is injected at build time and is empty in source builds. Neither does a server whose operator has not granted enrichment consent. Between that and bringing your own keys, there are two independent routes to zero maintainer contact.
| Variable | Default | Purpose |
|---|---|---|
OBELO_KEY_ROTATION |
on | Set to off (or false/0/no) to disable the channel entirely. |
OBELO_KEY_ROTATION_URL |
build-injected; empty from source | The endpoint polled. Empty disables the fetch as surely as turning the channel off. |
OBELO_KEY_ROTATION_INTERVAL |
6h |
Re-poll cadence after the first successful fetch. 0 means a single fetch at startup and no more. |
Bad configuration is caught at startup with a message naming the variable, rather than failing mysteriously later. The server refuses to start if:
-
OBELO_TLS_MODEis not one ofoff,files,acme— a typo likefielsis not silently ignored, because that would leave you on plain HTTP believing you had TLS; -
OBELO_TLS_LISTEN_ADDRequalsOBELO_LISTEN_ADDR— HTTPS is an additional listener, not a replacement, so the two need separate ports; -
filesmode is missing a cert or key, or the pair does not match; -
acmemode has noOBELO_TLS_DOMAINS, or a domain entry is malformed (including one carrying a port — a certificate is issued for a name, not aname:port); - an
OBELO_TRUSTED_PROXIESentry is neither an address nor a CIDR.
The deliberate exception is acme mode failing to obtain a certificate,
which does not stop the server: it boots, keeps serving plain HTTP, logs the
reason, and keeps trying. A CA outage is not your typo, and it should not cost
you your media server. See HTTPS for why the two behave differently.
-
Durations are Go duration strings:
30m,1h,90s,6h. -
Lists are comma-separated with no spaces:
media.example.com,www.example.com. -
Booleans accept
true/false,1/0, and for the ones that read as switches,on/off.
Getting started
Reference
Going further
Plugins
Sharing with another household
Reaching it from away
When it breaks