Compare commits
3 Commits
362c852b24
...
28d33784ae
Author | SHA1 | Date | |
---|---|---|---|
28d33784ae | |||
54a1b9102c | |||
41838586a0 |
31
README.md
31
README.md
@@ -1,3 +1,34 @@
|
||||
# heidisql-ssh-tunnel
|
||||
|
||||
Helper script to spawn SSH tunnels based on HeidiSQL settings
|
||||
|
||||
# What's this?
|
||||
|
||||
The script assumes you have HeidiSQL directory in `~/'.heidisql` where your `portable_settings.txt` file is living.
|
||||
|
||||
It peeks into that file and offers auto-completion based on saved sessions. It creates local SSH tunnels for these sessions.
|
||||
|
||||
# Setup
|
||||
|
||||
1. Git clone this repo
|
||||
1. Add a symlink to bash completion script such as:
|
||||
```
|
||||
sudo ln -s <repo>/hs ~/.local/share/bash-completion/hs
|
||||
```
|
||||
1. Add symlink to script itself and make it executable:
|
||||
```
|
||||
sudo ln -s <repo>/hs.sh /usr/local/bin/hs
|
||||
chmod +x <repo>/hs.sh
|
||||
```
|
||||
|
||||
# What it looks like
|
||||
|
||||
Here with `fzf` as auto-completion helper (optional, is a personal preference):
|
||||
|
||||

