#!/bin/sh
# Careful, the zzz_evobackup template was last updated on 2020/06/08
#
# Script Evobackup client
# See https://gitea.evolix.org/evolix/evobackup
#
# Author: Gregory Colpart <reg@evolix.fr>
# Contributors:
# Romain Dessort <rdessort@evolix.fr>
# Benoît Série <bserie@evolix.fr>
# Tristan Pilat <tpilat@evolix.fr>
# Victor Laborie <vlaborie@evolix.fr>
# Jérémy Lecour <jlecour@evolix.fr>
#
# Licence: AGPLv3
#
# /!\ DON'T FORGET TO SET "MAIL" and "SERVERS" VARIABLES

# Fail on unassigned variables
set -u

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

# email adress for notifications
MAIL={{ evobackup_client__mail }}

# list of hosts (hostname or IP) and SSH port for Rsync
SERVERS="{% for host in evobackup_client__hosts %}{{ host.name }}:{{ host.port }}{% if loop.index != loop.length %} {% endif %}{% endfor %}"

# Should we fallback on servers when the first is unreachable ?
SERVERS_FALLBACK={{ evobackup_client__servers_fallback }}

# timeout (in seconds) for SSH connections
SSH_CONNECT_TIMEOUT=${SSH_CONNECT_TIMEOUT:-30}

## We use /home/backup : feel free to use your own dir
LOCAL_BACKUP_DIR="{{ evobackup_client__backup_path }}"

# You can set "linux" or "bsd" manually or let it choose automatically
SYSTEM=$(uname | tr '[:upper:]' '[:lower:]')

# Change these 2 variables if you have more than one backup cron
PIDFILE="{{ evobackup_client__pid_path }}"
LOGFILE="{{ evobackup_client__log_path }}"

## Enable/Disable tasks
LOCAL_TASKS=${LOCAL_TASKS:-1}
SYNC_TASKS=${SYNC_TASKS:-1}

##### SETUP AND FUNCTIONS #############################################

BEGINNING=$(/bin/date +"%d-%m-%Y ; %H:%M")

# shellcheck disable=SC2174
mkdir -p -m 700 ${LOCAL_BACKUP_DIR}

PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin

## lang = C for english outputs
export LANGUAGE=C
export LANG=C

## Force umask
umask 077

## Initialize variable to store SSH connection errors
SERVERS_SSH_ERRORS=""

# Call test_server with "HOST:PORT" string
# It will return with 0 if the server is reachable.
# It will return with 1 and a message on stderr if not.
test_server() {
    item=$1
    # split HOST and PORT from the input string
    host=$(echo "${item}" | cut -d':' -f1)
    port=$(echo "${item}" | cut -d':' -f2)

    # Test if the server is accepting connections
    ssh -q -o "ConnectTimeout ${SSH_CONNECT_TIMEOUT}" -i {{ evobackup_client__root_key_path }} "${host}" -p "${port}" -t "exit"
    # shellcheck disable=SC2181
    if [ $? = 0 ]; then
        # SSH connection is OK
        return 0
    else
        # SSH connection failed
        new_error=$(printf "Failed to connect to \`%s' within %s seconds" "${item}" "${SSH_CONNECT_TIMEOUT}")
        SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d')

        return 1
    fi
}
# Call pick_server with an optional positive integer to get the nth server in the list.
pick_server() {
    increment=${1:-0}
    list_length=$(echo "${SERVERS}" | wc -w)

    if [ "${increment}" -ge "${list_length}" ]; then
        # We've reached the end of the list
        new_error="No more server available"
        SERVERS_SSH_ERRORS=$(printf "%s\\n%s" "${SERVERS_SSH_ERRORS}" "${new_error}" | sed -e '/^$/d')

        # Log errors to stderr
        printf "%s\\n" "${SERVERS_SSH_ERRORS}" >&2
        # Log errors to logfile
        printf "%s\\n" "${SERVERS_SSH_ERRORS}" >> $LOGFILE
        return 1
    fi

    # Extract the day of month, without leading 0 (which would give an octal based number)
    today=$(date +%e)
    # A salt is useful to randomize the starting point in the list
    # but stay identical each time it's called for a server (based on hostname).
    salt=$(hostname | cksum | cut -d' ' -f1)
    # Pick an integer between 0 and the length of the SERVERS list
    # It changes each day
    item=$(( (today + salt + increment) % list_length ))
    # cut starts counting fields at 1, not 0.
    field=$(( item + 1 ))

    echo "${SERVERS}" | cut -d' ' -f${field}
}

