Skip to content

Instantly share code, notes, and snippets.

@tomterragni
Created September 10, 2025 14:37
Show Gist options
  • Select an option

  • Save tomterragni/d7fccd6cf897098f03684cf414618c75 to your computer and use it in GitHub Desktop.

Select an option

Save tomterragni/d7fccd6cf897098f03684cf414618c75 to your computer and use it in GitHub Desktop.
Script to automate ssh connection and allow tunnelling of remote port 3306 to a local port
#!/usr/bin/env bash
set -Eeuo pipefail
# ---------- Defaults ----------
CERT=""
USER=""
HOST=""
DO_TUN=false
DO_STOP=false
DO_STATUS=false
DO_LIST=false
LOCAL_PORT=""
# Directory to store SSH control sockets
CTL_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/ssh-tunnels"
mkdir -p "$CTL_DIR"
helpFunction(){
cat <<EOF
================================================================
Connects to a specific host with optional MySQL tunnel.
================================================================
Usage: $(basename "$0") --ip <ip_address> --user <username> --cert <cert_path> [--port <port>] [--tun] [--stop] [--status] [--list] [-h]
--ip <ip> Target IP address (required)
--user <user> SSH username (required)
--cert <path> Path to SSH certificate/key file (required)
--port <port> Local port for tunnel (default: auto-generated from IP)
--tun Start a local tunnel: 127.0.0.1:<port> -> remote 127.0.0.1:3306
--stop Stop the tunnel for this host (if running)
--status Check tunnel status for this host
--list List status of all tunnels started by $(basename "$0")
-h Print this help and exit
Examples:
# SSH shell
$(basename "$0") --ip <ip_of_remote_server> --user root --cert /path/to/cert.pem
# Start tunnel only (no shell), auto-generated local port
$(basename "$0") --ip <ip_of_remote_server> --user root --cert /path/to/cert.pem --tun
# Start tunnel with specific local port
$(basename "$0") --ip <ip_of_remote_server> --user root --cert /path/to/cert.pem --port 3342 --tun
# Stop tunnel
$(basename "$0") --ip <ip_of_remote_server> --user root --cert /path/to/cert.pem --stop
# Check status
$(basename "$0") --ip <ip_of_remote_server> --user root --cert /path/to/cert.pem --status
# List all tunnels
$(basename "$0") --list
EOF
exit 0
}
# Function to generate a local port from IP address
generate_port_from_ip() {
local ip="$1"
# Extract the last octet and add 3300 to it
local last_octet="${ip##*.}"
echo "$((3300 + last_octet))"
}
# Function to validate IP address
validate_ip() {
local ip="$1"
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
IFS='.' read -ra ADDR <<< "$ip"
for i in "${ADDR[@]}"; do
if [[ $((10#$i)) -gt 255 ]]; then
echo "Invalid IP address: $ip"
exit 1
fi
done
else
echo "Invalid IP address format: $ip"
exit 1
fi
}
# Function to validate certificate file
validate_cert() {
local cert="$1"
if [[ ! -f "$cert" ]]; then
echo "Certificate file does not exist: $cert"
exit 1
fi
}
# ---------- Parse args ----------
if (( $# == 0 )); then echo "No arguments provided"; helpFunction; fi
while (( "$#" )); do
case "$1" in
--ip)
if [[ -n "${2:-}" ]] && [[ ${2:0:1} != "-" ]]; then
HOST="$2"
validate_ip "$HOST"
shift 2
else
echo "Error: --ip requires an IP address" >&2
exit 1
fi
;;
--user)
if [[ -n "${2:-}" ]] && [[ ${2:0:1} != "-" ]]; then
USER="$2"
shift 2
else
echo "Error: --user requires a username" >&2
exit 1
fi
;;
--cert)
if [[ -n "${2:-}" ]] && [[ ${2:0:1} != "-" ]]; then
CERT="$2"
validate_cert "$CERT"
shift 2
else
echo "Error: --cert requires a certificate file path" >&2
exit 1
fi
;;
--port)
if [[ -n "${2:-}" ]] && [[ ${2:0:1} != "-" ]]; then
if [[ "$2" =~ ^[0-9]+$ ]] && (( $2 >= 1024 && $2 <= 65535 )); then
LOCAL_PORT="$2"
shift 2
else
echo "Error: --port requires a valid port number (1024-65535)" >&2
exit 1
fi
else
echo "Error: --port requires a port number" >&2
exit 1
fi
;;
--tun) DO_TUN=true; shift ;;
--stop) DO_STOP=true; shift ;;
--status) DO_STATUS=true; shift ;;
--list) DO_LIST=true; shift ;;
-h) helpFunction ;;
-* ) echo "Unknown flag $1"; helpFunction ;;
* ) echo "Unknown argument '$1'. Use flags to specify parameters."; helpFunction ;;
esac
done
# If just listing, no other parameters required
if $DO_LIST; then
:
else
# Validate required parameters
if [[ -z "$HOST" ]]; then
echo "Error: --ip is required"
helpFunction
fi
if [[ -z "$USER" ]]; then
echo "Error: --user is required"
helpFunction
fi
if [[ -z "$CERT" ]]; then
echo "Error: --cert is required"
helpFunction
fi
# Generate local port if not specified
if [[ -z "$LOCAL_PORT" ]]; then
LOCAL_PORT=$(generate_port_from_ip "$HOST")
fi
fi
CTL="${CTL_DIR}/ssh-${USER}@${HOST}-${LOCAL_PORT}.ctl"
# ---------- Helpers ----------
tunnel_start() {
ssh -i "$CERT" \
-M -S "$CTL" \
-fNT \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=60 -o ServerAliveCountMax=3 \
-L "127.0.0.1:${LOCAL_PORT}:127.0.0.1:3306" \
"${USER}@${HOST}"
echo "Tunnel UP: 127.0.0.1:${LOCAL_PORT} → ${HOST}:3306"
echo "Control socket: $CTL"
}
tunnel_stop() {
if ssh -S "$CTL" -O check "${USER}@${HOST}" >/dev/null 2>&1; then
ssh -S "$CTL" -O exit "${USER}@${HOST}" >/dev/null 2>&1 || true
echo "Tunnel STOPPED for ${HOST}"
else
echo "No active tunnel for ${HOST}"
fi
}
tunnel_status() {
if ssh -S "$CTL" -O check "${USER}@${HOST}" >/dev/null 2>&1; then
echo "Tunnel is RUNNING for ${HOST} (local: ${LOCAL_PORT})"
else
echo "Tunnel is NOT running for ${HOST}"
fi
}
list_all() {
shopt -s nullglob
local any=0
printf "%-22s %-22s %-10s %s\n" "Local endpoint" "Remote endpoint" "Status" "Socket"
printf "%-22s %-22s %-10s %s\n" "----------------------" "----------------------" "----------" "-----"
for sock in "$CTL_DIR"/ssh-*@*-*.ctl; do
any=1
# sock name pattern: ssh-<user>@<host>-<localport>.ctl
local base; base="$(basename "$sock")"
local u_at_h="${base#ssh-}"; u_at_h="${u_at_h%-*.ctl}"
local lp="${base##*-}"; lp="${lp%.ctl}"
local user_part="${u_at_h%@*}"
local host_part="${u_at_h#*@}"
local status="DOWN"
if ssh -S "$sock" -O check "${user_part}@${host_part}" >/dev/null 2>&1; then
status="RUNNING"
fi
printf "%-22s %-22s %-10s %s\n" "127.0.0.1:${lp}" "${host_part}:3306" "$status" "$sock"
done
shopt -u nullglob
if (( any == 0 )); then
echo "No tunnels found in $CTL_DIR"
fi
}
connect_shell() {
ssh -i "$CERT" "${USER}@${HOST}"
}
# ---------- Main ----------
if $DO_LIST; then
list_all
exit 0
fi
if $DO_STATUS; then
tunnel_status
exit 0
fi
if $DO_STOP; then
tunnel_stop
exit 0
fi
if $DO_TUN; then
tunnel_start
exit 0
fi
# default: interactive SSH (no tunnel)
connect_shell
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment