#!/bin/bash

###############################################################################
# Inspired from                                                               #
# https://community.letsencrypt.org/t/                                        #
# how-to-completely-automating-certificate-renewals-on-debian/5615            #
###############################################################################

###################
#  Configuration  #
###################

# This line MUST be present in all scripts executed by cron!
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Certs that will expire in less than this value will be renewed
REMAINING_DAYS_TO_RENEW=30

# Command to execute if certs have been renewed
SERVICES_TO_RESTART="nginx postfix metronome"

# Parameters for email alert
EMAIL_ALERT_FROM="cron-certrenewer@DOMAIN_NAME (Cron certificate renewer)"
EMAIL_ALERT_TO="ADMIN_EMAIL"
EMAIL_ALERT_SUBJ="WARNING: SSL certificate renewal for CERT_NAME failed!"

# Letsencrypt stuff

# The executable
LEBIN="/root/.letsencrypt/letsencrypt-auto"
# The config file
LECFG="/etc/letsencrypt/conf.ini"
# The directory where current .pem certificates are stored
LELIVE="/etc/letsencrypt/live"
# Renewal directory, which contains renewal configs for all certificates.
LERENEWAL="/etc/letsencrypt/renewal"

################
#  Misc tools  #
################

# -----------------------------------
# Given a certificate file, return the number of days before it expires
# -----------------------------------
function daysBeforeCertificateExpire()
{
    local CERT_FILE=$1
    local DATE_EXPIRE=$(openssl x509 -in $CERT_FILE -text -noout \
                       | grep "Not After"                        \
                       | cut -c 25-)
    local D1=$(date -d "$DATE_EXPIRE" +%s)
    local D2=$(date -d "now"        +%s)
    local DAYS_EXP=$(( ( D1 - D2) / 86400 ))
    echo $DAYS_EXP
}

# -----------------------------------
# Send an alert email stating that the renewing of a cert failed, and paste the
# logs into the mail body
# -----------------------------------
function sendAlert()
{
    local CERT_NAME=$1
    local LOG_FILE=$2
    local SUBJ=$(echo $EMAIL_ALERT_SUBJ | sed "s/CERT_NAME/${CERT_NAME}/g")

    echo -e " Here is the log of what happened\n"             \
            "Consider also checking /var/log/letsencrypt/\n"  \
            "--------------------------------------------\n"  \
    | cat - ${LOG_FILE}                                       \
    | mail -s "${SUBJ}"                                       \
           -r "${EMAIL_ALERT_FROM}"                           \
               ${EMAIL_ALERT_TO}
}

# -----------------------------------
# -----------------------------------
function restartServices()
{
    eval "/bin/sync"

    local SERVICE
    for SERVICE in ${SERVICES_TO_RESTART}
    do
        eval "service ${SERVICE} restart"
    done
}

###############################
#  Actual lets encrypt stuff  #
###############################

# -----------------------------------
# Given a certificate name, echo True or False if it will soon expire
# (see REMAINING_DAYS_TO_RENEW)
# -----------------------------------
function certificateNeedsToBeRenewed()
{
    local CERT_NAME=$1
    local CERT_FILE="${LELIVE}/${CERT_NAME}/cert.pem"
    local DAYS_BEFORE_EXPIRE=`daysBeforeCertificateExpire $CERT_FILE`

    if [[ ${DAYS_BEFORE_EXPIRE} -lt ${REMAINING_DAYS_TO_RENEW} ]]
    then
        echo "True"
    else
        echo "False"
    fi
}

# -----------------------------------
# Given a certificate name, attempt to renew it
# Stuff is logged in a file
# -----------------------------------
function renewCertificate()
{
    local CERT_NAME=$1
    local LOG_FILE=$2
    local CERT_FILE="${LELIVE}/${CERT_NAME}/cert.pem"
    local CERT_CONF="${LERENEWAL}/${CERT_NAME}.conf"
    # Parse "domains = xxxx", we might need to remove the last character
    # if it's a comma
    local DOMAINS=$(grep -o --perl-regex "(?<=domains \= ).*" "${CERT_CONF}")
    local LAST_CHAR=$(echo ${DOMAINS} | awk '{print substr($0,length,1)}')
    if [ "${LAST_CHAR}" = "," ]
    then
        local DOMAINS=$(echo ${DOMAINS} |awk '{print substr($0, 1, length-1)}')
    fi

    # Recreate the webroot folder (expected to be in /tmp/)
    WEBROOT_PATH=$(cat $CERT_CONF    \
                 | grep webroot_path \
                 | tr ',' ' '        \
                 | awk '{print $3}')
    mkdir -p ${WEBROOT_PATH}

    rm ${LOG_FILE}
    touch ${LOG_FILE}
    ${LEBIN} certonly          \
        --renew-by-default     \
        --config "${LECFG}"    \
        --domains "${DOMAINS}" \
        > ${LOG_FILE} 2>&1
}

# -----------------------------------
# Attempt to renew all certificates in LELIVE directory
# -----------------------------------
function renewAllCertificates()
{
    local AT_LEAST_ONE_CERT_RENEWED="False"

    # Loop on certificates in live directory
    local CERT
    for CERT in $(ls -1 "${LELIVE}")
    do
        echo "Checking $CERT certificate ..."
        # Check if current certificate needs to be renewed
        if [[ `certificateNeedsToBeRenewed ${CERT}` == "True" ]]
        then
            echo " > Needs to be renewed. Attempting to ..."

            # If yes, attempt to renew it
            local LOG_FILE="/tmp/cron-cert-renewer.log"
            renewCertificate ${CERT} ${LOG_FILE}

            # Check it worked
            if [[ `certificateNeedsToBeRenewed $CERT` == "False" ]]
            then
                echo " > Cert was succesfully renewed."
                local AT_LEAST_ONE_CERT_RENEWED="True"
            else
                echo " > An error occured, an email was sent."
                sendAlert ${CERT} ${LOG_FILE}
            fi
        else
            echo " > No need to renew it."
        fi
    done

    if [[ ${AT_LEAST_ONE_CERT_RENEWED} == "True" ]]
    then
        return 1
    else
        return 0
    fi
}

###################
#  Main function  #
###################

function main()
{
    renewAllCertificates

    if [[ $? -eq 1 ]]
    then
        restartServices
    fi

}
main