用 Rust 写一个不依赖 OpenSSL 的命令行 SSH 工作台:r-shell 实战
运维和后端开发里有一类麻烦,不是「连不上服务器」这种大事,而是一堆很小但天天重复的操作:
- 连上去敲一条命令看看状态,
- 把一个安装包传上去,
- 把一份日志拉下来,
- 瞄一眼 CPU 和内存满没满,
- 然后在生产、测试、数据库、日志机之间来回切。
每一件单独看都不算事。可一旦机器多起来,系统自带的ssh/scp/sftp各管一摊、IP 和端口靠脑子记、参数每次重敲——这种「碎」就成了真正的时间黑洞。
r-shell 想把这摊碎活收进一个命令行二进制里:connections管连接、exec跑命令、shell开交互终端、upload/download走 SFTP、stats打系统快照。它用 Rust 写内核,SSH 栈是纯 Rust 的russh,不依赖 OpenSSL / libssh,所以 macOS / Linux / Windows 都能出一个零依赖、拷过去就能跑的单文件。
这篇不讲 AI,只讲一件事:怎么把多服务器的日常操作,收敛成一个顺手、可脚本化、带安全默认值的命令行工具。文中每条命令和输出都对应仓库里能跑的实现。
一、它能干什么
一张表先把全貌摆出来:
| 子命令 | 作用 |
|---|---|
connections | 增删改查已保存的主机(存在本地workspace.json) |
exec | 在远程主机上执行单条命令并打印输出 |
shell | 打开完整交互式 PTY(vim / htop / less 都能用) |
ls | 列出远程目录 |
upload/download | 通过 SFTP 单文件上传 / 下载 |
stats | 一次性的 CPU / 内存 / 磁盘 / 网络快照(Linux 与 Windows 通吃) |
mcp | 启动本地 MCP 服务器(可选,本文不展开) |
同一个二进制在三大平台都能跑。exec/shell/upload/download适用于任意 POSIX 主机;ls面向 Linux(依赖 GNUls);stats则做了 OS 适配,Linux 读/proc、Windows 走 PowerShell CIM,都能出数。
二、和系统自带的 ssh / scp 有什么区别
常有人问:ssh、scp不是够用了吗?够用,但 r-shell 解决的是「够用之上的碎」。它的定位看这张表最清楚:
| 场景 | 传统 ssh / scp | r-shell |
|---|---|---|
| 连接信息管理 | 手写~/.ssh/config或脑子记 IP | connections子命令统一增删改查 |
| 执行远程命令 | ssh user@host "cmd" | r-shell exec -c prod -- cmd |
| 文件传输 | scp单独拼路径 | upload/download复用同一份连接定义 |
| 看负载 | 手动top/df/free各看一遍 | stats一条命令聚合成一屏 |
| 输出 | 面向人眼 | 表格之外都给--json,对脚本友好 |
| 安全默认 | 取决于系统与个人习惯 | 内置主机密钥 TOFU、凭据文件0600 |
| 分发 | 依赖系统自带 | 纯 Rust 单二进制,零系统依赖,拷了就跑 |
一句话:r-shell 不是要取代ssh,而是把「连接管理 + 常用操作 + 安全默认 + 可脚本化」打包成一个顺手的工具。
三、技术选型:为什么是 Rust + russh,且不依赖 OpenSSL
三个关键决策:
- 语言用 Rust。SSH、SFTP、系统采样这些活儿,既要正确又要能扛并发,Rust 的所有权和类型系统能把「连接状态、凭据边界」这类不变量直接编译进类型里,少一类运行期惊吓。
- SSH 栈用纯 Rust 的
russh,而不是去绑 OpenSSL / libssh。这一条对「分发」太关键了:一旦动态依赖 OpenSSL,你在 Windows 上就得操心运行时和版本地狱;改用russh后,整套握手、认证、PTY、known_hosts都在 Rust 里,编出来就是一个零系统依赖的单二进制,拷到一台干净机器上直接能跑。 - 异步用 tokio。SSH / SFTP 是典型 I/O 密集场景,全程异步;命令行参数与子命令树交给
clap的 derive 宏声明,--help自动生成;交互终端的 raw mode 和无回显密码输入交给crossterm。
依赖很克制,核心就这几样(core的Cargo.toml):
russh # 纯 Rust SSH 协议:握手 / 认证 / 通道 / PTY russh-keys # 密钥与 known_hosts 处理 russh-sftp # SFTP 子系统:文件传输 / 列目录 tokio # 异步运行时 sysinfo # 本机资源采样CLI 这一层再叠上clap(命令行)和crossterm(终端控制)。全程没有 OpenSSL。
四、架构:CLI 与桌面端共享同一套内核
r-shell 最初其实是个图形界面应用。正因为业务逻辑和界面一开始就解耦,后来重构出命令行版时,几乎零成本复用了全部后端——CLI 和桌面 GUI共用同一个core:同样的连接管理、同样的 SSH 实现、同样的系统采样。你在 GUI 里存的连接,CLI 也认;反过来也成立。
┌───────────────────────────┐ 桌面 GUI ───┤ flutter_rust_bridge (FFI) ├──┐ └───────────────────────────┘ │ ▼ r-shell CLI ───────────────────────▶ core(共享内核) ├── native_backend SSH/SFTP、连接管理器 ├── monitor /proc 与 PowerShell CIM 采样 ├── model SavedConnection / 脱敏 sanitized() └── storage workspace.json(0600)CLI 入口本身很薄,clap解析完就把活儿丢给core:
cli/ └── src/main.rs # 参数解析、子命令分发、输出格式化(表格 / JSON) core/ └── src/ # native_backend / monitor / model / storage / mcp —— CLI 与 GUI 共用五、命令实战(附真实输出)
下面按子命令给出可直接复制的示例,输出格式都对应main.rs里真实的打印逻辑。
1. connections —— 像通讯录一样管连接
# 新增一个用公钥认证的连接 r-shell connections add \ --name prod-web --host 203.0.113.10 --username deploy \ --auth publickey --key-path ~/.ssh/id_ed25519 \ --folder Work --description "生产环境 Web 服务器" # 列表(表格) r-shell connections list表格输出:
ID NAME HOST AUTH FOLDER ssh-1781247286839 prod-web deploy@203.0.113.10:22 publickey Work ssh-1781247290114 test-db root@10.0.0.5:22 password Staging加--json给脚本用,而且是脱敏的——只告诉你有没有密码 / 私钥,绝不回吐明文:
[ { "connection_id": "ssh-1781247286839", "name": "prod-web", "host": "203.0.113.10", "username": "deploy", "port": 22, "auth_method": "publickey", "folder": "Work", "tags": [], "description": "生产环境 Web 服务器", "status": "Disconnected", "has_password": false, "has_private_key_path": true } ]改字段、删连接也都在一条命令里:
r-shell connections update ssh-1781247290114 --port 2222 --folder Staging r-shell connections remove ssh-17812472901142. exec —— 跑一条命令就走
--之后的内容原样发给远端。既能用已存连接(-c),也能临时给--host:
r-shell exec -c prod-web -- uname -a r-shell exec -c prod-web -- "ls -la /var/www && df -h" r-shell exec --host 203.0.113.10 --user deploy -- systemctl status nginx「连接 → 执行 → 自动断开」一气呵成,特别适合写进巡检 / 部署脚本里批量跑。
3. shell —— 完整交互式终端
raw 模式下的完整 PTY,vim、htop、less这类需要伪终端的程序都能正常用。会话卡住时按Ctrl-]强制退出本地循环:
r-shell shell -c prod-web4. ls —— 列远程目录
r-shell ls -c prod-web /var/log输出列依次是:类型(DIR/FILE/LNK)、权限、大小、修改时间、名称:
DIR drwxr-xr-x 4096 2026-06-20 14:31 nginx FILE -rw-r--r-- 10240 2026-06-21 09:02 syslog FILE -rw-r--r-- 204800 2026-06-21 08:55 dpkg.log LNK lrwxrwxrwx 24 2026-06-18 02:00 alternatives.log加--json同样可以拿去喂脚本。
5. upload / download —— SFTP 传文件
走 SFTP,不依赖远端有没有scp,只要 SSH 通就能传:
# 本地 -> 远程 r-shell upload -c prod-web ./app.tar.gz /tmp/app.tar.gz # Uploaded ./app.tar.gz -> /tmp/app.tar.gz (12.4 MB) # 远程 -> 本地 r-shell download -c prod-web /tmp/remote.log ./local.log # Downloaded /tmp/remote.log -> ./local.log (843.2 KB)6. stats —— 一屏看完系统资源
连续采样两次,算出 CPU 占用和网络速率,一条命令出一份快照。Linux 上读/proc:
r-shell stats -c prod-webOS: Linux 6.1.0 Uptime: 12d 4h 31m CPU: 7.4% (8 cores, load 0.42) Memory: 61.2% (4.9/7.8 GB) Disk: 40.0% (3.8/9.5 GB) Network: down 1.5 KB/s up 320 B/s同一条命令打到 Windows 主机也能出数——内核里换成一段 PowerShell CIM 采集,自动处理好编码:
OS: Microsoft Windows 11 专业工作站版 10.0.29591 Uptime: 7d 6h 12m CPU: 9.0% (8 cores, load 0.00) Memory: 52.0% (8.3/15.9 GB) Disk: 52.4% (56.3/118.1 GB) Network: down 18.6 KB/s up 2.4 KB/s六、一次性命令的生命周期:连接 → 执行 → 断开
exec/ls/upload/download/stats都遵循同一套「用完即走」的模型,这也是它适合脚本化的原因:
解析目标(-c 已存连接 / --host 临时主机;缺密码则安全提示输入) ↓ 建立 SSH 连接(握手 → 主机密钥 TOFU 校验 → 密码或公钥认证) ↓ 执行子命令对应的操作 ↓ 自动关闭连接,干净退出每条命令都自包含、互不残留状态,写进 CI / 巡检脚本里行为可预测。(需要在一条会话里连发多条命令、保持工作目录的场景,则交给shell的交互式 PTY。)
七、面向运维的安全默认值
SSH 工具直连服务器凭据,安全不能是「可选项」。r-shell 把几条底线做成了默认行为:
- 主机密钥 TOFU(首次信任)。按
~/.ssh/known_hosts校验:首次连接记录主机密钥,之后必须一致;一旦对不上(典型的中间人,或主机被换),直接中止连接而不是默默连上去。只有显式传--insecure才跳过——那是留给一次性测试机的,不是日常姿势。 - 凭据只进不出。密码、私钥路径、口令绝不会被打印;任何连接列表都走
sanitized(),只暴露has_password/has_private_key_path这种布尔值。 - 密码输入不回显。终端进 raw 模式逐字节读取,屏幕上不显形。
- 配置文件仅属主可读。
workspace.json在 Unix 上以0600创建、目录0700,不让同机器上别的用户cat到你的跳板信息。
已保存连接的落盘位置:
| 系统 | 路径 |
|---|---|
| macOS | ~/Library/Application Support/r-shell/workspace.json |
| Linux | ~/.local/share/r-shell/workspace.json |
| Windows | %LOCALAPPDATA%\r-shell\workspace.json |
八、安装
源码编译
只需要 Rust 和 Cargo(rustup.rs),不需要 OpenSSL 或 libssh:
git clone https://github.com/MageGojo/r-shell-cli.git cd r-shell-cli cargo build --release --manifest-path cli/Cargo.toml sudo install -m 0755 cli/target/release/r-shell /usr/local/bin/r-shell r-shell --version不想装,直接跑也行:
cargo run --manifest-path cli/Cargo.toml -- exec -c prod-web -- uptime预编译包
懒得编译的话,Releases 里有现成的:
| 平台 | 文件 |
|---|---|
| macOS(Apple Silicon) | r-shell-macos-apple-silicon.dmg |
| macOS(Intel) | r-shell-macos-intel.dmg |
| Windows x64 | r-shell-windows-x64-installer.exe |
macOS 上把二进制拷进PATH后,记得清一下 Gatekeeper 隔离标记:xattr -dr com.apple.quarantine /usr/local/bin/r-shell;Windows 安装器会自动把r-shell加进PATH。因为是没买付费证书的开源软件,首次打开时按系统提示放行即可。
九、顺带一提:它还有个桌面端
抛开命令行,同一套core还撑起了一个 Flutter 写界面的桌面应用:命令块终端(每条命令和它的输出各成一块,带退出码、耗时)、实时监控走势图、SFTP 拖拽、多标签管多台。CLI 适合脚本和远程会话,GUI 适合盯监控和翻历史,两者数据互通。
另外,
r-shell mcp还能起一个只绑127.0.0.1的本地 MCP 服务器,让支持 MCP 的工具借一条常驻 SSH 会话干活——那是另一个话题,本文不展开。
十、适合谁
- 运维 / DevOps:统一管理一堆服务器,批量
exec巡检、upload/download发版拉日志,把远程操作写进流水线。 - 后端开发:快速连测试机执行命令、传安装包,不用每次拼一长串
ssh/scp。 - 想要零依赖单文件的人:不想在目标机上装一堆东西,拷一个二进制就能用。
- Rust 学习者:
clap子命令树、russh的 SSH 编程、tokio异步 I/O、known_hosts校验、业务与界面解耦——都是能直接照着读的活例子。
如果你只管一两台机器,它是个顺手的 SSH 工具;如果你要管很多台并写自动化,它的连接管理和可脚本化会更值。
十一、开源与下载
r-shell 已开源(MIT),由极数本源(apizero.cn)· MageGojo出品:
- 源码:github.com/MageGojo/r-shell-cli
- 下载:Releases
觉得有用就去仓库点个 star。日常要在多台服务器之间跑命令、传文件、看负载的,可以拿它当一个轻量、安全、可脚本化的 SSH 工作台;在学 Rust 命令行开发的,也很适合作为源码阅读和二次开发的参考。
⚠️免责声明:本项目仅供学习与合法运维使用。SSH / 命令执行 / 文件读写均会对远程主机产生实际影响,使用者应自行确认操作对象与命令内容,并对重要数据做好备份。请仅在你拥有合法授权的主机上使用;使用本工具产生的一切后果由使用者自行承担。
