Compare commits
	
		
			3 Commits
		
	
	
		
			362c852b24
			...
			28d33784ae
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 28d33784ae | |||
| 54a1b9102c | |||
| 41838586a0 | 
							
								
								
									
										33
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								README.md
									
									
									
									
									
								
							@@ -1,3 +1,34 @@
 | 
				
			|||||||
# heidisql-ssh-tunnel
 | 
					# heidisql-ssh-tunnel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Helper script to spawn SSH tunnels based on HeidiSQL settings
 | 
					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