SSH Tunnel Management

I do most of my Grafana development on a headless VM running on my home server — always on, available over Tailscale when remote, and the heavy builds run on plugged-in hardware instead of draining laptop battery. The workflow is: connect to the VM, start the dev server, tunnel port 3000 back to hit localhost:3000 in a local browser.

Two small tools make this easier: sshp for persistent shell sessions, and tunnelctl for managing tunnels by name.


shpool

For persistent shell sessions, I use shpool — to borrow its tagline, think tmux, then aim lower. Sessions survive disconnects, so I can close my laptop, reconnect later, and pick up where I left off.

To make connecting easier, I've wrapped it in a convenience function. The syntax sshp grafana+dev-vm connects to host dev-vm and attaches to (or creates) the session named grafana. If no + is present, it falls through to regular ssh:

if [[ "$1" == *+* ]]; then local session="${1%+*}" local host="${1#*+}" shift ssh -t "$host" "shpool attach '$session' || shpool create '$session'" "$@" else ssh "$@" fi

tunnelctl

Manages named SSH tunnels with persistent configuration. Running tunnelctl up grafana prompts for connection details the first time, then remembers them for subsequent runs.

When starting a tunnel, we first check if a metadata file exists for this name. If so, we source it to get the previous values:

# Load previous values as defaults if [[ -f "$metafile" ]]; then source "$metafile" default_host="$host" default_local_port="$local_port" default_remote_port="$remote_port" fi

We then prompt for each parameter using gum, pre-filling with the defaults. On subsequent runs, hitting enter accepts the previous config:

host=$(gum input --prompt "Host: " --value "$default_host") local_port=$(gum input --prompt "Local port: " --value "$default_local_port") remote_port=$(gum input --prompt "Remote port: " --value "$default_remote_port")

Finally, we start the tunnel in the background, save the PID for later cleanup, and write the current values back to the metadata file:

ssh -N -L "${local_port}:localhost:${remote_port}" "$host" & echo "$!" > "$(_tunnel_pidfile "$name")" echo "host=$host\nlocal_port=$local_port\nremote_port=$remote_port" > "$metafile"
Source on GitHub
© 2025 William Wernert