Files
VoiceStudio/docs/remote-gpu.md
T
fd083749c6 fix(settings): network & privacy panels — clearable proxy after reload, HF-mirror panel never vanishes, guarded remote-backend save, honest privacy claims (#1063)
Settings → Network / Models / Sharing / Privacy / OpenAPI fixes:

- NetworkTab: a proxy persisted in a previous session can now be cleared —
  the Clear button and "Set" badge derive from the backend-persisted value
  (sysInfo.proxy_url), not only from a save in the current session. Proxy row
  copy now matches its real semantics ("Applies now" badge; desc/toast no
  longer claim a restart is needed or leak yt-dlp jargon — reworded in all
  21 locales). FFmpeg path placeholder is platform-appropriate instead of
  Windows-only on every OS.
- HFMirrorPanel: the panel no longer disappears when the initial GET fails —
  the section shell always renders, with a loading state and an error +
  Retry affordance. Saving now toasts, the active preset is marked
  (aria-pressed), and the custom-URL row is labelled "Custom mirror URL"
  instead of raw HF_ENDPOINT jargon (env var moved to the row note).
- RemoteBackendPanel: full i18n (was 100% hardcoded English); Save & reload
  now validates the URL (http/https, parseable) and asks for confirmation
  before saving a URL that hasn't passed a connection test — a typo'd base
  no longer bricks every API call after reload. Dropped the contradictory
  "Restart required" badge (saving reloads the app itself; description says
  so). docs/remote-gpu.md updated to match (docs-sync).
- PrivacyTab: the "Network calls" row no longer shows the green "Offline
  translator" assurance when the backend is down or reports 'unknown' —
  green is reserved for confirmed-offline providers (nllb/argos/
  libretranslate), everything unconfirmed shows a neutral "Unknown" badge.
  The online-translator warning now deep-links to Translation settings.
- OpenApiPanel: a failed clipboard copy toasts an error instead of silence.
- a11y: all five text inputs across these panels now carry accessible names
  (aria-label), previously announced only by their vanishing placeholders.

Tests: new colocated suites for NetworkTab, HFMirrorPanel,
RemoteBackendPanel, PrivacyTab; OpenApiPanel suite extended with copy
success/failure. Frontend suite 140 files / 1061 tests green; i18n parity
probes green (new keys en-only with defaultValue, reworded keys updated in
every locale).

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 04:08:41 +05:30

4.3 KiB

Remote GPU backend

Run the OmniVoice backend on one machine (a GPU box, a home server) and drive it from the desktop app or a browser on another — over your tailnet, with the inference staying on the powerful machine.

This is opt-in and off by default: with no API key set, the backend stays loopback-only exactly as before.

The shape

┌──────────────┐     tailnet (WireGuard)      ┌─────────────────────┐
│ laptop        │  ws/https to MagicDNS URL   │ gpu-box              │
│ OmniVoice UI  │ ──────────────────────────▶ │ OmniVoice backend    │
│ (thin client) │  Authorization: Bearer …    │ OMNIVOICE_API_KEY set │
└──────────────┘                              └─────────────────────┘

The desktop app is the thin client — there is no separate binary. You set a Backend URL and an API key in Settings, and every request (including the dictation and TTS WebSockets) is sent to the remote with the key attached.

1. On the GPU box: run the backend with a key

Generate a key and start the backend with it set:

export OMNIVOICE_API_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(24))')"
export OMNIVOICE_SERVER_MODE=1          # headless: relaxes the loopback admin gate
uv run uvicorn backend.main:app --host 0.0.0.0 --port 3900

The Docker image is the same idea — pass -e OMNIVOICE_API_KEY=….

When OMNIVOICE_API_KEY is set, every non-loopback HTTP and WebSocket request must present it, as Authorization: Bearer <key>, ?api_key=<key> (browser WebSockets can't set headers), or the ov_key cookie the backend sets after the first authenticated request. Loopback traffic on the box itself is never gated, so local tools keep working.

2. Reach it over Tailscale

Install Tailscale on both machines (its client is BSD-3 open source; self-host the control plane with headscale if you want a fully open stack). Then the box is reachable at its MagicDNS name:

http://gpu-box.your-tailnet.ts.net:3900

For TLS (recommended — see the warning below), put the port behind Tailscale Serve on the box:

tailscale serve 3900
# now reachable at https://gpu-box.your-tailnet.ts.net

Serve terminates on the node and forwards from 127.0.0.1, so to the backend the request looks like loopback — which is why the API key is still required in that path (the bearer gate doesn't rely on the source address for non-local exposure; set the key and it always applies to keyed clients).

Do not use tailscale funnel (public-internet exposure) for this. Even with a key, a voice-cloning backend should not be on the open internet.

3. In the app: point at the remote

Settings → Sharing → Remote backend:

  • Backend URL: the MagicDNS URL from step 2 (with :3900 if you didn't use Serve, or no port if you did).
  • API key: the value of OMNIVOICE_API_KEY from step 1.
  • Test connection hits {url}/health and shows the remote's version and device.
  • Save & reload stores both in this browser/app and restarts the UI against the remote. The URL must be a full http:// or https:// URL (gpu-box:3900 alone is rejected), and saving a URL that hasn't passed Test connection asks for confirmation first — a wrong base would leave the app unable to reach any backend until you change it back here.

Leave the URL empty to go back to the local backend.

Security notes

  • Plain HTTP is sniffable. A bearer key over http:// on a hostile network can be read off the wire. Use Tailscale (WireGuard-encrypted) or Tailscale Serve (TLS) for anything beyond a fully trusted LAN.
  • The API key and the LAN-share PIN are independent: the PIN guards a casual share session, the key is the durable remote credential. Either can be active; both are checked when set.
  • Admin routes (/system/*, /api/settings/*) stay loopback-gated unless OMNIVOICE_SERVER_MODE=1 is set on the box; in server mode the key is the access control for those too.
  • The key is compared in constant time and never logged.