____ _____ _____
/ ___||_ _|_ _ _ __|_ _|
\___ \ | | | | | | '_ \| |
___) | | | | |_| | | | | |
|____/ |_| \__,_|_| |_|_|
Stupid Tunnel Tricks
Stupid Tunnel Tricks — a terminal user interface for defining, configuring, and managing SSH tunnel connections, Kubernetes port-forwards, and sshuttle VPN sessions.
stunt2.mp4
stunt allows you to define tunnels with SSH, Kubernetes and sshuttle that persist when you close the app.
- Local port forwards (
ssh -L) — expose a remote service on a local port - Remote port forwards (
ssh -R) — expose a local service on a remote port - Dynamic SOCKS proxy (
ssh -D) — route traffic through a remote host - Optional SSH username and identity file per entry
- Custom SSH port support
- Forward to pods, services, or deployments via
kubectl port-forward - Optional kubeconfig context and namespace per entry
- Multiple port bindings per workload entry
- Route entire subnets through a remote host via sshuttle
- Multiple subnets per entry (comma-separated in the form)
- Optional SSH port, username, and identity file per entry
- Linux and macOS only (sshuttle is not supported on Windows)
- Start and stop tunnels from a single dashboard
- Per-entry connection state: connecting, connected, reconnecting, failed, suspended
- Auto-reconnect with exponential backoff (up to 10 retries, max 60s delay)
- Suspended state — manual disconnect of an auto-restart tunnel suppresses reconnection
- Session state persisted across restarts (PIDs tracked in
sessions.json) - Adopts existing tunnel processes on startup if they are still alive
- Warning when
kubectlorsshuttleis unavailable but entries of that type are configured
- Configuration stored as TOML (
tunnels.toml) in the platform data directory - Atomic saves with
.bakbackup on every write - Automatic migration of legacy
[[server]]format to current[[entries]]format - Multiple port-forward definitions per SSH and K8s entry
- Full-screen TUI built with ratatui
- Create and edit entries with an in-app form (no editor required)
- Type-selection step when creating a new entry (SSH, K8s, or sshuttle)
- In-line forward sub-form with type cycling (
Ctrl+T) - Status bar with transient feedback messages
- Rust 1.85+ (2024 edition)
- A working
sshclient on$PATH(for SSH tunnels) kubectlon$PATH(for Kubernetes port-forwards)sshuttleon$PATH(for sshuttle VPN sessions — Linux/macOS only)
Download the latest release for your platform from the releases page, or install via a package manager:
# Homebrew (macOS / Linux)
brew install keathmilligan/tap/stunt
# Scoop (Windows)
scoop bucket add keathmilligan https://github.com/keathmilligan/scoop-bucket
scoop install stunt
# apt (Debian / Ubuntu)
# See release page for repo setup instructions
# rpm (Fedora / RHEL)
# See release page for repo setup instructions
# cargo
cargo install stuntgit clone https://github.com/keathmilligan/stunt.git && cd stunt
cargo build --release
# Binary at: target/release/stuntstunt| Key | Action |
|---|---|
n |
New entry |
e |
Edit selected entry |
d |
Delete selected entry |
Enter |
Connect / disconnect selected entry |
j / k or arrow keys |
Navigate list |
q / Ctrl+C |
Quit |
| Key | Action |
|---|---|
Tab / Down |
Next field |
Shift+Tab / Up |
Previous field |
Ctrl+A |
Add a new forward |
Ctrl+D |
Delete selected forward |
Ctrl+T |
Cycle forward type (Local / Remote / Dynamic for SSH; Pod / Service / Deployment for K8s) |
Enter |
Confirm field / save entry |
Esc |
Cancel / go back |
Configuration is stored at:
| Platform | Path |
|---|---|
| Linux | ~/.local/share/tunnel-mgr/tunnels.toml |
| macOS | ~/Library/Application Support/tunnel-mgr/tunnels.toml |
| Windows | %APPDATA%\tunnel-mgr\tunnels.toml |
[[entries]]
type = "ssh"
name = "prod-db"
host = "bastion.example.com"
port = 22 # optional, default 22
user = "deploy" # optional
identity_file = "~/.ssh/id_ed25519" # optional
auto_restart = true # optional, default false
[[entries.forwards]]
type = "local"
bind_port = 5432
remote_host = "db.internal"
remote_port = 5432
[[entries.forwards]]
type = "dynamic"
bind_port = 1080[[entries]]
type = "k8s"
name = "api-debug"
context = "prod" # optional, uses current context if omitted
namespace = "default" # optional
resource_type = "deployment"
resource_name = "api-server"
auto_restart = false
[[entries.forwards]]
local_port = 8080
remote_port = 80Supported resource_type values: pod, service, deployment.
[[entries]]
type = "sshuttle"
name = "corp-vpn"
host = "bastion.example.com"
subnets = ["10.0.0.0/8", "192.168.0.0/16"]
port = 22 # optional, uses sshuttle default if omitted
user = "alice" # optional
identity_file = "~/.ssh/id_ed25519" # optional
auto_restart = true # optional, default false# Build
cargo build
# Run tests
cargo test
# Lint
cargo clippy -- -D warnings
# Format
cargo fmtMIT