diff options
Diffstat (limited to 'debvm-waitssh')
-rwxr-xr-x | debvm-waitssh | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/debvm-waitssh b/debvm-waitssh new file mode 100755 index 0000000..82eb14d --- /dev/null +++ b/debvm-waitssh @@ -0,0 +1,178 @@ +#!/bin/sh +# Copyright 2023 Helmut Grohne <helmut@subdivi.de> +# SPDX-License-Identifier: MIT + +: <<'POD2MAN' +=head1 NAME + +debvm-waitssh - Wait for a ssh server to be reachable + +=head1 SYNOPSIS + +B<debvm-waitssh> [B<-q>] [B<-t> I<timeout>] [I<hostname>:]I<port> + +=head1 DESCRIPTION + +B<debvm-waitssh> can be used to wait for a virtual machine with exposed ssh port to be reachable on that port. +If no hostname is given, B<127.0.0.1> is assumed. No authentication is attempted by B<debvm-waitssh>, so neither +a username nor a key have to be supplied. + +=head1 OPTIONS + +=over 8 + +=item B<-t> I<timeout>, B<--timeout>=I<timeout> + +Set the maximum duration for waiting in seconds. +Defaults to one minute. + +=item B<-q>, B<--quiet> + +Be quiet. +Do not output a message when the timeout has been reached without success. + +=back + +=head1 EXIT VALUES + +=over 8 + +=item B<0> + +The server is reachable. + +=item B<1> + +A timeout was reached before the server answered. + +=item B<2> + +Usage error. + +=back + +=head1 SEE ALSO + + debvm-run(1) + +=cut +POD2MAN + +set -u + +TOTALTIMEOUT=60 +SCANTIMEOUT=10 +SCANDELAY=1 +VERBOSITY=1 + +nth_arg() { + shift "$1" + printf "%s" "$1" +} + +die() { + echo "$*" >&2 + exit 2 +} +usage() { + die "usage: $0 [-q] [-t <timeout>] [<host>:]<port>" +} +usage_error() { + echo "error: $*" >&2 + usage +} + +opt_help() { + # shellcheck disable=SC2317 # not dead, called as "opt_$OPTARG" + usage +} +opt_quiet() { + VERBOSITY=0 +} +opt_timeout() { + TOTALTIMEOUT=$1 +} + +while getopts :qt:-: OPTCHAR; do + case "$OPTCHAR" in + q) opt_quiet ;; + t) opt_timeout "$OPTARG" ;; + -) + case "$OPTARG" in + help|quiet) + "opt_$OPTARG" + ;; + timeout) + test "$OPTIND" -gt "$#" && usage_error "missing argument for --$OPTARG" + "opt_$OPTARG" "$(nth_arg "$OPTIND" "$@")" + OPTIND=$((OPTIND+1)) + ;; + timeout=) + "opt_${OPTARG%%=*}" "${OPTARG#*=}" + ;; + *) + usage_error "unrecognized option --$OPTARG" + ;; + esac + ;; + :) + usage_error "missing argument for -$OPTARG" + ;; + '?') + usage_error "unrecognized option -$OPTARG" + ;; + *) + die "internal error while parsing command options, please report a bug" + ;; + esac +done +shift "$((OPTIND - 1))" + +test "$#" = 1 || usage + +case "$1" in + "") + usage + ;; + *:*) + HOST=${1%:*} + PORT=${1##*:} + ;; + *) + HOST=127.0.0.1 + PORT=$1 + ;; +esac + +case "$HOST" in *@*) + die "$0: hostname '$HOST' must not contain the '@' character. No username is required." +;; esac + +# Guard against strings containing anything but digits, strings starting with +# zero and empty strings as the port number. +# +# We cannot use [!0-9] because that matches on any character (or possibly +# multi-character collation element) that sorts in between 0 and 9. +case "$PORT" in *[!0123456789]*|0?*|""|??????*) + die "$0: port '$PORT' is not an integer between 1 and 65535" +;; esac +if test "$PORT" -lt 1 -o "$PORT" -gt 65535; then + die "$0: port '$PORT' is not an integer between 1 and 65535" +fi + +now=$(date +%s) +deadline=$((now + TOTALTIMEOUT)) +while test "$now" -lt "$deadline"; do + start=$now + ssh-keyscan -t rsa -T "$SCANTIMEOUT" -p "$PORT" "$HOST" >/dev/null 2>&1 && exit 0 + now=$(date +%s) + if test "$((now - start))" -lt "$SCANTIMEOUT"; then + sleep "$SCANDELAY" + now=$(date +%s) + fi +done +if [ "$VERBOSITY" -ge 1 ]; then + echo "$0: timeout reached trying to contact $HOST:$PORT after waiting $TOTALTIMEOUT seconds." >&2 +fi +exit 1 + |