p-arndt/hop

GitHub: p-arndt/hop

hop 是一个终端内的多主机 SSH 会话管理 TUI,让用户在单个界面中无缝切换远程 shell、SFTP 浏览和远程文件编辑,无需反复认证或开新窗口。

Stars: 9 | Forks: 0

hop Logo

hop

**在服务器之间跳转,无需离开你的终端。** 按下一个键,你就进入了 shell。再按一个,你就在浏览它的文件。再按一个,你就 *在那台机器上*编辑文件,在一个标签页里,与其他所有页面并排。然后跳到下一个 主机,你留下的一切都原封不动地待在原地。 *没有新窗口。没有重新认证。无需费力寻找你之前打开的那个 shell。* [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/p-arndt/hop/actions/workflows/ci.yml) [![Release](https://github.com/p-arndt/hop/actions/workflows/release.yml/badge.svg)](https://github.com/p-arndt/hop/actions/workflows/release.yml) [![Go](https://img.shields.io/badge/go-1.26-00ADD8?logo=go&logoColor=white)](go.mod) [![Platforms](https://img.shields.io/badge/platforms-windows%20%7C%20macOS%20%7C%20linux-informational)](#-install) [![Built with Bubble Tea](https://img.shields.io/badge/built%20with-Bubble%20Tea-ff69b4)](https://github.com/charmbracelet/bubbletea) [安装](#-install) · [快速开始](#-quick-start) · [按键](#-keys) · [开发](#-development) · [路线图](#-roadmap)
## 🎬 运行界面

hop: filter the fleet, open a shell, open a second one, browse the files, edit one on the server

按键在按下时会显示在右下角。`●` 已连接 · `◐` 连接中 · `○` 空闲 · `×2` 打开了两个 shell · `▤` SFTP 浏览器已打开 顶部始终会告诉你**你的按键正发往何处**。这是嵌入他人程序的 TUI 最容易让人晕头转向的一点, 因此它占据了永久的屏幕空间。
The host list with the details card for the host under the cursor
The host list. Status dot, group, and what enter would do to the host under the cursor.
A live remote shell in a hop pane
A shell. A real terminal in the pane; the footer shows the three ways back out.
The SFTP file browser
The SFTP browser. f, over the connection that's already open.
A file open in a remote editor tab inside hop
A remote editor tab. enter on a file runs the editor on the server, so :w writes the real file.
更多截图:一个连接上的两个 shell、设置和按键说明卡
Two shells on one host, shown as a tab strip
Two shells, one connection. S (or alt+0) opens another channel, no second handshake.
The settings popover
Settings. ,. The accent is a swatch strip that recolours hop as you walk it.
The keys card listing every binding
Every key hop binds. ?. It lists the keyboard you actually have, with vim motions included only if you turned them on.
录制中的每个主机、文件和命令都是虚构的。它运行在一个一次性的 SSH 服务器 (`tools/demoserver`)上,拥有自己的 HOME 目录,因此 `just demo` 可以在任何机器上重新录制它,而不会暴露任何信息。详见 [demo/hop.tape](demo/hop.tape)。 ## ✨ 功能特性 | | | | --- | --- | | 🖥️ **内嵌 SSH shell** | 窗格中的真实终端:一个纯 Go 编写的 SSH 客户端(`x/crypto/ssh`)为一个真实的 VT 模拟器(`x/vt`)提供数据。支持 Agent *或* 私钥认证、调整大小、光标等一切功能。 | | 🔑 **2FA 和密码** | 当主机要求验证码(`pam_google_authenticator`)或密码时,会在其发出请求的瞬间弹出一张卡片。拨号会在握手中等待,而不是重启——一次性验证码只能使用一次。不存储任何信息;每个主机只提示一次,因为每个额外的 shell 都共用同一个连接。 | | 🗂️ **每个主机多个 shell** | `S`(或 `alt+0`)在你已连接的主机上打开另一个 shell。它是一个*新通道*,因此无需新握手也无需二次认证。顶部的标签页可通过 `alt+1…9` 切换。 | | 📁 **SFTP 文件浏览器** | `f` 可通过你现有的连接浏览远程文件系统。用 `d` 下载,用 `o` 在本地打开。 | | ✎ **远程编辑器标签页** | 在文件上按 `enter` 会**在服务器上**通过第二个通道运行 `$EDITOR`,并将其呈现在一个标签页中。无需下载,无需复制,`:w` 会直接写入真实文件。 | | 📂 **在你所在的位置使用 VS Code** | 在 shell 中按 `ctrl+o` `ctrl+o`(或在列表中按 `o`)会根据 shell 当前所在目录打开 VS Code Remote,而不是你登录时的目录。hop 通过 OSC 7 追踪它,自动将 prompt hook 安装到 bash/zsh 中,并清除它输入的那一行,让窗格看起来毫无痕迹。 | | ⇅ **回滚历史** | `shift+↑` 会将 shell 暂停到其历史记录中,并支持类似 vim 的翻页。当全屏程序占据屏幕时,它会自动礼貌地拒绝此操作。 | | 🔁 **掉线后重连** | 断开的连接(挂起的笔记本电脑、断掉的 VPN、重启的机器)会被*察觉到*——通过 keepalive 探测而非死寂——并且窗格会保留主机绘制的最后一个画面,而不是悄悄卡死。`r` 会重新拨号,并恢复会话的原貌:相同的 shell 标签页,以及停留在原目录的浏览器。 | | 🔐 **诚实的主机密钥** | 未知的密钥会**中止拨号**并显示指纹卡片。`y` 信任它并追加;`n` 不予信任。*不匹配*始终是严重的错误。 | | 📥 **SSH 配置导入** | `i`(或 `hop import`)会更新来自 `~/.ssh/config` 的所有主机。这是一次*同步*,而不是一次性行为:重新导入会刷新,而你手动添加的主机会被保留。 | | 🔎 **模糊查找** | `/` 会在你输入时进行过滤,并高亮匹配的字符,让意外的命中结果一目了然。 | | 🖱️ **鼠标** | 支持在列表、浏览器和窗格中滚动和点击:滚轮可滚动 shell 的历史记录,双击可建立连接,点击标签页可切换到该页面。每一个手势都映射为你已有的按键,而请求使用鼠标的远程程序(vim 的 `set mouse=a`、`htop`)会原样获取指针操作。`,` → *Mouse* → 关闭,可将点击拖拽的选择权交还给终端。 | | ⚙️ **实时设置** | `,` 会打开一个弹窗,用于设置编辑器、下载目录、强调色(你能*看到*的色板条,而不是需要查找的数字)、打开方式以及 vim 按键。即刻生效。 | | 🎯 **频率+最近使用排序** | 你实际使用的主机会浮到列表顶部。 | | 🪟 **跨平台** | 适用于 Windows、macOS 和 Linux(amd64 + arm64)的静态、无依赖二进制文件。没有 cgo、libc 或运行时。 | | ⌨️ **可选的 vim 按键** | 默认关闭,因为 `h`/`l` 代表“退出”和“进入”主机,对于没有此需求的人来说会感到意外。只需拨动一个开关,相应的动作就会立刻出现在各个地方。 | ## 📦 安装说明 ### 下载二进制文件 从 [最新发布版本](https://github.com/p-arndt/hop/releases/latest) 获取适用于你平台的压缩包, 并将 `hop` 放到你的 `PATH` 路径中。 ``` # macOS / Linux tar -xzf hop_*_darwin_arm64.tar.gz sudo mv hop /usr/local/bin/ ``` ``` # Windows Expand-Archive hop_*_windows_amd64.zip -DestinationPath . # 然后将 hop.exe 移动到你的 PATH 上 ``` 每个版本都附带一个 `hop__checksums.txt`,因此你可以使用 `sha256sum -c` 进行校验。 ### 保持最新 ``` hop check-update # is there a newer release? hop self-update # download it, verify its checksum, swap this binary ``` `self-update` 会从最新的 GitHub 发布版本中获取*你所用*平台的压缩包, 根据该版本的 `checksums.txt` 校验其 SHA-256,并原子性地替换正在运行的二进制文件。 在 Windows 上,旧的 `hop.exe` 会被重命名移至一旁,并在下次启动 hop 时被清理。 源码构建(`version = dev`)会被拒绝:因为没有东西可以用来与它们比对。 hop 还会在后台每天检查一次,并在页脚和 CLI 中提示有新版本。 设置 `HOP_NO_UPDATE_CHECK=1` 可关闭此功能;上述两条命令依然有效。 ### 从源码构建 需要 [Go 1.26+](https://go.dev/dl/)(以及可选的 [`just`](https://github.com/casey/just) ≥ 1.39)。 ``` git clone https://github.com/p-arndt/hop.git && cd hop just build # -> ./hop (or: go build -o hop .) just build-release # stripped + version-stamped ``` ## 🚀 快速开始 ``` hop # launch the TUI ``` 首次运行且没有任何主机时,hop 会主动为你导入 `~/.ssh/config`:按一下 `enter`,你的列表就会被填满。当你已经在 shell 中时,也可以使用 CLI: ``` hop import # sync hosts from ~/.ssh/config hop import path/to/cfg # …or from somewhere else hop add web1 deploy@10.0.0.4:2222 hop list # alias user@host:port hop check-update # is a newer release out? hop self-update # upgrade this binary in place hop version ``` 然后,在 TUI 中:`↑`/`↓` 移动,`enter` 连接,`ctrl+o` 返回。 这就是整个操作模型。 ### 文件存放位置 | | 路径 | | --- | --- | | 主机数据库 | `/hop/hop.db` (SQLite) | | 设置 | `/hop/config.json` (纯 JSON,可手动编辑) | | 更新检查缓存 | `/hop/update-check.json` (最后一次检查 + 看到的最新版本) | | 已知主机 | 你常规的 `~/.ssh/known_hosts` | `` 在 Windows 上是 `%AppData%\hop\`,在 macOS 上是 `~/Library/Application Support/hop/`,在 Linux 上是 `~/.config/hop/`。 配置文件缺失或格式错误时,hop 会使用默认配置启动,而不会拒绝运行。 ## ⌨️ 按键 hop 有三种模式,页脚始终显示你当前所处模式的快捷键。 每种模式都可以通过 **`ctrl+o`** 返回到主机列表。 | 模式 | 何时处于此模式 | 谁在接收你的按键 | | --- | --- | --- | | **导航** | 主机列表获得焦点时(默认) | hop | | **浏览** | 你用 `f` 打开了 SFTP 浏览器 | hop | | **终端** | 你用 `enter` 或 `s` 进行了连接 | **远程 shell** |
主机列表 | 按键 | 动作 | | --- | --- | | `↑` `↓` / `pgup` `pgdn` | 移动 / 翻页 | | `enter` `→` | 连接,或聚焦已打开的 shell | | `s` / `S` | 聚焦此主机的会话 / 在其上打开**另一个** shell | | `f` | SFTP 浏览器 | | `o` | 在此主机 shell 所在的目录中打开 VS Code Remote | | `d` | 断开连接 | | `r` | 重新连接掉线的会话 | | `a` `e` `x` | 添加 / 编辑 / 删除主机 | | `i` | 从 OpenSSH 配置中导入 | | `/` | 模糊过滤 | | `,` `?` | 设置 / 按键说明卡 | | `ctrl+b` | 隐藏 / 显示侧边栏 | | `q` `ctrl+c` | 退出 |
在 shell 内部 | 按键 | 动作 | | --- | --- | | `ctrl+o` | 返回 hop | | `esc` `esc` | 返回 hop(在 400 毫秒内) | | `alt+0` | 在此主机上打开另一个 shell | | `alt+←` `alt+→` / `alt+1…9` | 切换 shell | | `ctrl+o` `ctrl+o` | 在 VS Code Remote 中打开**当前目录**(先退出窗格,然后打开) | | `shift+↑` / `shift+pgup` | 进入回滚历史 | | `ctrl+b` | 隐藏侧边栏 —— shell 将占据整个窗口 | | *其他所有按键* | 发送到远程 shell | `ctrl+o` `ctrl+o` 会在 shell 当前所在目录打开 VS Code Remote —— `cd` 到某处,`ctrl+o` 退出窗格,再按一次 `ctrl+o`。(之所以使用组合键,是因为 远程 shell 接管了所有普通按键,而控制字节是所有终端都无需配置即可发送的唯一方式。)hop 通过终端用于此目的的转义序列 **OSC 7** 获知该目录,并为 **bash** 和 **zsh** 自动安装 prompt hook:在首次出现提示符时输入一行命令,然后从窗格中抹除, 让会话看起来毫无痕迹。如果 shell 已经在发送 OSC 7,或者全屏程序(vim、tmux、`ForceCommand`)占据了屏幕,则不会向其输入任何内容;如果除了 hop 自己的命令行之外还有其他内容,也会拒绝执行擦除操作。在 hop 无法获知目录的任何地方,该按键仍会在主机的默认目录中打开,并予以提示。详见 [KEYBINDINGS.md](KEYBINDINGS.md)。
文件浏览器和编辑器标签页 | 按键 | 动作 | | --- | --- | | `enter` `→` | 进入目录,或在远程编辑器标签页中打开文件 | | `o` / `d` | 在本地桌面应用中打开 / 下载 | | `←` `backspace` | 返回上一级目录 | | `r` | 刷新 | | `ctrl+b` | / 显示侧边栏 | | `alt+←` `alt+→` / `alt+1…9` | 切换编辑器标签页 | | `:q` | 关闭编辑器标签页 | | `ctrl+o` | 返回上一级 |
当连接断开时 | 按键 | 动作 | | --- | --- | | `r` `enter` | 重新连接并重新打开之前打开的内容 | | `d` `x` | 丢弃会话 —— 主机恢复为空闲状态 | | `ctrl+o` `esc` `q` | 返回主机列表,窗格保留在屏幕上 | 窗格会在横幅下保留主机绘制的最后一个画面,横幅说明了发生的情况。 没有内容会被转发到远端,因为远端已经不存在了。
📖 **[KEYBINDINGS.md](KEYBINDINGS.md)** 是完整的参考文档,包括 vim 移动指令、设置弹窗,以及*为什么*保留每个特定组合键的原因。 ## 🛠️ 开发说明 ``` just # list recipes just run list # go run . list just build # dev binary just test # go test ./... just test-e2e # + the Docker 2FA end-to-end tests (needs Docker) just vet just fmt # gofmt -w . just ci # fmt-check + vet + test (what CI runs) just demo # re-record assets/demo.gif + the stills (needs vhs) ``` `justfile` 刻意保持通用性:指令体是可在 `sh` 和 PowerShell 下运行的纯命令,而需要真正 shell 逻辑的那两条命令(`fmt-check`、`clean`)通过 `[unix]` / `[windows]` 属性进行了拆分。 **演示。** `just demo` 录制了上面的 GIF 和截图。`scripts/demo.mjs` 构建 hop,将 `HOME` 指向一个带有预设主机数据库的一次性目录,并启动 `tools/demoserver`,这是一个仅限本地回环的 SSH 服务器,它虚构了屏幕上的所有 内容:一个带有预设命令输出表格的假 shell、一个通过 SFTP 挂载的内存文件系统,以及一个虚假的 vi。角落里的按键叠加显示是 hop 自身的功能,仅在 `-tags hopdemo`(`internal/tui/keycast.go`)下编译,因此发布的二进制文件不会包含它。 **测试。** 无头测试使用真实的按键驱动真实的 Bubble Tea 模型,针对进程内的 Go SSH/SFTP 服务器和临时文件存储进行测试。详见 `internal/tui/hostmgmt_test.go`、`TestEmbeddedRoundTrip`、`TestSFTPRoundTrip`。 **2FA 端到端测试。** 进程内的 Go SSH 服务器会按照你的设定进行响应,但这无法证明 hop 能否真正登录到开启了双因素认证的机器上。因此 `internal/dockerenv` 会启动一个运行着真实 `openssh-server` 和真实的 `libpam-google-authenticator` 的 Ubuntu 容器,按照官方指南进行配置,并在四种情况下进行监听:仅验证码、强化的 `publickey,keyboard-interactive`、密码后加验证码,以及将这两种方式作为备选提供。这些测试像手机一样计算 TOTP 验证码并进行登录 —— `internal/sshx` 通过 SSH 引擎登录,`internal/tui` 通过在实际的卡片中输入进行测试。阴性对照(错误的验证码、十分钟前的验证码)也是测试套件的一部分,因为如果容器接受任何输入,就会让这里的所有其他测试通过,却什么也证明不了。使用 `just test-e2e` 选择启用;如果没有 `HOP_DOCKER_E2E=1`,它们将被跳过,因此 CI 和没有 Docker 的笔记本电脑不受影响。 CI 会在 Windows / Linux / macOS 矩阵上运行审查 + 测试 + 构建,因为 agent 传输和本地打开处理器是针对特定平台的:单操作系统的运行无法判断其他系统是否还能编译。 **发布。** ``` just release # patch bump: stamps VERSION, commits, tags, pushes just release minor # or major, or an explicit 1.0.0 ``` 标签的推送会触发发布工作流:它会受到三操作系统测试矩阵的检验, 然后从一台 Linux runner 交叉编译所有六个目标(windows/linux/darwin × amd64/arm64),并附带校验和及由 git-cliff 生成的更新日志。Windows 会获得一个 `.zip` 压缩包,其他所有系统则是 `.tar.gz`,以保留执行位。 ## 🗺️ 路线图 接下来: - 🔌 **隧道 / 端口转发**:通过 hop 已建立的连接,为每个主机提供本地和远程转发 - 💓 **健康面板**:针对每个主机的可达性、延迟、运行时间和磁盘状态 - ⬆️ 浏览器中的**上传与文件操作**(`u`、`x`、`R`、`m`),并带有异步传输进度 - 🏷️ 列表中的**分组与标签**:按组划分、按标签过滤、固定收藏夹 - 📐 窄屏终端布局,支持向窗格中复制/粘贴 包含每个项目更多细节的最新动态版本请参见 [TODO.md](TODO.md)。 ## 🙏 构建技术 [Bubble Tea](https://github.com/charmbracelet/bubbletea) · [Lip Gloss](https://github.com/charmbracelet/lipgloss) · [x/vt](https://github.com/charmbracelet/x) · [x/crypto/ssh](https://pkg.go.dev/golang.org/x/crypto/ssh) · [pkg/sftp](https://github.com/pkg/sftp) · [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) · [sahilm/fuzzy](https://github.com/sahilm/fuzzy) · [skeema/knownhosts](https://github.com/skeema/knownhosts)
专为拥有太多服务器却没有足够终端的人打造。
标签:EVTX分析, Go, Ruby工具, SSH客户端, 日志审计, 服务器管理, 终端工具, 运维工具, 远程连接