diff options
-rwxr-xr-x | mrgrctrnl | 198 |
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 | ||
7 | PRGN="${0##*/}" | 7 | PRGN="${0##*/}" |
8 | 8 | ||
9 | usage() { | ||
10 | cat <<END | ||
11 | $PRGN: make magic ssh tunnels | ||
12 | usage: $PRGN [-h] [-k] | ||
13 | $PRGN [-n] [-c CONF | -s SSH] | ||
14 | |||
15 | options: | ||
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. | ||
24 | END | ||
25 | } | ||
26 | |||
27 | # entry point | 9 | # entry point |
28 | main() { | 10 | main() { |
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 | ||
97 | tunnel() { | 103 | usage() { |
98 | # shellcheck disable=2086 | 104 | cat <<END |
99 | $_quiet || echo ssh "$@" | 105 | $PRGN: make magic ssh tunnels |
100 | $_run && { | 106 | usage: $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" && { | 109 | options: |
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 | |||
127 | config 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 | ||
133 | END | ||
134 | } | ||
135 | |||
136 | tunnel_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 | ||
122 | tear_down() { | 163 | tunnel_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 | |||
171 | untunnel() { | ||
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 | |||
185 | log() { | ||
186 | $_LOG && printf '%s: %s\n' "$PRGN" "$*" >&2 | ||
133 | } | 187 | } |
134 | 188 | ||
135 | main "$@" | 189 | main "$@" |