All runtime configuration is via environment variables in .env. See .env.example for the full template.
Every setting is named OTAVI_<COMPONENT>_<SETTING> (components: HUB, DB, REDIS, S3, MQTT, AUTH, SMTP, EBIRD, BACKUP, MOBILE, WORKER; the deployment identity and the four hostnames are OTAVI_APP_*, OTAVI_MQTT_FQDN, OTAVI_S3_FQDN, OTAVI_DOCS_FQDN). The only unprefixed names are the host's PUID, PGID, TZ and Compose's own COMPOSE_*.
Renamed in hub 0.56.0 (and BNG_* to OTAVI_* in hub 0.55.0). An older .env is converted in place, keys and ${...} references alike, with the original kept beside it:
scripts/env-rename.sh .env
The hub logs every old name it still finds at startup together with its replacement, and ignores the old one. Containers no longer receive the whole .env: docker-compose.yml hands each service exactly the variables it reads, so a variable that is not listed there has no effect inside a container.
Hub process mode: full (single process), api (HTTP + WebSocket only, scalable), dispatcher (background workers only, single instance)
OTAVI_HUB_PORT
3000
HTTP listen port (internal)
OTAVI_AUTH_JWT_SECRET
required
Secret for signing JWT session tokens
OTAVI_AUTH_PLATFORM_ADMIN_EMAILS
—
Comma-separated emails for platform admin access
OTAVI_HUB_INTERNAL_API_KEY
—
Backwards-compatible machine access key
OTAVI_AUTH_MFA_ENCRYPTION_KEY
—
32-byte hex key (openssl rand -hex 32) used to AES-256-GCM-encrypt stored TOTP secrets. Required once MFA is enrolled — rotating it invalidates every existing TOTP and forces re-enrollment. Without it, MFA features fail-closed at boot.
OTAVI_AUTH_WEBAUTHN_RP_ID
derived
WebAuthn Relying Party ID (must match the eTLD+1 the user logs in on). Defaults to OTAVI_APP_FQDN.
OTAVI_AUTH_WEBAUTHN_RP_NAME
Otavi
Friendly display name shown by browser passkey prompts.
OTAVI_AUTH_WEBAUTHN_ORIGIN
derived
Full origin URL passed to WebAuthn verification. Defaults to https://${OTAVI_APP_FQDN}.
eBird API key. When set, enables "expected species near here" filtering, rare-detection effort-aware confidence (Johnston 2021), and the Expected species card on satellite detail. Free to obtain from ebird.org
Configured in Platform Settings page, stored in platform_settings table.
Setting
Default
Description
allow_self_registration
true
Users can register without invite
allow_tenant_creation
false
Logged-in users can create tenants
allow_invite_links
true
Admins can generate invite links
image_download_delay_seconds
0 (auto)
Delay between Wikipedia image downloads. 0 derives from authenticated/unauthenticated rate tier
wikimedia_access_token
—
OAuth 2.0 token for Wikimedia API (raises rate limit from 500 to 10 000 req/hr)
wikimedia_contact_email
—
Contact email for Wikimedia User-Agent header
inaturalist_enabled
false
Opt-in fallback for gallery extras when Wikipedia is exhausted hub 0.29
image_license_filter
["cc0","cc-by","cc-by-sa"]
License codes accepted by iNaturalist fetches; admin can widen to cc-by-nc / cc-by-nc-sa / cc-by-nc-nd. Photos with no license_code (= all rights reserved) are always rejected hub 0.29
species_extra_images_count
5
Gallery extras per species beyond the primary. No upper bound; 0 disables extras (hub 0.29 removed the hard 20 cap)
audio_storage_cap_gb
50
Soft cap (GB). When total usage crosses, next sweep purges oldest unprotected audio
audio_storage_hard_cap_gb
60 (= soft × 1.2)
Hard cap (GB). Same eviction policy as soft pass — protected (keep-best + pinned) audio is never auto-deleted (v0.29 correctness pass)
audio_retention_days
30
Delete non-keep-best audio older than this
keep_best_per_species
10
Top-N detections by confidence per species, never auto-deleted
silent_chunk_retention_hours
6
Purge chunks with 0 detections after this many hours
apk_keep_last_n
5
Keep the N most recent APK builds in mobile_releases; older ones drop on next sweep
retention_enabled
false
Run scheduled retention sweeps every 6h
inference_mode
single
single = default model only / compare = also run enabled non-default models on sampled chunks (writes to shadow_detections)
inference_compare_sample_rate
10
Percentage of chunks routed through compare mode (1–100)
dismissed_catalog_ids
[]
Catalog template IDs admin has hidden via the × button
RMS silence-gate floor, 0 = off (default since hub 0.59.0: real microphones sit far under any fixed floor and YAMNet scores silence near zero). Set only to detect a dead input at a known site