2026-06-09 10:47:39 -04:00
2026-06-16 15:00:43 -04:00
2026-06-09 10:47:39 -04:00
2026-06-09 10:47:39 -04:00
2026-06-09 10:47:39 -04:00
2026-06-16 15:00:43 -04:00
2026-06-09 10:47:39 -04:00
2026-06-16 15:00:43 -04:00
2026-06-16 15:00:43 -04:00
2026-06-16 15:00:43 -04:00
2026-06-16 15:00:43 -04:00

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 on 127.0.0.1:7777.
  • The VM runs the open-proxy open client, which connects to 127.0.0.1:7777 on the VM.
  • An SSH reverse tunnel maps the VM's 127.0.0.1:7777 back 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.html that 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.
openssl rand -hex 16 > ~/.config/open-proxy-token
export OPEN_PROXY_TOKEN_FILE=~/.config/open-proxy-token

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

  1. Drop the binary on the VM and symlink it early on $PATH under 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/bin must come before /usr/bin in $PATH. The binary checks argv[0], so a symlink named xdg-open or open runs in client mode.

  2. If you set a token on the host, set the same one on the VM (e.g. via a secret-managed file):

    export OPEN_PROXY_TOKEN_FILE=~/.config/open-proxy-token
    

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_TOKEN_FILE (unset) File containing the shared secret, used when OPEN_PROXY_TOKEN is unset.
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_TOKEN to gate it.
  • The server writes received files to a temp dir and opens them. Only forward to hosts/VMs you trust.
Description
No description provided
Readme 39 KiB
Languages
Go 95.3%
Nix 4.7%