2019-05-10 15:41:24 +02:00
#!/usr/bin/env bash
2018-01-12 09:30:54 +02:00
2018-10-15 11:37:10 +02:00
if [ [ ! -z ${ MAILCOW_BACKUP_LOCATION } ] ] ; then
BACKUP_LOCATION = " ${ MAILCOW_BACKUP_LOCATION } "
fi
2018-01-12 09:30:54 +02:00
if [ [ ! ${ 1 } = ~ ( backup| restore) ] ] ; then
echo "First parameter needs to be 'backup' or 'restore'"
exit 1
fi
2019-10-23 21:41:19 +02:00
if [ [ ${ 1 } = = "backup" && ! ${ 2 } = ~ ( crypt| vmail| redis| rspamd| postfix| mysql| all| --delete-days) ] ] ; then
echo "Second parameter needs to be 'vmail', 'crypt', 'redis', 'rspamd', 'postfix', 'mysql', 'all' or '--delete-days'"
2018-01-12 09:30:54 +02:00
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
2019-02-08 19:11:46 +02:00
mkdir -p ${ BACKUP_LOCATION }
2018-01-12 09:30:54 +02:00
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
2020-03-28 20:50:15 +02:00
2018-01-12 09:30:54 +02:00
BACKUP_LOCATION = $( echo ${ BACKUP_LOCATION } | sed 's#/$##' )
SCRIPT_DIR = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) " && pwd ) "
COMPOSE_FILE = ${ SCRIPT_DIR } /../docker-compose.yml
2020-03-28 20:50:15 +02:00
if [ ! -f ${ COMPOSE_FILE } ] ; then
echo "Compose file not found"
exit 1
fi
2018-01-12 09:30:54 +02:00
echo " Using ${ BACKUP_LOCATION } as backup/restore location. "
echo
2020-03-28 20:50:15 +02:00
2018-01-12 09:30:54 +02:00
source ${ SCRIPT_DIR } /../mailcow.conf
2020-03-28 20:50:15 +02:00
if [ [ -z ${ COMPOSE_PROJECT_NAME } ] ] ; then
echo "Could not determine compose project name"
exit 1
else
echo " Found project name ${ COMPOSE_PROJECT_NAME } "
2020-03-29 10:45:27 +02:00
CMPS_PRJ = $( echo ${ COMPOSE_PROJECT_NAME } | tr -cd "[0-9A-Za-z-_]" )
2020-03-28 20:50:15 +02:00
fi
2018-01-12 09:30:54 +02:00
function backup( ) {
DATE = $( date +"%Y-%m-%d-%H-%M-%S" )
mkdir -p " ${ BACKUP_LOCATION } /mailcow- ${ DATE } "
chmod 755 " ${ BACKUP_LOCATION } /mailcow- ${ DATE } "
2018-07-29 14:27:51 +02:00
cp " ${ SCRIPT_DIR } /../mailcow.conf " " ${ BACKUP_LOCATION } /mailcow- ${ DATE } "
2018-01-12 09:30:54 +02:00
while ( ( " $# " ) ) ; do
case " $1 " in
vmail| all)
docker run --rm \
-v ${ BACKUP_LOCATION } /mailcow-${ DATE } :/backup \
2019-01-05 01:52:37 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _vmail-vol-1) :/vmail:ro \
2019-06-28 22:49:02 +02:00
debian:stretch-slim /bin/tar --warning= 'no-file-ignored' --use-compress-program= "gzip --rsyncable --best" -Pcvpf /backup/backup_vmail.tar.gz /vmail
2018-01-12 09:30:54 +02:00
; ; &
[Docker API] Use TLS encryption for communication with "on-the-fly" created key paris (non-exposed)
[Docker API] Create pipe to pass Rspamd UI worker password
[Dovecot] Pull Spamassassin ruleset to be read by Rspamd (MANY THANKS to Peer Heinlein!)
[Dovecot] Garbage collector for deleted maildirs (set keep time via MAILDIR_GC_TIME which defaults to 1440 minutes)
[Web] Flush memcached after mailbox item changes, fixes #1808
[Web] Fix duplicate IDs, fixes #1792
[Compose] Use SQL sockets
[PHP-FPM] Update APCu and Redis libs
[Dovecot] Encrypt maildir with global key pair in crypt-vol-1 (BACKUP!), also fixes #1791
[Web] Fix deletion of spam aliases
[Helper] Add "crypt" to backup script
[Helper] Override file for external SQL socket (not supported!)
[Compose] New images for Rspamd, PHP-FPM, SOGo, Dovecot, Docker API, Watchdog, ACME, Postfix
2018-09-29 22:01:23 +02:00
crypt| all)
docker run --rm \
-v ${ BACKUP_LOCATION } /mailcow-${ DATE } :/backup \
2019-01-05 01:52:37 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _crypt-vol-1) :/crypt:ro \
2019-06-28 22:49:02 +02:00
debian:stretch-slim /bin/tar --warning= 'no-file-ignored' --use-compress-program= "gzip --rsyncable --best" -Pcvpf /backup/backup_crypt.tar.gz /crypt
[Docker API] Use TLS encryption for communication with "on-the-fly" created key paris (non-exposed)
[Docker API] Create pipe to pass Rspamd UI worker password
[Dovecot] Pull Spamassassin ruleset to be read by Rspamd (MANY THANKS to Peer Heinlein!)
[Dovecot] Garbage collector for deleted maildirs (set keep time via MAILDIR_GC_TIME which defaults to 1440 minutes)
[Web] Flush memcached after mailbox item changes, fixes #1808
[Web] Fix duplicate IDs, fixes #1792
[Compose] Use SQL sockets
[PHP-FPM] Update APCu and Redis libs
[Dovecot] Encrypt maildir with global key pair in crypt-vol-1 (BACKUP!), also fixes #1791
[Web] Fix deletion of spam aliases
[Helper] Add "crypt" to backup script
[Helper] Override file for external SQL socket (not supported!)
[Compose] New images for Rspamd, PHP-FPM, SOGo, Dovecot, Docker API, Watchdog, ACME, Postfix
2018-09-29 22:01:23 +02:00
; ; &
2018-01-12 09:30:54 +02:00
redis| all)
2018-01-14 21:53:26 +02:00
docker exec $( docker ps -qf name = redis-mailcow) redis-cli save
2018-01-12 09:30:54 +02:00
docker run --rm \
-v ${ BACKUP_LOCATION } /mailcow-${ DATE } :/backup \
2019-01-05 01:52:37 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _redis-vol-1) :/redis:ro \
2019-06-28 22:49:02 +02:00
debian:stretch-slim /bin/tar --warning= 'no-file-ignored' --use-compress-program= "gzip --rsyncable --best" -Pcvpf /backup/backup_redis.tar.gz /redis
2018-01-12 09:30:54 +02:00
; ; &
rspamd| all)
docker run --rm \
-v ${ BACKUP_LOCATION } /mailcow-${ DATE } :/backup \
2019-01-05 01:52:37 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _rspamd-vol-1) :/rspamd:ro \
2019-06-28 22:49:02 +02:00
debian:stretch-slim /bin/tar --warning= 'no-file-ignored' --use-compress-program= "gzip --rsyncable --best" -Pcvpf /backup/backup_rspamd.tar.gz /rspamd
2018-01-12 09:30:54 +02:00
; ; &
postfix| all)
2018-01-13 23:09:17 +02:00
docker run --rm \
2018-01-12 09:30:54 +02:00
-v ${ BACKUP_LOCATION } /mailcow-${ DATE } :/backup \
2019-01-05 01:52:37 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _postfix-vol-1) :/postfix:ro \
2019-06-28 22:49:02 +02:00
debian:stretch-slim /bin/tar --warning= 'no-file-ignored' --use-compress-program= "gzip --rsyncable --best" -Pcvpf /backup/backup_postfix.tar.gz /postfix
2018-01-12 09:30:54 +02:00
; ; &
mysql| all)
SQLIMAGE = $( grep -iEo '(mysql|mariadb)\:.+' ${ COMPOSE_FILE } )
2020-03-28 20:50:15 +02:00
if [ [ -z " ${ SQLIMAGE } " ] ] ; then
echo "Could not determine SQL image version, skipping backup..."
shift
continue
else
echo " Using SQL image ${ SQLIMAGE } , starting... "
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 } /mysql:/backup \
${ SQLIMAGE } /bin/sh -c " mariabackup --host mysql --user root --password ${ DBROOT } --backup --rsync --target-dir=/backup "
docker run --rm \
--network $( docker network ls -qf name = ${ CMPS_PRJ } _) \
--entrypoint= \
-v ${ BACKUP_LOCATION } /mailcow-${ DATE } /mysql:/backup \
${ SQLIMAGE } /bin/sh -c "mariabackup --prepare --target-dir=/backup"
fi
2019-10-23 21:41:19 +02:00
; ; &
--delete-days)
shift
if [ [ " ${ 1 } " = ~ ^[ 0-9] +$ ] ] ; then
find ${ BACKUP_LOCATION } /* -maxdepth 0 -mmin +$(( ${ 1 } * 60 * 24 )) -exec rm -rvf { } \;
else
echo "Parameter of --delete-days is not a number."
fi
2018-01-12 09:30:54 +02:00
; ;
esac
shift
done
}
function restore( ) {
2020-03-28 20:50:15 +02:00
echo
echo "Stopping watchdog-mailcow..."
2018-01-12 09:30:54 +02:00
docker stop $( docker ps -qf name = watchdog-mailcow)
2020-03-28 20:50:15 +02:00
echo
2018-01-12 09:30:54 +02:00
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 \
2018-04-26 22:57:20 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _vmail-vol-1) :/vmail \
2018-07-09 22:28:09 +02:00
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_vmail.tar.gz
2018-01-12 09:30:54 +02:00
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 \
2018-04-26 22:57:20 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _redis-vol-1) :/redis \
2018-07-09 22:28:09 +02:00
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_redis.tar.gz
2018-01-12 09:30:54 +02:00
docker start $( docker ps -aqf name = redis-mailcow)
; ;
[Docker API] Use TLS encryption for communication with "on-the-fly" created key paris (non-exposed)
[Docker API] Create pipe to pass Rspamd UI worker password
[Dovecot] Pull Spamassassin ruleset to be read by Rspamd (MANY THANKS to Peer Heinlein!)
[Dovecot] Garbage collector for deleted maildirs (set keep time via MAILDIR_GC_TIME which defaults to 1440 minutes)
[Web] Flush memcached after mailbox item changes, fixes #1808
[Web] Fix duplicate IDs, fixes #1792
[Compose] Use SQL sockets
[PHP-FPM] Update APCu and Redis libs
[Dovecot] Encrypt maildir with global key pair in crypt-vol-1 (BACKUP!), also fixes #1791
[Web] Fix deletion of spam aliases
[Helper] Add "crypt" to backup script
[Helper] Override file for external SQL socket (not supported!)
[Compose] New images for Rspamd, PHP-FPM, SOGo, Dovecot, Docker API, Watchdog, ACME, Postfix
2018-09-29 22:01:23 +02:00
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)
; ;
2018-01-12 09:30:54 +02:00
rspamd)
docker stop $( docker ps -qf name = rspamd-mailcow)
docker run -it --rm \
-v ${ RESTORE_LOCATION } :/backup \
2018-04-26 22:57:20 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _rspamd-vol-1) :/rspamd \
2018-07-09 22:28:09 +02:00
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_rspamd.tar.gz
2018-01-12 09:30:54 +02:00
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 \
2018-04-26 22:57:20 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _postfix-vol-1) :/postfix \
2018-07-09 22:28:09 +02:00
debian:stretch-slim /bin/tar -Pxvzf /backup/backup_postfix.tar.gz
2018-01-12 09:30:54 +02:00
docker start $( docker ps -aqf name = postfix-mailcow)
; ;
mysql)
SQLIMAGE = $( grep -iEo '(mysql|mariadb)\:.+' ${ COMPOSE_FILE } )
2020-03-28 20:50:15 +02:00
read -p "mailcow will be stopped and the corresponding mailcow.conf file of the backup will be restored, do you want to proceed? [y|N] " MYSQL_STOP_MAILCOW
if [ [ ${ MYSQL_STOP_MAILCOW ,, } = ~ ^( yes| y) $ ] ] ; then
echo "Stopping mailcow..."
docker-compose down
else
echo "OK, skipped."
shift
continue
fi
#docker stop $(docker ps -qf name=mysql-mailcow)
2020-02-26 08:09:56 +02:00
if [ [ -d " ${ RESTORE_LOCATION } /mysql " ] ] ; then
docker run --rm \
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _mysql-vol-1) :/var/lib/mysql/:rw \
--entrypoint= \
-v ${ RESTORE_LOCATION } /mysql:/backup \
${ SQLIMAGE } /bin/bash -c "shopt -s dotglob ; /bin/rm -rf /var/lib/mysql/* ; rsync -avh --usermap=root:mysql --groupmap=root:mysql /backup/ /var/lib/mysql/"
elif [ [ -f " ${ RESTORE_LOCATION } /backup_mysql.gz " ] ] ; then
2018-01-12 09:30:54 +02:00
docker run \
-it --rm \
2018-04-26 22:57:20 +02:00
-v $( docker volume ls -qf name = ${ CMPS_PRJ } _mysql-vol-1) :/var/lib/mysql/ \
2018-01-12 09:30:54 +02:00
--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; "
2020-02-26 08:09:56 +02:00
fi
2020-03-28 20:50:15 +02:00
echo "Copying mailcow.conf..."
mv mailcow.conf mailcow.conf_backup_$( date +"%Y-%m-%d-%H-%M-%S" )
cp ${ RESTORE_LOCATION } /mailcow.conf mailcow.conf
echo "Starting mailcow..."
docker-compose up -d
#docker start $(docker ps -aqf name=mysql-mailcow)
2018-01-12 09:30:54 +02:00
; ;
esac
shift
done
2020-03-28 20:50:15 +02:00
echo
echo "Starting watchdog-mailcow..."
2018-01-12 09:30:54 +02:00
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 } ] } "
2020-03-03 21:12:11 +02:00
if [ [ -z $( find " ${ FOLDER_SELECTION [ ${ input_sel } ] } " -maxdepth 1 -type f,d -regex ".*\(redis\|rspamd\|mysql\|crypt\|vmail\|postfix\).*" ) ] ] ; then
2018-01-12 09:30:54 +02:00
echo "No datasets found"
exit 1
fi
2019-09-08 10:54:21 +02:00
2019-11-14 10:35:57 +02:00
echo "[ 0 ] - all"
# find all files in folder with *.gz extension, print their base names, remove backup_, remove .tar (if present), remove .gz
2020-03-03 21:12:11 +02:00
FILE_SELECTION[ 0] = $( find " ${ FOLDER_SELECTION [ ${ input_sel } ] } " -maxdepth 1 -type f,d \( -name '*.gz' -o -name 'mysql' \) -printf '%f\n' | sed 's/backup_*//' | sed 's/\.[^.]*$//' | sed 's/\.[^.]*$//' )
2018-01-12 09:30:54 +02:00
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++) )
[Docker API] Use TLS encryption for communication with "on-the-fly" created key paris (non-exposed)
[Docker API] Create pipe to pass Rspamd UI worker password
[Dovecot] Pull Spamassassin ruleset to be read by Rspamd (MANY THANKS to Peer Heinlein!)
[Dovecot] Garbage collector for deleted maildirs (set keep time via MAILDIR_GC_TIME which defaults to 1440 minutes)
[Web] Flush memcached after mailbox item changes, fixes #1808
[Web] Fix duplicate IDs, fixes #1792
[Compose] Use SQL sockets
[PHP-FPM] Update APCu and Redis libs
[Dovecot] Encrypt maildir with global key pair in crypt-vol-1 (BACKUP!), also fixes #1791
[Web] Fix deletion of spam aliases
[Helper] Add "crypt" to backup script
[Helper] Override file for external SQL socket (not supported!)
[Compose] New images for Rspamd, PHP-FPM, SOGo, Dovecot, Docker API, Watchdog, ACME, Postfix
2018-09-29 22:01:23 +02:00
elif [ [ ${ file } = ~ crypt ] ] ; then
echo " [ ${ i } ] - Crypt data "
FILE_SELECTION[ ${ i } ] = "crypt"
( ( i++) )
2018-01-12 09:30:54 +02:00
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
2019-09-08 12:21:12 +02:00
input_sel = -1
2019-09-08 10:54:21 +02:00
while [ [ ${ input_sel } -lt 0 || ${ input_sel } -gt ${ i } ] ] ; do
2018-01-12 09:30:54 +02:00
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