远程命令与文件传输服务

RemoteCmd 是一款中继式远程运维服务,用于在无需开放入站端口的情况下跨 NAT/防火墙执行远程命令、传输文件和截取屏幕,并可供脚本或 AI 代理通过 MCP 接口调用。

  • by nks-hub
  • C#
  • 混合部署
  • 更新于
在 GitHub 查看 nks-hub/remote-cmd

使用说明

RemoteCmd

Run commands and move files on remote machines through NAT and firewalls, from scripts or from AI agents. A relay sits in the middle; machines poll it over outbound HTTP, so they need no open ports. The relay speaks MCP itself, so an agent such as Claude Code connects with one URL.

 AI agent / curl / script                Relay (RemoteCmd.Server)                Machines (RemoteCmd.Client)
 ────────────────────────                ────────────────────────                ───────────────────────────
 MCP  POST /mcp  (OAuth) ───────┐        :7890  relay port, token required  ◄─── poll every 800 ms (outbound only)
 REST /api/exec, /api/upload ───┴──────► :7891  dashboard, this machine only       run PowerShell / bash, move files

Features

  • 🌐 No inbound ports - Machines poll the relay over outbound HTTP every 800 ms and work behind NAT and firewalls
  • 🤖 Built-in MCP server - AI agents connect with one URL (Streamable HTTP, OAuth with PKCE) after approval on the dashboard
  • 💻 Remote commands - Run PowerShell or bash on any paired machine and get the output back
  • 📁 File transfer - Upload and download files (REST up to 200 MB, MCP inline up to 20 MB)
  • 📸 Screenshots - Capture the remote machine's screen as PNG
  • 🔑 Pairing codes - One-time codes with ready-made install commands; each machine gets its own 256-bit key
  • 🔒 Layered security - TLS with certificate pinning plus AES-256-GCM payload encryption, hashed tokens, brute-force lockout
  • 📊 Local dashboard - Machines, live activity, command output, pairing, agent approvals and credential revocation
  • 🖥️ Cross-platform - Relay and client for Windows, Linux and macOS (x64 and arm64), installed as a service
  • 🧩 Legacy and Android clients - .NET Framework 4.8 client for Windows 7 and a Kotlin client for rooted Android devices

Requirements

  • Windows, Linux or macOS on x64 or arm64; release builds are self-contained and need no separate .NET runtime
  • Administrator / root rights for the installer, which registers a Windows Service, systemd unit or launchd job
  • .NET 9 SDK to build from source
  • Legacy client: .NET Framework 4.8 (Windows 7 and later)
  • Android client: rooted device (su), JDK 17 to build
  • Screenshots on Linux: one of grim, scrot, gnome-screenshot, spectacle or ImageMagick's import

Installation

Install the relay

Windows (elevated PowerShell):

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/nks-hub/remote-cmd/main/install.ps1))) -Role server

Linux / macOS:

curl -fsSL https://raw.githubusercontent.com/nks-hub/remote-cmd/main/install.sh | sudo sh -s -- server

This installs the relay as a service (Windows Service, systemd or launchd) and prints the dashboard address. Open http://localhost:7891 on that machine.

Usage

Add machines

On the dashboard press Pair a machine, pick the address the machine reaches the relay at (LAN addresses come first, VPN and virtual adapters are labelled) and create a code. You get a one-time code and ready-made commands with it filled in; run the one for the machine's system. The dialog follows the machine until its client is online, and says so if it paired but never connected:

# Windows, elevated PowerShell
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/nks-hub/remote-cmd/main/install.ps1))) -Server 'https://relay:7890' -Code 'K7F2Q-9XDMA'
# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/nks-hub/remote-cmd/main/install.sh | sudo sh -s -- client --server 'https://relay:7890' --code 'K7F2Q-9XDMA'

The script downloads the client for the right system and CPU (x64 or arm64), trades the code for the machine's own key, pins the relay's certificate and starts the client as a service. Running the same script again without a code upgrades the client and keeps its settings.

The code can also be created on the relay's command line: RemoteCmd.Server pair --name build-01.

Connect an AI agent

claude mcp add --transport http remote-cmd http://localhost:7891/mcp      # agent on the relay's machine
claude mcp add --transport http remote-cmd https://relay:7890/mcp         # agent elsewhere

The agent opens a browser page with a six-digit code. The same request appears on the relay's dashboard, where you approve it after checking that the codes match. Approval issues the agent its own credential; revoking it on the dashboard cuts the agent off.

Tools: remote_list_clients, remote_exec, remote_write_file, remote_read_file (file contents travel inline, up to 20 MB; use the REST API for larger files) and remote_screenshot, which returns a picture of the machine's screen.

A screenshot needs a desktop to photograph. A client installed as a Windows service runs in session 0 and captures a blank image; the reply says so rather than leaving the agent with an unexplained black rectangle. Run the client in the interactive session when screenshots matter. Linux clients need one of grim, scrot, gnome-screenshot, spectacle or ImageMagick's import; macOS and Windows need nothing extra.

Scripts and curl

Create a controller token on the dashboard (Access → New controller key) or with RemoteCmd.Server token add --name ci, then:

H="X-Token: <controller token>"
curl -H "$H" https://relay:7890/api/clients
curl -H "$H" -H "Content-Type: application/json" "https://relay:7890/api/exec?client=build-01" -d '{"command":"hostname","timeoutSeconds":30}'
curl -H "$H" --data-binary @local.zip "https://relay:7890/api/upload?client=build-01&path=C:/dest/file.zip"
curl -H "$H" -o out.log "https://relay:7890/api/download?client=build-01&path=/var/log/app.log"

Add -k while the relay uses its self-signed certificate.

