#!/usr/bin/env bash # # SSH Connection Utility (SCU) # - ssh connection utility with pre-defined hosts list # https://github.com/dualmi/scu # # This project is licensed under GPL 3 License - see [https://www.gnu.org/licenses/gpl-3.0.en.html] # set -euo pipefail if (( BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 3) )); then echo "SCU requires bash >= 4.3" >&2 exit 1 fi declare -A sshhostlist; : "${ansiblepath:=}" : "${netbox_hostname:=}" : "${curl_connect_timeout:=3}" : "${netbox_listing_limit:=1000}" : "${netbox_cache_refresh_time:=300}" hosts_file="$HOME/.scurc"; host_connect="${1:-}" format_epoch() { local ts="$1" [[ -n "$ts" && "$ts" != 0 ]] || { echo "unknown" return } if date -r 0 >/dev/null 2>&1; then date -r "$ts" "+%Y-%m-%d %H:%M:%S" else date --date="@$ts" "+%Y-%m-%d %H:%M:%S" fi } echo -e "\n\tSSH Connection Utility with predefined hosts list\n"; # Hosts file check part. # If file doesn't exists it can be generated when 'init' param is set. if [[ -f $hosts_file ]]; then # shellcheck source=/dev/null source "$hosts_file" else if [[ ${1:-} = "init" ]]; then # At next step example config file will be generated cat > "$hosts_file" < "$hosts_file.yaml" <"$ts_file"; } curl_netbox_request() { local type="${1:-}" local location="" local outfile="${2:?Outfile must be provided during the function call}" if [[ "$type" == "virtual_machines" ]]; then location="/api/virtualization/virtual-machines/?limit=${netbox_listing_limit}&include=primary_ip" elif [[ "$type" == "devices" ]]; then location="/api/dcim/devices/?limit=${netbox_listing_limit}&include=primary_ip" fi # shellcheck disable=SC2086,SC2090 curl --connect-timeout ${curl_connect_timeout} -fsS -H "Authorization: Token ${NETBOX_TOKEN}" -H 'Accept: application/json' "${netbox_hostname}${location}" \ | jq -r '.results[] | select(.primary_ip4.address != null) | "\(.name):\(.config_context.ansible_user // "root" ) \(.primary_ip4.address | split("/")[0]) \(.config_context.ansible_port // 22 ) \(.config_context.ansible_ssh_common_args // "")"' | sed 's/[[:space:]]*$//' >> "$outfile" || return 1 } cache_refresh_job() { mkdir "$lock_dir" 2>/dev/null || return 0 trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT local tmp tmp="$(mktemp "$cache_dir/netbox.cache.XXXXXX")" if curl_netbox_request virtual_machines "$tmp" && curl_netbox_request devices "$tmp"; then if awk ' { if ($1 ~ /^[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$/ && $2 ~ /^([0-9]+\.){3}[0-9]+$/ && $3 ~ /^[0-9]+$/ ) { ok=1 } } END { exit(ok ? 0 : 1) } ' "$tmp"; then mv -f "$tmp" "$cache_file" return 0 fi fi rm -f "$tmp" return 1 } cache_refresh_background() { cache_mark_refresh_attempt ( exec /dev/null 2>&1 cache_refresh_job ) & disown 2>/dev/null || true } if cache_load; then : # sshhostlist loaded else cache_mark_refresh_attempt if cache_refresh_job; then cache_load || true fi fi if [[ -s "$cache_file" ]] && cache_should_refresh; then cache_refresh_background fi fi # Indexing hosts list. After that connection by host number will be available. declare -A array_index; array_i=1; for value in "${!sshhostlist[@]}"; do array_index[$value]=$array_i; array_i=$(( array_i+1 )) done # connect to selected host function sshconnect() { local host_to_connect=$1 for sshhost in "${!sshhostlist[@]}"; do if [[ $sshhost = "$host_to_connect" ]] || [[ ${array_index[$sshhost]} = "$host_to_connect" ]]; then read -r USERNAME ADDRESS PORT SSHARGS <<<"${sshhostlist[$sshhost]}" PORT=${PORT:-22} SSHARGS=${SSHARGS//\\\"/\"} echo -n "Connecting to $sshhost with command: "; bash -x -c "ssh -p $(printf %q "$PORT") $SSHARGS $(printf %q "$USERNAME@$ADDRESS")" exit 0; fi done } # Connecting to host if exists sshconnect "$host_connect" # # Usage information and hosts list # which will be displayed if no valid host was choosen # echo -e "\t\tUsage: $0 \n"; echo "Available hosts:"; counter=1; counter2=1; for value in "${!sshhostlist[@]}"; do name="$value"; value="${array_index[$value]}: $value"; if [[ ${#value} -gt 7 ]]; then if [[ ${#value} -gt 15 ]]; then RES="$value\t"; else RES="$value\t\t\t"; fi else RES="$value\t\t\t\t"; fi if [[ -n $host_connect ]]; then [[ $RES =~ $host_connect ]] || continue; MATCHVALUE="$name"; echo -ne "$RES"; else echo -ne "$RES"; fi counter=$(( counter+1 )) counter2=$(( counter2+1 )) if [[ $counter -gt 3 ]]; then echo ""; counter=1; fi done echo ""; if [ $counter2 -eq 2 ] && [ -n "$MATCHVALUE" ]; then echo "Connecting to only one matched host $MATCHVALUE..."; sshconnect "$MATCHVALUE"; exit 0; fi if [ -n "$host_connect" ] && [ $counter2 -eq 1 ]; then echo "There is no host to connect with name you wrote..."; fi echo ""; if [ -n "$host_connect" ] && [ $counter2 -gt 1 ]; then echo -n "Enter host number / host name to connect: " read -r input sshconnect "$input" exit 0; fi