Network Egress & Offline Mode¶
Jarvis is local-first. Out of the box, a self-hosted deployment makes no unsolicited connections to the public internet — voice commands, transcription, speech, memory, and device control all happen on your own hardware.
Everything that could reach the public internet is gated behind a setting that defaults to off. Nothing leaves your network unless you explicitly opt in, or unless you install a command package or device family that talks to a third party by design.
Default posture
A fresh install with default settings is effectively offline. The only outbound traffic is between your own Jarvis services (all discovered via jarvis-config-service) and your own infrastructure (PostgreSQL, Redis, MinIO, the MQTT broker, Loki). To reach the public internet, you turn on a specific capability below.
Local vs. external¶
| Treated as local (always allowed) | Treated as external (opt-in) |
|---|---|
Service-to-service calls discovered via config-service (localhost, *.local, private IPs, container names, host.docker.internal) |
Vendor clouds (weather, music, smart-home clouds) |
| Self-hosted infra: PostgreSQL, Redis, MinIO/S3, Mosquitto (MQTT), Loki/Grafana | Push providers (Expo → Apple APNs / Firebase FCM) |
| A node talking to your command center (LAN or your own cloud-hosted CC) | Model/asset downloads (HuggingFace, GitHub releases) |
| LAN device families (Hue, Kasa, LIFX, Z-Wave, HomeKit) | Update checks (api.github.com) |
Third-party reader proxy (r.jina.ai) |
Outbound egress toggles¶
Every switch below defaults to off (or to a local option). Turn one on only when you want that capability.
| Capability | Reaches | Setting | Where to set | Default |
|---|---|---|---|---|
| Web search (quick answers) | DuckDuckGo + fetched pages | web_search.enabled (household) |
Mobile app → Household Settings (admin) | off |
| External reader proxy (deep-research fallback) | r.jina.ai |
web_scraping.allow_external (household) |
Mobile app → Household Settings (admin) | off |
| Node update checks | api.github.com |
updates.allow_check (command-center, global) |
Settings DB / settings server | off |
| Node self-update (OTA) | GitHub releases | allow_updates / JARVIS_ALLOW_UPDATES (node) |
Node config.json or env |
off |
| Wake-word model download | GitHub (openWakeWord) | wake_word_model_autodownload_enabled / JARVIS_WAKE_WORD_MODEL_AUTODOWNLOAD_ENABLED (node) |
Node config.json or env |
off |
| Speech-to-text model download | huggingface.co |
whisper.allow_model_autodownload / WHISPER_ALLOW_MODEL_AUTODOWNLOAD (whisper) |
Settings DB or env | off |
| Text-to-speech engine | huggingface.co (Kokoro) vs. local (Piper) |
tts.provider / TTS_PROVIDER (tts) |
Settings DB or env | piper (local) |
| Admin auto-update | api.github.com |
JARVIS_ALLOW_UPDATES (admin) |
Admin env / ~/.jarvis/admin.json |
off |
| Mobile push notifications | Expo (exp.host) → APNs/FCM |
in-app toggle (@jarvis/push_notifications_enabled) |
Mobile app → Settings | off (opt-in) |
| Push delivery relay | your relay → Expo | RELAY_URL (notifications) |
.env |
unset (off) |
Scope of each toggle
- Household settings (
web_search.enabled,web_scraping.allow_external) are set once by a household admin in the mobile app and apply to everyone in the household. - Global service settings (
updates.allow_check,whisper.*,tts.provider) live in the settings database and apply to that service. - Node settings live in each node's
config.json(or its environment) and apply to that device. - Push is per-device — each phone decides for itself and contacts Expo only after you opt in.
Fully-offline checklist¶
To keep a deployment fully local:
- Leave every toggle in the table above off (that is the default — you don't have to do anything).
- Keep
tts.provider=piper(the default). Piper is baked into the image and needs no download. Kokoro would fetch its weights from HuggingFace on first use. - Provide a local Whisper model and point
whisper.model_path(orWHISPER_MODEL) at it, sincewhisper.allow_model_autodownloadis off. With no local model and the gate off, transcription fails loudly with instructions rather than silently downloading one. - Pre-stage the wake-word model (or accept keyboard/fallback wake), since
wake_word_model_autodownload_enabledis off. Installers that already ship the model need nothing further. - Do not set
RELAY_URL, and leave mobile push disabled — no push payloads leave the device or the box. - Be aware that installing a command package (
jarvis-cmd-*) or a vendor-cloud device family (e.g. Nest, Ecobee, Govee cloud) intentionally adds outbound traffic for that feature. Installing it is the opt-in.
Verifying
From the box, you can confirm the posture with a passive check like sudo lsof -i -nP | grep ESTABLISHED or your firewall/router logs — with everything above off, you should see only intra-network connections (your own services + infra), not connections to github.com, huggingface.co, exp.host, or r.jina.ai.
Command packages & device families¶
- Command packages (
jarvis-cmd-weather,-news,-spotify, …) are external integrations by nature. Installing one from the Pantry is your consent for the traffic it needs; the package's code runs on your node, not in the cloud. - LAN device families (Hue, Kasa, LIFX, Z-Wave, HomeKit) stay entirely on your network.
- Vendor-cloud device families (e.g. Nest/Google, Govee cloud, Resideo, SimpliSafe) require the vendor's cloud to function. Choosing to add one is the opt-in.
Enabling updates¶
Updates are off by default so a local-only box never phones home. Turn them on per surface:
The node-update flow spans two services, so allow it in both places:
-
On the node — allow the device to download and apply a release:
or set the environment variable:
-
In command-center — allow the version lookup that finds the latest release:
How it flows: you trigger an update from the mobile app → command-center looks up the latest release on api.github.com (needs updates.allow_check) → the node downloads and applies it (needs allow_updates). Applying an explicit version to a node skips the GitHub lookup (no egress for the check), but the node still needs allow_updates to fetch the release.
The admin app has no household/JWT context at its update-check call site, so its gate is a box-level flag:
or in ~/.jarvis/admin.json:
With it off, /api/update/check makes no request to api.github.com and /api/update/apply returns 403.
Updates require internet
Enabling any update path allows outbound connections to GitHub (and, for the applied release, GitHub release assets). Leave these off if you want a fully air-gapped deployment and update manually instead.
Where these settings live¶
- Household & service settings (
web_search.enabled,web_scraping.allow_external,updates.allow_check,whisper.*,tts.provider) are runtime settings — change them via the mobile app, the settings server API, or the settings database. See Configuration → Runtime Settings. - Node settings live in each node's
config.json(or its environment). - Environment variables (
JARVIS_ALLOW_UPDATES,JARVIS_WAKE_WORD_MODEL_AUTODOWNLOAD_ENABLED,WHISPER_ALLOW_MODEL_AUTODOWNLOAD,TTS_PROVIDER) are documented in the Environment Variables reference.
See also: Cloud Services for the optional cloud components (Pantry, Relay, Web) and how they stay opt-in.