中文 | English
TunnelCapsule 是一个面向日常使用场景的 macOS 菜单栏 SSH 本地端口转发工具。
它不是单纯把 ssh -L 命令做成一个壳,而是把多隧道管理、菜单栏状态、Keychain 凭据保存、IPv4/IPv6 地址处理、自动重连、日志排障、代理开关和流量统计这些真实使用时会遇到的问题,整理成一个可以长期放在菜单栏里的原生应用。
你可以把它理解为:
- 底层负责 SSH 连接、端口转发和连接生命周期管理
- 上层负责规则管理、菜单栏交互、配置持久化、日志与错误翻译
- 最终提供一个更适合 macOS 日常驻留使用的隧道管理工具
项目核心目标:
- 提供一个真正可长期使用的菜单栏 SSH 隧道工具,而不是一次性的命令行包装
- 优先处理 IPv4 / IPv6、本地绑定地址、密钥认证、自动重连这些稳定性问题
- 用原生 macOS UI 降低多条隧道管理和日常切换的使用成本
- 给开发和排障场景提供足够详细的日志与错误信息
- 纯菜单栏应用,默认无 Dock 图标
- 内置中文 / English 双语界面
- 支持同时管理多条 SSH 本地端口转发规则
- 支持密码认证和私钥认证,敏感信息写入 Keychain
- 支持 IPv4 / IPv6 SSH 地址与本地绑定地址
- 支持常用本地绑定地址快速选择,也支持自定义输入
SSH 服务器与远程目标主机保持一致,只需要切换远程端口- 支持每条规则单独启用 / 关闭本地代理
- 支持自动重连、KeepAlive、连接超时配置
- 支持累计流量统计和手动清零
- 支持配置导入 / 导出
- 隧道编辑窗口支持
Test Connection - 内置详细错误翻译,便于识别端口错误、认证失败、连接被拒绝等问题
- 配置窗口内置日志面板,便于开发者调试
- 状态栏图标具备连接状态动效与颜色区分
- 对部分原生 SSH 协商失败的旧服务端自动降级到系统
/usr/bin/ssh
- macOS 14+
- Xcode Command Line Tools
- Swift 5.9+
安装命令行工具:
xcode-select --installgit clone git@github.com:liigoo/ssh-tunnel-bar.git
cd ssh-tunnel-bar
swift build
make run# 调试构建
swift build
# 或
make build
# 运行
make run
# 构建 Release .app
make release
# 安装到 /Applications
make install
# 生成 DMG
make dmg
# 清理构建产物
make clean如果你要分发 .app,可以使用 Makefile 中的签名和公证目标:
make sign IDENTITY='Developer ID Application: Your Name (TEAMID)'
make notarize \
IDENTITY='Developer ID Application: Your Name (TEAMID)' \
APPLE_ID='you@example.com' \
TEAM_ID='TEAMID' \
APP_PASSWORD='xxxx-xxxx-xxxx-xxxx'- 启动应用后,从菜单栏图标打开主菜单。
- 选择
Add New Tunnel...或编辑已有隧道。 - 填写 SSH 服务器、用户名、认证方式和本地监听信息。
- 配置远程目标端口。
- 如有需要,先点击
Test Connection验证 SSH 连接。 - 保存后,可直接从菜单栏连接、断开、编辑、复制或删除隧道。
- 在配置窗口左下角切到
日志,可以查看滚动日志与底层错误详情。
配置文件默认保存在:
~/Library/Application Support/TunnelCapsule/tunnels.json
日志文件默认保存在:
~/Library/Application Support/TunnelCapsule/Logs/tunnelcapsule.log
敏感信息存储方式:
- SSH 密码保存在登录 Keychain
- 私钥口令保存在登录 Keychain
- 其他隧道参数以 JSON 持久化保存
- SSH 服务器地址支持
example.com、192.168.1.10、2001:db8::1、[2001:db8::1] - 本地绑定地址支持
127.0.0.1、0.0.0.0、::1、::以及自定义 IPv4 / IPv6 地址 - 远程目标主机会自动跟随 SSH 服务器地址,只允许修改远程端口
- 如果某些旧 SSH 服务端和原生 Swift SSH 栈协商失败,应用会自动尝试系统
ssh降级路径 - 文本输入框支持标准 macOS 编辑快捷键,包括
Cmd+V
Sources/TunnelCapsule/
├── App/
│ ├── AppDelegate.swift
│ └── StatusBarController.swift
├── Config/
│ ├── AppLog.swift
│ ├── AppPreferences.swift
│ ├── ConfigStore.swift
│ ├── ErrorTranslator.swift
│ └── KeychainHelper.swift
├── Tunnel/
│ ├── ExternalSSHTunnel.swift
│ ├── LocalPortForwarder.swift
│ ├── SSHConnection.swift
│ ├── TunnelConfiguration.swift
│ ├── TunnelManager.swift
│ └── TunnelState.swift
└── Views/
├── PreferencesView.swift
└── TunnelConfigWindowController.swift
目录职责:
App/:应用入口、菜单栏图标和菜单交互Config/:配置读写、日志、Keychain、错误翻译、全局偏好Tunnel/:SSH 连接、端口转发、状态管理、系统ssh降级兼容Views/:隧道编辑窗口、日志面板、偏好设置界面Resources/:图标、Info.plist、本地化文案、隐私声明
- Citadel - Swift SSH 客户端库
- SwiftNIO - 通过 Citadel 间接使用的网络基础设施
- macOS Keychain / UserNotifications / SwiftUI / AppKit - 系统能力支持
- 修复:点击日志按钮后界面卡死的问题。根因是日志视图每秒在主线程同步读取整个日志文件(2MB+),改为从文件尾部反向读取最后 500 行,UI 恢复流畅
- 修复:浏览器访问本地代理端口时崩溃的问题(NIOSSH
SSHChildChannel.handleOutboundChannelWindowAdjust中EXC_BAD_INSTRUCTION)。根因是 TCP 连接断开后SSHToTCPForwarder继续向已关闭的 channel 写入数据,导致 NIOSSH 内部窗口管理状态被破坏
- 添加 Apache 2.0 许可证
- 重写双语 README 结构
- 简化 App 图标设计
- 优化状态栏图标形态
- 添加自定义 App 和状态栏图标
- 修复隧道编辑器中粘贴快捷键问题
- 添加对 Dropbear 服务器的系统 SSH 降级支持
- 完善隧道诊断与编辑器功能
感谢 Swift、SwiftNIO、Citadel 以及 macOS 原生框架生态提供的基础能力。
这个项目的重点不是”再包一层 SSH 命令”,而是把桌面端长期使用时真正需要的隧道管理体验打磨出来,包括:规则管理、状态反馈、IPv6 处理、错误翻译、日志排障和菜单栏常驻交互。
本项目使用 Apache License 2.0。
TunnelCapsule is a macOS menu-bar SSH local port forwarding utility built for real daily use.
It is not just a thin wrapper around ssh -L. Instead, it turns multi-tunnel management, menu-bar controls, Keychain credential storage, IPv4/IPv6 handling, auto reconnect, diagnostics, local proxy toggles, and traffic counters into a native app that can stay in the macOS menu bar all day.
In practice:
- the lower layer handles SSH connections, local port forwarding, and connection lifecycle
- the upper layer handles tunnel configuration, menu-bar interaction, persistence, logs, and human-readable errors
- the result is a macOS-native tunnel manager suited for long-running everyday workflows
Core goals:
- provide a menu-bar SSH tunnel tool that is practical for long-term use, not a one-shot shell wrapper
- prioritize stability around IPv4 / IPv6, bind addresses, key authentication, and auto reconnect
- reduce the operational overhead of managing and switching between multiple tunnels with native macOS UI
- offer detailed logs and translated error messages for troubleshooting and development use
- menu-bar-only app with no Dock icon
- built-in Chinese / English UI
- multiple SSH local port-forwarding tunnel profiles
- password and private-key authentication with Keychain storage
- IPv4 / IPv6 support for SSH endpoints and local bind addresses
- quick presets for common bind addresses plus custom input
SSH ServerandRemote Target Hoststay aligned, so only the remote target port is edited- per-tunnel local proxy enable / disable
- auto reconnect, keepalive, and connection timeout settings
- cumulative traffic counters with manual reset
- configuration import / export
- built-in
Test Connectionin the tunnel editor - detailed translated errors for auth failures, wrong ports, refused connections, DNS failures, and similar issues
- integrated log panel inside the configuration window for debugging
- animated status bar icon with state-specific colors
- automatic fallback to system
/usr/bin/sshfor certain legacy servers when native SSH negotiation fails
- macOS 14+
- Xcode Command Line Tools
- Swift 5.9+
Install command line tools:
xcode-select --installgit clone git@github.com:liigoo/ssh-tunnel-bar.git
cd ssh-tunnel-bar
swift build
make run# debug build
swift build
# or
make build
# run
make run
# build release .app
make release
# install to /Applications
make install
# build DMG
make dmg
# clean build outputs
make cleanIf you want to distribute the .app, use the Makefile targets for signing and notarization:
make sign IDENTITY='Developer ID Application: Your Name (TEAMID)'
make notarize \
IDENTITY='Developer ID Application: Your Name (TEAMID)' \
APPLE_ID='you@example.com' \
TEAM_ID='TEAMID' \
APP_PASSWORD='xxxx-xxxx-xxxx-xxxx'- Launch the app and open the main menu from the menu bar icon.
- Choose
Add New Tunnel...or edit an existing tunnel. - Fill in the SSH server, username, authentication method, and local listen settings.
- Configure the remote target port.
- Use
Test Connectionfirst if you want to validate SSH access. - Save the tunnel, then connect, disconnect, edit, duplicate, or delete it from the menu bar.
- Switch to
Logsin the lower-left area of the configuration window to inspect runtime logs and low-level errors.
Default tunnel config location:
~/Library/Application Support/TunnelCapsule/tunnels.json
Default log file location:
~/Library/Application Support/TunnelCapsule/Logs/tunnelcapsule.log
Sensitive data storage:
- SSH passwords are stored in the login Keychain
- private-key passphrases are stored in the login Keychain
- non-sensitive tunnel settings are persisted as JSON
- SSH server fields accept
example.com,192.168.1.10,2001:db8::1, and[2001:db8::1] - local bind addresses support
127.0.0.1,0.0.0.0,::1,::, and custom IPv4 / IPv6 input - the remote target host automatically follows the SSH server host, so only the remote port is configurable
- if a legacy SSH server fails to negotiate with the native Swift SSH stack, the app can fall back to the system
sshpath automatically - text inputs support standard macOS editing shortcuts, including
Cmd+V
Sources/TunnelCapsule/
├── App/
│ ├── AppDelegate.swift
│ └── StatusBarController.swift
├── Config/
│ ├── AppLog.swift
│ ├── AppPreferences.swift
│ ├── ConfigStore.swift
│ ├── ErrorTranslator.swift
│ └── KeychainHelper.swift
├── Tunnel/
│ ├── ExternalSSHTunnel.swift
│ ├── LocalPortForwarder.swift
│ ├── SSHConnection.swift
│ ├── TunnelConfiguration.swift
│ ├── TunnelManager.swift
│ └── TunnelState.swift
└── Views/
├── PreferencesView.swift
└── TunnelConfigWindowController.swift
Directory responsibilities:
App/: app entry point, menu-bar icon, and menu interactionConfig/: persistence, logs, Keychain, translated errors, and app preferencesTunnel/: SSH connection, port forwarding, state coordination, and systemsshfallbackViews/: tunnel editor, log panel, and preferences UIResources/: icons, Info.plist, localizations, and privacy declaration
- Citadel - Swift SSH client library
- SwiftNIO - networking foundation used indirectly through Citadel
- macOS Keychain / UserNotifications / SwiftUI / AppKit - platform capabilities used by the app
- Fix: UI freeze when clicking the Logs button. Root cause: the log view read the entire log file (2MB+) synchronously on the main thread every second. Now reads only the last 500 lines via backward file seeking, keeping the UI responsive.
- Fix: crash when accessing local proxy port from a browser (
EXC_BAD_INSTRUCTIONin NIOSSHSSHChildChannel.handleOutboundChannelWindowAdjust). Root cause:SSHToTCPForwarderkept writing to a closed TCP channel after the browser disconnected, corrupting NIOSSH's internal window management state.
- Add Apache 2.0 license
- Rewrite bilingual README structure
- Simplify app icon artwork
- Refine status bar icon shapes
- Add custom app and status bar icons
- Fix paste shortcuts in tunnel editor
- Add system SSH fallback for Dropbear servers
- Improve tunnel diagnostics and editor
Thanks to Swift, SwiftNIO, Citadel, and the macOS native framework ecosystem for the building blocks behind this project.
The point of this app is not to merely wrap an SSH command. The real value is to make desktop tunnel management usable over time: profile management, status feedback, IPv6 handling, translated errors, diagnostics, and menu-bar-native interaction.
Released under the Apache License 2.0.