mirror of
https://github.com/natelandau/ansible-homelab-config.git
synced 2025-11-17 09:23:40 -05:00
949 lines
29 KiB
Django/Jinja
949 lines
29 KiB
Django/Jinja
#!/usr/bin/env bash
|
|
|
|
_mainScript_() {
|
|
|
|
_setPATH_ "/bin" "/usr/bin" "/usr/local/bin"
|
|
|
|
debug "whoami: $(whoami)"
|
|
|
|
if ! _rootAvailable_; then fatal "This script must be run as root"; fi
|
|
|
|
if [ -z ${JOB:-} ]; then
|
|
error "Service name is not set"
|
|
_safeExit_ 1
|
|
fi
|
|
|
|
JOB_DIR="{{ interpolated_localfs_service_storage }}/${JOB}"
|
|
debug "JOB_DIR: ${JOB_DIR}" ${LINENO}
|
|
|
|
if [ -z ${JOB:-} ] || [ ! -d "${JOB_DIR}" ]; then
|
|
error "Can not find job directory: ${JOB_DIR}" "${LINENO}"
|
|
_safeExit_ 1
|
|
fi
|
|
|
|
if [ ! -d "${BACKUP_DIR}" ]; then
|
|
error "Can not find backup directory: ${BACKUP_DIR}" "${LINENO}"
|
|
_safeExit_ 1
|
|
fi
|
|
|
|
# Identify the latest backup
|
|
# shellcheck disable=SC2010
|
|
MOST_RECENT_BACKUP="$(ls "${BACKUP_DIR}" | grep --color=never "${JOB}" | sort -n -t _ -k 2 | tail -1)"
|
|
|
|
if [ -f "${BACKUP_DIR}/${MOST_RECENT_BACKUP}" ]; then
|
|
debug "Most recent backup: ${MOST_RECENT_BACKUP}"
|
|
else
|
|
error "Most recent backup does not exist" "${LINENO}"
|
|
fi
|
|
|
|
# Don't run as root on macOS
|
|
if [[ $(_detectOS_) == mac ]]; then
|
|
# Ensure destination directory is clean
|
|
_execute_ "command rm -rf \"${JOB_DIR:?}\""
|
|
_execute_ "mkdir \"${JOB_DIR:?}\""
|
|
|
|
# Extract the backup
|
|
if [ ${DRYRUN} == true ]; then
|
|
dryrun "tar zxvf \"${BACKUP_DIR}/${MOST_RECENT_BACKUP}\" -C \"${JOB_DIR}\""
|
|
elif tar zxvf "${BACKUP_DIR}/${MOST_RECENT_BACKUP}" -C "${JOB_DIR}"; then
|
|
info "Restore successful"
|
|
else
|
|
error "Restore failed"
|
|
_safeExit_ 1
|
|
fi
|
|
|
|
# Ensure permissions are correct
|
|
_execute_ "chown -R {{ ansible_user_uid }}:{{ ansible_user_gid }} \"${JOB_DIR}\""
|
|
else
|
|
# Ensure destination directory is clean
|
|
_execute_ "_runAsRoot_ command rm -rf \"${JOB_DIR:?}\""
|
|
_execute_ "_runAsRoot_ mkdir \"${JOB_DIR:?}\""
|
|
|
|
# Extract the backup
|
|
if [ ${DRYRUN} == true ]; then
|
|
dryrun "_runAsRoot_ tar zxvf \"${BACKUP_DIR}/${MOST_RECENT_BACKUP}\" -C \"${JOB_DIR}\""
|
|
elif _runAsRoot_ tar zxvf "${BACKUP_DIR}/${MOST_RECENT_BACKUP}" -C "${JOB_DIR}"; then
|
|
info "Restore successful"
|
|
else
|
|
error "Restore failed"
|
|
_safeExit_ 1
|
|
fi
|
|
|
|
# Ensure permissions are correct
|
|
_execute_ "_runAsRoot_ chown -R {{ ansible_user_uid }}:{{ ansible_user_gid }} \"${JOB_DIR}\""
|
|
fi
|
|
|
|
notice "Restored: ${MOST_RECENT_BACKUP}"
|
|
}
|
|
# end _mainScript_
|
|
|
|
# ################################## Flags and defaults
|
|
# Required variables
|
|
QUIET=false
|
|
LOGLEVEL=NOTICE
|
|
VERBOSE=false
|
|
FORCE=false
|
|
DRYRUN=false
|
|
declare -a ARGS=()
|
|
|
|
# Script specific
|
|
MULTIHOST=false
|
|
BACKUP_DIR="{{ interpolated_nfs_service_storage }}/pi-cluster/backups/config_backups"
|
|
LOGFILE="{{ interpolated_nfs_service_storage }}/pi-cluster/logs/$(basename "$0").log"
|
|
|
|
# ################################## Custom utility functions (Pasted from repository)
|
|
_execute_() {
|
|
# DESC:
|
|
# Executes commands while respecting global DRYRUN, VERBOSE, LOGGING, and QUIET flags
|
|
# ARGS:
|
|
# $1 (Required) - The command to be executed. Quotation marks MUST be escaped.
|
|
# $2 (Optional) - String to display after command is executed
|
|
# OPTS:
|
|
# -v Always print output from the execute function to STDOUT
|
|
# -n Use NOTICE level alerting (default is INFO)
|
|
# -p Pass a failed command with 'return 0'. This effectively bypasses set -e.
|
|
# -e Bypass _alert_ functions and use 'echo RESULT'
|
|
# -s Use '_alert_ success' for successful output. (default is 'info')
|
|
# -q Do not print output (QUIET mode)
|
|
# OUTS:
|
|
# stdout: Configurable output
|
|
# USE :
|
|
# _execute_ "cp -R \"~/dir/somefile.txt\" \"someNewFile.txt\"" "Optional message"
|
|
# _execute_ -sv "mkdir \"some/dir\""
|
|
# NOTE:
|
|
# If $DRYRUN=true, no commands are executed and the command that would have been executed
|
|
# is printed to STDOUT using dryrun level alerting
|
|
# If $VERBOSE=true, the command's native output is printed to stdout. This can be forced
|
|
# with '_execute_ -v'
|
|
|
|
local _localVerbose=false
|
|
local _passFailures=false
|
|
local _echoResult=false
|
|
local _echoSuccessResult=false
|
|
local _quietMode=false
|
|
local _echoNoticeResult=false
|
|
local opt
|
|
|
|
local OPTIND=1
|
|
while getopts ":vVpPeEsSqQnN" opt; do
|
|
case $opt in
|
|
v | V) _localVerbose=true ;;
|
|
p | P) _passFailures=true ;;
|
|
e | E) _echoResult=true ;;
|
|
s | S) _echoSuccessResult=true ;;
|
|
q | Q) _quietMode=true ;;
|
|
n | N) _echoNoticeResult=true ;;
|
|
*)
|
|
{
|
|
error "Unrecognized option '$1' passed to _execute_. Exiting."
|
|
_safeExit_
|
|
}
|
|
;;
|
|
esac
|
|
done
|
|
shift $((OPTIND - 1))
|
|
|
|
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
|
|
|
|
local _command="${1}"
|
|
local _executeMessage="${2:-$1}"
|
|
|
|
local _saveVerbose=${VERBOSE}
|
|
if "${_localVerbose}"; then
|
|
VERBOSE=true
|
|
fi
|
|
|
|
if "${DRYRUN}"; then
|
|
if "${_quietMode}"; then
|
|
VERBOSE=${_saveVerbose}
|
|
return 0
|
|
fi
|
|
if [ -n "${2:-}" ]; then
|
|
dryrun "${1} (${2})" "$(caller)"
|
|
else
|
|
dryrun "${1}" "$(caller)"
|
|
fi
|
|
elif ${VERBOSE}; then
|
|
if eval "${_command}"; then
|
|
if "${_quietMode}"; then
|
|
VERBOSE=${_saveVerbose}
|
|
elif "${_echoResult}"; then
|
|
printf "%s\n" "${_executeMessage}"
|
|
elif "${_echoSuccessResult}"; then
|
|
success "${_executeMessage}"
|
|
elif "${_echoNoticeResult}"; then
|
|
notice "${_executeMessage}"
|
|
else
|
|
info "${_executeMessage}"
|
|
fi
|
|
else
|
|
if "${_quietMode}"; then
|
|
VERBOSE=${_saveVerbose}
|
|
elif "${_echoResult}"; then
|
|
printf "%s\n" "warning: ${_executeMessage}"
|
|
else
|
|
warning "${_executeMessage}"
|
|
fi
|
|
VERBOSE=${_saveVerbose}
|
|
"${_passFailures}" && return 0 || return 1
|
|
fi
|
|
else
|
|
if eval "${_command}" >/dev/null 2>&1; then
|
|
if "${_quietMode}"; then
|
|
VERBOSE=${_saveVerbose}
|
|
elif "${_echoResult}"; then
|
|
printf "%s\n" "${_executeMessage}"
|
|
elif "${_echoSuccessResult}"; then
|
|
success "${_executeMessage}"
|
|
elif "${_echoNoticeResult}"; then
|
|
notice "${_executeMessage}"
|
|
else
|
|
info "${_executeMessage}"
|
|
fi
|
|
else
|
|
if "${_quietMode}"; then
|
|
VERBOSE=$_saveVerbose
|
|
elif "${_echoResult}"; then
|
|
printf "%s\n" "error: ${_executeMessage}"
|
|
else
|
|
warning "${_executeMessage}"
|
|
fi
|
|
VERBOSE=${_saveVerbose}
|
|
"${_passFailures}" && return 0 || return 1
|
|
fi
|
|
fi
|
|
VERBOSE=${_saveVerbose}
|
|
return 0
|
|
}
|
|
|
|
_runAsRoot_() {
|
|
# DESC:
|
|
# Run the requested command as root (via sudo if requested)
|
|
# ARGS:
|
|
# $1 (optional): Set to zero to not attempt execution via sudo
|
|
# $@ (required): Passed through for execution as root user
|
|
# OUTS:
|
|
# Runs the requested command as root
|
|
# CREDIT:
|
|
# https://github.com/ralish/bash-script-template
|
|
|
|
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
|
|
|
|
local _skip_sudo=false
|
|
|
|
if [[ ${1} =~ ^0$ ]]; then
|
|
_skip_sudo=true
|
|
shift
|
|
fi
|
|
|
|
if [[ ${EUID} -eq 0 ]]; then
|
|
"$@"
|
|
elif [[ -z ${_skip_sudo} ]]; then
|
|
sudo -H -- "$@"
|
|
else
|
|
fatal "Unable to run requested command as root: $*"
|
|
fi
|
|
}
|
|
|
|
_rootAvailable_() {
|
|
# DESC:
|
|
# Validate we have superuser access as root (via sudo if requested)
|
|
# ARGS:
|
|
# $1 (optional): Set to any value to not attempt root access via sudo
|
|
# OUTS:
|
|
# 0 if true
|
|
# 1 if false
|
|
# CREDIT:
|
|
# https://github.com/ralish/bash-script-template
|
|
|
|
local _superuser
|
|
local _testEUID
|
|
if [[ ${EUID} -eq 0 ]]; then
|
|
_superuser=true
|
|
elif [[ -z ${1:-} ]]; then
|
|
debug 'Sudo: Updating cached credentials ...'
|
|
if sudo -v; then
|
|
if [[ $(sudo -H -- "$BASH" -c 'printf "%s" "$EUID"') -eq 0 ]]; then
|
|
_superuser=true
|
|
else
|
|
_superuser=false
|
|
fi
|
|
else
|
|
_superuser=false
|
|
fi
|
|
fi
|
|
|
|
if [[ ${_superuser} == true ]]; then
|
|
debug 'Successfully acquired superuser credentials.'
|
|
return 0
|
|
else
|
|
debug 'Unable to acquire superuser credentials.'
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
_detectOS_() {
|
|
# DESC:
|
|
# Identify the OS the script is run on
|
|
# ARGS:
|
|
# None
|
|
# OUTS:
|
|
# 0 - Success
|
|
# 1 - Failed to detect OS
|
|
# stdout: One of 'mac', 'linux', 'windows'
|
|
# USAGE:
|
|
# _detectOS_
|
|
# CREDIT:
|
|
# https://github.com/labbots/bash-utility
|
|
|
|
local _uname
|
|
local _os
|
|
if _uname=$(command -v uname); then
|
|
case $("${_uname}" | tr '[:upper:]' '[:lower:]') in
|
|
linux*)
|
|
_os="linux"
|
|
;;
|
|
darwin*)
|
|
_os="mac"
|
|
;;
|
|
msys* | cygwin* | mingw* | nt | win*)
|
|
# or possible 'bash on windows'
|
|
_os="windows"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
return 1
|
|
fi
|
|
printf "%s" "${_os}"
|
|
|
|
}
|
|
# ################################## Functions required for this template to work
|
|
|
|
# Functions for providing alerts to the user and printing them to the log
|
|
|
|
_setColors_() {
|
|
# DESC:
|
|
# Sets colors use for alerts.
|
|
# ARGS:
|
|
# None
|
|
# OUTS:
|
|
# None
|
|
# USAGE:
|
|
# echo "${blue}Some text${reset}"
|
|
|
|
if tput setaf 1 >/dev/null 2>&1; then
|
|
bold=$(tput bold)
|
|
underline=$(tput smul)
|
|
reverse=$(tput rev)
|
|
reset=$(tput sgr0)
|
|
|
|
if [[ $(tput colors) -ge 256 ]] >/dev/null 2>&1; then
|
|
white=$(tput setaf 231)
|
|
blue=$(tput setaf 38)
|
|
yellow=$(tput setaf 11)
|
|
tan=$(tput setaf 3)
|
|
green=$(tput setaf 82)
|
|
red=$(tput setaf 1)
|
|
purple=$(tput setaf 171)
|
|
gray=$(tput setaf 250)
|
|
else
|
|
white=$(tput setaf 7)
|
|
blue=$(tput setaf 38)
|
|
yellow=$(tput setaf 3)
|
|
tan=$(tput setaf 3)
|
|
green=$(tput setaf 2)
|
|
red=$(tput setaf 1)
|
|
purple=$(tput setaf 13)
|
|
gray=$(tput setaf 7)
|
|
fi
|
|
else
|
|
bold="\033[4;37m"
|
|
reset="\033[0m"
|
|
underline="\033[4;37m"
|
|
reverse=""
|
|
white="\033[0;37m"
|
|
blue="\033[0;34m"
|
|
yellow="\033[0;33m"
|
|
tan="\033[0;33m"
|
|
green="\033[1;32m"
|
|
red="\033[0;31m"
|
|
purple="\033[0;35m"
|
|
gray="\033[0;37m"
|
|
fi
|
|
}
|
|
|
|
_alert_() {
|
|
# DESC:
|
|
# Controls all printing of messages to log files and stdout.
|
|
# ARGS:
|
|
# $1 (required) - The type of alert to print
|
|
# (success, header, notice, dryrun, debug, warning, error,
|
|
# fatal, info, input)
|
|
# $2 (required) - The message to be printed to stdout and/or a log file
|
|
# $3 (optional) - Pass '${LINENO}' to print the line number where the _alert_ was triggered
|
|
# OUTS:
|
|
# stdout: The message is printed to stdout
|
|
# log file: The message is printed to a log file
|
|
# USAGE:
|
|
# [_alertType] "[MESSAGE]" "${LINENO}"
|
|
# NOTES:
|
|
# - The colors of each alert type are set in this function
|
|
# - For specified alert types, the funcstac will be printed
|
|
|
|
local _color
|
|
local _alertType="${1}"
|
|
local _message="${2}"
|
|
local _line="${3:-}" # Optional line number
|
|
|
|
[[ $# -lt 2 ]] && fatal 'Missing required argument to _alert_'
|
|
|
|
if [[ -n ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
|
|
_message="${_message} ${gray}(line: ${_line}) $(_printFuncStack_)"
|
|
elif [[ -n ${_line} && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
|
|
_message="${_message} ${gray}(line: ${_line})"
|
|
elif [[ -z ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
|
|
_message="${_message} ${gray}$(_printFuncStack_)"
|
|
fi
|
|
|
|
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
|
|
_color="${bold}${red}"
|
|
elif [ "${_alertType}" == "info" ]; then
|
|
_color="${gray}"
|
|
elif [ "${_alertType}" == "warning" ]; then
|
|
_color="${red}"
|
|
elif [ "${_alertType}" == "success" ]; then
|
|
_color="${green}"
|
|
elif [ "${_alertType}" == "debug" ]; then
|
|
_color="${purple}"
|
|
elif [ "${_alertType}" == "header" ]; then
|
|
_color="${bold}${white}${underline}"
|
|
elif [ ${_alertType} == "notice" ]; then
|
|
_color="${bold}"
|
|
elif [ ${_alertType} == "input" ]; then
|
|
_color="${bold}${underline}"
|
|
elif [ "${_alertType}" = "dryrun" ]; then
|
|
_color="${blue}"
|
|
else
|
|
_color=""
|
|
fi
|
|
|
|
_writeToScreen_() {
|
|
("${QUIET}") && return 0 # Print to console when script is not 'quiet'
|
|
[[ ${VERBOSE} == false && ${_alertType} =~ ^(debug|verbose) ]] && return 0
|
|
|
|
if ! [[ -t 1 || -z ${TERM:-} ]]; then # Don't use colors on non-recognized terminals
|
|
_color=""
|
|
reset=""
|
|
fi
|
|
|
|
if [[ ${_alertType} == header ]]; then
|
|
printf "${_color}%s${reset}\n" "${_message}"
|
|
else
|
|
printf "${_color}[%7s] %s${reset}\n" "${_alertType}" "${_message}"
|
|
fi
|
|
}
|
|
_writeToScreen_
|
|
|
|
_writeToLog_() {
|
|
[[ ${_alertType} == "input" ]] && return 0
|
|
[[ ${LOGLEVEL} =~ (off|OFF|Off) ]] && return 0
|
|
if [ -z "${LOGFILE:-}" ]; then
|
|
LOGFILE="$(pwd)/$(basename "$0").log"
|
|
fi
|
|
[ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")"
|
|
[[ ! -f ${LOGFILE} ]] && touch "${LOGFILE}"
|
|
|
|
# Don't use colors in logs
|
|
local cleanmessage="$(echo "${_message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
|
|
# Print message to log file
|
|
printf "%s [%7s] %s %s\n" "$(date +"%b %d %R:%S")" "${_alertType}" "[$(/bin/hostname)]" "${cleanmessage}" >>"${LOGFILE}"
|
|
}
|
|
|
|
# Write specified log level data to logfile
|
|
case "${LOGLEVEL:-ERROR}" in
|
|
ALL | all | All)
|
|
_writeToLog_
|
|
;;
|
|
DEBUG | debug | Debug)
|
|
_writeToLog_
|
|
;;
|
|
INFO | info | Info)
|
|
if [[ ${_alertType} =~ ^(error|fatal|warning|info|notice|success) ]]; then
|
|
_writeToLog_
|
|
fi
|
|
;;
|
|
NOTICE | notice | Notice)
|
|
if [[ ${_alertType} =~ ^(error|fatal|warning|notice|success) ]]; then
|
|
_writeToLog_
|
|
fi
|
|
;;
|
|
WARN | warn | Warn)
|
|
if [[ ${_alertType} =~ ^(error|fatal|warning) ]]; then
|
|
_writeToLog_
|
|
fi
|
|
;;
|
|
ERROR | error | Error)
|
|
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
|
|
_writeToLog_
|
|
fi
|
|
;;
|
|
FATAL | fatal | Fatal)
|
|
if [[ ${_alertType} =~ ^fatal ]]; then
|
|
_writeToLog_
|
|
fi
|
|
;;
|
|
OFF | off)
|
|
return 0
|
|
;;
|
|
*)
|
|
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
|
|
_writeToLog_
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
} # /_alert_
|
|
|
|
error() { _alert_ error "${1}" "${2:-}"; }
|
|
warning() { _alert_ warning "${1}" "${2:-}"; }
|
|
notice() { _alert_ notice "${1}" "${2:-}"; }
|
|
info() { _alert_ info "${1}" "${2:-}"; }
|
|
success() { _alert_ success "${1}" "${2:-}"; }
|
|
dryrun() { _alert_ dryrun "${1}" "${2:-}"; }
|
|
input() { _alert_ input "${1}" "${2:-}"; }
|
|
header() { _alert_ header "${1}" "${2:-}"; }
|
|
debug() { _alert_ debug "${1}" "${2:-}"; }
|
|
fatal() {
|
|
_alert_ fatal "${1}" "${2:-}"
|
|
_safeExit_ "1"
|
|
}
|
|
|
|
# shellcheck disable=SC1009,SC1054,SC1056,SC1072,SC1073,SC1083
|
|
{% raw %}
|
|
_printFuncStack_() {
|
|
# DESC:
|
|
# Prints the function stack in use. Used for debugging, and error reporting.
|
|
# ARGS:
|
|
# None
|
|
# OUTS:
|
|
# stdout: Prints [function]:[file]:[line]
|
|
# NOTE:
|
|
# Does not print functions from the alert class
|
|
local _i
|
|
_funcStackResponse=()
|
|
for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do
|
|
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | debug | dryrun | header | success) continue ;; esac
|
|
_funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[_i - 1]}")
|
|
done
|
|
printf "( "
|
|
printf %s "${_funcStackResponse[0]}"
|
|
printf ' < %s' "${_funcStackResponse[@]:1}"
|
|
printf ' )\n'
|
|
}
|
|
{% endraw %}
|
|
|
|
_safeExit_() {
|
|
# DESC:
|
|
# Cleanup and exit from a script
|
|
# ARGS:
|
|
# $1 (optional) - Exit code (defaults to 0)
|
|
# OUTS:
|
|
# None
|
|
|
|
if [[ -d ${SCRIPT_LOCK:-} ]]; then
|
|
if command rm -rf "${SCRIPT_LOCK}"; then
|
|
debug "Removing script lock"
|
|
else
|
|
warning "Script lock could not be removed. Try manually deleting ${tan}'${LOCK_DIR}'"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n ${TMP_DIR:-} && -d ${TMP_DIR:-} ]]; then
|
|
if [[ ${1:-} == 1 && -n "$(ls "${TMP_DIR}")" ]]; then
|
|
command rm -r "${TMP_DIR}"
|
|
else
|
|
command rm -r "${TMP_DIR}"
|
|
debug "Removing temp directory"
|
|
fi
|
|
fi
|
|
|
|
trap - INT TERM EXIT
|
|
exit ${1:-0}
|
|
}
|
|
|
|
_trapCleanup_() {
|
|
# DESC:
|
|
# Log errors and cleanup from script when an error is trapped. Called by 'trap'
|
|
# ARGS:
|
|
# $1: Line number where error was trapped
|
|
# $2: Line number in function
|
|
# $3: Command executing at the time of the trap
|
|
# $4: Names of all shell functions currently in the execution call stack
|
|
# $5: Scriptname
|
|
# $6: $BASH_SOURCE
|
|
# USAGE:
|
|
# trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM
|
|
# OUTS:
|
|
# Exits script with error code 1
|
|
|
|
local _line=${1:-} # LINENO
|
|
local _linecallfunc=${2:-}
|
|
local _command="${3:-}"
|
|
local _funcstack="${4:-}"
|
|
local _script="${5:-}"
|
|
local _sourced="${6:-}"
|
|
|
|
if [[ "$(declare -f "fatal")" && "$(declare -f "_printFuncStack_")" ]]; then
|
|
_funcstack="'$(echo "${_funcstack}" | sed -E 's/ / < /g')'"
|
|
if [[ ${_script##*/} == "${_sourced##*/}" ]]; then
|
|
fatal "${7:-} command: '${_command}' (line: ${_line}) [func: $(_printFuncStack_)]"
|
|
else
|
|
fatal "${7:-} command: '${_command}' (func: ${_funcstack} called at line ${_linecallfunc} of '${_script##*/}') (line: ${_line} of '${_sourced##*/}') "
|
|
fi
|
|
else
|
|
printf "%s\n" "Fatal error trapped. Exiting..."
|
|
fi
|
|
|
|
if [ "$(declare -f "_safeExit_")" ]; then
|
|
_safeExit_ 1
|
|
else
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
_makeTempDir_() {
|
|
# DESC:
|
|
# Creates a temp directory to house temporary files
|
|
# ARGS:
|
|
# $1 (Optional) - First characters/word of directory name
|
|
# OUTS:
|
|
# Sets $TMP_DIR variable to the path of the temp directory
|
|
# USAGE:
|
|
# _makeTempDir_ "$(basename "$0")"
|
|
|
|
[ -d "${TMP_DIR:-}" ] && return 0
|
|
|
|
if [ -n "${1:-}" ]; then
|
|
TMP_DIR="${TMPDIR:-/tmp/}${1}.${RANDOM}.${RANDOM}.$$"
|
|
else
|
|
TMP_DIR="${TMPDIR:-/tmp/}$(basename "$0").${RANDOM}.${RANDOM}.${RANDOM}.$$"
|
|
fi
|
|
(umask 077 && mkdir "${TMP_DIR}") || {
|
|
fatal "Could not create temporary directory! Exiting."
|
|
}
|
|
debug "\$TMP_DIR=${TMP_DIR}"
|
|
}
|
|
|
|
_acquireScriptLock_() {
|
|
# DESC:
|
|
# Acquire script lock to prevent running the same script a second time before the
|
|
# first instance exits
|
|
# ARGS:
|
|
# $1 (optional) - Scope of script execution lock (system or user)
|
|
# OUTS:
|
|
# exports $SCRIPT_LOCK - Path to the directory indicating we have the script lock
|
|
# Exits script if lock cannot be acquired
|
|
# NOTE:
|
|
# If the lock was acquired it's automatically released in _safeExit_()
|
|
|
|
local _lockDir
|
|
if [[ ${1:-} == 'system' ]]; then
|
|
_lockDir="${TMPDIR:-/tmp/}$(basename "$0").lock"
|
|
else
|
|
_lockDir="${TMPDIR:-/tmp/}$(basename "$0").$UID.lock"
|
|
fi
|
|
|
|
if command mkdir "${LOCK_DIR}" 2>/dev/null; then
|
|
readonly SCRIPT_LOCK="${_lockDir}"
|
|
debug "Acquired script lock: ${yellow}${SCRIPT_LOCK}${purple}"
|
|
else
|
|
if [ "$(declare -f "_safeExit_")" ]; then
|
|
error "Unable to acquire script lock: ${tan}${LOCK_DIR}${red}"
|
|
fatal "If you trust the script isn't running, delete the lock dir"
|
|
else
|
|
printf "%s\n" "ERROR: Could not acquire script lock. If you trust the script isn't running, delete: ${LOCK_DIR}"
|
|
exit 1
|
|
fi
|
|
|
|
fi
|
|
}
|
|
|
|
_setPATH_() {
|
|
# DESC:
|
|
# Add directories to $PATH so script can find executables
|
|
# ARGS:
|
|
# $@ - One or more paths
|
|
# OUTS: Adds items to $PATH
|
|
# USAGE:
|
|
# _setPATH_ "/usr/local/bin" "${HOME}/bin" "$(npm bin)"
|
|
|
|
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
|
|
|
|
local _newPath
|
|
|
|
for _newPath in "$@"; do
|
|
if [ -d "${_newPath}" ]; then
|
|
if ! echo "${PATH}" | grep -Eq "(^|:)${_newPath}($|:)"; then
|
|
if PATH="${_newPath}:${PATH}"; then
|
|
debug "Added '${_newPath}' to PATH"
|
|
else
|
|
return 1
|
|
fi
|
|
else
|
|
debug "_setPATH_: '${_newPath}' already exists in PATH"
|
|
fi
|
|
else
|
|
debug "_setPATH_: can not find: ${_newPath}"
|
|
return 0
|
|
fi
|
|
done
|
|
return 0
|
|
}
|
|
|
|
_useGNUutils_() {
|
|
# DESC:
|
|
# Add GNU utilities to PATH to allow consistent use of sed/grep/tar/etc. on MacOS
|
|
# ARGS:
|
|
# None
|
|
# OUTS:
|
|
# 0 if successful
|
|
# 1 if unsuccessful
|
|
# PATH: Adds GNU utilities to the path
|
|
# USAGE:
|
|
# # if ! _useGNUUtils_; then exit 1; fi
|
|
# NOTES:
|
|
# GNU utilities can be added to MacOS using Homebrew
|
|
|
|
! declare -f "_setPATH_" &>/dev/null && fatal "${FUNCNAME[0]} needs function _setPATH_"
|
|
|
|
if _setPATH_ \
|
|
"/usr/local/opt/gnu-tar/libexec/gnubin" \
|
|
"/usr/local/opt/coreutils/libexec/gnubin" \
|
|
"/usr/local/opt/gnu-sed/libexec/gnubin" \
|
|
"/usr/local/opt/grep/libexec/gnubin" \
|
|
"/usr/local/opt/findutils/libexec/gnubin" \
|
|
"/opt/homebrew/opt/findutils/libexec/gnubin" \
|
|
"/opt/homebrew/opt/gnu-sed/libexec/gnubin" \
|
|
"/opt/homebrew/opt/grep/libexec/gnubin" \
|
|
"/opt/homebrew/opt/coreutils/libexec/gnubin" \
|
|
"/opt/homebrew/opt/gnu-tar/libexec/gnubin"; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
|
|
}
|
|
|
|
_homebrewPath_() {
|
|
# DESC:
|
|
# Add homebrew bin dir to PATH
|
|
# ARGS:
|
|
# None
|
|
# OUTS:
|
|
# 0 if successful
|
|
# 1 if unsuccessful
|
|
# PATH: Adds homebrew bin directory to PATH
|
|
# USAGE:
|
|
# # if ! _homebrewPath_; then exit 1; fi
|
|
|
|
! declare -f "_setPATH_" &>/dev/null && fatal "${FUNCNAME[0]} needs function _setPATH_"
|
|
|
|
if _uname=$(command -v uname); then
|
|
if "${_uname}" | tr '[:upper:]' '[:lower:]' | grep -q 'darwin'; then
|
|
if _setPATH_ "/usr/local/bin" "/opt/homebrew/bin"; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
if _setPATH_ "/usr/local/bin" "/opt/homebrew/bin"; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
{% raw %}
|
|
_parseOptions_() {
|
|
# DESC:
|
|
# Iterates through options passed to script and sets variables. Will break -ab into -a -b
|
|
# when needed and --foo=bar into --foo bar
|
|
# ARGS:
|
|
# $@ from command line
|
|
# OUTS:
|
|
# Sets array 'ARGS' containing all arguments passed to script that were not parsed as options
|
|
# USAGE:
|
|
# _parseOptions_ "$@"
|
|
|
|
# Iterate over options
|
|
local _optstring=h
|
|
declare -a _options
|
|
local _c
|
|
local i
|
|
while (($#)); do
|
|
case $1 in
|
|
# If option is of type -ab
|
|
-[!-]?*)
|
|
# Loop over each character starting with the second
|
|
for ((i = 1; i < ${#1}; i++)); do
|
|
_c=${1:i:1}
|
|
_options+=("-${_c}") # Add current char to options
|
|
# If option takes a required argument, and it's not the last char make
|
|
# the rest of the string its argument
|
|
if [[ ${_optstring} == *"${_c}:"* && ${1:i+1} ]]; then
|
|
_options+=("${1:i+1}")
|
|
break
|
|
fi
|
|
done
|
|
;;
|
|
# If option is of type --foo=bar
|
|
--?*=*) _options+=("${1%%=*}" "${1#*=}") ;;
|
|
# add --endopts for --
|
|
--) _options+=(--endopts) ;;
|
|
# Otherwise, nothing special
|
|
*) _options+=("$1") ;;
|
|
esac
|
|
shift
|
|
done
|
|
set -- "${_options[@]:-}"
|
|
unset _options
|
|
|
|
# Read the options and set stuff
|
|
while [[ ${1:-} == -?* ]]; do
|
|
case $1 in
|
|
# Custom options
|
|
--job)
|
|
shift
|
|
JOB="$1"
|
|
;;
|
|
|
|
# Common options
|
|
-h | --help)
|
|
_usage_
|
|
_safeExit_
|
|
;;
|
|
--loglevel)
|
|
shift
|
|
LOGLEVEL=${1}
|
|
;;
|
|
--logfile)
|
|
shift
|
|
LOGFILE="${1}"
|
|
;;
|
|
-n | --dryrun) DRYRUN=true ;;
|
|
-v | --verbose) VERBOSE=true ;;
|
|
-q | --quiet) QUIET=true ;;
|
|
--force) FORCE=true ;;
|
|
--endopts)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
if [ "$(declare -f "_safeExit_")" ]; then
|
|
fatal "invalid option: $1"
|
|
else
|
|
printf "%s\n" "Invalid option: $1"
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [[ -z ${*} || ${*} == null ]]; then
|
|
ARGS=()
|
|
else
|
|
ARGS+=("$@") # Store the remaining user input as arguments.
|
|
fi
|
|
}
|
|
{% endraw %}
|
|
|
|
_usage_() {
|
|
cat <<USAGE_TEXT
|
|
|
|
Restores the most recent backup of a service to a target directory on the host. It is assumed
|
|
that the destination directory is already mounted on the host and available at: {{ interpolated_nfs_service_storage }}/[service name]
|
|
|
|
${bold}Options:${reset}
|
|
--job [service name] Name of the service to restore
|
|
|
|
-h, --help Display this help and exit
|
|
--loglevel [LEVEL] One of: FATAL, ERROR, WARN, INFO, NOTICE, DEBUG, ALL, OFF
|
|
(Default is 'ERROR')
|
|
--logfile [FILE] Full PATH to logfile. (Default is '${HOME}/logs/$(basename "$0").log')
|
|
-n, --dryrun Non-destructive. Makes no permanent changes.
|
|
-q, --quiet Quiet (no output)
|
|
-v, --verbose Output more information. (Items echoed to 'verbose')
|
|
--force Skip all user interaction. Implied 'Yes' to all actions.
|
|
|
|
${bold}Example Usage:${reset}
|
|
|
|
${gray}# Run the script and specify log level and log file.${reset}
|
|
$(basename "$0") -vn --logfile "/path/to/file.log" --loglevel 'WARN'
|
|
USAGE_TEXT
|
|
}
|
|
|
|
# ################################## INITIALIZE AND RUN THE SCRIPT
|
|
# (Comment or uncomment the lines below to customize script behavior)
|
|
|
|
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM
|
|
|
|
# Trap errors in subshells and functions
|
|
set -o errtrace
|
|
|
|
# Exit on error. Append '||true' if you expect an error
|
|
set -o errexit
|
|
|
|
# Use last non-zero exit code in a pipeline
|
|
set -o pipefail
|
|
|
|
# Confirm we have BASH greater than v4
|
|
[ "${BASH_VERSINFO:-0}" -ge 4 ] || {
|
|
printf "%s\n" "ERROR: BASH_VERSINFO is '${BASH_VERSINFO:-0}'. This script requires BASH v4 or greater."
|
|
exit 1
|
|
}
|
|
|
|
# Make `for f in *.txt` work when `*.txt` matches zero files
|
|
shopt -s nullglob globstar
|
|
|
|
# Set IFS to preferred implementation
|
|
IFS=$' \n\t'
|
|
|
|
# Run in debug mode
|
|
# set -o xtrace
|
|
|
|
# Initialize color constants
|
|
_setColors_
|
|
|
|
# Disallow expansion of unset variables
|
|
set -o nounset
|
|
|
|
# Force arguments when invoking the script
|
|
# [[ $# -eq 0 ]] && _parseOptions_ "-h"
|
|
|
|
# Parse arguments passed to script
|
|
_parseOptions_ "$@"
|
|
|
|
# Create a temp directory '$TMP_DIR'
|
|
# _makeTempDir_ "$(basename "$0")"
|
|
|
|
# Acquire script lock
|
|
# _acquireScriptLock_
|
|
|
|
# Add Homebrew bin directory to PATH (MacOS)
|
|
_homebrewPath_
|
|
|
|
# Source GNU utilities from Homebrew (MacOS)
|
|
_useGNUutils_
|
|
|
|
# Run the main logic script
|
|
_mainScript_
|
|
|
|
# Exit cleanly
|
|
_safeExit_
|