Skip to main content

Configuration

Everything is a Spring property and every property is overridable via environment variables (relaxed binding: app.ister.server.nameAPP_ISTER_SERVER_NAME). Many settings also have a dedicated short env var, listed below. Defaults are sensible for a single-node home setup.

Core services

SettingEnv varDefaultNotes
Database host / port / nameDB_HOST / DB_PATH / DB_NAMElocalhost / 5432 / isterPostgreSQL
Database credentialsDB_USER / DB_PASSWORDister / isterchange in production
Connection poolDB_POOL_SIZE20playback fans out to many concurrent queries
Connection timeout (ms)DB_CONNECTION_TIMEOUT10000how long a thread waits for a pooled connection before failing
RabbitMQSPRING_RABBITMQ_HOST / _PORT / _USERNAME / _PASSWORDlocalhost, 5672, user/passwordport defaults to the RabbitMQ default 5672
OIDC issuerOIDC_URLhttp://keycloak:8060/realms/IsterKeycloak-compatible; the default matches the bundled dev Keycloak. Production: e.g. https://keycloak.example.com/realms/Home

Identity and paths

SettingEnv varDefaultNotes
app.ister.server.nameAPP_ISTER_SERVER_NAMETest servermust be unique per node
app.ister.server.urlAPP_ISTER_SERVER_URLhttp://localhost:8080how clients and other nodes reach this node
app.ister.cluster.nameAPP_ISTER_CLUSTER_NAMEthe server namesame value on every node of a cluster
Cache directoryCACHE_DIR/cache/images, podcast downloads
Temp directoryTMP_DIR/tmp/ister/HLS transcode output
app.ister.server.websocket.allowed-originsAPP_ISTER_SERVER_WEBSOCKET_ALLOWED_ORIGINSemptyextra origins allowed on the GraphQL websocket handshake. The public host from app.ister.server.url is allowed automatically (both schemes, any port), so you only need this when the server is served under a second name. * allows any origin.

Metadata and languages

SettingEnv varDefaultNotes
app.ister.server.TMDB.apikeyAPP_ISTER_SERVER_TMDB_APIKEYunsetTMDB API read access token. Without it, movie/show metadata fetching is skipped — you get bare filenames.
app.ister.server.TMDB.max-requests-per-second30stays under TMDB's ~40 rps limit
app.ister.worker.tmdb.certification-countryUSISO 3166-1 country whose certification/content rating (e.g. 16, PG-13, TV-MA) is stored on movies and shows; falls back to US, then any country that has one.
app.ister.languagesISTER_LANGUAGESen,nlcomma-separated ISO-639-1 tags; first = primary. Drives which languages metadata is fetched in and which languages search indexes. Changing it requires a re-scan plus rebuildSearchIndex — see Search.
app.ister.worker.musicbrainz.min-request-interval-millis1100spacing between MusicBrainz calls; stays just above their 1-request-per-second policy — lower it only against your own mirror

Search (Typesense)

Env varDefault
TYPESENSE_ENABLEDfalse
TYPESENSE_HOST / TYPESENSE_PORT / TYPESENSE_PROTOCOLlocalhost / 8108 / http
TYPESENSE_API_KEY / TYPESENSE_COLLECTIONempty / media

See Search for the enable/reindex procedure.

Transcoding

