Аккуратный перенос учёток с zimbra на mailcow
Добавлено: 04 июн 2026, 12:49
Уважаемый читатель, данную статью не следует принимать за истину в последней инстанции. НО! Поскольку других подобных статей в интернете не нашлось, я считаю необходимым описать свои действия, как мне удалось перенести учетки с паролями с zimbra на mailcow.
Не почту! А именно учётки и пароли!
Я не буду описывать лишний раз процесс установки mailcow, он и так прекрасно описан на сайте mailcow.
Я начну описание с генерации файла с учетными записями zimbra.
Настоятельно рекомендуется перед тем как генерировать этот файл удалить все "закрытые" и "заблокированные" почтовые ящики из zimbra, чтобы потом не блокировать их заново в mailcow.
Переходим под учётную запись zimbra
С помощью zmprov сначала составим полный список учеток и выведем это в файл:
На выходе получаем файл с содержимым такого вида:
На этом этапе можно открыть файл и уже сейчас удалить выключенные или лишние ящики такие как например:
В mailcow они нам не понадобятся.
Теперь нужно составить из этих адресов csv файл формата:
Для чего создаём там же в /tmp/ любым редактором такой скрипт:
что тут происходит:
Пример содержимого файла users.csv:
Для работы с mailcow нам понадобится только этот последний файл, users.csv.
На запущенном сервере mailcow этот файл нужно положить в директорию с базой данных mailcow. Путь её такой:
Так же надо будет поправить права этого файла, поскольку он временный, можно не заморачиватсья и дать ему полные права:
Теперь в директорию /opt/mailcow-dockerized/data/web/
Скачайте adminer.
Теперь по адресу сервера cow можно будет открыть и adminer.
Например если адрес mailcow cow.domain.ru, то adminer будет доступен по ссылке cow.domain.ru/latest-mysql.php
Введите адрес сервера mysql: mysql
Логин: root
А пароль БД можно взять из файла mailcow.conf в строке DBROOT= Внимание!!!
Прежде чем выполнять дальнейшие действия, добавьте свой домен через админку mailcow.
Я точно не знаю в какой последовательности это нужно делать, но я это делал так.
Выбираем базу данных mailcow: Выбираем SQL запрос в левом верхнем углу: Теперь вводим такой запрос:
Теперь нужно добавить ещё несколько полей, которые не добавляются автоматически.
Если вы хотите выключить квоты для всех почтовых ящиков сразу, или выставить их в какое-то адекватное значение выполните запрос:
А так же нам нужно выставить поле attributes, по умолчанию оно пустое, но когда заводишь ящик через веб интерфейс оно принимает какое-то значение. Это значение по-умолчанию мы и выставим:
При составлении запроса, обратите пожалуйста внимание что это одна длинная строка, без переносов.
Далее нужно заполнить таблицу aliases. Я не совсем понимаю структуру этой таблицы, но когда заводишь новый ящик туда добавляется запись типа
Ещё раз повторяюсь я не знаю почему так, но это так. Так что нам нужно ещё раз заполнить эту таблицу. Можно сделать из файла users.csv
А можно и скопировать из mailboxes, это уже как кому нравится. Я сделал из users.csv:
Теперь нужно заполнить ещё пару таблиц. Одна из них это quota2. Я просто скопировал username из mailboxes в эту таблицу:
На выходе имеем:
Далее я по аналогии c quota2 я заполнил таблицу user_acl:
На выходе имеем:
Запрос может показаться вычурным, возможно следует объяснить его синтаксис, если это интересно.
INSERT INTO user_acl (username) - мы делаем вставку только поля username в user_acl (остальные поля будут заполнены согласно структуре таблицы и имеют дефолтные значения.
SELECT m.username FROM mailbox m
это выборка только поля username из таблицы mailbox. m это сокращение для таблицы mailbox.
Запись можно было бы записать длиннее: SELECT mailbox.username FROM mailbox.
Далее LEFT JOIN будет искать соответствие строк в таблицах user_acl.username и mailbox.username
и инструкция INSERT INTO будет применятся только к тем значениям, которых ещё нет в таблице user_acl.
Можно значительно упростить запрос, если вы не заводили ни одного почтового ящика. Это будет выглядеть так:
INSERT INTO user_acl (username) SELECT username FROM mailbox;
Однако если вы уже успели завести хотя бы один ящик, этот запрос выдаст ошибку.
На этом работы в базой данных можно завершить.
Есть ещё одна бд, которая должна быть заполнена значениями, это _sogo_static_view, однако, она заполнилась у меня значениями автоматически. Возможно, чтобы там что-то появилось нужно открыть домен и нажать кнопку "сохранить" в админке mailcow, но это не точно.
Ещё раз повторюсь что данная статья не является инструкцией, согласованной с mailcow.
На форумах mailcow все говорят что перенести таким образом учётки не получится и надо делать весь перенос через api и в результате все пароли поменяются.
Но я вам только что расписал все шаги, по которым я прошёл чтобы полностью скопировать логины-пароли и имена с зимбры на корову, и у меня в такой конфигурации всё прекрасно работает.
Не забудьте удалить файлы latest-mysql.php и users.csv после переноса.
Не почту! А именно учётки и пароли!
Я не буду описывать лишний раз процесс установки mailcow, он и так прекрасно описан на сайте mailcow.
Я начну описание с генерации файла с учетными записями zimbra.
Настоятельно рекомендуется перед тем как генерировать этот файл удалить все "закрытые" и "заблокированные" почтовые ящики из zimbra, чтобы потом не блокировать их заново в mailcow.
Переходим под учётную запись zimbra
Код: Выделить всё
sudo su - zimbraКод: Выделить всё
zmprov -l gaa > /tpm/emails.txtКод: Выделить всё
user1@domain.ru
user2@domain.ru
user3@domain.ruКод: Выделить всё
spam.xxjngi@domain.ru
ham.i_psjg@domain.ru
virus-quarantine.puosbq2r@domain.ru
Теперь нужно составить из этих адресов csv файл формата:
Код: Выделить всё
"email","password","name"Код: Выделить всё
#!/usr/bin/env bash
while read -r email; do
info=$(zmprov -l ga "$email" userPassword displayName)
password=$(awk -F': ' '/^userPassword:/ {print $2}' <<< "$info")
name=$(awk -F': ' '/^displayName:/ {print $2}' <<< "$info")
printf '"%s","%s","%s"\n' "$email" "$password" "$name"
done < /tmp/emails.txt >> /tmp/users.csv
- в последней строке мы передаём конструкции while значения из файла /tmp/emails.txt
- while читает этот файл по одной строке и делает запрос zmprov -l ga "$email" userPassword displayName
- ответ на этот запрос будет иметь трёхстрочный формат, где 1 строка это коментарий, вторая строка будет иметь вид userPassword: {SSHA}<hash> а третья строка будет иметь вид displayName: Иванов Иван. Все три строки попадают в переменную info.
- При помощи awk мы присваиваем переменной password значение {SSHA}<hash>
- При помощи awk мы присваиваем переменной name значение "Иванов Иван"
- printf выводит на стандартный вывод строку из трёх строковых переменных email, password и name.
- Возвращаемся к последней строке скрипта, >> /tpm/users.csv вывод printf в итоге попадает в users.csv.
Пример содержимого файла users.csv:
Код: Выделить всё
"user1@domain.ru","{SSHA512}tzJhcu...","Иванов Иван"
"user2@domain.ru","{SSHA512}cWJxP...","Петров Петр"
"user3@domain.ru","{SSHA512}qoqzY...","Сидоров Сидр"
На запущенном сервере mailcow этот файл нужно положить в директорию с базой данных mailcow. Путь её такой:
Код: Выделить всё
sudo cp users.scv /var/lib/docker/volumes/mailcowdockerized_mysql-vol-1/_data/mailcow/Код: Выделить всё
sudo chmod 777 /var/lib/docker/volumes/mailcowdockerized_mysql-vol-1/_data/mailcow/users.csvСкачайте adminer.
Код: Выделить всё
cd /opt/mailcow-dockerized/data/web/
sudo wget https://www.adminer.org/latest-mysql.phpНапример если адрес mailcow cow.domain.ru, то adminer будет доступен по ссылке cow.domain.ru/latest-mysql.php
Введите адрес сервера mysql: mysql
Логин: root
А пароль БД можно взять из файла mailcow.conf в строке DBROOT= Внимание!!!
Прежде чем выполнять дальнейшие действия, добавьте свой домен через админку mailcow.
Я точно не знаю в какой последовательности это нужно делать, но я это делал так.
Выбираем базу данных mailcow: Выбираем SQL запрос в левом верхнем углу: Теперь вводим такой запрос:
Код: Выделить всё
LOAD DATA INFILE 'users.csv'
INTO TABLE mailbox
CHARACTER SET utf8mb4
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
(@username, @password, @name)
SET
username = @username,
password = @password,
name = @name,
local_part = SUBSTRING_INDEX(@username, '@', 1),
domain = SUBSTRING_INDEX(@username, '@', -1);
Если вы хотите выключить квоты для всех почтовых ящиков сразу, или выставить их в какое-то адекватное значение выполните запрос:
Код: Выделить всё
UPDATE mailbox
SET quota = 0;Код: Выделить всё
UPDATE mailbox
SET attributes = { "force_pw_update": "0", "force_tfa": "0", "tls_enforce_in": "0", "tls_enforce_out": "0", "sogo_access": "1", "imap_access": "1", pop3_access": "1", "smtp_access": "1", "sieve_access": "1", "eas_access": "0","dav_access": "0", "relayhost": "0", "passwd_update": "2026-06-02 12:13:19", "mailbox_format": "maildir:", "quarantine_notification": "hourly", "quarantine_category": "reject", "attribute_hash": ""};
Далее нужно заполнить таблицу aliases. Я не совсем понимаю структуру этой таблицы, но когда заводишь новый ящик туда добавляется запись типа
Код: Выделить всё
address: user1@domain.ru
goto: user1@domain.ru
domain: domain.ruА можно и скопировать из mailboxes, это уже как кому нравится. Я сделал из users.csv:
Код: Выделить всё
LOAD DATA INFILE 'users.csv'
INTO TABLE mailbox
CHARACTER SET utf8mb4
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
(@username, @password, @name)
SET
address = @username,
goto = @username
domain = SUBSTRING_INDEX(@username, '@', -1);
Код: Выделить всё
INSERT INTO quota2 (username)
SELECT m.username
FROM mailbox m
LEFT JOIN quota2 q ON q.username = m.username
WHERE q.username IS NULL;Код: Выделить всё
INSERT INTO user_acl (username)
SELECT m.username
FROM mailbox m
LEFT JOIN user_acl u ON u.username = m.username
WHERE u.username IS NULL;INSERT INTO user_acl (username) - мы делаем вставку только поля username в user_acl (остальные поля будут заполнены согласно структуре таблицы и имеют дефолтные значения.
SELECT m.username FROM mailbox m
это выборка только поля username из таблицы mailbox. m это сокращение для таблицы mailbox.
Запись можно было бы записать длиннее: SELECT mailbox.username FROM mailbox.
Далее LEFT JOIN будет искать соответствие строк в таблицах user_acl.username и mailbox.username
и инструкция INSERT INTO будет применятся только к тем значениям, которых ещё нет в таблице user_acl.
Можно значительно упростить запрос, если вы не заводили ни одного почтового ящика. Это будет выглядеть так:
INSERT INTO user_acl (username) SELECT username FROM mailbox;
Однако если вы уже успели завести хотя бы один ящик, этот запрос выдаст ошибку.
На этом работы в базой данных можно завершить.
Есть ещё одна бд, которая должна быть заполнена значениями, это _sogo_static_view, однако, она заполнилась у меня значениями автоматически. Возможно, чтобы там что-то появилось нужно открыть домен и нажать кнопку "сохранить" в админке mailcow, но это не точно.
Ещё раз повторюсь что данная статья не является инструкцией, согласованной с mailcow.
На форумах mailcow все говорят что перенести таким образом учётки не получится и надо делать весь перенос через api и в результате все пароли поменяются.
Но я вам только что расписал все шаги, по которым я прошёл чтобы полностью скопировать логины-пароли и имена с зимбры на корову, и у меня в такой конфигурации всё прекрасно работает.
Не забудьте удалить файлы latest-mysql.php и users.csv после переноса.