{
  "markdown": "# rstream-go\n\n`rstream-go` is the Go SDK for **rstream**, a secure connectivity platform built around a globally distributed edge network and lightweight agents. Agents maintain outbound-only tunnels from local and private environments, while the edge network authenticates traffic, enforces access policy, and routes requests to upstream services. rstream supports HTTP and non-HTTP workloads and provides end-to-end visibility through connection logs and metrics.\n\nThe Go SDK is the **reference implementation**. It covers the broadest rstream API surface and is the most complete SDK in terms of protocol support and tunnel lifecycle features. The rstream CLI is implemented in Go and lives in this repository, so the SDK and CLI share the same configuration model and operational behavior.\n\nLooking for native integration? The C++ SDK is available at https://github.com/rstreamlabs/rstream-cpp.\n\n## What is a tunnel?\n\nA tunnel is a secure way to expose services without requiring inbound ports, public IPs, or NAT changes. In rstream, tunnels are established **outbound** to the edge network, reducing exposure while keeping access controllable and observable.\n\nWhen you create a **published** tunnel with rstream, you get a forwarding address that routes inbound traffic to a local service. For example, a tunnel for `localhost:8080` provides a forwarding address like `https://abc123.rstream.io` that forwards HTTP requests to local port 8080.\n\n## How rstream works\n\nrstream establishes outbound tunnels between environments running services or devices and the rstream edge network. Clients connect to the edge using a forwarding address for published tunnels or a tunnel identifier for private tunnels. The edge authenticates the connection, applies policy, and forwards traffic through the existing tunnel path to the upstream service.\n\nTunnel transports are encrypted, and edge enforcement decisions are surfaced through logs and metrics.\n\n## Tunnel types\n\nrstream supports two fundamental tunnel types:\n\n**Bytestream tunnels** (TCP-like) provide reliable, ordered transmission for protocols such as HTTP and TLS, as well as custom bytestream services.\n\n**Datagram tunnels** (UDP-like) provide low-latency, message-oriented communication for protocols such as QUIC and DTLS, as well as custom datagram services.\n\n## Published vs private tunnels\n\n**Published tunnels** are accessible via standard clients (browsers, curl, etc.) through forwarding addresses. Published tunnels can be configured with edge authentication and access policies depending on protocol and deployment.\n\n**Private tunnels** require an rstream client to connect. Private tunnels are accessed by name (if specified) or by ID through the rstream dialer instead of a public forwarding address.\n\n## Use cases\n\n**Local development**: Expose a local service for testing, demos, and collaboration without changing network configuration.\n\n**Fleet operations**: Provide controlled access to devices and machines across environments with consistent identity, policy, and observability.\n\n**Infrastructure and platforms**: Use rstream as a connectivity layer for internal tools, CI workflows, and production access paths.\n\n**Generative AI workflows**: Distribute work across fleets of runners or machines while keeping access scoped and auditable.\n\n**Real-time systems**: Support low-latency traffic patterns for telemetry, streaming, and datagram workloads.\n\n## Supported features\n\n**Core tunneling**: Create tunnels for TCP-like and UDP-like workloads with outbound-only connectivity.\n\n**Multi-protocol support**: HTTP (1.1, 2, 3), TLS, DTLS, QUIC, plus WebSocket and WebTransport in HTTP tunnels.\n\n**Access control**: IP restrictions, GeoIP policies, mutual TLS, token-based access, and account-based access depending on tunnel configuration.\n\n**Operational visibility**: Connection logs and metrics for traffic, enforcement decisions, and performance signals.\n\n**Transport configuration**: IPv4/IPv6 selection, DNS override, interface binding, HTTP CONNECT proxy support, SOCKS5 proxy support, and MASQUE proxy support for QUIC transport.\n\n**Resilience**: Long-lived agents, reconnect behavior, and transport-level multiplexing for stable connectivity.\n\nControl-channel liveness and payload lifetime are intentionally separate. A\nnegotiated heartbeat grace absorbs short network interruptions. If that grace\nexpires, the listener stops admitting new streams and reconnects, while already\naccepted byte streams and registered QUIC datagram channels remain usable until\nthe application closes them or the data path itself ends. Explicit tunnel,\ncontrol-channel, client or process shutdown still closes owned sessions. SDK\ncallers therefore retain normal ownership of every connection returned by\n`Accept`; they must close it even if `ControlChannel.Done` reports a transport\nfailure.\n\n## Supported protocols\n\n**HTTP protocols**: HTTP/1.1, HTTP/2 (H2C), HTTP/3 with WebSocket and WebTransport support.\n\n**Secure transports**: TLS- and QUIC-based transports for agent-to-edge connectivity, plus DTLS and QUIC as published tunnel protocols when enabled by the deployment.\n\n**Network options**: IPv4/IPv6, MPTCP, HTTP CONNECT, SOCKS5, MASQUE CONNECT-UDP for QUIC transport, and custom DNS resolution.\n\n## Compatibility\n\nrstream is compatible with Linux, macOS 13 or newer, and Windows. Additionally, rstream supports other UNIX systems such as FreeBSD, OpenBSD and NetBSD through manual installation.\n\n## Installation (rstream CLI)\n\nThe installation paths in this section install the `rstream` CLI binary and its runtime dependencies. They do not install the Go SDK as a library dependency.\n\n### Local build\nTo build the CLI locally from this repository on the current platform, run:\n```bash\nmake\n```\n\n### Debian/Ubuntu\nFor Debian-based distributions, the installer deploys packaged CLI binaries and dependencies:\n```bash\nsudo /bin/bash -i -c \"$(curl -fsSL https://rstream.io/scripts/install-debian.sh)\"\n```\n\n### macOS\nOn macOS, the Homebrew tap provides the standard CLI installation path:\n```bash\nbrew tap rstreamlabs/rstream && brew install rstream\n```\n\n### Windows\nOn Windows, install rstream from the official WinGet Community Repository:\n```powershell\nwinget install --id rstream.rstream --exact --source winget\n```\n\nIf `winget` is not available, or a new stable release has not reached the\nCommunity Repository yet, use the PowerShell installer:\n```powershell\n& { Invoke-Expression ([System.Text.Encoding]::UTF8.GetString((Invoke-WebRequest -Uri 'https://rstream.io/scripts/install.ps1' -UseBasicParsing).Content)) }\n```\n\n### Manual installation\nFor generic environments, use the manual installer script for the CLI binary:\n```bash\n/bin/bash -i -c \"$(curl -fsSL https://rstream.io/scripts/install.sh)\"\n```\n\n### Docker\nIf you run the CLI in containers, pull the public image:\n```bash\ndocker pull rstream/rstream:latest\n```\n\n## Authentication\n\nThe standard developer-machine path is browser-based login:\n\n```bash\nrstream login\n```\n\nThis uses OAuth 2.0 Device Authorization Grant by default. The legacy rstream login flow remains available for compatibility checks with `rstream login --auth-flow legacy`.\n\nIf this is a new rstream account, the browser step opened by `rstream login` is also where the user signs up or signs in and approves CLI access.\n\nCodex can start the same flow through local MCP after `rstream codex setup`: `rstream_auth_start` returns the approval URL, and `rstream_auth_poll` stores the approved token locally without returning it to the prompt. The approval code is only returned separately when the provider cannot embed it in the URL. The default MCP login scope is limited; when the user explicitly asks Codex to create projects or change project settings, `rstream_auth_start` can request a broader `permissions` array and the hosted approval page shows that elevated grant.\n\nFor advanced authentication modes (token-based login, remote device flows, and project-scoped contexts), see [docs/001-cli-workflow.md](docs/001-cli-workflow.md).\n\nBefore running SDK examples, ensure a project context is set up with the CLI (`rstream project use <project-endpoint>`). The SDK and CLI share the same configuration model and config file.\n\nFor agent and CI checks, run:\n\n```bash\nrstream doctor -o json\n```\n\nThe diagnostic output covers config, context, token claims, Control plane API authentication, project resolution, DNS, TLS, and engine inventory without printing secrets.\n\n## Environment variables\n\nThese variables are shared across CLI and SDK configuration resolution. Prefer configuration contexts for regular usage, and use overrides for automation or constrained environments.\n\n- `RSTREAM_CONFIG`: Override the CLI config file path.\n- `RSTREAM_CONTEXT`: Select the context by name.\n- `RSTREAM_ENGINE`: Override the engine URL used for Engine API operations.\n- `RSTREAM_AUTHENTICATION_TOKEN`: Override the authentication token.\n- `RSTREAM_MTLS_CERT_FILE`: Client certificate file for mTLS agent authentication.\n- `RSTREAM_MTLS_KEY_FILE`: Client private key file for mTLS agent authentication.\n- `RSTREAM_API_URL`: Override the Control plane API URL.\n- `RSTREAM_REGION`: Select an authorized region for a managed project.\n- `RSTREAM_CONTROL_PLANE_HEADERS`: Add Control plane request headers as a JSON object.\n\nResolution behavior follows the same model used by `config.NewClientFromEnv()`: explicit SDK options are evaluated first, then environment overrides, then context/environment values from the config file. `RSTREAM_CONFIG` selects the config file path before fallback to the default config location. Token authentication and mTLS agent authentication are mutually exclusive for the control-channel connection. When the mTLS certificate and key variables are set, config-derived tokens are not used for that connection; setting mTLS variables together with `RSTREAM_AUTHENTICATION_TOKEN` is an error. Engine HTTP API requests use token authentication.\n\nRegion selection requires a managed project endpoint and cannot be combined\nwith an explicit engine override. Control plane headers are intended for an\nadditional deployment access layer. Authentication, forwarding, and hop-by-hop\nheaders are reserved; malformed values and case-insensitive duplicates are\nrejected before network I/O.\n\n## Usage\n\n### Basic HTTP tunnel\nUse this command to publish a local HTTP service with default protocol and publication settings.\n\n```bash\n# Create an HTTP tunnel for local port 8080 (default: HTTP protocol, published)\nrstream forward 8080\n```\n\nThis command creates a public HTTP tunnel and displays the forwarding address (e.g., `https://abc123.rstream.io`). Any HTTP request sent to this URL will be redirected to `localhost:8080`. The tunnel remains active until you stop the command.\n\n### TLS tunnel\nUse `--tls` when the exposed tunnel endpoint must terminate TLS.\n\n```bash\n# Create a secure TLS tunnel for local port 8080\nrstream forward 8080 --tls\n```\n\nCreates a secure TLS-encrypted tunnel accessible through the rstream network. Standard TLS clients can connect to the tunnel's forwarding address.\n\n### Private tunnels\nUse `--no-publish` to create private tunnels that are reachable only through rstream clients.\n\n```bash\n# Create a private tunnel (not publicly accessible)\nrstream forward 22 --tls --no-publish --name ssh-tunnel\n```\n\nPrivate tunnels require rstream clients to connect and are identified by name or ID rather than public URLs.\n\n### WebTTY remote terminal\nUse `rstream webtty server -v --rstream` to expose a remote shell through rstream. In rstream mode, WebTTY defaults to a published HTTP/WebSocket tunnel with the standard WebTTY labels. Add `--no-publish` to create a private tunnel instead.\n\n```bash\n# Start a published WebTTY server over rstream\nrstream webtty server -v --rstream --name shell\n\n# Start a private WebTTY server over rstream\nrstream webtty server -v --rstream --name shell --no-publish\n\n# Create and enroll a registered WebTTY server on this machine\nrstream project use project-endpoint\nrstream webtty server create prod-shell --enroll\n\n# Start an enrolled registered WebTTY server over managed rstream WebTTY\nrstream webtty server -v --server-id server_id\n\n# If the server record is created from another machine, enroll on the runtime host\nrstream project use project-endpoint\nrstream webtty server create prod-shell\nrstream webtty server enroll server_id\n\n# Start a WebTTY daemon from an operator-managed runtime config\nrstream webtty server -v --webtty-config /etc/rstream/webtty/prod-shell.yaml\n\n# Start a local plain WebTTY server over TLS\nrstream webtty server -v --transport plain --allow-unauthenticated --tls-cert-file server.crt --tls-key-file server.key\n\n# Start a local WebTransport WebTTY server for a browser app\nrstream webtty server -v --transport webtransport --allow-unauthenticated --allowed-origin http://127.0.0.1:3000 --tls-cert-file server.crt --tls-key-file server.key\n\n# Create and show local WebTTY endpoint identities\nrstream webtty identity create --name dev-shell\nrstream webtty identity show --name dev-shell --endpoint-identity\nrstream webtty identity create --name client-laptop\nrstream webtty identity show --name client-laptop --endpoint-identity\nrstream webtty identity list\n\n# Authorize the client public endpoint identity on the server host\nrstream webtty authorized-client add client-laptop --identity dev-shell --key client_endpoint_identity\nrstream webtty authorized-client list --identity dev-shell\n\n# Start a local WebTTY server that requires E2E and client proof\nrstream webtty server -v --allow-unauthenticated --identity dev-shell\n\n# Start a server that requires an explicit OS login identity\nrstream webtty server -v --execution-mode login --login-user operator\n\n# Connect through a standard websocket endpoint\nrstream webtty client --url wss://example.rstream.io/ -- whoami\n\n# Connect through the native rstream dialer using a tunnel name or ID\nrstream webtty client --url rstrm://shell -- whoami\n\n# Execute a command with JSON output for agents and scripts\nrstream webtty exec --url rstrm://shell -- uname -a\n\n# Execute through a locally known E2E WebTTY server endpoint identity\nrstream webtty known-server add dev-shell --key server_endpoint_identity --client-identity client-laptop\nrstream webtty known-server list\nrstream webtty exec --url rstrm://dev-shell -- whoami\nrstream webtty exec --url ws://127.0.0.1:8080 --known-server dev-shell -- whoami\n\n# Expose a WebDAV filesystem sidecar rooted at $HOME\nrstream webtty server -v --rstream --name shell --fs-root \"$HOME\"\n\n# Read a file through the filesystem sidecar\nrstream webtty fs read --url rstrm://shell /README.md\n\n# List the available WebTTY servers\nrstream webtty list\n\n# Inspect managed WebTTY sessions and recorded events\nrstream webtty sessions list\nrstream webtty sessions show session_id\nrstream webtty sessions events session_id\nrstream webtty sessions export session_id --format text\nrstream webtty sessions export session_id --format json --file session-export.json\nrstream webtty sessions participants session_id\nrstream webtty sessions join session_id\nrstream webtty sessions join session_id --interactive --request-control\nrstream webtty sessions control-requests session_id\n\n# List workspaces, select a project, and enroll this CLI as a trusted device\nrstream workspace list\nrstream project use project-endpoint\nrstream workspace device enroll --label ops-cli\nrstream workspace device status\n\n# Use advertised exec_path and fs_path values even when they match the defaults\n\n# Register local rstream MCP tools for Codex\nrstream codex setup\n\n# Also start the configured server and verify the MCP handshake and tools\nrstream codex setup --verify\n\n# From Codex MCP, expose a service that is local to the remote WebTTY host\n# rstream_remote_expose webtty_url=rstrm://shell port=8765 protocol=http\n# For a direct authenticated-E2E WebTTY URL, also pass known_server=shell\n\n# From Codex MCP, expose and discover a remote MCP surface on that host\n# rstream_remote_expose webtty_url=rstrm://shell port=8765 mcp_path=/mcp labels=role=robot\n# rstream_remote_mcp_discover\n\n# Expose a local dev server through an MCP-managed local tunnel\n# Available to Codex through rstream_local_tunnel_expose\nrstream forward 3000 --name codex-local-tunnel --label role=codex\n\n# Publish the local rstream MCP server over an HTTP tunnel\nrstream mcp publish --name codex-rstream-mcp --label role=codex\n\n# Open the live terminal UI for clients, tunnels, and WebTTY servers\nrstream ui\n```\n\n`rstream codex setup --verify` always validates a newly started MCP process.\nAn MCP process already attached to an open Codex task keeps running the CLI\nversion with which it was started; replacing the signed Homebrew binary cannot\nreplace code inside that process. After every rstream CLI upgrade, open a new\nCodex task or reload Codex before relying on MCP, then run an actual tool call.\nCode signing preserves binary identity and integrity, but does not restart\nlong-lived processes.\n\nInside `rstream ui`, press `c` to open the context and project picker. Configured\nlocal contexts are available immediately, including unlinked contexts that have\nno Control plane API. Projects for the active Control plane API are loaded in\nthe background when the selected credentials permit project discovery. Use `1`\nand `2` (or `Tab`) to move between the separate context and project views. Each\nview shows explicit `CURRENT` and `DEFAULT` columns and a detail panel for the\nselected row. Press `Enter` to switch only for the current UI process, or `d` to\nswitch and save the selection as the default context in the active rstream\nconfig file. The picker also supports `/` to search and `r` to refresh remote\nprojects.\n\nProject discovery failures never hide or disable local contexts. An explicit\n`--api-url` or `RSTREAM_API_URL` scopes linked contexts and remote projects to\nthat API, while unlinked contexts remain selectable. `RSTREAM_ENGINE` remains a\nhard override: the UI rejects a switch to a context that targets a different\nengine. Close an active WebTTY session with `Ctrl+g q` before changing context.\n\nLogin execution mode is passwordless. `--login-user <username>` names the\nexisting **operating-system account that will own every remote session**; it is\nnot an rstream account or the user connecting from the WebTTY client. For\nexample, `--login-user alice` runs commands with the local `alice` home, shell,\nand identity. Run `id -un` on Linux or macOS, or `$env:USERNAME` in\nWindows PowerShell, to print the local username to pass to this option. rstream\ndoes not create the account, and the server refuses to start when the configured\naccount cannot be resolved. On Unix-like systems, selecting a different OS user\napplies the target uid, primary gid, and supplementary groups, so the WebTTY\nserver process needs the corresponding privileges. On Windows, set\n`--login-user` to the same Windows account that runs the server; switching to a\ndifferent account or using a password is not supported.\n\nPublished WebTTY tunnels can be reached either through their forwarding `wss://` address or through the native `rstrm://<tunnel-id-or-name>` form. Private WebTTY tunnels are reachable only through the native `rstrm://` form. WebTTY servers advertise capabilities, execution mode, and endpoint paths through labels: command execution uses `exec_path`, currently `/` by default, and the optional filesystem sidecar uses `fs_path`, currently `/fs` when `--fs-root` is set. Filesystem paths are relative to that configured root: if the server starts with `--fs-root \"$HOME/project\"`, read `compose.yaml` as `/compose.yaml`, not `/home/user/project/compose.yaml`. The sidecar rejects symlinks that resolve outside the configured root, but it is not a sandbox and still uses the WebTTY server process permissions. The filesystem sidecar is a separate WebDAV surface and is rejected when WebTTY E2E payload encryption is active.\n\nTunnel WebTTY servers created with `rstream webtty server -v --rstream` use the\nHTTP/WebSocket tunnel path with standard WebTTY inventory labels. Registered\nWebTTY servers are normally created and enrolled on the runtime host with\n`rstream webtty server create <name> --enroll`. Split-machine setup creates the\nrecord first, then runs `rstream webtty server enroll <server-id>` on the\nruntime host. The generated local enrollment is stored under\n`~/.rstream/webtty/enrollments/<server-id>.yaml`, and publish managed\n`protocol=webtty` tunnels when started with `--server-id`. The enrollment stores\nthe control-plane server ID, project ID, server public key fingerprint, local\nidentity file path, and server encryption policy. Enrollment uses the local\nauthenticated rstream context and is not the operator runtime config.\n\nLive managed attach is engine-coordinated: the participant and control request\nresources use the HTTP control API, then the terminal stream upgrades to WebTTY\nprotobuf with an `Attach` handshake. Direct WebTTY servers accept only new\n`Open` sessions and reject managed attach handshakes with a clear protocol\nerror. CLI live attach first checks the engine capability document and then\nresolves E2E decrypt material from trusted workspace devices or local WebTTY\nidentity grants before opening the terminal stream.\n\nUse `--webtty-config` or `RSTREAM_WEBTTY_CONFIG` when a daemon should be\nconfigured from YAML instead of a long flag list. This runtime config is\noperator-managed and may contain `server.serverId`; when it does, the CLI loads\nthe generated enrollment for that server ID before publishing managed WebTTY:\n\n```yaml\nversion: 1\nserver:\n  serverId: srv_prod_shell_01\n  transport: websocket\n  executionMode: login\n  loginUser: operator\n  labels:\n    env: production\n    role: bastion\n```\n\nCLI flags override values from `--webtty-config`, and `--server-id` implies\nrstream managed mode. `server.serverEnrollment` can be used in the runtime\nconfig only when the generated enrollment lives outside the default\n`~/.rstream/webtty/enrollments/<server-id>.yaml` path.\n\nWebTTY E2E keeps the protobuf session envelope visible while encrypting\nstdin/stdout/stderr payload bytes. Server identities are local host identities,\nstored by default under `~/.rstream/webtty/identities/*.identity.json` with file\nmode `0600`. Workspace keys are separate product keys and should live under the\nworkspace key hierarchy, not in the WebTTY identity directory. Explicit server\nidentity material, known server keys, or an enrolled server whose policy\nrequires encryption imply protected mode. For `rstrm://...` targets, the client\ncan resolve labels and local known-server entries before opening the session.\nDirect `ws://...` targets need direct server trust material. Use\n`--known-server <name>` to select a pinned server from the local trust store, or\npass `--known-server-key` when automation owns the trust material directly.\n`--e2e` is only a fail-closed assertion when a command must refuse plaintext.\n\nLocal WebTTY files use this layout:\n\n- `~/.rstream/webtty/identities/<name>.identity.json`\n- `~/.rstream/webtty/enrollments/<server-id>.yaml`\n- `~/.rstream/webtty/known_servers.json`\n- `~/.rstream/workspaces/<workspace-id>/devices/<device-id>.json`\n- operator-managed daemon configs such as `/etc/rstream/webtty/prod-shell.yaml`\n\nIdentity, enrollment, known-server, and workspace-device files are created\nwith `0600` permissions on POSIX systems and rejected at runtime if group or\nother users can read them.\n\nEnvironment overrides are available for container and CI deployments:\n`RSTREAM_WEBTTY_CONFIG`, `RSTREAM_WEBTTY_AUTH_TOKEN`,\n`RSTREAM_WEBTTY_IDENTITY`, `RSTREAM_WEBTTY_IDENTITY_FILE`,\n`RSTREAM_WEBTTY_AUTHORIZED_CLIENT_KEYS`, `RSTREAM_WEBTTY_KNOWN_SERVER_KEY`,\nand `RSTREAM_WEBTTY_KNOWN_SERVERS_FILE`.\n\nServer identity precedence is `RSTREAM_WEBTTY_IDENTITY` for an inline endpoint\nidentity JSON document, then `--identity-file`, then `--identity`, then\n`RSTREAM_WEBTTY_IDENTITY_FILE`, then the registered server enrollment identity\nfile, then the default\n`~/.rstream/webtty/identities/default.identity.json` for ad hoc servers started\nwith `--e2e` and no explicit identity. E2E servers require authorized client\nsigning keys from the default authorized-client store, `--authorized-client-key`,\n`RSTREAM_WEBTTY_AUTHORIZED_CLIENT_KEYS`, or an explicit authorized-clients file.\n\nClient identity precedence uses the same endpoint identity sources:\n`RSTREAM_WEBTTY_IDENTITY`, then `--identity-file`, then `--identity`, then\n`RSTREAM_WEBTTY_IDENTITY_FILE`, then the target-scoped `client_identity`\nstored in `~/.rstream/webtty/known_servers.json`. Authenticated E2E clients do\nnot silently use the default identity. Client known server material comes from\n`--known-server`, repeated `--known-server-key`,\n`RSTREAM_WEBTTY_KNOWN_SERVER_KEY`, `--known-servers-file`, or\n`RSTREAM_WEBTTY_KNOWN_SERVERS_FILE`; the key value should normally be the\nendpoint identity printed by `rstream webtty identity show --endpoint-identity`.\nThe default known-server store is target-scoped for resolved `rstrm://...`\nconnections. Direct `ws://...` connections should pass `--known-server <name>`\nor explicit trust material when no resolved tunnel metadata is available.\nThe two-field encryption-only form remains accepted by lower-level helpers for\nspecialized encryption-only integrations, but it is not the recommended\noperator workflow. The default\n`~/.rstream/webtty/known_servers.json` is loaded when no explicit trusted\nserver keys were provided, so E2E can be inferred without an extra client flag.\n\nWorkspace-managed registered servers pin the public workspace keyset identity\nautomatically during server enrollment when the runtime host is already a\ntrusted workspace device. If local trust material changes later, refresh the\npin with `rstream webtty server trust <server-id>`. That repair command lets\nthe remote server verify workspace-managed client proofs without copying\nworkspace private keys to the host.\n\nThe implemented E2E helper suite is AES-256-GCM payload encryption with fresh\n96-bit nonces and HPKE Base\n`DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-256-GCM` key envelopes.\nGo WebTTY `tls://` plain transport and local WebTransport use TLS 1.3 minimum.\n\nSee [docs/006-webtty.md](docs/006-webtty.md) for the complete WebTTY CLI, local file,\nruntime config, and E2E workflow reference.\n\n`rstream mcp serve` exposes the trusted, device-attached MCP surface over stdio. `rstream codex setup` installs that server for Codex, and `rstream codex setup --verify` performs a bounded MCP `2025-11-25` handshake and checks the essential local tools without login or network access.\n\nThe CLI surface owns workstation context, local tunnels, WebTTY E2E identities and grants, filesystem sidecars, remote service exposure, and remote MCP bridging. The hosted `/api/mcp` surface owns OAuth-scoped Control Plane automation and deliberately cannot use device-private E2E material. Their shared tools use the same names, titles, behavior annotations, semantic output keys, and object-shaped result contract.\n\n`rstream mcp publish` exposes the device surface over Streamable HTTP at `/mcp`. Published clients require a short-lived, narrowly scoped rstream token; the current full-surface publication and secret-bearing result limitations are tracked explicitly before this can be treated as a least-privilege delegation boundary.\n\nSee [docs/009-mcp.md](docs/009-mcp.md) for setup, the two-surface contract, security boundaries, validation, and deferred hardening work.\n\n### Netcat-style TCP and rstream streams\nUse `rstream netcat` for bytestream sessions over plain TCP or native rstream tunnels. The command is also available as `rstream ncat` and `rstream nc`.\n\n```bash\n# Connect to a plain TCP service\nrstream nc 127.0.0.1:1234\n\n# Expose a command over TCP\nrstream nc -L 127.0.0.1:1234 -c \"date\"\n\n# Expose a local SSH daemon through a private rstream tunnel\nrstream nc -L rstrm://ssh-server -R 127.0.0.1:22\n\n# Connect to that private rstream tunnel by name or ID\nrstream nc rstrm://ssh-server\n```\n\nWhen `--listen` uses `rstrm://[name]`, the CLI creates a private unpublished bytestream tunnel. If no name is provided, the generated tunnel identifier is printed on startup so another rstream client can dial it.\n\nFor SSH access to a private machine, `rstream forward` is usually the simpler server-side entrypoint. Start it on the remote machine that has access to the local SSH daemon:\n\n```bash\nrstream forward 22 --bytestream --no-publish --name ssh-server\n```\n\nOn the client machine, validate the path with a one-off SSH command:\n\n```bash\nssh -o 'ProxyCommand rstream nc rstrm://ssh-server' admin@ssh-server hostname\n```\n\nFor regular use, move the client-side configuration into SSH `ProxyCommand`:\n\n```sshconfig\nHost ssh-server\n  HostName ssh-server\n  User admin\n  ProxyCommand rstream nc rstrm://%h\n```\n\nThis keeps the tunnel private while SSH still performs its normal host key verification and user authentication.\n\nIn client mode, `--exec`/`--sh-exec` run a local command and bridge its stdin/stdout to the connection, which provides a bidirectional path without shell pipe plumbing:\n\n```bash\nrstream nc rstrm://ssh-server -c \"my-local-client\"\n```\n\n### Netcat datagram mode\n\n`rstream nc --datagram` (`-u`) carries packets instead of a byte stream. Datagram mode requires `rstrm://` endpoints on both sides: `--listen rstrm://[name]` creates a private unpublished datagram tunnel, and `rstream nc -u rstrm://<id-or-name>` dials one. `--remote` is not supported in datagram mode.\n\nSince stdin/stdout are byte streams, packet boundaries on stdio are preserved with explicit framing, selected with `--framing`. The default and currently only supported framing is `rfc4571`, where each datagram is prefixed with a 2-byte big-endian length as defined by RFC 4571. One frame on stdio equals one datagram on the tunnel, so any program that reads and writes this framing exchanges packets through the tunnel without further adaptation.\n\n```bash\n# Expose a datagram producer; one child process per accepted session\nrstream nc -u -L rstrm://media -c \"media-producer\"\n\n# Dial the tunnel; stdio carries RFC 4571 frames\nrstream nc -u rstrm://media\n\n# Or run a local consumer with bidirectional framed stdio\nrstream nc -u rstrm://media -c \"media-consumer\" --idle-timeout 60s\n```\n\nDatagram tunnels can also bridge local UDP sockets instead of stdio, which connects UDP-native applications without any framing concern. One UDP packet equals one tunnel datagram, and `--framing` does not apply to udp endpoints.\n\n```bash\n# Bridge tunnel sessions to a local UDP service (one connected socket per session)\nrstream nc -u -L rstrm://media -R udp://127.0.0.1:5004\n\n# Bind a local UDP socket and bridge it to a tunnel (one session per local peer)\nrstream nc -u -L udp://127.0.0.1:5004 -R rstrm://media\n\n# Receive-only local apps never send first; pin the peer to open the session eagerly\nrstream nc -u -L udp://127.0.0.1:5004 -R rstrm://media --udp-peer 127.0.0.1:5006\n```\n\nThe CLI defaults to automatic tunnel transport selection: it prefers QUIC and falls back to TLS when the UDP path is unavailable. When QUIC is selected, tunnel-side packets can ride QUIC datagrams (RFC 9221): they are congestion-controlled but never retransmitted, so delivery is not guaranteed and each datagram must fit the path MTU budget (roughly 1200 bytes is a safe payload target, for stdio frames and UDP packets alike). Oversized datagrams are dropped and logged without terminating the session. Use `--datagram-guaranteed-delivery` when packet boundaries must be preserved without loss; that mode carries packets over reliable streams even when the control channel uses QUIC.\n\nClosing either side of a datagram channel closes the peer session as well. `--idle-timeout` handles a different case: it closes a session after no datagram has been received for the given duration. Use it only on a side that expects inbound packets. A send-only producer receives no traffic to refresh the deadline and must not set it. In datagram exec sessions the child's stderr goes to the local stderr rather than the connection, since raw stderr bytes would corrupt the framing.\n\n### UDP/datagram tunnels\nFor datagram transport, choose DTLS mode when you need encrypted UDP-style traffic.\n\n```bash\n# Create a DTLS tunnel for UDP traffic on port 5000\nrstream forward 5000 --dtls\n```\n\nDTLS tunnels automatically handle datagram traffic. The `--datagram` flag is implied with `--dtls`.\n\n### Run (declarative tunnels)\n\n`rstream run` keeps tunnels in sync from a YAML file or Docker labels, with optional watch/reconcile.\n\n- `forward`: creates a single tunnel from CLI args and forwards immediately (single tunnel, interactive).\n- `run --apply`: declarative list of tunnels from YAML, supports watch/reconcile.\n- `run --docker`: discovers tunnels from Docker labels, supports watch/reconcile.\n\n```bash\n# Apply a YAML spec once\nrstream -v run --apply examples/run-yaml/tunnels.yaml\n\n# Watch the YAML for changes and reconcile\nrstream -v run --apply examples/run-yaml/tunnels.yaml --watch\n\n# Discover tunnels from Docker labels\nrstream -v run --docker --watch\n```\n\nSee `docs/008-cmd-run.md` for the full YAML schema, Docker label reference, and reconciliation details.\n\n## Build and compilation\n\nrstream includes a comprehensive Makefile supporting multiple platforms and packaging formats.\n\n### Development\nFor daily development loops, these targets build, test, and clean local artifacts.\n```bash\nmake          # Build for current platform\nmake clean    # Clean build artifacts\nmake tests    # Run test suite\nmake examples # Build example applications\n```\n\n### Cross-platform compilation\nWhen producing binaries for multiple targets, use:\n```bash\nmake cross    # Build for all supported platforms\n```\n\nSupports Linux, macOS, Windows, and BSD systems across multiple architectures including x86, ARM, MIPS, PowerPC, and RISC-V.\n\n### Packaging\nFor release packaging workflows, these targets build archives and platform packages:\n```bash\nmake pkg         # Create packages for current platform\nmake pkg-cross   # Create packages for all platforms\nmake deb         # Create Debian/Ubuntu packages\nmake docker      # Build Docker images\nmake nupkg       # Create Windows NuGet packages\n```\n\n## Code examples\n\nThe Go SDK enables applications to create and manage tunnels programmatically. The examples below use `config.NewClientFromEnv()` to read the same config and environment settings as the CLI. Ensure a default context (or `RSTREAM_ENGINE`) is set, and provide either `RSTREAM_AUTHENTICATION_TOKEN` or the mTLS certificate/key environment variables if the selected agent control channel requires authentication. Engine HTTP API operations require token authentication.\n\n### Managed TURN credentials\n\nThe SDK also exposes helpers to generate managed TURN credentials.\n\n- `config.CreateTURNCredentialsFromEnv(...)` resolves the current config, context, and token automatically.\n- Auto mode selects local PAT derivation when the active token carries\n  `token_endpoint` and the context includes the TURN domain, realm, and listener\n  ports. It falls back to the Control plane API when that routing contract is\n  incomplete.\n- Explicit `PAT` mode requires a PAT token. Explicit `API` mode can use either a project ID or a project endpoint.\n\nThe TURN domain is the relay hostname placed in ICE URLs. The TURN realm is the\nauthentication scope used for credential derivation. A regional relay attached\nto a global edge network can use different values. Context resolution keeps\nboth fields explicit and never infers the realm from the engine address.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/rstreamlabs/rstream-go/config\"\n)\n\nfunc main() {\n\tturn, err := config.CreateTURNCredentialsFromEnv(\n\t\tcontext.Background(),\n\t\tconfig.TURNCredentialsEnvOptions{},\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(turn.Username)\n\tfmt.Println(turn.URLs)\n}\n```\n\n### Explicit client options (exception)\n\nUse this only when bypassing config and environment resolution is required.\n\n```go\npackage main\n\nimport \"github.com/rstreamlabs/rstream-go\"\n\nfunc main() {\n\tclient, err := rstream.NewClient(rstream.ClientOptions{\n\t\tEngine: \"engine.example:443\",\n\t\tToken:  \"authentication_token\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t_ = client\n}\n```\n\n### HTTP server (published tunnel)\n\nThis example creates a published HTTP tunnel and serves requests through the tunnel listener. The forwarding address printed by `ForwardingAddress()` is the public URL that can be used from a browser or `curl`.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/rstreamlabs/rstream-go\"\n\t\"github.com/rstreamlabs/rstream-go/config\"\n)\n\nfunc main() {\n\tclient, err := config.NewClientFromEnv()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tctrl, err := client.Connect(context.Background(), nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ctrl.Close()\n\ttunnel, err := ctrl.CreateTunnel(context.Background(), rstream.TunnelProperties{\n\t\tProtocol:    rstream.ProtocolPtr(rstream.ProtocolHTTP),\n\t\tHTTPVersion: rstream.HTTPVersionPtr(rstream.HTTP1_1),\n\t\tPublish:     rstream.BoolPtr(true),\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer tunnel.Close()\n\taddr, _ := tunnel.ForwardingAddress()\n\tfmt.Printf(\"Server accessible at: %s\\n\", addr)\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintln(w, \"Hello from rstream!\")\n\t})\n\thttp.Serve(tunnel.(net.Listener), nil)\n}\n```\n\n### TLS echo (private tunnel)\n\nThis example shows a private tunnel workflow. The server creates a non-published tunnel named `echo` and accepts inbound tunnel connections. The client dials the private tunnel by name and exchanges data over the resulting stream.\n\n**Server code:**\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\n\t\"github.com/rstreamlabs/rstream-go\"\n\t\"github.com/rstreamlabs/rstream-go/config\"\n)\n\nfunc main() {\n\tclient, err := config.NewClientFromEnv()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tctrl, err := client.Connect(context.Background(), nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ctrl.Close()\n\ttunnel, err := ctrl.CreateTunnel(context.Background(), rstream.TunnelProperties{\n\t\tName:    rstream.StringPtr(\"echo\"),\n\t\tPublish: rstream.BoolPtr(false),\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer tunnel.Close()\n\tprops, err := tunnel.Properties()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Printf(\"Echo server running as private tunnel: %s\", *props.Name)\n\tlistener := tunnel.(net.Listener)\n\tfor {\n\t\tconn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tgo func(c net.Conn) {\n\t\t\tdefer c.Close()\n\t\t\tlog.Printf(\"New connection from %s\", c.RemoteAddr())\n\t\t\tio.Copy(c, c)\n\t\t}(conn)\n\t}\n}\n```\n\n**Client code:**\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/rstreamlabs/rstream-go\"\n\t\"github.com/rstreamlabs/rstream-go/config\"\n)\n\nfunc main() {\n\tclient, err := config.NewClientFromEnv()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tconn, err := client.Dial(context.Background(), rstream.Addr{\n\t\tIdOrName: \"echo\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer conn.Close()\n\tmessage := \"Hello from private tunnel!\"\n\tconn.Write([]byte(message))\n\tbuffer := make([]byte, len(message))\n\tconn.Read(buffer)\n\tfmt.Printf(\"Sent: %s\\nReceived: %s\\n\", message, string(buffer))\n}\n```\n\n### DTLS datagram server\n\nThis example creates a published DTLS datagram tunnel. The forwarding address is where datagram clients connect. The server uses the packet listener API to accept datagram sessions and echo packets.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/rstreamlabs/rstream-go\"\n\t\"github.com/rstreamlabs/rstream-go/config\"\n)\n\nfunc main() {\n\tclient, err := config.NewClientFromEnv()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tctrl, err := client.Connect(context.Background(), nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ctrl.Close()\n\ttunnel, err := ctrl.CreateTunnel(context.Background(), rstream.TunnelProperties{\n\t\tName:     rstream.StringPtr(\"dtls\"),\n\t\tType:     rstream.TunnelTypePtr(rstream.TunnelTypeDatagram),\n\t\tProtocol: rstream.ProtocolPtr(rstream.ProtocolDTLS),\n\t\tPublish:  rstream.BoolPtr(true),\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer tunnel.Close()\n\taddr, _ := tunnel.ForwardingAddress()\n\tfmt.Printf(\"DTLS server accessible at: %s\\n\", addr)\n\tpacketListener := tunnel.(rstream.PacketListener)\n\tfor {\n\t\tconn, _, err := packetListener.Accept()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Accept error: %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tgo func() {\n\t\t\tdefer conn.Close()\n\t\t\tbuffer := make([]byte, 1024)\n\t\t\tfor {\n\t\t\t\tn, addr, err := conn.ReadFrom(buffer)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tlog.Printf(\"Received %d bytes from %s\", n, addr)\n\t\t\t\tn, err = conn.WriteTo(buffer[:n], addr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n}\n```\n\n### QUIC server\n\nThis example creates a published QUIC datagram tunnel. QUIC is a modern transport designed for low latency and resilience to network changes. The sample generates a local TLS configuration and serves QUIC streams over the tunnel packet listener.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/big\"\n\n\t\"github.com/quic-go/quic-go\"\n\t\"github.com/rstreamlabs/rstream-go\"\n\t\"github.com/rstreamlabs/rstream-go/config\"\n)\n\nfunc generateTLSConfig() (*tls.Config, error) {\n\tkey, err := rsa.GenerateKey(rand.Reader, 1024)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttemplate := x509.Certificate{SerialNumber: big.NewInt(1)}\n\tcertDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeyPEM := pem.EncodeToMemory(&pem.Block{Type: \"RSA PRIVATE KEY\", Bytes: x509.MarshalPKCS1PrivateKey(key)})\n\tcertPEM := pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: certDER})\n\ttlsCert, err := tls.X509KeyPair(certPEM, keyPEM)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &tls.Config{\n\t\tCertificates: []tls.Certificate{tlsCert},\n\t}, nil\n}\n\nfunc main() {\n\tclient, err := config.NewClientFromEnv()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tctrl, err := client.Connect(context.Background(), nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ctrl.Close()\n\ttunnel, err := ctrl.CreateTunnel(context.Background(), rstream.TunnelProperties{\n\t\tName:     rstream.StringPtr(\"quic\"),\n\t\tType:     rstream.TunnelTypePtr(rstream.TunnelTypeDatagram),\n\t\tProtocol: rstream.ProtocolPtr(rstream.ProtocolQUIC),\n\t\tPublish:  rstream.BoolPtr(true),\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer tunnel.Close()\n\taddr, _ := tunnel.ForwardingAddress()\n\tfmt.Printf(\"QUIC server accessible at: %s\\n\", addr)\n\ttlsCfg, err := generateTLSConfig()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\ttransport := quic.Transport{\n\t\tConn: rstream.PacketConnFromPacketListener(tunnel.(rstream.PacketListener)),\n\t}\n\tlistener, err := transport.Listen(tlsCfg, nil)\n\tdefer listener.Close()\n\tfor {\n\t\tconn, err := listener.Accept(context.Background())\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Accept error: %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tgo func() {\n\t\t\tdefer conn.CloseWithError(0, \"server done\")\n\t\t\tstream, err := conn.AcceptStream(context.Background())\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer stream.Close()\n\t\t\tbuffer := make([]byte, 1024)\n\t\t\tfor {\n\t\t\t\tn, err := stream.Read(buffer)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tlog.Printf(\"Received %d bytes from %s\", n, conn.RemoteAddr())\n\t\t\t\tn, err = stream.Write(buffer[:n])\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n}\n```\n\n## References\n\n- Documentation: https://rstream.io/docs\n- Go SDK (reference implementation): https://github.com/rstreamlabs/rstream-go\n- C++ SDK: https://github.com/rstreamlabs/rstream-cpp\n\nOperational and advanced CLI/SDK workflows:\n\n- CLI workflow and authentication: [docs/001-cli-workflow.md](docs/001-cli-workflow.md)\n- Declarative run workflows: [docs/008-cmd-run.md](docs/008-cmd-run.md)\n- Transport configuration: [docs/002-transport.md](docs/002-transport.md)\n- Tunnel property reference: [docs/003-tunnel-properties.md](docs/003-tunnel-properties.md)\n\n## Contributing\n\nPull requests are encouraged and appreciated. Whether you're fixing bugs, adding features, improving documentation, or suggesting enhancements, your contributions help make rstream better for everyone. Build locally, run checks, and submit focused pull requests with clear validation notes. See [CONTRIBUTING.md](CONTRIBUTING.md) for the repository-specific contribution guidelines.\n\n## Support\n\n**Get help:**  \nsupport@rstream.io\n\n**Report security concerns:**  \nreports@rstream.io\n\nSee [SECURITY.md](SECURITY.md) for the security reporting guidance used by this repository.\n\n## License\n\nThis repository is licensed under the Apache License 2.0. See [LICENSE](LICENSE).\n",
  "bytes": 44653,
  "sha": "234812acc9f44ad9cff5886d95a72bf84f8aafbf7ea7be386632ea9fce1e9514",
  "repo_slug": "rstreamlabs/rstream-go",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_rstreamlabs_rstream_cdaeef95/readme"
}