mirror of
https://github.com/docker-mailserver/docker-mailserver.git
synced 2025-07-02 21:44:51 +02:00
refactoring: split helper functions into smaller scripts (#2420)
This commit is contained in:
parent
2927cc47c7
commit
b61dfe1e24
41 changed files with 389 additions and 396 deletions
62
target/scripts/helpers/dns.sh
Executable file
62
target/scripts/helpers/dns.sh
Executable file
|
@ -0,0 +1,62 @@
|
|||
#! /bin/bash
|
||||
|
||||
# Outputs the DNS label count (delimited by `.`) for the given input string.
|
||||
# Useful for determining an FQDN like `mail.example.com` (3), vs `example.com` (2).
|
||||
function _get_label_count
|
||||
{
|
||||
awk -F '.' '{ print NF }' <<< "${1}"
|
||||
}
|
||||
|
||||
# Sets HOSTNAME and DOMAINNAME globals used throughout the scripts,
|
||||
# and any subprocesses called that intereact with it.
|
||||
function _obtain_hostname_and_domainname
|
||||
{
|
||||
# Normally this value would match the output of `hostname` which mirrors `/proc/sys/kernel/hostname`,
|
||||
# However for legacy reasons, the system ENV `HOSTNAME` was replaced here with `hostname -f` instead.
|
||||
#
|
||||
# TODO: Consider changing to `DMS_FQDN`; a more accurate name, and removing the `export`, assuming no
|
||||
# subprocess like postconf would be called that would need access to the same value via `$HOSTNAME` ENV.
|
||||
#
|
||||
# TODO: `OVERRIDE_HOSTNAME` was introduced for non-Docker runtimes that could not configure an explicit hostname.
|
||||
# k8s was the particular runtime in 2017. This does not update `/etc/hosts` or other locations, thus risking
|
||||
# inconsistency with expected behaviour. Investigate if it's safe to remove support. (--net=host also uses this as a workaround)
|
||||
export HOSTNAME="${OVERRIDE_HOSTNAME:-$(hostname -f)}"
|
||||
|
||||
# If the container is misconfigured.. `hostname -f` (which derives it's return value from `/etc/hosts` or DNS query),
|
||||
# will result in an error that returns an empty value. This warrants a panic.
|
||||
if [[ -z ${HOSTNAME} ]]
|
||||
then
|
||||
dms_panic__misconfigured 'obtain_hostname' '/etc/hosts'
|
||||
fi
|
||||
|
||||
# If the `HOSTNAME` is more than 2 labels long (eg: mail.example.com),
|
||||
# We take the FQDN from it, minus the 1st label (aka _short hostname_, `hostname -s`).
|
||||
#
|
||||
# TODO: For some reason we're explicitly separating out a domain name from our FQDN,
|
||||
# `hostname -d` was probably not the correct command for this intention either.
|
||||
# Needs further investigation for relevance, and if `/etc/hosts` is important for consumers
|
||||
# of this variable or if a more deterministic approach with `cut` should be relied on.
|
||||
if [[ $(_get_label_count "${HOSTNAME}") -gt 2 ]]
|
||||
then
|
||||
if [[ -n ${OVERRIDE_HOSTNAME} ]]
|
||||
then
|
||||
# Emulates the intended behaviour of `hostname -d`:
|
||||
# Assign the HOSTNAME value minus everything up to and including the first `.`
|
||||
DOMAINNAME=${HOSTNAME#*.}
|
||||
else
|
||||
# Operates on the FQDN returned from querying `/etc/hosts` or fallback DNS:
|
||||
#
|
||||
# Note if you want the actual NIS `domainname`, use the `domainname` command,
|
||||
# or `cat /proc/sys/kernel/domainname`.
|
||||
# Our usage of `domainname` is under consideration as legacy, and not advised
|
||||
# going forward. In future our docs should drop any mention of it.
|
||||
|
||||
#shellcheck disable=SC2034
|
||||
DOMAINNAME="$(hostname -d)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Otherwise we assign the same value (eg: example.com):
|
||||
# Not an else statement in the previous conditional in the event that `hostname -d` fails.
|
||||
DOMAINNAME="${DOMAINNAME:-${HOSTNAME}}"
|
||||
}
|
74
target/scripts/helpers/error.sh
Executable file
74
target/scripts/helpers/error.sh
Executable file
|
@ -0,0 +1,74 @@
|
|||
#! /bin/bash
|
||||
|
||||
function _errex
|
||||
{
|
||||
echo -e "Error :: ${*}\nAborting." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# `dms_panic` methods are appropriate when the type of error is a not recoverable,
|
||||
# or needs to be very clear to the user about misconfiguration.
|
||||
#
|
||||
# Method is called with args:
|
||||
# PANIC_TYPE => (Internal value for matching). You should use the convenience methods below based on your panic type.
|
||||
# PANIC_INFO => Provide your own message string to insert into the error message for that PANIC_TYPE.
|
||||
# PANIC_SCOPE => Optionally provide a string for debugging to better identify/locate the source of the panic.
|
||||
function dms_panic
|
||||
{
|
||||
local PANIC_TYPE=${1}
|
||||
local PANIC_INFO=${2}
|
||||
local PANIC_SCOPE=${3} #optional
|
||||
|
||||
local SHUTDOWN_MESSAGE
|
||||
|
||||
case "${PANIC_TYPE:-}" in
|
||||
( 'fail-init' ) # PANIC_INFO == <name of service or process that failed to start / initialize>
|
||||
SHUTDOWN_MESSAGE="Failed to start ${PANIC_INFO}!"
|
||||
;;
|
||||
|
||||
( 'no-env' ) # PANIC_INFO == <ENV VAR name>
|
||||
SHUTDOWN_MESSAGE="Environment Variable: ${PANIC_INFO} is not set!"
|
||||
;;
|
||||
|
||||
( 'no-file' ) # PANIC_INFO == <invalid filepath>
|
||||
SHUTDOWN_MESSAGE="File ${PANIC_INFO} does not exist!"
|
||||
;;
|
||||
|
||||
( 'misconfigured' ) # PANIC_INFO == <something possibly misconfigured, eg an ENV var>
|
||||
SHUTDOWN_MESSAGE="${PANIC_INFO} appears to be misconfigured, please verify."
|
||||
;;
|
||||
|
||||
( 'invalid-value' ) # PANIC_INFO == <an unsupported or invalid value, eg in a case match>
|
||||
SHUTDOWN_MESSAGE="Invalid value for ${PANIC_INFO}!"
|
||||
;;
|
||||
|
||||
( * ) # `dms_panic` was called directly without a valid PANIC_TYPE
|
||||
SHUTDOWN_MESSAGE='Something broke :('
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n ${PANIC_SCOPE:-} ]]
|
||||
then
|
||||
_shutdown "${PANIC_SCOPE} | ${SHUTDOWN_MESSAGE}"
|
||||
else
|
||||
_shutdown "${SHUTDOWN_MESSAGE}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Convenience wrappers based on type:
|
||||
function dms_panic__fail_init { dms_panic 'fail-init' "${1}" "${2}"; }
|
||||
function dms_panic__no_env { dms_panic 'no-env' "${1}" "${2}"; }
|
||||
function dms_panic__no_file { dms_panic 'no-file' "${1}" "${2}"; }
|
||||
function dms_panic__misconfigured { dms_panic 'misconfigured' "${1}" "${2}"; }
|
||||
function dms_panic__invalid_value { dms_panic 'invalid-value' "${1}" "${2}"; }
|
||||
|
||||
# Call this method when you want to panic (emit a 'FATAL' log level error, and exit uncleanly).
|
||||
# `dms_panic` methods should be preferred if your failure type is supported.
|
||||
function _shutdown
|
||||
{
|
||||
local FATAL_ERROR_MESSAGE=$1
|
||||
|
||||
_notify 'fatal' "${FATAL_ERROR_MESSAGE}"
|
||||
_notify 'err' "Shutting down.."
|
||||
kill 1
|
||||
}
|
|
@ -1,16 +1,35 @@
|
|||
#! /bin/bash
|
||||
|
||||
# shellcheck source-path=target/scripts/helpers
|
||||
# This file serves as a single import for all helpers
|
||||
|
||||
# Global checksum file mainly needed for the changedetector.
|
||||
# Used in the folling scripts:
|
||||
#
|
||||
# - ../check-for-changes.sh
|
||||
# - ../start-mailserver.sh
|
||||
# - ../startup/setup-stack.sh
|
||||
# - ../../../test/test_helper/common.bash
|
||||
#
|
||||
# shellcheck disable=SC2034
|
||||
CHKSUM_FILE=/tmp/docker-mailserver-config-chksum
|
||||
|
||||
function _import_scripts
|
||||
{
|
||||
local PATH_TO_SCRIPTS='/usr/local/bin/helpers'
|
||||
|
||||
. "${PATH_TO_SCRIPTS}/postfix.sh"
|
||||
. "${PATH_TO_SCRIPTS}/accounts.sh"
|
||||
. "${PATH_TO_SCRIPTS}/aliases.sh"
|
||||
. "${PATH_TO_SCRIPTS}/relay.sh"
|
||||
. "${PATH_TO_SCRIPTS}/sasl.sh"
|
||||
. "${PATH_TO_SCRIPTS}/ssl.sh"
|
||||
source "${PATH_TO_SCRIPTS}/accounts.sh"
|
||||
source "${PATH_TO_SCRIPTS}/aliases.sh"
|
||||
source "${PATH_TO_SCRIPTS}/dns.sh"
|
||||
source "${PATH_TO_SCRIPTS}/error.sh"
|
||||
source "${PATH_TO_SCRIPTS}/lock.sh"
|
||||
source "${PATH_TO_SCRIPTS}/log.sh"
|
||||
source "${PATH_TO_SCRIPTS}/network.sh"
|
||||
source "${PATH_TO_SCRIPTS}/postfix.sh"
|
||||
source "${PATH_TO_SCRIPTS}/relay.sh"
|
||||
source "${PATH_TO_SCRIPTS}/sasl.sh"
|
||||
source "${PATH_TO_SCRIPTS}/ssl.sh"
|
||||
source "${PATH_TO_SCRIPTS}/utils.sh"
|
||||
}
|
||||
|
||||
_import_scripts
|
||||
|
|
39
target/scripts/helpers/lock.sh
Normal file
39
target/scripts/helpers/lock.sh
Normal file
|
@ -0,0 +1,39 @@
|
|||
#! /bin/bash
|
||||
|
||||
# This becomes the sourcing script name
|
||||
# (example: check-for-changes.sh)
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
# Used inside of lock files to identify them and
|
||||
# prevent removal by other instances of docker-mailserver
|
||||
LOCK_ID="$(uuid)"
|
||||
|
||||
function _create_lock
|
||||
{
|
||||
LOCK_FILE="/tmp/docker-mailserver/${SCRIPT_NAME}.lock"
|
||||
while [[ -e "${LOCK_FILE}" ]]
|
||||
do
|
||||
_notify 'warn' "Lock file ${LOCK_FILE} exists. Another ${SCRIPT_NAME} execution is happening. Trying again shortly..."
|
||||
# Handle stale lock files left behind on crashes
|
||||
# or premature/non-graceful exits of containers while they're making changes
|
||||
if [[ -n "$(find "${LOCK_FILE}" -mmin +1 2>/dev/null)" ]]
|
||||
then
|
||||
_notify 'warn' "Lock file older than 1 minute. Removing stale lock file."
|
||||
rm -f "${LOCK_FILE}"
|
||||
_notify 'inf' "Removed stale lock ${LOCK_FILE}."
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
trap _remove_lock EXIT
|
||||
echo "${LOCK_ID}" > "${LOCK_FILE}"
|
||||
}
|
||||
|
||||
function _remove_lock
|
||||
{
|
||||
LOCK_FILE="${LOCK_FILE:-"/tmp/docker-mailserver/${SCRIPT_NAME}.lock"}"
|
||||
[[ -z "${LOCK_ID}" ]] && _errex "Cannot remove ${LOCK_FILE} as there is no LOCK_ID set"
|
||||
if [[ -e "${LOCK_FILE}" ]] && grep -q "${LOCK_ID}" "${LOCK_FILE}" # Ensure we don't delete a lock that's not ours
|
||||
then
|
||||
rm -f "${LOCK_FILE}"
|
||||
_notify 'inf' "Removed lock ${LOCK_FILE}."
|
||||
fi
|
||||
}
|
24
target/scripts/helpers/log.sh
Executable file
24
target/scripts/helpers/log.sh
Executable file
|
@ -0,0 +1,24 @@
|
|||
#! /bin/bash
|
||||
|
||||
function _notify
|
||||
{
|
||||
{ [[ -z ${1:-} ]] || [[ -z ${2:-} ]] ; } && return 0
|
||||
|
||||
local RESET LGREEN LYELLOW LRED RED LBLUE LGREY LMAGENTA
|
||||
|
||||
RESET='\e[0m' ; LGREEN='\e[92m' ; LYELLOW='\e[93m'
|
||||
LRED='\e[31m' ; RED='\e[91m' ; LBLUE='\e[34m'
|
||||
LGREY='\e[37m' ; LMAGENTA='\e[95m'
|
||||
|
||||
case "${1}" in
|
||||
'tasklog' ) echo "-e${3:-}" "[ ${LGREEN}TASKLOG${RESET} ] ${2}" ;;
|
||||
'warn' ) echo "-e${3:-}" "[ ${LYELLOW}WARNING${RESET} ] ${2}" ;;
|
||||
'err' ) echo "-e${3:-}" "[ ${LRED}ERROR${RESET} ] ${2}" ;;
|
||||
'fatal' ) echo "-e${3:-}" "[ ${RED}FATAL${RESET} ] ${2}" ;;
|
||||
'inf' ) [[ ${DMS_DEBUG} -eq 1 ]] && echo "-e${3:-}" "[[ ${LBLUE}INF${RESET} ]] ${2}" ;;
|
||||
'task' ) [[ ${DMS_DEBUG} -eq 1 ]] && echo "-e${3:-}" "[[ ${LGREY}TASKS${RESET} ]] ${2}" ;;
|
||||
* ) echo "-e${3:-}" "[ ${LMAGENTA}UNKNOWN${RESET} ] ${2}" ;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
40
target/scripts/helpers/network.sh
Executable file
40
target/scripts/helpers/network.sh
Executable file
|
@ -0,0 +1,40 @@
|
|||
#! /bin/bash
|
||||
|
||||
function _mask_ip_digit
|
||||
{
|
||||
if [[ ${1} -ge 8 ]]
|
||||
then
|
||||
MASK=255
|
||||
elif [[ ${1} -le 0 ]]
|
||||
then
|
||||
MASK=0
|
||||
else
|
||||
VALUES=(0 128 192 224 240 248 252 254 255)
|
||||
MASK=${VALUES[${1}]}
|
||||
fi
|
||||
|
||||
local DVAL=${2}
|
||||
((DVAL&=MASK))
|
||||
|
||||
echo "${DVAL}"
|
||||
}
|
||||
|
||||
# Transforms a specific IP with CIDR suffix
|
||||
# like 1.2.3.4/16 to subnet with cidr suffix
|
||||
# like 1.2.0.0/16.
|
||||
# Assumes correct IP and subnet are provided.
|
||||
function _sanitize_ipv4_to_subnet_cidr
|
||||
{
|
||||
local DIGIT_PREFIX_LENGTH="${1#*/}"
|
||||
|
||||
declare -a MASKED_DIGITS DIGITS
|
||||
IFS='.' ; read -r -a DIGITS < <(echo "${1%%/*}") ; unset IFS
|
||||
|
||||
for ((i = 0 ; i < 4 ; i++))
|
||||
do
|
||||
MASKED_DIGITS[i]=$(_mask_ip_digit "${DIGIT_PREFIX_LENGTH}" "${DIGITS[i]}")
|
||||
DIGIT_PREFIX_LENGTH=$((DIGIT_PREFIX_LENGTH - 8))
|
||||
done
|
||||
|
||||
echo "${MASKED_DIGITS[0]}.${MASKED_DIGITS[1]}.${MASKED_DIGITS[2]}.${MASKED_DIGITS[3]}/${1#*/}"
|
||||
}
|
|
@ -94,9 +94,6 @@ function _relayhost_configure_postfix
|
|||
"smtp_sender_dependent_authentication = yes"
|
||||
}
|
||||
|
||||
# ? --------------------------------------------- Callers
|
||||
|
||||
# setup-stack.sh:
|
||||
function _setup_relayhost
|
||||
{
|
||||
_notify 'task' 'Setting up Postfix Relay Hosts'
|
||||
|
@ -120,7 +117,6 @@ function _setup_relayhost
|
|||
fi
|
||||
}
|
||||
|
||||
# check-for-changes.sh:
|
||||
function _rebuild_relayhost
|
||||
{
|
||||
if [[ -n ${RELAY_HOST} ]]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#! /bin/bash
|
||||
# Support for SASL
|
||||
|
||||
function _sasl_passwd_create
|
||||
{
|
||||
|
|
8
target/scripts/helpers/ssl.sh
Normal file → Executable file
8
target/scripts/helpers/ssl.sh
Normal file → Executable file
|
@ -94,7 +94,7 @@ function _setup_ssl
|
|||
# 2020 feature intended for Traefik v2 support only:
|
||||
# https://github.com/docker-mailserver/docker-mailserver/pull/1553
|
||||
# Extracts files `key.pem` and `fullchain.pem`.
|
||||
# `_extract_certs_from_acme` is located in `helper-functions.sh`
|
||||
# `_extract_certs_from_acme` is located in `helpers/ssl.sh`
|
||||
# NOTE: See the `SSL_TYPE=letsencrypt` case below for more details.
|
||||
function _traefik_support
|
||||
{
|
||||
|
@ -180,7 +180,7 @@ function _setup_ssl
|
|||
# SSL_DOMAIN will have any wildcard prefix stripped for the output FQDN folder it is stored in.
|
||||
# TODO: A wildcard cert needs to be provisioned via Traefik to validate if acme.json contains any other value for `main` or `sans` beyond the wildcard.
|
||||
#
|
||||
# NOTE: HOSTNAME is set via `helper-functions.sh`, it is not the original system HOSTNAME ENV anymore.
|
||||
# NOTE: HOSTNAME is set via `helpers/dns.sh`, it is not the original system HOSTNAME ENV anymore.
|
||||
# TODO: SSL_DOMAIN is Traefik specific, it no longer seems relevant and should be considered for removal.
|
||||
|
||||
_traefik_support
|
||||
|
@ -407,7 +407,6 @@ function _setup_ssl
|
|||
|
||||
esac
|
||||
}
|
||||
export -f _setup_ssl
|
||||
|
||||
function _extract_certs_from_acme
|
||||
{
|
||||
|
@ -441,13 +440,11 @@ function _extract_certs_from_acme
|
|||
|
||||
_notify 'inf' "_extract_certs_from_acme | Certificate successfully extracted for '${CERT_DOMAIN}'"
|
||||
}
|
||||
export -f _extract_certs_from_acme
|
||||
|
||||
# Remove the `*.` prefix if it exists, else returns the input value
|
||||
function _strip_wildcard_prefix {
|
||||
[[ ${1} == "*."* ]] && echo "${1:2}" || echo "${1}"
|
||||
}
|
||||
export -f _strip_wildcard_prefix
|
||||
|
||||
# Compute checksums of monitored files,
|
||||
# returned output on `stdout`: hash + filepath tuple on each line
|
||||
|
@ -496,4 +493,3 @@ function _monitored_files_checksums
|
|||
|
||||
sha512sum -- "${CHANGED_FILES[@]}"
|
||||
}
|
||||
export -f _monitored_files_checksums
|
||||
|
|
13
target/scripts/helpers/utils.sh
Executable file
13
target/scripts/helpers/utils.sh
Executable file
|
@ -0,0 +1,13 @@
|
|||
#! /bin/bash
|
||||
|
||||
function _escape
|
||||
{
|
||||
echo "${1//./\\.}"
|
||||
}
|
||||
|
||||
# Check if string input is an empty line, only whitespaces
|
||||
# or `#` as the first non-whitespace character.
|
||||
function _is_comment
|
||||
{
|
||||
grep -q -E "^\s*$|^\s*#" <<< "${1}"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue