diff --git a/aech/dobapor.sh b/aech/dobapor.sh new file mode 100644 index 0000000..010d5d5 --- /dev/null +++ b/aech/dobapor.sh @@ -0,0 +1,457 @@ +#!/bin/bash +# DOBAPOR - DumpOrBackupAndPruneOrRestore +# (c) 2019 Tobias Strobel + +DATEFORMAT='%Y-%m-%d-%H-%M-%S' +BKPPATH='/root/backup' +LOGPATH="${BKPPATH}/log" +TMPDIR=$(mktemp -d) +HOST=$(hostname) +DOMAIN=$(hostname -d) +ROOT_DIR='/opt' +CONFIG_DIR="${ROOT_DIR}/config" +DATA_DIR="${ROOT_DIR}/data" +RUNTIME_DIR="${ROOT_DIR}/runtime" +ACTIONS=( dump backup restore prune status ) +MCSERVICE='vmail|crypt|redis|rspamd|postfix|mysql' +SERVICE='rootfs|etc' +#DUMPSERVICE='nextcloud|wallabag|mailcow|ttrss|ejabberd' +DUMPSERVICE='wallabag|mailcow|ejabberd' +CHOOSESERVICE="${SERVICE}|${DUMPSERVICE}|all" +CHOOSEMCSERVICE="${MCSERVICE}|all" +DB_KEEPLATEST=4 # n+1 +LOGS_KEEPLATEST=11 # n+1 +#BORG_KEEPWITHIN=2d +#BORG_KEEPDAILY=7 +#BORG_KEEPWEEKLY=4 +#BORG_KEEPMONTHLY=3 +BORG_KEEPWITHIN=1H +BORG_KEEPDAILY=0 +BORG_KEEPWEEKLY=0 +BORG_KEEPMONTHLY=0 +declare -a BORG_REPOS +declare -a BORG_PASSPHRASES +BORG_REPOS[1]='/localbackup' +BORG_PASSPHRASES[1]='truth plug charcoal spoils thank ladder chaperone scale playmate retiree' +BORG_REPOS[2]='rsync.net:borg-aech' +BORG_PASSPHRASES[2]='debtor snowcap protrude swinger doozy catchy frenzy shininess denote ferris' + +# only for Rsync.net users +export BORG_REMOTE_PATH=/usr/local/bin/borg1/borg1 + +if [[ ! ${1} =~ (dump|backup|restore|prune|status) ]]; then + echo "First parameter needs to be 'dump|backup|restore|prune|status'" + exit 1 +elif [[ ${1} =~ (backup|prune) && ! ${2} =~ (${CHOOSESERVICE}) ]]; then + echo "Second parameter needs to be '${CHOOSESERVICE}'" + exit 1 +elif [[ ${1} =~ (dump) && ! ${2} =~ (${DUMPSERVICE}) ]]; then + echo "Second parameter needs to be '${DUMPSERVICE}'" + exit 1 +fi + +ACTION=${1} +if [ ! -d "${BKPPATH}" ]; then + mkdir -p ${BKPPATH} +fi +if [ ! -d "${LOGPATH}" ]; then + mkdir -p ${LOGPATH} +fi +LOGFILE="${LOGPATH}/${ACTION}-$(date +${DATEFORMAT}).log" + +## FUNCTIONS ################################################### + +function dumpDBfromDocker() { + + if [[ ! ${1} =~ (mysql|postgres) ]]; then + echo 'First parameter needs to be mysql or postgres.' + exit 1 + fi + if [[ ${1} =~ (postgres) ]] && [[ ! ${2} =~ (nextcloud|wallabag|ttrss|ejabberd) ]]; then + echo 'Second parameter needs to be nextcloud, wallabag, ttrss or ejabberd.' + exit 1 + fi + + mkdir -p "${DATA_DIR}/${2}/dumps" + chmod 700 -R "${DATA_DIR}/${2}/dumps" + + echo "Dump ${1} database of ${2}..." + case "${1}" in + mysql) + if [[ ! -f ${CONFIG_DIR}/${2}/${2}.env ]]; then + echo "No env file for ${2} found!" + exit 1 + fi + source ${CONFIG_DIR}/${2}/${2}.env + SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${CONFIG_DIR}/${2}/docker-compose.yml|head -n1) + docker run --rm \ + --network $(docker network ls -qf name=${2}_internal) \ + -v ${RUNTIME_DIR}/${2}/db:/var/lib/mysql/ \ + --entrypoint= \ + -v ${DATA_DIR}/${2}/dumps:/dump \ + ${SQLIMAGE} /bin/sh -c "mysqldump -h${MYSQL_HOST:-db} -u${MYSQL_USER:-${2}} -p${MYSQL_PASSWORD} --all-databases --single-transaction --quick|gzip -c >/dump/mysql-$(date +${DATEFORMAT}).gz; \ + ls -tp /dump/mysql*| grep -v '/$' | tail -n +${DB_KEEPLATEST:-4} | xargs -d '\n' -r rm --" + check $? + ;;& + postgres) + if [[ ! -f ${CONFIG_DIR}/${2}/${2}.env ]]; then + echo "No env file for ${2} found!" + exit 1 + fi + source ${CONFIG_DIR}/${2}/${2}.env + SQLIMAGE=$(grep -iEo 'postgres\:.+' ${CONFIG_DIR}/${2}/docker-compose.yml|head -n1) + docker run --rm \ + --network $(docker network ls -qf name=${2}_internal) \ + -v ${RUNTIME_DIR}/${2}/postgres:/var/lib/postgresql/data \ + --entrypoint= \ + -v ${DATA_DIR}/${2}/dumps:/dump \ + ${SQLIMAGE} /bin/sh -c "pg_dump -Fc postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-${2}} -f /dump/postgres-$(date +${DATEFORMAT}).dump; \ + ls -tp /dump/postgres*| grep -v '/$' | tail -n +${DB_KEEPLATEST:-4} | xargs -d '\n' -r rm --" + check $? + ;; + esac +} + +function runBorgBackup() { + if [[ ! ${1} =~ (${SERVICE}|${DUMPSERVICE}) ]]; then + echo "First parameter needs specify the service: '${SERVICE}'" + exit 1 + fi + if [[ ! -f ${TMPDIR}/fileList.borg ]]; then + echo 'No file list for borg is present in tmpdir!' + exit 1 + fi + if [[ ! -f ${TMPDIR}/excludeList.borg ]]; then + touch ${TMPDIR}/excludeList.borg + fi + + for ((i=1;i<=${#BORG_REPOS[@]};i++)) + do + BORG_REPO=${BORG_REPOS[$i]} + export BORG_PASSPHRASE=${BORG_PASSPHRASES[$i]} + echo "Push backup of ${1} to $BORG_REPO..." + borg create -v --stats --compression lz4 --exclude-from ${TMPDIR}/excludeList.borg \ + ${BORG_REPO}::${1}'-{now:%Y-%m-%d_%H:%M:%S}' $(<${TMPDIR}/fileList.borg) + check $? + echo + done + >${TMPDIR}/excludeList.borg + >${TMPDIR}/fileList.borg +} + +function runBorgPrune() { + if [[ ! ${1} =~ (${SERVICE}|${DUMPSERVICE}) ]]; then + echo "First parameter needs specify the service: '${SERVICE}'" + exit 1 + fi + + + echo "###### Pruning backup for ${1} on $(date) ######" + for ((i=1;i<=${#BORG_REPOS[@]};i++)) + do + BORG_REPO=${BORG_REPOS[$i]} + export BORG_PASSPHRASE=${BORG_PASSPHRASES[$i]} + borg prune -v --list --prefix ${1} ${BORG_REPO} \ + --keep-within=${BORG_KEEPWITHIN:-2d} \ + --keep-daily=${BORG_KEEPDAILY:-7} \ + --keep-weekly=${BORG_KEEPWEEKLY:-4} \ + --keep-monthly=${BORG_KEEPMONTHLY:-6} + check $? + echo + done +} + +function pruneLogs() { + cd ${LOGPATH} + for ACT in ${ACTIONS[@]} + do + find . -maxdepth 1 -name "${ACT}*.log" | tail -n +${LOGS_KEEPLATEST:-6} | xargs -i rm -- {} + done +} + +function showBorgStats() { + for ((i=1;i<=${#BORG_REPOS[@]};i++)) + do + BORG_REPO=${BORG_REPOS[$i]} + export BORG_PASSPHRASE=${BORG_PASSPHRASES[$i]} + #borg info ${1} ${BORG_REPO} + echo + done +} + +function mailcowBaR () { + if [[ ! ${1} =~ (${MCSERVICE}|all) ]]; then + echo "First parameter needs to be ${MCSERVICE}|all" + exit 1 + fi + echo "Execute mailcow backup script for ${1}..." + MAILCOW_BACKUP_LOCATION=${ROOT_DIR}/data/mailcow/dumps /opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh backup ${1} >/dev/null + check $? + cd ${ROOT_DIR}/data/mailcow/dumps/; ls -1 -d "$PWD/"* -t | tail -n +${DB_KEEPLATEST:-4} | xargs -i rm -r -- {} +} + +function nextcloudMaintenanceMode() { + /usr/bin/docker exec $(docker ps -qf name=nextcloud_cron) php /var/www/html/occ maintenance:mode --${1:-off} +} + +function notifyAdmin() { + if [ -f ${TMPDIR}/errorsOccured ]; then + /usr/bin/mailx -a "From: ${HOST}.${DOMAIN} " -s "[${HOST}] ${ACTION} - errors occured" hostmaster@${DOMAIN} < ${LOGFILE} + rm ${TMPDIR}/errorsOccured + fi +} + +function check() { + if [ $1 -eq 1 ]; then + echo '==> FAILED!' + if [ ! -f ${TMPDIR}/errorsOccured ]; then + touch ${TMPDIR}/errorsOccured + fi + return 1 + fi +} + +function dump() { + while (( "$#" )); do + case "${1}" in +# nextcloud|all) +# nextcloudMaintenanceMode on +# dumpDBfromDocker postgres nextcloud +# nextcloudMaintenanceMode off +# ;;& + wallabag|all) + dumpDBfromDocker postgres wallabag + ;;& +# ttrss|all) +# dumpDBfromDocker postgres ttrss +# ;;& + ejabberd|all) + dumpDBfromDocker postgres ejabberd + ;;& + mailcow|all) + while (( "$#" )); do + case "${2}" in + all) + mailcowBaR all + ;;& + vmail) + mailcowBaR vmail + ;;& + crypt) + mailcowBaR crypt + ;;& + redis) + mailcowBaR redis + ;;& + rspamd) + mailcowBaR rspamd + ;;& + postfix) + mailcowBaR postfix + ;;& + mysql) + mailcowBaR mysql + ;; + esac + shift + done + ;; + esac + shift + done +} + +function backup() { + echo "###### Start backup of $HOST on $(date) ######" + + while (( "$#" )); do + case "${2}" in + rootfs|all) + echo 'Get current locally installed software...' + dpkg --get-selections > ${BKPPATH}/software.list + check $? + cat <>${TMPDIR}/fileList.borg + /root + /home + /opt +EOT + cat <>${TMPDIR}/excludeList.borg + /root/.ssh/rsyncnet* + ${LOGPATH}/*.log + ${RUNTIME_DIR} + /opt/mailcow-dockerized + ${DATA_DIR}/nextcloud* + ${DATA_DIR}/wallabag* + ${DATA_DIR}/ttrss* + ${DATA_DIR}/mailcow* + ${DATA_DIR}/ejabberd* + ${DATA_DIR}/filer* +EOT + runBorgBackup rootfs + ;;& + etc|rootfs) + cat <>${TMPDIR}/fileList.borg + /etc +EOT + cat <>${TMPDIR}/excludeList.borg + /etc/shadow* +EOT + runBorgBackup etc + ;;& +# nextcloud|all) +# dump nextcloud +# nextcloudMaintenanceMode on +# cat <>${TMPDIR}/fileList.borg +# ${CONFIG_DIR}/nextcloud +# ${DATA_DIR}/nextcloud +#EOT +# runBorgBackup nextcloud +# nextcloudMaintenanceMode off +# ;;& + wallabag|all) + dump wallabag + cat <>${TMPDIR}/fileList.borg + ${CONFIG_DIR}/wallabag + ${DATA_DIR}/wallabag +EOT + runBorgBackup wallabag + ;;& +# ttrss|all) +# dump ttrss +# cat <>${TMPDIR}/fileList.borg +# ${CONFIG_DIR}/ttrss +# ${DATA_DIR}/ttrss +#EOT +# runBorgBackup ttrss +# ;;& + ejabberd|all) + dump ejabberd + cat <>${TMPDIR}/fileList.borg + ${CONFIG_DIR}/ejabberd + ${DATA_DIR}/ejabberd + ${CONFIG_DIR}/filer + ${DATA_DIR}/filer +EOT + runBorgBackup ejabberd + ;;& + mailcow|all) +# if [ "${2}" -eq 'all' ] ; then +# 3='all' +# fi +# while (( "$#" )); do +# case "${3}" in +# all) + dump mailcow all + cat <>${TMPDIR}/fileList.borg + ${ROOT_DIR}/mailcow-dockerized + ${DATA_DIR}/mailcow +EOT + runBorgBackup mailcow +# ;; +# vmail) +# dump mailcow vmail +# # IMPLEMENT BACKUP VIA MC-VMAIL and so on!! +# ;; +# crypt) +# dump mailcow crypt +# ;; +# redis) +# dump mailcow redis +# ;; +# rspamd) +# dump mailcow rspamd +# ;; +# postfix) +# dump mailcow postfix +# ;; +# mysql) +# dump mailcow mysql +# ;; +# esac +# shift +# done + ;; + esac + shift + done + echo +} + +function prune() { + while (( "$#" )); do + case "${2}" in + rootfs|all) + runBorgPrune rootfs + ;;& + etc|all) + runBorgPrune rootfs + ;;& +# nextcloud|all) +# runBorgPrune nextcloud +# ;;& + wallabag|all) + runBorgPrune wallabag + ;;& +# ttrss|all) +# runBorgPrune ttrss +# ;;& + ejabberd|all) + runBorgPrune ejabberd + ;;& + mailcow|all) +# if [ "${2}" = 'all' ] ; then +# mailcowOpt='all' +# fi +# while (( "$#" )); do +# case "${mailcowOpt}" in +# all) + runBorgPrune mailcow +# ;; +# vmail) +# runBorgPrune mailcow +# ;; +# crypt) +# runBorgPrune mailcow +# ;; +# redis) +# runBorgPrune mailcow +# ;; +# rspamd) +# runBorgPrune mailcow +# ;; +# postfix) +# runBorgPrune mailcow +# ;; +# mysql) +# runBorgPrune mailcow +# ;; +# esac +# shift +# done +# ;;& +# all) +# pruneLogs + ;; + esac + shift + done +} + +# Write output to logfile +exec > >(tee -i ${LOGFILE}) +exec 2>&1 + +if [[ ${ACTION} == 'dump' ]]; then + dump ${@,,} +elif [[ ${ACTION} == 'backup' ]]; then + backup ${@,,} +elif [[ ${ACTION} == 'restore' ]]; then + echo 'Restore not yet implemented!' +elif [[ ${ACTION} == 'prune' ]]; then + prune ${@,,} +elif [[ ${ACTION} == 'status' ]]; then + showBorgStats ${2} +fi +echo "Finished!" +notifyAdmin +rm -r $TMPDIR