#!/usr/bin/env bash # shellcheck disable=SC2034 # variable not used # Exit on error. Append "|| true" if you expect an error. set -o errexit # Exit on error inside any functions or subshells. set -o errtrace # Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR set -o nounset # Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip` set -o pipefail # Turn on traces, useful while debugging but commented out by default # set -o xtrace ## Globals { readonly _sqr_version=16 readonly _sqr_versionMajor=0 readonly _sqr_versionMinor=0 readonly _sqr_versionString="${_sqr_version}.${_sqr_versionMajor}.${_sqr_versionMinor}" ## Seq readonly seq_name="${_sqn_alias:-${0##*/}}" readonly seq_dir="$(cd -- "$(dirname -- "${0}")" && pwd)" readonly seq_origin="$(cd -- "$(dirname -- "$(readlink -f -- "${0}")")" && pwd)" readonly seq_file=$(basename -- "${0}") readonly seq_fileName=${seq_file%%.*} # shellcheck disable=SC2015 # && || is not if else readonly seq_invocation="$(printf '%q' "${0}")$( (($#)) && printf ' %q' "$@" || true)" # May be overwritten by seq seq_configFile="${seq_fileName}.cfg" seq_configTemplate="${0%.*}.cfg.example" ## Sequencer readonly sqr_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly sqr_file="$(basename -- "${BASH_SOURCE[0]}")" readonly sqr_name="${sqr_file%%.*}" readonly sqr_origin="$(cd -- "$(dirname -- \ "$(readlink -f -- "${BASH_SOURCE[0]}")")" && pwd)" _sqr_stepMax=512 _sqr_interactive=1 _sqr_debug=0 _sqr_dry=0 _sqr_verbose=0 # Colors col_black= ; [ -t 1 ] && col_black='\033[0;30m' col_darkgrey= ; [ -t 1 ] && col_darkgrey='\033[1;30m' col_red= ; [ -t 1 ] && col_red='\033[0;31m' col_lightred= ; [ -t 1 ] && col_lightred='\033[1;31m' col_green= ; [ -t 1 ] && col_green='\033[0;32m' col_lightgreen= ; [ -t 1 ] && col_lightgreen='\033[1;32m' col_orange= ; [ -t 1 ] && col_orange='\033[0;33m' col_yellow= ; [ -t 1 ] && col_yellow='\033[1;33m' col_blue= ; [ -t 1 ] && col_blue='\033[0;34m' col_lightblue= ; [ -t 1 ] && col_lightblue='\033[1;34m' col_purple= ; [ -t 1 ] && col_purple='\033[0;35m' col_lightpurple= ; [ -t 1 ] && col_lightpurple='\033[1;35m' col_cyan= ; [ -t 1 ] && col_cyan='\033[0;36m' col_lightcyan= ; [ -t 1 ] && col_lightcyan='\033[1;36m' col_lightgray= ; [ -t 1 ] && col_lightgray='\033[0;37m' col_white= ; [ -t 1 ] && col_white='\033[1;37m' col_off= ;[ -t 1 ] && col_off='\033[0m' # No Color } ## Logging { LOG_LEVEL="${LOG_LEVEL:-3}" # 4 = debug -> 0 = fatal (stop) LOG_TIME="${LOG_TIME:-}" # 1 = show time stamps # sqr::log [LOG LEVEL] [LOG_COLOR] [OPTIONS] [LOG MESSAGE] # Construct log messages # # [OPTIONS] # -a : append text (no info and timestamp) # Uses color from last sqr::log call without -a # -e : Output to stderr # -- : End of options # sqr::log () { sqr::debugPause local appendText= local arg= local col_end="${col_off}" local outp='/dev/stdout' local log_level="${1:-}" shift local log_color="${1:-}" shift for arg in "${@}" ; do case "${1:-}" in --) shift && break ;; -a) appendText=1 shift ;; -e) outp='/dev/stderr' shift ;; esac done [[ -z "${log_color}" ]] && col_end="" # all remaining arguments are to be printed local log_line="" while IFS=$'\n' read -r log_line ; do printf '%b' "${log_color}" >${outp} if [[ -n "${LOG_TIME}" ]] ; then if (( appendText )) ; then printf '%24s' "" >${outp} else printf '%s ' "$(date -u +"%Y-%m-%d %H:%M:%S UTC")" >${outp} fi fi if (( ! appendText )) ; then printf " %3s " "[${log_level}]" >${outp} printf "%s" "${log_line}" >${outp} else # +3 : "[] " printf "%$((${#log_level} + 4))s%s" "" "${log_line}" >${outp} fi printf '%b\n' "${col_end}" >${outp} done <<< "${@:-}" sqr::debugContinue } stop () { sqr::log "stop" "${col_red}" "${@}"; exit 1; } error () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && sqr::log "e" "${col_red}" "${@}"; true; } warning () { [[ "${LOG_LEVEL:-0}" -ge 2 ]] && sqr::log "w" "${col_orange}" "${@}"; true; } info () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && sqr::log "i" "" "${@}"; true; } debug () { [[ "${LOG_LEVEL:-0}" -ge 4 ]] && sqr::log "dbug" "${col_lightpurple}" "${@}"; true; } # internal printf same loglevel as info # shellcheck disable=SC2059 # don't use variables in format sqr::print () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && printf "$@"; true; } sqr::debugPause() { if (( _sqr_debug )) ; then set +o xtrace; else true; fi } sqr::debugContinue() { if (( _sqr_debug )) ; then set -o xtrace; else true; fi } # color [BACKGROUND COLOR] color() { [ ! -t 1 ] && return 0 [ -z "${1:-}" ] && tput sgr0 && return 0 case "${1:-}" in black) tput setaf 0 ;; red) tput setaf 1 ;; green) tput setaf 2 ;; yellow) tput setaf 3 ;; blue) tput setaf 4 ;; magenta) tput setaf 5 ;; cyan) tput setaf 6 ;; white) tput setaf 7 ;; none) tput sgr0 return 0 ;; *) tput setaf ${1:-} ;; esac case "${2:-}" in black) tput setab 0 ;; red) tput setab 1 ;; green) tput setab 2 ;; yellow) tput setab 3 ;; blue) tput setab 4 ;; magenta) tput setab 5 ;; cyan) tput setab 6 ;; white) tput setab 7 ;; esac } } ## Traps { sqr::trap_exit () { exists -f seq_trapExit && seq_trapExit debug "Sequencer exit" } trap sqr::trap_exit EXIT # requires `set -o errtrace` sqr::error_report() { local error_code=${?} error "Error in ${sqr_file} in function ${1} on line ${2}" exit ${error_code} } # Uncomment the following line for always providing an error backtrace # trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR } # check if there is another PID other than this one running() { pidof -o %PPID -x "${0##*/}">>/dev/null } # exists [-f] [--] [ELEMENT] # [ELEMENT] # : either a variable name or # -f : function exists() { sqr::debugPause local func= local arg= for arg in "$@" ; do case "${1:-}" in --) shift && break ;; -f) func="${2:-}" esac done sqr::debugContinue if [[ -n "${func}" ]] ; then declare -F "${func}" &>>/dev/null else [[ -n "${!1:-}" ]] fi } # interactive # Started with -q to use defaults for confirmations interactive() { (( _sqr_interactive )) } # quiet # Log level smaller 3 (info) are treated as quiet request quiet() { [[ $LOG_LEVEL -lt 3 ]] } # silent # Log level equals 0 (stop) silent() { [[ $LOG_LEVEL -eq 0 ]] } # dry-run # Started with --dry-run dry() { (( _sqr_dry )) } # verbose # Started with --verbose verbose() { (( _sqr_verbose )) } ### interactive # confirm [OPTIONS] [--] [QUESTION] # Default (empty character) = no # # [OPTIONS] # -f : interactive even if quiet # -n : no input help # -y : default = yes # -- : end of options confirm() { sqr::debugPause local arg= local rexReply='^[Yy]$' # default no local inputHelp='[y/N] ' # default no local noHelp=0 local force=0 for arg in "${@}" ; do case "${1:-}" in --) shift && break ;; -f) force=1 shift ;; -n) noHelp=1 shift ;; -y) rexReply='^[Yy]*$' # default yes inputHelp='[Y/n] ' # default yes shift ;; esac done (( noHelp )) && inputHelp= if interactive || (( force )) ; then read -r -p "${1:-} ${inputHelp}" -n 1 echo "" else REPLY='' fi sqr::debugContinue [[ $REPLY =~ ${rexReply} ]] } # ask [OPTION] [QUESTION] [DEFAULT] # Will ask for input even if quiet, when [DEFAULT] is empty # # [OPTION] # -e : allow empty input # Ignored if [DEFAULT] is available # -s : ask for secret (don't print input) # Does not add a newline. Usage: # pass=$(ask -s "Password"); echo # -- : End of options ask() { sqr::debugPause local hidden= local empty=0 local arg= for arg in "$@" ; do case "${1}" in --) shift && break ;; -e) empty=1 shift ;; -s) hidden="-s" shift ;; esac done local answer= if [[ -n "${2:-}" ]] ; then ! interactive && printf '%s\n' "${2}" && sqr::debugContinue && return 0 read ${hidden?} -r -p "${1:-"User input"} ($2) " answer else read ${hidden?} -r -p "${1:-"User input"} " answer fi if [[ -z "$answer" ]] ; then answer="${2:-}" fi printf '%s\n' "${answer}" # return if answer is empty sqr::debugContinue if (( ! empty )) ; then [[ -n "${answer}" ]] fi } # listSteps [FILTER STRING] # [FILTER STRING] # show only steps and aliases starting with [FILTER STRING] listSteps() { local aList=() local aSearch="${1:-}" local locAlias= for ((i=1; i<=${_sqr_stepMax}; i++)); do # Display step reference in help if step function exists exists -f step_${i} || continue # Display alias if exists if exists -f step_${i}_alias ; then step_${i}_alias locAlias=("$ALIAS") else locAlias=("$i") fi if [ -z "$aSearch" ]; then aList+=("$locAlias") elif [[ "$locAlias" =~ ^$aSearch ]]; then aList+=("$locAlias") fi done [ ${#aList[@]} -ne 0 ] && printf '%s\n' "${aList[@]}" } # showVersion showVersion() { printf 'Sequencer %s\n' "${_sqr_versionString}" printf 'Seq Revision %s\n' "${VERSION_SEQREV:-"-"}" } # exe # Handle dry run and verbose output for commands without pipe and/or redirects exe() { dry && printf -- '--' if dry || verbose ; then (set -x; : "$@") fi if ! dry ; then "$@" fi } # exep # Handle dry run and verbose output for commands containing pipe and/or redirects exep() { if dry ; then printf -- '--++ : %s\n' "$*" elif verbose ; then printf '++ : %s\n' "$*" fi if ! dry ; then bash -c "$*" fi } sqr::main() { # options check for arg in "$@" ; do case "$1" in --debug) _sqr_debug="1" shift ;; --dry-run|-d) _sqr_dry=1 shift ;; --liststeps|-ls) shift listSteps "${1:-}" exit 0;; --quiet|-q) _sqr_interactive=0 shift ;; --silent|-qq) _sqr_interactive=0 LOG_LEVEL=0 shift ;; --verbose|-v) _sqr_verbose=1 shift ;; --version) # version request showVersion exit 0;; esac done # debug mode if [[ "${_sqr_debug:-}" = "1" ]]; then set -o xtrace PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' # Enable error backtracing trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR fi sqr::print 'Running...\n' seq_config 2>/dev/null || true step_1 "$@" } sqr::main "$@"