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"))

Reply via email to