The dashboard

Listens on http://localhost:7891 and nowhere else.

  • Overview: one page with a card per machine (alias, real host name, account, system, client version flagged when a newer release exists), a command line, and the activity log below it: every command with its output, transfers, connections and refused requests, with search and filters. Clicking a machine narrows the log to it; its details panel has re-pairing and removal from the list once it is offline.
  • Access: every key with when it was last used and a revoke button, open pairing codes, and machines that keep polling with a key the relay does not know (usually an old client left running after a re-pairing), each with a button to pair it again.
  • AI agents: MCP sign-ins waiting for approval and the claude mcp add commands.
  • Settings: the relay address used in install commands, chosen from this machine's addresses, the dashboard password, and TLS details.

The pages are files in RemoteCmd.Server/Web, embedded in the binary and served with a Content-Security-Policy that allows only their own scripts and styles.

With no password the dashboard is open to anyone who has a session on the relay's machine. Set one on the page or with RemoteCmd.Server set-password.

Security

What How
Machines Each paired machine gets its own 256-bit key. It may only poll for work and answer it. It cannot send commands, list other machines or pose as another machine, because its identity comes from its key, not from what it claims.
Controllers Separate tokens for scripts and agents. Agents get theirs only through OAuth (PKCE, dynamic client registration) approved on the dashboard.
Storage The relay keeps hashes of tokens and the derived encryption keys, never the tokens. Settings files are readable only by the owner (0600, or SYSTEM + Administrators + owner on Windows). Tokens stay off command lines and out of service definitions.
Pairing The one-time code (50 bits, 15 min, burnt after 5 wrong proofs) authenticates an ECDH exchange, so the issued key never crosses the network in a usable form, even over plain HTTP.
Transport TLS with the relay's own certificate; paired machines pin it and refuse anything else. Every command, result and file is additionally AES-256-GCM encrypted with the machine's key.
Dashboard Loopback only; refuses foreign Host headers (DNS rebinding); every change needs a custom header a cross-site page cannot send; optional password with an HttpOnly, SameSite=Strict session.
Brute force Wrong tokens, codes and passwords lock the sender out for 5 minutes after 10 tries a minute.

Tokens given on the relay's command line (RemoteCmd.Server <token>…) still work, with full rights, for setups from before pairing. Move them into the settings file with RemoteCmd.Server token import <token> --name fleet and start the relay without them.

Command reference

Relay

RemoteCmd.Server [--port 7890] [--admin-port 7891] [--config <file>] [--no-tls] [--dashboard]
RemoteCmd.Server install-service | uninstall-service
RemoteCmd.Server pair [--name <machine>] [--ttl <minutes>]
RemoteCmd.Server token add --name <label> [--role controller|client]
RemoteCmd.Server token import <token> --name <label> [--role legacy|client|controller]
RemoteCmd.Server token list | token revoke <id>
RemoteCmd.Server set-password [--clear]

Settings live in %ProgramData%\RemoteCmd\server.json (Linux/macOS /etc/remotecmd/server.json), or in the user's profile when that is not writable.

Client

RemoteCmd.Client pair <server> <code> [--name <alias>] [--install-service]
RemoteCmd.Client [--dashboard] [--keep-awake]                    # run with saved settings
RemoteCmd.Client install-service | uninstall-service [--service-name <name>]
RemoteCmd.Client <server> <token> [--name <alias>]                # without pairing, as before

<server> may be a bare host (https on port 7890), host:port or a full URL. Several clients can share a machine with different --service-name values.

Legacy Windows 7 / .NET Framework 4.8 client

RemoteCmd.Client48 is a wire-compatible port for hosts without .NET 9. It works with tokens (RemoteCmd.Client48.exe <server> <token> --name <alias>, install-service likewise), not pairing.

Android client (rooted devices)

android/ is a Kotlin app with a foreground service that runs commands as root via su. Configure server, token and name in the app. Build with cd android && ./gradlew assembleDebug.

API

Controller endpoints take a token in the X-Token header or as Authorization: Bearer.

Method Endpoint Description
GET /api/clients Machines with state, host, user, system and version
GET /api/status[?client=X] Aggregate status, or one machine's
POST /api/exec[?client=X] Run {"command":"...","timeoutSeconds":30}
POST /api/upload?path=<remote>[&client=X] Upload a file (binary body, max 200 MB)
GET /api/download?path=<remote>[&client=X] Download a file
POST /api/screenshot[?client=X][&path=<remote>] Photograph the screen, returns PNG
GET /api/events[?limit=N] Recent relay activity and counters
POST /mcp MCP, Streamable HTTP, OAuth bearer token

Without client, a command goes to the only connected machine; with several connected, the error lists them.

Environment variables

Variable Where Description
REMOTECMD_PORT / REMOTECMD_ADMIN_PORT Relay Ports (default 7890 / relay port + 1)
REMOTECMD_CONFIG Relay Settings file
REMOTECMD_NO_TLS Relay 1 serves plain HTTP (payloads stay encrypted)
REMOTECMD_TOKEN(S) Relay Command-line style tokens, full rights, this run only
REMOTECMD_NO_UPDATE_CHECK Relay 1 skips the GitHub release check

Development

dotnet build RemoteCmd.sln
dotnet test RemoteCmd.sln

dotnet publish RemoteCmd.Client -c Release -r win-x64 --self-contained \
  -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o publish/client

Releases ship the relay and client for win-x64, win-arm64, linux-x64, linux-arm64, osx-x64 and osx-arm64.

Contributing

Contributions are welcome! For major changes, please open an issue first.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'feat: description')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Support

License

MIT License — see LICENSE for details.


Made with ❤️ by NKS Hub