README 改写成开源首页:中英文两版、真实窗口截图(主界面深浅两色、分栏、SFTP、端口转发),原来的逐项说明、发布和开发说明移到 docs/;以 MIT OR Apache-2.0 双许可发布
This commit is contained in:
1 parent
8df26abf37
commit
3d6801d469
16 files changed
+906
-290
No files matched your search
@@ -16,7 +16,7 @@
|
||||
# Running it by hand (workflow_dispatch) builds and packages from the
|
||||
# current commit without publishing, to try the pipeline.
|
||||
#
|
||||
# Secrets, see README.md › 发布:
|
||||
# Secrets, see docs/release.md:
|
||||
# repository: APPLE_CERTIFICATE_P12 (base64), APPLE_CERTIFICATE_PASSWORD,
|
||||
# APPLE_SIGNING_IDENTITY, APPLE_TEAM_ID, APPLE_API_KEY_P8
|
||||
# (base64), APPLE_API_KEY_ID, APPLE_API_ISSUER
|
||||
|
||||
@@ -6,7 +6,7 @@ ShellRS(crate 与二进制都叫 `shellrs`)是一个类似 Xshell / WinSCP
|
||||
|
||||
**保存的一项叫「主机」,代码里叫 `Host`。** 左侧列表里保存的一项(地址、端口、用户、认证方式)在界面上叫「主机」,量词用「台」,代码和数据库里一律叫 host(`Host`、`HostId`、`HostStore`、`host/` 模块、`hosts` 表)。它以前叫「会话」/ `Session`,界面、代码和表名都已改掉,新写的文案和标识符不要再用「会话」/ session 指它。填 IP / 域名的那个字段叫「地址」(`Host.address`、`hosts.address`),不叫「主机」(「主机密钥」「首次连接此主机」这些照旧)。SSH 协议里的 session 不在此列:「SSH 会话通道」、russh 的 `client::Session` / `server::Session`、`channel_open_session`、SFTP 会话照旧。
|
||||
|
||||
主机和分组是**真实持久化**的,存在一个 SQLite 文件里(`~/Library/Application Support/shellrs/shellrs.db`,`SHELLRS_DATA_DIR` 可覆盖目录)。本地终端(`portable-pty` + `alacritty_terminal`)和 SSH 远程连接(`russh`)也都是真的。密码与私钥口令存在系统钥匙串里(`keyring`),**数据库里永远不出现秘密**。SFTP 双栏浏览与上传也已接入真实文件系统,支持断点续传;下载、目录同步和传输队列尚未实现。详见 `README.md`。
|
||||
主机和分组是**真实持久化**的,存在一个 SQLite 文件里(`~/Library/Application Support/shellrs/shellrs.db`,`SHELLRS_DATA_DIR` 可覆盖目录)。本地终端(`portable-pty` + `alacritty_terminal`)和 SSH 远程连接(`russh`)也都是真的。密码与私钥口令存在系统钥匙串里(`keyring`),**数据库里永远不出现秘密**。SFTP 双栏浏览与上传也已接入真实文件系统,支持断点续传;下载、目录同步和传输队列尚未实现。详见 `docs/manual.md`。
|
||||
|
||||
已确认的产品决定:WinSCP 式双栏文件浏览器是每个主机独立的「SFTP」Dock 标签页;暂不做传输队列和 Dock 布局持久化;首次启动是空库,不预置任何分组或主机;分组支持任意层级嵌套,主机也可以不属于任何分组(渲染在树的根层级);删除分组会连同其子分组和里面的主机一起删,删除前确认并写明数量。
|
||||
|
||||
@@ -45,7 +45,7 @@ cargo test -- --ignored # 会读写真实钥匙串的测试
|
||||
- `explorer/` — 目录与选择快照、`DataTable` 双栏、上传确认 / 冲突对话框、进度和取消 / 恢复展示。`ExplorerAction` 统一路由到工作区;文件扫描与网络操作在后台,目录响应按请求编号丢弃过期结果。
|
||||
- `sftp/` — 可注入传输与本地目录接口、`RemotePath`、russh-sftp 3.0.0 协议适配、有界并发分块上传、UUID 临时文件、安全替换、原子 JSON 续传记录和三次自动重连。`ssh/connection.rs` 的 `SshConnector` 与终端共享认证、钥匙串服务及 known_hosts 写锁。
|
||||
- `workspace/` — `workspace_view.rs` 持有 `HostStore`、`DockArea`、按主机登记的面板注册表,以及**全部动作处理器**;`title_bar.rs`、`status_bar.rs`、`recent_hosts.rs`(中间区没有标签页时显示的「最近连接」开始页,**不是** Dock 面板)、`dock_skin.rs`(`WorkspaceDockSkin`:包一层 `DockSkin`,中间区为空时用 `deferred` 把开始页画在空的中间区之上;工作区在 `DockEvent::LayoutChanged` 时同步「中间区是否为空」并在刚变空时把焦点移到开始页)。
|
||||
- `update/` — 在线升级:`feed.rs` 从 `dl.shellrs.com` 取已签名的清单和安装包(reqwest,走系统代理),`verify.rs` 先验 minisign 签名(trusted comment 绑定通道和版本)再信清单、按大小和 SHA-256 信安装包,`install*.rs` 按安装方式装(macOS 整包交换、Windows 运行 Inno 安装程序、Linux 覆盖 AppImage),`updater.rs` 的 `Updater` 实体管检查、下载和重启,工作线程的事件由 UI 定时轮询。不依赖 `workspace` 和 `settings`;`new_with_services` 里的 `Updater` 没有服务、从不联网,生产路径才 `set_services` + `start`,UI 测试经 `workspace.updater()` 注入假的。详见 `CLAUDE.md` 和 `README.md` 的「下载与更新」「发布」。
|
||||
- `update/` — 在线升级:`feed.rs` 从 `dl.shellrs.com` 取已签名的清单和安装包(reqwest,走系统代理),`verify.rs` 先验 minisign 签名(trusted comment 绑定通道和版本)再信清单、按大小和 SHA-256 信安装包,`install*.rs` 按安装方式装(macOS 整包交换、Windows 运行 Inno 安装程序、Linux 覆盖 AppImage),`updater.rs` 的 `Updater` 实体管检查、下载和重启,工作线程的事件由 UI 定时轮询。不依赖 `workspace` 和 `settings`;`new_with_services` 里的 `Updater` 没有服务、从不联网,生产路径才 `set_services` + `start`,UI 测试经 `workspace.updater()` 注入假的。详见 `CLAUDE.md`、`docs/manual.md` 的「下载与更新」和 `docs/release.md`。
|
||||
- `shared/` — 多个功能共用的展示片段(`ClosableTabTitle`、`HostMark`)。
|
||||
|
||||
关键流程与不变量:
|
||||
|
||||
@@ -8,7 +8,7 @@ ShellRS(crate 与二进制都叫 `shellrs`)是一个类似 Xshell / WinSCP
|
||||
|
||||
**保存的一项叫「主机」,代码里叫 `Host`。** 左侧列表里保存的一项(地址、端口、用户、认证方式)在界面上叫「主机」,量词用「台」,代码和数据库里一律叫 host(`Host`、`HostId`、`HostStore`、`host/` 模块、`hosts` 表)。它以前叫「会话」/ `Session`,界面、代码和表名都已改掉,新写的文案和标识符不要再用「会话」/ session 指它。填 IP / 域名的那个字段叫「地址」(`Host.address`、`hosts.address`),不叫「主机」(「主机密钥」「首次连接此主机」这些照旧)。SSH 协议里的 session 不在此列:「SSH 会话通道」、russh 的 `client::Session` / `server::Session`、`channel_open_session`、SFTP 会话照旧。
|
||||
|
||||
主机和分组是**真实持久化**的,存在一个 SQLite 文件里(`~/Library/Application Support/shellrs/shellrs.db`,`SHELLRS_DATA_DIR` 可覆盖目录)。本地终端(`portable-pty` + `alacritty_terminal`)和 SSH 远程连接(`russh`)也都是真的。密码与私钥口令存在系统钥匙串里(`keyring`),**数据库里永远不出现秘密**。SFTP 双栏浏览、上传、下载和文件操作(删除、重命名、属性、新建)也已接入真实文件系统,上传和下载都支持断点续传,并按 WinSCP 的方式排队(每个 SFTP 标签一个传输队列);目录同步尚未实现。SSH 端口转发(本地 `-L`、远程 `-R`、动态 `-D`)也是真的,规则和主机存在同一个库里。凭据(密码 / 密钥 / SSH Agent)也存在这个库里(密码和口令照旧只进钥匙串,粘贴或生成的私钥是数据目录 `keys/` 下的文件),主机可以「使用凭据」而不是自己填登录信息。主机还有「连接方式」:直接连接、经过别的主机做 SSH 跳板(可多级)、或经由 HTTP / SOCKS5 代理,终端、SFTP、端口转发、外部 CLI 和测试连接都照它走。详见 `README.md`。
|
||||
主机和分组是**真实持久化**的,存在一个 SQLite 文件里(`~/Library/Application Support/shellrs/shellrs.db`,`SHELLRS_DATA_DIR` 可覆盖目录)。本地终端(`portable-pty` + `alacritty_terminal`)和 SSH 远程连接(`russh`)也都是真的。密码与私钥口令存在系统钥匙串里(`keyring`),**数据库里永远不出现秘密**。SFTP 双栏浏览、上传、下载和文件操作(删除、重命名、属性、新建)也已接入真实文件系统,上传和下载都支持断点续传,并按 WinSCP 的方式排队(每个 SFTP 标签一个传输队列);目录同步尚未实现。SSH 端口转发(本地 `-L`、远程 `-R`、动态 `-D`)也是真的,规则和主机存在同一个库里。凭据(密码 / 密钥 / SSH Agent)也存在这个库里(密码和口令照旧只进钥匙串,粘贴或生成的私钥是数据目录 `keys/` 下的文件),主机可以「使用凭据」而不是自己填登录信息。主机还有「连接方式」:直接连接、经过别的主机做 SSH 跳板(可多级)、或经由 HTTP / SOCKS5 代理,终端、SFTP、端口转发、外部 CLI 和测试连接都照它走。功能的详细说明见 `docs/manual.md`(`README.md` / `README.en.md` 是开源首页,截图在 `docs/screenshots/`)。
|
||||
|
||||
已确认的产品决定:WinSCP 式双栏文件浏览器是「SFTP」Dock 标签页,和终端一样每次「打开 SFTP」都新开一个(同一主机可以开多个,各自独立连接),操作逻辑对标 WinSCP Commander(两行工具栏、路径标签、WinSCP 的列、列表右键菜单、高亮多选;面板顶上不再有「用户@主机:端口」一行;本地侧与远程侧对称,本地删除进废纸篓;书签按主机、按本地 / 远程各一份持久化);传输进行中还能再发起传输,新批次在 SFTP 标签底部的「传输队列」里排队依次执行(WinSCP 的队列面板,不做前台进度对话框);暂不做 Dock 布局持久化、过滤、查找文件和目录树;首次启动是空库,不预置任何分组或主机;分组支持任意层级嵌套,主机也可以不属于任何分组(渲染在树的根层级);删除分组会连同其子分组和里面的主机一起删,删除前确认并写明数量。端口转发不是 Dock 标签页,而是侧栏的另一个列表,由标题栏「ShellRS」后面的三个图标(主机 / 端口转发 / 凭据)切换;一条规则经由一个已保存的主机,启动时自己建一条 SSH 连接,不计入主机的连接状态,主机「断开连接」不停它;删除主机(或含它的分组)连同经由它的规则一起删,确认框写明数量;规则可设「启动 ShellRS 时自动开启」,「创建」只保存不启动;新建规则时「经由主机」留空(不预选当前标签的主机,也不预选第一台),由用户自己选;断线后按 1、3、10 秒自动重连三次,重连不弹任何问答;配置对话框用排成一行的三张类型卡片、实时示意图、一句话说明和一直循环的流向动画说明三种转发,示意图和那句话里的服务器按角色叫「SSH 服务器」,不写所选主机的名称;暂不做转发的外部 CLI 接口、规则排序和监听端口 0。凭据是侧栏的第三个列表(密码 / 密钥 / SSH Agent 三种),**凭据带用户名**:主机「使用凭据」时不再填用户名,改凭据的用户名所有用它的主机一起变;主机对话框的「认证方式」是三选一:「密码」(只有用户名和密码,密码留空就每次连接时询问,不悄悄试密钥)、「使用凭据」和「无密码」(只有用户名:依次试服务器免认证、SSH Agent 和 `~/.ssh` 的默认私钥,服务器要求密码时直接报错、不询问;密钥之后再要验证码照样问);主机自己不再有私钥文件,要用私钥就建密钥凭据;密钥凭据的私钥有三种来源——「本机文件」(只引用路径,不导入内容)、「粘贴」和「生成新密钥」(Ed25519 默认,另有 RSA 4096)——在同一个对话框里(类型选「密钥」后多一排来源单选),不按类型拆对话框,因为编辑时要能改类型、换钥匙,用它的主机一起跟着变;粘贴和生成的私钥由 ShellRS 写成数据目录 `keys/` 下的文件(不进钥匙串:Windows 凭据管理器一条最多 2560 字节,RSA 私钥放不下),凭据引用它的路径,编辑时就显示为「本机文件」;空列表和空白处的右键菜单多一个「生成密钥…」,打开的是同一个对话框、已选好「密钥 · 生成新密钥」,密钥凭据的右键菜单有「复制公钥」;删除仍在用的凭据要确认并写明台数,删除后那些主机保留用户名,密码凭据的改成「密码」(连接时询问),密钥和 Agent 凭据的改成「无密码」(`CredentialKind::without_credential`),密码和私钥不复制过去,已打开的连接不断开;主机对话框里不做「新建凭据…」(设计规范不叠弹层);Windows 上 Agent 走 OpenSSH Agent 的命名管道,「无密码」也试它;暂不做自定义 Agent 套接字、Pageant、凭据的外部 CLI 接口和凭据排序。主机对话框「分组」下面是「连接方式」三选一:「直接连接」(默认)、「SSH 跳板」和「代理连接」。跳板选的是已保存的主机,可多级,**只走这里列出的链**:跳板主机自己的连接方式不参与(所以不会成环),每台用它自己的地址、端口、认证和 known_hosts 条目;跳板列表不做拖动排序(删了重加)。代理是 HTTP(`CONNECT`)或 SOCKS5(域名交给代理解析),用户名和密码可选,密码按「代理地址 + 用户名」进钥匙串(`SecretRef::Proxy`),代理要求认证或拒绝时直接报错、不弹问答。跳板和代理不叠加。删除被别的主机用作跳板的主机:确认框写明台数,那些主机的跳板列表里留下「已删除的主机」占位(库里 `ON DELETE SET NULL`),连接时直接报错,不悄悄改成直连或少跳一级,表单里留着占位就保存会报错;删除不重连已打开的连接。在线升级:主域名 sLine truncated
|
||||
|
||||
@@ -119,7 +119,7 @@ cargo test -- --ignored # 会读写真实钥匙串的测试
|
||||
- 连不上(没在运行,或崩溃留下的套接字文件)才正常启动。两个同时启动、都没连上对方的话,后一个建套接字失败,只弹「外部 CLI 无法启动」。不做多窗口:并排看靠 Dock 分栏;将来要做,共享的数据和服务得先提到应用层。
|
||||
- **macOS 上关窗口是隐藏,不是退出。** ShellRS 只有一个窗口,在跑的东西(连接、传输、端口转发、外部 CLI)都归窗口里的工作区;GPUI 在 macOS 上默认不随最后一个窗口退出(`QuitMode::Default`),真关掉窗口会停掉这一切,却留下一个没有窗口、点 Dock 图标也唤不回的进程。所以 `main` 给窗口挂 `app::hide_when_closed`(`on_window_should_close` 里 `cx.hide()` 并返回 `false`),点 Dock 图标(`Application::on_reopen`)和再次打开 ShellRS(`come_forward_when_opened_again`)都走 `app::bring_forward`:先 `NSApplication::unhide`(只 `activate` 不一定让隐藏的窗口重新出现),再激活窗口。⌘Q 才退出。Windows / Linux 关窗口即退出(GPUI 的默认)。测试平台的 `hide()` 是 `unimplemented!()`,所以这条只挂在生产路径上,没有 UI 测试,改动后要在真实窗口里试。
|
||||
- **在线升级的接入照外部 CLI 的先例。** `new_with_services` 只建一个没有服务的 `Updater`(阶段 `Off`、从不联网),生产路径 `Workspace::new` 再 `set_services(UpdateServices::system(cx.app_path()))` 和 `start()`;UI 测试经 `workspace.updater()` 注入假的 feed、安装器、测试公钥和临时目录。「自动升级」和「更新渠道」存在 `AppSettings::update`(`automatic`、`channel`,后者直接用 `update::Channel`,在 settings 里实现 `Choice`),工作区在 `observe_in(&settings)` 里 `sync_updater`(`set_automatic`、`set_channel`),`update` 模块不依赖 `settings`。动作是 `CheckForUpdates` / `DownloadUpdate` / `ShowUpdate` / `RestartToUpdate` / `OpenDownloadPage`,处理器在 `workspace/updates.rs`;对话框里「重启会中断什么」由工作区数(`restart_impact`:运行中的远程终端、已连接的 SFTP 标签、本地终端、传输、端口转发)。重启走 gpui 的 `cx.set_restart_path` + `cx.restart()`,它会照常跑 `on_app_quit`(SFTP 借此保存续传进度);脚本没能起来时 2 秒后报失败。**Windows 不在退出过程中起进程**:gpui 退出时在 App 仍被借用的状态下跑 `on_app_quit`,而创建进程会泵消息循环、重入 GPUI,所以安装程序的等待进程交给 `update::start_handed_over()`,由 `main` 在 `run()` 返回之后启动(Windows 的事件循环结束后 `run` 会返回)。更新前写 `updates/applied.json`(from、to),新进程据此通知「已更新到」或「更新没有完成」。下载目录是 `app::updates_dir()`(用户缓存目录;设了 `SHELLRS_DATA_DIR` 时在数据目录下)。
|
||||
- **发布走 tag。** 推 `v<版本>`(和 `Cargo.toml` 一致,`CHANGELOG.md` 里要有 `## [版本]` 一节)触发 `.github/workflows/release.yml`:分平台构建打包(`packaging/macos/bundle.sh`、`packaging/windows/shellrs.iss`、`packaging/linux/appimage.sh`),在需要人工批准的 `release` 环境里上传 R2、建 GitHub Release,**最后**才用 `packaging/make-manifest.sh` 生成并上传清单、清 CDN 缓存。清单格式见 `packaging/manifest.example.json`(`the_example_manifest_parses` 守着它和解析器一致,`the_release_script_signs_what_the_client_accepts` 守着脚本和验签一致)。一次性准备和 secrets 见 `README.md` 的「发布」。
|
||||
- **发布走 tag。** 推 `v<版本>`(和 `Cargo.toml` 一致,`CHANGELOG.md` 里要有 `## [版本]` 一节)触发 `.github/workflows/release.yml`:分平台构建打包(`packaging/macos/bundle.sh`、`packaging/windows/shellrs.iss`、`packaging/linux/appimage.sh`),在需要人工批准的 `release` 环境里上传 R2、建 GitHub Release,**最后**才用 `packaging/make-manifest.sh` 生成并上传清单、清 CDN 缓存。清单格式见 `packaging/manifest.example.json`(`the_example_manifest_parses` 守着它和解析器一致,`the_release_script_signs_what_the_client_accepts` 守着脚本和验签一致)。一次性准备和 secrets 见 `docs/release.md`。
|
||||
- **库只往前升,旧版本不碰新库。** `migrate` 遇到 `user_version > SCHEMA_VERSION`(或早于第一步)直接报错、不动文件(`open_store` 退回内存 store 并提示);迁移已有的库之前先 `VACUUM INTO '<库>.v<旧版本>.bak'`(WAL 下不能直接复制文件),备份失败只打日志、照样迁移。
|
||||
- **窗口还在搭的时候不能直接弹通知。** 通知层属于窗口的 `Root`,而 `Root` 要等 `open_window` 的构造闭包返回才挂上;在那之前 `window.push_notification` 会 panic(「window first layer should be a gpui_component::Root」),应用启动即崩溃。`main` 里读库 / 读设置的问题、`Workspace::new` 里外部 CLI 启动失败,都用 `workspace::notify_once_open`(`window.defer`)延后到窗口建好。`cx.emit` 出去的事件本来就在构造结束后才送达,订阅里弹通知没有这个问题(自动开启的转发启动失败就是这样报的)。
|
||||
- **一个命令一个处理器。** 按钮、菜单、右键菜单、快捷键都只派发 `app/actions.rs` 里的动作,由 `Workspace` 处理。新增命令加在那里,不要写临时闭包直接改状态。
|
||||
|
||||
+3
-1
@@ -3,7 +3,9 @@ name = "shellrs"
|
||||
version = "0.1.0-beta.4"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
description = "SSH session manager with local terminal support"
|
||||
description = "SSH client and host manager: terminal, SFTP, port forwarding"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/since2006/shell-rs"
|
||||
default-run = "shellrs"
|
||||
|
||||
# The app. On macOS and Linux it doubles as the `shellrs` command when
|
||||
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 ShellRS contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
<p align="center">
|
||||
<img src="assets/logo/shellrs.svg" width="96" alt="ShellRS">
|
||||
</p>
|
||||
|
||||
<h1 align="center">ShellRS</h1>
|
||||
|
||||
<p align="center">
|
||||
A high-performance, native, cross-platform SSH client written in Rust, with a GPU-rendered interface: Xshell-style host management, WinSCP-style SFTP and port forwarding, all in one window.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/since2006/shell-rs/releases"><img src="https://img.shields.io/github/v/release/since2006/shell-rs?include_prereleases&label=release" alt="Release"></a>
|
||||
<a href="#license"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue" alt="License: MIT OR Apache-2.0"></a>
|
||||
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-lightgrey" alt="Platform: macOS | Windows | Linux">
|
||||
<img src="https://img.shields.io/badge/rust-1.98-orange" alt="Rust 1.98">
|
||||
</p>
|
||||
|
||||
<p align="center"><a href="README.md">简体中文</a> | <b>English</b></p>
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/main-dark.png">
|
||||
<img src="docs/screenshots/main-light.png" alt="ShellRS main window: a grouped host tree on the left, htop running in a remote terminal on the right, connection latency in the tab bar">
|
||||
</picture>
|
||||
|
||||
## About
|
||||
|
||||
ShellRS brings Xshell-style host management and tabbed terminals, a WinSCP-style dual-pane SFTP browser and SSH port forwarding together in one cross-platform app. It is written in Rust, and its UI is built on GPUI, the GPU-accelerated UI framework of the Zed editor (through [gpui-kit](https://gpui-kit.com)). There is no Electron and no JVM.
|
||||
|
||||
Hosts, groups, credentials and forwarding rules live in a local SQLite database. Passwords and key passphrases go only into the system keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service); the database never holds a secret. ShellRS also ships a `shellrs` command that lets AI agents such as Claude Code or Codex run commands and transfer files on your saved hosts without ever seeing a password.
|
||||
|
||||
> [!NOTE]
|
||||
> ShellRS is in beta, and its interface is currently in Simplified Chinese only. An English translation is in progress.
|
||||
|
||||
## Features
|
||||
|
||||
**Hosts**
|
||||
|
||||
- Groups nest to any depth; drag to rearrange, type to search.
|
||||
- The remote OS is detected on connect and shown as a badge in the host tree and on tabs (Linux distributions, the BSDs, macOS, Windows).
|
||||
- Three ways to log in: password; no password (the server's `none` auth, then the SSH agent, then the default keys in `~/.ssh`); or a saved credential.
|
||||
- Three ways to connect: directly, through a chain of SSH jump hosts, or through an HTTP or SOCKS5 proxy. Terminals, SFTP, port forwards and the CLI all follow it.
|
||||
- "Test connection" performs a real login and explains any failure.
|
||||
|
||||
**Terminal**
|
||||
|
||||
- Tabbed remote and local terminals (built on `alacritty_terminal`); full-screen programs like vim and htop work as expected.
|
||||
- Each tab shows the SSH round-trip latency live.
|
||||
- Find (smart case) and local clear; configurable font, size and line height.
|
||||
- Drag tabs into side-by-side or stacked splits to watch a terminal and SFTP together.
|
||||
|
||||
**SFTP**
|
||||
|
||||
- A dual-pane browser modeled on WinSCP Commander: symmetric local and remote panes with WinSCP's columns, path labels, bookmarks and shortcuts (F5, F2, F7, F8…).
|
||||
- Upload and download by dragging or by shortcut, recursively. Start new transfers while one runs; they wait in a per-tab transfer queue.
|
||||
- Resumable transfers: data goes to a `.filepart` first, dropped connections are retried after 1, 3 and 10 seconds, and after a restart ShellRS offers to resume the same transfer.
|
||||
- Delete (local items go to the Trash), rename, create, and change permissions (a 3×3 grid plus octal, optionally recursive).
|
||||
- Each SFTP tab has its own connection and asks only for the SFTP subsystem, so no shell is needed on the server.
|
||||
|
||||
**Port forwarding**
|
||||
|
||||
- Local (`-L`), remote (`-R`) and dynamic (`-D`, SOCKS5 / SOCKS4) forwards.
|
||||
- The rule dialog draws a live diagram and a one-line explanation of where connections enter and where they go.
|
||||
- Every rule keeps its own SSH connection and reconnects three times after a drop; rules can start with ShellRS.
|
||||
|
||||
**Credentials**
|
||||
|
||||
- Password, key and SSH agent credentials that many hosts can share. Change a credential and every host using it follows.
|
||||
- Keys can reference a file on disk, be pasted in, or be generated on the spot (Ed25519 or RSA 4096), with the public key one click away.
|
||||
|
||||
**A CLI for AI agents**
|
||||
|
||||
- `shellrs list` / `exec` / `upload` / `download`. The running ShellRS logs in on the command's behalf, so the command itself never handles a password or private key.
|
||||
- Install `shellrs` into your PATH and an Agent Skill for Claude Code, Codex or OpenCode from the settings page.
|
||||
|
||||
**Security and updates**
|
||||
|
||||
- Secrets stay in the system keychain. Host keys are recorded in ShellRS's own `known_hosts`, never in `~/.ssh`.
|
||||
- In-app updates: the update manifest is minisign-signed and each package is checked against its SHA-256. Updates download in the background and install on restart or quit. There are stable and beta channels, and an update check sends only the version, OS and CPU architecture.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/split.png" alt="A split view: a remote terminal on top and SFTP for the same host below"><br>
|
||||
<em>Dock splits: a remote terminal and SFTP stacked in one window</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/sftp.png" alt="Dual-pane SFTP: three local items selected on the left, remote /etc on the right, and a transfer queue with one batch uploading and one waiting"><br>
|
||||
<em>WinSCP-style SFTP: highlight selection and a transfer queue with progress, speed and time left</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/forward.png" alt="Edit port forward dialog with the diagram and explanation of a local forward, and two running rules in the forward list"><br>
|
||||
<em>Port forwarding: the diagram shows where each connection goes</em>
|
||||
</p>
|
||||
|
||||
## Download
|
||||
|
||||
Get the package for your platform from [Releases](https://github.com/since2006/shell-rs/releases). All releases so far are betas (marked Pre-release).
|
||||
|
||||
| System | Package | Notes |
|
||||
| --- | --- | --- |
|
||||
| macOS 11+ (Apple Silicon and Intel) | `ShellRS-<version>-macos-universal.dmg` | Signed and notarized. Open the DMG and drag ShellRS into Applications |
|
||||
| Windows x64 | `ShellRS-<version>-windows-x86_64-setup.exe` | Installs per user, no administrator rights needed. The installer is not signed yet; if SmartScreen stops it, choose "More info › Run anyway" |
|
||||
| Linux x64 | `ShellRS-<version>-linux-x86_64.AppImage` | `chmod +x` and run. Needs glibc 2.35 or newer (Ubuntu 22.04, Debian 12 and later) |
|
||||
|
||||
Every package comes with a minisign signature (`.minisig`), and each release has a `SHA256SUMS` file.
|
||||
|
||||
Once installed, ShellRS keeps itself up to date: it checks, downloads and verifies new versions in the background, then shows an icon in the top-right corner of the title bar; click it and choose "重启并安装" (restart and install), or just quit and the update is installed on the way out. Automatic updates are unavailable when ShellRS runs from inside the DMG or outside the Applications folder, or on Linux when it isn't run as the AppImage.
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Click "新建主机…" (New host) in the title bar (⌘N, or Ctrl+N on Windows and Linux) and fill in the address, port and login. Passwords go into the system keychain.
|
||||
2. Double-click a host to open a terminal. On the first connection you're asked to confirm the host key.
|
||||
3. Right-click a host and choose "打开 SFTP" (Open SFTP), or click "SFTP" at the right of the terminal's tab bar. Drag files between the two panes to transfer them.
|
||||
4. The three icons after "ShellRS" in the title bar switch the sidebar between hosts, port forwards and credentials.
|
||||
5. ⌘T (Ctrl+T) opens a local terminal. Drag a tab to the top, bottom, left or right edge of the tab area to split it.
|
||||
|
||||
## For AI agents
|
||||
|
||||
In 设置 › 外部 CLI (Settings › External CLI), turn on 启用外部 CLI (Enable external CLI), then install the `shellrs` command and the Agent Skills. Your agent can then use the hosts you have saved:
|
||||
|
||||
```sh
|
||||
shellrs list # list hosts and their IDs
|
||||
shellrs exec <ID> "uptime && df -h" # run a command; the exit code is the remote one
|
||||
shellrs upload <ID> ./dist/app.tar.gz /opt/app/ # upload
|
||||
shellrs download <ID> /var/log/syslog ./logs/ # download
|
||||
```
|
||||
|
||||
The command hands each request to the running ShellRS, which logs in with the saved settings and keychain. An unknown host or a missing password fails right away with a stable error code instead of a prompt that would hang the agent. See the [user manual](docs/manual.md#外部-cli) (in Chinese) for details.
|
||||
|
||||
## Building from source
|
||||
|
||||
Install [rustup](https://rustup.rs). The repository's `rust-toolchain.toml` pins Rust 1.98.1, which rustup installs on the first build.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/since2006/shell-rs.git
|
||||
cd shell-rs
|
||||
cargo run
|
||||
```
|
||||
|
||||
The first build compiles the whole GPUI stack and takes a while. See [development notes](docs/development.md) (in Chinese) for the Linux system packages and how to run the tests.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation is in Chinese for now:
|
||||
|
||||
- [User manual](docs/manual.md): every feature in detail, with shortcuts
|
||||
- [Development](docs/development.md): building, testing, the data directory
|
||||
- [Releasing](docs/release.md): packaging, signing and publishing updates
|
||||
- [Changelog](CHANGELOG.md)
|
||||
|
||||
## Roadmap
|
||||
|
||||
Not implemented yet:
|
||||
|
||||
- An English interface (today only the text built into GPUI Kit's components switches language)
|
||||
- Directory sync, filtering, file search and a directory tree in SFTP
|
||||
- Keeping the Dock layout across restarts
|
||||
- Combining a jump host chain with a proxy, and HTTPS proxies
|
||||
- Managing port forwards and credentials through the external CLI
|
||||
- Code signing for the Windows installer
|
||||
|
||||
## Contributing
|
||||
|
||||
Issues and pull requests are welcome on [GitHub](https://github.com/since2006/shell-rs/issues). Architecture notes and code conventions are in [CLAUDE.md](CLAUDE.md) (in Chinese). Before sending a change, make sure these pass:
|
||||
|
||||
```sh
|
||||
cargo fmt --check
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
- [GPUI](https://github.com/zed-industries/zed) (Zed) and [gpui-kit](https://gpui-kit.com): the UI framework
|
||||
- [russh](https://github.com/Eugeny/russh) and [russh-sftp](https://github.com/AspectUnk/russh-sftp): SSH and SFTP
|
||||
- [alacritty_terminal](https://crates.io/crates/alacritty_terminal) and [portable-pty](https://crates.io/crates/portable-pty): terminal emulation and local PTYs
|
||||
- [keyring](https://crates.io/crates/keyring): the system keychain
|
||||
- [Simple Icons](https://simpleicons.org): the OS logos on host badges (CC0)
|
||||
- [WinSCP](https://winscp.net) and Xshell: the interaction designs ShellRS follows
|
||||
|
||||
## License
|
||||
|
||||
ShellRS is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
|
||||
@@ -1,332 +1,183 @@
|
||||
# ShellRS
|
||||
<p align="center">
|
||||
<img src="assets/logo/shellrs.svg" width="96" alt="ShellRS">
|
||||
</p>
|
||||
|
||||
基于 GPUI Kit 的中文 SSH 主机管理工具,提供持久化的主机与分组、可在多台主机间共用的登录凭据、本地终端、SSH 多终端、对标 WinSCP 的 SFTP 双栏浏览器和 SSH 端口转发。
|
||||
<h1 align="center">ShellRS</h1>
|
||||
|
||||
标题栏「ShellRS」后面的三个图标切换左侧栏显示的内容:主机列表、端口转发列表或凭据列表。侧栏隐藏时点其中一个会把它重新展开。
|
||||
<p align="center">
|
||||
用 Rust 编写、由 GPU 渲染界面的高性能原生跨平台 SSH 客户端:Xshell 式的主机管理、WinSCP 式 SFTP 和端口转发,都在一个窗口里。
|
||||
</p>
|
||||
|
||||
ShellRS 同一时间只运行一个:已经开着时再打开一次,会把已有的窗口调到前面,新启动的那个随即退出。两个 ShellRS 各自把主机读进内存再写回同一个数据库,会互相覆盖对方的改动。(用 `SHELLRS_DATA_DIR` 指到不同数据目录的算不同的 ShellRS,可以同时运行。)要并排看两个终端或 SFTP,把标签拖成分栏即可。
|
||||
<p align="center">
|
||||
<a href="https://github.com/since2006/shell-rs/releases"><img src="https://img.shields.io/github/v/release/since2006/shell-rs?include_prereleases&label=release" alt="Release"></a>
|
||||
<a href="#许可证"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue" alt="License: MIT OR Apache-2.0"></a>
|
||||
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-lightgrey" alt="Platform: macOS | Windows | Linux">
|
||||
<img src="https://img.shields.io/badge/rust-1.98-orange" alt="Rust 1.98">
|
||||
</p>
|
||||
|
||||
在 macOS 上,点窗口左上角的关闭按钮只是把 ShellRS 隐藏到后台:已打开的连接、SFTP 传输、端口转发和外部 CLI 照常运行,点 Dock 图标恢复窗口,原来的标签都在。要真正退出用 ⌘Q,或者右键 Dock 图标选「退出」。Windows 和 Linux 上关闭窗口就是退出。
|
||||
<p align="center"><b>简体中文</b> | <a href="README.en.md">English</a></p>
|
||||
|
||||
## 主机列表
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/main-dark.png">
|
||||
<img src="docs/screenshots/main-light.png" alt="ShellRS 主界面:左侧是分组的主机树,右侧远程终端里运行着 htop,标签栏显示连接延迟">
|
||||
</picture>
|
||||
|
||||
左侧栏里保存的每一项是一台主机:地址、端口和认证方式。同一台机器用不同用户登录,就保存成两台主机。
|
||||
## 简介
|
||||
|
||||
主机对话框的「认证方式」有三种:
|
||||
ShellRS 把 Xshell 式的主机管理和多标签终端、WinSCP 式的双栏 SFTP 文件管理、SSH 端口转发放进同一个跨平台应用。它用 Rust 编写,界面基于 Zed 编辑器的 GPU 加速 UI 框架 GPUI(通过 [gpui-kit](https://gpui-kit.com)),不依赖 Electron 或 JVM。
|
||||
|
||||
- **密码**:填用户名和密码,密码保存在系统钥匙串;密码留空则每次连接时询问。只用密码登录,不会先拿 SSH Agent 或私钥去试。
|
||||
- **使用凭据**:选一条保存好的凭据,用户名和登录方式都来自凭据(见下面「凭据」),对话框里不再填用户名和密码。「测试连接」同样使用凭据保存的密码。要用私钥登录,就建一条密钥凭据。
|
||||
- **无密码**:只填用户名。依次尝试服务器免认证、SSH Agent 和 `~/.ssh` 下的默认私钥(`id_ed25519`、`id_ecdsa`、`id_rsa`),和命令行 `ssh` 默认的做法一样。服务器要求密码时连接失败并说明原因,不会询问;服务器在密钥之后再要验证码时照样询问。
|
||||
主机、分组、凭据和转发规则保存在本地的 SQLite 数据库里。密码和私钥口令只存进系统钥匙串(macOS 钥匙串、Windows 凭据管理器、Linux Secret Service),数据库里没有任何秘密。ShellRS 还带一个 `shellrs` 命令,让 Claude Code、Codex 等 AI Agent 用你保存的主机执行命令、传输文件,不用把密码交给它们。
|
||||
|
||||
无论选哪种,连接时都先问服务器是否允许免认证登录,允许就直接登录。
|
||||
> [!NOTE]
|
||||
> 目前是 Beta 版本,界面只有简体中文。
|
||||
|
||||
主机对话框「分组」下面的「连接方式」决定怎么连到这台主机,终端、SFTP、端口转发、外部 CLI 和「测试连接」都照它走:
|
||||
## 功能
|
||||
|
||||
- **直接连接**:默认,本机直接连主机的地址和端口。
|
||||
- **SSH 跳板**:经过别的已保存主机连过去,支持多级。框里一排标签写出整条路(「本机 → 阿里云99 → 禅道 → 当前主机」),下面每台跳板一行,「×」移除,「添加跳板主机」可按名称或地址搜索;要调顺序就移除后重新添加。连接时先登录第 1 台,由它连第 2 台,依此类推,最后一台连到主机。每台跳板都用它自己的地址、端口、认证方式(密码、凭据或无密码)和主机信任记录,首次连接各问一次主机密钥,询问框里写明是哪台跳板主机;跳板主机自己的「连接方式」在这里不生效,要多级就在这里按顺序列出。主机的地址按跳板主机那边的网络来填,例如内网地址。
|
||||
- **代理连接**:经由 HTTP 代理(`CONNECT`)或 SOCKS5 代理连接,在下拉框里选。填代理的地址和端口(例如本机的 `127.0.0.1:7890`);代理需要认证时再填用户名和密码,密码保存在系统钥匙串,经由同一个代理、同一个用户名的主机共用。主机的地址交给代理去解析。代理要求认证却没有密码、或者拒绝了用户名和密码时,连接失败并说明原因,不会询问。
|
||||
**主机管理**
|
||||
|
||||
改了一台主机的地址或认证方式,经由它跳转的主机也会重新连接(只改名称不会)。删除一台被别的主机用作跳板的主机,确认框写明有几台主机在用它;删除后那些主机的跳板列表里留下一行「已删除的主机」,连接时直接报错,不会悄悄改成直连或少跳一级,编辑时移除或换一台即可。已经打开的连接不受影响。
|
||||
- 分组可以任意层级嵌套,支持拖动整理和搜索。
|
||||
- 连上主机后自动识别远程系统,在主机树和标签上显示对应的系统徽章(各 Linux 发行版、BSD、macOS、Windows)。
|
||||
- 认证方式三选一:密码、无密码(依次尝试服务器免认证、SSH Agent 和 `~/.ssh` 下的默认私钥)、使用凭据。
|
||||
- 连接方式三选一:直接连接、多级 SSH 跳板、HTTP / SOCKS5 代理。终端、SFTP、端口转发和外部 CLI 都按它连接。
|
||||
- 「测试连接」会真实登录一次,失败时写明原因。
|
||||
|
||||
**尚未实现:跳板和代理叠加使用、拖动调整跳板顺序、HTTPS 代理、在主机列表里标出连接方式。**
|
||||
**终端**
|
||||
|
||||
对话框最后是「备注」,可以写多行(回车换行),记下机器的用途、负责人、到期时间之类,只保存在本地数据库里,不影响连接。
|
||||
- 多标签的远程终端和本地终端(基于 `alacritty_terminal`),vim、htop 等全屏程序正常显示。
|
||||
- 标签栏实时显示这条 SSH 连接的往返延迟。
|
||||
- 支持查找(智能大小写)和本地清屏,字体、字号、行高都可以设置。
|
||||
- 标签可以拖成左右或上下分栏,终端和 SFTP 并排看。
|
||||
|
||||
鼠标停在主机上,行的右侧会显示 `用户@地址:端口`,主机有备注时下面接着显示备注(太长的只显示前 6 行)。右键主机可连接、打开 SFTP、编辑、复制、复制 ID 或删除。「复制 ID」复制的是主机的 16 位随机标识(形如 `Jwg5rHvXCxw89paM`),供其他工具标识这台主机:它在主机创建时生成,改名、改地址都不变,复制出来的主机得到新的 ID,删掉的 ID 不会再分配。没有打开任何标签时,中间区显示「最近连接」列表,双击或回车连接;右键其中的主机弹出同一个菜单,并选中该主机。
|
||||
**SFTP**
|
||||
|
||||
拖动分组或主机到同类项目的上侧 / 下侧可调整同级顺序;主机拖到分组会移入该分组,分组拖到另一个分组的中部会成为子分组。拖到列表空白处可移回根层级。分组始终排在同级主机之前,顺序与分组的展开状态会保存到本地数据库。搜索时暂停拖动排序。
|
||||
- 对标 WinSCP Commander 的双栏文件浏览器:本地和远程两侧对称,WinSCP 的列、路径标签、书签和快捷键(F5、F2、F7、F8……)。
|
||||
- 拖动或快捷键上传、下载,支持递归。传输进行中还能继续发起,新批次进传输队列依次执行。
|
||||
- 断点续传:先写 `.filepart`,断线后按 1、3、10 秒自动重连;重启后再传同样的文件,会询问是否续传。
|
||||
- 删除(本地移到废纸篓)、重命名、新建、修改权限(3×3 复选框加八进制,可递归)。
|
||||
- 每个 SFTP 标签单独建立连接,只请求 SFTP 子系统,不要求服务器提供 shell。
|
||||
|
||||
## 端口转发
|
||||
**端口转发**
|
||||
|
||||
点标题栏的端口转发图标,左侧栏换成转发列表。每条转发规则经由一台已保存的主机,启动时为自己建立一条 SSH 连接,不依赖该主机的终端或 SFTP 标签:关掉标签、对主机「断开连接」都不影响它,主机的连接状态也不把它算在内。有转发在运行时,标题栏的端口转发图标旁显示运行数量。
|
||||
- 本地(`-L`)、远程(`-R`)和动态(`-D`,SOCKS5 / SOCKS4)转发。
|
||||
- 配置对话框里有实时示意图和一句话说明,看得出连接从哪进、到哪去。
|
||||
- 每条规则自己建立 SSH 连接,断线后自动重连三次;可以设为随 ShellRS 启动自动开启。
|
||||
|
||||
三种转发,都按「入口 → 出口 → 目标」理解,连接从入口进、经 SSH 隧道到出口、再由出口去连目标:
|
||||
**凭据**
|
||||
|
||||
| 类型 | 入口(监听) | 出口 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| 本地转发(`ssh -L`) | 本机 | 服务器 | 在本机访问服务器那边的服务,例如用本机的客户端连只对服务器开放的数据库 |
|
||||
| 远程转发(`ssh -R`) | 服务器 | 本机 | 让服务器那边访问本机的服务,例如把本机开发中的站点给对方看、接收 Webhook |
|
||||
| 动态转发(`ssh -D`) | 本机(SOCKS 代理) | 服务器 | 把服务器当代理,目标由应用自己指定,例如让浏览器经由服务器上网 |
|
||||
- 密码、密钥、SSH Agent 三种凭据,多台主机可以共用一条;改凭据,用它的主机一起变。
|
||||
- 密钥可以引用本机文件、直接粘贴,或当场生成 Ed25519 / RSA 4096,并复制公钥。
|
||||
|
||||
- **新建和编辑**:列表标题栏的「+」打开对话框。上方一行三张卡片选类型,中间的示意图随表单实时填入地址、标出入口在哪台机器上,下面一句话说明这条规则的效果(如「在本机连接 127.0.0.1:8080,就等于从 SSH 服务器连接 db.internal:3306。」)。示意图和这句话里的「SSH 服务器」就是「经由主机」选的那一台,图里按它扮演的角色称呼,不写主机的名称。示意图上有一个圆点沿连接方向缓缓移动:先过 SSH 隧道,再到目标,停一下,周而复始;切换类型时从新的入口重新出发。名称可以不填,列表里显示转发摘要(`8080 → db.internal:3306`、`9000 ← localhost:3000`、`SOCKS 1080`)。
|
||||
- **启动和停止**:每行末尾的按钮,或双击该行、选中后回车;右键菜单里还有编辑和删除。「创建」只保存规则,不会自动启动。勾选「启动 ShellRS 时自动开启」的规则随应用启动。
|
||||
- **状态**:行尾显示正在连接、运行中(及当前连接数)、正在重连或已停止的原因。某个连接转发不了(例如服务器连不上目标)只在该行提示,转发本身继续运行。
|
||||
- **登录**:使用主机的配置、钥匙串里保存的密码和 ShellRS 自己的主机信任记录。首次连接的主机、需要输入的密码会弹出和终端一样的询问框,并注明是哪条转发在连接;取消询问等于停止这条转发。
|
||||
- **断线重连**:连接中断后按 1、3、10 秒自动重连三次,重连期间本机监听端口保留,新进来的连接会被直接关闭。自动重连不弹任何询问:需要重新输入密码、确认主机密钥时停止并写明原因,手动启动即可。三次都失败也会停止,并弹出通知。
|
||||
- **修改后**:编辑运行中规则的类型、主机、监听或目标,或编辑它所经由的主机的地址、认证方式和连接方式(包括这台主机所用凭据的用户名、类型或私钥文件,以及它的跳板主机),这条转发会按新设置重新启动;只改名称或自动开启不会。
|
||||
- **删除主机**:删除主机(或包含它的分组)会连同经由它的转发规则一起删除,运行中的先停止,确认框里写明数量。
|
||||
- **动态转发**支持 SOCKS5(无认证,`CONNECT`,IPv4、域名、IPv6)和 SOCKS4 / 4a。域名交给服务器解析。
|
||||
- **监听地址**默认 `127.0.0.1`。填其他地址时,本地和动态转发会被同一网络中的其他设备使用;远程转发需要服务器的 `sshd_config` 设置 `GatewayPorts clientspecified` 或 `yes`,其他机器才连得上;默认是 `no`,这时转发照常运行、不报错,但只监听服务器自己的 `127.0.0.1`(和手写 `ssh -R` 一样)。对话框在填了非回环地址时会提示这一点。1024 以下的端口需要管理员(服务器上是 root)权限。不支持端口 0(由系统挑选端口)。
|
||||
**给 AI Agent 用的 CLI**
|
||||
|
||||
**尚未实现:通过外部 CLI 管理转发、规则排序。系统的「减少动态效果」设置目前不会关掉示意图的动画。**
|
||||
- `shellrs list` / `exec` / `upload` / `download`,由正在运行的 ShellRS 代为登录,命令本身不接触密码和私钥。
|
||||
- 在设置页一键把 `shellrs` 放进 PATH,并为 Claude Code、Codex、OpenCode 安装 Agent Skill。
|
||||
|
||||
## 凭据
|
||||
**安全与更新**
|
||||
|
||||
点标题栏的凭据(钥匙)图标,左侧栏换成凭据列表。一条凭据是一套可以让多台主机共用的登录信息,包括名称、用户名和登录方式:
|
||||
- 秘密只进系统钥匙串;主机信任记录存在 ShellRS 自己的 `known_hosts` 里,不改动 `~/.ssh`。
|
||||
- 在线升级:更新清单经 minisign 签名,安装包按 SHA-256 校验;后台下载,重启或退出时安装。分稳定版和 Beta 两个渠道,检查更新只发送版本号、系统和 CPU 架构。
|
||||
|
||||
| 类型 | 保存什么 | 登录时 |
|
||||
## 截图
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/split.png" alt="Dock 分栏:上面是远程终端,下面是同一台主机的 SFTP"><br>
|
||||
<em>Dock 分栏:远程终端和 SFTP 上下并排</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/sftp.png" alt="双栏 SFTP:左侧本地目录多选了三项,右侧是远程 /etc,下方传输队列里一批正在上传、一批等待中"><br>
|
||||
<em>WinSCP 式双栏 SFTP:高亮多选,传输队列显示进度、速度和剩余时间</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/forward.png" alt="编辑端口转发对话框:本地转发的示意图和说明,左侧转发列表里两条规则正在运行"><br>
|
||||
<em>端口转发:示意图实时说明连接怎么走</em>
|
||||
</p>
|
||||
|
||||
## 下载
|
||||
|
||||
到 [Releases](https://github.com/since2006/shell-rs/releases) 下载对应平台的安装包。目前发布的都是 Beta 版(在 Releases 里标为 Pre-release)。
|
||||
|
||||
| 系统 | 安装包 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 密码 | 用户名和密码 | 用凭据保存的密码;没存或被拒绝时询问 |
|
||||
| 密钥 | 用户名、私钥和口令 | 只用这把私钥;口令按私钥文件保存,用同一个文件的凭据共用 |
|
||||
| SSH Agent | 用户名 | 只用 SSH Agent 中的密钥:macOS / Linux 上是 `SSH_AUTH_SOCK` 指向的 Agent,Windows 上是 OpenSSH Authentication Agent 服务 |
|
||||
| macOS 11+(Apple Silicon 和 Intel) | `ShellRS-<版本>-macos-universal.dmg` | 已签名并经过 Apple 公证。打开 DMG,把 ShellRS 拖进「应用程序」 |
|
||||
| Windows x64 | `ShellRS-<版本>-windows-x86_64-setup.exe` | 按当前用户安装,不需要管理员权限。安装程序暂未签名,SmartScreen 拦下时点「更多信息 › 仍要运行」 |
|
||||
| Linux x64 | `ShellRS-<版本>-linux-x86_64.AppImage` | `chmod +x` 后直接运行,需要 glibc 2.35 以上(Ubuntu 22.04、Debian 12 及更新的版本) |
|
||||
|
||||
- **新建和编辑**:列表标题栏的「+」、空列表里的「新建凭据…」或空白处的右键菜单打开对话框;双击凭据、选中后回车或右键「编辑凭据…」编辑。密码和私钥口令存进系统钥匙串,数据库里只有名称、用户名、类型和私钥路径。
|
||||
- **密钥从哪里来**:密钥凭据的「私钥」有三种来源。
|
||||
- **本机文件**:选一个私钥文件,只保存它的路径;文件移动或删除后需要重新选择。
|
||||
- **粘贴**:贴进 OpenSSH、PEM 或 PuTTY 格式的私钥。贴成公钥或认不出的内容时会说明原因。私钥加了口令时才显示口令栏。
|
||||
- **生成新密钥**:选 Ed25519(默认)或 RSA 4096,选中就生成;对话框里显示公钥,旁边可以复制。口令可留空;填了就用它加密私钥,口令存进钥匙串。生成的密钥以凭据名称作为注释。
|
||||
- 粘贴和生成的私钥由 ShellRS 写成数据目录下 `keys/` 里的文件(随机文件名,只有自己的账户能读),凭据引用这个文件,编辑时显示为「本机文件」和它的路径。之后再粘贴或生成会直接覆盖这个文件,已连接的主机不会因此重连;改成别的文件或别的类型、或删除凭据时,这个文件连同它的口令一起删除,对话框会先提示。用户自己的私钥文件 ShellRS 从不改动或删除。
|
||||
- 空列表和空白处的右键菜单里另有「生成密钥…」,打开的是同一个对话框,已经选好「密钥 · 生成新密钥」。密钥凭据的右键菜单有「复制公钥」,用来把公钥加到服务器的 `~/.ssh/authorized_keys`。
|
||||
- **主机跟着凭据变**:改凭据的用户名,所有使用它的主机的用户名一起变。改用户名、类型或私钥文件时,使用它的已连接主机会重新连接(只改名称不会);对话框里写明有几台主机在用。
|
||||
- **删除**:右键「删除」。有主机在用时确认框写明数量和它们之后怎么登录:用密码凭据的主机改为「密码」,连接时询问;用密钥或 SSH Agent 凭据的主机改为「无密码」。用户名不变,密码和私钥不会复制过去。已经打开的连接不受影响,凭据的密码随凭据一起从钥匙串删除。
|
||||
- **SSH Agent 凭据**连不上 Agent、Agent 里没有密钥或服务器不接受其中任何一把时,会直接说明原因,不会改为询问密码。「无密码」的主机也会尝试同一个 Agent,连不上时静默跳过,接着试默认私钥(Windows 上同样如此)。
|
||||
每个安装包都附带 minisign 签名(`.minisig`),另有一份 `SHA256SUMS`。
|
||||
|
||||
**尚未实现:在主机对话框里直接新建凭据、通过外部 CLI 管理凭据、凭据排序、自定义 SSH Agent 的套接字、把公钥自动装到主机上、导出 ShellRS 保存的私钥。**
|
||||
装好之后不用再手动下载:ShellRS 会在后台检查、下载并校验新版本,标题栏右上角出现提示后点「重启并安装」;不点的话,退出时也会自动装好。直接在 DMG 里运行、没放进「应用程序」文件夹,或者在 Linux 上不是用 AppImage 运行时,无法自动更新。
|
||||
|
||||
## 设置
|
||||
## 快速上手
|
||||
|
||||
侧栏最下方的「设置」按钮(或 ⌘,,其他平台 Ctrl+,)在中间区打开「设置」标签页;已经打开时切换到该标签,不会重复打开。设置页分两栏:左边是分类(外观、终端、外部 CLI、关于),上方可搜索,栏宽可拖动;右边是所选分类的设置。设置改动立即生效并保存在数据目录的 `settings.json` 里。
|
||||
1. 点标题栏的「新建主机…」(⌘N,Windows / Linux 上 Ctrl+N),填写地址、端口和认证方式,密码会存进系统钥匙串。
|
||||
2. 双击主机打开终端。第一次连接时会请你确认主机密钥。
|
||||
3. 右键主机选「打开 SFTP」,或点终端标签栏右侧的「SFTP」,在两栏之间拖动文件即可上传、下载。
|
||||
4. 标题栏「ShellRS」后面的三个图标切换侧栏:主机、端口转发、凭据。
|
||||
5. ⌘T(Ctrl+T)打开本地终端。把标签拖到中间区域的上、下、左、右边缘,就能分栏。
|
||||
|
||||
- **外观 → 常规 → 界面语言**:跟随系统、简体中文、English,默认简体中文。英文界面尚在翻译中,目前只有 GPUI Kit 组件自带的文字(如搜索框提示)会切换。
|
||||
- **外观 → 常规 → 应用外观**:深色、浅色、跟随系统,默认跟随系统;选「跟随系统」时随系统的深浅色切换。标题栏的主题按钮也会改写这一项。
|
||||
- **终端 → 字体配置**:字体(只列出本机安装的等宽字体,默认是系统的等宽字体,macOS 上为 Menlo)、字号(8–32 像素,默认 13)、行高(字号的 1.0–2.0 倍,默认 1.54,即原来的 20 像素行高)。改动立即作用于所有已打开的终端。下方的预览按终端的排法显示字母、数字、易混字符、符号、中文、制表符和一行命令。设置文件里的字体在本机没有安装时,使用默认字体。
|
||||
- **外部 CLI**:见下一节。
|
||||
- **关于**:「应用更新」一组:当前版本和检查更新、更新渠道、自动升级,见「下载与更新」。
|
||||
## 给 AI Agent 用
|
||||
|
||||
## 外部 CLI
|
||||
|
||||
`shellrs` 命令让 Codex、Claude Code、OpenCode 等 AI Agent 在 ShellRS 里已保存的主机上执行远程命令、传输文件。命令本身不接触密码和私钥,也不读数据库:它把请求交给正在运行的 ShellRS,由 ShellRS 用保存的配置和钥匙串去连接。请求走本机的进程间通信:macOS、Linux 上是数据目录下的套接字 `cli.sock`,Windows 上是命名管道 `\\.\pipe\shellrs-cli-<数据目录的散列>`。
|
||||
|
||||
在 设置 → 外部 CLI 中:
|
||||
|
||||
- **启用外部 CLI**:默认关闭。关闭时 ShellRS 仍在监听,但拒绝所有请求,命令会提示去这里打开。开启后,本机当前用户下的任何程序都能这样使用已保存的主机。套接字只对当前用户可读写,连接时还会核对对方的用户身份;Windows 的管道同样只允许当前用户、只接受本机连接,命令在发请求之前还会确认管道属于当前用户(或管理员),防止别人抢注同名管道。
|
||||
- **CLI 二进制**:把 `shellrs` 放进 PATH。
|
||||
- macOS:链接到 `/usr/local/bin/shellrs`,目录不可写时弹系统授权框。
|
||||
- Linux:链接到 `~/.local/bin/shellrs`。
|
||||
- macOS、Linux 上「移除」只删除指向 shellrs 的链接,同名的其他文件不会被改动。
|
||||
- Windows:图形程序没有控制台,命令是单独的控制台程序 `shellrs-cli.exe`(与 `shellrs.exe` 放在一起)。「安装」把它复制成 `%LOCALAPPDATA%\ShellRS\bin\shellrs.exe`,并把这个文件夹加入当前用户的 PATH(不需要管理员),重新打开的终端和 Agent 才能找到它。ShellRS 升级后启动时会自动更新这份副本;更新不了时显示「更新」。副本正被使用时也能更新。「移除」删掉副本并把文件夹移出 PATH。
|
||||
- **Agent Skills**:把教 Agent 使用 `shellrs` 的 skill 写到通用目录(`~/.agents/skills`)或 Codex、Claude Code、OpenCode 各自的 skills 目录;内容和当前版本不同时显示「更新」。「复制 skills」把 skill 全文复制到剪贴板。
|
||||
|
||||
命令(`shellrs --help` 查看完整说明):
|
||||
在「设置 › 外部 CLI」里打开「启用外部 CLI」,安装 `shellrs` 命令和 Agent Skills。之后 Agent 就能直接用你保存的主机:
|
||||
|
||||
```sh
|
||||
shellrs list [--query <关键词>] [--json] # 列出主机;输出不是终端时自动用 JSON
|
||||
shellrs exec <ID> "<命令>" # 或 --stdin 从标准输入读命令
|
||||
shellrs upload <ID> <本地路径> <远程路径>
|
||||
shellrs download <ID> <远程路径> <本地路径>
|
||||
shellrs list # 列出主机和它们的 ID
|
||||
shellrs exec <ID> "uptime && df -h" # 执行命令,退出码就是远程命令的退出码
|
||||
shellrs upload <ID> ./dist/app.tar.gz /opt/app/ # 上传
|
||||
shellrs download <ID> /var/log/syslog ./logs/ # 下载
|
||||
```
|
||||
|
||||
- `<ID>` 是 16 位的主机 ID,即 `shellrs list` 的 `id`,也就是主机右键「复制 ID」复制的内容。
|
||||
- `exec` 每次临时建立连接,执行完即断开。远程命令没有标准输入,退出码就是远程命令的退出码;被信号终止时是 128 加信号编号。
|
||||
- 上传和下载沿用 SFTP 标签的传输引擎(递归、`.filepart`、断线重连),目标路径按 scp 的规则:目标是已存在的目录就放进去并保留原名,否则目标就是副本自己的路径,其父目录必须存在。已存在的文件直接覆盖;新建的文件保留源文件的可执行权限。`~` 表示登录目录。
|
||||
- 失败时在标准错误输出 `shellrs: [错误码] 说明`,退出码 255。错误码有 `not_running`(ShellRS 未运行)、`not_enabled`(未启用外部 CLI)、`host_not_found`、`host_key_unknown`(还没在 ShellRS 里连过这台主机)、`host_key_changed`、`missing_credential`(没有保存密码或口令)、`connect_failed`、`transfer_failed`、`bad_request`、`version_mismatch`。CLI 不弹任何询问:陌生主机、缺少密码都直接失败,请先在 ShellRS 里连接一次。传输完成但有项目失败时退出码为 1。
|
||||
- 支持 macOS、Linux 和 Windows。Windows 的 PowerShell 里,多行或带引号的命令用 here-string 经 `--stdin` 传入(`@'…'@ | shellrs exec <ID> --stdin`);输出有中文时先执行 `$OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::new()`。命令文本会去掉开头的 BOM、把 CRLF 换成 LF,远程 shell 不会看到多余的回车符。
|
||||
命令把请求交给正在运行的 ShellRS,由它用保存的配置和钥匙串登录。遇到陌生主机或缺少密码时,命令直接失败、返回稳定的错误码,不会弹框卡住 Agent。详见[使用手册](docs/manual.md#外部-cli)。
|
||||
|
||||
## 标签页
|
||||
## 从源码构建
|
||||
|
||||
远程终端标签和 SFTP 标签都以主机系统徽章为图标(与主机列表相同)。右键标签可重命名(仅本次运行有效,留空恢复为主机的名称,用于区分同一台主机的多个连接)、在新标签页中连接、打开 SFTP、复制 IP 地址 / 主机名、重新连接、编辑主机,以及关闭当前、左侧、右侧、其他或全部标签。SFTP 标签的菜单是「重命名标签…」(留空恢复为「主机的名称 · SFTP」)、打开 SFTP、复制 IP 地址 / 主机名、重新连接加关闭命令,标签栏右侧和远程终端一样有「SFTP」和「重新连接」按钮;本地终端标签是「重新启动」加关闭命令。标签栏右侧「…」按钮的菜单与当前标签的右键菜单相同。双击任一标签的标题可隐藏或显示左侧的侧栏(同 ⌘B 和标题栏的按钮)。远程终端运行时和 SFTP 标签已连接时,标签栏右侧显示这条 SSH 连接的往返延迟,每 5 秒更新一次:低于 100 ms 为绿色,100–200 ms 为黄色,高于 200 ms 或超时为红色。SFTP 传输进行中也照常测量,读数会随传输占满带宽而升高。批量关闭时,正在上传的 SFTP 标签仍会先确认。
|
||||
|
||||
## 终端
|
||||
|
||||
右键终端内容区可复制、粘贴、查找、清屏;远程终端另有打开 SFTP、重新连接和断开连接。断开连接只断开这一个标签,标签保留,可随时重新连接,同一台主机的其他标签和 SFTP 不受影响。
|
||||
|
||||
- **查找**(⌘F,其他平台 Ctrl+Shift+F):在终端右上角打开查找条,按字面量搜索屏幕和回滚历史。查询全为小写时不区分大小写,含大写字母时区分。所有匹配高亮显示,当前项颜色更深,查找条显示「当前项/总数」。回车或 ⌘G(F3)跳到下一项(更新的输出),Shift+回车或 ⌘⇧G(Shift+F3)跳到上一项(更早的输出),到头后循环。Esc 关闭。打开时如有选中文字,会作为查询词。
|
||||
- **清屏**(⌘K,其他平台 Ctrl+Shift+K;焦点不在终端时 ⌘K 仍是聚焦侧栏的搜索框):清掉屏幕和回滚历史,只留下光标所在的提示符行,包括已输入但未回车的内容。清屏只在本地进行,不向服务器发送任何内容。vim、top 等全屏程序运行时不可用。
|
||||
|
||||
## SFTP
|
||||
|
||||
打开主机的「SFTP」标签即可浏览本地与远程目录;和终端一样,每次「打开 SFTP」都新开一个标签,同一台主机可以开多个,各自独立连接、各看各的目录。操作方式对标 WinSCP Commander。SFTP 使用独立 SSH 连接,仅请求 SFTP 子系统,不要求服务器提供 Shell;关闭终端不会中止传输。
|
||||
|
||||
### 工具栏
|
||||
|
||||
每一侧面板都有两行工具栏,下方是路径标签。面板太窄放不下时,按钮从右往左整个隐藏,不会换行(对应的命令在右键菜单和快捷键里仍然可用)。新开的 SFTP 标签本地、远程两侧各占一半;两侧和队列之间的分隔线拖到哪里,在这个标签关闭之前都会记住,切到别的标签再回来也不变:
|
||||
|
||||
- 第一行(导航):
|
||||
- 「目录列表」:列出从根到当前目录的每一级,本地另有主目录、桌面、文稿、下载,选中即前往。
|
||||
- 书签:点按钮打开「打开目录/书签」(见下);旁边的下拉箭头列出这台主机这一侧的书签,点击即前往,也可添加或移除当前路径。书签保存在本地数据库,删除主机时一并删除。
|
||||
- 上级目录、根目录、主目录、刷新、后退、前进。
|
||||
- 第二行(文件):
|
||||
- 远程是「下载…」,本地是「上传…」,下拉里还有「选择文件上传…」。
|
||||
- 删除、重命名…、属性…、新建(文件夹… / 文件…)。
|
||||
|
||||
### 路径标签
|
||||
|
||||
与 WinSCP 的路径标签相同:
|
||||
|
||||
- 显示当前目录的完整路径,例如 `/home/tester/`。
|
||||
- 每一级都可以点:悬停时从根到该级高亮,单击即前往。
|
||||
- 当前使用的一侧(最近点过的一侧)底部有一条深蓝色线。
|
||||
- 路径放不下时保留根目录和末尾几级,中间折成 `…`,点 `…` 前往被折叠的最后一级。
|
||||
- 单击当前目录、双击路径右侧空白处打开「打开目录/书签」。
|
||||
- 右键:前往、刷新、添加路径到书签、复制路径、打开目录/书签…。
|
||||
|
||||
### 打开目录/书签
|
||||
|
||||
与 WinSCP 的「打开目录」对话框相同,可以从路径标签、书签按钮、⌘O(其他平台 Ctrl+O;macOS 另有 ⌘⇧G)或路径标签的右键菜单打开:
|
||||
|
||||
- 「目录」预填当前目录并已选中,直接输入即可替换。`~` 表示主目录,相对路径从当前目录算起。本地侧另有「浏览…」。
|
||||
- 下方是这台主机这一侧的书签。与目录相同的书签处于选中状态;单击书签把它填入目录,双击直接打开。
|
||||
- 「添加」把目录加入书签,「删除」(或在列表中按 Delete)删除选中的书签并选中相邻的一个,「上移」「下移」调整顺序。
|
||||
- 书签的改动立即保存,按「取消」也不会撤销,与 WinSCP 相同。
|
||||
- 回车或「打开」前往目录;Esc 或「取消」关闭。
|
||||
|
||||
列表下方的状态栏平时显示项目数和已选数量;连接中和读取目录超过 0.3 秒时显示「正在连接 SFTP…」或「正在读取目录…」。读不了目录或连接断开时,原因用红字显示在窗口最左下角(平时显示「已连接 主机的名称」的位置);空闲时断网也会自己发现,不用等到刷新,时机和同一台主机的终端一样;重新连接用标签栏的「重新连接」按钮或标签菜单;断开时在远程列表里做任何操作(刷新、打开目录、删除、上传下载等)会弹出「SFTP 连接已断开」对话框,说明原因,并可直接「重新连接」。这些都不会让列表移动。
|
||||
|
||||
列与 WinSCP 相同:本地为名称、大小、类型、修改时间,远程为名称、大小、修改时间、权限、所有者。
|
||||
|
||||
- 大小默认以 KB 显示(`4,008,960 KB`,向上取整,只有空文件是 0 KB),与 WinSCP 相同。右键「大小」列标题可切换为「字节」(`4,105,175,040 B`)、「千字节(KB)」或「简短格式」(按大小用 B、KB、MB、GB,如 `3.8 GB`),对所有 SFTP 标签生效并保存在设置里。按大小排序始终按实际字节数。
|
||||
- 时间形如 `2026/4/22 12:44:53`。
|
||||
- 权限形如 `rwxr-xr-x`。
|
||||
- 所有者来自服务器的 `ls -l` 行。
|
||||
- 链接到目录的符号链接显示为带箭头的文件夹,可以双击进入。
|
||||
|
||||
### 选择与右键菜单
|
||||
|
||||
- 与 WinSCP 关闭「整行选择」时相同,「名称」格才是项目:鼠标移上去时只有名称格变灰,单击、双击、右键都作用在名称格上,选中时整个名称格高亮;行里的其他列和列表下方都算空白处。
|
||||
- 鼠标:单击名称格选中(其他选择取消),⌘ 单击(其他平台 Ctrl 单击)增减,Shift 单击选取范围,双击打开。单击空白处取消全部选择(按住 ⌘ 或 Shift 时不取消)。
|
||||
- 框选:在名称右边的空白、大小、类型、时间等列或列表下方按下拖动,画出选框,框住的行被选中;按住 ⌘ 或 Shift 开始拖动则加到已有选择上。拖到列表上下边缘时自动滚动。拖动文件名才是把文件拖到另一侧传输:按下未选中的文件名时只选中它(其他选择取消),拖走的就是它一个;按下已选中的文件名则拖走全部已选。拖动时只有到了另一侧列表上才显示「上传 / 下载 N 个项目」,在自己的列表或其他不能放下的地方,指针显示为禁止。
|
||||
- 键盘:↑↓、Home、End、PageUp、PageDown 移动,加 Shift 扩展;空格或 Insert 切换当前行;⌘A 全选;Tab 切到另一侧。
|
||||
- 右键未选中的名称格会先选中它;右键空白处取消选择并弹出空白处菜单。
|
||||
- 文件名上的菜单:打开、上传… / 下载…、删除、重命名…、属性…。
|
||||
- 空白处的菜单:前往(上级 / 根 / 主目录 / 后退 / 前进)、刷新、添加路径到书签、新建。
|
||||
|
||||
### 快捷键
|
||||
|
||||
| 命令 | 快捷键 |
|
||||
|---|---|
|
||||
| 上传 / 下载(按所在面板) | F5 |
|
||||
| 重命名 | F2 |
|
||||
| 新建文件夹 | F7 |
|
||||
| 删除 | F8、Delete;macOS 另有 ⌘⌫ |
|
||||
| 属性 | F9;macOS 另有 ⌘I |
|
||||
| 刷新 | ⌘R / Ctrl+R |
|
||||
| 上级目录 | Backspace;macOS 另有 ⌘↑ |
|
||||
| 根目录 | ⌘\ / Ctrl+\ |
|
||||
| 主目录 | macOS ⌘⇧H,其他平台 Ctrl+H |
|
||||
| 后退 / 前进 | ⌥← / ⌥→;macOS 另有 ⌘[ / ⌘] |
|
||||
| 添加路径到书签 | ⌘D / Ctrl+D |
|
||||
| 打开目录 | Enter;macOS 另有 ⌘↓ |
|
||||
| 打开目录/书签 | ⌘O / Ctrl+O;macOS 另有 ⌘⇧G |
|
||||
|
||||
### 文件操作
|
||||
|
||||
- 删除前会确认,并写明对象和后果:
|
||||
- 本地项目移到废纸篓。
|
||||
- 远程项目永久删除,目录连同其中全部内容。
|
||||
- 删除符号链接只删除链接本身。
|
||||
- 重命名和新建:
|
||||
- 按当前列表检查名称;不能为空,不能是 `.` 或 `..`,不能与已有项目同名。
|
||||
- 重命名不会覆盖已有项目。
|
||||
- 属性:
|
||||
- 显示位置、大小、修改时间,远程另有所有者和组。
|
||||
- 权限用 3×3 复选框和八进制数字编辑,两者同步。
|
||||
- 多选时,项目之间不一致的权限位保持原样,除非你改动它。
|
||||
- 含目录时可以递归应用;递归时默认「为目录添加执行权限(X)」,避免目录无法进入。
|
||||
- 符号链接不参与修改权限。
|
||||
|
||||
### 上传与下载
|
||||
|
||||
- 入口:
|
||||
- 上传:「上传…」、本地列表中的 F5、「选择文件上传…」,或按住本地项目的文件名拖到远程列表,或把 Finder 中的文件拖到远程列表。
|
||||
- 下载:「下载…」、远程列表中的 F5,或按住远程项目的文件名拖到本地列表。
|
||||
- 拖到目录行时放进该目录,拖到其余区域时放进当前目录。
|
||||
- 所有入口都显示确认框,可以修改目标目录;下载可以用「浏览…」选择本机目录。确认框写明数量和种类(如「上传 2 个文件到 …」),来源的所在目录只写一次,每个项目占一行,悬停可看完整路径。
|
||||
- 确认后来源、端点和目标即固定。每个 SFTP 标签同时运行一个批次(上传和下载共用),批内依次处理;传输进行中还可以继续上传或下载,新的批次排进传输队列,前一批结束后自动开始。其他 SFTP 标签、主机和终端可以继续操作。
|
||||
- 递归传输包含空目录。符号链接原样创建,不跟随。默认二进制传输,保留普通文件的修改时间。
|
||||
- 新建项使用目标一侧的默认权限,覆盖时保留目标原有的基本权限。不复制所有者、ACL 或扩展属性。
|
||||
- 同名目录合并。文件或链接冲突时询问覆盖、跳过或取消,可以应用于本批剩余冲突;目录与非目录冲突不会自动删除。
|
||||
- 出错时可以重试、跳过或取消,完成后的汇总分别列出成功、跳过和失败。
|
||||
|
||||
### 传输队列
|
||||
|
||||
与 WinSCP 的队列面板相同,有传输时出现在双栏下方,高度可拖动,清除全部已结束的批次后收起。
|
||||
|
||||
- 每一批一行:操作(上传 / 下载)、来源(一个项目时是它的路径,多个项目时是所在目录)、目标、已传输(单位与「大小」列相同)、时间(剩余时间)、速度(最近几秒)和进度。
|
||||
- 进行中的批次显示总进度条,下面一行是正在传的文件和它自己的进度条,对应 WinSCP 进度对话框里的两个进度条。
|
||||
- 进度列:等待中、正在扫描、百分比、等待回答、重连说明、已停止、已完成、部分失败(N 项)、已移出。
|
||||
- 点行首的箭头展开这一批的逐项结果(完成、已跳过、失败及原因),最新的在最上面。
|
||||
- 工具栏:继续(停止的批次)、停止(进行中的批次)、移出队列(选中的排队中或已结束的批次,或停止的批次)、丢弃续传进度、清除已完成。
|
||||
- 停止的批次留在队首,等你继续、丢弃续传进度或移出队列之后,后面的批次才会开始;移出队列不删除续传记录,下次传同样的文件仍会询问是否续传。
|
||||
- 队列只在本次运行中保留,关闭 SFTP 标签时一起清除;有未完成的批次时关闭标签会先确认。
|
||||
|
||||
## 断点续传和替换
|
||||
|
||||
文件先写入 `<目标文件名>.filepart`。写入请求采用有界流水线,收到每个写入确认并关闭成功后才发布正式文件;服务器支持时先调用 `fsync@openssh.com`。支持 `posix-rename@openssh.com` 的服务器使用原子替换;其他服务器先将旧文件移到 `.shellrs-<UUID>.backup`,再发布新文件,此兼容路径不保证原子性。发布响应丢失时根据受管理临时路径、阶段、类型和大小恢复,无法安全判断则保留恢复文件并报告。
|
||||
|
||||
续传记录保存在应用数据目录的 `upload-resume/`,采用版本化 JSON 和原子写入,不写入密码、口令或私钥,不改变数据库结构。记录用于绑定端点、主机指纹、来源、目标和安全替换阶段。续传方式与 WinSCP 相同:读取远端 `.filepart` 的当前大小,在本地文件中跳过相同长度并继续传输;使用者需确保来源仍是同一版本。上传过程不会为了校验而回读远端文件内容。
|
||||
|
||||
下载同样先写入本机的 `<目标文件名>.filepart`,完成后设置远程的修改时间、fsync,再在同一文件系统内重命名为正式文件(原子替换)。并发的读取请求可能乱序、短读返回,但临时文件严格按顺序写入,因为续传只信它的长度。下载续传记录保存在 `download-resume/`,绑定端点、主机指纹、远程来源及其大小与修改时间、本地目标;远程文件变化后不能续传,只能重新下载或跳过。
|
||||
|
||||
网络故障每批最多自动重连三次,间隔 1、3、10 秒。耗尽后使用「继续上传」或「继续下载」,手动恢复会重置次数。认证失败、主机密钥变化和主动取消需要用户处理。重连沿用原连接配置,编辑主机不会改变正在传输的目标。
|
||||
|
||||
取消、关闭标签、断开连接或退出时保留续传记录;关闭正在传输的标签会先确认。重启后不会自动继续,再次选择相同来源和目标时会询问续传。停止状态下可使用「丢弃续传进度」清理该批受管理的临时文件;存在无法判断的恢复备份时会保留文件并说明原因。成功发布的项目自动清理记录和备份。
|
||||
|
||||
文件名不能准确表示时会明确报错。当前 `russh-sftp 3.0.0` 的远端字符串解码会替换无效 UTF-8,浏览器因此拒绝包含替换字符 `U+FFFD` 的远端名称,避免访问错误路径。符号链接沿用 OpenSSH 的 SFTP v3 参数顺序。
|
||||
|
||||
**尚未实现:目录同步、前台传输进度对话框、队列排序、过滤、查找文件、目录树和 Dock 布局持久化。**
|
||||
|
||||
## 下载与更新
|
||||
|
||||
安装包在 [shellrs.com/download](https://shellrs.com/download):
|
||||
|
||||
| 系统 | 首次安装 | 自动更新 |
|
||||
|---|---|---|
|
||||
| macOS(Apple Silicon 和 Intel 共用一个包) | DMG,把 ShellRS 拖进「应用程序」 | 支持 |
|
||||
| Windows x64 | 安装程序,按当前用户安装到 `%LOCALAPPDATA%\Programs\ShellRS`,不需要管理员 | 支持 |
|
||||
| Linux x64 | AppImage | 支持 |
|
||||
|
||||
ShellRS 启动 30 秒后检查一次有没有新版本,之后每 6 小时检查一次。有新版本就在后台下载并校验,完成后标题栏右上角出现更新图标。点开可以看更新内容,点「重启并安装」就换成新版本并重新打开。重启会断开已打开的连接、停止传输(续传进度保留)和端口转发,对话框里会写明具体有哪些。
|
||||
|
||||
「设置 › 关于」里的「应用更新」只有三行:
|
||||
|
||||
- **当前版本**:下面写着现在的状态(已是最新、正在下载、已下载等),右边是版本号和一个按钮:平时是「检查更新」,下载好之后变成「重启并安装…」。构建号和平台在版本号的悬停提示里。
|
||||
- **更新渠道**:稳定版适合日常使用;Beta 可以提前收到测试版本,稳定版发布时也会收到。切换后马上按新渠道检查(关掉自动升级时等你手动检查)。
|
||||
- **自动升级**:关掉之后只在手动检查时才联网,发现新版本时由你决定是否下载。
|
||||
|
||||
- **校验**:更新清单由 ShellRS 的发布密钥签名,安装包按清单里的大小和 SHA-256 核对,任何一步对不上都不会安装。macOS 上还会核对新版本的开发者签名。
|
||||
- **隐私**:检查更新只发送 ShellRS 的版本号、操作系统和 CPU 架构,不发送任何能识别这台电脑或用户的信息。
|
||||
- **不能自动更新的情况**:从 DMG 里直接运行、没有放进「应用程序」文件夹、对安装位置没有写权限、Linux 上不是用 AppImage 运行,或者是自己编译的开发构建。这时「关于」页会说明原因,有新版本时给出「前往下载页」。
|
||||
- **退回旧版本**:数据库在升级到新结构之前会备份成 `shellrs.db.v<旧版本>.bak`。旧版本打开新结构的数据库时会拒绝打开,以免把新版本保存的数据弄乱;需要退回时,把备份改名回 `shellrs.db`。
|
||||
|
||||
## 发布
|
||||
|
||||
在 `CHANGELOG.md` 里写好 `## [x.y.z]` 一节,把 `Cargo.toml` 的版本改成 `x.y.z`,提交后推送 tag `vx.y.z`。`.github/workflows/release.yml` 依次:
|
||||
|
||||
1. 核对 tag 与 `Cargo.toml` 一致,截出更新说明,确认代码里有发布公钥;
|
||||
2. 构建并打包:macOS 合成 universal,签名、公证、staple,产出升级用的 `.app.zip` 和首次安装用的 `.dmg`;Windows 用 Inno Setup 打安装程序;Linux 打 AppImage;
|
||||
3. 在 `release` 环境里人工批准后:给每个包签名,上传到 R2(`dl.shellrs.com/releases/<版本>/`),建 GitHub Release,**最后**才替换更新清单 `dl.shellrs.com/update/v1/<通道>.json` 并清 CDN 缓存。清单里每个包有两个下载地址:先 R2,下载失败时客户端接着试 GitHub Release(仓库是公开的,下载不用登录)。
|
||||
|
||||
版本号带 `-` 的(如 `0.3.0-beta.1`)只发到 beta 通道;正式版同时成为 beta 通道的最新版。在 Actions 里手动运行这个 workflow 只构建打包、不发布,用来演练。客户端只接受比自己新的版本,所以发错的版本撤不回来:把清单改回上一版能阻止更多人升级,修复要发新的补丁版。
|
||||
|
||||
一次性准备:
|
||||
|
||||
- **更新签名密钥**:`minisign -G -p shellrs-update.pub -s shellrs-update.key` 生成两对(一对日常用,一对离线保存备用),把两个公钥(`.pub` 文件的第二行)填进 `src/update/build_info.rs` 的 `TRUSTED_KEYS`。私钥和口令只放进 GitHub 的 `release` 环境,另外离线备份;丢了私钥,已发出去的 ShellRS 就再也收不到更新。换钥匙时先发一版带上新公钥,再换签名用的私钥。
|
||||
- **Cloudflare**:shellrs.com 的 DNS 托管在 Cloudflare;建 R2 bucket,绑定自定义域 `dl.shellrs.com`,关闭 `r2.dev` 地址;Cache Rules 让 `/releases/*` 和 `/update/*` 按源站的 Cache-Control 缓存(默认不缓存 `.json`、`.AppImage`);一个只能写这个 bucket 的 R2 API Token,一个只能清这个 zone 缓存的 API Token。
|
||||
- **Apple**:Developer ID Application 证书(导出 .p12)和 notarytool 用的 App Store Connect API Key。`packaging/macos/Info.plist.in` 里的 bundle id `com.shellrs.ShellRS` 和签名的 Team ID 一经发布就不能再改:钥匙串按它们授权,改了之后每条保存的密码都要重新允许。
|
||||
- **图标**:`assets/logo/` 里的 `shellrs.icns`(macOS)、`shellrs.png`(1024×1024,Linux,macOS 没有 icns 时也由它生成)和 `shellrs.ico`(Windows 安装程序)。
|
||||
- **GitHub secrets**:仓库级的 `APPLE_CERTIFICATE_P12`(base64)、`APPLE_CERTIFICATE_PASSWORD`、`APPLE_SIGNING_IDENTITY`、`APPLE_TEAM_ID`、`APPLE_API_KEY_P8`(base64)、`APPLE_API_KEY_ID`、`APPLE_API_ISSUER`;`release` 环境(设为需要审批)里的 `MINISIGN_SECRET_KEY`、`MINISIGN_PASSWORD`、`R2_ACCOUNT_ID`、`R2_ACCESS_KEY_ID`、`R2_SECRET_ACCESS_KEY`、`R2_BUCKET`、`CF_ZONE_ID`、`CF_API_TOKEN`。
|
||||
|
||||
清单格式见 `packaging/manifest.example.json`,由 `packaging/make-manifest.sh` 生成:一个文件里是清单原文和它的 minisign 签名,签名的 trusted comment 写明通道和版本。格式只增加字段、不改已有的;已发出去的 ShellRS 一直按 `/update/v1/` 这个地址检查。
|
||||
|
||||
## 运行和验证
|
||||
|
||||
工具链固定 Rust 1.98.1,`russh-sftp` 固定 3.0.0;保持仓库锁定的 GPUI 与 russh 版本。
|
||||
需要先装 [rustup](https://rustup.rs)。仓库里的 `rust-toolchain.toml` 固定了 Rust 1.98.1,第一次构建时会自动安装。
|
||||
|
||||
```sh
|
||||
git clone https://github.com/since2006/shell-rs.git
|
||||
cd shell-rs
|
||||
cargo run
|
||||
cargo test
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo fmt --check
|
||||
```
|
||||
|
||||
数据目录默认是系统应用数据目录中的 `shellrs`,可通过 `SHELLRS_DATA_DIR` 覆盖。主机、端口转发规则和凭据(不含密码)保存在 `shellrs.db`,主机信任记录保存在该目录的 `known_hosts`,凭据里粘贴或生成的私钥保存在该目录的 `keys/` 下,密码和私钥口令使用系统钥匙串(服务名 `shellrs`;账户名是主机端点的 `password:<用户>@<地址>:<端口>`、私钥的 `passphrase:<路径>`、密码凭据的 `credential:<随机 ID>` 和代理的 `proxy:<用户>@<代理地址>:<端口>`)。
|
||||
首次构建要编译整个 GPUI 栈,需要一段时间。Linux 需要的系统依赖和测试方法见[开发与测试](docs/development.md)。
|
||||
|
||||
`src/sftp/tests.rs` 使用临时目录与故障注入覆盖上传和下载的文件内容、空目录、链接、覆盖、权限、取消、源文件变化、乱序与短读、续传及替换各阶段恢复,以及删除、重命名、新建和递归改权限不跟随链接。Unix 协议测试使用本机 OpenSSH `sftp-server`(macOS `/usr/libexec/sftp-server`,Linux `/usr/lib/openssh/sftp-server`);工作线程测试仅监听 `127.0.0.1` 的随机端口,使用测试凭据。限制本地套接字的沙箱会跳过该部分,需要在允许回环套接字的环境运行。
|
||||
## 文档
|
||||
|
||||
`src/forward/protocol_tests.rs` 用进程内的 SSH 服务器和本机的回显服务测试真实的转发工作线程:三种转发的往返、目标不可达、服务器禁止转发、端口被占用、空闲断线后重连、重试耗尽和停止后的清理,同样只监听 `127.0.0.1` 的随机端口。
|
||||
- [使用手册](docs/manual.md):每个功能的详细说明和快捷键
|
||||
- [开发与测试](docs/development.md):构建、测试和数据目录
|
||||
- [发布流程](docs/release.md):打包、签名和在线更新的发布步骤
|
||||
- [更新日志](CHANGELOG.md)
|
||||
|
||||
`tests/ui/` 里的 UI 测试驱动真实 `Workspace`,通过 `Workspace::new_with_services` 注入 SFTP、本地目录、终端和端口转发服务,不连接用户服务器或真实钥匙串;在线升级的测试给 `workspace.updater()` 注入假的更新服务器和安装器,不联网、不改任何安装。Finder 的系统原生拖放仍需在真实 macOS 窗口中补充人工验收;无头测试覆盖原生文件拖入事件和面板内部拖放。
|
||||
## 路线图
|
||||
|
||||
开发构建(`cargo run`)不检查更新。要在本机走一遍更新流程,用 debug 构建加环境变量 `SHELLRS_UPDATE_CHANNEL=beta` 编译,运行时用 `SHELLRS_UPDATE_URL=http://127.0.0.1:8000/{channel}.json` 指向本地的静态服务器(`python3 -m http.server`),清单用 `packaging/make-manifest.sh` 和自己生成的测试密钥签名,测试公钥放在环境变量 `SHELLRS_UPDATE_PUBLIC_KEY` 里。release 构建不认这两个环境变量。
|
||||
以下功能尚未实现:
|
||||
|
||||
上传交互参考 [WinSCP 上传流程](https://winscp.net/eng/docs/task_upload),协议适配参考 [russh-sftp 请求接口](https://docs.rs/russh-sftp/3.0.0/russh_sftp/client/struct.RawSftpSession.html) 与 [OpenSSH 扩展规范](https://raw.githubusercontent.com/openssh/openssh-portable/master/PROTOCOL)。
|
||||
- 英文界面(目前只有 GPUI Kit 组件自带的文字会切换语言)
|
||||
- SFTP 的目录同步、过滤、查找文件和目录树
|
||||
- Dock 布局在重启后保留
|
||||
- 跳板和代理叠加使用、HTTPS 代理
|
||||
- 通过外部 CLI 管理端口转发和凭据
|
||||
- Windows 安装程序的代码签名
|
||||
|
||||
## 参与贡献
|
||||
|
||||
欢迎提交 [Issue](https://github.com/since2006/shell-rs/issues) 和 Pull Request。架构说明和代码约定见 [CLAUDE.md](CLAUDE.md)。提交前请确认下面三条都通过:
|
||||
|
||||
```sh
|
||||
cargo fmt --check
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo test
|
||||
```
|
||||
|
||||
## 致谢
|
||||
|
||||
- [GPUI](https://github.com/zed-industries/zed)(Zed)和 [gpui-kit](https://gpui-kit.com):界面框架
|
||||
- [russh](https://github.com/Eugeny/russh) 和 [russh-sftp](https://github.com/AspectUnk/russh-sftp):SSH 与 SFTP 协议
|
||||
- [alacritty_terminal](https://crates.io/crates/alacritty_terminal) 和 [portable-pty](https://crates.io/crates/portable-pty):终端模拟与本地 PTY
|
||||
- [keyring](https://crates.io/crates/keyring):系统钥匙串
|
||||
- [Simple Icons](https://simpleicons.org):系统徽章使用的 logo(CC0)
|
||||
- [WinSCP](https://winscp.net) 和 Xshell:交互设计的参照
|
||||
|
||||
## 许可证
|
||||
|
||||
ShellRS 以 [MIT](LICENSE-MIT) 或 [Apache-2.0](LICENSE-APACHE) 双许可发布,使用时任选其一。
|
||||
@@ -0,0 +1,44 @@
|
||||
# 开发与测试
|
||||
|
||||
[返回 README](../README.md)
|
||||
|
||||
## 构建
|
||||
|
||||
装好 [rustup](https://rustup.rs) 即可:`rust-toolchain.toml` 固定了 Rust 1.98.1,第一次构建时 rustup 会自动装上它,不改动机器上默认的工具链。首次构建要编译整个 GPUI 栈,需要一段时间。
|
||||
|
||||
Linux 需要先装这些开发包(Debian / Ubuntu,与发布构建用的一致):
|
||||
|
||||
```sh
|
||||
sudo apt-get install -y cmake clang pkg-config libfontconfig-dev \
|
||||
libfreetype-dev libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev \
|
||||
libx11-xcb-dev libxcb1-dev libvulkan-dev libdbus-1-dev libssl-dev
|
||||
```
|
||||
|
||||
Windows 只在 CI(`.github/workflows/windows.yml`)上编译和测试。
|
||||
|
||||
## 运行和验证
|
||||
|
||||
工具链固定 Rust 1.98.1,`russh-sftp` 固定 3.0.0;保持仓库锁定的 GPUI 与 russh 版本。
|
||||
|
||||
```sh
|
||||
cargo run
|
||||
cargo test
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo fmt --check
|
||||
```
|
||||
|
||||
数据目录默认是系统应用数据目录中的 `shellrs`,可通过 `SHELLRS_DATA_DIR` 覆盖。主机、端口转发规则和凭据(不含密码)保存在 `shellrs.db`,主机信任记录保存在该目录的 `known_hosts`,凭据里粘贴或生成的私钥保存在该目录的 `keys/` 下,密码和私钥口令使用系统钥匙串(服务名 `shellrs`;账户名是主机端点的 `password:<用户>@<地址>:<端口>`、私钥的 `passphrase:<路径>`、密码凭据的 `credential:<随机 ID>` 和代理的 `proxy:<用户>@<代理地址>:<端口>`)。
|
||||
|
||||
`src/sftp/tests.rs` 使用临时目录与故障注入覆盖上传和下载的文件内容、空目录、链接、覆盖、权限、取消、源文件变化、乱序与短读、续传及替换各阶段恢复,以及删除、重命名、新建和递归改权限不跟随链接。Unix 协议测试使用本机 OpenSSH `sftp-server`(macOS `/usr/libexec/sftp-server`,Linux `/usr/lib/openssh/sftp-server`);工作线程测试仅监听 `127.0.0.1` 的随机端口,使用测试凭据。限制本地套接字的沙箱会跳过该部分,需要在允许回环套接字的环境运行。
|
||||
|
||||
`src/forward/protocol_tests.rs` 用进程内的 SSH 服务器和本机的回显服务测试真实的转发工作线程:三种转发的往返、目标不可达、服务器禁止转发、端口被占用、空闲断线后重连、重试耗尽和停止后的清理,同样只监听 `127.0.0.1` 的随机端口。
|
||||
|
||||
`tests/ui/` 里的 UI 测试驱动真实 `Workspace`,通过 `Workspace::new_with_services` 注入 SFTP、本地目录、终端和端口转发服务,不连接用户服务器或真实钥匙串;在线升级的测试给 `workspace.updater()` 注入假的更新服务器和安装器,不联网、不改任何安装。Finder 的系统原生拖放仍需在真实 macOS 窗口中补充人工验收;无头测试覆盖原生文件拖入事件和面板内部拖放。
|
||||
|
||||
开发构建(`cargo run`)不检查更新。要在本机走一遍更新流程,用 debug 构建加环境变量 `SHELLRS_UPDATE_CHANNEL=beta` 编译,运行时用 `SHELLRS_UPDATE_URL=http://127.0.0.1:8000/{channel}.json` 指向本地的静态服务器(`python3 -m http.server`),清单用 `packaging/make-manifest.sh` 和自己生成的测试密钥签名,测试公钥放在环境变量 `SHELLRS_UPDATE_PUBLIC_KEY` 里。release 构建不认这两个环境变量。
|
||||
|
||||
上传交互参考 [WinSCP 上传流程](https://winscp.net/eng/docs/task_upload),协议适配参考 [russh-sftp 请求接口](https://docs.rs/russh-sftp/3.0.0/russh_sftp/client/struct.RawSftpSession.html) 与 [OpenSSH 扩展规范](https://raw.githubusercontent.com/openssh/openssh-portable/master/PROTOCOL)。
|
||||
|
||||
## README 截图
|
||||
|
||||
`docs/screenshots/` 里的截图来自真实窗口(macOS 的 `screencapture -o -l <窗口号>`,2 倍分辨率)。截图时不要用自己的数据:把数据目录复制一份,改掉分组、主机的名称和地址,用 `SHELLRS_DATA_DIR` 指向这份副本启动 ShellRS;钥匙串里的密码按「用户@地址:端口」保存,所以副本里没改地址的主机照样能真实连接。截好后检查状态栏、对话框和终端输出里有没有真实的 IP、主机名或业务信息,有就打码。
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
# ShellRS 使用手册
|
||||
|
||||
[返回 README](../README.md)
|
||||
|
||||
基于 GPUI Kit 的中文 SSH 主机管理工具,提供持久化的主机与分组、可在多台主机间共用的登录凭据、本地终端、SSH 多终端、对标 WinSCP 的 SFTP 双栏浏览器和 SSH 端口转发。
|
||||
|
||||
标题栏「ShellRS」后面的三个图标切换左侧栏显示的内容:主机列表、端口转发列表或凭据列表。侧栏隐藏时点其中一个会把它重新展开。
|
||||
|
||||
ShellRS 同一时间只运行一个:已经开着时再打开一次,会把已有的窗口调到前面,新启动的那个随即退出。两个 ShellRS 各自把主机读进内存再写回同一个数据库,会互相覆盖对方的改动。(用 `SHELLRS_DATA_DIR` 指到不同数据目录的算不同的 ShellRS,可以同时运行。)要并排看两个终端或 SFTP,把标签拖成分栏即可。
|
||||
|
||||
在 macOS 上,点窗口左上角的关闭按钮只是把 ShellRS 隐藏到后台:已打开的连接、SFTP 传输、端口转发和外部 CLI 照常运行,点 Dock 图标恢复窗口,原来的标签都在。要真正退出用 ⌘Q,或者右键 Dock 图标选「退出」。Windows 和 Linux 上关闭窗口就是退出。
|
||||
|
||||
## 主机列表
|
||||
|
||||
左侧栏里保存的每一项是一台主机:地址、端口和认证方式。同一台机器用不同用户登录,就保存成两台主机。
|
||||
|
||||
主机对话框的「认证方式」有三种:
|
||||
|
||||
- **密码**:填用户名和密码,密码保存在系统钥匙串;密码留空则每次连接时询问。只用密码登录,不会先拿 SSH Agent 或私钥去试。
|
||||
- **使用凭据**:选一条保存好的凭据,用户名和登录方式都来自凭据(见下面「凭据」),对话框里不再填用户名和密码。「测试连接」同样使用凭据保存的密码。要用私钥登录,就建一条密钥凭据。
|
||||
- **无密码**:只填用户名。依次尝试服务器免认证、SSH Agent 和 `~/.ssh` 下的默认私钥(`id_ed25519`、`id_ecdsa`、`id_rsa`),和命令行 `ssh` 默认的做法一样。服务器要求密码时连接失败并说明原因,不会询问;服务器在密钥之后再要验证码时照样询问。
|
||||
|
||||
无论选哪种,连接时都先问服务器是否允许免认证登录,允许就直接登录。
|
||||
|
||||
主机对话框「分组」下面的「连接方式」决定怎么连到这台主机,终端、SFTP、端口转发、外部 CLI 和「测试连接」都照它走:
|
||||
|
||||
- **直接连接**:默认,本机直接连主机的地址和端口。
|
||||
- **SSH 跳板**:经过别的已保存主机连过去,支持多级。框里一排标签写出整条路(「本机 → 阿里云99 → 禅道 → 当前主机」),下面每台跳板一行,「×」移除,「添加跳板主机」可按名称或地址搜索;要调顺序就移除后重新添加。连接时先登录第 1 台,由它连第 2 台,依此类推,最后一台连到主机。每台跳板都用它自己的地址、端口、认证方式(密码、凭据或无密码)和主机信任记录,首次连接各问一次主机密钥,询问框里写明是哪台跳板主机;跳板主机自己的「连接方式」在这里不生效,要多级就在这里按顺序列出。主机的地址按跳板主机那边的网络来填,例如内网地址。
|
||||
- **代理连接**:经由 HTTP 代理(`CONNECT`)或 SOCKS5 代理连接,在下拉框里选。填代理的地址和端口(例如本机的 `127.0.0.1:7890`);代理需要认证时再填用户名和密码,密码保存在系统钥匙串,经由同一个代理、同一个用户名的主机共用。主机的地址交给代理去解析。代理要求认证却没有密码、或者拒绝了用户名和密码时,连接失败并说明原因,不会询问。
|
||||
|
||||
改了一台主机的地址或认证方式,经由它跳转的主机也会重新连接(只改名称不会)。删除一台被别的主机用作跳板的主机,确认框写明有几台主机在用它;删除后那些主机的跳板列表里留下一行「已删除的主机」,连接时直接报错,不会悄悄改成直连或少跳一级,编辑时移除或换一台即可。已经打开的连接不受影响。
|
||||
|
||||
**尚未实现:跳板和代理叠加使用、拖动调整跳板顺序、HTTPS 代理、在主机列表里标出连接方式。**
|
||||
|
||||
对话框最后是「备注」,可以写多行(回车换行),记下机器的用途、负责人、到期时间之类,只保存在本地数据库里,不影响连接。
|
||||
|
||||
鼠标停在主机上,行的右侧会显示 `用户@地址:端口`,主机有备注时下面接着显示备注(太长的只显示前 6 行)。右键主机可连接、打开 SFTP、编辑、复制、复制 ID 或删除。「复制 ID」复制的是主机的 16 位随机标识(形如 `Jwg5rHvXCxw89paM`),供其他工具标识这台主机:它在主机创建时生成,改名、改地址都不变,复制出来的主机得到新的 ID,删掉的 ID 不会再分配。没有打开任何标签时,中间区显示「最近连接」列表,双击或回车连接;右键其中的主机弹出同一个菜单,并选中该主机。
|
||||
|
||||
拖动分组或主机到同类项目的上侧 / 下侧可调整同级顺序;主机拖到分组会移入该分组,分组拖到另一个分组的中部会成为子分组。拖到列表空白处可移回根层级。分组始终排在同级主机之前,顺序与分组的展开状态会保存到本地数据库。搜索时暂停拖动排序。
|
||||
|
||||
## 端口转发
|
||||
|
||||
点标题栏的端口转发图标,左侧栏换成转发列表。每条转发规则经由一台已保存的主机,启动时为自己建立一条 SSH 连接,不依赖该主机的终端或 SFTP 标签:关掉标签、对主机「断开连接」都不影响它,主机的连接状态也不把它算在内。有转发在运行时,标题栏的端口转发图标旁显示运行数量。
|
||||
|
||||
三种转发,都按「入口 → 出口 → 目标」理解,连接从入口进、经 SSH 隧道到出口、再由出口去连目标:
|
||||
|
||||
| 类型 | 入口(监听) | 出口 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| 本地转发(`ssh -L`) | 本机 | 服务器 | 在本机访问服务器那边的服务,例如用本机的客户端连只对服务器开放的数据库 |
|
||||
| 远程转发(`ssh -R`) | 服务器 | 本机 | 让服务器那边访问本机的服务,例如把本机开发中的站点给对方看、接收 Webhook |
|
||||
| 动态转发(`ssh -D`) | 本机(SOCKS 代理) | 服务器 | 把服务器当代理,目标由应用自己指定,例如让浏览器经由服务器上网 |
|
||||
|
||||
- **新建和编辑**:列表标题栏的「+」打开对话框。上方一行三张卡片选类型,中间的示意图随表单实时填入地址、标出入口在哪台机器上,下面一句话说明这条规则的效果(如「在本机连接 127.0.0.1:8080,就等于从 SSH 服务器连接 db.internal:3306。」)。示意图和这句话里的「SSH 服务器」就是「经由主机」选的那一台,图里按它扮演的角色称呼,不写主机的名称。示意图上有一个圆点沿连接方向缓缓移动:先过 SSH 隧道,再到目标,停一下,周而复始;切换类型时从新的入口重新出发。名称可以不填,列表里显示转发摘要(`8080 → db.internal:3306`、`9000 ← localhost:3000`、`SOCKS 1080`)。
|
||||
- **启动和停止**:每行末尾的按钮,或双击该行、选中后回车;右键菜单里还有编辑和删除。「创建」只保存规则,不会自动启动。勾选「启动 ShellRS 时自动开启」的规则随应用启动。
|
||||
- **状态**:行尾显示正在连接、运行中(及当前连接数)、正在重连或已停止的原因。某个连接转发不了(例如服务器连不上目标)只在该行提示,转发本身继续运行。
|
||||
- **登录**:使用主机的配置、钥匙串里保存的密码和 ShellRS 自己的主机信任记录。首次连接的主机、需要输入的密码会弹出和终端一样的询问框,并注明是哪条转发在连接;取消询问等于停止这条转发。
|
||||
- **断线重连**:连接中断后按 1、3、10 秒自动重连三次,重连期间本机监听端口保留,新进来的连接会被直接关闭。自动重连不弹任何询问:需要重新输入密码、确认主机密钥时停止并写明原因,手动启动即可。三次都失败也会停止,并弹出通知。
|
||||
- **修改后**:编辑运行中规则的类型、主机、监听或目标,或编辑它所经由的主机的地址、认证方式和连接方式(包括这台主机所用凭据的用户名、类型或私钥文件,以及它的跳板主机),这条转发会按新设置重新启动;只改名称或自动开启不会。
|
||||
- **删除主机**:删除主机(或包含它的分组)会连同经由它的转发规则一起删除,运行中的先停止,确认框里写明数量。
|
||||
- **动态转发**支持 SOCKS5(无认证,`CONNECT`,IPv4、域名、IPv6)和 SOCKS4 / 4a。域名交给服务器解析。
|
||||
- **监听地址**默认 `127.0.0.1`。填其他地址时,本地和动态转发会被同一网络中的其他设备使用;远程转发需要服务器的 `sshd_config` 设置 `GatewayPorts clientspecified` 或 `yes`,其他机器才连得上;默认是 `no`,这时转发照常运行、不报错,但只监听服务器自己的 `127.0.0.1`(和手写 `ssh -R` 一样)。对话框在填了非回环地址时会提示这一点。1024 以下的端口需要管理员(服务器上是 root)权限。不支持端口 0(由系统挑选端口)。
|
||||
|
||||
**尚未实现:通过外部 CLI 管理转发、规则排序。系统的「减少动态效果」设置目前不会关掉示意图的动画。**
|
||||
|
||||
## 凭据
|
||||
|
||||
点标题栏的凭据(钥匙)图标,左侧栏换成凭据列表。一条凭据是一套可以让多台主机共用的登录信息,包括名称、用户名和登录方式:
|
||||
|
||||
| 类型 | 保存什么 | 登录时 |
|
||||
| --- | --- | --- |
|
||||
| 密码 | 用户名和密码 | 用凭据保存的密码;没存或被拒绝时询问 |
|
||||
| 密钥 | 用户名、私钥和口令 | 只用这把私钥;口令按私钥文件保存,用同一个文件的凭据共用 |
|
||||
| SSH Agent | 用户名 | 只用 SSH Agent 中的密钥:macOS / Linux 上是 `SSH_AUTH_SOCK` 指向的 Agent,Windows 上是 OpenSSH Authentication Agent 服务 |
|
||||
|
||||
- **新建和编辑**:列表标题栏的「+」、空列表里的「新建凭据…」或空白处的右键菜单打开对话框;双击凭据、选中后回车或右键「编辑凭据…」编辑。密码和私钥口令存进系统钥匙串,数据库里只有名称、用户名、类型和私钥路径。
|
||||
- **密钥从哪里来**:密钥凭据的「私钥」有三种来源。
|
||||
- **本机文件**:选一个私钥文件,只保存它的路径;文件移动或删除后需要重新选择。
|
||||
- **粘贴**:贴进 OpenSSH、PEM 或 PuTTY 格式的私钥。贴成公钥或认不出的内容时会说明原因。私钥加了口令时才显示口令栏。
|
||||
- **生成新密钥**:选 Ed25519(默认)或 RSA 4096,选中就生成;对话框里显示公钥,旁边可以复制。口令可留空;填了就用它加密私钥,口令存进钥匙串。生成的密钥以凭据名称作为注释。
|
||||
- 粘贴和生成的私钥由 ShellRS 写成数据目录下 `keys/` 里的文件(随机文件名,只有自己的账户能读),凭据引用这个文件,编辑时显示为「本机文件」和它的路径。之后再粘贴或生成会直接覆盖这个文件,已连接的主机不会因此重连;改成别的文件或别的类型、或删除凭据时,这个文件连同它的口令一起删除,对话框会先提示。用户自己的私钥文件 ShellRS 从不改动或删除。
|
||||
- 空列表和空白处的右键菜单里另有「生成密钥…」,打开的是同一个对话框,已经选好「密钥 · 生成新密钥」。密钥凭据的右键菜单有「复制公钥」,用来把公钥加到服务器的 `~/.ssh/authorized_keys`。
|
||||
- **主机跟着凭据变**:改凭据的用户名,所有使用它的主机的用户名一起变。改用户名、类型或私钥文件时,使用它的已连接主机会重新连接(只改名称不会);对话框里写明有几台主机在用。
|
||||
- **删除**:右键「删除」。有主机在用时确认框写明数量和它们之后怎么登录:用密码凭据的主机改为「密码」,连接时询问;用密钥或 SSH Agent 凭据的主机改为「无密码」。用户名不变,密码和私钥不会复制过去。已经打开的连接不受影响,凭据的密码随凭据一起从钥匙串删除。
|
||||
- **SSH Agent 凭据**连不上 Agent、Agent 里没有密钥或服务器不接受其中任何一把时,会直接说明原因,不会改为询问密码。「无密码」的主机也会尝试同一个 Agent,连不上时静默跳过,接着试默认私钥(Windows 上同样如此)。
|
||||
|
||||
**尚未实现:在主机对话框里直接新建凭据、通过外部 CLI 管理凭据、凭据排序、自定义 SSH Agent 的套接字、把公钥自动装到主机上、导出 ShellRS 保存的私钥。**
|
||||
|
||||
## 设置
|
||||
|
||||
侧栏最下方的「设置」按钮(或 ⌘,,其他平台 Ctrl+,)在中间区打开「设置」标签页;已经打开时切换到该标签,不会重复打开。设置页分两栏:左边是分类(外观、终端、外部 CLI、关于),上方可搜索,栏宽可拖动;右边是所选分类的设置。设置改动立即生效并保存在数据目录的 `settings.json` 里。
|
||||
|
||||
- **外观 → 常规 → 界面语言**:跟随系统、简体中文、English,默认简体中文。英文界面尚在翻译中,目前只有 GPUI Kit 组件自带的文字(如搜索框提示)会切换。
|
||||
- **外观 → 常规 → 应用外观**:深色、浅色、跟随系统,默认跟随系统;选「跟随系统」时随系统的深浅色切换。标题栏的主题按钮也会改写这一项。
|
||||
- **终端 → 字体配置**:字体(只列出本机安装的等宽字体,默认是系统的等宽字体,macOS 上为 Menlo)、字号(8–32 像素,默认 13)、行高(字号的 1.0–2.0 倍,默认 1.54,即原来的 20 像素行高)。改动立即作用于所有已打开的终端。下方的预览按终端的排法显示字母、数字、易混字符、符号、中文、制表符和一行命令。设置文件里的字体在本机没有安装时,使用默认字体。
|
||||
- **外部 CLI**:见下一节。
|
||||
- **关于**:「应用更新」一组:当前版本和检查更新、更新渠道、自动升级,见「下载与更新」。
|
||||
|
||||
## 外部 CLI
|
||||
|
||||
`shellrs` 命令让 Codex、Claude Code、OpenCode 等 AI Agent 在 ShellRS 里已保存的主机上执行远程命令、传输文件。命令本身不接触密码和私钥,也不读数据库:它把请求交给正在运行的 ShellRS,由 ShellRS 用保存的配置和钥匙串去连接。请求走本机的进程间通信:macOS、Linux 上是数据目录下的套接字 `cli.sock`,Windows 上是命名管道 `\\.\pipe\shellrs-cli-<数据目录的散列>`。
|
||||
|
||||
在 设置 → 外部 CLI 中:
|
||||
|
||||
- **启用外部 CLI**:默认关闭。关闭时 ShellRS 仍在监听,但拒绝所有请求,命令会提示去这里打开。开启后,本机当前用户下的任何程序都能这样使用已保存的主机。套接字只对当前用户可读写,连接时还会核对对方的用户身份;Windows 的管道同样只允许当前用户、只接受本机连接,命令在发请求之前还会确认管道属于当前用户(或管理员),防止别人抢注同名管道。
|
||||
- **CLI 二进制**:把 `shellrs` 放进 PATH。
|
||||
- macOS:链接到 `/usr/local/bin/shellrs`,目录不可写时弹系统授权框。
|
||||
- Linux:链接到 `~/.local/bin/shellrs`。
|
||||
- macOS、Linux 上「移除」只删除指向 shellrs 的链接,同名的其他文件不会被改动。
|
||||
- Windows:图形程序没有控制台,命令是单独的控制台程序 `shellrs-cli.exe`(与 `shellrs.exe` 放在一起)。「安装」把它复制成 `%LOCALAPPDATA%\ShellRS\bin\shellrs.exe`,并把这个文件夹加入当前用户的 PATH(不需要管理员),重新打开的终端和 Agent 才能找到它。ShellRS 升级后启动时会自动更新这份副本;更新不了时显示「更新」。副本正被使用时也能更新。「移除」删掉副本并把文件夹移出 PATH。
|
||||
- **Agent Skills**:把教 Agent 使用 `shellrs` 的 skill 写到通用目录(`~/.agents/skills`)或 Codex、Claude Code、OpenCode 各自的 skills 目录;内容和当前版本不同时显示「更新」。「复制 skills」把 skill 全文复制到剪贴板。
|
||||
|
||||
命令(`shellrs --help` 查看完整说明):
|
||||
|
||||
```sh
|
||||
shellrs list [--query <关键词>] [--json] # 列出主机;输出不是终端时自动用 JSON
|
||||
shellrs exec <ID> "<命令>" # 或 --stdin 从标准输入读命令
|
||||
shellrs upload <ID> <本地路径> <远程路径>
|
||||
shellrs download <ID> <远程路径> <本地路径>
|
||||
```
|
||||
|
||||
- `<ID>` 是 16 位的主机 ID,即 `shellrs list` 的 `id`,也就是主机右键「复制 ID」复制的内容。
|
||||
- `exec` 每次临时建立连接,执行完即断开。远程命令没有标准输入,退出码就是远程命令的退出码;被信号终止时是 128 加信号编号。
|
||||
- 上传和下载沿用 SFTP 标签的传输引擎(递归、`.filepart`、断线重连),目标路径按 scp 的规则:目标是已存在的目录就放进去并保留原名,否则目标就是副本自己的路径,其父目录必须存在。已存在的文件直接覆盖;新建的文件保留源文件的可执行权限。`~` 表示登录目录。
|
||||
- 失败时在标准错误输出 `shellrs: [错误码] 说明`,退出码 255。错误码有 `not_running`(ShellRS 未运行)、`not_enabled`(未启用外部 CLI)、`host_not_found`、`host_key_unknown`(还没在 ShellRS 里连过这台主机)、`host_key_changed`、`missing_credential`(没有保存密码或口令)、`connect_failed`、`transfer_failed`、`bad_request`、`version_mismatch`。CLI 不弹任何询问:陌生主机、缺少密码都直接失败,请先在 ShellRS 里连接一次。传输完成但有项目失败时退出码为 1。
|
||||
- 支持 macOS、Linux 和 Windows。Windows 的 PowerShell 里,多行或带引号的命令用 here-string 经 `--stdin` 传入(`@'…'@ | shellrs exec <ID> --stdin`);输出有中文时先执行 `$OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::new()`。命令文本会去掉开头的 BOM、把 CRLF 换成 LF,远程 shell 不会看到多余的回车符。
|
||||
|
||||
## 标签页
|
||||
|
||||
远程终端标签和 SFTP 标签都以主机系统徽章为图标(与主机列表相同)。右键标签可重命名(仅本次运行有效,留空恢复为主机的名称,用于区分同一台主机的多个连接)、在新标签页中连接、打开 SFTP、复制 IP 地址 / 主机名、重新连接、编辑主机,以及关闭当前、左侧、右侧、其他或全部标签。SFTP 标签的菜单是「重命名标签…」(留空恢复为「主机的名称 · SFTP」)、打开 SFTP、复制 IP 地址 / 主机名、重新连接加关闭命令,标签栏右侧和远程终端一样有「SFTP」和「重新连接」按钮;本地终端标签是「重新启动」加关闭命令。标签栏右侧「…」按钮的菜单与当前标签的右键菜单相同。双击任一标签的标题可隐藏或显示左侧的侧栏(同 ⌘B 和标题栏的按钮)。远程终端运行时和 SFTP 标签已连接时,标签栏右侧显示这条 SSH 连接的往返延迟,每 5 秒更新一次:低于 100 ms 为绿色,100–200 ms 为黄色,高于 200 ms 或超时为红色。SFTP 传输进行中也照常测量,读数会随传输占满带宽而升高。批量关闭时,正在上传的 SFTP 标签仍会先确认。
|
||||
|
||||
## 终端
|
||||
|
||||
右键终端内容区可复制、粘贴、查找、清屏;远程终端另有打开 SFTP、重新连接和断开连接。断开连接只断开这一个标签,标签保留,可随时重新连接,同一台主机的其他标签和 SFTP 不受影响。
|
||||
|
||||
- **查找**(⌘F,其他平台 Ctrl+Shift+F):在终端右上角打开查找条,按字面量搜索屏幕和回滚历史。查询全为小写时不区分大小写,含大写字母时区分。所有匹配高亮显示,当前项颜色更深,查找条显示「当前项/总数」。回车或 ⌘G(F3)跳到下一项(更新的输出),Shift+回车或 ⌘⇧G(Shift+F3)跳到上一项(更早的输出),到头后循环。Esc 关闭。打开时如有选中文字,会作为查询词。
|
||||
- **清屏**(⌘K,其他平台 Ctrl+Shift+K;焦点不在终端时 ⌘K 仍是聚焦侧栏的搜索框):清掉屏幕和回滚历史,只留下光标所在的提示符行,包括已输入但未回车的内容。清屏只在本地进行,不向服务器发送任何内容。vim、top 等全屏程序运行时不可用。
|
||||
|
||||
## SFTP
|
||||
|
||||
打开主机的「SFTP」标签即可浏览本地与远程目录;和终端一样,每次「打开 SFTP」都新开一个标签,同一台主机可以开多个,各自独立连接、各看各的目录。操作方式对标 WinSCP Commander。SFTP 使用独立 SSH 连接,仅请求 SFTP 子系统,不要求服务器提供 Shell;关闭终端不会中止传输。
|
||||
|
||||
### 工具栏
|
||||
|
||||
每一侧面板都有两行工具栏,下方是路径标签。面板太窄放不下时,按钮从右往左整个隐藏,不会换行(对应的命令在右键菜单和快捷键里仍然可用)。新开的 SFTP 标签本地、远程两侧各占一半;两侧和队列之间的分隔线拖到哪里,在这个标签关闭之前都会记住,切到别的标签再回来也不变:
|
||||
|
||||
- 第一行(导航):
|
||||
- 「目录列表」:列出从根到当前目录的每一级,本地另有主目录、桌面、文稿、下载,选中即前往。
|
||||
- 书签:点按钮打开「打开目录/书签」(见下);旁边的下拉箭头列出这台主机这一侧的书签,点击即前往,也可添加或移除当前路径。书签保存在本地数据库,删除主机时一并删除。
|
||||
- 上级目录、根目录、主目录、刷新、后退、前进。
|
||||
- 第二行(文件):
|
||||
- 远程是「下载…」,本地是「上传…」,下拉里还有「选择文件上传…」。
|
||||
- 删除、重命名…、属性…、新建(文件夹… / 文件…)。
|
||||
|
||||
### 路径标签
|
||||
|
||||
与 WinSCP 的路径标签相同:
|
||||
|
||||
- 显示当前目录的完整路径,例如 `/home/tester/`。
|
||||
- 每一级都可以点:悬停时从根到该级高亮,单击即前往。
|
||||
- 路径放不下时保留根目录和末尾几级,中间折成 `…`,点 `…` 前往被折叠的最后一级。
|
||||
- 单击当前目录、双击路径右侧空白处打开「打开目录/书签」。
|
||||
- 右键:前往、刷新、添加路径到书签、复制路径、打开目录/书签…。
|
||||
|
||||
### 打开目录/书签
|
||||
|
||||
与 WinSCP 的「打开目录」对话框相同,可以从路径标签、书签按钮、⌘O(其他平台 Ctrl+O;macOS 另有 ⌘⇧G)或路径标签的右键菜单打开:
|
||||
|
||||
- 「目录」预填当前目录并已选中,直接输入即可替换。`~` 表示主目录,相对路径从当前目录算起。本地侧另有「浏览…」。
|
||||
- 下方是这台主机这一侧的书签。与目录相同的书签处于选中状态;单击书签把它填入目录,双击直接打开。
|
||||
- 「添加」把目录加入书签,「删除」(或在列表中按 Delete)删除选中的书签并选中相邻的一个,「上移」「下移」调整顺序。
|
||||
- 书签的改动立即保存,按「取消」也不会撤销,与 WinSCP 相同。
|
||||
- 回车或「打开」前往目录;Esc 或「取消」关闭。
|
||||
|
||||
列表下方的状态栏平时显示项目数和已选数量;连接中和读取目录超过 0.3 秒时显示「正在连接 SFTP…」或「正在读取目录…」。读不了目录或连接断开时,原因用红字显示在窗口最左下角(平时显示「已连接 主机的名称」的位置);空闲时断网也会自己发现,不用等到刷新,时机和同一台主机的终端一样;重新连接用标签栏的「重新连接」按钮或标签菜单;断开时在远程列表里做任何操作(刷新、打开目录、删除、上传下载等)会弹出「SFTP 连接已断开」对话框,说明原因,并可直接「重新连接」。这些都不会让列表移动。
|
||||
|
||||
列与 WinSCP 相同:本地为名称、大小、类型、修改时间,远程为名称、大小、修改时间、权限、所有者。
|
||||
|
||||
- 大小默认以 KB 显示(`4,008,960 KB`,向上取整,只有空文件是 0 KB),与 WinSCP 相同。右键「大小」列标题可切换为「字节」(`4,105,175,040 B`)、「千字节(KB)」或「简短格式」(按大小用 B、KB、MB、GB,如 `3.8 GB`),对所有 SFTP 标签生效并保存在设置里。按大小排序始终按实际字节数。
|
||||
- 时间形如 `2026/4/22 12:44:53`。
|
||||
- 权限形如 `rwxr-xr-x`。
|
||||
- 所有者来自服务器的 `ls -l` 行。
|
||||
- 链接到目录的符号链接显示为带箭头的文件夹,可以双击进入。
|
||||
|
||||
### 选择与右键菜单
|
||||
|
||||
- 与 WinSCP 关闭「整行选择」时相同,「名称」格才是项目:鼠标移上去时只有名称格变灰,单击、双击、右键都作用在名称格上,选中时整个名称格高亮;行里的其他列和列表下方都算空白处。
|
||||
- 鼠标:单击名称格选中(其他选择取消),⌘ 单击(其他平台 Ctrl 单击)增减,Shift 单击选取范围,双击打开。单击空白处取消全部选择(按住 ⌘ 或 Shift 时不取消)。
|
||||
- 框选:在名称右边的空白、大小、类型、时间等列或列表下方按下拖动,画出选框,框住的行被选中;按住 ⌘ 或 Shift 开始拖动则加到已有选择上。拖到列表上下边缘时自动滚动。拖动文件名才是把文件拖到另一侧传输:按下未选中的文件名时只选中它(其他选择取消),拖走的就是它一个;按下已选中的文件名则拖走全部已选。拖动时只有到了另一侧列表上才显示「上传 / 下载 N 个项目」,在自己的列表或其他不能放下的地方,指针显示为禁止。
|
||||
- 键盘:↑↓、Home、End、PageUp、PageDown 移动,加 Shift 扩展;空格或 Insert 切换当前行;⌘A 全选;Tab 切到另一侧。
|
||||
- 右键未选中的名称格会先选中它;右键空白处取消选择并弹出空白处菜单。
|
||||
- 文件名上的菜单:打开、上传… / 下载…、删除、重命名…、属性…。
|
||||
- 空白处的菜单:前往(上级 / 根 / 主目录 / 后退 / 前进)、刷新、添加路径到书签、新建。
|
||||
|
||||
### 快捷键
|
||||
|
||||
| 命令 | 快捷键 |
|
||||
|---|---|
|
||||
| 上传 / 下载(按所在面板) | F5 |
|
||||
| 重命名 | F2 |
|
||||
| 新建文件夹 | F7 |
|
||||
| 删除 | F8、Delete;macOS 另有 ⌘⌫ |
|
||||
| 属性 | F9;macOS 另有 ⌘I |
|
||||
| 刷新 | ⌘R / Ctrl+R |
|
||||
| 上级目录 | Backspace;macOS 另有 ⌘↑ |
|
||||
| 根目录 | ⌘\ / Ctrl+\ |
|
||||
| 主目录 | macOS ⌘⇧H,其他平台 Ctrl+H |
|
||||
| 后退 / 前进 | ⌥← / ⌥→;macOS 另有 ⌘[ / ⌘] |
|
||||
| 添加路径到书签 | ⌘D / Ctrl+D |
|
||||
| 打开目录 | Enter;macOS 另有 ⌘↓ |
|
||||
| 打开目录/书签 | ⌘O / Ctrl+O;macOS 另有 ⌘⇧G |
|
||||
|
||||
### 文件操作
|
||||
|
||||
- 删除前会确认,并写明对象和后果:
|
||||
- 本地项目移到废纸篓。
|
||||
- 远程项目永久删除,目录连同其中全部内容。
|
||||
- 删除符号链接只删除链接本身。
|
||||
- 重命名和新建:
|
||||
- 按当前列表检查名称;不能为空,不能是 `.` 或 `..`,不能与已有项目同名。
|
||||
- 重命名不会覆盖已有项目。
|
||||
- 属性:
|
||||
- 显示位置、大小、修改时间,远程另有所有者和组。
|
||||
- 权限用 3×3 复选框和八进制数字编辑,两者同步。
|
||||
- 多选时,项目之间不一致的权限位保持原样,除非你改动它。
|
||||
- 含目录时可以递归应用;递归时默认「为目录添加执行权限(X)」,避免目录无法进入。
|
||||
- 符号链接不参与修改权限。
|
||||
|
||||
### 上传与下载
|
||||
|
||||
- 入口:
|
||||
- 上传:「上传…」、本地列表中的 F5、「选择文件上传…」,或按住本地项目的文件名拖到远程列表,或把 Finder 中的文件拖到远程列表。
|
||||
- 下载:「下载…」、远程列表中的 F5,或按住远程项目的文件名拖到本地列表。
|
||||
- 拖到目录行时放进该目录,拖到其余区域时放进当前目录。
|
||||
- 所有入口都显示确认框,可以修改目标目录;下载可以用「浏览…」选择本机目录。确认框写明数量和种类(如「上传 2 个文件到 …」),来源的所在目录只写一次,每个项目占一行,悬停可看完整路径。
|
||||
- 确认后来源、端点和目标即固定。每个 SFTP 标签同时运行一个批次(上传和下载共用),批内依次处理;传输进行中还可以继续上传或下载,新的批次排进传输队列,前一批结束后自动开始。其他 SFTP 标签、主机和终端可以继续操作。
|
||||
- 递归传输包含空目录。符号链接原样创建,不跟随。默认二进制传输,保留普通文件的修改时间。
|
||||
- 新建项使用目标一侧的默认权限,覆盖时保留目标原有的基本权限。不复制所有者、ACL 或扩展属性。
|
||||
- 同名目录合并。文件或链接冲突时询问覆盖、跳过或取消,可以应用于本批剩余冲突;目录与非目录冲突不会自动删除。
|
||||
- 出错时可以重试、跳过或取消,完成后的汇总分别列出成功、跳过和失败。
|
||||
|
||||
### 传输队列
|
||||
|
||||
与 WinSCP 的队列面板相同,有传输时出现在双栏下方,高度可拖动,清除全部已结束的批次后收起。
|
||||
|
||||
- 每一批一行:操作(上传 / 下载)、来源(一个项目时是它的路径,多个项目时是所在目录)、目标、已传输(单位与「大小」列相同)、时间(剩余时间)、速度(最近几秒)和进度。
|
||||
- 进行中的批次显示总进度条,下面一行是正在传的文件和它自己的进度条,对应 WinSCP 进度对话框里的两个进度条。
|
||||
- 进度列:等待中、正在扫描、百分比、等待回答、重连说明、已停止、已完成、部分失败(N 项)、已移出。
|
||||
- 点行首的箭头展开这一批的逐项结果(完成、已跳过、失败及原因),最新的在最上面。
|
||||
- 工具栏:继续(停止的批次)、停止(进行中的批次)、移出队列(选中的排队中或已结束的批次,或停止的批次)、丢弃续传进度、清除已完成。
|
||||
- 停止的批次留在队首,等你继续、丢弃续传进度或移出队列之后,后面的批次才会开始;移出队列不删除续传记录,下次传同样的文件仍会询问是否续传。
|
||||
- 队列只在本次运行中保留,关闭 SFTP 标签时一起清除;有未完成的批次时关闭标签会先确认。
|
||||
|
||||
## 断点续传和替换
|
||||
|
||||
文件先写入 `<目标文件名>.filepart`。写入请求采用有界流水线,收到每个写入确认并关闭成功后才发布正式文件;服务器支持时先调用 `fsync@openssh.com`。支持 `posix-rename@openssh.com` 的服务器使用原子替换;其他服务器先将旧文件移到 `.shellrs-<UUID>.backup`,再发布新文件,此兼容路径不保证原子性。发布响应丢失时根据受管理临时路径、阶段、类型和大小恢复,无法安全判断则保留恢复文件并报告。
|
||||
|
||||
续传记录保存在应用数据目录的 `upload-resume/`,采用版本化 JSON 和原子写入,不写入密码、口令或私钥,不改变数据库结构。记录用于绑定端点、主机指纹、来源、目标和安全替换阶段。续传方式与 WinSCP 相同:读取远端 `.filepart` 的当前大小,在本地文件中跳过相同长度并继续传输;使用者需确保来源仍是同一版本。上传过程不会为了校验而回读远端文件内容。
|
||||
|
||||
下载同样先写入本机的 `<目标文件名>.filepart`,完成后设置远程的修改时间、fsync,再在同一文件系统内重命名为正式文件(原子替换)。并发的读取请求可能乱序、短读返回,但临时文件严格按顺序写入,因为续传只信它的长度。下载续传记录保存在 `download-resume/`,绑定端点、主机指纹、远程来源及其大小与修改时间、本地目标;远程文件变化后不能续传,只能重新下载或跳过。
|
||||
|
||||
网络故障每批最多自动重连三次,间隔 1、3、10 秒。耗尽后使用「继续上传」或「继续下载」,手动恢复会重置次数。认证失败、主机密钥变化和主动取消需要用户处理。重连沿用原连接配置,编辑主机不会改变正在传输的目标。
|
||||
|
||||
取消、关闭标签、断开连接或退出时保留续传记录;关闭正在传输的标签会先确认。重启后不会自动继续,再次选择相同来源和目标时会询问续传。停止状态下可使用「丢弃续传进度」清理该批受管理的临时文件;存在无法判断的恢复备份时会保留文件并说明原因。成功发布的项目自动清理记录和备份。
|
||||
|
||||
文件名不能准确表示时会明确报错。当前 `russh-sftp 3.0.0` 的远端字符串解码会替换无效 UTF-8,浏览器因此拒绝包含替换字符 `U+FFFD` 的远端名称,避免访问错误路径。符号链接沿用 OpenSSH 的 SFTP v3 参数顺序。
|
||||
|
||||
**尚未实现:目录同步、前台传输进度对话框、队列排序、过滤、查找文件、目录树和 Dock 布局持久化。**
|
||||
|
||||
## 下载与更新
|
||||
|
||||
安装包在 [GitHub Releases](https://github.com/since2006/shell-rs/releases):
|
||||
|
||||
| 系统 | 首次安装 | 自动更新 |
|
||||
|---|---|---|
|
||||
| macOS(Apple Silicon 和 Intel 共用一个包) | DMG,把 ShellRS 拖进「应用程序」 | 支持 |
|
||||
| Windows x64 | 安装程序,按当前用户安装到 `%LOCALAPPDATA%\Programs\ShellRS`,不需要管理员 | 支持 |
|
||||
| Linux x64 | AppImage | 支持 |
|
||||
|
||||
ShellRS 启动 30 秒后检查一次有没有新版本,之后每 6 小时检查一次。有新版本就在后台下载并校验,完成后标题栏右上角出现更新图标。点开可以看更新内容,点「重启并安装」就换成新版本并重新打开。重启会断开已打开的连接、停止传输(续传进度保留)和端口转发,对话框里会写明具体有哪些。
|
||||
|
||||
「设置 › 关于」里的「应用更新」只有三行:
|
||||
|
||||
- **当前版本**:下面写着现在的状态(已是最新、正在下载、已下载等),右边是版本号和一个按钮:平时是「检查更新」,下载好之后变成「重启并安装…」。构建号和平台在版本号的悬停提示里。
|
||||
- **更新渠道**:稳定版适合日常使用;Beta 可以提前收到测试版本,稳定版发布时也会收到。切换后马上按新渠道检查(关掉自动升级时等你手动检查)。
|
||||
- **自动升级**:关掉之后只在手动检查时才联网,发现新版本时由你决定是否下载。
|
||||
|
||||
- **校验**:更新清单由 ShellRS 的发布密钥签名,安装包按清单里的大小和 SHA-256 核对,任何一步对不上都不会安装。macOS 上还会核对新版本的开发者签名。
|
||||
- **隐私**:检查更新只发送 ShellRS 的版本号、操作系统和 CPU 架构,不发送任何能识别这台电脑或用户的信息。
|
||||
- **不能自动更新的情况**:从 DMG 里直接运行、没有放进「应用程序」文件夹、对安装位置没有写权限、Linux 上不是用 AppImage 运行,或者是自己编译的开发构建。这时「关于」页会说明原因,有新版本时给出「前往下载页」。
|
||||
- **退回旧版本**:数据库在升级到新结构之前会备份成 `shellrs.db.v<旧版本>.bak`。旧版本打开新结构的数据库时会拒绝打开,以免把新版本保存的数据弄乱;需要退回时,把备份改名回 `shellrs.db`。
|
||||
@@ -0,0 +1,21 @@
|
||||
# 发布
|
||||
|
||||
[返回 README](../README.md)
|
||||
|
||||
在 `CHANGELOG.md` 里写好 `## [x.y.z]` 一节,把 `Cargo.toml` 的版本改成 `x.y.z`,提交后推送 tag `vx.y.z`。`.github/workflows/release.yml` 依次:
|
||||
|
||||
1. 核对 tag 与 `Cargo.toml` 一致,截出更新说明,确认代码里有发布公钥;
|
||||
2. 构建并打包:macOS 合成 universal,签名、公证、staple,产出升级用的 `.app.zip` 和首次安装用的 `.dmg`;Windows 用 Inno Setup 打安装程序;Linux 打 AppImage;
|
||||
3. 在 `release` 环境里人工批准后:给每个包签名,上传到 R2(`dl.shellrs.com/releases/<版本>/`),建 GitHub Release,**最后**才替换更新清单 `dl.shellrs.com/update/v1/<通道>.json` 并清 CDN 缓存。清单里每个包有两个下载地址:先 R2,下载失败时客户端接着试 GitHub Release(仓库是公开的,下载不用登录)。
|
||||
|
||||
版本号带 `-` 的(如 `0.3.0-beta.1`)只发到 beta 通道;正式版同时成为 beta 通道的最新版。在 Actions 里手动运行这个 workflow 只构建打包、不发布,用来演练。客户端只接受比自己新的版本,所以发错的版本撤不回来:把清单改回上一版能阻止更多人升级,修复要发新的补丁版。
|
||||
|
||||
一次性准备:
|
||||
|
||||
- **更新签名密钥**:`minisign -G -p shellrs-update.pub -s shellrs-update.key` 生成两对(一对日常用,一对离线保存备用),把两个公钥(`.pub` 文件的第二行)填进 `src/update/build_info.rs` 的 `TRUSTED_KEYS`。私钥和口令只放进 GitHub 的 `release` 环境,另外离线备份;丢了私钥,已发出去的 ShellRS 就再也收不到更新。换钥匙时先发一版带上新公钥,再换签名用的私钥。
|
||||
- **Cloudflare**:shellrs.com 的 DNS 托管在 Cloudflare;建 R2 bucket,绑定自定义域 `dl.shellrs.com`,关闭 `r2.dev` 地址;Cache Rules 让 `/releases/*` 和 `/update/*` 按源站的 Cache-Control 缓存(默认不缓存 `.json`、`.AppImage`);一个只能写这个 bucket 的 R2 API Token,一个只能清这个 zone 缓存的 API Token。
|
||||
- **Apple**:Developer ID Application 证书(导出 .p12)和 notarytool 用的 App Store Connect API Key。`packaging/macos/Info.plist.in` 里的 bundle id `com.shellrs.ShellRS` 和签名的 Team ID 一经发布就不能再改:钥匙串按它们授权,改了之后每条保存的密码都要重新允许。
|
||||
- **图标**:`assets/logo/` 里的 `shellrs.icns`(macOS)、`shellrs.png`(1024×1024,Linux,macOS 没有 icns 时也由它生成)和 `shellrs.ico`(Windows 安装程序)。
|
||||
- **GitHub secrets**:仓库级的 `APPLE_CERTIFICATE_P12`(base64)、`APPLE_CERTIFICATE_PASSWORD`、`APPLE_SIGNING_IDENTITY`、`APPLE_TEAM_ID`、`APPLE_API_KEY_P8`(base64)、`APPLE_API_KEY_ID`、`APPLE_API_ISSUER`;`release` 环境(设为需要审批)里的 `MINISIGN_SECRET_KEY`、`MINISIGN_PASSWORD`、`R2_ACCOUNT_ID`、`R2_ACCESS_KEY_ID`、`R2_SECRET_ACCESS_KEY`、`R2_BUCKET`、`CF_ZONE_ID`、`CF_API_TOKEN`。
|
||||
|
||||
清单格式见 `packaging/manifest.example.json`,由 `packaging/make-manifest.sh` 生成:一个文件里是清单原文和它的 minisign 签名,签名的 trusted comment 写明通道和版本。格式只增加字段、不改已有的;已发出去的 ShellRS 一直按 `/update/v1/` 这个地址检查。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 404 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 810 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 814 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 537 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 565 KiB |
Reference in new issue
Block a user