From 26dcc247bb78893be64ca7a2e05ebc4b5d1fd5a4 Mon Sep 17 00:00:00 2001 From: reachableceo Date: Sat, 5 Sep 2026 23:39:46 -0500 Subject: [PATCH] [#797] vendor bash3boilerplate (MIT) as house standard for new operational scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main.sh engine + example.sh demo + adoption policy (new scripts start from b3bp; existing retrofit opportunistically on touch; vendored copy pruned from shellcheck scan — upstream code, not house-audited). Founder directive 2026-09-05. https://projects.knownelement.com/issues/797 --- scripts/bash3boilerplate/README.md | 21 ++ scripts/bash3boilerplate/example.sh | 142 ++++++++ scripts/bash3boilerplate/main.sh | 494 ++++++++++++++++++++++++++++ scripts/check-rules.sh | 2 +- 4 files changed, 658 insertions(+), 1 deletion(-) create mode 100644 scripts/bash3boilerplate/README.md create mode 100644 scripts/bash3boilerplate/example.sh create mode 100644 scripts/bash3boilerplate/main.sh diff --git a/scripts/bash3boilerplate/README.md b/scripts/bash3boilerplate/README.md new file mode 100644 index 0000000..f990969 --- /dev/null +++ b/scripts/bash3boilerplate/README.md @@ -0,0 +1,21 @@ +# bash3boilerplate (b3bp) — house standard for new operational shell scripts + +Vendored 2026-09-05 from https://github.com/kvz/bash3boilerplate (MIT, +attribution headers retained in main.sh/example.sh) per founder directive. + +## Adoption policy + +- **New operational scripts** in fleet repos should start from + `example.sh` (copy, rename, prune) — it gives: strict mode + (errexit/errtrace/nounset/pipefail), usage-heredoc-driven CLI parsing, + severity-leveled STDERR logging (LOG_LEVEL + NO_COLOR), safe magic + variables (`__dir`, `__file`). +- **Existing scripts**: retrofit opportunistically when touched; do not + churn working scripts solely to adopt b3bp. +- House enforcement (check-rules.sh: shellcheck, secret scan, ticket gate) + continues to apply on top — b3bp does not replace it. + +## Files + +- `main.sh` — the b3bp engine; source it from your script (see example.sh) +- `example.sh` — full-pattern demonstration diff --git a/scripts/bash3boilerplate/example.sh b/scripts/bash3boilerplate/example.sh new file mode 100644 index 0000000..438b1a9 --- /dev/null +++ b/scripts/bash3boilerplate/example.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# This file: +# +# - Demos BASH3 Boilerplate (change this for your script) +# +# Usage: +# +# LOG_LEVEL=7 ./example.sh -f /tmp/x -d (change this for your script) +# +# Based on a template by BASH3 Boilerplate v2.7.2 +# https://bash3boilerplate.sh/#authors +# +# The MIT License (MIT) +# Copyright (c) 2013 Kevin van Zonneveld and contributors +# You are not obligated to bundle the LICENSE file with your b3bp projects as long +# as you leave these references intact in the header comments of your source files. + +### BASH3 Boilerplate (b3bp) Header +############################################################################## + +# Commandline options. This defines the usage page, and is used to parse cli +# opts & defaults from. The parsing is unforgiving so be precise in your syntax +# - A short option must be preset for every long option; but every short option +# need not have a long option +# - `--` is respected as the separator between options and arguments +# - We do not bash-expand defaults, so setting '~/app' as a default will not resolve to ${HOME}. +# you can use bash variables to work around this (so use ${HOME} instead) + +# shellcheck disable=SC2034 +read -r -d '' __usage <<-'EOF' || true # exits non-zero when EOF encountered + -f --file [arg] Filename to process. Required. + -t --temp [arg] Location of tempfile. Default="/tmp/bar" + -v Enable verbose mode, print script as it is executed + -d --debug Enables debug mode + -h --help This page + -n --no-color Disable color output + -1 --one Do just one thing + -i --input [arg] File to process. Can be repeated. + -x Specify a flag. Can be repeated. +EOF + +# shellcheck disable=SC2034 +read -r -d '' __helptext <<-'EOF' || true # exits non-zero when EOF encountered + This is Bash3 Boilerplate's help text. Feel free to add any description of your + program or elaborate more on command-line arguments. This section is not + parsed and will be added as-is to the help. +EOF + +# shellcheck source=main.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/main.sh" + +### Overrides — these sections duplicate main.sh defaults intentionally, +### showing how a sourcing script can customize traps and argument handling. +############################################################################## + +function __b3bp_cleanup_before_exit() { + info "Cleaning up. Done" +} +trap __b3bp_cleanup_before_exit EXIT + +# requires `set -o errtrace` +__b3bp_err_report() { + local error_code=${?} + # shellcheck disable=SC2154 + error "Error in ${__file} in function ${1} on line ${2}" + exit ${error_code} +} +# Uncomment the following line for always providing an error backtrace +# trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR + +### Command-line argument switches (like -d for debugmode, -h for showing helppage) +############################################################################## + +# debug mode +if [[ "${arg_d:?}" = "1" ]]; then + set -o xtrace + PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + LOG_LEVEL="7" + # Enable error backtracing + trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR +fi + +# verbose mode +if [[ "${arg_v:?}" = "1" ]]; then + set -o verbose +fi + +# no color mode +if [[ "${arg_n:?}" = "1" ]]; then + NO_COLOR="true" +fi + +# help mode +if [[ "${arg_h:?}" = "1" ]]; then + help "Help using ${0}" +fi + +### Validation. Error out if the things required for your script are not present +############################################################################## + +[[ "${arg_f:-}" ]] || help "Setting a filename with -f or --file is required" +[[ "${LOG_LEVEL:-}" ]] || emergency "Cannot continue without LOG_LEVEL. " + +### Runtime +############################################################################## + +# shellcheck disable=SC2154 +info "__i_am_main_script: ${__i_am_main_script}" +# shellcheck disable=SC2154 +info "__file: ${__file}" +# shellcheck disable=SC2154 +info "__dir: ${__dir}" +# shellcheck disable=SC2154 +info "__base: ${__base}" +info "OSTYPE: ${OSTYPE}" + +info "arg_f: ${arg_f}" +info "arg_d: ${arg_d}" +info "arg_v: ${arg_v}" +info "arg_h: ${arg_h}" +if [[ -n "${arg_i:-}" ]]; then + info "arg_i: ${#arg_i[@]}" + for input_file in "${arg_i[@]}"; do + info " - ${input_file}" + done +else + info "arg_i: 0" +fi +# shellcheck disable=SC2015 +[[ -n "${arg_x:-}" ]] && info "arg_x: ${#arg_x[@]}" || info "arg_x: 0" + +info "$(echo -e "multiple lines example - line #1\\nmultiple lines example - line #2\\nimagine logging the output of 'ls -al /path/'")" + +# All of these go to STDERR, so you can use STDOUT for piping machine readable information to other software +debug "Info useful to developers for debugging the application, not useful during operations." +info "Normal operational messages - may be harvested for reporting, measuring throughput, etc. - no action required." +notice "Events that are unusual but not error conditions - might be summarized in an email to developers or admins to spot potential problems - no immediate action required." +warning "Warning messages, not an error, but indication that an error will occur if action is not taken, e.g. file system 85% full - each item must be resolved within a given time." +error "Non-urgent failures, these should be relayed to developers or admins; each item must be resolved within a given time." +critical "Should be corrected immediately, but indicates failure in a primary system, an example is a loss of a backup ISP connection." +alert "Should be corrected immediately, therefore notify staff who can fix the problem. An example would be the loss of a primary ISP connection." +emergency "A \"panic\" condition usually affecting multiple apps/servers/sites. At this level it would usually notify all tech staff on call." diff --git a/scripts/bash3boilerplate/main.sh b/scripts/bash3boilerplate/main.sh new file mode 100644 index 0000000..c444599 --- /dev/null +++ b/scripts/bash3boilerplate/main.sh @@ -0,0 +1,494 @@ +#!/usr/bin/env bash +# This file: +# +# - Demos BASH3 Boilerplate (change this for your script) +# +# Usage: +# +# LOG_LEVEL=7 ./main.sh -f /tmp/x -d (change this for your script) +# +# Based on a template by BASH3 Boilerplate v2.8.0 +# https://bash3boilerplate.sh/#authors +# +# The MIT License (MIT) +# Copyright (c) 2013 Kevin van Zonneveld and contributors +# You are not obligated to bundle the LICENSE file with your b3bp projects as long +# as you leave these references intact in the header comments of your source files. + +# 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 a pipe e.g. `mysqldump |gzip` returns mysqldump's exit code, not gzip's +set -o pipefail +# Turn on traces, useful while debugging but commented out by default +# set -o xtrace + +if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + __i_am_main_script="0" # false + + if [[ "${__usage+x}" ]]; then + if [[ "${BASH_SOURCE[1]}" = "${0}" ]]; then + __i_am_main_script="1" # true + fi + + __b3bp_external_usage="true" + __b3bp_tmp_source_idx=1 + fi +else + __i_am_main_script="1" # true + [[ "${__usage+x}" ]] && unset -v __usage + [[ "${__helptext+x}" ]] && unset -v __helptext +fi + +# Set magic variables for current file, directory, os, etc. +__dir="$(cd "$(dirname "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" && pwd)" +__file="${__dir}/$(basename "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" +__base="$(basename "${__file}" .sh)" +# shellcheck disable=SC2034,SC2015 +__invocation="$(printf %q "${__file}")$( (($#)) && printf ' %q' "$@" || true)" + +# Define the environment variables (and their defaults) that this script depends on +LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency +NO_COLOR="${NO_COLOR:-}" # true = disable color. otherwise autodetected + +### Functions +############################################################################## + +function __b3bp_log() { + local log_level="${1}" + shift + + # shellcheck disable=SC2034 + local color_debug="\\x1b[35m" color_info="\\x1b[32m" color_notice="\\x1b[34m" \ + color_warning="\\x1b[33m" color_error="\\x1b[31m" color_critical="\\x1b[1;31m" \ + color_alert="\\x1b[1;37;41m" color_emergency="\\x1b[1;4;5;37;41m" + + local colorvar="color_${log_level}" + + local color="${!colorvar:-${color_error}}" + local color_reset="\\x1b[0m" + + if [[ "${NO_COLOR:-}" = "true" ]] \ + || { [[ "${TERM:-}" != "xterm"* ]] && [[ "${TERM:-}" != "screen"* ]]; } \ + || [[ ! -t 2 ]]; then + if [[ "${NO_COLOR:-}" != "false" ]]; then + # Don't use colors on pipes or non-recognized terminals + color="" + color_reset="" + fi + fi + + # all remaining arguments are to be printed + local log_line="" + + while IFS=$'\n' read -r log_line; do + echo -e "$(date -u +"%Y-%m-%d %H:%M:%S UTC") ${color}$(printf "[%9s]" "${log_level}")${color_reset} ${log_line}" 1>&2 + done <<<"${@:-}" +} + +# The `; true` ensures the function exits 0 even when the log level check +# short-circuits the && chain, preventing failures under `set -o errexit`. +function emergency() { + __b3bp_log emergency "${@}" + exit 1 +} +function alert() { + [[ "${LOG_LEVEL:-0}" -ge 1 ]] && __b3bp_log alert "${@}" + true +} +function critical() { + [[ "${LOG_LEVEL:-0}" -ge 2 ]] && __b3bp_log critical "${@}" + true +} +function error() { + [[ "${LOG_LEVEL:-0}" -ge 3 ]] && __b3bp_log error "${@}" + true +} +function warning() { + [[ "${LOG_LEVEL:-0}" -ge 4 ]] && __b3bp_log warning "${@}" + true +} +function notice() { + [[ "${LOG_LEVEL:-0}" -ge 5 ]] && __b3bp_log notice "${@}" + true +} +function info() { + [[ "${LOG_LEVEL:-0}" -ge 6 ]] && __b3bp_log info "${@}" + true +} +function debug() { + [[ "${LOG_LEVEL:-0}" -ge 7 ]] && __b3bp_log debug "${@}" + true +} + +function help() { + echo "" 1>&2 + echo " ${*}" 1>&2 + echo "" 1>&2 + echo " ${__usage:-No usage available}" 1>&2 + echo "" 1>&2 + + if [[ "${__helptext:-}" ]]; then + echo " ${__helptext}" 1>&2 + echo "" 1>&2 + fi + + exit 1 +} + +function __b3bp_set_opt_display_suffix() { + local __b3bp_tmp_short_opt="${1}" + local __b3bp_tmp_out_varname="${2}" + local __b3bp_tmp_varname="__b3bp_tmp_opt_short2long_${__b3bp_tmp_short_opt}" + local __b3bp_tmp_opt_long="" + + printf -v "__b3bp_tmp_opt_long" '%s' "${!__b3bp_tmp_varname:-}" + + if [[ "${__b3bp_tmp_opt_long:-}" ]]; then + printf -v "${__b3bp_tmp_out_varname}" ' (--%s)' "${__b3bp_tmp_opt_long//_/-}" + else + printf -v "${__b3bp_tmp_out_varname}" '%s' "" + fi +} + +### Parse commandline options +############################################################################## + +# Commandline options. This defines the usage page, and is used to parse cli +# opts & defaults from. The parsing is unforgiving so be precise in your syntax +# - A short option must be preset for every long option; but every short option +# need not have a long option +# - `--` is respected as the separator between options and arguments +# - We do not bash-expand defaults, so setting '~/app' as a default will not resolve to ${HOME}. +# you can use bash variables to work around this (so use ${HOME} instead) + +# shellcheck disable=SC2015 +[[ "${__usage+x}" ]] || read -r -d '' __usage <<-'EOF' || true # exits non-zero when EOF encountered + -f --file [arg] Filename to process. Required. + -t --temp [arg] Location of tempfile. Default="/tmp/bar" + -v Enable verbose mode, print script as it is executed + -d --debug Enables debug mode + -h --help This page + -n --no-color Disable color output + -1 --one Do just one thing + -i --input [arg] File to process. Can be repeated. + -x Specify a flag. Can be repeated. +EOF + +# shellcheck disable=SC2015 +[[ "${__helptext+x}" ]] || read -r -d '' __helptext <<-'EOF' || true # exits non-zero when EOF encountered + This is Bash3 Boilerplate's help text. Feel free to add any description of your + program or elaborate more on command-line arguments. This section is not + parsed and will be added as-is to the help. +EOF + +# Translate usage string -> getopts arguments, and set $arg_ defaults +while read -r __b3bp_tmp_line; do + if [[ "${__b3bp_tmp_line}" =~ ^- ]]; then + # fetch single character version of option string + __b3bp_tmp_opt="${__b3bp_tmp_line%% *}" + __b3bp_tmp_opt="${__b3bp_tmp_opt:1}" + + # fetch long version if present + __b3bp_tmp_long_opt="" + + if [[ "${__b3bp_tmp_line}" = *"--"* ]]; then + __b3bp_tmp_long_opt="${__b3bp_tmp_line#*--}" + __b3bp_tmp_long_opt="${__b3bp_tmp_long_opt%% *}" + fi + + # map opt long name to+from opt short name + printf -v "__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" '%s' "${__b3bp_tmp_opt}" + printf -v "__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt}" '%s' "${__b3bp_tmp_long_opt//-/_}" + + # check if option takes an argument + if [[ "${__b3bp_tmp_line}" =~ \[.*\] ]]; then + __b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg + __b3bp_tmp_init="" # it has an arg. init with "" + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "1" + elif [[ "${__b3bp_tmp_line}" =~ \{.*\} ]]; then + __b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg + __b3bp_tmp_init="" # it has an arg. init with "" + # remember that this option requires an argument + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2" + else + __b3bp_tmp_init="0" # it's a flag. init with 0 + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "0" + fi + __b3bp_tmp_opts="${__b3bp_tmp_opts:-}${__b3bp_tmp_opt}" + + if [[ "${__b3bp_tmp_line}" =~ ^Can\ be\ repeated\. ]] || [[ "${__b3bp_tmp_line}" =~ \.\ *Can\ be\ repeated\. ]]; then + # remember that this option can be repeated + printf -v "__b3bp_tmp_is_array_${__b3bp_tmp_opt:0:1}" '%s' "1" + else + printf -v "__b3bp_tmp_is_array_${__b3bp_tmp_opt:0:1}" '%s' "0" + fi + fi + + [[ "${__b3bp_tmp_opt:-}" ]] || continue + + if [[ "${__b3bp_tmp_line}" =~ ^Default= ]] || [[ "${__b3bp_tmp_line}" =~ \.\ *Default= ]]; then + # ignore default value if option does not have an argument + __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" + if [[ "${!__b3bp_tmp_varname}" != "0" ]]; then + # take default + __b3bp_tmp_init="${__b3bp_tmp_line##*Default=}" + # strip double quotes from default argument + __b3bp_tmp_re='^"(.*)"$' + if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then + __b3bp_tmp_init="${BASH_REMATCH[1]}" + else + # strip single quotes from default argument + __b3bp_tmp_re="^'(.*)'$" + if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then + __b3bp_tmp_init="${BASH_REMATCH[1]}" + fi + fi + fi + fi + + if [[ "${__b3bp_tmp_line}" =~ ^Required\. ]] || [[ "${__b3bp_tmp_line}" =~ \.\ *Required\. ]]; then + # remember that this option requires an argument + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2" + fi + + # Init var with value unless it is an array / a repeatable + __b3bp_tmp_varname="__b3bp_tmp_is_array_${__b3bp_tmp_opt:0:1}" + [[ "${!__b3bp_tmp_varname}" = "0" ]] && printf -v "arg_${__b3bp_tmp_opt:0:1}" '%s' "${__b3bp_tmp_init}" +done <<<"${__usage:-}" + +# run getopts only if options were specified in __usage +if [[ "${__b3bp_tmp_opts:-}" ]]; then + # Allow long options like --this + __b3bp_tmp_opts="${__b3bp_tmp_opts}-:" + __b3bp_tmp_opt_long_suffix="" + + # Reset in case getopts has been used previously in the shell. + OPTIND=1 + + # start parsing command line + set +o nounset # getopts may reference unbound OPTARG / positional params + # Overwrite $arg_ defaults with the actual CLI options + while getopts "${__b3bp_tmp_opts}" __b3bp_tmp_opt; do + [[ "${__b3bp_tmp_opt}" = "?" ]] && help "Invalid use of script: ${*} " + + if [[ "${__b3bp_tmp_opt}" = "-" ]]; then + __b3bp_tmp_optarg_has_explicit=0 + __b3bp_tmp_optarg_value="" + + # OPTARG is long-option-name or long-option=value + if [[ "${OPTARG}" =~ .*=.* ]]; then + # --key=value format + __b3bp_tmp_long_opt="${OPTARG%%=*}" + __b3bp_tmp_optarg_value="${OPTARG#*=}" + __b3bp_tmp_optarg_has_explicit=1 + else + # --key value format + __b3bp_tmp_long_opt="${OPTARG}" + fi + + if [[ ! "${__b3bp_tmp_long_opt}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]*$ ]]; then + help "Invalid use of script: --${__b3bp_tmp_long_opt}" + fi + + # Set opt to the short option corresponding to the long option + __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" + if [[ -z "${!__b3bp_tmp_varname:-}" ]]; then + help "Invalid use of script: --${__b3bp_tmp_long_opt}" + fi + printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" + + # Decide whether this long option may/must receive an argument. + __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt}" + __b3bp_tmp_varvalue="${!__b3bp_tmp_varname:-0}" + + if [[ "${__b3bp_tmp_varvalue}" = "0" ]]; then + # Flags are not allowed to receive a value in --flag=value syntax. + if [[ "${__b3bp_tmp_optarg_has_explicit}" = "1" ]]; then + __b3bp_set_opt_display_suffix "${__b3bp_tmp_opt}" "__b3bp_tmp_opt_long_suffix" + help "Option -${__b3bp_tmp_opt}${__b3bp_tmp_opt_long_suffix} does not take an argument" + fi + OPTARG="" + else + if [[ "${__b3bp_tmp_optarg_has_explicit}" = "1" ]]; then + OPTARG="${__b3bp_tmp_optarg_value}" + else + # --key value format: consume next token as-is, including empty or hyphen-prefixed values. + if [[ "${!OPTIND+x}" != "x" ]]; then + __b3bp_set_opt_display_suffix "${__b3bp_tmp_opt}" "__b3bp_tmp_opt_long_suffix" + help "Option -${__b3bp_tmp_opt}${__b3bp_tmp_opt_long_suffix} requires an argument" + fi + printf -v "__b3bp_tmp_optarg_value" '%s' "${!OPTIND-}" + OPTARG="${__b3bp_tmp_optarg_value}" + ((OPTIND += 1)) + fi + fi + # we have set opt/OPTARG to the short value and the argument as OPTARG if it exists + fi + + __b3bp_tmp_value="${OPTARG}" + __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" + __b3bp_tmp_opt_has_arg="${!__b3bp_tmp_varname:-0}" + + __b3bp_tmp_varname="__b3bp_tmp_is_array_${__b3bp_tmp_opt:0:1}" + if [[ "${!__b3bp_tmp_varname}" != "0" ]]; then + # repeatables + # shellcheck disable=SC2016 + if [[ "${__b3bp_tmp_opt_has_arg}" = "0" ]]; then + # repeatable flags, they increment + __b3bp_tmp_varname="arg_${__b3bp_tmp_opt:0:1}" + debug "cli arg ${__b3bp_tmp_varname} = (${__b3bp_tmp_default}) -> ${!__b3bp_tmp_varname}" + # shellcheck disable=SC2004 + __b3bp_tmp_value=$((${!__b3bp_tmp_varname} + 1)) + printf -v "${__b3bp_tmp_varname}" '%s' "${__b3bp_tmp_value}" + else + # repeatable args, they get appended to an array + __b3bp_tmp_varname="arg_${__b3bp_tmp_opt:0:1}[@]" + debug "cli arg ${__b3bp_tmp_varname} append ${__b3bp_tmp_value}" + declare -a "${__b3bp_tmp_varname}"='("${!__b3bp_tmp_varname}" "${__b3bp_tmp_value}")' + fi + else + # non-repeatables + __b3bp_tmp_varname="arg_${__b3bp_tmp_opt:0:1}" + __b3bp_tmp_default="${!__b3bp_tmp_varname}" + + if [[ "${__b3bp_tmp_opt_has_arg}" = "0" ]]; then + __b3bp_tmp_value=$((__b3bp_tmp_default + 1)) + fi + + printf -v "${__b3bp_tmp_varname}" '%s' "${__b3bp_tmp_value}" + + debug "cli arg ${__b3bp_tmp_varname} = (${__b3bp_tmp_default}) -> ${!__b3bp_tmp_varname}" + fi + done + set -o nounset # no more unbound variable references expected + + shift $((OPTIND - 1)) + + if [[ "${1:-}" = "--" ]]; then + shift + fi +fi + +### Automatic validation of required option arguments +############################################################################## + +for __b3bp_tmp_varname in ${!__b3bp_tmp_has_arg_*}; do + # validate only options which required an argument + [[ "${!__b3bp_tmp_varname}" = "2" ]] || continue + + __b3bp_tmp_opt_short="${__b3bp_tmp_varname##*_}" + __b3bp_tmp_varname="arg_${__b3bp_tmp_opt_short}" + [[ "${!__b3bp_tmp_varname}" ]] && continue + + __b3bp_set_opt_display_suffix "${__b3bp_tmp_opt_short}" "__b3bp_tmp_opt_long_suffix" + help "Option -${__b3bp_tmp_opt_short}${__b3bp_tmp_opt_long_suffix} requires an argument" +done + +### Cleanup Environment variables +############################################################################## + +for __tmp_varname in ${!__b3bp_tmp_*}; do + unset -v "${__tmp_varname}" +done + +unset -v __tmp_varname + +### Externally supplied __usage. Nothing else to do here +############################################################################## + +if [[ "${__b3bp_external_usage:-}" = "true" ]]; then + unset -v __b3bp_external_usage + return +fi + +### Signal trapping and backtracing +############################################################################## + +function __b3bp_cleanup_before_exit() { + info "Cleaning up. Done" +} +trap __b3bp_cleanup_before_exit EXIT + +# requires `set -o errtrace` +function __b3bp_err_report() { + local error_code="${?}" + error "Error in ${__file} in function ${1} on line ${2}" + exit "${error_code}" +} +# Uncomment the following line for always providing an error backtrace +# trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR + +### Command-line argument switches (like -d for debugmode, -h for showing helppage) +############################################################################## + +# debug mode +if [[ "${arg_d:?}" = "1" ]]; then + set -o xtrace + PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + LOG_LEVEL="7" + # Enable error backtracing + trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR +fi + +# verbose mode +if [[ "${arg_v:?}" = "1" ]]; then + set -o verbose +fi + +# no color mode +if [[ "${arg_n:?}" = "1" ]]; then + NO_COLOR="true" +fi + +# help mode +if [[ "${arg_h:?}" = "1" ]]; then + # Help exists with code 1 + help "Help using ${0}" +fi + +### Validation. Error out if the things required for your script are not present +############################################################################## + +[[ "${arg_f:-}" ]] || help "Setting a filename with -f or --file is required" +[[ "${LOG_LEVEL:-}" ]] || emergency "Cannot continue without LOG_LEVEL. " + +### Runtime +############################################################################## + +info "__i_am_main_script: ${__i_am_main_script}" +info "__file: ${__file}" +info "__dir: ${__dir}" +info "__base: ${__base}" +info "OSTYPE: ${OSTYPE}" + +info "arg_f: ${arg_f}" +info "arg_d: ${arg_d}" +info "arg_v: ${arg_v}" +info "arg_h: ${arg_h}" + +if [[ -n "${arg_i:-}" ]]; then + info "arg_i:" + for input_file in "${arg_i[@]}"; do + info " - ${input_file}" + done +else + info "arg_i: 0" +fi + +info "arg_x: ${arg_x:-0}" + +info "$(echo -e "multiple lines example - line #1\\nmultiple lines example - line #2\\nimagine logging the output of 'ls -al /path/'")" + +# All of these go to STDERR, so you can use STDOUT for piping machine readable information to other software +debug "Info useful to developers for debugging the application, not useful during operations." +info "Normal operational messages - may be harvested for reporting, measuring throughput, etc. - no action required." +notice "Events that are unusual but not error conditions - might be summarized in an email to developers or admins to spot potential problems - no immediate action required." +warning "Warning messages, not an error, but indication that an error will occur if action is not taken, e.g. file system 85% full - each item must be resolved within a given time." +error "Non-urgent failures, these should be relayed to developers or admins; each item must be resolved within a given time." +critical "Should be corrected immediately, but indicates failure in a primary system, an example is a loss of a backup ISP connection." +alert "Should be corrected immediately, therefore notify staff who can fix the problem. An example would be the loss of a primary ISP connection." +emergency "A \"panic\" condition usually affecting multiple apps/servers/sites. At this level it would usually notify all tech staff on call." diff --git a/scripts/check-rules.sh b/scripts/check-rules.sh index 82d117f..1c11f84 100755 --- a/scripts/check-rules.sh +++ b/scripts/check-rules.sh @@ -41,7 +41,7 @@ TODAY="$(date +%Y-%m-%d)" # Runs in Docker so the host stays clean (no native shellcheck required). # ---------------------------------------------------------------------------- $RULE_VERBOSE && log_step "Shell scripts (shellcheck)" -mapfile -d '' SH_FILES < <(find . -path ./.git -prune -o -path ./.tmp -prune -o -path ./vendor -prune -o -path ./node_modules -prune -o \( -name '*.sh' -o -name '*.bash' \) -print0 2>/dev/null) +mapfile -d '' SH_FILES < <(find . -path ./.git -prune -o -path ./.tmp -prune -o -path ./vendor -prune -o -path ./node_modules -prune -o -path ./scripts/bash3boilerplate -prune -o \( -name '*.sh' -o -name '*.bash' \) -print0 2>/dev/null) if [ "${#SH_FILES[@]}" -gt 0 ]; then if have shellcheck; then if shellcheck "${SH_FILES[@]}" >/dev/null 2>&1; then