Skip to content

Commit 0902ed6

Browse files
authored
ssh-et: apply upstream updates (#527)
- Fixes SSH config not being applied to original remote - Adds retries in case multiple processes are spawned concurrently (can happen with tmux-xpanes for example)
1 parent 02d9dce commit 0902ed6

File tree

1 file changed

+57
-28
lines changed

1 file changed

+57
-28
lines changed

scripts/ssh-et

+57-28
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/env bash
22
# Usage:
33
# ssh-et [ssh_options] <remote>
4-
#
5-
# See also https://github.com/infokiller/ssh-et
64

75
# See https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
86
set -o errexit -o errtrace -o nounset -o pipefail
97

8+
readonly MAX_RETRIES=10
9+
1010
_command_exists() {
1111
command -v -- "$1" &> /dev/null
1212
}
@@ -57,13 +57,18 @@ _check_args() {
5757
fi
5858
}
5959

60+
# https://stackoverflow.com/a/6943581
61+
_is_port_open() {
62+
local port="$1"
63+
! { printf '' > /dev/tcp/127.0.0.1/"${port}"; } &> /dev/null
64+
}
65+
6066
# The range 49152–65535 contains ephemeral/dynamic ports. We scan 200 ports
6167
# that start with the prefixes "522" or "622" (the 22 part of the prefix is
6268
# useful to remember it's used for SSH).
6369
_find_ephemeral_port() {
6470
for port in {52200..52299} {62200..62299}; do
65-
# Check if port is open. See: https://stackoverflow.com/a/6943581
66-
if ! { printf '' > /dev/tcp/127.0.0.1/"${port}"; } &> /dev/null; then
71+
if _is_port_open "${port}"; then
6772
echo "${port}"
6873
return 0
6974
fi
@@ -82,34 +87,58 @@ main() {
8287
# defined when the trap is ran.
8388
# shellcheck disable=SC2064
8489
trap "rm -rf -- '${tmpdir}' &> /dev/null || true" EXIT ERR INT HUP TERM
85-
local port
86-
if ! port="$(_find_ephemeral_port)"; then
87-
_log_error 'Could not find an ephemeral port'
88-
return 2
89-
fi
90-
_log_info "Found open port: ${port}"
9190
local et_fifo="${tmpdir}/et_fifo"
9291
mkfifo "${et_fifo}" || return $?
93-
local et_cmd=(et -t "${port}":22 -N "${remote}")
94-
_log_info "Running: ${et_cmd[*]}"
95-
"${et_cmd[@]}" > "${et_fifo}" &
96-
et_pid=$!
97-
found=0
98-
while IFS='' read -r line; do
99-
printf 'et: %s\n' "${line}"
100-
if [[ $line == *"feel free to background"* ]]; then
101-
found=1
102-
break
92+
local port num_retries=0 success=0 has_stdout=0
93+
while ((!success && num_retries < MAX_RETRIES)); do
94+
if ! port="$(_find_ephemeral_port)"; then
95+
_log_error 'Could not find an ephemeral port'
96+
return 1
10397
fi
104-
done < "${et_fifo}"
105-
((found)) || return 3
106-
# We use the localhost loopback address for all remote hosts, so we don't want
107-
# to register it in the known hosts file or do any host auth against it (which
108-
# will lead to errors since SSH will think the host key changed). et does its
109-
# own host auth so this is hopefully safe.
98+
_log_info "Found open port: ${port}"
99+
local et_cmd=(et -t "${port}":22 -N "${remote}")
100+
_log_info "Running: ${et_cmd[*]}"
101+
"${et_cmd[@]}" > "${et_fifo}" &
102+
et_pid=$!
103+
success=0
104+
has_stdout=0
105+
while IFS='' read -r line; do
106+
has_stdout=1
107+
printf 'et: %s\n' "${line}"
108+
if [[ $line == *"Address already in use"* ]]; then
109+
wait "${et_pid}" || true
110+
if ((num_retries < MAX_RETRIES - 1)); then
111+
_log_info 'Port became used, finding another one...'
112+
sleep 0.$((RANDOM))
113+
fi
114+
((num_retries += 1))
115+
break
116+
fi
117+
if [[ $line == *"feel free to background"* ]]; then
118+
success=1
119+
break
120+
fi
121+
return 1
122+
done < "${et_fifo}"
123+
((has_stdout)) || return 1
124+
done
125+
((success)) || return 1
126+
# We use the localhost loopback address for all remote hosts, but we still
127+
# want to use the SSH options set for the remote in the client
128+
# config. Therefore, we use the original remote hostname, but override two SSH
129+
# options:
130+
# - HostName: to connect to the loopback address
131+
# - HostKeyAlias: to avoid warnings about the host key file
132+
# NOTE: We must put the user provided SSH args first, since SSH gives priority
133+
# to the *first* args it encounters.
134+
# TODO: This doesn't work correctly when using the "%h" token in the ssh
135+
# config, since it will be set to the loopback address instead of the remote
136+
# name given on the command line. One possible workaround to explore is to
137+
# create a temporary SSH config that includes the real config, and makes sure
138+
# to only set HostName after all other config is passed.
110139
local ssh_cmd=(
111-
ssh -o 'UserKnownHostsFile=/dev/null' -o 'StrictHostKeyChecking=no'
112-
'127.0.0.1' -p "${port}" "${ssh_args[@]}"
140+
ssh -p "${port}" "${ssh_args[@]}" -oHostName='127.0.0.1'
141+
-oHostKeyAlias="${remote}" "${remote}"
113142
)
114143
_log_info "Running: ${ssh_cmd[*]}"
115144
"${ssh_cmd[@]}"

0 commit comments

Comments
 (0)