--- name: ssh-tunnel description: Use when you need to create, manage, inspect, or troubleshoot SSH tunnels / port-forwards on this machine through the `tunlite` CLI — local (-L), remote (-R), or dynamic SOCKS (-D) forwards, auto-reconnecting tunnels supervised by a daemon, registering that daemon to start at login, and setting up passwordless (key-based) access to a target host. Trigger when a task says things like "forward a port over SSH", "keep an SSH tunnel alive", "open a SOCKS proxy through host X", "make a reverse tunnel", "start my tunnels on boot", or "set up passwordless SSH to host Y". --- # Driving `tunlite` (SSH tunnel manager) `tunlite` defines named SSH tunnels in a config file and supervises them with a background daemon that auto-reconnects and can be registered to OS startup. **Always pass `--json`** for machine-readable output, and **branch on the exit code** rather than parsing prose. ## Step 0 — get `tunlite`, then just use it There is **one** operational tunlite: an *anchored* install (a stable copy with a pinned node path). npm, the curl bootstrap, and any copy bundled with this skill are all just ways to **seed** that one install — not separate tools to choose between. So, once, up front: 1. Run `tunlite --version`. - **`0.10.0` or newer** → you're set. - **`command not found`, or `0.9.x` or earlier** (missing, or below this skill's floor of **≥ 0.10.0** — the ssh-native `-L`/`-R`/`-D` + `run` + `set` surface): **seed it, and tell the user — never install silently.** If this skill bundle ships a copy, run `node /bin/tunlite.js install`; otherwise `npx tunlite install`. Both anchor a stable copy. (Installed but merely old → `tunlite update`.) 2. **After step 1, always just call `tunlite …`** — it resolves to the anchored copy. Stop deciding between `npx` / a bundled path / PATH; there is one tunlite now. 3. **Persistence is one extra line — and you must offer it, not wait to be asked.** A tunnel stays up only while the daemon does, and a bare `tunlite install` does **not** register autostart (nor does `enable` — it only starts the daemon on demand). So whenever a tunnel is meant to last, **proactively ask the user whether it should keep running across reboots / logins.** If yes (or it's meant to be permanent or unattended), run `tunlite install service` to register OS autostart. A tunnel the user assumes is "always on" that silently vanishes after a reboot is the most common surprise — don't skip the question; but don't enable it without telling them either (it writes a real launchd/systemd/Task Scheduler service). Newer than 0.10.x? Proceed anyway — don't stop because the tool is ahead. Only if a step below returns an unexpected usage (exit 2) or not-found (exit 3) error is the skill likely stale; refresh it with `tunlite install skill` and re-read before retrying. ## Exit codes (stable — branch on these) | code | meaning | what to do | |---|---|---| | 0 | ok | continue | | 2 | usage error | fix the command syntax | | 3 | not found | the named tunnel/host doesn't exist | | 4 | needs-auth | target is not passwordless — see "Passwordless" below | | 5 | daemon unreachable | run `tunlite daemon start` (or `tunlite enable` auto-starts it) | | 1 | other error | read the JSON `error` field | ## Core workflow ```bash # 1. Define a tunnel — forwards use ssh-native repeatable flags -L / -R / -D. # Spec is [bind:]PORT:HOST:HOSTPORT (-L/-R) or [bind:]PORT (-D). The name is a # bare positional; the target rides in --to; at least one forward is required. tunlite add web-8080 --to user@host -L 8080:localhost:80 --json # reach server's :80 at localhost:8080 tunlite add rev-9000 --to user@host -R 9000:localhost:3000 --json # expose local 3000 on server:9000 tunlite add px-1080 --to user@host -D 1080 --json # local SOCKS5 proxy on :1080 tunlite add multi --to user@host -L 8080:localhost:80 -D 1080 --json # several forwards on one tunnel # SSH port rides in the target: --to user@host:2222 # -L = reach a remote service locally; -R = expose a local service on the server; -D = local SOCKS5. # a bind: prefix is the listen address (omit = localhost; 0.0.0.0 to expose on all interfaces); # IPv6 literals must be bracketed, e.g. -L [::1]:8080:localhost:80 # options: -i --jump [user@]bastion[:port] --ssh-opt "-o Foo=bar" --tag