The problem with dot-notation folders in Dovecot 2.3, which were no longer
accepted in Dovecot 2.4, is SOLVED!
Solution:
# systemctl stop dovecot
# /usr/local/bin/migrate-dovecot-folders.sh
# systemctl start dovecot
# doveadm force-resync -u '*' '*'
Now there is another challenge: many individual user folders are not
automatically displayed. This is automatically synchronized using the script
"auto-subscribe-dovecot-folders.sh".
Afterward, all mailboxes will function as expected.
Here are the two scripts:
#!/usr/bin/bash
# migrate-dovecot-folders.sh
# Usage:
# ./migrate-dovecot-folders.sh (dry-run)
# ./migrate-dovecot-folders.sh --apply (perform changes)
# ./migrate-dovecot-folders.sh --user user@domain (only that user)
set -euo pipefail
BASE="/var/vmail"
DRY_RUN=1
ONLY_USER=""
PRESERVE_CASE=1 # 1 = keep original case after .INBOX., 0 = TitleCase
if [[ "${1:-}" == "--apply" ]]; then DRY_RUN=0; shift; fi
while [[ $# -gt 0 ]]; do
case "$1" in
--apply) DRY_RUN=0; shift ;;
--user) ONLY_USER="$2"; shift 2 ;;
--lower) PRESERVE_CASE=0; shift ;;
*) echo "Unknown arg: $1"; exit 1 ;;
esac
done
log() {
echo "$(date +'%F %T') $*"
}
# transform a source folder (basename) to target name
transform_name() {
local src="$1"
# if starts with .INBOX. => strip that prefix
if [[ "$src" =~ ^\.INBOX\.(.+) ]]; then
local after="${BASH_REMATCH[1]}"
if [[ $PRESERVE_CASE -eq 1 ]]; then
echo "$after"
else
# Title case: replace separators and uppercase first letters
echo "$after" | sed -E 's/[^A-Za-z0-9]+/ /g' | awk '{ for(i=1;i<=NF;i++){
$i = toupper(substr($i,1,1)) tolower(substr($i,2)) } ; print $0 }' | sed 's/
/_/g'
fi
return
fi
# otherwise strip leading dot
if [[ "$src" =~ ^\.(.+) ]]; then
echo "${BASH_REMATCH[1]}"
return
fi
# otherwise return unchanged
echo "$src"
}
# move/merge a mailbox subdir
move_folder() {
local userdir="$1" # full path to user dir, e.g. /var/vmail/domain/user
local srcname="$2" # basename e.g. .INBOX.Fail2ban
local tgtname="$3" # basename e.g. Fail2ban
local src="$userdir/$srcname"
local tgt="$userdir/$tgtname"
# sanity checks
[[ -d "$src" ]] || { log "SKIP: src not dir: $src"; return 0; }
if [[ "$src" == "$tgt" ]]; then
log "SKIP: source == target for $src"
return 0
fi
if [[ $DRY_RUN -eq 1 ]]; then
log "DRYRUN: would rename '$src' -> '$tgt'"
return 0
fi
# if target exists, merge contents
if [[ -d "$tgt" ]]; then
log "Merging '$src' -> '$tgt'"
# move cur/new/tmp files (avoid clobbering same names)
for part in cur new tmp; do
if [[ -d "$src/$part" ]]; then
mkdir -p "$tgt/$part"
# move files, avoid overwrite: use mv -n if available; otherwise loop
if mv -n "$src/$part/"* "$tgt/$part/" 2>/dev/null; then
true
else
# fallback: move one-by-one with unique suffix
for f in "$src/$part/"*; do
[[ -e "$f" ]] || continue
base="$(basename "$f")"
if [[ -e "$tgt/$part/$base" ]]; then
# append PID timestamp
mv "$f" "$tgt/$part/${base}.$(date +%s).$$"
else
mv "$f" "$tgt/$part/"
fi
done
fi
fi
done
# move dovecot.* files (index/cache/uidlist etc.) - if target has file, we
keep target file
for f in dovecot.* maildirsize subscriptions mailbox*; do
if [[ -e "$src/$f" && ! -e "$tgt/$f" ]]; then
mv "$src/$f" "$tgt/"
else
# if both exist, prefer keeping target and remove src's file
if [[ -e "$src/$f" ]]; then
rm -f "$src/$f"
fi
fi
done
# remove now-empty src directories if empty
find "$src" -mindepth 1 -maxdepth 1 | read -r || rmdir
--ignore-fail-on-non-empty "$src" || true
chown -R vmail:vmail "$tgt"
log "Merged done: $src -> $tgt"
return 0
fi
# Otherwise simple rename
log "Renaming '$src' -> '$tgt'"
mv "$src" "$tgt"
chown -R vmail:vmail "$tgt"
log "Renamed done: $src -> $tgt"
}
# iterate all user dirs
find_users() {
if [[ -n "$ONLY_USER" ]]; then
# Only process single user: split domain/user
# user dir may be under /var/vmail/<domain>/<localpart or fulluser?>
# try to find the exact directory
find "$BASE" -mindepth 2 -maxdepth 3 -type d -path "*/$ONLY_USER"
2>/dev/null
return
fi
# list user dirs: /var/vmail/<domain>/<user>
find "$BASE" -mindepth 2 -maxdepth 2 -type d -printf '%h/%f\n' 2>/dev/null
}
# Main
log "Starting migration script (DRY_RUN=$DRY_RUN) base=$BASE
only_user=$ONLY_USER"
while IFS= read -r userdir; do
[[ -n "$userdir" ]] || continue
# ensure it's a user directory (has cur/new/tmp or maildirfolder)
if [[ ! -d "$userdir" ]]; then continue; fi
# scan for dot-folders in that user dir
while IFS= read -r src; do
srcbase="$(basename "$src")"
# skip standard maildir parts
case "$srcbase" in
cur|new|tmp|Maildir|dovecot.*|subscriptions|maildirsize) continue ;;
esac
# select only directories starting with dot OR name equal to "INBOX"
variants
if [[ "$srcbase" =~ ^\. ]] || [[ "$srcbase" =~ ^INBOX ]]; then
tgtbase="$(transform_name "$srcbase")"
# if transform yields empty -> skip
[[ -n "$tgtbase" ]] || continue
# avoid converting e.g. ".INBOX" -> "" (keep INBOX)
if [[ "$tgtbase" == "" ]]; then tgtbase="INBOX"; fi
move_folder "$userdir" "$srcbase" "$tgtbase"
fi
done < <(find "$userdir" -mindepth 1 -maxdepth 1 -type d -printf '%p\n'
2>/dev/null)
done < <(find_users)
log "Done. If not --apply then this was a dry-run. Verify and then run with
--apply."
# after running with --apply, run (per-user) reindex/resync, e.g.:
# doveadm force-resync -u user@domain '*'
# or for many users:
# for u in user1@d; do doveadm force-resync -u '*' '*'; done
And:
#!/bin/bash
# auto-subscribe-dovecot-folders.sh
# Automatically subscribes to all existing Maildir folders for all users.
BASE="/var/vmail"
SYSTEM_FOLDERS=("Drafts" "Sent" "Junk" "Trash" "Archive" "Quarantine" "INBOX"
"new" "cur" "tmp" "sieve")
echo "Starting auto-subscribe of non-system folders..."
# Loop through all domains
for DOMAIN in "$BASE"/*; do
[ -d "$DOMAIN" ] || continue
DOMAIN_NAME=$(basename "$DOMAIN")
# Loop through all users
for USERDIR in "$DOMAIN"/*; do
[ -d "$USERDIR" ] || continue
USERNAME=$(basename "$USERDIR")
USER_EMAIL="$USERNAME@$DOMAIN_NAME"
echo "Processing user $USER_EMAIL..."
# Loop through all folders in the user directory
for MAILBOX in "$USERDIR"/*; do
[ -d "$MAILBOX" ] || continue
FOLDER=$(basename "$MAILBOX")
# Check if it's a system folder
SKIP=0
for SYS in "${SYSTEM_FOLDERS[@]}"; do
if [[ "$FOLDER" == "$SYS" ]]; then
SKIP=1
break
fi
done
[ $SKIP -eq 1 ] && continue
# Subscribe to mailbox
echo "Subscribing $FOLDER for $USER_EMAIL..."
doveadm mailbox subscribe -u "$USER_EMAIL" "$FOLDER"
done
done
done
echo "Done."
This is working as desired now. I hope this helps everyone who is facing a
similar problem.
ByteMe
_______________________________________________
dovecot mailing list -- [email protected]
To unsubscribe send an email to [email protected]