#!/usr/bin/env /bin/bash #################################### # To include this in a script: # #. ${HOME}/code/bashtools/bashtools.sh #if [[ -z $HAVE_BASHTOOLS ]]; then # echo "ERROR: bashtools not installed download from https://git.nethack.net/rob/bashtools" >/dev/stderr # exit 1 #fi # #################################### export BASHTOOLS_DIR="${HOME}/.bashtools" if [[ $1 == "install" ]]; then mkdir -p "$BASHTOOLS_DIR" cp -a $0 "$BASHTOOLS_DIR/" echo "Bashtools has been installed in $BASHTOOLS_DIR" $(return 2>/dev/null) rv="$?" if [[ $rv -ne 0 ]]; then exit 1 else return fi fi #true # need a successful command before the sourced script check #$(return 2>/dev/null) #rv="$?" #if [[ $rv -ne 0 ]]; then # echo "ERROR: This script should be sourced ('. $0') rather than run directly." # return 2>/dev/null # jist in case # exit 1; #fi if [[ $1 != "reload" && -n $HAVE_BASHTOOLS ]]; then return fi export HAVE_BASHTOOLS=1 trap bashtools_cleanup INT SPINDELAY=0.05 # delay in ms between spinner frames SPINNERFRAMES='|/-\' SPINNERFILE="$BASHTOOLS_DIR/spinner.pid" [[ ! -e $BASHTOOLS_DIR ]] && mkdir "$BASHTOOLS_DIR" [[ -e $SPINNERFILE ]] && rm -f "${SPINNERFILE}" export REGEXP_IP='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$' function ansi(){ export BOLD="\033[1m" export PLAIN="\033[0m" export ITALIC="\033[3m" export STRIKE="\033[9m" export UNDERLINE="\033[4m" export RED="\033[31m" export YELLOW="\033[33m" export GREEN="\033[32m" export BLUE="\033[34m" export MAGENTA="\033[35m" export CYAN="\033[36m" export ORANGE="${PLAIN}\033[38;2;255;165;0m" export ORANGEBOLD="${BOLD}\033[38;2;255;220;0m" export MAGENTARGB="${PLAIN}\033[38;2;208;65;126m" export MAGENTARGBBOLD="${BOLD}\033[38;2;255;135;196m" export INFOCOL="$CYAN" export INFORMCOL="$ORANGE" export INFORMCOLB="$ORANGEBOLD" export NOTIFYCOL="$MAGENTARGB" export NOTIFYCOLB="$MAGENTARGBBOLD" export GREY="\033[38;2;110;110;110m" export LINK="$BLUE$UNDERLINE" export UNDERLINE_PRINTED=$(echo -en "$UNDERLINE") export UNDERLINE_LENGTH=${#UNDERLINE_PRINTED} } function noansi() { local allcols c allcols=$(set | egrep "\\033.*m\'" | sed 's/=.*//') for c in $allcols; do eval "$c=''" done } # provide specific "bold" colour function cecho() { # [-n] [-s] col boldcol "str" local col bcol str minusn="" autobold=0 while [[ ${1:0:1} == "-" ]]; do if [[ $1 == "-n" ]]; then minusn="-n" fi if [[ $1 == "-s" ]]; then autobold=1 fi shift done col="$1" shift if [[ $autobold -eq 1 ]]; then bcol="$col$BOLD" else bcol="$1" shift fi str="$*" str=${str//\^b/${bcol}} str=${str//\^p/${PLAIN}${col}} str=${str//\^i/${col}${ITALIC}} str=${str//\^u/${col}${UNDERLINE}} str=${str//\^s/${col}${STRIKE}} echo $minusn -e "${col}${str}${PLAIN}" } # use bold version of supplied colour function csecho() { # [-n] col "str" local args col while [[ $# -gt 2 ]]; do args="$args $1" shift done col="$1" shift cecho $args -s "$col" "$*" } alias try=notify function notify() { #echo -en "${NOTIFYCOLB}* ${NOTIFYCOL}$*...${PLAIN} " cecho -n "$NOTIFYCOL" "$NOTIFYCOLB" "^b* ^p$*... " >/dev/stderr if [[ $NOSPINNER -ne 1 ]]; then start_spinner & tput civis >/dev/stderr fi innotify=1 } function inform() { cecho "$INFORMCOL" "$INFORMCOLB" "^b* ^p$* " >/dev/stderr } function info() { [[ $AUTOYES -eq 1 ]] && return csecho "${INFOCOL}" "^b>>^p $*" >/dev/stderr } function bashtools_cleanup() { stop_spinner innotify=0 echo -e "$PLAIN" } function ok() { local msg=${*:-ok} stop_spinner [[ $innotify -eq 0 ]] && return 1 innotify=0 echo -e "$GREEN$msg$PLAIN" >/dev/stderr } function fail() { local msg=${*:-failed} stop_spinner [[ $innotify -eq 0 ]] && return 1 innotify=0 echo -e "$RED$msg$PLAIN" >/dev/stderr } function partial() { local msg=${*:-partial} stop_spinner [[ $innotify -eq 0 ]] && return 1 innotify=0 echo -e "$YELLOW$msg$PLAIN" >/dev/stderr } function warn() { cecho -s "$YELLOW" "^bWarning: ^p$*" >/dev/stderr } function error() { cecho -s "$RED" "^bERROR: ^p$*" >/dev/stderr } function start_spinner() { local idx=0 len spid ( len=${#SPINNERFRAMES} echo -n " " >/dev/stderr while [ 1 ] ; do echo -en "\b${SPINNERFRAMES:$idx:1}" >/dev/stderr idx=$((idx + 1)) [[ $idx -ge $len ]] && idx=0 sleep $SPINDELAY done ) & spid=$! echo $spid >>"$SPINNERFILE" } function stop_spinner() { local f pid [[ $NOSPINNER -eq 1 ]] && return 0 [[ ! -e "$SPINNERFILE" ]] && return 1 while read -r pid ; do if [[ $pid = *[[:digit:]]* ]]; then { kill $pid && wait $pid; } 2>/dev/null echo -en "\b " >/dev/stderr fi done < "$SPINNERFILE" rm -f "$SPINNERFILE" tput cnorm >/dev/stderr } function getsysstats() { CPUS=$(/usr/bin/grep ^processor /proc/cpuinfo | /usr/bin/wc -l | /usr/bin/bc) MEM_GB=$(dmidecode -t memory | awk 'BEGIN { tot=0 } /Size: [0-9]/ { tot += $2 } END { print tot }') MEM_MB=$(echo "$MEM_GB * 1024" | bc ) DISKFREE_MB=$(df -m /data | grep -v Filesystem | awk '{ print $4 }') DISKFREE_GB=$(echo "$DISKFREE_MB / 1024" | bc ) } function ask() { # [-s == dont echo input] $1 = prompt $default_val $2 = return_variable_name local answer prompt default retvar readopts="" if [[ $1 == "-s" ]]; then readopts="-s" shift fi prompt="$1" default="$2" retvar="$3" cecho -n -s "$YELLOW" "$1 " read $readopts answer [[ -z $answer ]] && answer="$default" eval "$retvar=\"$answer\"" } # ask [ -r RETVAR ] [-R NUMRETVAR] [-a] [-c num] [-l v|h ] [-x AB] [ -q "question text" ] [ -d default ] choice1 choice2 ... choiceN [ -D desc1 desc2 ... descN ] function menu() { local answer answernum autoselect bestval local cols choice choiceformat cperrow cwidth default defaultnum desc local idx layout mode n nchoices ndescs local question retvar retvarnum thislen thisnum thistext local x y repfrom repto local found autoselect=0 default="" question="Select an option:" mode="choices" layout=v # 'v'ertical or 'h'orizontal nchoices=0 ndescs=0 cwidth=0 retvar=_SELECTION cperrow=-1 found=0 while [ $# -ge 1 ]; do case $1 in "-a") autoselect=1 ;; "-c") shift cperrow="$1" ;; "-d") shift default="$1" ;; "-l") shift layout="$1" ;; "-r") shift retvar="$1" ;; "-R") shift retvarnum="$1" ;; "-x") shift repfrom="${1:0:1}" repto="${1:1:1}" ;; "-q") shift question="$1" ;; "-D") mode="descriptions" ;; *) if [ "$mode" == "choices" ]; then choice[${nchoices}]="$1" if [[ ! -z $repfrom && ! -z $repto ]]; then choice[${nchoices}]=`echo ${choice[$nchoices]} | tr "$repfrom" "$repto"` fi thislen=${#1} if [ $thislen -gt $cwidth ]; then cwidth=$thislen fi nchoices=$((nchoices + 1)) else desc[${ndescs}]="$1" # length of this description + length of matching choice # + 3 for parantheses thislen=$(( ${#1} + ${#choice[$ndescs]} + 3)) ndescs=$((ndescs + 1)) if [ $thislen -gt $cwidth ]; then cwidth=$thislen fi fi ;; esac shift done # Validate layout if [[ ! $layout =~ v|h ]] ; then error "error in myask(): invalid layout. Must be 'v'ertical or 'h'orizontal. [$question]" die fi # Validate descriptions if [ $ndescs -gt $nchoices ]; then error "error in myask(): Number of descriptions ($ndescs) is larger than number of choices ($nchoices)! [$question]" die fi # Validate default choice (if given) if [ -n "$default" ]; then defaultnum=-1 for n in ${!choice[@]}; do if [ "${choice[$n]}" == "$default" ]; then defaultnum=$n fi done if [ $defaultnum -eq -1 ]; then error "error in myask(): given default '$default' does not match any choice. [$question]" csecho "$RED" " choices are:" for n in ${!choice[@]}; do csecho "$RED" " ${choice[$n]}" done die fi fi # Determine choice width - longest choice + 6 chars # to account for 'xxx) ' cwidth=$((cwidth + 5)) # Determine maximum amount of choices per row if [ $cperrow -eq -1 ]; then if [ $nchoices -le 3 ]; then cperrow=1 else cols=$(tput cols) cperrow=$((cols / cwidth)) fi fi # Reduce choices per row to minimise (nchoices % cperrow). # ie. try to make list of choices as close to a # grid shape as possible. # bestval=$(( cperrow - (nchoices % cperrow) )) n=$((cperrow - 1)) while [ $n -ge 1 ]; do thisval=$(( cperrow - (nchoices % n) )) if [ $thisval -lt $bestval ]; then bestval=$thisval cperrow=$n fi n=$((n - 1)) done # Determine how many rows we'll need for a # vertical layout (ie. counting downwards instead # of right). if [ "$layout" == "v" ]; then choicerows=$((nchoices / cperrow)) if [ $((nchoices % cperrow)) -gt 0 ]; then choicerows=$((choicerows + 1)) fi fi # printf format string choiceformat="${GREEN}${BOLD}%3s${PLAIN}${GREEN}) %-$((cwidth-5))s$PLAIN" answer="" # Prompt for a choice until we get a valid answer. while [ -z "$answer" ]; do # Display menu csecho "$GREEN" "^b$question^p" if [ "$layout" == "h" ]; then for n in ${!choice[@]}; do if [ -n "${desc[$n]}" ]; then thistext="${choice[$n]} (${desc[$n]})" else thistext="${choice[$n]}" fi thisnum=$((n + 1)) if [ "${choice[$n]}" == "${default}" ]; then thisnum="*$thisnum" fi printf "$choiceformat" "$thisnum" "${thistext}" if [ $(( (n + 1) % $cperrow )) -eq 0 ]; then printf "\n" # newline fi done else # ie. vertical n=0 for (( y=1; y<=$choicerows; y++)); do for (( x=0; x<$cperrow; x++)); do idx=$((n + (x * choicerows))) if [ $idx -lt $nchoices ]; then if [ -n "${desc[$idx]}" ]; then thistext="${choice[$idx]} (${desc[$idx]})" else thistext="${choice[$idx]}" fi thisnum=$((idx + 1)) if [ "${choice[$idx]}" == "${default}" ]; then thisnum="*$thisnum" fi printf "$choiceformat" "$thisnum" "${thistext}" fi done n=$((n + 1)) printf "\n" # newline done fi printf "\n" if [ -n "$default" ]; then csecho "$GREEN" " ^i(* denotes default)^p" fi csecho -n "$GREEN" "^b-> ^p" # If there's only one answer, just select it. Useful for # cases where we are basing our list of choices on a dynamic variable. if [ $autoselect -eq 1 -a $nchoices -eq 1 ]; then answernum=1 csecho "$GREEN" "1 (autoselect)" # make it look like we typed it else echo -e -n "$GREEN" read answernum echo -e -n "$PLAIN" fi if [ -z "$answernum" ]; then if [ -n "$default" ]; then answer=$default info "[defaulting to $answer]" # populate 'answernum' correctly for z in ${!choice[@]}; do if [[ ${choice[$z]} == $answer ]]; then answernum=$((z + 1)) break fi done else csecho "$RED" "Invalid response.\n" fi elif [[ ! $answernum =~ ^[0-9]*$ ]] ; then # try to match based on name found=0 shopt -s nocasematch for z in ${!choice[@]}; do if [[ ${choice[$z]} =~ $answernum ]]; then possanswernum[$found]=$((z + 1)) possanswer[$found]=${choice[z]} found=$((found + 1)) fi done if [[ $found -eq 1 ]]; then selectedposs=${possanswer[0]} selectedpossnum=${possanswernum[0]} answerhilite=${selectedposs/${answernum}/^b${answernum}^p} star="" if [[ -z $BOLD ]]; then star="*" fi csecho "$CYAN" "Matched '${star}${answerhilite}${star}'.\n" answernum="$selectedpossnum" answer="$selectedposs" elif [[ $found -gt 1 ]]; then csecho "${RED}" "Invalid response - '^b$answernum^p' matches $found answers.\n" for z in ${!possanswer[@]}; do csecho "$RED" " ${possanswernum[$z]}) ${possanswer[$z]}" done else csecho "${RED}" "Invalid response.\n" fi shopt -u nocasematch elif [ $answernum -lt 1 -o $answernum -gt $nchoices ]; then csecho "$RED" "Choice out of range.\n" else answer=${choice[$((answernum - 1))]} fi done answer="${answer//\'/\\\'}" eval "$retvar=$'$answer'" eval "$retvarnum='$answernum'" return 0 } function checkreqs() { # appname cpus ram_in_gb disk_in_gb local rv=0 appname errs x appname="$1" shift getsysstats try "Checking system requirements" if [[ $CPUS -lt $1 ]]; then errs+=("- CPUs required: ^b${1}^p (system has $CPUS)") fi if [[ $MEM_GB -lt $2 ]]; then errs+=("- RAM required: ^b${2} GB^p (system has ${MEM_GB} GB)") fi if [[ $DISKFREE_GB -lt $3 ]]; then errs+=("- Disk space required: ^b${3} GB^p (system has ${DISKFREE_GB} GB)") fi if [[ -z $errs ]]; then ok rv=0 else fail error "This system does not meet the requirements for ^b${appname}^p." for x in ${!errs[@]}; do cecho -s "$RED" " ${errs[$x]}" done rv=1 fi return $rv } function generate_ssl_cert() { # usage: $0 fqdn (populates global $sslcert and $sslkey ) local ssp name local country state loc org orgunit email cn name="$1" sslcert="" sslkey="" [[ -z $name ]] && return 1; sslp=$(cat /proc/sys/kernel/random/uuid) cd /etc/ssl/certs/ openssl genrsa -aes128 -passout pass:${sslp} 2048 > $name.key openssl rsa -in $name.key -passin pass:${sslp} -out $name.key country=AU state=Empty loc=Empty org=NTT orgunit=IT email=root@localhost cn=${fqdn} openssl req -utf8 -new -key $name.key -out $name.csr -subj "/C=$country/ST=$state/L=$loc/O=$org/OU=orgunit/CN=$cn/emailAddress=$email" # note: change this later to: # 1. use our CA # 2. not last for 1000 years openssl x509 -in $name.csr -out $name.crt -req -signkey $name.key -days 36500 chmod 600 $name.key # Set globals sslcert=/etc/ssl/certs/$name.crt sslkey=/etc/ssl/certs/$name.key cd - >/dev/null 2>&1 return 0 } function dnslookup() { # $1 = a_record_to_look_up local answer rv ip digargs="" if [[ $ip =~ $REGEXP_IP ]]; then digargs="-x" fi answer=$(dig +short $digargs ${1} 2>/dev/null) rv=$? answer=${answer%.} echo "$answer" return $rv } function profile() { local now str diff [[ -z $__PROFILE || $__PROFILE -ne 1 ]] && return; #now=$(date +%Y/%m/%d-%H:%M:%S) now=$(printf "%0.f" "$(bc <<<"$(gdate +"%s.%N")*1000")") str="^b${FUNCNAME[1]}()^p ===> ^b$*^p" if [[ -n $__PROF_PREV ]]; then diff=$(echo "scale=2; ($now - $__PROF_PREV)" | bc) str="$str $GREEN($BOLD+$diff ms$PLAIN$GREEN)^p" fi cecho -s "${MAGENTA}" "$str" >&2 __PROF_PREV=$now } #declare -fx $(bash -c "source $0 &> /dev/null; compgen -A function") ansi declare -fx $(compgen -A function | grep -v ^_)