## Verify other evobackup process and kill if needed
if [ -e "${PIDFILE}" ]; then
    pid=$(cat "${PIDFILE}")
    # Does process still exist ?
    if kill -0 "${pid}" 2>/dev/null; then
        # Killing the childs of evobackup.
        for ppid in $(pgrep -P "${pid}"); do
            kill -9 "${ppid}";
        done
        # Then kill the main PID.
        kill -9 "${pid}"
        printf "%s is still running (PID %s). Process has been killed" "$0" "${pid}\\n" >&2
    else
        rm -f ${PIDFILE}
    fi
fi
echo "$$" > ${PIDFILE}
# shellcheck disable=SC2064
trap "rm -f ${PIDFILE}" EXIT


##### LOCAL BACKUP ####################################################

if [ "${LOCAL_TASKS}" = "1" ]; then
    ## Dump system and kernel versions
    uname -a > ${LOCAL_BACKUP_DIR}/uname

    ## Dump network routes with mtr and traceroute (warning: could be long with aggressive firewalls)
    for addr in 8.8.8.8 www.evolix.fr travaux.evolix.net; do
        mtr -r ${addr} > ${LOCAL_BACKUP_DIR}/mtr-${addr}
        traceroute -n ${addr} > ${LOCAL_BACKUP_DIR}/traceroute-${addr} 2>&1
    done

    ## Dump process with ps
    ps auwwx >${LOCAL_BACKUP_DIR}/ps.out

    if [ "${SYSTEM}" = "linux" ]; then
        ## Dump network connections with ss
        ss -taupen > ${LOCAL_BACKUP_DIR}/netstat.out

        ## List Debian packages
        dpkg -l > ${LOCAL_BACKUP_DIR}/packages
        dpkg --get-selections > ${LOCAL_BACKUP_DIR}/packages.getselections
        apt-cache dumpavail > ${LOCAL_BACKUP_DIR}/packages.available

        ## Dump MBR / table partitions
        disks=$(lsblk -l | grep disk | grep -v -E '(drbd|fd[0-9]+)' | awk '{print $1}')
        for disk in ${disks}; do
            dd if="/dev/${disk}" of="${LOCAL_BACKUP_DIR}/MBR-${disk}" bs=512 count=1 2>&1 | grep -Ev "(records in|records out|512 bytes)"
            fdisk -l "/dev/${disk}" > "${LOCAL_BACKUP_DIR}/partitions-${disk}" 2>&1
        done
        cat ${LOCAL_BACKUP_DIR}/partitions-* > ${LOCAL_BACKUP_DIR}/partitions

        ## Dump iptables
        if [ -x /sbin/iptables ]; then
            { /sbin/iptables -L -n -v; /sbin/iptables -t filter -L -n -v; } > ${LOCAL_BACKUP_DIR}/iptables.txt
        fi

        ## Dump findmnt(8) output
        FINDMNT_BIN=$(command -v findmnt)
        if [ -x "${FINDMNT_BIN}" ]; then
            ${FINDMNT_BIN} > ${LOCAL_BACKUP_DIR}/findmnt.txt
        fi
    else
        ## Dump network connections with netstat
        netstat -finet -atn > ${LOCAL_BACKUP_DIR}/netstat.out

        ## List OpenBSD packages
        pkg_info -m > ${LOCAL_BACKUP_DIR}/packages

        ## Dump MBR / table partitions
        disklabel sd0 > ${LOCAL_BACKUP_DIR}/partitions

        ## Dump pf infos
        pfctl -sa > ${LOCAL_BACKUP_DIR}/pfctl-sa.txt

    fi

    ## Dump rights
    #getfacl -R /var > ${LOCAL_BACKUP_DIR}/rights-var.txt
    #getfacl -R /etc > ${LOCAL_BACKUP_DIR}/rights-etc.txt
    #getfacl -R /usr > ${LOCAL_BACKUP_DIR}/rights-usr.txt
    #getfacl -R /home > ${LOCAL_BACKUP_DIR}/rights-home.txt

