|
@@ -6,14 +6,16 @@ set -e -o errexit -o nounset -o pipefail
|
|
# Some splash of color for important messages
|
|
# Some splash of color for important messages
|
|
declare -g error_message_color="\033[1;31m"
|
|
declare -g error_message_color="\033[1;31m"
|
|
declare -g warn_message_color="\033[1;34m"
|
|
declare -g warn_message_color="\033[1;34m"
|
|
|
|
+declare -g notice_message_color="\033[1;34m"
|
|
declare -g color_clear="\033[1;0m"
|
|
declare -g color_clear="\033[1;0m"
|
|
|
|
|
|
# Current and previous log prefix
|
|
# Current and previous log prefix
|
|
|
|
+declare -g script_name=
|
|
|
|
+declare -g script_name_previous=
|
|
declare -g log_prefix=
|
|
declare -g log_prefix=
|
|
-declare -g log_prefix_previous=
|
|
|
|
|
|
|
|
# dot-env files to source when reading config
|
|
# dot-env files to source when reading config
|
|
-declare -ra dot_env_files=(
|
|
|
|
|
|
+declare -a dot_env_files=(
|
|
/var/www/.env.docker
|
|
/var/www/.env.docker
|
|
/var/www/.env
|
|
/var/www/.env
|
|
)
|
|
)
|
|
@@ -21,16 +23,24 @@ declare -ra dot_env_files=(
|
|
# environment keys seen when source dot files (so we can [export] them)
|
|
# environment keys seen when source dot files (so we can [export] them)
|
|
declare -ga seen_dot_env_variables=()
|
|
declare -ga seen_dot_env_variables=()
|
|
|
|
|
|
|
|
+declare -g docker_state_path="$(readlink -f ./storage/docker)"
|
|
|
|
+declare -g docker_locks_path="${docker_state_path}/lock"
|
|
|
|
+declare -g docker_once_path="${docker_state_path}/once"
|
|
|
|
+
|
|
|
|
+declare -g runtime_username=$(id -un ${RUNTIME_UID})
|
|
|
|
+
|
|
# @description Restore the log prefix to the previous value that was captured in [entrypoint-set-script-name ]
|
|
# @description Restore the log prefix to the previous value that was captured in [entrypoint-set-script-name ]
|
|
# @arg $1 string The name (or path) of the entrypoint script being run
|
|
# @arg $1 string The name (or path) of the entrypoint script being run
|
|
function entrypoint-set-script-name() {
|
|
function entrypoint-set-script-name() {
|
|
- log_prefix_previous="${log_prefix}"
|
|
|
|
- log_prefix="ENTRYPOINT - [$(get-entrypoint-script-name $1)] - "
|
|
|
|
|
|
+ script_name_previous="${script_name}"
|
|
|
|
+ script_name="${1}"
|
|
|
|
+
|
|
|
|
+ log_prefix="[entrypoint / $(get-entrypoint-script-name $1)] - "
|
|
}
|
|
}
|
|
|
|
|
|
# @description Restore the log prefix to the previous value that was captured in [entrypoint-set-script-name ]
|
|
# @description Restore the log prefix to the previous value that was captured in [entrypoint-set-script-name ]
|
|
function entrypoint-restore-script-name() {
|
|
function entrypoint-restore-script-name() {
|
|
- log_prefix="${log_prefix_previous}"
|
|
|
|
|
|
+ entrypoint-set-script-name "${script_name_previous}"
|
|
}
|
|
}
|
|
|
|
|
|
# @description Run a command as the [runtime user]
|
|
# @description Run a command as the [runtime user]
|
|
@@ -38,7 +48,7 @@ function entrypoint-restore-script-name() {
|
|
# @exitcode 0 if the command succeeeds
|
|
# @exitcode 0 if the command succeeeds
|
|
# @exitcode 1 if the command fails
|
|
# @exitcode 1 if the command fails
|
|
function run-as-runtime-user() {
|
|
function run-as-runtime-user() {
|
|
- run-command-as "$(id -un ${RUNTIME_UID})" "${@}"
|
|
|
|
|
|
+ run-command-as "${runtime_username}" "${@}"
|
|
}
|
|
}
|
|
|
|
|
|
# @description Run a command as the [runtime user]
|
|
# @description Run a command as the [runtime user]
|
|
@@ -64,9 +74,9 @@ function run-command-as() {
|
|
log-info-stderr "👷 Running [${*}] as [${target_user}]"
|
|
log-info-stderr "👷 Running [${*}] as [${target_user}]"
|
|
|
|
|
|
if [[ ${target_user} != "root" ]]; then
|
|
if [[ ${target_user} != "root" ]]; then
|
|
- su --preserve-environment "${target_user}" --shell /bin/bash --command "${*}"
|
|
|
|
|
|
+ stream-prefix-command-output su --preserve-environment "${target_user}" --shell /bin/bash --command "${*}"
|
|
else
|
|
else
|
|
- "${@}"
|
|
|
|
|
|
+ stream-prefix-command-output "${@}"
|
|
fi
|
|
fi
|
|
|
|
|
|
exit_code=$?
|
|
exit_code=$?
|
|
@@ -80,11 +90,62 @@ function run-command-as() {
|
|
return $exit_code
|
|
return $exit_code
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+# @description Streams stdout from the command and echo it
|
|
|
|
+# with log prefixing.
|
|
|
|
+# @see stream-prefix-command-output
|
|
|
|
+function stream-stdout-handler() {
|
|
|
|
+ local prefix="${1:-}"
|
|
|
|
+
|
|
|
|
+ while read line; do
|
|
|
|
+ log-info "(stdout) ${line}"
|
|
|
|
+ done
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# @description Streams stderr from the command and echo it
|
|
|
|
+# with a bit of color and log prefixing.
|
|
|
|
+# @see stream-prefix-command-output
|
|
|
|
+function stream-stderr-handler() {
|
|
|
|
+ while read line; do
|
|
|
|
+ log-info-stderr "(${error_message_color}stderr${color_clear}) ${line}"
|
|
|
|
+ done
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# @description Steam stdout and stderr from a command with log prefix
|
|
|
|
+# and stdout/stderr prefix. If stdout or stderr is being piped/redirected
|
|
|
|
+# it will automatically fall back to non-prefixed output.
|
|
|
|
+# @arg $@ string The command to run
|
|
|
|
+function stream-prefix-command-output() {
|
|
|
|
+ local stdout=stream-stdout-handler
|
|
|
|
+ local stderr=stream-stderr-handler
|
|
|
|
+
|
|
|
|
+ # if stdout is being piped, print it like normal with echo
|
|
|
|
+ if [ ! -t 1 ]; then
|
|
|
|
+ stdout= echo >&1 -ne
|
|
|
|
+ fi
|
|
|
|
+
|
|
|
|
+ # if stderr is being piped, print it like normal with echo
|
|
|
|
+ if [ ! -t 2 ]; then
|
|
|
|
+ stderr= echo >&2 -ne
|
|
|
|
+ fi
|
|
|
|
+
|
|
|
|
+ "$@" > >($stdout) 2> >($stderr)
|
|
|
|
+}
|
|
|
|
+
|
|
# @description Print the given error message to stderr
|
|
# @description Print the given error message to stderr
|
|
# @arg $message string A error message.
|
|
# @arg $message string A error message.
|
|
# @stderr The error message provided with log prefix
|
|
# @stderr The error message provided with log prefix
|
|
function log-error() {
|
|
function log-error() {
|
|
- echo -e "${error_message_color}${log_prefix}ERROR - ${*}${color_clear}" >/dev/stderr
|
|
|
|
|
|
+ local msg
|
|
|
|
+
|
|
|
|
+ if [[ $# -gt 0 ]]; then
|
|
|
|
+ msg="$@"
|
|
|
|
+ elif [[ ! -t 0 ]]; then
|
|
|
|
+ read msg || log-error-and-exit "[${FUNCNAME}] could not read from stdin"
|
|
|
|
+ else
|
|
|
|
+ log-error-and-exit "[${FUNCNAME}] did not receive any input arguments and STDIN is empty"
|
|
|
|
+ fi
|
|
|
|
+
|
|
|
|
+ echo -e "${error_message_color}${log_prefix}ERROR - ${msg}${color_clear}" >/dev/stderr
|
|
}
|
|
}
|
|
|
|
|
|
# @description Print the given error message to stderr and exit 1
|
|
# @description Print the given error message to stderr and exit 1
|
|
@@ -94,6 +155,8 @@ function log-error() {
|
|
function log-error-and-exit() {
|
|
function log-error-and-exit() {
|
|
log-error "$@"
|
|
log-error "$@"
|
|
|
|
|
|
|
|
+ show-call-stack
|
|
|
|
+
|
|
exit 1
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
|
|
@@ -101,15 +164,35 @@ function log-error-and-exit() {
|
|
# @arg $@ string A warning message.
|
|
# @arg $@ string A warning message.
|
|
# @stderr The warning message provided with log prefix
|
|
# @stderr The warning message provided with log prefix
|
|
function log-warning() {
|
|
function log-warning() {
|
|
- echo -e "${warn_message_color}${log_prefix}WARNING - ${*}${color_clear}" >/dev/stderr
|
|
|
|
|
|
+ local msg
|
|
|
|
+
|
|
|
|
+ if [[ $# -gt 0 ]]; then
|
|
|
|
+ msg="$@"
|
|
|
|
+ elif [[ ! -t 0 ]]; then
|
|
|
|
+ read msg || log-error-and-exit "[${FUNCNAME}] could not read from stdin"
|
|
|
|
+ else
|
|
|
|
+ log-error-and-exit "[${FUNCNAME}] did not receive any input arguments and STDIN is empty"
|
|
|
|
+ fi
|
|
|
|
+
|
|
|
|
+ echo -e "${warn_message_color}${log_prefix}WARNING - ${msg}${color_clear}" >/dev/stderr
|
|
}
|
|
}
|
|
|
|
|
|
# @description Print the given message to stdout unless [ENTRYPOINT_QUIET_LOGS] is set
|
|
# @description Print the given message to stdout unless [ENTRYPOINT_QUIET_LOGS] is set
|
|
# @arg $@ string A info message.
|
|
# @arg $@ string A info message.
|
|
# @stdout The info message provided with log prefix unless $ENTRYPOINT_QUIET_LOGS
|
|
# @stdout The info message provided with log prefix unless $ENTRYPOINT_QUIET_LOGS
|
|
function log-info() {
|
|
function log-info() {
|
|
|
|
+ local msg
|
|
|
|
+
|
|
|
|
+ if [[ $# -gt 0 ]]; then
|
|
|
|
+ msg="$@"
|
|
|
|
+ elif [[ ! -t 0 ]]; then
|
|
|
|
+ read msg || log-error-and-exit "[${FUNCNAME}] could not read from stdin"
|
|
|
|
+ else
|
|
|
|
+ log-error-and-exit "[${FUNCNAME}] did not receive any input arguments and STDIN is empty"
|
|
|
|
+ fi
|
|
|
|
+
|
|
if [ -z "${ENTRYPOINT_QUIET_LOGS:-}" ]; then
|
|
if [ -z "${ENTRYPOINT_QUIET_LOGS:-}" ]; then
|
|
- echo "${log_prefix}$*"
|
|
|
|
|
|
+ echo -e "${log_prefix}${msg}"
|
|
fi
|
|
fi
|
|
}
|
|
}
|
|
|
|
|
|
@@ -117,8 +200,18 @@ function log-info() {
|
|
# @arg $@ string A info message.
|
|
# @arg $@ string A info message.
|
|
# @stderr The info message provided with log prefix unless $ENTRYPOINT_QUIET_LOGS
|
|
# @stderr The info message provided with log prefix unless $ENTRYPOINT_QUIET_LOGS
|
|
function log-info-stderr() {
|
|
function log-info-stderr() {
|
|
|
|
+ local msg
|
|
|
|
+
|
|
|
|
+ if [[ $# -gt 0 ]]; then
|
|
|
|
+ msg="$@"
|
|
|
|
+ elif [[ ! -t 0 ]]; then
|
|
|
|
+ read msg || log-error-and-exit "[${FUNCNAME}] could not read from stdin"
|
|
|
|
+ else
|
|
|
|
+ log-error-and-exit "[${FUNCNAME}] did not receive any input arguments and STDIN is empty"
|
|
|
|
+ fi
|
|
|
|
+
|
|
if [ -z "${ENTRYPOINT_QUIET_LOGS:-}" ]; then
|
|
if [ -z "${ENTRYPOINT_QUIET_LOGS:-}" ]; then
|
|
- echo "${log_prefix}$*"
|
|
|
|
|
|
+ echo -e "${log_prefix}$msg" >/dev/stderr
|
|
fi
|
|
fi
|
|
}
|
|
}
|
|
|
|
|
|
@@ -196,3 +289,164 @@ function ensure-directory-exists() {
|
|
function get-entrypoint-script-name() {
|
|
function get-entrypoint-script-name() {
|
|
echo "${1#"$ENTRYPOINT_ROOT"}"
|
|
echo "${1#"$ENTRYPOINT_ROOT"}"
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+# @description Ensure a command is only run once (via a 'lock' file) in the storage directory.
|
|
|
|
+# The 'lock' is only written if the passed in command ($2) successfully ran.
|
|
|
|
+# @arg $1 string The name of the lock file
|
|
|
|
+# @arg $@ string The command to run
|
|
|
|
+function only-once() {
|
|
|
|
+ local name="${1:-$script_name}"
|
|
|
|
+ local file="${docker_once_path}/${name}"
|
|
|
|
+ shift
|
|
|
|
+
|
|
|
|
+ if [[ -e "${file}" ]]; then
|
|
|
|
+ log-info "Command [${*}] has already run once before (remove file [${file}] to run it again)"
|
|
|
|
+
|
|
|
|
+ return 0
|
|
|
|
+ fi
|
|
|
|
+
|
|
|
|
+ ensure-directory-exists "$(dirname "${file}")"
|
|
|
|
+
|
|
|
|
+ if ! "$@"; then
|
|
|
|
+ return 1
|
|
|
|
+ fi
|
|
|
|
+
|
|
|
|
+ touch "${file}"
|
|
|
|
+ return 0
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# @description Best effort file lock to ensure *something* is not running in multiple containers.
|
|
|
|
+# The script uses "trap" to clean up after itself if the script crashes
|
|
|
|
+# @arg $1 string The lock identifier
|
|
|
|
+function acquire-lock() {
|
|
|
|
+ local name="${1:-$script_name}"
|
|
|
|
+ local file="${docker_locks_path}/${name}"
|
|
|
|
+
|
|
|
|
+ ensure-directory-exists "$(dirname "${file}")"
|
|
|
|
+
|
|
|
|
+ log-info "🔑 Trying to acquire lock: ${file}: "
|
|
|
|
+ while [[ -e "${file}" ]]; do
|
|
|
|
+ log-info "🔒 Waiting on lock ${file}"
|
|
|
|
+
|
|
|
|
+ staggered-sleep
|
|
|
|
+ done
|
|
|
|
+
|
|
|
|
+ touch "${file}"
|
|
|
|
+
|
|
|
|
+ log-info "🔐 Lock acquired [${file}]"
|
|
|
|
+
|
|
|
|
+ on-trap "release-lock ${name}" EXIT INT QUIT TERM
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# @description Release a lock aquired by [acquire-lock]
|
|
|
|
+# @arg $1 string The lock identifier
|
|
|
|
+function release-lock() {
|
|
|
|
+ local name="${1:-$script_name}"
|
|
|
|
+ local file="${docker_locks_path}/${name}"
|
|
|
|
+
|
|
|
|
+ log-info "🔓 Releasing lock [${file}]"
|
|
|
|
+
|
|
|
|
+ rm -f "${file}"
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# @description Helper function to append multiple actions onto
|
|
|
|
+# the bash [trap] logic
|
|
|
|
+# @arg $1 string The command to run
|
|
|
|
+# @arg $@ string The list of trap signals to register
|
|
|
|
+function on-trap() {
|
|
|
|
+ local trap_add_cmd=$1
|
|
|
|
+ shift || log-error-and-exit "${FUNCNAME} usage error"
|
|
|
|
+
|
|
|
|
+ for trap_add_name in "$@"; do
|
|
|
|
+ trap -- "$(
|
|
|
|
+ # helper fn to get existing trap command from output
|
|
|
|
+ # of trap -p
|
|
|
|
+ extract_trap_cmd() { printf '%s\n' "${3:-}"; }
|
|
|
|
+ # print existing trap command with newline
|
|
|
|
+ eval "extract_trap_cmd $(trap -p "${trap_add_name}")"
|
|
|
|
+ # print the new trap command
|
|
|
|
+ printf '%s\n' "${trap_add_cmd}"
|
|
|
|
+ )" "${trap_add_name}" ||
|
|
|
|
+ log-error-and-exit "unable to add to trap ${trap_add_name}"
|
|
|
|
+ done
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# Set the trace attribute for the above function.
|
|
|
|
+#
|
|
|
|
+# This is required to modify DEBUG or RETURN traps because functions don't
|
|
|
|
+# inherit them unless the trace attribute is set
|
|
|
|
+declare -f -t on-trap
|
|
|
|
+
|
|
|
|
+# @description Waits for the database to be healthy and responsive
|
|
|
|
+function await-database-ready() {
|
|
|
|
+ log-info "❓ Waiting for database to be ready"
|
|
|
|
+
|
|
|
|
+ case "${DB_CONNECTION:-}" in
|
|
|
|
+ mysql)
|
|
|
|
+ while ! echo "SELECT 1" | mysql --user="$DB_USERNAME" --password="$DB_PASSWORD" --host="$DB_HOST" "$DB_DATABASE" --silent >/dev/null; do
|
|
|
|
+ staggered-sleep
|
|
|
|
+ done
|
|
|
|
+ ;;
|
|
|
|
+
|
|
|
|
+ pgsql)
|
|
|
|
+ while ! echo "SELECT 1" | psql --user="$DB_USERNAME" --password="$DB_PASSWORD" --host="$DB_HOST" "$DB_DATABASE" >/dev/null; do
|
|
|
|
+ staggered-sleep
|
|
|
|
+ done
|
|
|
|
+ ;;
|
|
|
|
+
|
|
|
|
+ sqlsrv)
|
|
|
|
+ log-warning "Don't know how to check if SQLServer is *truely* ready or not - so will just check if we're able to connect to it"
|
|
|
|
+
|
|
|
|
+ while ! timeout 1 bash -c "cat < /dev/null > /dev/tcp/${DB_HOST}/${DB_PORT}"; do
|
|
|
|
+ staggered-sleep
|
|
|
|
+ done
|
|
|
|
+ ;;
|
|
|
|
+
|
|
|
|
+ sqlite)
|
|
|
|
+ log-info "sqlite are always ready"
|
|
|
|
+ ;;
|
|
|
|
+
|
|
|
|
+ *)
|
|
|
|
+ log-error-and-exit "Unknown database type: [${DB_CONNECT}]"
|
|
|
|
+ ;;
|
|
|
|
+ esac
|
|
|
|
+
|
|
|
|
+ log-info "✅ Successfully connected to database"
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# @description sleeps between 1 and 3 seconds to ensure a bit of randomness
|
|
|
|
+# in multiple scripts/containers doing work almost at the same time.
|
|
|
|
+function staggered-sleep() {
|
|
|
|
+ sleep $(get-random-number-between 1 3)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# @description Helper function to get a random number between $1 and $2
|
|
|
|
+# @arg $1 int Minimum number in the range (inclusive)
|
|
|
|
+# @arg $2 int Maximum number in the range (inclusive)
|
|
|
|
+function get-random-number-between() {
|
|
|
|
+ local -i from=${1:-1}
|
|
|
|
+ local -i to="${2:-10}"
|
|
|
|
+
|
|
|
|
+ shuf -i "${from}-${to}" -n 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# @description Helper function to show the bask call stack when something
|
|
|
|
+# goes wrong. Is super useful when needing to debug an issue
|
|
|
|
+function show-call-stack() {
|
|
|
|
+ local stack_size=${#FUNCNAME[@]}
|
|
|
|
+ local func
|
|
|
|
+ local lineno
|
|
|
|
+ local src
|
|
|
|
+
|
|
|
|
+ # to avoid noise we start with 1 to skip the get_stack function
|
|
|
|
+ for ((i = 1; i < $stack_size; i++)); do
|
|
|
|
+ func="${FUNCNAME[$i]}"
|
|
|
|
+ [ x$func = x ] && func=MAIN
|
|
|
|
+
|
|
|
|
+ lineno="${BASH_LINENO[$((i - 1))]}"
|
|
|
|
+ src="${BASH_SOURCE[$i]}"
|
|
|
|
+ [ x"$src" = x ] && src=non_file_source
|
|
|
|
+
|
|
|
|
+ log-error " at: ${func} ${src}:${lineno}"
|
|
|
|
+ done
|
|
|
|
+}
|