网络感知的 SSH 路由器。它能检测你当前激活的网络或 VPN,并自动为每次 SSH 连接选择正确的主机、端口、身份文件和跳板机——无需修改 ~/.ssh/config。
为每个逻辑主机定义一次,包含一个 default 配置文件以及可选的按网络覆盖项。在每次连接时,sshroute 会检测你当前所处的网络(VPN、公司局域网、WireGuard 对端等),解析出正确的 SSH 参数,然后交给真实的 /usr/bin/ssh 执行。
ssh myserver
→ sshroute 检测到:corp-vpn 已激活
→ 解析出:10.100.0.50:2222 通过 bastion.corp.internal
→ exec /usr/bin/ssh -p 2222 -i ~/.ssh/corp_key -J bastion.corp.internal 10.100.0.50
你的实验室很可能至少存在两种现实状态:要么在家通过局域网连接,要么外出通过 WireGuard 或其他 VPN 接入。问题在于 ~/.ssh/config 不知道你当前在哪种状态下——所以你最终不得不为同一台主机创建不同的别名(server-lan、server-vpn),或者使用一个只在一半情况下有效的跳板机,又或者干脆记住 IP 地址。
sshroute 通过在每次连接前检测当前网络来解决这个问题。当 WireGuard 接口启动且对端路由存在时,它直接连接隧道 IP;当你在局域网时,它使用本地地址;当两者都不可达时,它回退到公网主机名。一个别名,三种现实,无需手动切换。
此外,它还能透明地拦截 SSH——一旦设置好影子模式,git push、rsync、scp 都会自动通过它处理。无需包装器、无需 shell 函数、无需思考。
企业网络更复杂。你有公网、可能有的站点到站点 VPN、可能有的个人 VPN 分流隧道,在这些内部,根据目标环境的不同——开发、测试、生产,每个都有自己的堡垒机和密钥——你还需要不同的跳板机。在 ~/.ssh/config 中保持这一切的清晰管理,要么是一个一旦基础设施变化就会出错的庞然大物,要么是团队中每个人都以不同方式维护的脚本。
sshroute 允许你声明性地定义路由逻辑,保存在版本化的 YAML 文件中,并在整个团队中共享。同一份配置文件对所有人都有效——根据每台机器上激活的接口或路由自动检测正确的网络。密钥、端口、用户和跳板机自动解析,用户无需思考。
Teleport 和 Boundary 属于不同类别——它们在路由之上增加了访问控制、审计日志和基于证书的认证。如果你需要这些,请使用它们。sshroute 适用于你希望获得路由智能但不想承担运行中央认证服务器的运维开销的场景。
从 GitHub 发行版 下载最新版本。提供 Linux、macOS 和 Android 的 AMD64 和 ARM64 二进制文件。
go install github.com/thereisnotime/sshroute@latest
从 GitHub 发行版 下载 android_arm64 压缩包,解压后将二进制文件放入 ~/.local/bin:
mkdir -p ~/.local/bin
curl -Lo "$TMPDIR/sshroute.tar.gz" \
https://github.com/thereisnotime/sshroute/releases/latest/download/sshroute_android_arm64.tar.gz
tar -xzf "$TMPDIR/sshroute.tar.gz" -C ~/.local/bin sshroute
chmod +x ~/.local/bin/sshroute
如果尚未添加,在 ~/.bashrc 或 ~/.profile 中将 ~/.local/bin 加入 PATH:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
或者,使用 Termux 的 Go 从源码编译。由于官方 Go 工具链不提供 android/arm64 二进制文件,请设置 GOTOOLCHAIN=local 以使用 Termux 自带的版本:
GOTOOLCHAIN=local go install github.com/thereisnotime/sshroute@latest
安装后,由于 Termux 没有 /usr/bin/ssh,需要设置 SSH 二进制路径:
# ~/.config/sshroute/config.yaml
ssh_binary: /data/data/com.termux/files/usr/bin/ssh
或者通过环境变量:export SSHROUTE_SSH=$(which ssh)
docker run --rm -v ~/.config/sshroute:/root/.config/sshroute \
ghcr.io/thereisnotime/sshroute network
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute \
ghcr.io/thereisnotime/sshroute network
在启用了 SELinux 的系统(Fedora、RHEL 等)上,请在卷参数后添加 :Z:
podman run --rm -v ~/.config/sshroute:/root/.config/sshroute:Z \
ghcr.io/thereisnotime/sshroute network
将 sshroute 作为 ssh 安装在 $PATH 中更靠前的位置。所有 SSH 调用——来自终端、git、rsync、scp——都会自动被拦截。配置文件中未定义的主机将原封不动地传递到 /usr/bin/ssh。
mkdir -p ~/.local/bin
ln -s $(which sshroute) ~/.local/bin/ssh
# 如果尚未添加,请添加到 ~/.bashrc 或 ~/.zshrc:
export PATH="$HOME/.local/bin:$PATH"
# 添加一个带有默认配置的主机
sshroute add myserver --host myserver.example.com --user alice --key ~/.ssh/id_ed25519
# 添加一个 VPN 特定的覆盖项
sshroute add myserver --network vpn --host 10.8.0.50 --port 2222 --jump bastion.vpn
# 连接——网络会自动检测
sshroute connect myserver
# 预览解析后的命令而不实际执行
sshroute connect myserver --dry-run
# 查看当前激活的网络
sshroute network
这些标志适用于所有命令:
init创建一个带有注释示例的 starter 配置文件。如果文件已存在,则失败。
| 标志 | 默认值 | 描述 |
|---|---|---|
--force | false | 覆盖现有配置文件 |
connect <别名>检测激活的网络,为 <别名> 解析 SSH 参数,并执行真正的 SSH 二进制文件。别名后的任何额外参数都会原封不动地传递给 SSH。
使用 --reconnect 时,sshroute 会在断开连接(笔记本睡眠、WiFi 切换、网络间漫游)时保持 ssh 存活。由于每次重连都会重新检测网络,它会跟随你切换到不同的路由:例如,在局域网上睡眠,在热点上唤醒,将重新通过公网连接,而不是重试现已不可达的局域网地址。正常注销(退出码 0)或认证/远程命令失败会停止循环;只有真正的连接断开才会重连。重连时 ssh 作为子进程运行(与 --fallback 类似),因此 sshroute 会保持驻留直到会话结束;SIGINT/SIGTERM 会终止它。跨短暂断开的会话状态由你的多路复用器(tmux/zellij)负责;结合 --reconnect 与 -- tmux attach 或 -- zellij attach -c <名称>,可以在断开后直接回到你的会话:
sshroute connect myserver --reconnect --fallback -- zellij attach -c work
list列出所有配置的主机以及在当前网络下会使用的 SSH 参数。支持 -o table|json|yaml。
add <别名>添加主机或更新现有主机。省略的标志会保留当前值。多次运行并使用不同的 --network 值来构建按网络覆盖项。
remove <别名>从配置中移除 <别名> 的所有配置文件。
network打印当前检测到的网络名称(如果没有匹配项,则打印 default)。
network list列出所有已配置的网络及其优先级、检查规则和当前激活状态。支持 -o table|json|yaml。
network test <名称>对网络 <名称> 运行每项检查,并逐条打印通过/失败结果。可用于调试检测逻辑。
config打印配置文件的解析路径。
config edit在 $EDITOR(回退到 nano)中打开配置文件。如果文件和父目录不存在,则创建它们。
resolve <别名>打印当前网络下为 <别名> 使用的 SSH 参数。适用于调试和脚本编写。使用 --network <名称> 覆盖检测到的网络。支持 -o table|json|yaml。
| 标志 | 默认值 | 描述 |
|---|---|---|
--network | 自动检测 | 要解析针对的网络配置文件 |
copy <别名> <源> <目标>使用与 connect 相同的解析参数(密钥、端口、跳板机)通过 scp 向或从已配置的主机复制文件。远程路径使用 <别名>:<路径> 语法:
sshroute copy myserver ./local.txt myserver:/remote/path/
sshroute copy myserver myserver:/remote/file.txt ./local/
SSHROUTE_SCP 环境变量可以覆盖所使用的 scp 二进制文件。
version打印版本、Git 提交、构建日期和 Go 运行时信息。
update原地更新 sshroute 到最新的 GitHub 发行版。它会下载对应平台压缩包,验证其 sha256 与 checksums.txt 是否匹配,并且如果安装了 cosign,还会验证发行版的 cosign 签名,然后原子替换正在运行的二进制文件。
sshroute update # 下载、验证并安装最新发行版
sshroute update --check # 仅报告是否有更新的版本可用
sshroute update --force # 即使已是最新也重新安装最新版本
如果 sha256(或 cosign,如果存在)验证失败,更新会中止,二进制文件保持不变。此命令针对的是发行版二进制文件的安装;如果你是通过 go install 或包管理器安装的,请改用相应方式更新。
默认位置:~/.config/sshroute/config.yaml
networks:
corp-vpn:
priority: 10 # 数值越小越优先检查
checks:
- type: interface
match: wg0
- type: route
match: 10.100.0.0
office:
priority: 20
checks:
- type: ping
host: 192.168.1.1
timeout: 500ms
hosts:
myserver:
default: # 必需——当没有网络匹配时使用
host: myserver.example.com
port: 22
user: alice
key: ~/.ssh/id_ed25519
options: # 可选——作为 SSH -o Key=Value 标志传递
ConnectTimeout: "10"
ServerAliveInterval: "30"
corp-vpn:
host: 10.100.0.50
port: 2222
key: ~/.ssh/corp_key
jump: bastion.corp.internal
options:
ConnectTimeout: "5" # 仅在此网络下覆盖默认值
office:
host: 192.168.1.50
每个主机必须有一个 default 配置文件。网络配置文件中只需指定与默认值不同的字段——未设置的字段会从 default 继承。
options 键会从 default 合并到网络配置文件中——网络值会覆盖匹配的键,未重叠的键则继承。
网络按 priority 顺序评估(数值最小的优先)。数值相同时按字母顺序排序。第一个所有检查都通过的网络会被使用;如果没有网络匹配,则应用 default。
一个网络定义内的多个检查使用与逻辑——必须全部通过。
可用的配置文件模板在 examples/ 中:
深度指南在 docs/ 中:
所有列出的命令都支持多种输出格式:
sshroute list # 表格(默认)
sshroute list -o json # JSON——适用于脚本
sshroute list -o yaml # YAML
sshroute network list -o json
获取软件——从 Releases 下载预编译二进制文件,使用 go install github.com/thereisnotime/sshroute@latest 安装,或者从源码构建。
反馈和错误报告——在 GitHub Issues 上打开 issue。对于意外行为请使用 bug 报告模板,对于想法请使用功能请求模板。
贡献——请参阅 CONTRIBUTING.md 了解如何设置项目、运行测试以及提交 pull request。安全漏洞应通过 GitHub 安全公告 私下报告。
git clone [email protected]:thereisnotime/sshroute.git
cd sshroute
just build # 输出到 bin/sshroute
just build-all # 交叉编译 linux/darwin × amd64/arm64
just test # 使用竞态检测器运行测试
just install # 使用版本 ldflags 注入执行 go install
|
|
| 特性 | ~/.ssh/config | 仅 WireGuard | Teleport / Boundary | sshroute |
|---|
| 检测当前网络 | ❌ | ❌ | ❌ | ✅ |
| 自动选择最佳路径 | ❌ | ❌ | ❌ | ✅ |
| 连接失败时回退 | ❌ | ❌ | ✅ | ✅ |
| 断开后自动重连并重新路由 | ❌ | ⚠️ 隧道漫游 | ⚠️ 通过固定代理 | ✅ |
| 每个主机一个命令,任何位置 | ❌ | ⚠️ VPN 必须启用 | ✅ | ✅ |
| 10 个主机 × 4 种路径的配置大小 | 📄 ~600 行 | 📄 ~600 行 + VPN 配置 | 📄 服务端配置 | 📄 ~60 行 |
| 移动设备漫游 | ⚠️ 手动别名 | ⚠️ 需要 VPN | ✅ | ✅ |
| 跳板机自动链式连接 | ⚠️ 手动 -J | ➖ 不适用 | ✅ | ✅ |
| 适用于 scp / rsync / git / Ansible | ✅ | ✅ | ⚠️ 部分 | ✅ |
| 目标端无服务端安装 | ✅ | ❌ | ❌ | ✅ |
| 无需认证服务器或守护进程 | ✅ | ❌ | ❌ | ✅ |
| 无客户端代理 | ✅ | ❌ | ❌ | ✅ |
| 开源,完全自托管 | ✅ | ✅ | ⚠️ 开放核心 | ✅ |
| 标志 | 环境变量 | 默认值 | 描述 |
|---|
--config | SSHROUTE_CONFIG | ~/.config/sshroute/config.yaml | 配置文件路径 |
-o, --output | table | 输出格式:table、json、yaml | |
-v, --verbose | SSHROUTE_VERBOSE=1 | false | 将调试日志输出到 stderr |
--dry-run | false | 打印解析后的 SSH 命令而不执行 |
| 标志 | 默认值 | 描述 |
|---|
--fallback | false | 按优先级顺序尝试每个配置文件,仅当连接失败(退出码 255)时才重试下一个 |
--reconnect | false | 监视连接,当断开时自动重连,每次重新检测激活的网络并重新解析路由 |
--reconnect-delay | 2s | 设置 --reconnect 时重连尝试之间的等待时间 |
| 标志 | 默认值 | 描述 |
|---|
--host | 主机名或 IP 地址 | |
--port | 22 | SSH 端口 |
--user | SSH 用户名 | |
--key | 身份文件路径(支持 ~) | |
--jump | 跳板机——作为 -J 传递给 SSH | |
--network | default | 要将参数写入的网络配置文件 |
| 字段 | 类型 | 描述 |
|---|
host | string | 主机名或 IP 地址 |
port | int | SSH 端口(默认:22) |
user | string | SSH 用户 |
key | string | 身份文件路径(~ 会被展开) |
jump | string | 跳板机别名或 user@host |
options | map | 任意 SSH -o Key=Value 标志(例如 ConnectTimeout、StrictHostKeyChecking) |
comment | string | 在 sshroute list 中显示的描述 |
tags | list | 用于通过 sshroute list --tag 过滤的标签 |
| 检查类型 | 通过条件 | 必需字段 |
|---|
route | 子网/IP 出现在内核路由表中 | match |
interface | 命名接口存在并且操作状态为 up | match |
ping | 主机在超时内响应 ICMP 回显 | host、timeout(可选,默认 2 秒) |
exec | Shell 命令以退出码 0 退出 | command |
| 文件 | 用例 |
|---|
basic.yaml | 单主机,VPN 与公网回退 |
multi-network.yaml | 办公室局域网、公司 VPN、远程 VPN、公网 |
wireguard-backconnect.yaml | 反向连接到你自己的 WireGuard 对端 |
jump-hosts.yaml | 每个网络使用不同的堡垒机 |
multi-zone-roaming.yaml | 含 WireGuard 网关和漫游移动设备的多区域家庭实验室 |
| 指南 | 描述 |
|---|
| 家庭实验室设置 | 带 WireGuard、跳板机、NAS、k3s 节点的多区域家庭实验室 |
| 多区域漫游 | 多个局域网、WireGuard 网关、在不同网络间漫游的移动设备 |
| 企业/多环境 | 开发/测试/生产环境,每个环境有自己的堡垒机和 VPN 检测 |
| 影子模式 | 透明 SSH 替代——git、rsync、scp、Ansible |
| Shell 自动补全 | bash、zsh、fish 的动态别名自动补全 |
| 脚本与自动化 | 在脚本和 CI 管道中使用 resolve 和 copy |