diff --git a/README.md b/README.md index af904ee..9e65cef 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,97 @@ -# template-bash +# zimbra-management -Template repository for projects on bash +Zimbra management and monitoring utils. -* [`script.sh`](https://git.hmp.today/pavel.muhortov/template-bash#script-sh) +* [`zimbra-man.sh`](https://git.hmp.today/pavel.muhortov/zimbra-management#zimbra-man-sh) ____ -## `script.sh` +## `zimbra-man`.sh **Description:** -> returning current username if privileged rights are exist -> or -> returning error, if privileged rights are not exist +> Print length of all zimbra queues or letsencrypt update certificate procedure. **Dependencies:** > -> * [bash](https://www.gnu.org/software/bash/) (tested version 5.1.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> * [whoami](https://www.gnu.org/software/coreutils/whoami) (tested version 8.30 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> * privileged rights +> * [zimbra zmcontrol, zmqstat, zmcertmgr](https://www.zimbra.com/) (tested version 8.8.15 on [CentOS 7](https://wiki.centos.org/Manuals/ReleaseNotes/CentOS7.2009)) +> * [curl](https://curl.se/download.html) (tested version 7.29 on [CentOS 7](https://wiki.centos.org/Manuals/ReleaseNotes/CentOS7.2009)) +> * [openssl](https://www.openssl.org/) (tested version 1.0.2k on [CentOS 7](https://wiki.centos.org/Manuals/ReleaseNotes/CentOS7.2009)) +> * [cerbot](https://certbot.eff.org/) (tested version 2.5.0 on [CentOS 7](https://wiki.centos.org/Manuals/ReleaseNotes/CentOS7.2009)) +> * [Python 3](https://www.python.org/downloads/) (tested version 3.9.5 on [CentOS 7](https://wiki.centos.org/Manuals/ReleaseNotes/CentOS7.2009)) +> * existing [/usr/local/bin/sendmail.py](https://git.hmp.today/pavel.muhortov/utils#sendmail-py) | POSITION | PARAMETERS | DESCRIPTION | DEFAULT | |-----------|--------------|------------------------|---------------| | 1 |**[qn]**|execution without pauses|| -| 2 |**[/path/to/conf]**|path to config|`./script.conf`| +| 2 |**que**|print length of all zimbra queues|`None`| +| 2 |**ssl**|letsencrypt certificate update procedure|`None`| +| 3 |**[/path/to/conf]**|custom configuration file path|`./zimbra-man.conf`| -Example usage in terminal with bash: +### Renew certificate by crontab + +The first issue of the certificate must be done manually, because you need to answer letsencrypt questions. ```bash -bash ./script.sh qn ./script.conf +# define first certificate +certfirst=mail.domain.zone + +# stop zimbra +sudo su - zimbra -c "/opt/zimbra/bin/zmcontrol stop" + +# add additional hostnames, issue certificate and download chain +sudo certbot certonly --standalone --email mail@domain.zone --preferred-chain "ISRG Root X1" -d "${certfirst}" -d smtp.domain.zone -d pop3.domain.zone -d imap.domain.zone +sudo sh -c "wget -O - https://letsencrypt.org/certs/isrgrootx1.pem.txt --no-check-certificate >> /etc/letsencrypt/live/${certfirst}/chain.pem" + +# start zimbra +sudo su - zimbra -c "/opt/zimbra/bin/zmcontrol start" + +# copy certificate, define files permission +sudo cp "/etc/letsencrypt/live/${certfirst}/privkey.pem" /opt/zimbra/ssl/zimbra/commercial/commercial.key +sudo cp "/etc/letsencrypt/live/${certfirst}/chain.pem" /opt/zimbra/ssl/zimbra/commercial/chain.pem +sudo cp "/etc/letsencrypt/live/${certfirst}/cert.pem" /opt/zimbra/ssl/zimbra/commercial/cert.pem +sudo chown -R zimbra:zimbra /opt/zimbra/ssl/zimbra/commercial/ + +# deploy certificate +sudo su - zimbra -c "/opt/zimbra/bin/zmcertmgr verifycrt comm /opt/zimbra/ssl/zimbra/commercial/commercial.key /opt/zimbra/ssl/zimbra/commercial/cert.pem /opt/zimbra/ssl/zimbra/commercial/chain.pem" +sudo su - zimbra -c "/opt/zimbra/bin/zmcertmgr deploycrt comm /opt/zimbra/ssl/zimbra/commercial/cert.pem /opt/zimbra/ssl/zimbra/commercial/chain.pem" + +# restart zimbra +sudo su - zimbra -c "/opt/zimbra/bin/zmcontrol restart" ``` -Example usage in terminal with make the script executable: +When the certificate is issued, renew certificate is possible by crontab. ```bash -chmod u+x ./script.sh -script.sh +# download +sudo wget https://git.hmp.today/pavel.muhortov/zimbra-management/raw/branch/master/zimbra-man.sh -O /usr/local/bin/zimbra-man.sh +sudo chmod +x /usr/local/bin/zimbra-man.sh +``` + +```bash +# edit config +sudo tee /usr/local/bin/zimbra-man.conf > /dev/null <<'EOF' +logs=/var/log/zimbra-man.log +python3=/usr/local/opt/python-3.9/bin/python3.9 +certfirst=mail.domain.zone +certalias=smtp.domain.zone pop3.domain.zone imap.domain.zone +certemail=mail@domain.zone +EOF +``` + +```bash +# sudo sh -c "EDITOR=nano crontab -e" +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +0 0 * * 1 bash /usr/local/bin/zimbra-man.sh qn ssl >> /dev/null 2>&1 +``` + +```bash +# check journal +tail -f -n 50 /var/log/zimbra-man.log +``` + +### Print length of all zimbra queues + +```bash +sudo /usr/local/bin/zimbra-man.sh - que ``` diff --git a/script.sh b/script.sh deleted file mode 100644 index 60f4d6b..0000000 --- a/script.sh +++ /dev/null @@ -1,125 +0,0 @@ -#! /bin/bash - -# DESCRIPTION: -# returning current username if privileged rights are exist -# or -# returning error, if privileged rights are not exist -# -# DEPENDENCIES: -# - whoami -# -# PARAMETERS: -# 1: "qn" - execution without pauses -# 2: custom configuration file path -# -# FUNCTIONS: -# - -####################################### -# Print message and add to log. -# Globals: -# logs -# Arguments: -# 1: message to print and logging -####################################### -addtologs() { - echo "$(date +'%Y.%m.%d-%H:%M:%S') $1" | tee -a "${logs}" -} - -####################################### -# Waiting for press [ENTER]. -# Globals: -# None -# Arguments: -# None -####################################### -execpause() { - read -r -p "Press [ENTER] to continue... " -} - -####################################### -# Exit procedure. -# Globals: -# show -# Arguments: -# None -####################################### -execquite() { - addtologs "execution time is $(($(date +%s)-time)) seconds, exit" - if [ "${show}" != "qn" ]; then - execpause - fi - exit -} - -####################################### -# Error exit procedure -# Globals: -# None -# Arguments: -# 1: message to print and logging -####################################### -execerror() { - addtologs "error: $1" - execquite -} - -####################################### -# Parsing config file and creating global vars. -# Globals: -# None -# Arguments: -# None -####################################### -getconfig() { - logs=$(grep "logs=" "${conf}" | cut -d= -f2) -} - -####################################### -# Checking user rights. -# Globals: -# None -# Arguments: -# None -# return: -# 0 - if privileged rights, 1 - if not privileged rights -####################################### -checkroot() { - if [ "${EUID}" -ne 0 ]; then - return 1 # false - else - return 0 # true - fi -} - -# -# VARIABLES: -# - -show=$1 -conf=$2 -if [ -z "${conf}" ] || [ "${conf}" == "-" ]; then - conf="$(dirname "$(realpath "$0")")/$(basename -s .sh "$0").conf" -fi - -time=$(date +%s) -cd "$(dirname "$(realpath "$0")")" || execerror -if [ ! -e "${conf}" ]; then - : -else - getconfig -fi -if [ -z "${logs}" ]; then - logs=/dev/null -elif [ ! -e "${logs}" ]; then - touch "${logs}" -fi - -# -# MAIN: -# - -checkroot \ -&& echo "Running as $(whoami)" \ -&& execquite \ -|| execerror "Restart this as root!" diff --git a/zimbra-man.sh b/zimbra-man.sh new file mode 100644 index 0000000..dc25f15 --- /dev/null +++ b/zimbra-man.sh @@ -0,0 +1,258 @@ +#! /bin/bash + +# DESCRIPTION: +# print length of all zimbra queues +# or +# letsencrypt update certificate procedure +# +# DEPENDENCIES: +# - privileged rights +# - zimbra zmcontrol, zmqstat, zmcertmgr +# - curl +# - openssl +# - cerbot +# - Python 3 +# - existing /usr/local/bin/sendmail.py +# +# PARAMETERS: +# 1: "qn" - execution without pauses +# 2: "que" - print length of all zimbra queues +# 2: "ssl" - letsencrypt certificate update procedure +# 3: custom configuration file path +# +# FUNCTIONS: +# + +####################################### +# Print message and add to log. +# Globals: +# logs +# Arguments: +# 1: message to print and logging +####################################### +addtologs() { + echo "$(date +'%Y.%m.%d-%H:%M:%S') $1" | tee -a "${logs}" +} + +####################################### +# Waiting for press [ENTER]. +# Globals: +# None +# Arguments: +# None +####################################### +execpause() { + read -r -p "Press [ENTER] to continue... " +} + +####################################### +# Exit procedure. +# Globals: +# show +# Arguments: +# None +####################################### +execquite() { + addtologs "execution time is $(($(date +%s)-time)) seconds, exit" + if [ "${show}" != "qn" ]; then + execpause + fi + exit +} + +####################################### +# Error exit procedure +# Globals: +# None +# Arguments: +# 1: message to print and logging +####################################### +execerror() { + addtologs "error: $1" + execquite +} + +####################################### +# Parsing config file and creating global vars. +# Globals: +# None +# Arguments: +# None +####################################### +getconfig() { + logs=$(grep "logs=" "${conf}" | cut -d= -f2) + python3=$(grep "python3=" "${conf}" | cut -d= -f2) + certemail=$(grep "certemail=" "${conf}" | cut -d= -f2) + certfirst=$(grep "certfirst=" "${conf}" | cut -d= -f2) + IFS=" " read -r -a certalias <<< "$(grep "certalias=" "${conf}" | cut -d= -f2)" + +} + +####################################### +# Checking user rights. +# Globals: +# None +# Arguments: +# None +# return: +# 0 - if privileged rights, 1 - if not privileged rights +####################################### +checkroot() { + if [ "${EUID}" -ne 0 ]; then + return 1 # false + else + return 0 # true + fi +} + +####################################### +# Counting zimbra queues. +# Globals: +# None +# Arguments: +# None +# return: +# length of all queues +####################################### +calcqueue(){ + object=0 + while read -r QUE; do + object=$(( object + $(echo "${QUE}" | cut -d= -f2) )) + done <<< "$(/opt/zimbra/libexec/zmqstat)" + printf "%s\n" "${object}" + return "${object}" +} + +####################################### +# Print certificate expiration date in epoch +# Globals: +# None +# Arguments: +# 1: certificate path or site url +####################################### +certcheck() { + if [ -e "${1}" ]; then + printf '%s\n' "$(date -d "$(openssl x509 -text -noout -in "${1}" | grep 'Not After' | cut -d':' -f2-)" +%s)" + else + export LANG=C + printf '%s\n' "$(date -d "$(curl --insecure -vvI "${1}" 2>&1 | grep "expire date" | cut -d':' -f2-)" +%s)" + fi +} + +####################################### +# Renew and deploy certificate +# Globals: +# certfirst +# certalias +# certemail +# Arguments: +# None +####################################### +certrenew() { + # letsencrypt request + su - zimbra -c "/opt/zimbra/bin/zmcontrol stop" + certarray="-d ${certfirst}" + if [ ${#certalias[@]} -ne 0 ]; then + for domain in "${certalias[@]}"; do + certarray+=" -d ${domain}" + done + fi + certbot certonly --standalone --email "${certemail}" --preferred-chain "ISRG Root X1" "${certarray}" + wget -O - https://letsencrypt.org/certs/isrgrootx1.pem.txt --no-check-certificate >> "/etc/letsencrypt/live/${certfirst}/chain.pem" + su - zimbra -c "/opt/zimbra/bin/zmcontrol start" + + # zimbra cert deploy + cp "/etc/letsencrypt/live/${certfirst}/privkey.pem" /opt/zimbra/ssl/zimbra/commercial/commercial.key + cp "/etc/letsencrypt/live/${certfirst}/chain.pem" /opt/zimbra/ssl/zimbra/commercial/chain.pem + cp "/etc/letsencrypt/live/${certfirst}/cert.pem" /opt/zimbra/ssl/zimbra/commercial/cert.pem + chown -R zimbra:zimbra /opt/zimbra/ssl/zimbra/commercial/ + su - zimbra -c "/opt/zimbra/bin/zmcertmgr verifycrt comm /opt/zimbra/ssl/zimbra/commercial/commercial.key /opt/zimbra/ssl/zimbra/commercial/cert.pem /opt/zimbra/ssl/zimbra/commercial/chain.pem" + su - zimbra -c "/opt/zimbra/bin/zmcertmgr deploycrt comm /opt/zimbra/ssl/zimbra/commercial/cert.pem /opt/zimbra/ssl/zimbra/commercial/chain.pem" + su - zimbra -c "/opt/zimbra/bin/zmcontrol restart" +} + +####################################### +# Send email information about deployed certificate +# Globals: +# python3 +# certfirst +# Arguments: +# None +####################################### +startsendmail() { + subj="[SSL Status] $(cat /etc/hostname): certificates did renew" + ( + "${python3}" /usr/local/bin/sendmail.py \ + -u "$(grep "from=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ + -p "$(grep "pass=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ + -d "$(grep "dest=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ + --smtp "$(grep "smtp=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ + --port "$(grep "port=" /usr/local/bin/sendmail.config | cut -d= -f2)" \ + --stls "True" \ + --subj "${subj}" \ + --text "$(curl --insecure -vvI "${certfirst}" 2>&1 | awk 'BEGIN { cert=0 } /^\* SSL connection/ { cert=1 } /^\*/ { if (cert) print }')" \ + >> /dev/null 2>&1 & + ) + addtologs "sent mail with subject '${subj}'" +} + +# +# VARIABLES: +# + +show=$1 +does=$2 +conf=$3 +if [ -z "${conf}" ] || [ "${conf}" == "-" ]; then + conf="$(dirname "$(realpath "$0")")/$(basename -s .sh "$0").conf" +fi + +time=$(date +%s) +cd "$(dirname "$(realpath "$0")")" || execerror +if [ ! -e "${conf}" ]; then + : +else + getconfig +fi +if [ -z "${logs}" ]; then + logs=/dev/null +elif [ ! -e "${logs}" ]; then + touch "${logs}" +fi + +if ! command -v curl &> /dev/null || \ + ! command -v openssl &> /dev/null || \ + ! command -v certbot &> /dev/null || \ + ! command -v /opt/zimbra/bin/zmcontrol &> /dev/null || \ + ! command -v /opt/zimbra/bin/zmcertmgr &> /dev/null || \ + ! command -v /opt/zimbra/libexec/zmqstat &> /dev/null || \ + ! command -v /usr/local/bin/sendmail.py &> /dev/null || \ + ! command -v "${python3}" &> /dev/null; then + execerror "Not found dependencies" +fi + +# +# MAIN: +# + +if checkroot; then + if [ "${does}" = "que" ]; then + calcqueue && exit 0 + elif [ "${does}" = "ssl" ]; then + expired=$(certcheck "/etc/letsencrypt/live/${certfirst}/cert.pem") + targets=$(( expired - 2592000 )) + if [[ "${time}" -le "${targets}" ]]; then + addtologs "${certfirst} expired $(date -d "1970-01-01 UTC $expired seconds" +"%Y.%m.%d %T")" + addtologs "${certfirst} certificates renew delayed" + else + certrenew && addtologs "${certfirst} certificates renewed" + startsendmail + fi + else + printf "%s\n" "Usage example: $0 qn ssl" + printf "%s\n" "Usage example: $0 - que" + fi + execquite +else + execerror "Restart this as root!" +fi