#!/bin/sh # Copyright 2023 Helmut Grohne # SPDX-License-Identifier: MIT : <<'POD2MAN' =head1 NAME debvm-waitssh - Wait for a ssh server to be reachable =head1 SYNOPSIS B [B<-q>] [B<-t> I] [I:]I =head1 DESCRIPTION B 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, so neither a username nor a key have to be supplied. =head1 OPTIONS =over 8 =item B<-t> I, B<--timeout>=I 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 ] [:]" } 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. # # In some locales, [0-9] can match other kinds of digits, see # https://unix.stackexchange.com/a/414230/46985. 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