241 lines
6.7 KiB
Bash
Executable File
241 lines
6.7 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
CONFDIR="$HOME/.config/ssh-tunnelier"
|
|
CONF="$CONFDIR/tunnels.conf"
|
|
# Just over a year in minutes
|
|
MAGIC_TIME=525601
|
|
|
|
BASE="base64 -w 0"
|
|
UNBASE="base64 -d"
|
|
if [[ "$OSTYPE" = "darwin"* ]]; then
|
|
BASE="base64"
|
|
UNBASE="base64 -D"
|
|
fi
|
|
LOCALHOSTSYMBOL="💻"
|
|
|
|
function _helpexit() {
|
|
echo "SSH tunnel manager
|
|
Runs and monitors preconfigured background ssh tunnels, with the ability
|
|
to kill existing ssh processes. You don't need to keep the program on
|
|
to remember connected sessions.
|
|
|
|
Configuration stored in: $CONF
|
|
|
|
If run with arguments, runs an ssh tunnel with arguments:
|
|
ssh-tunnelier hostname [local-port] remote-port
|
|
|
|
If local port missing, the same port assumed in local
|
|
"
|
|
exit 0
|
|
}
|
|
|
|
for (( i=1; i<=$#; i++ )); do
|
|
[[ ${!i} = "-h" ]] && _helpexit
|
|
[[ ${!i} = "--help" ]] && _helpexit
|
|
done
|
|
|
|
[[ -f "$CONF" ]] || {
|
|
echo No config file.
|
|
echo "$CONF each line like (comma separated list of localport:host:remoteport, or ssh switches):"
|
|
echo "servername: 8888,8889:8989,8890:host:9090,-4"
|
|
mkdir -p "$CONFDIR"
|
|
echo -e "# example:\nservername: 8888,8889:8989,8890:host:9090" > "$CONF"
|
|
}
|
|
number_re='^[0-9]+$'
|
|
# import qolop
|
|
_qCol(){ true; } # incase qolop missing
|
|
. qolop &>/dev/null
|
|
colZ=$( _qCol z )
|
|
colTitle=$( _qCol z G )
|
|
colRow=$( _qCol z S )
|
|
colMenu=$( _qCol z y )
|
|
colWarn=$( _qCol z R )
|
|
|
|
function fpgrep() {
|
|
[[ -z $( pgrep -x -u $UID "$@" ) ]] && { echo '----'; return; }
|
|
pgrep -x -u $UID "$@" | xargs ps -o pid,start,args
|
|
}
|
|
|
|
function get_id() {
|
|
id=$( printf "%s" "$1" | $BASE )
|
|
echo -n $id
|
|
}
|
|
|
|
function get_command() {
|
|
switches=$( echo "$1" | $UNBASE | parse_switches )
|
|
echo -n "ssh -f -n $switches \"nice /bin/bash -c 'for ((i=1;i<$MAGIC_TIME;i++)); do cut -f4 -d \\\" \\\" /proc/\\\$PPID/stat | xargs kill -0 || exit ; sleep 60;done'; echo tunnelier $1\""
|
|
}
|
|
|
|
function parse_switches() {
|
|
python3 -c "
|
|
import sys
|
|
host, tunnels = sys.stdin.read(1024*10).split(':',1)
|
|
|
|
for i, cmd in enumerate(tunnels.split(',')):
|
|
cmd = cmd.strip()
|
|
if cmd.startswith('-'):
|
|
sys.stdout.write('{} '.format(cmd))
|
|
sys.stderr.write('Added switch {}\n'.format(cmd))
|
|
continue
|
|
tunnel = cmd.split(':')
|
|
thost='localhost'
|
|
if len(tunnel) == 1:
|
|
tport=tunnel[0]
|
|
if len(tunnel) == 2:
|
|
tport=tunnel[1]
|
|
if len(tunnel) == 3:
|
|
thost=tunnel[1]
|
|
tport=tunnel[2]
|
|
sys.stdout.write('-L {}:{}:{} '.format(tunnel[0], thost, tport))
|
|
sys.stderr.write('Generated tunnel {}: http://localhost:{} -> {}:{}\n'.format(i+1,tunnel[0],thost, tport))
|
|
sys.stdout.write(host)
|
|
"
|
|
}
|
|
|
|
function get_pid() {
|
|
pgrep -f "$MAGIC_TIME.*echo tunnelier $1" | head -n 1
|
|
}
|
|
|
|
function list_all_ssh() {
|
|
printf "\n${colTitle}=============================================\n"
|
|
printf "${colTitle} List of SSH:${colZ}\n"
|
|
printf "${colTitle}Enter PID to kill, empty to return $colZ\n"
|
|
printf "${colTitle}Outgoing SSH processes\n"
|
|
printf "${colTitle}=============================================\n${colRow}"
|
|
fpgrep ssh
|
|
printf "${colTitle}Incoming SSH processes\n"
|
|
printf "${colTitle}=============================================\n${colRow}"
|
|
fpgrep sshd
|
|
read -t 600 inputpid
|
|
|
|
[[ "$inputpid" =~ $number_re ]] && {
|
|
ask_to_kill $inputpid
|
|
}
|
|
}
|
|
|
|
function reset_all_ssh() {
|
|
match="ssh.*sleep.*echo tunnelier"
|
|
printf "\n${colTitle}=============================================\n"
|
|
printf "${colTitle} List of tunneliers:${colZ}\n"
|
|
printf "${colTitle}=============================================\n${colRow}"
|
|
if [[ -z $( pgrep -u $UID -f "$match" ) ]]; then
|
|
echo 'No tunneliers running.'
|
|
return
|
|
else
|
|
pgrep -u $UID -f "$match" | xargs ps -o pid,start,args
|
|
fi
|
|
printf "\n k kill all\n t terminate all\n empty returns${colZ}\n"
|
|
read -t 600 input1
|
|
[[ "$input1" = "k" ]] && pkill -u $UID -f "$match"
|
|
[[ "$input1" = "t" ]] && kill -u $UID -9 -f "$match"
|
|
}
|
|
|
|
function read_config() {
|
|
while read line; do
|
|
id=$( get_id "$line" )
|
|
pid=$( get_pid "$id" )
|
|
printf "%-3d %-7s %s\n" $i "$pid" "$line" | sed 's,:localhost:,:'$LOCALHOSTSYMBOL':,g'
|
|
ids+=( $id )
|
|
i=$(( i + 1 ))
|
|
done < <( grep -v ^# "$CONF" | grep '[a-zA-Z]' )
|
|
}
|
|
|
|
function run_args() {
|
|
if [[ -z "$2" ]]; then
|
|
# only hostname
|
|
if grep -q "^$1:" "$CONF"; then
|
|
grep "^$1:" "$CONF"
|
|
host="$1"
|
|
line=$( grep "^$1:" "$CONF" )
|
|
switches=$( echo "$line" | parse_switches )
|
|
else
|
|
echo No such host
|
|
cat "$CONF"
|
|
exit 1
|
|
fi
|
|
else
|
|
host="$1"
|
|
shift 1
|
|
line="$host: $@"
|
|
switches=$( echo "$line" | parse_switches )
|
|
fi
|
|
id=$( get_id "$line" )
|
|
eval "ssh -f -n $switches \"nice /bin/bash -c 'for ((i=1;i<$MAGIC_TIME;i++)); do cut -f4 -d \\\" \\\" /proc/\\\$PPID/stat | xargs kill -0 || exit ; sleep 60;done'; echo tunnelier $id\""
|
|
exit $?
|
|
}
|
|
|
|
function run_command() {
|
|
eval $( get_command "$1" )
|
|
}
|
|
|
|
function ask_to_kill() {
|
|
printf "${colRow}"
|
|
ps -o pid,bsdstart,args "$1"
|
|
printf "\n k kill\n t terminate${colZ}\n"
|
|
read -t 600 input2
|
|
[[ "$input2" = "k" ]] && kill $1
|
|
[[ "$input2" = "t" ]] && kill -9 $1
|
|
}
|
|
|
|
function instant_entry() {
|
|
printf "${colMenu}Start a local forward tunnel${colZ}\n"
|
|
printf "${colMenu}Host name?${colZ}\n"
|
|
read -e HOST
|
|
test -z "$HOST" && return
|
|
printf "${colMenu}Target port at host?${colZ}\n"
|
|
read -e REMOTE
|
|
test -z "$REMOTE" && return
|
|
printf "${colMenu}Local port where tunnel starts?${colZ}\n"
|
|
read -e -i "$REMOTE" LOCAL
|
|
test -z "$LOCAL" && return
|
|
printf "${colRow}-L ${LOCAL}:localhost:${REMOTE} ${HOST}${colZ}\n"
|
|
run_args "$HOST" "$LOCAL:$REMOTE"
|
|
printf "${colMenu}See the connection in 'list ssh'${colZ}\n"
|
|
}
|
|
|
|
if [[ -n "$1" ]]; then
|
|
run_args "$@"
|
|
exit $?
|
|
fi
|
|
|
|
while true; do
|
|
ids=()
|
|
i=1
|
|
printf "\n${colTitle}=============================================\n"
|
|
printf "${colTitle} List of tunnels: (${LOCALHOSTSYMBOL}=localhost) [%s]${colZ}\n" $( date +%H:%M )
|
|
printf "${colTitle} (q)uit (e)dit (l)ist (i)nstant (r)eset${colZ}\n"
|
|
printf "${colTitle}=============================================\n"
|
|
printf "${colRow}ID PID host: ports\n"
|
|
read_config
|
|
printf "$colZ"
|
|
read -t 600 input
|
|
|
|
[[ "$input" = "q" ]] && exit 0
|
|
[[ "$input" = "i" ]] && instant_entry
|
|
[[ "$input" = "e" ]] && vim "$CONF"
|
|
[[ "$input" = "l" ]] && list_all_ssh
|
|
[[ "$input" = "r" ]] && reset_all_ssh
|
|
[[ "$input" = "0" ]] && continue
|
|
[[ "$input" =~ $number_re ]] && {
|
|
j=$(( $input - 1 ))
|
|
[[ $j -gt $(( $i - 1 )) ]] && {
|
|
printf "${colWarn}No such tunnel number${colZ}\n"
|
|
continue
|
|
}
|
|
|
|
printf "\n${colTitle}Tunnel command: %s${colZ}\n" "$( echo "${ids[$j]}" | $UNBASE )"
|
|
this_pid="$( get_pid "${ids[$j]}" )"
|
|
[[ -n "$this_pid" ]] && {
|
|
# PID exists, ask to kill
|
|
ask_to_kill "$this_pid"
|
|
}
|
|
[[ -z "$this_pid" ]] && {
|
|
# PID empty, run
|
|
run_command "${ids[$j]}"
|
|
sleep 1
|
|
}
|
|
}
|
|
done
|
|
|
|
|