143 lines
5.2 KiB
Markdown
143 lines
5.2 KiB
Markdown
# 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
|
|
|
|
```sh
|
|
go build -o open-proxy .
|
|
```
|
|
|
|
Produces one static binary used for both roles.
|
|
|
|
## Host setup
|
|
|
|
```sh
|
|
# 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
|
|
|
|
1. Drop the binary on the VM and symlink it **early on `$PATH`** under the
|
|
opener names so it shadows the system ones transparently:
|
|
|
|
```sh
|
|
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. in your
|
|
shell rc):
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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_TOKEN` to gate it.
|
|
- The server writes received files to a temp dir and opens them. Only forward to
|
|
hosts/VMs you trust.
|