5a56965a48
/home/myhome/dir1/dir2/ is not a directory create it now? The expected result is that it create the folder also if is nested.
249 lines
8.9 KiB
Bash
Executable File
249 lines
8.9 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then
|
|
BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}"
|
|
fi
|
|
|
|
if [[ ! ${1} =~ (backup|restore) ]]; then
|
|
echo "First parameter needs to be 'backup' or 'restore'"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ${1} == "backup" && ! ${2} =~ (crypt|vmail|redis|rspamd|postfix|mysql|all) ]]; then
|
|
echo "Second parameter needs to be 'vmail', 'crypt', 'redis', 'rspamd', 'postfix', 'mysql' or 'all'"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z ${BACKUP_LOCATION} ]]; then
|
|
while [[ -z ${BACKUP_LOCATION} ]]; do
|
|
read -ep "Backup location (absolute path, starting with /): " BACKUP_LOCATION
|
|
done
|
|
fi
|
|
|
|
if [[ ! ${BACKUP_LOCATION} =~ ^/ ]]; then
|
|
echo "Backup directory needs to be given as absolute path (starting with /)."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -f ${BACKUP_LOCATION} ]]; then
|
|
echo "${BACKUP_LOCATION} is a file!"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -d ${BACKUP_LOCATION} ]]; then
|
|
echo "${BACKUP_LOCATION} is not a directory"
|
|
read -p "Create it now? [y|N] " CREATE_BACKUP_LOCATION
|
|
if [[ ! ${CREATE_BACKUP_LOCATION,,} =~ ^(yes|y)$ ]]; then
|
|
exit 1
|
|
else
|
|
mkdir -p ${BACKUP_LOCATION}
|
|
chmod 755 ${BACKUP_LOCATION}
|
|
fi
|
|
else
|
|
if [[ ${1} == "backup" ]] && [[ -z $(echo $(stat -Lc %a ${BACKUP_LOCATION}) | grep -oE '[0-9][0-9][5-7]') ]]; then
|
|
echo "${BACKUP_LOCATION} is not write-able for others, that's required for a backup."
|
|
exit 1
|
|
fi
|
|
fi
|
|
BACKUP_LOCATION=$(echo ${BACKUP_LOCATION} | sed 's#/$##')
|
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
COMPOSE_FILE=${SCRIPT_DIR}/../docker-compose.yml
|
|
echo "Using ${BACKUP_LOCATION} as backup/restore location."
|
|
echo
|
|
source ${SCRIPT_DIR}/../mailcow.conf
|
|
CMPS_PRJ=$(echo $COMPOSE_PROJECT_NAME | tr -cd "[A-Za-z-_]")
|
|
|
|
function backup() {
|
|
DATE=$(date +"%Y-%m-%d-%H-%M-%S")
|
|
mkdir -p "${BACKUP_LOCATION}/mailcow-${DATE}"
|
|
chmod 755 "${BACKUP_LOCATION}/mailcow-${DATE}"
|
|
cp "${SCRIPT_DIR}/../mailcow.conf" "${BACKUP_LOCATION}/mailcow-${DATE}"
|
|
while (( "$#" )); do
|
|
case "$1" in
|
|
vmail|all)
|
|
docker run --rm \
|
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_vmail-vol-1):/vmail:ro \
|
|
debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_vmail.tar.gz /vmail
|
|
;;&
|
|
crypt|all)
|
|
docker run --rm \
|
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_crypt-vol-1):/crypt:ro \
|
|
debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_crypt.tar.gz /crypt
|
|
;;&
|
|
redis|all)
|
|
docker exec $(docker ps -qf name=redis-mailcow) redis-cli save
|
|
docker run --rm \
|
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_redis-vol-1):/redis:ro \
|
|
debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_redis.tar.gz /redis
|
|
;;&
|
|
rspamd|all)
|
|
docker run --rm \
|
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_rspamd-vol-1):/rspamd:ro \
|
|
debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_rspamd.tar.gz /rspamd
|
|
;;&
|
|
postfix|all)
|
|
docker run --rm \
|
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_postfix-vol-1):/postfix:ro \
|
|
debian:stretch-slim /bin/tar --warning='no-file-ignored' -Pcvpzf /backup/backup_postfix.tar.gz /postfix
|
|
;;&
|
|
mysql|all)
|
|
SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${COMPOSE_FILE})
|
|
docker run --rm \
|
|
--network $(docker network ls -qf name=${CMPS_PRJ}_) \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_mysql-vol-1):/var/lib/mysql/:ro \
|
|
--entrypoint= \
|
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup \
|
|
${SQLIMAGE} /bin/sh -c "mysqldump -hmysql -uroot -p${DBROOT} --all-databases | gzip > /backup/backup_mysql.gz"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
}
|
|
|
|
function restore() {
|
|
docker stop $(docker ps -qf name=watchdog-mailcow)
|
|
RESTORE_LOCATION="${1}"
|
|
shift
|
|
while (( "$#" )); do
|
|
case "$1" in
|
|
vmail)
|
|
docker stop $(docker ps -qf name=dovecot-mailcow)
|
|
docker run -it --rm \
|
|
-v ${RESTORE_LOCATION}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_vmail-vol-1):/vmail \
|
|
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_vmail.tar.gz
|
|
docker start $(docker ps -aqf name=dovecot-mailcow)
|
|
echo
|
|
echo "In most cases it is not required to run a full resync, you can run the command printed below at any time after testing wether the restore process broke a mailbox:"
|
|
echo
|
|
echo "docker exec $(docker ps -qf name=dovecot-mailcow) doveadm force-resync -A '*'"
|
|
echo
|
|
read -p "Force a resync now? [y|N] " FORCE_RESYNC
|
|
if [[ ${FORCE_RESYNC,,} =~ ^(yes|y)$ ]]; then
|
|
docker exec $(docker ps -qf name=dovecot-mailcow) doveadm force-resync -A '*'
|
|
else
|
|
echo "OK, skipped."
|
|
fi
|
|
;;
|
|
redis)
|
|
docker stop $(docker ps -qf name=redis-mailcow)
|
|
docker run -it --rm \
|
|
-v ${RESTORE_LOCATION}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_redis-vol-1):/redis \
|
|
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_redis.tar.gz
|
|
docker start $(docker ps -aqf name=redis-mailcow)
|
|
;;
|
|
crypt)
|
|
docker stop $(docker ps -qf name=dovecot-mailcow)
|
|
docker run -it --rm \
|
|
-v ${RESTORE_LOCATION}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_crypt-vol-1):/crypt \
|
|
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_crypt.tar.gz
|
|
docker start $(docker ps -aqf name=dovecot-mailcow)
|
|
;;
|
|
rspamd)
|
|
docker stop $(docker ps -qf name=rspamd-mailcow)
|
|
docker run -it --rm \
|
|
-v ${RESTORE_LOCATION}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_rspamd-vol-1):/rspamd \
|
|
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_rspamd.tar.gz
|
|
docker start $(docker ps -aqf name=rspamd-mailcow)
|
|
;;
|
|
postfix)
|
|
docker stop $(docker ps -qf name=postfix-mailcow)
|
|
docker run -it --rm \
|
|
-v ${RESTORE_LOCATION}:/backup \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_postfix-vol-1):/postfix \
|
|
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_postfix.tar.gz
|
|
docker start $(docker ps -aqf name=postfix-mailcow)
|
|
;;
|
|
mysql)
|
|
SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${COMPOSE_FILE})
|
|
docker stop $(docker ps -qf name=mysql-mailcow)
|
|
docker run \
|
|
-it --rm \
|
|
-v $(docker volume ls -qf name=${CMPS_PRJ}_mysql-vol-1):/var/lib/mysql/ \
|
|
--entrypoint= \
|
|
-u mysql \
|
|
-v ${RESTORE_LOCATION}:/backup \
|
|
${SQLIMAGE} /bin/sh -c "mysqld --skip-grant-tables & \
|
|
until mysqladmin ping; do sleep 3; done && \
|
|
echo Restoring... && \
|
|
gunzip < backup/backup_mysql.gz | mysql -uroot && \
|
|
mysql -uroot -e SHUTDOWN;"
|
|
docker start $(docker ps -aqf name=mysql-mailcow)
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
docker start $(docker ps -aqf name=watchdog-mailcow)
|
|
}
|
|
|
|
if [[ ${1} == "backup" ]]; then
|
|
backup ${@,,}
|
|
elif [[ ${1} == "restore" ]]; then
|
|
i=1
|
|
declare -A FOLDER_SELECTION
|
|
if [[ $(find ${BACKUP_LOCATION}/mailcow-* -maxdepth 1 -type d 2> /dev/null| wc -l) -lt 1 ]]; then
|
|
echo "Selected backup location has no subfolders"
|
|
exit 1
|
|
fi
|
|
for folder in $(ls -d ${BACKUP_LOCATION}/mailcow-*/); do
|
|
echo "[ ${i} ] - ${folder}"
|
|
FOLDER_SELECTION[${i}]="${folder}"
|
|
((i++))
|
|
done
|
|
echo
|
|
input_sel=0
|
|
while [[ ${input_sel} -lt 1 || ${input_sel} -gt ${i} ]]; do
|
|
read -p "Select a restore point: " input_sel
|
|
done
|
|
i=1
|
|
echo
|
|
declare -A FILE_SELECTION
|
|
RESTORE_POINT="${FOLDER_SELECTION[${input_sel}]}"
|
|
if [[ -z $(find "${FOLDER_SELECTION[${input_sel}]}" -maxdepth 1 -type f -regex ".*\(redis\|rspamd\|mysql\|crypt\|vmail\|postfix\).*") ]]; then
|
|
echo "No datasets found"
|
|
exit 1
|
|
fi
|
|
for file in $(ls -f "${FOLDER_SELECTION[${input_sel}]}"); do
|
|
if [[ ${file} =~ vmail ]]; then
|
|
echo "[ ${i} ] - Mail directory (/var/vmail)"
|
|
FILE_SELECTION[${i}]="vmail"
|
|
((i++))
|
|
elif [[ ${file} =~ crypt ]]; then
|
|
echo "[ ${i} ] - Crypt data"
|
|
FILE_SELECTION[${i}]="crypt"
|
|
((i++))
|
|
elif [[ ${file} =~ redis ]]; then
|
|
echo "[ ${i} ] - Redis DB"
|
|
FILE_SELECTION[${i}]="redis"
|
|
((i++))
|
|
elif [[ ${file} =~ rspamd ]]; then
|
|
echo "[ ${i} ] - Rspamd data"
|
|
FILE_SELECTION[${i}]="rspamd"
|
|
((i++))
|
|
elif [[ ${file} =~ postfix ]]; then
|
|
echo "[ ${i} ] - Postfix data"
|
|
FILE_SELECTION[${i}]="postfix"
|
|
((i++))
|
|
elif [[ ${file} =~ mysql ]]; then
|
|
echo "[ ${i} ] - SQL DB"
|
|
FILE_SELECTION[${i}]="mysql"
|
|
((i++))
|
|
fi
|
|
done
|
|
echo
|
|
input_sel=0
|
|
while [[ ${input_sel} -lt 1 || ${input_sel} -gt ${i} ]]; do
|
|
read -p "Select a dataset to restore: " input_sel
|
|
done
|
|
echo "Restoring ${FILE_SELECTION[${input_sel}]} from ${RESTORE_POINT}..."
|
|
restore "${RESTORE_POINT}" ${FILE_SELECTION[${input_sel}]}
|
|
fi
|