Application: "Zimbra Collaboration Suite (ZCS) v.8+", "Bash".
Задача: миграция учётных записей и почтовых сообщений пользователей с сервиса на базе спарки "Postfix + Dovecot" в комбайн "Zimbra".
Разумеется, это не общая для всех случаев инструкция, так как вариантов реализации почтового сервиса на opensource-компонентах множество - здесь приводится пример решения, когда учётные записи пользователей "Dovecot" хранятся в текстовом файле с паролями в открытом виде (это самый распространённый подход для мелких офисов, на самом деле), а почтовые сообщения размещаются в maildir-структуре. А вообще на сайте разработчиков "Zimbra" на тему миграции есть, что почитать.
Последовательность действий при простейшей миграции примерно такова:
1. Запуск нового почтового сервиса "Zimbra".
2. Миграция учётных записей со старого сервера на новый.
3. Перевод на новый сервер DNS-записи MX.
4. Отключение SMTP, POP3 и IMAP на старом сервере.
5. Миграция почтовых сообщений со старого сервера на новый.
2. Миграция учётных записей со старого сервера на новый.
3. Перевод на новый сервер DNS-записи MX.
4. Отключение SMTP, POP3 и IMAP на старом сервере.
5. Миграция почтовых сообщений со старого сервера на новый.
Пример установки и настройки почтового сервиса "Zimbra" приводится в отдельной публикации, второй пункт с миграцией мы сейчас пройдём, перевод MX-записи тривиален, остановку сервисов и миграцию сообщений рассмотрим далее здесь же.
Миграция учётных записей пользователей почтового сервиса.
В большинстве простейших самодельных почтовых серверов пароли пользователей хранятся в текстовом файле в открытом виде, и нам это сильно облегчит процедуру миграции.
Напишем bash-скрипт, элементарно разбирающий текстовый файл с пользователями "Dovecot" и, на основе добытых оттуда логинов и паролей, создающий в "Zimbra" учётные с привязкой к нужному доменному имени:
# vi /usr/local/etc/mail/migrate-dvcaccs2zmbr.sh
#!/bin/bash
# example: ./migrate-dvcaccs2zmbr.sh domain.ltd ./mail.pwd
#
# expected file contents:
# username0:{PLAIN}Password0
# username1:{PLAIN}Password1
# usernameX:{PLAIN}PasswordX
# Используемые ресурсы
ZMPROV="/opt/zimbra/bin/zmprov"
# Принимаем аргументы запроса
DOMAIN="${1}"
FACCS="${2}"
# Процедуры с сервисам "Zimbra" очень желательно производить от имени выделенного для них пользователя
[ "`whoami`" != "zimbra" ] && { echo "It works only in the context of the user \"zimbra\"."; exit 1; }
# Проверяем наличие необходимых входящих переменных
[ -z "${DOMAIN}" -o -z "${FACCS}" ] && { echo "Usage: $(basename $0) domain.ltd ./mail.pwd"; exit 1; }
[ ! -s "${FACCS}" ] && { echo "File \"${FACCS}\" missing or unavailable."; exit 1; }
# Проверяем наличие указанного домена в целевой системе
$ZMPROV getDomain ${DOMAIN} > /dev/null 2>&1
[ "$?" -ne "0" ] && { echo "Domain name \"${DOMAIN}\" is not served."; exit 1; }
# Перебираем построчно текстовый файл с логинами и паролями
while read LINE ; do
# Вычленяем логин и пароль из текстовой строки
UNAME=`echo "${LINE}" | awk -F ':' '{print $1}'`
PASSWORD=`echo "${LINE}" | awk -F ':' '{print $2}' | awk -F '{PLAIN}' '{print $2}'`
# При наличии логина и пароля переходим к работе с аккаунтами
if [ ! -z "${UNAME}" -a ! -z "${PASSWORD}" ] ; then
# Проверяем наличие почтового аккаунта в целевой системе
$ZMPROV getMailboxInfo ${UNAME}@${DOMAIN} > /dev/null 2>&1
if [ "$?" -eq "0" ] ; then
# Уведомляем о наличии аккаунта
echo "Exist: Почтовый аккаунт \"${UNAME}@${DOMAIN}\" уже имеется в целевой системе."
else
# Пробуем создать новый почтовый аккаунт
$ZMPROV ca ${UNAME}@${DOMAIN} ${PASSWORD} > /dev/null 2>&1
if [ "$?" -eq "0" ] ; then
echo "Success: Почтовый аккаунт \"${UNAME}@${DOMAIN}\" успешно создан в целевой системе."
else
echo "Fail: Сбой при создании почтового аккаунта \"${UNAME}@${DOMAIN}\"."
echo "Abort: Процедура миграции прерывается."
exit 1
fi
fi
fi
done < "${FACCS}"
exit ${?}
# example: ./migrate-dvcaccs2zmbr.sh domain.ltd ./mail.pwd
#
# expected file contents:
# username0:{PLAIN}Password0
# username1:{PLAIN}Password1
# usernameX:{PLAIN}PasswordX
# Используемые ресурсы
ZMPROV="/opt/zimbra/bin/zmprov"
# Принимаем аргументы запроса
DOMAIN="${1}"
FACCS="${2}"
# Процедуры с сервисам "Zimbra" очень желательно производить от имени выделенного для них пользователя
[ "`whoami`" != "zimbra" ] && { echo "It works only in the context of the user \"zimbra\"."; exit 1; }
# Проверяем наличие необходимых входящих переменных
[ -z "${DOMAIN}" -o -z "${FACCS}" ] && { echo "Usage: $(basename $0) domain.ltd ./mail.pwd"; exit 1; }
[ ! -s "${FACCS}" ] && { echo "File \"${FACCS}\" missing or unavailable."; exit 1; }
# Проверяем наличие указанного домена в целевой системе
$ZMPROV getDomain ${DOMAIN} > /dev/null 2>&1
[ "$?" -ne "0" ] && { echo "Domain name \"${DOMAIN}\" is not served."; exit 1; }
# Перебираем построчно текстовый файл с логинами и паролями
while read LINE ; do
# Вычленяем логин и пароль из текстовой строки
UNAME=`echo "${LINE}" | awk -F ':' '{print $1}'`
PASSWORD=`echo "${LINE}" | awk -F ':' '{print $2}' | awk -F '{PLAIN}' '{print $2}'`
# При наличии логина и пароля переходим к работе с аккаунтами
if [ ! -z "${UNAME}" -a ! -z "${PASSWORD}" ] ; then
# Проверяем наличие почтового аккаунта в целевой системе
$ZMPROV getMailboxInfo ${UNAME}@${DOMAIN} > /dev/null 2>&1
if [ "$?" -eq "0" ] ; then
# Уведомляем о наличии аккаунта
echo "Exist: Почтовый аккаунт \"${UNAME}@${DOMAIN}\" уже имеется в целевой системе."
else
# Пробуем создать новый почтовый аккаунт
$ZMPROV ca ${UNAME}@${DOMAIN} ${PASSWORD} > /dev/null 2>&1
if [ "$?" -eq "0" ] ; then
echo "Success: Почтовый аккаунт \"${UNAME}@${DOMAIN}\" успешно создан в целевой системе."
else
echo "Fail: Сбой при создании почтового аккаунта \"${UNAME}@${DOMAIN}\"."
echo "Abort: Процедура миграции прерывается."
exit 1
fi
fi
fi
done < "${FACCS}"
exit ${?}
# su - zimbra
$ /usr/local/etc/mail/migrate-dvcaccs2zmbr.sh example.net ./mail.pwd
$ /usr/local/etc/mail/migrate-dvcaccs2zmbr.sh example.net ./mail.pwd
Не могу не отметить, что используемые CLI-утилиты "Zimbra" работают чрезвычайно медленно. На проверку наличия и создание каждой учётной записи требуется не менее двух секунд. Возможно этот процесс ускоряется, но после установки "по умолчанию" программный комплекс "Zimbra" очень неповоротливый.
Перевод пользователей на новый почтовый сервис.
Сразу после того, как на новом сервере созданы необходимые учётные записи и он стал готов принимать SMTP-сообщения к адресам таковых, а также обслуживать пользователей по протоколам POP3 и IMAP, есть смысл сразу перенаправить MX-запись на него. Уж не буду здесь о том, как это сделать - ибо просто.
Убедившись, что новая "Zimbra" обслуживает почтовые транзакции, отключаем сервисы на старом сервере:
# systemctl stop postfix
# systemctl stop dovecot
# systemctl disable postfix
# systemctl disable dovecot
# systemctl stop dovecot
# systemctl disable postfix
# systemctl disable dovecot
Можно перестраховаться и наладить SMTP-пересылку со старого на новый сервер для тех, кто будет упорно пытаться обратится на старый сервер, игнорируя изменения MX-записи.
Копируем всю иерархию почтовых ящиков со старого на новый сервер, чтобы из неё выбирать файлы в процессе миграции:
# rsync -av -e ssh --exclude='/mnt/mail/badboy' /mnt/mail user@mail.example.net:/mnt/.backup/mail
Как вариант, если сетевое соединение высокоскоростное, можно обойтись без предварительного копирования, просто смонтировав на время почтовую директорию старого сервера на новом:
# sshfs user@old.example.net:/mnt/mail /mnt/.backup/mail -o allow_other,uid=$(id -u zimbra)
Миграция сообщений почтовых ящиков из maildir-структуры в "Zimbra".
У "Zimbra" свой подход к хранению почтовых сообщений - несовместимый с традиционными "maildir" и "mailbox" - при переводе сервиса с другого почтового сервера необходимо произвести миграцию данных.
Напишем bash-скрипт переноса содержимого почтовых ящиков из иерархии "maildir", типичной для связки серверов "Postfix + Dovecot", в "Zimbra":
# vi /usr/local/etc/mail/migrate-mailbox2zmbr.sh
#!/bin/bash
# example: ./migrate-maildir2zmbr.sh domain.ltd ./maildir
#
# expected file structure:
# ./maildir/username/{cur|new|tmp}
# ./maildir/username/subfolder1/{cur|new|tmp}
# ./maildir/username/subfolder2/subfolder3{cur|new|tmp}
# Используемые ресурсы
ZMPROV="/opt/zimbra/bin/zmprov"
ZMMBOX="/opt/zimbra/bin/zmmailbox"
LOG="/opt/zimbra/log/migrate-maildir2zmbr.log"
# Принимаем аргументы запроса
DOMAIN="${1}"
FDIR="${2}"
# Процедуры с сервисам "Zimbra" очень желательно производить от имени выделенного для них пользователя
[ "`whoami`" != "zimbra" ] && { echo "It works only in the context of the user \"zimbra\"."; exit 1; }
# Проверяем наличие необходимых входящих переменных
[ -z "${DOMAIN}" -o -z "${FDIR}" ] && { echo "Usage: $(basename $0) domain.ltd ./maildir"; exit 1; }
[ ! -d "${FDIR}" ] && { echo "Directory \"${FDIR}\" missing or unavailable."; exit 1; }
# Проверяем наличие указанного домена в целевой системе
$ZMPROV getDomain ${DOMAIN} > /dev/null 2>&1
[ "$?" -ne "0" ] && { echo "Domain name \"${DOMAIN}\" is not served."; exit 1; }
# Переходим в корень импортируемой иерархии и перебираем все ключевые ветки директорий
cd "${FDIR}" && FDIR=$(pwd)
DIRS=( $(find . -type d -name 'cur' | sort) )
for DIR in ${DIRS[@]} ; do
# Вычленяем имя пользователя и директории с сообщениями
UNAME=`echo ${DIR} | cut -d'/' -f2`
DIRNAME=`echo ${DIR} | awk -F "/${UNAME}/" '{print $2}'`
# Проверяем наличие почтового аккаунта в целевой системе
$ZMPROV getMailboxInfo ${UNAME}@${DOMAIN} > /dev/null 2>&1
if [ "$?" -eq "0" ] ; then
# Если имя директории "cur" - значит мы в почтовой папке верхнего уровня "Inbox"
if [ "${DIRNAME}" == "cur" ] ; then
# Импортируем почтовые сообщения в корневую папку "Inbox"
echo addMessage --noValidation /Inbox \'${FDIR}/${UNAME}/cur\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} >> "${LOG}"
[ -d "${FDIR}/${UNAME}/new" ] && echo addMessage --noValidation --flags \'u\' /Inbox \'${FDIR}/${UNAME}/new\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} >> "${LOG}"
else
# Если мы не в директории верхнего уровня, значит в поддиректории.
# Вычленяем имя почтовой подпапки и предварительно создаём её
# (подпапки могут быть вложенными, главное создавать их от корня последовательно)
DIRNAME=$(echo ${DIRNAME} | sed -e 's/\/cur$//')
echo createFolder \'/${DIRNAME}\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} > /dev/null 2>&1
# Импортируем почтовые сообщения в выделенную подпапку
echo addMessage --noValidation \'/${DIRNAME}\' \'${FDIR}/${UNAME}/${DIRNAME}/cur\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} >> "${LOG}"
[ -d "${FDIR}/${UNAME}/${DIRNAME}/new" ] && echo addMessage --noValidation --flags \'u\' \'/${DIRNAME}\' \'${FDIR}/${UNAME}/${DIRNAME}/new\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} >> "${LOG}"
fi
else
echo "Absent: Почтовый ящик \"${UNAME}@${DOMAIN}\" отсутствует в целевой системе." | tee -a "${LOG}"
echo "Not Done: Сообщения иерархии \"./${UNAME}/\" не будут импортироваться." | tee -a "${LOG}"
fi
done
exit ${?}
# example: ./migrate-maildir2zmbr.sh domain.ltd ./maildir
#
# expected file structure:
# ./maildir/username/{cur|new|tmp}
# ./maildir/username/subfolder1/{cur|new|tmp}
# ./maildir/username/subfolder2/subfolder3{cur|new|tmp}
# Используемые ресурсы
ZMPROV="/opt/zimbra/bin/zmprov"
ZMMBOX="/opt/zimbra/bin/zmmailbox"
LOG="/opt/zimbra/log/migrate-maildir2zmbr.log"
# Принимаем аргументы запроса
DOMAIN="${1}"
FDIR="${2}"
# Процедуры с сервисам "Zimbra" очень желательно производить от имени выделенного для них пользователя
[ "`whoami`" != "zimbra" ] && { echo "It works only in the context of the user \"zimbra\"."; exit 1; }
# Проверяем наличие необходимых входящих переменных
[ -z "${DOMAIN}" -o -z "${FDIR}" ] && { echo "Usage: $(basename $0) domain.ltd ./maildir"; exit 1; }
[ ! -d "${FDIR}" ] && { echo "Directory \"${FDIR}\" missing or unavailable."; exit 1; }
# Проверяем наличие указанного домена в целевой системе
$ZMPROV getDomain ${DOMAIN} > /dev/null 2>&1
[ "$?" -ne "0" ] && { echo "Domain name \"${DOMAIN}\" is not served."; exit 1; }
# Переходим в корень импортируемой иерархии и перебираем все ключевые ветки директорий
cd "${FDIR}" && FDIR=$(pwd)
DIRS=( $(find . -type d -name 'cur' | sort) )
for DIR in ${DIRS[@]} ; do
# Вычленяем имя пользователя и директории с сообщениями
UNAME=`echo ${DIR} | cut -d'/' -f2`
DIRNAME=`echo ${DIR} | awk -F "/${UNAME}/" '{print $2}'`
# Проверяем наличие почтового аккаунта в целевой системе
$ZMPROV getMailboxInfo ${UNAME}@${DOMAIN} > /dev/null 2>&1
if [ "$?" -eq "0" ] ; then
# Если имя директории "cur" - значит мы в почтовой папке верхнего уровня "Inbox"
if [ "${DIRNAME}" == "cur" ] ; then
# Импортируем почтовые сообщения в корневую папку "Inbox"
echo addMessage --noValidation /Inbox \'${FDIR}/${UNAME}/cur\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} >> "${LOG}"
[ -d "${FDIR}/${UNAME}/new" ] && echo addMessage --noValidation --flags \'u\' /Inbox \'${FDIR}/${UNAME}/new\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} >> "${LOG}"
else
# Если мы не в директории верхнего уровня, значит в поддиректории.
# Вычленяем имя почтовой подпапки и предварительно создаём её
# (подпапки могут быть вложенными, главное создавать их от корня последовательно)
DIRNAME=$(echo ${DIRNAME} | sed -e 's/\/cur$//')
echo createFolder \'/${DIRNAME}\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} > /dev/null 2>&1
# Импортируем почтовые сообщения в выделенную подпапку
echo addMessage --noValidation \'/${DIRNAME}\' \'${FDIR}/${UNAME}/${DIRNAME}/cur\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} >> "${LOG}"
[ -d "${FDIR}/${UNAME}/${DIRNAME}/new" ] && echo addMessage --noValidation --flags \'u\' \'/${DIRNAME}\' \'${FDIR}/${UNAME}/${DIRNAME}/new\' | $ZMMBOX -z -m ${UNAME}@${DOMAIN} >> "${LOG}"
fi
else
echo "Absent: Почтовый ящик \"${UNAME}@${DOMAIN}\" отсутствует в целевой системе." | tee -a "${LOG}"
echo "Not Done: Сообщения иерархии \"./${UNAME}/\" не будут импортироваться." | tee -a "${LOG}"
fi
done
exit ${?}
# su - zimbra
$ /usr/local/etc/mail/migrate-mailbox2zmbr.sh example.net ./maildir
$ /usr/local/etc/mail/migrate-mailbox2zmbr.sh example.net ./maildir
Если почтовых сообщений большое количество, то процесс затянется и через журнал событий за ним можно будет понаблюдать:
# tail -f /opt/zimbra/log/migrate-maildir2zmbr.log