|
||||
|
||||
> Here `hs` first attempts to set up an SSH tunnel for a HeidiSQL saved session and fails because something's already listening on the local port.
|
||||
>
|
||||
> We `kill` the connection in question - an SSH tunnel from a prior test - and try again. This time `hs` correctly creates our tunnel. With `set -xv` we see the actual command that gets executed.
|
||||
>
|
||||
> Once done we clean up after ourselves and kill SSH tunnels we no longer need.
|
||||
|
40
hs
Normal file
40
hs
Normal file
@@ -0,0 +1,40 @@
|
||||
_hs()
|
||||
{
|
||||
local cur prev
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
local settings_file
|
||||
settings_file=~/'.heidisql/portable_settings.txt'
|
||||
if [[ ! -r "${settings_file}" ]]; then
|
||||
COMPREPLY="$(printf -- '%s' '<File '"${settings_file}"' not readable, nothing to auto-complete>')"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local saved_sessions
|
||||
# Find all saved sessions where NetType is set to '2' aka 'MariaDB or
|
||||
# MySQL (SSH tunnel)'
|
||||
saved_sessions="$(grep -Pio -- '(?<=^Servers\\)[^\\]+(?=\\NetType<\|\|\|>[[:digit:]]<\|\|\|>2)' "${settings_file}" | sort | uniq)"
|
||||
|
||||
# Commands below depend on this IFS
|
||||
local IFS=$'\n'
|
||||
|
||||
if [[ "${prev}" != 'hs' ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${cur}" == * ]]; then
|
||||
# Filter our candidates
|
||||
CANDIDATES=($(compgen -W "${saved_sessions[*]}" -- "${cur}"))
|
||||
|
||||
# Correctly set our candidates to COMPREPLY
|
||||
if [[ "${#CANDIDATES[*]}" -eq '0' ]]; then
|
||||
COMPREPLY=()
|
||||
else
|
||||
COMPREPLY=($(printf ''"'"'%s'"'"'\n' "${CANDIDATES[@]}"))
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
complete -F _hs hs
|
103
hs.sh
Executable file
103
hs.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
|
||||
function get_ssh_param () {
|
||||
local session_name ssh_param
|
||||
session_name="${1:?}"
|
||||
ssh_param="${2:?}"
|
||||
|
||||
# SSHtunnelUser -> ssh_user
|
||||
# SSHtunnelHost -> ssh_target_server
|
||||
# SSHtunnelHostPort -> ssh_target_port
|
||||
# SSHtunnelPort -> ssh_local_port
|
||||
# Host -> tunneled_target_host
|
||||
# Port -> tunneled_target_port
|
||||
|
||||
local value
|
||||
value="$(grep -Pio -- '(?<=^Servers\\'"${session_name}"'\\'"${ssh_param}"'<\|\|\|>[[:digit:]]<\|\|\|>)[^\r\n\f]+' "${settings_file}")"
|
||||
if [[ ! "${value}" ]]; then
|
||||
case "${ssh_param}" in
|
||||
SSHtunnelUser)
|
||||
value="${USER}"
|
||||
;;
|
||||
SSHtunnelHostPort)
|
||||
value='22'
|
||||
;;
|
||||
Host)
|
||||
value='127.0.0.1'
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
printf -- '%s' "${value}"
|
||||
}
|
||||
|
||||
function local_port_is_free () {
|
||||
local local_port local_listeners
|
||||
local_port="${1:?}"
|
||||
local_listeners="$(ss --no-header -tln 'sport :'"${local_port}")"
|
||||
if [[ "${local_listeners}" ]]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function prep_tunnel () {
|
||||
local session_name
|
||||
session_name="${1:?}"
|
||||
|
||||
local ssh_user ssh_target_server ssh_target_port ssh_local_port tunneled_target_host tunneled_target_port
|
||||
|
||||
ssh_user="$(get_ssh_param "${session_name}" 'SSHtunnelUser')"
|
||||
ssh_target_server="$(get_ssh_param "${session_name}" 'SSHtunnelHost')"
|
||||
ssh_target_port="$(get_ssh_param "${session_name}" 'SSHtunnelHostPort')"
|
||||
ssh_local_port="$(get_ssh_param "${session_name}" 'SSHtunnelPort')"
|
||||
tunneled_target_host="$(get_ssh_param "${session_name}" 'Host')"
|
||||
tunneled_target_port="$(get_ssh_param "${session_name}" 'Port')"
|
||||
|
||||
if [[ ! "${ssh_user}" ]] || \
|
||||
[[ ! "${ssh_target_server}" ]] || \
|
||||
[[ ! "${ssh_target_port}" ]] || \
|
||||
[[ ! "${ssh_local_port}" ]] || \
|
||||
[[ ! "${tunneled_target_host}" ]] || \
|
||||
[[ ! "${tunneled_target_port}" ]]; then
|
||||
printf 'Session name\n\n %s\n\nUnable to set all necessary SSH vars, exiting 256 ...\n' "'${session_name}'"
|
||||
exit 256
|
||||
fi
|
||||
|
||||
printf '%s\n' 'Starting SSH tunnel ...'
|
||||
|
||||
if local_port_is_free "${ssh_local_port}"; then
|
||||
set -xv
|
||||
ssh "${ssh_user}"'@'"${ssh_target_server}" \
|
||||
-o 'ExitOnForwardFailure=yes' \
|
||||
-o 'ConnectTimeout=2' \
|
||||
-f \
|
||||
-p "${ssh_target_port}" \
|
||||
-N \
|
||||
-L "${ssh_local_port}"':'"${tunneled_target_host}"':'"${tunneled_target_port}" && {
|
||||
set +xv
|
||||
printf -- 'Done!'
|
||||
local_port_is_free "${ssh_local_port}"
|
||||
}
|
||||
set +xv
|
||||
else
|
||||
printf -- '%s\n' \
|
||||
'Failed! Something is listening on local TCP port '"${ssh_local_port}"'.' \
|
||||
'Try ss --no-header -tlpn '"'"'sport :'"${ssh_local_port}"''"'"' to ID processes.' \
|
||||
'Exiting 256 ...'
|
||||
exit 256
|
||||
fi
|
||||
}
|
||||
|
||||
function main () {
|
||||
settings_file=~/'.heidisql/portable_settings.txt'
|
||||
if [[ ! -r "${settings_file}" ]]; then
|
||||
COMPREPLY="$(printf -- '%s' '<File '"${settings_file}"' not readable, nothing to auto-complete>')"
|
||||
return 1
|
||||
fi
|
||||
prep_tunnel "${@}"
|
||||
}
|
||||
|
||||
main "${@}"
|
Reference in New Issue
Block a user