Hi guys,
this page needs to be updated:
https://wiki2.dovecot.org/MailLocation/Maildir#Control_files
(link was from here: https://wiki2.dovecot.org/MailLocation)
Section 'Control files' is mentioning only 2 files but, in fact,
Dovecot will store some additional files in CONTROL location:
dovecot-uidvalidity
dovecot-uidvalidity.*
subscriptions
(anything more?)
Even more, page https://wiki2.dovecot.org/MailLocation has missing
note to CONTROL files location about storing files 'dovecot-uidlist'
and 'dovecot-keywords' of INBOX inside .INBOX directory instead of
root directory (as it's stored inside mailbox).
Missing anything from above will cause re-downloading of all e-mails
by e-mail clients!
And finally, i'm attaching a Python script which will safely copy/move
CONTROL files from mail_location to new location (btw, moving it on
SSD storage caused quite high performance boost in our environment).
azur
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Jozef Sudolsky, ELBIA, s. r. o.
# All rights reserved.
# BSD Licence
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of SK-NIC Python-lib nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
################################################################################
# Dovecot's 'mail_location' path without formatting:
# - only Maildir format is supported
# - only this path formatting is supported: /path/%d/%n
# - moving CONTROL files back to mail_location is NOT supported
mail_location = "/var/mail/vhosts"
# New path for CONTROL files without formatting, must be empty.
new_control_location = "/flash/dovecot_control"
# One of:
# - copy = only copy CONTROL files to new_control_location
# - delete = only delete CONTROL files from mail_location (usefull when you first want to only copy files to see if everything is ok and delete them from mailboxes afterwards)
# - move = copy + delete
operation_mode = "copy"
# UID of newly created files/directories (usually UID of user postfix, see Dovecot configuration)
uid = 102
# GID of newly created files/directories (usually GID of group mail, see Dovecot configuration)
gid = 8
#################################STOP#HERE######################################
# Tested with Dovecot 2.3.4.
# Usage:
# 1.) Stop Dovecot.
# 2.) Check, if it's really stopped.
# 3.) Re-check, if it's really stopped.
# 4.) Run this script (see configuration above).
# 5.) Check that everything is ok.
# 6.) Update Dovecot's configuration and set CONTROL argument for mail_location setting (can be there multiple times, depends on your configuration).
# 7.) Start Dovecot and pray for everything is ok.
# 8.) ???
# 9.) Profit.
################################################################################
import os
import sys
import shutil
if __name__ == "__main__":
if not os.path.isdir(mail_location):
print("ERROR: 'mail_location' path doesn't exists")
sys.exit(1)
if not os.path.isdir(new_control_location):
print("ERROR: 'new_control_location' path doesn't exists")
sys.exit(1)
if operation_mode not in ("copy", "delete", "move"):
print("ERROR: invalid 'operation_mode' setting")
sys.exit(1)
for i in os.listdir(mail_location):
if operation_mode in ("copy", "move"):
os.mkdir(os.path.join(new_control_location, i))
os.chown(os.path.join(new_control_location, i), uid, gid)
os.chmod(os.path.join(new_control_location, i), 0770)
for j in os.listdir(os.path.join(mail_location, i)):
if operation_mode in ("copy", "move"):
os.mkdir(os.path.join(new_control_location, i, j))
os.chown(os.path.join(new_control_location, i, j), uid, gid)
os.chmod(os.path.join(new_control_location, i, j), 0770)
os.mkdir(os.path.join(new_control_location, i, j, ".INBOX"))
os.chown(os.path.join(new_control_location, i, j, ".INBOX"), uid, gid)
os.chmod(os.path.join(new_control_location, i, j, ".INBOX"), 0770)
if os.path.isfile(os.path.join(mail_location, i, j, "dovecot-uidlist")):
if operation_mode in ("copy", "move"):
shutil.copy2(os.path.join(mail_location, i, j, "dovecot-uidlist"), os.path.join(new_control_location, i, j, ".INBOX", "dovecot-uidlist"))
s = os.stat(os.path.join(mail_location, i, j, "dovecot-uidlist"))
os.chown(os.path.join(new_control_location, i, j, ".INBOX", "dovecot-uidlist"), s.st_uid, s.st_gid)
if operation_mode in ("delete", "move"):
os.remove(os.path.join(mail_location, i, j, "dovecot-uidlist"))
if os.path.isfile(os.path.join(mail_location, i, j, "dovecot-keywords")):
if operation_mode in ("copy", "move"):
shutil.copy2(os.path.join(mail_location, i, j, "dovecot-keywords"), os.path.join(new_control_location, i, j, ".INBOX", "dovecot-keywords"))
s = os.stat(os.path.join(mail_location, i, j, "dovecot-keywords"))
os.chown(os.path.join(new_control_location, i, j, ".INBOX", "dovecot-keywords"), s.st_uid, s.st_gid)
if operation_mode in ("delete", "move"):
os.remove(os.path.join(mail_location, i, j, "dovecot-keywords"))
if os.path.isfile(os.path.join(mail_location, i, j, "subscriptions")):
if operation_mode in ("copy", "move"):
shutil.copy2(os.path.join(mail_location, i, j, "subscriptions"), os.path.join(new_control_location, i, j, "subscriptions"))
s = os.stat(os.path.join(mail_location, i, j, "subscriptions"))
os.chown(os.path.join(new_control_location, i, j, "subscriptions"), s.st_uid, s.st_gid)
if operation_mode in ("delete", "move"):
os.remove(os.path.join(mail_location, i, j, "subscriptions"))
if os.path.isfile(os.path.join(mail_location, i, j, "dovecot-uidvalidity")):
if operation_mode in ("copy", "move"):
shutil.copy2(os.path.join(mail_location, i, j, "dovecot-uidvalidity"), os.path.join(new_control_location, i, j, "dovecot-uidvalidity"))
s = os.stat(os.path.join(mail_location, i, j, "dovecot-uidvalidity"))
os.chown(os.path.join(new_control_location, i, j, "dovecot-uidvalidity"), s.st_uid, s.st_gid)
if operation_mode in ("delete", "move"):
os.remove(os.path.join(mail_location, i, j, "dovecot-uidvalidity"))
f = open(os.path.join(mail_location, i, j, "dovecot-uidvalidity"), "r")
d = f.read().strip()
f.close()
if os.path.isfile(os.path.join(mail_location, i, j, "dovecot-uidvalidity.%s" % d)):
if operation_mode in ("copy", "move"):
shutil.copy2(os.path.join(mail_location, i, j, "dovecot-uidvalidity.%s" % d), os.path.join(new_control_location, i, j, "dovecot-uidvalidity.%s" % d))
s = os.stat(os.path.join(mail_location, i, j, "dovecot-uidvalidity.%s" % d))
os.chown(os.path.join(new_control_location, i, j, "dovecot-uidvalidity.%s" % d), s.st_uid, s.st_gid)
if operation_mode in ("delete", "move"):
os.remove(os.path.join(mail_location, i, j, "dovecot-uidvalidity.%s" % d))
for k in os.listdir(os.path.join(mail_location, i, j)):
if k[0] == "." and os.path.isdir(os.path.join(mail_location, i, j, k)):
if operation_mode in ("copy", "move"):
os.mkdir(os.path.join(new_control_location, i, j, k))
os.chown(os.path.join(new_control_location, i, j, k), uid, gid)
os.chmod(os.path.join(new_control_location, i, j, k), 0770)
if os.path.isfile(os.path.join(mail_location, i, j, k, "dovecot-uidlist")):
if operation_mode in ("copy", "move"):
shutil.copy2(os.path.join(mail_location, i, j, k, "dovecot-uidlist"), os.path.join(new_control_location, i, j, k, "dovecot-uidlist"))
s = os.stat(os.path.join(mail_location, i, j, k, "dovecot-uidlist"))
os.chown(os.path.join(new_control_location, i, j, k, "dovecot-uidlist"), s.st_uid, s.st_gid)
if operation_mode in ("delete", "move"):
os.remove(os.path.join(mail_location, i, j, k, "dovecot-uidlist"))
if os.path.isfile(os.path.join(mail_location, i, j, k, "dovecot-keywords")):
if operation_mode in ("copy", "move"):
shutil.copy2(os.path.join(mail_location, i, j, k, "dovecot-keywords"), os.path.join(new_control_location, i, j, k, "dovecot-keywords"))
s = os.stat(os.path.join(mail_location, i, j, k, "dovecot-keywords"))
os.chown(os.path.join(new_control_location, i, j, k, "dovecot-keywords"), s.st_uid, s.st_gid)
if operation_mode in ("delete", "move"):
os.remove(os.path.join(mail_location, i, j, k, "dovecot-keywords"))