fi

##### REMOTE BACKUP ###################################################

n=0
server=""
if [ "${SERVERS_FALLBACK}" = "1" ]; then
    # We try to find a suitable server
    while :; do
        server=$(pick_server "${n}")
        test $? = 0 || exit 2

        if test_server "${server}"; then
            break
        else
            server=""
            n=$(( n + 1 ))
        fi
    done
else
    # we force the server
    server=$(pick_server "${n}")
fi

SSH_SERVER=$(echo "${server}" | cut -d':' -f1)
SSH_PORT=$(echo "${server}" | cut -d':' -f2)

HOSTNAME=$(hostname)

if [ "${SYSTEM}" = "linux" ]; then
    rep="/bin /boot /lib /opt /sbin /usr /srv"
else
    rep="/bsd /bin /sbin /usr"
fi


if [ "${SYNC_TASKS}" = "1" ]; then
    # /!\ DO NOT USE COMMENTS in the rsync command /!\
    # It breaks the command and destroys data, simply remove (or add) lines.

    # Remote shell command
    RSH_COMMAND="ssh -i {{ evobackup_client__root_key_path }} -p ${SSH_PORT} -o 'ConnectTimeout ${SSH_CONNECT_TIMEOUT}'"

    # ignore check because we want it to split the different arguments to $rep
    # shellcheck disable=SC2086
    rsync -avzh --stats --delete --delete-excluded --force --ignore-errors --partial \
        --exclude "lost+found"                             \
        --exclude ".nfs.*"                                 \
        --exclude "/var/log"                               \
        --exclude "/var/log/evobackup*"                    \
        --exclude "/var/lib/mysql"                         \
        --exclude "/var/lib/postgres"                      \
        --exclude "/var/lib/postgresql"                    \
        --exclude "/var/lib/sympa"                         \
        --exclude "/var/lib/metche"                        \
        --exclude "/var/run"                               \
        --exclude "/var/lock"                              \
        --exclude "/var/state"                             \
        --exclude "/var/apt"                               \
        --exclude "/var/cache"                             \
        --exclude "/usr/src"                               \
        --exclude "/usr/doc"                               \
        --exclude "/usr/share/doc"                         \
        --exclude "/usr/obj"                               \
        --exclude "dev"                                    \
        --exclude "/var/spool/postfix"                     \
        --exclude "/var/lib/amavis/amavisd.sock"           \
        --exclude "/var/lib/munin/*tmp*"                   \
        --exclude "/var/lib/php5"                          \
        --exclude "/var/spool/squid"                       \
        --exclude "/var/lib/elasticsearch"                 \
        --exclude "/var/lib/amavis/tmp"                    \
        --exclude "/var/lib/clamav/*.tmp"                  \
        --exclude "/home/mysqltmp"                         \
        --exclude "/var/lib/php/sessions"                  \
            ${rep}                                         \
            /etc                                           \
            /root                                          \
            /var                                           \
        -e "${RSH_COMMAND}"                                \
        "root@${SSH_SERVER}:/var/backup/"                  \
        | tail -30 >> $LOGFILE
fi

##### REPORTING #######################################################

END=$(/bin/date +"%d-%m-%Y ; %H:%M")

printf "EvoBackup - %s - START %s ON %s (LOCAL_TASKS=%s SYNC_TASKS=%s)\\n" \
       "${HOSTNAME}" "${BEGINNING}" "${SSH_SERVER}" "${LOCAL_TASKS}" "${SYNC_TASKS}" \
       >> $LOGFILE

printf "EvoBackup - %s - STOP %s ON %s (LOCAL_TASKS=%s SYNC_TASKS=%s)\\n" \
       "${HOSTNAME}" "${END}" "${SSH_SERVER}" "${LOCAL_TASKS}" "${SYNC_TASKS}" \
       >> $LOGFILE

tail -10 $LOGFILE |                                    \
    mail -s "[info] EvoBackup - Client ${HOSTNAME}"    \
    ${MAIL}