SettingEnv varDefaultNotes
FFmpeg directoryFFMPEG_DIR/usr/bindirectory holding ffmpeg/ffprobe
mkvextract / subtile-ocrMKVEXTRACT / SUBTILE_OCR/usr/bin/...image-subtitle extraction and OCR
app.ister.server.subtitle-ocr-default-languageengOCR language assumed for embedded image subtitles without a language tag; empty disables OCR for untagged streams
app.ister.server.subtitle-ocr-tessdata-dirSUBTITLE_OCR_TESSDATA_DIRimage: /usr/share/tesseract/tessdata-bestdirectory with tessdata_best models; used for a language only when its <lang>.traineddata is there, otherwise the distro (tessdata_fast) model applies. The images ship eng nld deu fra spa ita por; add languages with --build-arg TESSDATA_BEST_LANGS="…" or mount a directory
app.ister.server.subtitle-ocr-dpi / -threshold / -borderSUBTITLE_OCR_DPI / _THRESHOLD / _BORDER300 / 0.6 / 10passed to subtile-ocr; raise the threshold when outlines bleed into the glyphs, lower it when thin strokes disappear
app.ister.server.subtitle-ocr-char-blacklistSUBTITLE_OCR_CHAR_BLACKLIST``/`_~``
app.ister.server.subtitle-ocr-timeoutSUBTITLE_OCR_TIMEOUT10mlimit per mkvextract / subtile-ocr run; the best models are three to four times slower than fast
app.ister.server.subtitle-ocr-cleanupSUBTITLE_OCR_CLEANUPtruepost-OCR repair of the systematic misreads of DVD fonts, where the dot of an i touches its stem ("this Is what", "belleve", "Just", "Ilke"). Uses the language's hunspell dictionary: a mid-sentence capital I/J on a common word is lower-cased (names stay), and an unknown word is replaced when exactly one i/l respelling is a word. Without a dictionary only the built-in function-word rule (a mid-sentence "Is"/"It") runs; languages other than English and Dutch are left untouched
app.ister.server.subtitle-ocr-dictionariesSUBTITLE_OCR_DICTIONARIESeng=en_US,nld=nl_NLOCR language → hunspell dictionary name for that cleanup; subtitle-ocr-dictionary-dir (SUBTITLE_OCR_DICTIONARY_DIR, /usr/share/hunspell) is where <name>.dic/.aff live and subtitle-ocr-hunspell (SUBTITLE_OCR_HUNSPELL, hunspell) the binary. The images install hunspell-en-us and hunspell-nl; add a distro dictionary package to use another language
app.ister.transcoder.hls.hwaccelHLS_HWACCELnonevaapi (Intel/AMD) or nvdec (NVIDIA); the compose file shows the required device mappings
app.ister.transcoder.hls.hwaccel-deviceHLS_HWACCEL_DEVICE/dev/dri/renderD128VAAPI only
app.ister.transcoder.hls.max-concurrent-filesHLS_MAX_CONCURRENT_FILES2files transcoded simultaneously; pre-transcoding shares this budget

Advanced transcoding

Rarely touched — the defaults suit most setups. These have no dedicated short env var (use relaxed binding, e.g. APP_ISTER_TRANSCODER_HLS_MAX_BACKGROUND_FILES). The mechanics behind them are in the architecture guide, Transcoding.

PropertyDefaultNotes
app.ister.transcoder.hls.max-concurrent-passes4FFmpeg passes running at once (thread pool size)
app.ister.transcoder.hls.max-background-files1files a background pre-transcode may hold, kept below the interactive budget
app.ister.transcoder.hls.max-background-passes2passes a background pre-transcode may hold
app.ister.transcoder.hls.background-nice10nice value for background passes so interactive playback wins the CPU
app.ister.transcoder.hls.nice-path/usr/bin/nicepath to the nice binary used for the above
app.ister.transcoder.hls.cache-retention-hours2a transcode cache dir is removed only after being untouched this long (and past its keep-until)
app.ister.transcoder.hls.segment-stability-ms200a segment file must be unchanged this long before it counts as finished
app.ister.transcoder.hls.pass-timeout-multiplier4pass timeout = media duration × this
app.ister.transcoder.hls.pass-timeout-min-seconds1800…but never below this floor
app.ister.transcoder.hls.pass-stall-timeout-seconds60abort a pass that writes no new segment for this long
app.ister.transcoder.hls.upload-drain-timeout-ms300000multi-node: how long to keep uploading segments to the requesting node after a pass ends
app.ister.server.hls.segment-timeout-ms60000how long an HTTP .ts request waits for the encoder to produce that segment
app.ister.server.hls.master-playlist-timeout-ms120000how long a master.m3u8 request waits for playlists to be generated

Pre-transcoding and prefetch

Two background mechanisms warm the transcode cache so playback starts instantly. Pre-transcoding works from the continue-watching list; prefetch reacts to what a play queue is doing right now. See the architecture guide, Transcoding.

PropertyDefaultNotes
app.ister.worker.pretranscode.enabledtruechecked at runtime: false stops the 15-minute pre-transcode scheduling entirely
app.ister.server.pretranscode.keep-minutes30how long a pre-transcoded file is kept warm before it may be swept
app.ister.server.prefetch.enabledtrueprefetch the next item in a play queue during playback
app.ister.server.prefetch.video-threshold-seconds120start prefetching the next video once this many seconds into the current one
app.ister.server.prefetch.track-threshold-seconds60same, for audio tracks
app.ister.server.prefetch.track-depth2how many upcoming tracks to prefetch
app.ister.server.prefetch.keep-hours24how long a prefetched file is kept warm

Continue watching

Env varDefaultNotes
CONTINUE_WATCHING_HISTORY_DAYS150how far back the continue-watching list looks; also drives what pre-transcoding keeps warm
CONTINUE_WATCHING_REBUILD_CRON0 30 3 * * *nightly self-heal rebuild
CONTINUE_WATCHING_REBUILD_ENABLEDtruechecked at runtime: false skips both the nightly rebuild and the one-time startup backfill that fills an empty table

Prefer these short env vars over the long property names here: the underlying properties are bound inconsistently (…rebuild.enabled with a dot but …rebuild-cron with a dash), which the env vars hide.

Cache cleanup and podcasts

SettingDefaultNotes
CACHE_CLEANUP_ENABLED / CACHE_CLEANUP_CRONtrue / 0 30 4 * * *daily zombie sweep of cache and tmp dirs
CACHE_CLEANUP_DRY_RUNtrueit only logs until you set this to false — see Maintenance
CACHE_CLEANUP_MIN_AGE24hnever deletes files younger than this
app.ister.server.cache-cleanup.podcast-retention-days30downloaded podcast episodes expire after this, unless someone is mid-episode
app.ister.worker.podcast.auto-download-count3newest episodes auto-downloaded per feed
app.ister.worker.podcast.refresh-cron0 10 * * * *hourly feed refresh
app.ister.worker.podcast.refresh-min-interval-minutes30a feed is not re-fetched again within this window, even if the cron fires

External metadata endpoints

Every external service the server calls is a property whose default is the real service, so you normally set none of these. They exist so a deployment can route through a proxy or mock — the chart's CI points them all at one WireMock pod:

spring.cloud.openfeign.client.config.tmdb.url, app.ister.worker.tmdb.image-base, app.ister.worker.musicbrainz.base / .coverart-release-base / .coverart-release-group-base / .commons-filepath-base, app.ister.worker.openlibrary.base / .covers-base / .author-photo-base, app.ister.worker.wikidata.entity-base / .api-base, app.ister.worker.wikipedia.summary-template, app.ister.api.podcast.itunes-base.

Libraries and directories

app.ister.disk.libraries[n].* and app.ister.disk.directories[n].* define what gets scanned — covered in full in Libraries and media layout. Helper nodes are assigned other nodes' disks with app.ister.helper.disks[n].name (+ .jobs), and an owner hands job families off with app.ister.helper.offload-jobs — see Multi-node.

Health, metrics, and other internals

The Spring Actuator (/actuator/health, /actuator/metrics, /actuator/prometheus) runs on its own port so it stays off the public API. That port separation is the only protection — the actuator endpoints themselves are unauthenticated — so never publish or proxy port 8081. See Installation.

PropertyDefaultNotes
management.server.port8081port for the Actuator endpoints
management.endpoints.web.exposure.includehealth,metrics,prometheuswhich Actuator endpoints are exposed
app.ister.server.blur-hash.chunk-size500images processed per chunk during the BlurHash sweep (keeps a chunk under the RabbitMQ consumer timeout)
app.ister.server.segment-detect.chunk-size4episodes fingerprinted per intro/outro-detection message; keep a chunk well under the RabbitMQ consumer timeout
app.ister.server.crop-detect-backfilltrueescape hatch: false skips the one-time crop-detection re-analysis of every pre-existing file on the first scan after upgrading — see Maintenance
app.ister.server.segment-detect-backfilltruesame escape hatch for the one-time intro/outro fingerprinting of every pre-existing episode

Local overrides (development)

When running from source, machine-specific settings go in gitignored *-local.properties files next to the module's properties file (e.g. core/src/main/resources/core-local.properties); ./gradlew bootRun activates the local profile automatically. Container deployments should use environment variables instead.