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.
Traefik certificate resolver for the three names (required)
letsencrypt
OTAVI_PREVIOUS_APP_FQDN / _MQTT_FQDN / _DOCS_FQDN
Previous names kept working during a move, with COMPOSE_FILE=docker-compose.yml:docker-compose.previous-names.yml
(unset)
Three names are required, all routed through Traefik. MinIO has no public name since hub 0.78.2 (OTAVI_S3_FQDN is no longer read). See Moving to new public names.
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 hub admin access
OTAVI_HUB_ALLOW_WEAK_SECRETS
—
1 lets a development hub start with placeholder or short secrets (the hub refuses otherwise and names them)
OTAVI_HUB_METRICS_TOKEN
—
Bearer token for the Prometheus endpoint GET /api/metrics; empty = endpoint off. See metrics
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
The Pi's own .env (packages/satellite/.env, read by systemd). Every setting is OTAVI_SAT_* since Pi 1.6.0; an older file is converted in place by packages/satellite/scripts/env-rename.sh (the hub-triggered update does it), and the app still honours the old names with a warning.
Variable
Default
Description
OTAVI_SAT_ID
required
UUID from satellite registration
OTAVI_SAT_TENANT_ID
required
Tenant UUID
OTAVI_SAT_MQTT_BROKER_URL
mqtt://localhost:1883
Broker URL (wss:// for production)
OTAVI_SAT_MQTT_USERNAME
required
MQTT username (= satellite ID)
OTAVI_SAT_MQTT_PASSWORD
required
MQTT password (from registration)
OTAVI_SAT_CAPTURE_MODE
simulate
alsa, simulate, or replay
OTAVI_SAT_AUDIO_DEVICE
default
ALSA device name
OTAVI_SAT_SAMPLE_RATE
48000
Audio sample rate (Hz)
OTAVI_SAT_CHUNK_DURATION_MS
3000
Chunk duration (ms)
OTAVI_SAT_OVERLAP_MS
0
Overlap between chunks
OTAVI_SAT_RECORDING_PROFILE
continuous
Default recording profile
OTAVI_SAT_LATITUDE
0
Static latitude
OTAVI_SAT_LONGITUDE
0
Static longitude
OTAVI_SAT_GPS_MODE
static
static or gpsd
OTAVI_SAT_GPSD_HOST
localhost
gpsd daemon host
OTAVI_SAT_GPSD_PORT
2947
gpsd daemon port
OTAVI_SAT_DATA_DIR
./data
Local storage directory
OTAVI_SAT_TELEMETRY_INTERVAL_MS
60000
Telemetry report interval
OTAVI_SAT_YAMNET_ENABLED
true (alsa)
Enable on-device YAMNet VAD gate
OTAVI_SAT_YAMNET_MIN_BIRD_PROB
0.05
Bird-likelihood threshold (overridable by hub push)
Configured on the Hub 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