open-proxy
Forward open / xdg-open from a remote VM to the host machine that actually
has the browser and GUI apps.
You SSH/mosh into a VM, run open report.html or open https://…, and it pops
open on your laptop instead of failing in a headless shell.
How it works
VM SSH reverse tunnel Host (your laptop)
┌─────────────────┐ 127.0.0.1:7777 ───▶ 127.0.0.1:7777
│ open <arg> │ HTTP POST ─────────────────────▶ │ open-proxy serve │
│ (open-proxy) │ │ → xdg-open/open │
└─────────────────┘ └──────────────────┘
- The host runs
open-proxy serve, listening on127.0.0.1:7777. - The VM runs the
open-proxy openclient, which connects to127.0.0.1:7777on the VM. - An SSH reverse tunnel maps the VM's
127.0.0.1:7777back to the host's server, so nothing is exposed on the network.
Per argument, the client does the cheapest thing that works:
| Argument | Behavior |
|---|---|
| Existing regular file | Streams the bytes to the host; host writes it to a temp dir (keeping the filename/extension) and opens that copy. |
URL (https://…, mailto:…) |
Forwarded as a string; host opens it. |
| Anything else | Forwarded as a string; host's open decides. |
Best effort, by design. A copied
report.htmlthat references sibling assets (./style.css, images) won't find them on the host — only the single file is copied. URLs and self-contained files work great.
Build
go build -o open-proxy .
Produces one static binary used for both roles.
Host setup
# Optional but recommended if the VM is multi-user: require a shared secret.
export OPEN_PROXY_TOKEN=$(openssl rand -hex 16)
open-proxy serve # listens on 127.0.0.1:7777
Run it under your login session (launchd/systemd user service) so it has access to the GUI.
VM setup
-
Drop the binary on the VM and symlink it early on
$PATHunder the opener names so it shadows the system ones transparently:install -m755 open-proxy ~/.local/bin/open-proxy ln -sf open-proxy ~/.local/bin/xdg-open # most Linux apps call this ln -sf open-proxy ~/.local/bin/open # convenience / macOS habit~/.local/binmust come before/usr/binin$PATH. The binary checksargv[0], so a symlink namedxdg-openoropenruns in client mode. -
If you set a token on the host, set the same one on the VM (e.g. in your shell rc):
export OPEN_PROXY_TOKEN=<same value as host>
The tunnel
The client just talks to 127.0.0.1:7777 on the VM, so you need a reverse
tunnel from the host:
ssh -R 7777:127.0.0.1:7777 vm
Or persist it in ~/.ssh/config:
Host vm
RemoteForward 7777 127.0.0.1:7777
mosh
mosh does not forward ports itself. Keep a plain SSH connection alive alongside mosh to carry the tunnel:
ssh -fN -R 7777:127.0.0.1:7777 vm # background the tunnel
mosh vm # then mosh as usual
(Or use autossh to keep the tunnel up.)
Fallback when the host is unreachable
If the tunnel is down or the host server isn't running, the client doesn't just
fail — it falls back to the real opener. It walks $PATH for the next
xdg-open/open that isn't this binary and runs that, so a shadowed VM still
behaves sanely offline.
Fallback fires only on a connection failure. An HTTP rejection (e.g. a 403 from a token mismatch) is surfaced as an error, not silently opened locally.
Both the fallback and the host's own opener resolve the real xdg-open/open
by skipping this binary on $PATH, so it's safe to shadow the opener names on
the host too (e.g. a box that is both host and VM) without recursing.
Usage
open https://example.com # opens in host browser
open report.pdf # copied to host, opens in host PDF viewer
open ./diagram.png # copied to host, opens in host image viewer
Configuration
| Variable | Default | Meaning |
|---|---|---|
OPEN_PROXY_ADDR |
127.0.0.1:7777 |
Client target / server listen address. |
OPEN_PROXY_TOKEN |
(unset) | Shared secret; if set on the server, clients must match. |
OPEN_PROXY_MAXSIZE |
104857600 (100M) |
Max file transfer size in bytes (server-side). |
Security notes
- The server only binds loopback; the SSH tunnel is the trust boundary.
- On a shared VM, any local user can reach the forwarded port — set
OPEN_PROXY_TOKENto gate it. - The server writes received files to a temp dir and opens them. Only forward to hosts/VMs you trust.