| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 个月前 | ||
| 3 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 3 个月前 | ||
| 4 个月前 | ||
| 4 个月前 |
cmuxd-remote (Go)
Go remote daemon for cmux ssh bootstrap, capability negotiation, and remote proxy RPC. It is not in the terminal keystroke hot path.
Commands
cmuxd-remote versioncmuxd-remote serve --stdiocmuxd-remote serve --stdio --persistent --slot <slot> [--persistent-lease-port <port>]cmuxd-remote serve --persistent-stop --slot <slot>— internal authenticated slot teardowncmuxd-remote serve --ws --auth-lease-file <path> [--rpc-auth-lease-file <path>] [--listen 127.0.0.1:7777]cmuxd-remote cli <command> [args...]— relay cmux commands to the local app over the reverse SSH forward
serve --ws is explicit opt-in for cloud VM images only. The normal cmux ssh
code path uses serve --stdio --persistent --slot <slot> over an SSH exec
channel. That stdio process is only a proxy to an authenticated per-slot daemon
with credentials and logs under ~/.cmux/daemon/<version>/<slot>/, so remote PTY sessions
can survive local surface close, local reconnect, and app relaunch. The persistent
server never opens a public listener; it accepts only a per-user Unix socket under
/tmp/cmuxd-remote-<uid>/ and the slot token.
When invoked as cmux (via wrapper/symlink installed during bootstrap), the binary auto-dispatches to the cli subcommand. This is busybox-style argv[0] detection.
RPC methods (newline-delimited JSON over stdio)
hellopingproxy.openproxy.closeproxy.writeproxy.stream.subscribe- async
proxy.stream.data/proxy.stream.eof/proxy.stream.errorevents session.opensession.closesession.attachsession.resizesession.detachsession.statuspty.attachpty.writepty.resizepty.detachpty.closepty.list
Current integration in cmux:
workspace.remote.configurenow bootstraps this binary over SSH when missing.- Client sends
hellobefore enabling remote proxy transport. - Local workspace proxy broker serves SOCKS5 + HTTP CONNECT and tunnels stream traffic through
proxy.*RPC overserve --stdio, using daemon-pushed stream events instead of polling reads. - Daemon status/capabilities are exposed in
workspace.remote.status -> remote.daemon(includingsession.resize.min). - Persistent SSH terminals require the
pty.session.persistent_daemoncapability before cmux will restore a saved remote PTY session ID after relaunch.
Persistent SSH PTY daemon
cmux ssh uses one persistent daemon slot per CLI-launched SSH workspace. The
slot name is generated locally, validated as [A-Za-z0-9._-]{1,128}, and sent
to the remote daemon bootstrap as --slot.
Remote slot files:
/tmp/cmuxd-remote-<uid>/cmuxd-<slot-hash>.sockauthenticated Unix socket for stdio proxies.~/.cmux/daemon/<version>/<slot>/auth.tokenrandom 32-byte hex token, mode0600.~/.cmux/daemon/<version>/<slot>/daemon.locksingle-owner lock.~/.cmux/daemon/<version>/<slot>/daemon.logstartup and crash diagnostics.
PTY lifecycle:
- A local attach creates or reuses a named
pty.*session in the persistent daemon. - If the local surface closes, the stdio proxy disconnects and its attachment detaches, but the PTY process and bounded scrollback remain in the daemon.
cmux ssh-session-listcallspty.list;cmux ssh-session-attachcreates a new local terminal whose startup script callsssh-pty-attach --require-existing.cmux ssh-session-cleanupcallspty.closeto terminate a persisted PTY session explicitly.- Sessions with no attachments keep their last-known size and are reaped by the daemon idle TTL.
- Closing the owning workspace sends an authenticated slot-shutdown request, waits a bounded interval for the daemon lock to be released, and removes the relay's shell-state directory. As defense in depth, a daemon launched with
--persistent-lease-portobserves that exact~/.cmux/relay/<port>.slotlease, exits after the observed lease disappears and stdio disconnects, and removes the matching shell-state directory. Older callers that omit the flag retain the prior behavior without unsafe broad lease scanning.
Cloud WebSocket PTY transport
The WebSocket PTY transport is locked until the backend writes a short-lived lease file. The baked image contains only the daemon binary and service command, not user secrets or provider API keys.
Lease file shape:
{
"version": 1,
"token_sha256": "<sha256 hex of client attach token>",
"expires_at_unix": 1770000000,
"session_id": "optional-session-binding",
"single_use": true
}
Client flow:
- Connect to
/terminal. - Send a text JSON auth frame first:
{"type":"auth","token":"...","session_id":"...","cols":80,"rows":24}. - After
{"type":"ready"}, binary WebSocket frames are terminal input/output. - Text frames after auth are control frames such as
{"type":"resize","cols":120,"rows":40}.
Security invariants:
serve --wsfails to start without--auth-lease-file.- Missing, expired, wrong-token, or wrong-session leases close with WebSocket policy violation before a PTY is started.
- Successful single-use leases are consumed before the shell is spawned, so a
replay gets
no active lease. - Provider traffic auth remains separate. E2B images should be created with
network.allowPublicTraffic: false, so E2B requirese2b-traffic-access-tokenbefore the daemon sees the request.
workspace.remote.configure contract notes:
port/local_proxy_portaccept integer values and numeric strings; explicitnullclears each field.- Out-of-range values and invalid types return
invalid_params. local_proxy_portis an internal deterministic test hook used by bind-conflict regressions.- SSH option precedence checks are case-insensitive; user overrides for
StrictHostKeyCheckingand control-socket keys prevent default injection.
Distribution
Release and nightly builds publish prebuilt cmuxd-remote binaries on GitHub Releases for:
darwin/arm64darwin/amd64linux/arm64linux/amd64
The app embeds a compact manifest in Info.plist with:
- exact release asset URLs
- pinned SHA-256 digests
- release tag and checksums asset URL
Release and nightly apps download and cache the matching binary locally, verify its SHA-256, then upload it to the remote host if needed. Dev builds can opt into a local go build fallback with CMUX_REMOTE_DAEMON_ALLOW_LOCAL_BUILD=1.
To inspect what a given app build trusts, run:
cmux remote-daemon-statuscmux remote-daemon-status --os linux --arch amd64
The command prints the exact release asset URL, expected SHA-256, local cache status, and a copy-pasteable gh attestation verify command for the selected platform.
CLI relay
The cli subcommand (or cmux wrapper/symlink) connects to the local cmux app through an SSH reverse forward and relays commands using the v2 JSON-RPC protocol.
Cloud VM images install /usr/local/bin/cmux as a symlink to cmuxd-remote,
so cmux --help works before a user-specific SSH bootstrap has written
~/.cmux/bin/cmux.
Socket discovery order:
--socket <path>flagCMUX_SOCKET_PATHenvironment variable~/.cmux/socket_addrfile (written by the app after the reverse relay establishes)
For TCP addresses, the CLI dials once and only refreshes ~/.cmux/socket_addr a single time if the first address was stale. Relay metadata is published only after the reverse forward is ready, so steady-state use does not rely on polling.
Authenticated relay details:
- Each SSH workspace gets its own relay ID and relay token.
- The app runs a local loopback relay server that requires an HMAC-SHA256 challenge-response before forwarding a command to the real local Unix socket.
- The remote shell never gets direct access to the local app socket. It only gets the reverse-forwarded relay port plus
~/.cmux/relay/<port>.auth, which is written with0600permissions and removed when the relay stops.
Integration additions for the relay path:
- Bootstrap installs
~/.cmux/bin/cmuxwrapper and keeps a default daemon target (~/.cmux/bin/cmuxd-remote-current). - A background
ssh -N -Rprocess reverse-forwards a TCP port to the authenticated local relay server. The relay address is written to~/.cmux/socket_addron the remote. - Relay startup writes
~/.cmux/relay/<port>.daemon_pathso the wrapper can route each shell to the correct daemon binary when multiple local cmux instances or versions coexist. - Relay startup writes
~/.cmux/relay/<port>.authwith the relay ID and token needed for HMAC authentication.
Protocol and flags
All relay commands use v2 JSON-RPC. Flags map to JSON params via flagToParamKey (e.g. --workspace → workspace_id). Boolean flags (--focus) accept true/false/1/0/yes/no and are sent as JSON booleans.
Environment fallbacks:
CMUX_WORKSPACE_ID— used asworkspace_idwhen--workspaceis not providedCMUX_SURFACE_ID— used assurface_idwhen--surfaceis not provided
Migration notes
new-workspace: The flag --working-directory was removed. It was accepted by the old relay but sent the wrong param name (working_directory instead of cwd), so the server silently ignored it. Use --cwd for the working directory. The flag --command is now supported: it sends the command text to the new workspace's default surface after creation.
send / send-key: The --text and --key flags were removed. Both commands now take their argument positionally, matching the Mac CLI convention: cmux send "hello world" and cmux send-key ctrl+c.
Window commands: Prior to this release, list-windows, current-window, new-window, focus-window, and close-window used a v1 text protocol and returned plain-text responses (e.g. window:abc123 per line). They now use v2 JSON-RPC and return JSON. Scripts parsing that output will need updating.
Browser relay behavior:
cmux browser ...inside an SSH session controls the local cmux browser through the authenticated relay, not a browser process inside the VM.- The remote CLI supports the common automation commands:
open,navigate,back,forward,reload,get-url,snapshot,eval,wait,click,dblclick,hover,focus,check,uncheck,fill,type,press,select, andscreenshot. - Commands that target an existing browser surface default to
CMUX_SURFACE_ID;opendefaults toCMUX_WORKSPACE_IDso agents can create a browser pane next to the active SSH terminal.
Workspace group relay behavior:
cmux workspace group <sub>(and thecmux workspace-group <sub>alias) maps to theworkspace.group.*v2 methods, with the same subcommands and flags as the macOS CLI:list,create,ungroup,delete,rename,collapse,expand,pin,unpin,add,remove,set-anchor,new-workspace,set-color,set-icon,move, andfocus.- The group id comes from
--group <id>or the first positional argument and accepts UUIDs or refs such asworkspace_group:1. Like the macOS CLI,addandset-anchorrequire explicit--group <id> --workspace <id>.