about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xmrgrctrnl198
1 files changed, 126 insertions, 72 deletions
diff --git a/mrgrctrnl b/mrgrctrnl index 796470c..b516596 100755 --- a/mrgrctrnl +++ b/mrgrctrnl
@@ -1,126 +1,175 @@
1#!/bin/sh 1#!/bin/sh
2# mrgrctrnl: configurable ssh tunneler 2# mrgrctrnl: configurable ssh tunneler
3# Author: Case Duckworth <acdw@acdw.net> 3# Author: Case Duckworth <acdw@acdw.net>
4# Version: 0.2 4# Version: 0.3
5# License: MIT 5# License: MIT
6 6
7PRGN="${0##*/}" 7PRGN="${0##*/}"
8 8
9usage() {
10 cat <<END
11$PRGN: make magic ssh tunnels
12usage: $PRGN [-h] [-k]
13 $PRGN [-n] [-c CONF | -s SSH]
14
15options:
16 -h show this help
17 -k kill all processes and exit
18 -r restart $PRGN
19 -n do a dry run: just print what would happen
20 -c CONFIG use a different CONFIG file instead of
21 \$XDG_CONFIG_HOME/$PRGN/config
22 -s CMDLINE directly input an ssh CMDLINE --
23 don't load the config file.
24END
25}
26
27# entry point 9# entry point
28main() { 10main() {
11 # flags
12 _RUN=true
13 _LOG=true
14 _USE_CONFIG=true
15 # options
29 __CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/$PRGN/config" 16 __CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/$PRGN/config"
30 __SSHCMD="-N \"\$user@\$machine\" -L \"\$local:\$remote\""
31 __PIDF="/tmp/$PRGN.pid" 17 __PIDF="/tmp/$PRGN.pid"
32 _run=true 18 __CMDF="/tmp/$PRGN.cmd"
33 _quiet=false 19 __SSH="$(command -v autossh || command -v ssh)"
34 _use_config=true 20 __SSH_ARGS="-nT -N \"\$user@\$machine\" -L \"\$local:\$remote\""
35 21
36 while getopts hqkrnc:s: OPT; do 22 # save command invocation (for -r)
23 printf '%s %s\n' "$PRGN" "$*" >"$__CMDF"
24
25 # parse options
26 while getopts hkrnqSAc:s: OPT; do
37 case "$OPT" in 27 case "$OPT" in
38 h) 28 h)
39 usage 29 usage
40 exit 0 30 exit 0
41 ;; 31 ;;
42 q) _quiet=true ;;
43 k) 32 k)
44 tear_down 33 untunnel
45 pkill -x "$PRGN" 34 pkill -x "$PRGN"
46 exit 35 exit "$?"
47 ;; 36 ;;
48 r) 37 r)
49 tear_down 38 untunnel
50 exec "$PRGN" 39 exec "$(cat "$__CMDF")"
51 ;; 40 ;;
52 n) _run=false ;; 41 n) _RUN=false ;;
42 q) _LOG=false ;;
43 S) __SSH='ssh' ;;
44 A) __SSH='autossh' ;;
53 c) __CONFIG="$OPTARG" ;; 45 c) __CONFIG="$OPTARG" ;;
54 s) 46 s)
55 __SSHCMD="$OPTARG" 47 __SSH_ARGS="$OPTARG"
56 _use_config=false 48 _USE_CONFIG=false
57 ;;
58 *)
59 usage
60 exit 1
61 ;; 49 ;;
50 *) exit 4 ;;
62 esac 51 esac
63 done 52 done
53 shift $((OPTIND - 1))
64 54
65 if $_use_config && [ ! -r "$__CONFIG" ]; then 55 # make sure ssh is installed
66 echo "!! $PRGN: Cannot find config file '$__CONFIG'." 56 if ! command -v "$__SSH" >/dev/null; then
67 echo "Aborting." 57 log "Not installed: $__SSH"
68 exit 2 58 exit 2
69 fi 59 fi
70 60
71 if $_use_config; then 61 # make sure the config is readable
62 if $_USE_CONFIG && [ ! -r "$__CONFIG" ]; then
63 log "Cannot find config file $__CONFIG; aborting."
64 exit 3
65 fi
66
67 # load the config
68 if $_USE_CONFIG; then
72 awk '{sub(/#.*$/,"");print}' "$__CONFIG" | 69 awk '{sub(/#.*$/,"");print}' "$__CONFIG" |
73 while read -r machine user local remote key rest; do 70 while read -r machine user local remote key rest; do
74 if [ -z "$machine" ] || [ -z "$user" ] || 71 if [ -z "$machine" ] ||
75 [ -z "$local" ] || [ -z "$remote" ]; then 72 [ -z "$user" ] ||
73 [ -z "$local" ] ||
74 [ -z "$remote" ]; then
76 continue 75 continue
77 fi 76 fi
78 77
78 case "$local" in
79 :*) local="localhost$local" ;;
80 esac
81 case "$remote" in
82 :*) remote="localhost$remote" ;;
83 esac
84
79 # shellcheck disable=2030 85 # shellcheck disable=2030
80 if [ -n "$key" ]; then 86 if [ -n "$key" ]; then
81 __SSHCMD="$__SSHCMD -i \"$key\"" 87 __SSH_ARGS="$__SSH_ARGS -i \"$key\""
82 fi 88 fi
83 if [ -n "$rest" ]; then 89 if [ -n "$rest" ]; then
84 __SSHCMD="$__SSHCMD $rest" 90 __SSH_ARGS="$__SSH_ARGS $rest"
85 fi 91 fi
86 92
87 eval "tunnel $__SSHCMD" & 93 eval "tunnel_${__SSH##*/} $__SSH_ARGS" &
88 done 94 done
89 else 95 else
90 # shellcheck disable=2031 96 #shellcheck disable=2031
91 eval "tunnel $__SSHCMD" & 97 eval "tunnel_${__SSH##*/} $__SSH_ARGS" &
92 fi 98 fi
93 99
94 wait 100 wait
95} 101}
96 102
97tunnel() { 103usage() {
98 # shellcheck disable=2086 104 cat <<END
99 $_quiet || echo ssh "$@" 105$PRGN: make magic ssh tunnels
100 $_run && { 106usage: $PRGN [-h] [-k] [-r]
101 touch "$__PIDF" 107 $PRGN [-n] [-q] [-S|-A] [-c FILE | -s ARGS]
102 while [ -e "$__PIDF" ]; do 108
103 grep -q -e "$*" "$__PIDF" && { 109options:
104 sleep 3 110 -h show this help
105 continue 111 -k demolish all tunnels and exit
106 } 112 -r demolish all tunnels and restart
107 # shellcheck disable=2015 113 -n dry run: just print what would happen
108 if ssh "$@" & then 114 -q quiet: don't print anything
109 printf '%s\t%s\n' "$!" "$*" >>"$__PIDF" 115
110 else 116 -S force use of 'ssh'
111 badpid="$!" 117 -A force use of 'autossh'
112 kill "$badpid" 118 default: use 'autossh' if installed, otherwise 'ssh'
113 sed "/^$badpid/d" "$__PIDF" >"$__PIDF~" && 119
114 mv "$__PIDF~" "$__PIDF" 120 -c FILE load FILE as config file
115 continue 121 default: \$XDG_CONFIG_HOME/$PRGN/config
116 fi 122
123 -s ARGS run CMD with ARGS, do not read config file
124 default: '-N USER@MACHINE -L LOCAL:REMOTE'
125 (populated from config file)
126
127config example:
128 # a hash mark comments to the end of the line
129 # if either 'local' or 'remote' begin with a colon,
130 # they'll be prefixed with 'localhost'
131 # machine user local remote key other
132 example.com bill :1299 :6667 # key and other are optional
133END
134}
135
136tunnel_ssh() {
137 set -- ssh "$@"
138 log "$@"
139 $_RUN || return
140
141 touch "$__PIDF"
142 while [ -e "$__PIDF" ]; do # quit if the pid file is removed
143 # make sure we haven't tunneled this connection already
144 if grep -q -e "$*" "$__PIDF"; then
117 sleep 3 145 sleep 3
118 done 146 continue
119 } 147 fi
148
149 # try to start ssh tunnel
150 if "$@" & then
151 printf '%s\t%s\n' "$!" "$*" >>"$__PIDF"
152 else
153 badpid="$!"
154 kill "$badpid"
155 sed "/^$badpid/d" "$__PIDF" >"$__PIDF~" &&
156 mv "$__PIDF~" "$__PIDF"
157 continue
158 fi
159 sleep 3
160 done
120} 161}
121 162
122tear_down() { 163tunnel_autossh() {
123 printf '%s...' "Killing extant tunnels" 164 set -- autossh -f "$@"
165 log "$@"
166 $_RUN || return
167
168 "$@" && printf '%s\t%s\n' "$!" "$*" >"$__PIDF"
169}
170
171untunnel() {
172 log "Killing tunnels"
124 if [ -e "$__PIDF" ]; then 173 if [ -e "$__PIDF" ]; then
125 while read -r pid _; do 174 while read -r pid _; do
126 kill "$pid" 2>/dev/null 175 kill "$pid" 2>/dev/null
@@ -128,8 +177,13 @@ tear_down() {
128 rm "$__PIDF" 177 rm "$__PIDF"
129 else 178 else
130 pkill -x ssh 179 pkill -x ssh
180 pkill -x autossh
131 fi 181 fi
132 echo Done. 182 log "Done"
183}
184
185log() {
186 $_LOG && printf '%s: %s\n' "$PRGN" "$*" >&2
133} 187}
134 188
135main "$@" 189main "$@"