Аккуратный перенос учёток с zimbra на mailcow

Тут собраны разные репепты для сисадминских дел, доступные всем без регистрации
Ответить
kasak
Администратор
Сообщения: 10
Зарегистрирован: 14 янв 2026, 18:28

Аккуратный перенос учёток с zimbra на mailcow

Сообщение kasak »

Уважаемый читатель, данную статью не следует принимать за истину в последней инстанции. НО! Поскольку других подобных статей в интернете не нашлось, я считаю необходимым описать свои действия, как мне удалось перенести учетки с паролями с zimbra на mailcow.
Не почту! А именно учётки и пароли!

Я не буду описывать лишний раз процесс установки mailcow, он и так прекрасно описан на сайте mailcow.
Я начну описание с генерации файла с учетными записями zimbra.
Настоятельно рекомендуется перед тем как генерировать этот файл удалить все "закрытые" и "заблокированные" почтовые ящики из zimbra, чтобы потом не блокировать их заново в mailcow.

Переходим под учётную запись zimbra

Код: Выделить всё

sudo su - zimbra
С помощью zmprov сначала составим полный список учеток и выведем это в файл:

Код: Выделить всё

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
В mailcow они нам не понадобятся.

Теперь нужно составить из этих адресов csv файл формата:

Код: Выделить всё

"email","password","name"
Для чего создаём там же в /tmp/ любым редактором такой скрипт:

Код: Выделить всё

#!/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.
Запустите скрипт и ожидайте. Зимбра делает эти запросы достаточно долго. На 300 ящиков ушло примерно минут 15-20.

Пример содержимого файла users.csv:

Код: Выделить всё

"user1@domain.ru","{SSHA512}tzJhcu...","Иванов Иван"
"user2@domain.ru","{SSHA512}cWJxP...","Петров Петр"
"user3@domain.ru","{SSHA512}qoqzY...","Сидоров Сидр"
Для работы с mailcow нам понадобится только этот последний файл, users.csv.

На запущенном сервере 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
Теперь в директорию /opt/mailcow-dockerized/data/web/
Скачайте adminer.

Код: Выделить всё

cd /opt/mailcow-dockerized/data/web/
sudo wget https://www.adminer.org/latest-mysql.php
Теперь по адресу сервера cow можно будет открыть и adminer.
Например если адрес mailcow cow.domain.ru, то adminer будет доступен по ссылке cow.domain.ru/latest-mysql.php

Введите адрес сервера mysql: mysql
Логин: root
А пароль БД можно взять из файла mailcow.conf в строке DBROOT=
mailcow1.png
mailcow1.png (22.48 КБ) 21 просмотр
Внимание!!!
Прежде чем выполнять дальнейшие действия, добавьте свой домен через админку mailcow.
Я точно не знаю в какой последовательности это нужно делать, но я это делал так.

Выбираем базу данных mailcow:
mailcow2.png
mailcow2.png (67.97 КБ) 21 просмотр
Выбираем SQL запрос в левом верхнем углу:
mailcow3.png
mailcow3.png (22.66 КБ) 21 просмотр
Теперь вводим такой запрос:

Код: Выделить всё

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);
mailcow4.png
mailcow4.png (105.03 КБ) 21 просмотр
Теперь нужно добавить ещё несколько полей, которые не добавляются автоматически.
Если вы хотите выключить квоты для всех почтовых ящиков сразу, или выставить их в какое-то адекватное значение выполните запрос:

Код: Выделить всё

UPDATE mailbox
SET quota = 0;
А так же нам нужно выставить поле attributes, по умолчанию оно пустое, но когда заводишь ящик через веб интерфейс оно принимает какое-то значение. Это значение по-умолчанию мы и выставим:

Код: Выделить всё

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
Ещё раз повторяюсь я не знаю почему так, но это так. Так что нам нужно ещё раз заполнить эту таблицу. Можно сделать из файла users.csv
А можно и скопировать из 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);
Теперь нужно заполнить ещё пару таблиц. Одна из них это quota2. Я просто скопировал username из mailboxes в эту таблицу:

Код: Выделить всё

INSERT INTO quota2 (username)
SELECT m.username
FROM mailbox m
LEFT JOIN quota2 q ON q.username = m.username
WHERE q.username IS NULL;
На выходе имеем:
quota.png
quota.png (167.28 КБ) 21 просмотр
Далее я по аналогии c quota2 я заполнил таблицу user_acl:

Код: Выделить всё

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;
На выходе имеем:
user_acl.png
user_acl.png (171.61 КБ) 21 просмотр
Запрос может показаться вычурным, возможно следует объяснить его синтаксис, если это интересно.
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 после переноса.
Ответить