Skip to main content

Configuration

Everything is a Spring property and every property is overridable via environment variables (relaxed binding: app.ister.server.name ⇒ APP_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
OIDC audienceOIDC_AUDIENCE(empty)Optional. When set, an access token must list it in aud or carry it as azp (the client id, e.g. ister). Empty = issuer and signature only, so tokens for other clients in the same realm are accepted too.

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.

Object storage (S3)​

See Object storage. Connections are indexed lists (APP_ISTER_S3_CONNECTIONS_0_NAME, …).

SettingEnv varDefaultNotes
app.ister.s3.connections[n].namereferenced by directories and the shared stores
app.ister.s3.connections[n].endpointemptyempty = AWS; http://garage:3900 for a self-hosted server
app.ister.s3.connections[n].regionus-east-1
app.ister.s3.connections[n].bucketone bucket per connection
app.ister.s3.connections[n].path-styletruepath-style addressing, needed by MinIO/Garage/Ceph
app.ister.s3.connections[n].access-key / .secret-keyemptyempty = the SDK's default credential chain (AWS)
app.ister.disk.directories[n].s3-connection / .prefixmakes the directory an S3 directory (no path); prefix optional
app.ister.s3.ffmpeg-directS3_FFMPEG_DIRECTfalsetrue: ffmpeg reads presigned S3 URLs instead of the node's own proxy
app.ister.s3.presign-ttlS3_PRESIGN_TTL2hlifetime of those URLs; keep above the longest pass
app.ister.server.self-urlSELF_URLhttp://127.0.0.1:<server.port>where the node reaches its own proxy for ffmpeg; keep it on loopback behind a gateway with request timeouts
app.ister.s3.local-copy-dir / .local-copy-max-bytesS3_LOCAL_COPY_DIR / S3_LOCAL_COPY_MAX_BYTES<tmp-dir>/s3-scratch/ / 2 GiBLRU cache of objects that must be read as a local file (epub/cbz/pdf, OCR)
app.ister.server.cache-s3-connection / .cache-s3-prefixCACHE_S3_CONNECTION / CACHE_S3_PREFIXempty / cachethe cluster-shared cache; replaces CACHE_DIR for new derived files
app.ister.server.tmp-s3-connection / .tmp-s3-prefixTMP_S3_CONNECTION / TMP_S3_PREFIXempty / tmpthe cluster-shared transcode store; TMP_DIR stays the local encoding scratch

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
app.ister.worker.openlibrary.min-request-interval-millis1000spacing between Open Library calls (search, author, work lookups)
app.ister.worker.wikipedia.min-request-interval-millis500spacing between Wikidata/Wikipedia calls (person bios, series lookups)

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
mkvextractMKVEXTRACT/usr/bin/mkvextractunpacks DVD (VobSub) subtitle tracks for the bitmap-subtitle sprite sheets; Blu-ray (PGS) needs only FFmpeg
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
BACKGROUND_NICE (app.ister.server.background-nice)10nice value for background ffmpeg/ffprobe — background passes and analysis (probing, crop and intro detection, subtitle extraction) — so playback and uploads win the CPU; 0 = off
NICE_PATH (app.ister.server.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

Media upload​

See Uploading media for what these protect against.

SettingDefaultNotes
UPLOAD_ENABLEDtrueswitches the admin upload endpoints off entirely
UPLOAD_CHUNK_SIZE16MBone chunk request; must fit the reverse proxy's body limit, minimum 5 MB
UPLOAD_MAX_ACTIVE_SESSIONS / UPLOAD_MAX_FILES_PER_SESSION2 / 100000uploads at once (cluster-wide), files per upload (a Steam or PS3 game is one upload of up to ~100k files)
UPLOAD_MAX_CONCURRENT_CHUNKS4chunk requests one node handles at once
UPLOAD_SESSION_IDLE_TIMEOUT / UPLOAD_CLEANUP_INTERVAL24h / PT1Hwhen an abandoned upload expires, and how often that is checked
UPLOAD_MIN_FREE_SPACE5GBspace a local directory keeps free after an upload

Games​

The per-user save cloud of GAME libraries (see Libraries and media layout).

SettingDefaultNotes
GAMES_SAVE_RETENTION10save snapshots kept per user per game; older ones are deleted when a new one arrives
GAMES_SAVE_MAX_BYTES512MBlargest save snapshot a client may upload
GAMES_ACHIEVEMENT_ICON_MAX_BYTES512KBlargest achievement icon a client may upload
app.ister.worker.ludusavi.manifest-urlthe Ludusavi manifest on GitHubwhere PC/Steam save locations come from; cached in the cache directory under ludusavi/, refreshed every refresh-hours (168); enabled: false turns the lookup off

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, app.ister.worker.ludusavi.manifest-url.

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
SEGMENT_DETECT_CONCURRENCY (app.ister.server.segment-detect.concurrency)1intro/outro-detection consumers on this node; detection for a season also waits until an upload into its directory has finished
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.yaml files next to the module's YAML file (e.g. core/src/main/resources/core-local.yaml); ./gradlew bootRun activates the local profile automatically. Container deployments should use environment variables instead.