Skip to content

ui: (demo) access server remotely via webrtc - #24577

Open
ngxson wants to merge 16 commits into
ggml-org:masterfrom
ngxson:xsn/remote_server
Open

ngxson wants to merge 16 commits into
ggml-org:masterfrom
ngxson:xsn/remote_server

Conversation

@ngxson

@ngxson ngxson commented Jun 13, 2026 •

Copy link
Copy Markdown
Collaborator

Overview

This is a PoC that demonstrates how to use llama.cpp remotely on a mobile device via p2p webrtc connection. It is NOT intended to be a production-ready, but can be useful for discussion.

My main goals are:

  1. Being able to access my local llama.cpp server outside of LAN
  2. Use p2p connection to avoid relaying data via a server (end-to-end encryption by design)
  3. No account needed, no vendor lock-in

To achieve that, implementation is as follow:

  • webrtc is used for p2p connection, no central servers are needed (only STUN servers are used, but for doing NAT hold-punching)
  • signaling is done via webtorrent --> many public servers to choose from, you are not vendor lock-in
  • on client, fetch() calls are intercepted and are tunneled via webrtc connection

User flow:

  • Host:
    • enable "remote access" from settings
    • generate the pass code
  • Client:
    • install llama-ui as a PWA
    • go to "remote access" in settings
    • paste the pass code

Caveats:

  1. It is a frontend-only feature for now, cannot be integrated directly into llama-server (that would require bundling the whole webrtc library into llama-server, which will be a big maintenance burden)
  2. Cannot upgrade frontend app version remotely (due to the way PWA works)

Demo

  1. app is installed on my iphone via LAN
  2. it is then disconnected from my LAN, it then uses 4G network
  3. the app connects back to the host via p2p network
20260613_162415.mp4

Points for discussion

  1. Should we continue with webrtc, or maybe implement this as a feature directly into llama-server? (which won't be webrtc anymore)
  2. Instead of leaving the user to install app via LAN, we can simply deploy a version on ui.llama.app (?)

Requirements

@ServeurpersoCom

ServeurpersoCom commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor

Cool experiment, but feels like feature creep to me. The kind of thing better off handled by wireguard, tailscale, pangolin etc.

server.cpp :

        SRV_WRN("%s", "      it is not recommended to use this mode in untrusted environments\n");

Exposing llama-server to WAN should be done with real caution imo.

Fair point on caution, but to clarify: this doesn't expose llama-server to the WAN. The server stays bound to localhost, the host browser just proxies same-origin requests over an encrypted p2p data channel, so nothing is actually listening on a public interface. That warning is about binding to a public address, which this setup doesn't do. wireguard/tailscale are great but they need per-device VPN setup, the whole point here is zero-config in the browser for non-technical users.

@allozaur

Copy link
Copy Markdown
Contributor

@ngxson great PoC :D will take a closer look at this in near future :)

@ServeurpersoCom

Copy link
Copy Markdown
Contributor

I'm going to rebase it and test it

@ServeurpersoCom
ServeurpersoCom marked this pull request as ready for review August 16, 2026 09:15
@ServeurpersoCom
ServeurpersoCom requested a review from a team as a code owner August 16, 2026 09:15
@ngxson

ngxson commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

@allozaur @ServeurpersoCom with #28277 in a working state now, let's try shipping this feature somewhere this month?

one important thing to decide is whether to push a pre-built version of llama-ui to ui.llama.app or similar, so that mobile users can access it easily

@ServeurpersoCom

Copy link
Copy Markdown
Contributor

@allozaur @ServeurpersoCom with #28277 in a working state now, let's try shipping this feature somewhere this month?

one important thing to decide is whether to push a pre-built version of llama-ui to ui.llama.app or similar, so that mobile users can access it easily

UI side rebased

ngxson and others added 16 commits September 29, 2026 14:55
The sidecar binary runs next to llama-server and covers the headless
case, so the browser no longer needs to act as a host. Only the client
side remains, the wire protocol is unchanged.
A local flag tracked the same thing as the tunnel status, so leaving an
attempt in progress restored the form while the pending call still held
the flag, leaving the code field disabled. The settings screen had the
same split across two flags.
The saved session becomes an array of entries discriminated by type, so
later work can add servers and other kinds of connection without
changing the stored shape. Entries hold the share code as the user pastes
it, leaving the room and pass split as a tunnel detail.
@ServeurpersoCom

Copy link
Copy Markdown
Contributor

Rebased

@allozaur

Copy link
Copy Markdown
Contributor

will take over this PR once 2 stacks are merged - #29644 and then #29587

@allozaur allozaur self-assigned this Sep 29, 2026
allozaur added a commit to allozaur/llama.cpp that referenced this pull request Nov 10, 2026
A remote llama.cpp server is one the UI reaches over a WebRTC data channel
instead of a base URL. It stays a provider: it is listed, named, enabled and
disabled, and its models appear in the selector like everyone else's.

What changes is where a provider's requests are addressed. A remote provider
has no URL of its own, so it is given a synthetic origin whose host carries its
id (`https://<id>.remote.llamacpp.invalid`, the suffix being reserved so the
host can never resolve). Every service already builds its URLs through `apiUrl`,
so nothing in the services had to learn about tunnels: `getBackendBaseUrl` hands
back the synthetic origin, and a fetch wrapper installed in `hydrateStores`
routes a request by that host instead of by the page origin.

Routing on the address is what makes this safe to run next to other providers.
The first remote-access build tunneled everything aimed at the page origin, so
one tunnel was the most it could hold, and a remote provider with a URL of its
own would have slipped past it. Now a request aimed at a tunnel origin with no
live tunnel is refused out loud, and because the origin cannot resolve, a call
site that ever bypasses the resolver dies in the browser rather than reaching
the server that happens to serve this UI. A request queued while the handshake
ran is refused the same way if the channel died underneath it.

Tunnels open lazily: only the provider the app opens with is handshaked during
startup, the rest when one of their models is picked or Connect is hit on their
card. A provider that is disabled or removed loses its data channel, and its
code - which is a credential - is dropped with it.

Three things this forces into the open, all of them pinned to the built-in
provider back when no second provider could exist:

- `validateApiKey` probed the active provider with the key of the local one, so
  a remote provider answering 401 raised the API key splash over a credential
  belonging to another machine.
- `streamTool` ran tools on the local server while the model asking for one
  could be anywhere. A tool now runs where its tokens are generated.
- settings export scrubbed `apiKey` and MCP headers but exported the provider
  list whole, keys included. Remote providers now leave the export entirely:
  without their code they name nothing, and with it they are a key.

The tunnel itself comes from ggml-org#24577 (ngxson), with the base-path stripping
dropped: a request already carries the path the host's server serves.

Co-Authored-By: ngxson <ngxson@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants