tunlite docs文档

A lightweight, cross-platform SSH tunnel manager. Define named tunnels once; a tiny zero-dependency daemon keeps them connected, restarts them at login, and helps set up passwordless access. Every command supports --json and stable exit codes. 轻量、跨平台的 SSH 隧道管理器。一次定义命名隧道,由一个零依赖的小守护进程保持连接、开机自启、帮你打通免密。每个命令都支持 --json 和稳定退出码。

Install安装

Prerequisite: Node ≥ 18 and the system ssh it wraps, both on your PATH. Pick one — no global npm required.前提:Node ≥ 18 和它封装的系统 ssh,都在 PATH 上。任选其一 —— 不依赖全局 npm。

$ npx tunlite install

tunlite install copies the runtime to a fixed directory and writes a launcher that pins node's absolute path (switching nvm/fnm won't break it). Run interactively, it also offers to register login autostart and install the Agent skill. A short tun alias is added too (skipped with a notice if the name is already taken).tunlite install 把运行时拷到固定目录,并写一个钉死 node 绝对路径的 launcher(切换 nvm/fnm 也不受影响)。交互运行时还会问你是否注册开机自启、安装 Agent skill。也会顺带写一个短别名 tun(若已被占用则跳过并提示)。

Windows support is beta. macOS and Linux are the primary, CI-tested platforms; Windows (Task Scheduler autostart, .cmd launcher, PATH setup) works but is less battle-tested — please report rough edges.Windows 支持目前为 beta。 macOS 和 Linux 是主力、有 CI 覆盖的平台;Windows(任务计划自启、.cmd launcher、PATH 设置)能用,但验证较少 —— 遇到问题欢迎反馈。

Quick start快速开始

Forwards use the standard ssh flags — -L / -R / -D, repeatable, so one tunnel can carry several (see Forwards).转发用原生 ssh 标志 —— -L / -R / -D,可重复,所以一条隧道可带多个(见转发模型)。

# reach the server's :80 on your local :8080
$ tunlite add web   --to me@host -L 8080:localhost:80
# expose your local :3000 as server:9000
$ tunlite add rev   --to me@host -R 9000:localhost:3000
# local SOCKS5 proxy (default :1080)
$ tunlite add socks --to me@host -D 1080

$ tunlite status             # aligned table of every tunnel
$ tunlite logs web -f        # follow logs
$ tunlite doctor             # why it won't connect (ssh/key/port/daemon)

Upgrading from 0.9.x? 0.10.0 leans into native ssh, so a couple of commands take a new shape — while your existing tunnel config keeps working, untouched. Forwards now speak ssh's own flags — add web --to me@host -L 8080:localhost:80 -D 1080 (repeatable; set <name> edits them in place), and the earlier add local/remote/dynamic form gives way to this. Switching a tunnel on and off is now enable / disable (it was up/down), each naming what it acts on — a name, --tag, or all; a retired or mistyped verb gently points you to the right one (tunlite up → "did you mean enable?"). Run tunlite update (or npx tunlite@latest install) and confirm with tunlite --version.从 0.9.x 升级?0.10.0 更拥抱原生 ssh,少数命令换了写法 —— 而你已有的隧道配置照常运行、纹丝不动。转发现在直接说 ssh 自己的标志 —— add web --to me@host -L 8080:localhost:80 -D 1080(可重复;用 set <名字> 就地编辑),早先的 add local/remote/dynamic 写法由此让位。开关一条隧道现在是 enable / disable(此前 up/down),每个都点明作用对象 —— 名字、--tagall;打错或用了退役的词,会温和地指向正确的那个(tunlite up → "did you mean enable?")。跑 tunlite update(或 npx tunlite@latest install)并用 tunlite --version 确认。

When the target isn't passwordless yet, running tunlite enable in a terminal prompts for the password once and installs your key. Or do it explicitly: tunlite check user@server (exit 0 = already works) and tunlite setup-key user@server.目标还没免密时,在终端跑 tunlite enable 会让你输一次密码并装好公钥。也可显式执行:tunlite check user@server(退出 0 = 已通)、tunlite setup-key user@server

Autostart (optional): tunlite install service registers the daemon to start at login — it also starts everything right away, so it replaces enable when you want tunnels up persistently.开机自启(可选):tunlite install service 把守护进程注册成登录自启 —— 它当场也会把一切启动起来,所以想让隧道持久常驻时它替代 enable,两者不用都做。

Update升级

$ tunlite update              # upgrade to the latest
$ tunlite update v0.9.0       # install / roll back to a tag
$ tunlite update --check      # compare vs latest, no change
$ tunlite update --no-restart # swap files only

update upgrades to the latest release tag: it fetches that tag's tarball and re-anchors in place (no npm, no git), then restarts the daemon — so the installed version is always a real published release. It only self-updates an anchored install; from a git checkout it points you to git pull, and from an npm i -g install to npm i -g tunlite@latest.update 升级到最新的 release tag:拉那个 tag 的 tarball 就地重新锚定(不走 npm、不走 git)再重启 daemon —— 所以装上的永远是一个真实已发布的版本。它只自更新锚定安装;在 git 检出里提示你用 git pull,在 npm i -g 安装里提示你用 npm i -g tunlite@latest

Uninstall卸载

$ tunlite uninstall            # stop daemon + remove everything (confirms first)
$ tunlite uninstall --force    # skip the confirmation
$ tunlite uninstall --purge    # also remove config and logs
$ tunlite uninstall service    # remove autostart only

Interactive uninstall asks for confirmation and warns if any tunnels are currently up. --force (or --json for scripts/Agents) skips the prompt.交互式 uninstall 会先确认,并在有隧道正在运行时提示。--force(脚本/Agent 用 --json)可跳过确认。

How it works工作原理

Three roles, each with one job — think remote, engine, guard.三个角色,各司一职 —— 可以理解成遥控器引擎保安

the remote遥控器

CLI tunlite …

The commands you type. Edits config.json, talks to the daemon, then exits.你敲的命令。改 config.json、和 daemon 通信,敲完即退。

the engine引擎

daemon daemon run

A long-lived process. Keeps every tunnel connected, reconnects on drop, serves status & logs.一个常驻进程。把每条隧道连着、断了重连、对外提供 status 和日志。

the guard保安

service install service

A launchd / systemd / Task Scheduler entry. Keeps the daemon alive across boots and crashes.launchd / systemd / 任务计划里的一条注册项。开机拉起、挂了重启,保活 daemon。

 you ── tunlite <cmd> (CLI) ──┬─ write config.json   (add/rm/up/down)
                              ├─ NDJSON IPC → daemon  (status/logs/restart)
                              └─ one-shot ssh         (check/setup-key)

 OS service ── runs ──▶ tunlite daemon run ──spawn──▶ ssh -N (-L/-R/-D)
   ▲ created by `install service`        │ supervise + reconnect (backoff)
   └ keeps the daemon alive              ▼
                              config.json ◀── reconcile on (re)start

config.json is the single source of truth. The OS service keeps the daemon alive; the daemon keeps every tunnel alive and reconciles against the config on every start. The daemon starts only on tunlite enable (on demand) or tunlite install service (at login) — other commands won't start it; if it's down they exit 5 and tell you how.config.json 是唯一事实来源。OS service 保活 daemon,daemon 保活每条隧道,每次启动还会与配置对账。daemon 只在 tunlite enable(按需)或 tunlite install service(开机)时启动 —— 其它命令不会启动它;它没在跑时会退出 5 并告诉你怎么启动。

Forwards转发模型

Forwards use the standard ssh flags, and they're repeatable — one tunnel can carry several over one connection. To change a tunnel's forwards, use set <name>: passing any -L/-R/-D replaces the whole forward set (set is the sole forward editor).转发用原生 ssh 标志,且可重复 —— 一条隧道可在一条连接上承载多个。要改一条隧道的转发,用 set <name>:带上任意 -L/-R/-D 就会替换整组转发set 是唯一的转发编辑入口)。

  • -L [bind:]PORT:HOST:HOSTPORTlocal forward: reach a remote service from your machine.-L [bind:]PORT:HOST:HOSTPORT —— 本地转发:在本地访问远端服务。
  • -R [bind:]PORT:HOST:HOSTPORTremote forward: expose a local service on the server.-R [bind:]PORT:HOST:HOSTPORT —— 远程转发:把本地服务暴露到服务器。
  • -D [bind:]PORTdynamic: a local SOCKS5 proxy.-D [bind:]PORT —— 动态:本地 SOCKS5 代理。

