2020-10-21 16:16:32 +00:00
#! /bin/bash
2019-08-07 00:24:56 +00:00
2021-01-16 09:16:05 +00:00
DMS_DEBUG = " ${ DMS_DEBUG : =0 } "
2020-09-05 14:19:12 +00:00
2021-06-08 01:20:20 +00:00
# ? --------------------------------------------- BIN HELPER
2021-01-16 09:16:05 +00:00
function errex
{
2021-02-23 19:03:01 +00:00
echo -e " Error :: ${ * } \nAborting. " >& 2
2021-01-16 09:16:05 +00:00
exit 1
}
function escape
{
echo " ${ 1 //./ \\ . } "
}
2021-06-15 12:03:41 +00:00
function create_lock
{
SCRIPT_NAME = " $1 "
LOCK_FILE = " /tmp/docker-mailserver/ ${ SCRIPT_NAME } .lock "
[ [ -e " ${ LOCK_FILE } " ] ] && errex " Lock file ${ LOCK_FILE } exists. Another $1 execution is happening. Try again later. "
trap remove_lock EXIT # This won't work if the script is, for example, check-for-changes.sh which uses a while loop to stay running; you'll need to include a remove_lock call at the end of your logic
touch " ${ LOCK_FILE } "
}
function remove_lock
{
SCRIPT_NAME = ${ SCRIPT_NAME :- $1 }
rm -f " /tmp/docker-mailserver/ ${ SCRIPT_NAME } .lock "
}
# ? – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – IP & CIDR
2020-09-05 14:19:12 +00:00
2020-10-02 13:45:57 +00:00
function _mask_ip_digit
2020-09-05 14:19:12 +00:00
{
if [ [ ${ 1 } -ge 8 ] ]
then
2020-06-30 20:43:22 +00:00
MASK = 255
2020-09-05 14:19:12 +00:00
elif [ [ ${ 1 } -le 0 ] ]
then
MASK = 0
2020-06-30 20:43:22 +00:00
else
2020-09-05 14:19:12 +00:00
VALUES = ( 0 128 192 224 240 248 252 254 255)
2020-09-06 10:27:40 +00:00
MASK = ${ VALUES [ ${ 1 } ] }
2020-06-30 20:43:22 +00:00
fi
2020-09-05 14:19:12 +00:00
local DVAL = ${ 2 }
( ( DVAL& = MASK) )
2020-09-06 10:27:40 +00:00
echo " ${ DVAL } "
2019-08-07 00:24:56 +00:00
}
2020-09-05 14:19:12 +00:00
# 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.
2020-10-02 13:45:57 +00:00
function _sanitize_ipv4_to_subnet_cidr
2020-09-05 14:19:12 +00:00
{
local DIGIT_PREFIX_LENGTH = " ${ 1 #*/ } "
2020-09-26 13:11:52 +00:00
declare -a MASKED_DIGITS DIGITS
2020-09-06 10:27:40 +00:00
IFS = '.' ; read -r -a DIGITS < <( echo " ${ 1 %%/* } " ) ; unset IFS
2020-09-05 14:19:12 +00:00
for ( ( i = 0 ; i < 4 ; i++) )
do
2020-09-06 10:27:40 +00:00
MASKED_DIGITS[ i] = $( _mask_ip_digit " ${ DIGIT_PREFIX_LENGTH } " " ${ DIGITS [i] } " )
2020-09-05 14:19:12 +00:00
DIGIT_PREFIX_LENGTH = $(( DIGIT_PREFIX_LENGTH - 8 ))
2020-06-30 20:43:22 +00:00
done
2019-08-07 00:24:56 +00:00
2020-09-05 14:19:12 +00:00
echo " ${ MASKED_DIGITS [0] } . ${ MASKED_DIGITS [1] } . ${ MASKED_DIGITS [2] } . ${ MASKED_DIGITS [3] } / ${ 1 #*/ } "
2020-06-30 20:43:22 +00:00
}
2020-09-05 14:19:12 +00:00
export -f _sanitize_ipv4_to_subnet_cidr
2021-06-08 01:20:20 +00:00
# ? --------------------------------------------- ACME
2020-09-05 14:19:12 +00:00
2020-10-02 13:45:57 +00:00
function _extract_certs_from_acme
2020-09-05 14:19:12 +00:00
{
local KEY
# shellcheck disable=SC2002
2020-07-19 21:57:16 +00:00
KEY = $( cat /etc/letsencrypt/acme.json | python -c "
import sys,json
acme = json.load( sys.stdin)
for key, value in acme.items( ) :
certs = value[ 'Certificates' ]
2020-12-06 19:29:12 +00:00
if certs is not None:
for cert in certs:
if 'domain' in cert and 'key' in cert:
if 'main' in cert[ 'domain' ] and cert[ 'domain' ] [ 'main' ] = = '${1}' or 'sans' in cert[ 'domain' ] and '${1}' in cert[ 'domain' ] [ 'sans' ] :
print cert[ 'key' ]
break
2020-07-19 21:57:16 +00:00
" )
2020-09-05 14:19:12 +00:00
local CERT
# shellcheck disable=SC2002
2020-07-19 21:57:16 +00:00
CERT = $( cat /etc/letsencrypt/acme.json | python -c "
import sys,json
acme = json.load( sys.stdin)
for key, value in acme.items( ) :
certs = value[ 'Certificates' ]
2020-12-06 19:29:12 +00:00
if certs is not None:
for cert in certs:
if 'domain' in cert and 'certificate' in cert:
if 'main' in cert[ 'domain' ] and cert[ 'domain' ] [ 'main' ] = = '${1}' or 'sans' in cert[ 'domain' ] and '${1}' in cert[ 'domain' ] [ 'sans' ] :
print cert[ 'certificate' ]
break
2020-07-19 21:57:16 +00:00
" )
2020-06-30 20:43:22 +00:00
2020-09-05 14:19:12 +00:00
if [ [ -n " ${ KEY } ${ CERT } " ] ]
then
mkdir -p " /etc/letsencrypt/live/ ${ HOSTNAME } / "
2020-09-06 10:27:40 +00:00
echo " ${ KEY } " | base64 -d >/etc/letsencrypt/live/" ${ HOSTNAME } " /key.pem || exit 1
echo " ${ CERT } " | base64 -d >/etc/letsencrypt/live/" ${ HOSTNAME } " /fullchain.pem || exit 1
2021-01-22 09:03:31 +00:00
_notify 'inf' " Cert found in /etc/letsencrypt/acme.json for ${ 1 } "
2020-09-05 14:19:12 +00:00
2020-06-30 20:43:22 +00:00
return 0
else
return 1
fi
}
2020-09-05 14:19:12 +00:00
export -f _extract_certs_from_acme
2021-06-08 01:20:20 +00:00
# ? --------------------------------------------- Notifications
2020-08-24 20:08:11 +00:00
2020-10-02 13:45:57 +00:00
function _notify
2020-09-05 14:19:12 +00:00
{
2021-02-24 16:28:59 +00:00
{ [ [ -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 } " ; ;
2020-08-24 20:08:11 +00:00
esac
2021-02-24 16:28:59 +00:00
return 0
2020-08-24 20:08:11 +00:00
}
2020-09-05 14:53:36 +00:00
export -f _notify
2020-09-05 14:19:12 +00:00
2021-06-08 01:20:20 +00:00
# ? --------------------------------------------- Relay Host Map
2020-08-24 20:08:11 +00:00
# setup /etc/postfix/relayhost_map
# --
# @domain1.com [smtp.mailgun.org]:587
# @domain2.com [smtp.mailgun.org]:587
# @domain3.com [smtp.mailgun.org]:587
2020-10-02 13:45:57 +00:00
function _populate_relayhost_map
2020-09-05 14:19:12 +00:00
{
2020-10-06 12:45:55 +00:00
: >/etc/postfix/relayhost_map
2020-08-24 20:08:11 +00:00
chown root:root /etc/postfix/relayhost_map
chmod 0600 /etc/postfix/relayhost_map
2020-09-05 14:19:12 +00:00
if [ [ -f /tmp/docker-mailserver/postfix-relaymap.cf ] ]
then
2020-09-05 14:53:36 +00:00
_notify 'inf' "Adding relay mappings from postfix-relaymap.cf"
2020-09-05 14:19:12 +00:00
# keep lines which are not a comment *and* have a destination.
sed -n '/^\s*[^#[:space:]]\S*\s\+\S/p' /tmp/docker-mailserver/postfix-relaymap.cf >> /etc/postfix/relayhost_map
2020-08-24 20:08:11 +00:00
fi
2020-09-05 14:19:12 +00:00
2020-08-24 20:53:54 +00:00
{
2020-09-05 14:19:12 +00:00
# note: won't detect domains when lhs has spaces (but who does that?!)
2020-08-24 20:53:54 +00:00
sed -n '/^\s*[^#[:space:]]/ s/^[^@|]*@\([^|]\+\)|.*$/\1/p' /tmp/docker-mailserver/postfix-accounts.cf
2020-09-05 14:19:12 +00:00
[ -f /tmp/docker-mailserver/postfix-virtual.cf ] && sed -n '/^\s*[^#[:space:]]/ s/^\s*[^@[:space:]]*@\(\S\+\)\s.*/\1/p' /tmp/docker-mailserver/postfix-virtual.cf
2021-02-23 19:03:01 +00:00
} | while read -r DOMAIN
2020-09-05 14:19:12 +00:00
do
2021-02-23 19:03:01 +00:00
# DOMAIN not already present *and* not ignored
if ! grep -q -e " ^@ ${ DOMAIN } \b " /etc/postfix/relayhost_map && ! grep -qs -e " ^\s*@ ${ DOMAIN } \s* $" /tmp/docker-mailserver/postfix-relaymap.cf
2020-09-05 14:19:12 +00:00
then
2021-02-23 19:03:01 +00:00
_notify 'inf' " Adding relay mapping for ${ DOMAIN } "
2021-06-07 12:58:34 +00:00
# shellcheck disable=SC2153
2021-02-23 19:03:01 +00:00
echo " @ ${ DOMAIN } [ ${ RELAY_HOST } ]: ${ RELAY_PORT } " >> /etc/postfix/relayhost_map
2020-08-24 20:08:11 +00:00
fi
done
}
2020-09-05 14:19:12 +00:00
export -f _populate_relayhost_map
2021-06-08 01:20:20 +00:00
# ? --------------------------------------------- File Checksums
2020-08-24 20:08:11 +00:00
2020-09-05 14:19:12 +00:00
# file storing the checksums of the monitored files.
# shellcheck disable=SC2034
2020-08-24 18:46:50 +00:00
CHKSUM_FILE = /tmp/docker-mailserver-config-chksum
# Compute checksums of monitored files.
2020-10-02 13:45:57 +00:00
function _monitored_files_checksums
2020-09-05 14:19:12 +00:00
{
2020-08-24 18:46:50 +00:00
(
2020-09-05 14:19:12 +00:00
cd /tmp/docker-mailserver || exit 1
2020-08-24 18:46:50 +00:00
exec sha512sum 2>/dev/null -- \
2020-09-05 14:19:12 +00:00
postfix-accounts.cf \
postfix-virtual.cf \
postfix-aliases.cf \
dovecot-quotas.cf \
/etc/letsencrypt/acme.json \
2020-09-06 10:27:40 +00:00
" /etc/letsencrypt/live/ ${ HOSTNAME } /key.pem " \
2020-11-16 14:49:35 +00:00
" /etc/letsencrypt/live/ ${ HOSTNAME } /privkey.pem " \
2020-09-06 10:27:40 +00:00
" /etc/letsencrypt/live/ ${ HOSTNAME } /fullchain.pem "
2020-08-24 18:46:50 +00:00
)
}
2020-09-05 14:19:12 +00:00
export -f _monitored_files_checksums
2021-06-19 20:24:06 +00:00
function _shutdown
{
_notify 'err' "Shutting down.."
kill 1
}