Reach a shared service
Applies to: v0.4.0. This chapter describes named services reached as service.node.room.vox.
Examples act as the only attached node; with several, add --node NAME.
You need a running local service on the host, two nodes whose fingerprints have been compared, and the host's trust in the guest. Vox does not start an SSH server or replace that server's own authentication. Do not enable a new service merely to follow an example.
How a service is named
Every shared service has a name the sharer chose, and a member reaches it only by its address. Each service has two addresses, and both reach it.
The readable address is the one you type:
SERVICE.NODE.ROOM.vox
SERVICE is the sharer's name for the service and ROOM is the room's name. NODE is your
name for the sharing node, the name you gave it with vox trust add. For a node you have no name
for, including your own, it is the first twelve characters of its fingerprint
(web.g5xawb52urpd.family.vox), and where either would be ambiguous (a name two of your nodes
share, or a short form another member's fingerprint also begins with) it is the whole fingerprint.
Two members can therefore see different readable addresses for the same service:
web.robertgpt.family.vox on your machine may be web.rob.family.vox on someone else's. Names are
matched without regard to case, so your alias robertGPT appears as robertgpt in an address. A part that names nothing
you know is refused with the reason, for example no node you trust is called `nobody` — only trusted nodes have names here.
The canonical address is the one to copy and send. It is made of fingerprints and IDs only,
SERVICE_ID.NODE_FINGERPRINT.ROOM_ID.vox, so it reaches the same service on every member's
machine. vox service list prints it under the readable one:
vox: shared in family (pym47virdp2b)
web.robertgpt.family.vox by robertgpt http
lnprznanqhhlxnmpzbtyfwrlomjfpzxzzovs5la2ru27smorscjq.xcxrnsegnn74dd5mxxmdrch7zfvooa4ekqxaswamlgzjwejmrwsq.pym47virdp2bauqugugmjbqa3tm6qeu762dxggg663vglzd44zua.vox
NODE.ROOM.vox and ROOM.vox reach nothing. A canonical address inside a message is shown to
you in its readable form, in vox room read and the TUI: a message saying open SERVICE_ID.NODE_FINGERPRINT.ROOM_ID.vox please reads open web.robertgpt.family.vox please on
your machine. --json and anything you copy keep the canonical form.
On a member's machine, vox service list also gives the commands for each service's kind, ready
to copy, with its canonical address in them, and what each needs, with whether it holds now:
ssh.robertgpt.family.vox by robertgpt ssh
SERVICE_ID.NODE_FINGERPRINT.ROOM_ID.vox
ssh ssh $USER@SERVICE_ID.NODE_FINGERPRINT.ROOM_ID.vox
forward vox forward SERVICE_ID.NODE_FINGERPRINT.ROOM_ID.vox 127.0.0.1:2222
then ssh -p 2222 $USER@127.0.0.1
needs robertgpt trusts this node (as the room's log says): yes
needs this node is attached: yes
needs the .vox proxy is running on 127.0.0.1:1080: yes
needs robertgpt is online: yes
for ssh by address, add this to ~/.ssh/config once:
Host *.vox
ProxyCommand nc -X 5 -x 127.0.0.1:1080 %h %p
A needs line that says no names what to fix first. In the TUI, the same commands are in the
room's Shared pane, and y copies the selected one to your clipboard.
A port shared into a new room
On the host, with SSH already listening on loopback port 22:
vox serve ssh=22
serve creates a room, shares 127.0.0.1:22 in it as ssh, and keeps running. It prints the
room ID, the room link, a generated room passphrase (^ send this another way than the address (in person, a call, a different app)) and the service's canonical address,
followed by the kind Vox detected:
sharing 127.0.0.1:22 as ssh — SERVICE_ID.FINGERPRINT.ROOM_ID.vox (ssh).
Send the link and the passphrase separately. Protect this output: it includes the room passphrase.
Several shares can be named at once, such as vox serve ssh=22 dns=53/udp; --at names a local
endpoint other than 127.0.0.1:PORT, and --name sets the new room's name, which every member sees (default
service). A bare port is refused: "22" has no name: every shared service is named.
serve then says who can reach it: a member of this room you have trusted. Someone with
the link and the passphrase who is not in your keyring reaches nothing. It names them, by your
names for them, and says it again as members join:
who can reach it: a member of this room you have trusted (`vox trust add`)
a joiner with the address and the passphrase reaches NOTHING until then
can reach it now: nobody yet
in the room and cannot (not trusted): nobody
After a trusted member joins, it prints can reach it now: ann.
Before it creates the room, serve warns about a service that is already exposed or sensitive:
vox: warning: `web` (nginx 0.0.0.0:8080 tcp (every interface)) listens on every interface of this machine, so its networks reach it without Vox; sharing it does not change that
vox: warning: `db` is on port 5432, PostgreSQL's: every node you trust in the room can reach it
Pick the service from a list
vox serve with no service named lists what is listening on this machine, with each program's
name, and asks which to share:
vox: services listening on this machine
3 sshd 127.0.0.1:22 tcp
another user's services, root's among them, may be missing here or listed without their program; name one with vox serve <name>=<port>
share which? (its number, or its port)
Answer with the number or the port. It then suggests a name, the service's
kind where Vox recognizes one (name it [ssh]); press Enter to
take it or type another. Before anything is created it shows the address members will use and
who can reach it, with any warning, and asks share it? [y/N]:
members will reach it as ssh.FINGERPRINT.<the new room>.vox
who can reach it: each node you trust, once it joins the room: carol, ann
who cannot: anyone else who joins with the room link and passphrase
share it? [y/N]
Anything but y stops with not shared, and nothing is created. On y it goes on as
vox serve ssh=22 does.
In the TUI, :serve in a room does the same into that room: it lists what listens here, and
Enter on one shows the preview, share python3.13 0.0.0.0:51529 tcp (every interface) as python3-13: members will reach it as python3-13.…family.vox, who can reach it and who cannot, and
any warning, then Enter: share it in this room · Esc: back to the list. Run as yourself, the list may miss another user's services, root's
among them; name such a service as NAME=PORT.
On the guest:
vox connect 'ROOM_LINK'
vox service list ROOM_ID
connect asks for the room passphrase at the terminal, or reads it from --passphrase-file, where
- reads stdin; a pipe is read only with --passphrase-file -, so input meant for something else
is never taken as the passphrase. It joins once and exits, printing the command to list what is
shared and that a service is reached through the daemon's proxy, running while a node is attached
(vox up says where). Both sides then exchange trust as in Identity and keyring; the host must
trust the guest's fingerprint before the guest can reach its service.
Offer a service in an existing room
With the node attached and the room on it:
vox service add ROOM_ID ssh 127.0.0.1:22
vox service list ROOM_ID
Vox first says who is to reach it (vox: about to offer "ssh" at 127.0.0.1:22 in "family" / the members of it in your keyring are to reach it: ann), then offering "ssh" at 127.0.0.1:22 in room ROOM_ID and who can reach it now; with nobody in your keyring in the room yet it says it is dark until you vox trust add someone — and they join this room. On the host, service list prints the readable address, by you and the kind
under shared in, the canonical address under it, and the endpoint under services offered. On a
guest it prints the readable address, who shares it and the kind, for example
ssh.robertgpt.family.vox by robertgpt ssh, and the canonical address under it. This offers
an existing endpoint; it does not start
sshd. The host's trust keyring controls reach, not the service name. Bind your underlying
service appropriately: a service already listening on every LAN interface is still exposed there
independently of Vox.
What kind of service it is
When a service is shared, Vox finds out what it is and records that with the share. Every member's
vox service list shows it, and the sharer's vox serve prints it in brackets. The kinds are:
| Kind | How Vox recognizes it |
|---|---|
ssh |
the service greets a new connection with an SSH banner |
https |
it answers a TLS handshake |
http |
it answers an HTTP request |
dns/udp |
a UDP service that answers a DNS query |
tcp, udp |
anything else |
To find out, Vox connects to the service: up to three short connections to a TCP service (one
each for the banner, the handshake and the request), or one query to a UDP service. Your
service's log may show them. For a service on this machine that none of these identifies, Vox
also looks up the name of the program listening on the port (lsof on macOS, ss on Linux),
so a local sshd is still ssh if it said nothing in time. The kind never comes from the port
number or the name you gave the service: a plain echo shared as ssh=7000 is tcp.
Reach it through the local proxy
While a node is attached, the daemon runs a SOCKS5 proxy on 127.0.0.1:1080 that resolves
.vox addresses and carries every room its attached nodes hold. Nothing else has to be started.
On the guest, ask where it is:
vox up
It prints vox up on 127.0.0.1:1080 — the vox daemon's proxy, carrying every room its attached nodes hold, a block to add to ~/.ssh/config once, and a line for other tools, then exits:
Host *.vox
ProxyCommand nc -X 5 -x 127.0.0.1:1080 %h %p
# or, without nc:
# ProxyCommand socat - SOCKS5:127.0.0.1:1080:%h:%p
Vox prints this block rather than editing your SSH configuration. Copy the one your vox up
printed: it names the port the proxy really listens on. To use another loopback port, start the
daemon with vox daemon --proxy 127.0.0.1:PORT, or set VOX_PROXY; the proxy listens on loopback
only. If the port is taken, vox up says the .vox proxy could not listen on 127.0.0.1:1080: Address already in use and names both settings. vox up --watch stays in the foreground and
prints what the proxy refuses or cuts, until stopped; the proxy runs on without it.
Use the real SSH account on the host and your service address:
ssh SSH_USER@ssh.robertgpt.family.vox
A Vox alias is not an SSH login name. Other tools use the proxy through
ALL_PROXY=socks5h://127.0.0.1:1080, for example
curl --socks5-hostname 127.0.0.1:1080 http://web.robertgpt.family.vox/.
Success means the expected service answers and its ordinary authentication still works. An SSH host-key warning belongs to SSH identity verification; do not disable it to make a Vox test pass. A published offer or accepted room join alone is not service reach.
Reach it through a local forward
For a tool without SOCKS support, the guest forwards a local port to the address:
vox forward ssh.robertgpt.family.vox 127.0.0.1:2222
It prints forwarding 127.0.0.1:2222 to ssh on ssh.robertgpt.family.vox and runs until Ctrl-C.
It is a client of the daemon, so nothing else needs stopping. Point the tool at loopback port
2222; for SSH, ssh -p 2222 SSH_USER@127.0.0.1. Do not bind a forward publicly unless you
explicitly intend other local-network users to access it.
vox status lists live tunnels under tunnels. vox tunnel close MEMBER [SERVICE], or
vox tunnel close --id NUMBER, closes them.
Stop sharing
For a service registered in a room:
vox service remove ROOM_ID ssh
vox service list ROOM_ID
Removal withdraws the offer and cuts its live sessions; warn affected users first. Vox says
which sessions it is to cut before it acts (its live sessions are to be cut: none is open) and
which it cut after (no longer offering "ssh"; live sessions cut: none was open). It does
not remove the guest from your keyring or stop the underlying local SSH/web server. Removing a
node from your keyring also cuts its reach at once. For the foreground serve example, Ctrl-C
stops that serving process and its live offer. Leaving or ending the room stops every service
shared in it.
A family LAN
vox lan up ROOM_ID puts this machine on a network interface on which the room's trusted
members are one subnet, so local-network discovery works across Vox. Creating an interface needs
root, and only a separate helper has it: run sudo vox lan helper in another terminal, then run
vox lan up as yourself, not with sudo. Without the helper it stops with no LAN helper is answering on /var/run/vox-lan.sock. The manual's command check went no further than that message.
When an anchor is needed
If peers can reach each other directly, including an appropriate same-LAN path, no anchor
is needed. If both are behind NAT and cannot otherwise discover/reach each other, an
always-on host they can reach can run an anchor. On that host, per vox node --help:
vox node create anchor --headless
vox node --node anchor --listen 0.0.0.0:PORT
The anchor prints a FINGERPRINT@ADDRESS specification. Verify it through a way you already
trust and supply it with --anchor to the commands that attach or start nodes, such as
vox node attach, serve and up; a room link also carries the anchors its sharer uses. The
manual's command check did not run an anchor; these two commands come from the command help.
An anchor is infrastructure you operate, not an account with a central provider. It holds no room key and stores nothing for rooms it is not in. Running it does not make an arbitrary private address publicly reachable; its actual address must be reachable by the participants. Do not blame an absent anchor when the failed step was a directly reached member refusing a passphrase.
When your machine changes network, for example from home Wi-Fi to a phone hotspot, the daemon notices within seconds, publishes its new addresses and dials its peers and anchors again; see after a network change.
If it fails, use service troubleshooting or join troubleshooting.
Source: service, proxy and forward arguments, tunnel behavior and diagnostics, how a service's kind is detected, service addresses and network changes and port mappings.