backup_and_restore.sh 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. #!/usr/bin/env bash
  2. DEBIAN_DOCKER_IMAGE="mailcow/backup:latest"
  3. if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then
  4. BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}"
  5. fi
  6. if [[ ! ${1} =~ (backup|restore) ]]; then
  7. echo "First parameter needs to be 'backup' or 'restore'"
  8. exit 1
  9. fi
  10. if [[ ${1} == "backup" && ! ${2} =~ (crypt|vmail|redis|rspamd|postfix|mysql|all|--delete-days) ]]; then
  11. echo "Second parameter needs to be 'vmail', 'crypt', 'redis', 'rspamd', 'postfix', 'mysql', 'all' or '--delete-days'"
  12. exit 1
  13. fi
  14. if [[ -z ${BACKUP_LOCATION} ]]; then
  15. while [[ -z ${BACKUP_LOCATION} ]]; do
  16. read -ep "Backup location (absolute path, starting with /): " BACKUP_LOCATION
  17. done
  18. fi
  19. if [[ ! ${BACKUP_LOCATION} =~ ^/ ]]; then
  20. echo "Backup directory needs to be given as absolute path (starting with /)."
  21. exit 1
  22. fi
  23. if [[ -f ${BACKUP_LOCATION} ]]; then
  24. echo "${BACKUP_LOCATION} is a file!"
  25. exit 1
  26. fi
  27. if [[ ! -d ${BACKUP_LOCATION} ]]; then
  28. echo "${BACKUP_LOCATION} is not a directory"
  29. read -p "Create it now? [y|N] " CREATE_BACKUP_LOCATION
  30. if [[ ! ${CREATE_BACKUP_LOCATION,,} =~ ^(yes|y)$ ]]; then
  31. exit 1
  32. else
  33. mkdir -p ${BACKUP_LOCATION}
  34. chmod 755 ${BACKUP_LOCATION}
  35. fi
  36. else
  37. if [[ ${1} == "backup" ]] && [[ -z $(echo $(stat -Lc %a ${BACKUP_LOCATION}) | grep -oE '[0-9][0-9][5-7]') ]]; then
  38. echo "${BACKUP_LOCATION} is not write-able for others, that's required for a backup."
  39. exit 1
  40. fi
  41. fi
  42. BACKUP_LOCATION=$(echo ${BACKUP_LOCATION} | sed 's#/$##')
  43. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
  44. COMPOSE_FILE=${SCRIPT_DIR}/../docker-compose.yml
  45. ENV_FILE=${SCRIPT_DIR}/../.env
  46. THREADS=$(echo ${THREADS:-1})
  47. ARCH=$(uname -m)
  48. if ! [[ "${THREADS}" =~ ^[1-9][0-9]?$ ]] ; then
  49. echo "Thread input is not a number!"
  50. exit 1
  51. elif [[ "${THREADS}" =~ ^[1-9][0-9]?$ ]] ; then
  52. echo "Using ${THREADS} Thread(s) for this run."
  53. echo "Notice: You can set the Thread count with the THREADS Variable before you run this script."
  54. fi
  55. if [ ! -f ${COMPOSE_FILE} ]; then
  56. echo "Compose file not found"
  57. exit 1
  58. fi
  59. if [ ! -f ${ENV_FILE} ]; then
  60. echo "Environment file not found"
  61. exit 1
  62. fi
  63. echo "Using ${BACKUP_LOCATION} as backup/restore location."
  64. echo
  65. source ${SCRIPT_DIR}/../mailcow.conf
  66. if [[ -z ${COMPOSE_PROJECT_NAME} ]]; then
  67. echo "Could not determine compose project name"
  68. exit 1
  69. else
  70. echo "Found project name ${COMPOSE_PROJECT_NAME}"
  71. CMPS_PRJ=$(echo ${COMPOSE_PROJECT_NAME} | tr -cd "[0-9A-Za-z-_]")
  72. fi
  73. if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then
  74. >&2 echo -e "\e[31mBusyBox grep detected on local system, please install GNU grep\e[0m"
  75. exit 1
  76. fi
  77. function backup() {
  78. DATE=$(date +"%Y-%m-%d-%H-%M-%S")
  79. mkdir -p "${BACKUP_LOCATION}/mailcow-${DATE}"
  80. chmod 755 "${BACKUP_LOCATION}/mailcow-${DATE}"
  81. cp "${SCRIPT_DIR}/../mailcow.conf" "${BACKUP_LOCATION}/mailcow-${DATE}"
  82. touch "${BACKUP_LOCATION}/mailcow-${DATE}/.$ARCH"
  83. for bin in docker; do
  84. if [[ -z $(which ${bin}) ]]; then
  85. >&2 echo -e "\e[31mCannot find ${bin} in local PATH, exiting...\e[0m"
  86. exit 1
  87. fi
  88. done
  89. while (( "$#" )); do
  90. case "$1" in
  91. vmail|all)
  92. docker run --name mailcow-backup --rm \
  93. -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
  94. -v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:ro,z \
  95. ${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_vmail.tar.gz /vmail
  96. ;;&
  97. crypt|all)
  98. docker run --name mailcow-backup --rm \
  99. -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
  100. -v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:ro,z \
  101. ${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_crypt.tar.gz /crypt
  102. ;;&
  103. redis|all)
  104. docker exec $(docker ps -qf name=redis-mailcow) redis-cli save
  105. docker run --name mailcow-backup --rm \
  106. -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
  107. -v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:ro,z \
  108. ${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_redis.tar.gz /redis
  109. ;;&
  110. rspamd|all)
  111. docker run --name mailcow-backup --rm \
  112. -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
  113. -v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:ro,z \
  114. ${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_rspamd.tar.gz /rspamd
  115. ;;&
  116. postfix|all)
  117. docker run --name mailcow-backup --rm \
  118. -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
  119. -v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:ro,z \
  120. ${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_postfix.tar.gz /postfix
  121. ;;&
  122. mysql|all)
  123. SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${COMPOSE_FILE})
  124. if [[ -z "${SQLIMAGE}" ]]; then
  125. echo "Could not determine SQL image version, skipping backup..."
  126. shift
  127. continue
  128. else
  129. echo "Using SQL image ${SQLIMAGE}, starting..."
  130. docker run --name mailcow-backup --rm \
  131. --network $(docker network ls -qf name=^${CMPS_PRJ}_mailcow-network$) \
  132. -v $(docker volume ls -qf name=^${CMPS_PRJ}_mysql-vol-1$):/var/lib/mysql/:ro,z \
  133. -t --entrypoint= \
  134. --sysctl net.ipv6.conf.all.disable_ipv6=1 \
  135. -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
  136. ${SQLIMAGE} /bin/sh -c "mariabackup --host mysql --user root --password ${DBROOT} --backup --rsync --target-dir=/backup_mariadb ; \
  137. mariabackup --prepare --target-dir=/backup_mariadb ; \
  138. chown -R 999:999 /backup_mariadb ; \
  139. /bin/tar --warning='no-file-ignored' --use-compress-program='gzip --rsyncable' -Pcvpf /backup/backup_mariadb.tar.gz /backup_mariadb ;"
  140. fi
  141. ;;&
  142. --delete-days)
  143. shift
  144. if [[ "${1}" =~ ^[0-9]+$ ]]; then
  145. find ${BACKUP_LOCATION}/mailcow-* -maxdepth 0 -mmin +$((${1}*60*24)) -exec rm -rvf {} \;
  146. else
  147. echo "Parameter of --delete-days is not a number."
  148. fi
  149. ;;
  150. esac
  151. shift
  152. done
  153. }
  154. function restore() {
  155. for bin in docker; do
  156. if [[ -z $(which ${bin}) ]]; then
  157. >&2 echo -e "\e[31mCannot find ${bin} in local PATH, exiting...\e[0m"
  158. exit 1
  159. fi
  160. done
  161. if [ "${DOCKER_COMPOSE_VERSION}" == "native" ]; then
  162. COMPOSE_COMMAND="docker compose"
  163. elif [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then
  164. COMPOSE_COMMAND="docker-compose"
  165. else
  166. echo -e "\e[31mCan not read DOCKER_COMPOSE_VERSION variable from mailcow.conf! Is your mailcow up to date? Exiting...\e[0m"
  167. exit 1
  168. fi
  169. echo
  170. echo "Stopping watchdog-mailcow..."
  171. docker stop $(docker ps -qf name=watchdog-mailcow)
  172. echo
  173. RESTORE_LOCATION="${1}"
  174. shift
  175. while (( "$#" )); do
  176. case "$1" in
  177. vmail)
  178. docker stop $(docker ps -qf name=dovecot-mailcow)
  179. docker run -i --name mailcow-backup --rm \
  180. -v ${RESTORE_LOCATION}:/backup:z \
  181. -v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:z \
  182. ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_vmail.tar.gz
  183. docker start $(docker ps -aqf name=dovecot-mailcow)
  184. echo
  185. 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:"
  186. echo
  187. echo "docker exec $(docker ps -qf name=dovecot-mailcow) doveadm force-resync -A '*'"
  188. echo
  189. read -p "Force a resync now? [y|N] " FORCE_RESYNC
  190. if [[ ${FORCE_RESYNC,,} =~ ^(yes|y)$ ]]; then
  191. docker exec $(docker ps -qf name=dovecot-mailcow) doveadm force-resync -A '*'
  192. else
  193. echo "OK, skipped."
  194. fi
  195. ;;
  196. redis)
  197. docker stop $(docker ps -qf name=redis-mailcow)
  198. docker run -i --name mailcow-backup --rm \
  199. -v ${RESTORE_LOCATION}:/backup:z \
  200. -v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:z \
  201. ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_redis.tar.gz
  202. docker start $(docker ps -aqf name=redis-mailcow)
  203. ;;
  204. crypt)
  205. docker stop $(docker ps -qf name=dovecot-mailcow)
  206. docker run -i --name mailcow-backup --rm \
  207. -v ${RESTORE_LOCATION}:/backup:z \
  208. -v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:z \
  209. ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_crypt.tar.gz
  210. docker start $(docker ps -aqf name=dovecot-mailcow)
  211. ;;
  212. rspamd)
  213. if [[ $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then
  214. echo -e "\e[33mCould not find a architecture signature of the loaded backup... Maybe the backup was done before the multiarch update?"
  215. sleep 2
  216. echo -e "Continuing anyhow. If rspamd is crashing opon boot try remove the rspamd volume with docker volume rm ${CMPS_PRJ}_rspamd-vol-1 after you've stopped the stack.\e[0m"
  217. sleep 2
  218. docker stop $(docker ps -qf name=rspamd-mailcow)
  219. docker run -i --name mailcow-backup --rm \
  220. -v ${RESTORE_LOCATION}:/backup:z \
  221. -v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
  222. ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz
  223. docker start $(docker ps -aqf name=rspamd-mailcow)
  224. elif [[ $ARCH != $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') ]]; then
  225. echo -e "\e[31mThe Architecture of the backed up mailcow OS is different then your restoring mailcow OS..."
  226. sleep 2
  227. echo -e "Skipping rspamd due to compatibility issues!\e[0m"
  228. else
  229. docker stop $(docker ps -qf name=rspamd-mailcow)
  230. docker run -i --name mailcow-backup --rm \
  231. -v ${RESTORE_LOCATION}:/backup:z \
  232. -v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
  233. ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz
  234. docker start $(docker ps -aqf name=rspamd-mailcow)
  235. fi
  236. ;;
  237. postfix)
  238. docker stop $(docker ps -qf name=postfix-mailcow)
  239. docker run -i --name mailcow-backup --rm \
  240. -v ${RESTORE_LOCATION}:/backup:z \
  241. -v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:z \
  242. ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_postfix.tar.gz
  243. docker start $(docker ps -aqf name=postfix-mailcow)
  244. ;;
  245. mysql|mariadb)
  246. SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${COMPOSE_FILE})
  247. if [[ -z "${SQLIMAGE}" ]]; then
  248. echo "Could not determine SQL image version, skipping restore..."
  249. shift
  250. continue
  251. elif [ ! -f "${RESTORE_LOCATION}/mailcow.conf" ]; then
  252. echo "Could not find the corresponding mailcow.conf in ${RESTORE_LOCATION}, skipping restore."
  253. echo "If you lost that file, copy the last working mailcow.conf file to ${RESTORE_LOCATION} and restart the restore process."
  254. shift
  255. continue
  256. else
  257. read -p "mailcow will be stopped and the currently active mailcow.conf will be modified to use the DB parameters found in ${RESTORE_LOCATION}/mailcow.conf - do you want to proceed? [Y|n] " MYSQL_STOP_MAILCOW
  258. if [[ ${MYSQL_STOP_MAILCOW,,} =~ ^(no|n|N)$ ]]; then
  259. echo "OK, skipped."
  260. shift
  261. continue
  262. else
  263. echo "Stopping mailcow..."
  264. ${COMPOSE_COMMAND} -f ${COMPOSE_FILE} --env-file ${ENV_FILE} down
  265. fi
  266. #docker stop $(docker ps -qf name=mysql-mailcow)
  267. if [[ -d "${RESTORE_LOCATION}/mysql" ]]; then
  268. docker run --name mailcow-backup --rm \
  269. -v $(docker volume ls -qf name=^${CMPS_PRJ}_mysql-vol-1$):/var/lib/mysql/:rw,z \
  270. --entrypoint= \
  271. -v ${RESTORE_LOCATION}/mysql:/backup:z \
  272. ${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/"
  273. elif [[ -f "${RESTORE_LOCATION}/backup_mysql.gz" ]]; then
  274. docker run \
  275. -i --name mailcow-backup --rm \
  276. -v $(docker volume ls -qf name=^${CMPS_PRJ}_mysql-vol-1$):/var/lib/mysql/:z \
  277. --entrypoint= \
  278. -u mysql \
  279. -v ${RESTORE_LOCATION}:/backup:z \
  280. ${SQLIMAGE} /bin/sh -c "mysqld --skip-grant-tables & \
  281. until mysqladmin ping; do sleep 3; done && \
  282. echo Restoring... && \
  283. gunzip < backup/backup_mysql.gz | mysql -uroot && \
  284. mysql -uroot -e SHUTDOWN;"
  285. elif [[ -f "${RESTORE_LOCATION}/backup_mariadb.tar.gz" ]]; then
  286. docker run --name mailcow-backup --rm \
  287. -v $(docker volume ls -qf name=^${CMPS_PRJ}_mysql-vol-1$):/backup_mariadb/:rw,z \
  288. --entrypoint= \
  289. -v ${RESTORE_LOCATION}:/backup:z \
  290. ${SQLIMAGE} /bin/bash -c "shopt -s dotglob ; \
  291. /bin/rm -rf /backup_mariadb/* ; \
  292. /bin/tar -Pxvzf /backup/backup_mariadb.tar.gz"
  293. fi
  294. echo "Modifying mailcow.conf..."
  295. source ${RESTORE_LOCATION}/mailcow.conf
  296. sed -i --follow-symlinks "/DBNAME/c\DBNAME=${DBNAME}" ${SCRIPT_DIR}/../mailcow.conf
  297. sed -i --follow-symlinks "/DBUSER/c\DBUSER=${DBUSER}" ${SCRIPT_DIR}/../mailcow.conf
  298. sed -i --follow-symlinks "/DBPASS/c\DBPASS=${DBPASS}" ${SCRIPT_DIR}/../mailcow.conf
  299. sed -i --follow-symlinks "/DBROOT/c\DBROOT=${DBROOT}" ${SCRIPT_DIR}/../mailcow.conf
  300. source ${SCRIPT_DIR}/../mailcow.conf
  301. echo "Starting mailcow..."
  302. ${COMPOSE_COMMAND} -f ${COMPOSE_FILE} --env-file ${ENV_FILE} up -d
  303. #docker start $(docker ps -aqf name=mysql-mailcow)
  304. fi
  305. ;;
  306. esac
  307. shift
  308. done
  309. echo
  310. echo "Starting watchdog-mailcow..."
  311. docker start $(docker ps -aqf name=watchdog-mailcow)
  312. }
  313. if [[ ${1} == "backup" ]]; then
  314. backup ${@,,}
  315. elif [[ ${1} == "restore" ]]; then
  316. i=1
  317. declare -A FOLDER_SELECTION
  318. if [[ $(find ${BACKUP_LOCATION}/mailcow-* -maxdepth 1 -type d 2> /dev/null| wc -l) -lt 1 ]]; then
  319. echo "Selected backup location has no subfolders"
  320. exit 1
  321. fi
  322. for folder in $(ls -d ${BACKUP_LOCATION}/mailcow-*/); do
  323. echo "[ ${i} ] - ${folder}"
  324. FOLDER_SELECTION[${i}]="${folder}"
  325. ((i++))
  326. done
  327. echo
  328. input_sel=0
  329. while [[ ${input_sel} -lt 1 || ${input_sel} -gt ${i} ]]; do
  330. read -p "Select a restore point: " input_sel
  331. done
  332. i=1
  333. echo
  334. declare -A FILE_SELECTION
  335. RESTORE_POINT="${FOLDER_SELECTION[${input_sel}]}"
  336. if [[ -z $(find "${FOLDER_SELECTION[${input_sel}]}" -maxdepth 1 \( -type d -o -type f \) -regex ".*\(redis\|rspamd\|mariadb\|mysql\|crypt\|vmail\|postfix\).*") ]]; then
  337. echo "No datasets found"
  338. exit 1
  339. fi
  340. echo "[ 0 ] - all"
  341. # find all files in folder with *.gz extension, print their base names, remove backup_, remove .tar (if present), remove .gz
  342. FILE_SELECTION[0]=$(find "${FOLDER_SELECTION[${input_sel}]}" -maxdepth 1 \( -type d -o -type f \) \( -name '*.gz' -o -name 'mysql' \) -printf '%f\n' | sed 's/backup_*//' | sed 's/\.[^.]*$//' | sed 's/\.[^.]*$//')
  343. for file in $(ls -f "${FOLDER_SELECTION[${input_sel}]}"); do
  344. if [[ ${file} =~ vmail ]]; then
  345. echo "[ ${i} ] - Mail directory (/var/vmail)"
  346. FILE_SELECTION[${i}]="vmail"
  347. ((i++))
  348. elif [[ ${file} =~ crypt ]]; then
  349. echo "[ ${i} ] - Crypt data"
  350. FILE_SELECTION[${i}]="crypt"
  351. ((i++))
  352. elif [[ ${file} =~ redis ]]; then
  353. echo "[ ${i} ] - Redis DB"
  354. FILE_SELECTION[${i}]="redis"
  355. ((i++))
  356. elif [[ ${file} =~ rspamd ]]; then
  357. if [[ $(find "${FOLDER_SELECTION[${input_sel}]}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then
  358. echo "[ ${i} ] - Rspamd data (unkown Arch detected, restore with caution!)"
  359. FILE_SELECTION[${i}]="rspamd"
  360. ((i++))
  361. elif [[ $ARCH != $(find "${FOLDER_SELECTION[${input_sel}]}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') ]]; then
  362. echo -e "\e[31m[ NaN ] - Rspamd data (incompatible Arch, cannot restore it)\e[0m"
  363. else
  364. echo "[ ${i} ] - Rspamd data"
  365. FILE_SELECTION[${i}]="rspamd"
  366. ((i++))
  367. fi
  368. elif [[ ${file} =~ postfix ]]; then
  369. echo "[ ${i} ] - Postfix data"
  370. FILE_SELECTION[${i}]="postfix"
  371. ((i++))
  372. elif [[ ${file} =~ mysql ]] || [[ ${file} =~ mariadb ]]; then
  373. echo "[ ${i} ] - SQL DB"
  374. FILE_SELECTION[${i}]="mysql"
  375. ((i++))
  376. fi
  377. done
  378. echo
  379. input_sel=-1
  380. while [[ ${input_sel} -lt 0 || ${input_sel} -gt ${i} ]]; do
  381. read -p "Select a dataset to restore: " input_sel
  382. done
  383. echo "Restoring ${FILE_SELECTION[${input_sel}]} from ${RESTORE_POINT}..."
  384. restore "${RESTORE_POINT}" ${FILE_SELECTION[${input_sel}]}
  385. fi