attempt with python ssh tunnelier

This commit is contained in:
Q
2024-01-11 20:36:10 +02:00
parent 62cdc7bd62
commit 4123c5d67d
6 changed files with 270 additions and 1 deletions

258
web/ssh-tunnelier.sh Executable file
View File

@@ -0,0 +1,258 @@
#!/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
Switches:
-h --help This help
-l List config entries
"
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'
if [[ "$LISTONLY" -eq 1 ]]; then
if [[ -n "$pid" ]]; then
echo "$line" | parse_switches 2>/dev/stdout 1>/dev/null | sed 's,^Generated , ,'
fi
fi
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"
}
for (( i=1; i<=$#; i++ )); do
[[ ${!i} = "-l" ]] && LISTONLY=1
done
if [[ "$LISTONLY" -ne 1 ]]; then
if [[ -n "$1" ]]; then
run_args "$@"
exit $?
fi
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"
if [[ "$LISTONLY" -eq 1 ]]; then
break
fi
read 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