457 lines
12 KiB
Bash
457 lines
12 KiB
Bash
#!/bin/bash
|
|
# DOBAPOR - DumpOrBackupAndPruneOrRestore
|
|
# (c) 2019 Tobias Strobel <git@strobeltobias.de>
|
|
|
|
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} <root@${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 <<EOT >>${TMPDIR}/fileList.borg
|
|
/root
|
|
/home
|
|
/opt
|
|
EOT
|
|
cat <<EOT >>${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 <<EOT >>${TMPDIR}/fileList.borg
|
|
/etc
|
|
EOT
|
|
cat <<EOT >>${TMPDIR}/excludeList.borg
|
|
/etc/shadow*
|
|
EOT
|
|
runBorgBackup etc
|
|
;;&
|
|
# nextcloud|all)
|
|
# dump nextcloud
|
|
# nextcloudMaintenanceMode on
|
|
# cat <<EOT >>${TMPDIR}/fileList.borg
|
|
# ${CONFIG_DIR}/nextcloud
|
|
# ${DATA_DIR}/nextcloud
|
|
#EOT
|
|
# runBorgBackup nextcloud
|
|
# nextcloudMaintenanceMode off
|
|
# ;;&
|
|
wallabag|all)
|
|
dump wallabag
|
|
cat <<EOT >>${TMPDIR}/fileList.borg
|
|
${CONFIG_DIR}/wallabag
|
|
${DATA_DIR}/wallabag
|
|
EOT
|
|
runBorgBackup wallabag
|
|
;;&
|
|
# ttrss|all)
|
|
# dump ttrss
|
|
# cat <<EOT >>${TMPDIR}/fileList.borg
|
|
# ${CONFIG_DIR}/ttrss
|
|
# ${DATA_DIR}/ttrss
|
|
#EOT
|
|
# runBorgBackup ttrss
|
|
# ;;&
|
|
ejabberd|all)
|
|
dump ejabberd
|
|
cat <<EOT >>${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 <<EOT >>${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
|