The optional bind: prefix is the listen address (default 127.0.0.1; use 0.0.0.0 to expose the listener to your LAN). Bracket IPv6 addresses: [::1].可选的 bind: 前缀是监听地址(默认 127.0.0.1;写 0.0.0.0 把监听暴露到局域网)。IPv6 地址要用方括号:[::1]

The SSH port rides in the target — --to user@host:2222 (defaults to 22; must be an integer 1–65535 or it's a usage error). IPv6 literals with a port need brackets: user@[::1]:2222.SSH 端口写进目标里 —— --to user@host:2222(默认 22;必须是 1–65535 的整数,否则报用法错)。IPv6 字面量带端口要加方括号:user@[::1]:2222

Daemon-less: run无守护进程:run

For containers and systemd entrypoints where a background daemon doesn't fit, run supervises one tunnel in the foreground (auto-reconnect, keepalive) and stays attached until stopped — no daemon, no config.json entry.容器、systemd 入口这类不方便跑后台守护进程的场景,用 run前台监管一条隧道(自动重连、keepalive),一直挂着直到被停止 —— 不起守护进程,也不写进 config.json

$ tunlite run --to me@host -L 8080:localhost:80
$ tunlite run --to me@host -R 9000:localhost:3000 --name rev --json --exit-on-failure

--name labels the tunnel for status lines (defaults to the target host). --json emits NDJSON state on stdout (one JSON object per state change). --exit-on-failure exits non-zero instead of retrying — needs-auth4, blocked/failed1 — so a supervisor restarts it.--name 为该临时隧道设置标签(默认使用目标主机名)。--json 在 stdout 输出 NDJSON 状态(每次状态变化一行 JSON)。--exit-on-failure 失败即以非零码退出而非重试 —— needs-auth4blocked/failed1 —— 方便上层 supervisor 重启。

Commands命令

Command命令What it does作用
add <name> --to user@host -L/-R/-D …Define a tunnel (-L/-R/-D repeatable for multiple forwards).定义一条隧道(-L/-R/-D 可重复,带多个转发)。
set <name> [--to …] [-L/-R/-D …] [-i key] [--jump …]Edit a tunnel; passing -L/-R/-D replaces its whole forward set.改隧道;带 -L/-R/-D 会替换整组转发。
rename <old> <new>Rename, handing off the live connection.重命名,平滑交接活动连接。
rm <name> · list [--tag T]Delete (and stop) a tunnel · list defined tunnels.删除(并停掉)隧道 · 列出已定义隧道。
run --to user@host -L/-R/-D … [--name LABEL] [--json]Daemon-less foreground tunnel (container / systemd entrypoint).无守护进程的前台隧道(容器 / systemd 入口)。
enable · disable · restart <name|--tag T|all>Control. Name a target — a tunnel, --tag (every tunnel with that label), or all; bare is a usage error.控制。必须指明对象 —— 隧道名、--tag(带该标签的所有隧道)或 all;不带对象会报用法错误。
status [name|--tag T]Structured status (no name = aligned table; with name = detail).结构化状态(无名 = 对齐表格;有名 = 详情)。
monitor [--interval s] [--tag T]Full-screen live dashboard.全屏实时面板。
logs <name> [-f] [-n N]View / follow logs.查看 / 跟随日志。
doctor [name]One-shot health check: why it won't connect.一键体检:为什么连不上。
check · setup-key <user@host>Probe passwordless / install your public key.免密探测 / 安装你的公钥。
webhook [set|on|off|events|test]Drop alerts: view / set / toggle / pick events / test.掉线告警:查看 / 设置 / 开关 / 挑事件 / 测试。
export · import <file> [--force]Export config (no keys) · merge tunnels back.导出配置(不含密钥)· 合并导入隧道。
update [version]Update (or pin a tag) and restart the daemon.升级(或锁定标签)并重启 daemon。
install [service|skill|completion]Anchor runtime; add autostart / skill / completion.锚定运行时;加自启 / skill / 补全。
uninstall [target] [--purge] [--force]Uninstall (no target = everything; confirms first, --force skips; --purge drops config/state).卸载(不带目标 = 全部;先确认、--force 跳过;--purge 连配置/状态一起删)。
daemon run|start|stop|statusDaemon lifecycle (run = foreground, for the service).daemon 生命周期(run = 前台,供服务调用)。

Naming convention: <purpose>-<port>, all-lowercase, hyphen-separated — e.g. tmux-prod-19999, db-staging-5432. That way status/logs read self-explanatory.命名规范:<用途>-<端口>、全小写、连字符分隔 —— 例如 tmux-prod-19999db-staging-5432。这样 status/logs 一眼自解释。

Jump hosts跳板机

Reach a target through one or more bastions with --jump [user@]host[:port][,…] (ssh -J / ProxyJump) on add, set, check, and setup-key. It's stored per-tunnel and used for the auth probe too.--jump [user@]host[:port][,…](ssh -J / ProxyJump)经一个或多个堡垒机到达目标,add / set / check / setup-key 都支持。它按隧道保存,免密探测也走它。

$ tunlite add db-5432 --to me@db -L 5432:localhost:5432 --jump me@bastion
$ tunlite set web-8080 --jump me@b1,me@b2   # chain multiple hops

Tags标签

Label tunnels with --tag (repeatable) on add, edit with set --tag (replaces the set) or set --no-tags (clears). Then act on a whole group: enable/disable/restart/status/list/monitor all accept --tag <label> (multiple --tag = union). A name and --tag are mutually exclusive. Tags are metadata only — they never change the ssh command.--tag(可重复)在 add 时贴标签,用 set --tag(整组替换)或 set --no-tags(清空)修改。之后对一整组批量操作:enable/disable/restart/status/list/monitor 都支持 --tag <标签>(多个 --tag 取并集)。名字与 --tag 不能同时给。标签只是元数据,不改变 ssh 命令。

$ tunlite add api-9001 --to me@host -R 9001:localhost:9001 --tag prod
$ tunlite enable --tag prod

Live monitor实时面板

tunlite monitor (alias mon) opens a top-style full-screen dashboard: daemon status and tunnel counts up top, a colour-coded tunnel table below.tunlite monitor(别名 mon)打开 top 风格的全屏面板:顶部 daemon 状态与隧道计数,下面是上色的隧道表。

  • ↑/k ↓/j select, s start, x stop, r restart (y/N confirm).↑/k ↓/j 选择,s 启动,x 停止,r 重启(y/N 确认)。
  • ? help, q quit, --interval <seconds> sets the refresh rate (default 1s).? 帮助,q 退出,--interval <秒> 调刷新间隔(默认 1s)。

Requires an interactive terminal; in scripts use tunlite status --json.需要交互式终端;脚本里用 tunlite status --json

Exit codes & --json退出码与 --json

Add --json to any command for machine-readable output. Branch on the exit code rather than parsing prose:任意命令加 --json 得到机器可读输出。按退出码分支,而不是解析人话:

0 ok成功 2 usage error用法错 3 not found找不到 4 needs auth需打通 5 daemon unreachable连不上 daemon 1 other error其它错误

state values: idle, starting, connected, retrying, needs-auth, blocked, failed, stopped, disabled, daemon-stopped. Treat connected as success; retrying is normal during a transient outage.state 取值:idlestartingconnectedretryingneeds-authblockedfailedstoppeddisableddaemon-stoppedconnected 即成功;retrying 是短暂断网时的正常状态。

Webhooks告警 webhook

When a tunnel drops, the daemon POSTs an alert to a webhook you configure, formatted per channel (currently generic + WeCom wecom). The channel is detected from the URL, or set with --channel. Pure Node built-in http/https — no new dependencies.隧道掉线时,daemon 往你配的 webhook POST 一条告警,按渠道排版(目前 generic + 企业微信 wecom)。渠道从 URL 自动识别,或用 --channel 指定。纯 Node 内置 http/https,无新依赖。

$ tunlite webhook            # show current config
$ tunlite webhook set <url>   # set + enable (channel auto)
$ tunlite webhook events down,recovered  # change events
$ tunlite webhook off        # disable (keeps the URL)
$ tunlite webhook test       # POST a test event

Events are edge-triggered (a reconnect storm fires once), in two scopes:事件按状态边沿触发(重连风暴只报一次),分两类:

  • tunnel: up, down, recovered, needs-auth, failed, stopped.隧道级updownrecoveredneeds-authfailedstopped
  • daemon: daemon-up, daemon-down, daemon-crash.daemon 级daemon-updaemon-downdaemon-crash

webhook events accepts names, the groups tunnel/daemon, all, or none. Default: down, recovered, needs-auth, failed, daemon-crash. Payload: {scope, tunnel, host, event, state, lastError, restarts, ts, machine, version}.webhook events 接受名称、组 tunnel/daemonallnone。默认:down, recovered, needs-auth, failed, daemon-crash。载荷:{scope, tunnel, host, event, state, lastError, restarts, ts, machine, version}

Import / export导入导出

$ tunlite export > backup.json        # dump config (no secrets)
$ tunlite import backup.json          # merge tunnels
$ tunlite import backup.json --force  # overwrite same-name

import merges tunnels only — it never changes your local settings/alerts. A missing file exits 3; a malformed file exits 1 and leaves the current config untouched.import 只合并隧道 —— 不会动你本地的 settings/告警。文件不存在退出 3;文件损坏退出 1,且不改动当前配置。

Shell completionShell 补全

$ tunlite install completion       # auto-detect the shell
$ tunlite install completion zsh   # or name it: bash/zsh/fish
$ tunlite uninstall completion     # disable

Once on, it completes subcommands; for up/down/restart/status/logs/rm/rename it also completes defined tunnel names, for both tunlite and the short tun. Reopen the shell or exec zsh to take effect.开启后补全子命令;对 up/down/restart/status/logs/rm/rename 还会补全已定义的隧道名,tunlite 和短别名 tun 都能补。重开 shell 或 exec zsh 生效。

For Agents给 Agent 用

Agents are a first-class user. Every command takes --json and returns a stable exit code, so an Agent acts on results without scraping prose. The bundled ssh-tunnel skill — installed with tunlite install skill and shipped in the npm package — tells an Agent exactly how to drive tunlite: --json, branching on exit codes, and how to handle needs-auth.Agent 是一等用户。每个命令都接受 --json 并返回稳定退出码,Agent 拿到结果就能直接判断,不用解析人话。内置的 ssh-tunnel skill —— 用 tunlite install skill 安装、随 npm 包一起发布 —— 告诉 Agent 怎么驱动 tunlite--json、按退出码分支、处理 needs-auth

Read the Agent skill (SKILL.md) →阅读 Agent skill(SKILL.md)→

Config paths配置路径

  • Config: $XDG_CONFIG_HOME/tunlite/config.json · %APPDATA%\tunlite\config.json配置:$XDG_CONFIG_HOME/tunlite/config.json · %APPDATA%\tunlite\config.json
  • State/logs: $XDG_STATE_HOME/tunlite · %LOCALAPPDATA%\tunlite状态/日志:$XDG_STATE_HOME/tunlite · %LOCALAPPDATA%\tunlite
  • Use TUNLITE_HOME to put everything under one root (handy for testing).TUNLITE_HOME 把所有东西归到一个根目录下(测试很方便)。

Versioning版本管理

Follows SemVer (vMAJOR.MINOR.PATCH). Release notes are in the CHANGELOG; the source lives on GitHub. Licensed MIT.遵循 SemVer(vMAJOR.MINOR.PATCH)。发布说明见 CHANGELOG;源码在 GitHub。MIT 许可证。