dovecot: migrate to dedicated homedir and separate maildir paths
Per the dovecot documentation[0] we were previously running with an unsupported home directory configuration, because we shared them among all virtual users at /var/vmail. After resolving this by creating per user home directories at /var/vmail/%{domain}/%{user} this now also overlaps with the location of the Maildir, which is not recommended. As a result we now need to migrate our Maildirs into /var/vmail/%{domain}/%{user}/mail, for which a small shell script is provided as part of this change. The script is included in the documentation because we cannot provide it in time for users, because they might already be seeing the relevant assertion and there is no safe waiting period that would allow us to skip shipping it like that. [0] https://doc.dovecot.org/2.3/configuration_manual/mail_location/
This commit is contained in:
parent
601b33d2a7
commit
62ea8a7e00
5 changed files with 216 additions and 4 deletions
132
migrations/nixos-mailserver-migration-03.py
Normal file
132
migrations/nixos-mailserver-migration-03.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from pwd import getpwnam
|
||||
|
||||
|
||||
class FolderLayout(Enum):
|
||||
Default = 1
|
||||
Folder = 2
|
||||
|
||||
|
||||
def check_user(vmail_root: Path):
|
||||
owner = vmail_root.owner()
|
||||
owner_uid = getpwnam(owner).pw_uid
|
||||
|
||||
if os.geteuid() == owner_uid:
|
||||
return
|
||||
|
||||
try:
|
||||
print(
|
||||
f"Trying to switch effective user id to {owner_uid} ({owner})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
os.seteuid(owner_uid)
|
||||
return
|
||||
except PermissionError:
|
||||
print(
|
||||
f"Failed switching to virtual mail user. Please run this script under it, for example by using `sudo -u {owner}`)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def is_maildir_related(path: Path, layout: FolderLayout) -> bool:
|
||||
if not path.is_dir():
|
||||
return False
|
||||
if path.name in ["cur", "new", "tmp"]:
|
||||
return True
|
||||
if layout is FolderLayout.Default and path.name.startswith("."):
|
||||
return True
|
||||
if layout is FolderLayout.Folder:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def mkdir(dst: Path, dry_run: bool = True):
|
||||
print(f'mkdir "{dst}"')
|
||||
if not dry_run:
|
||||
# u+rwx, setgid
|
||||
dst.mkdir(mode=0o2700)
|
||||
|
||||
|
||||
def move(src: Path, dst: Path, dry_run: bool = True):
|
||||
print(f'mv "{src}" "{dst}"')
|
||||
if not dry_run:
|
||||
src.rename(dst)
|
||||
|
||||
|
||||
def delete(dst: Path, dry_run: bool = True):
|
||||
if not dst.exists():
|
||||
return
|
||||
|
||||
if dst.is_dir():
|
||||
print(f'rm --recursive "{dst}"')
|
||||
if not dry_run:
|
||||
shutil.rmtree(dst)
|
||||
else:
|
||||
print(f'rm "{dst}"')
|
||||
if not dry_run:
|
||||
dst.unlink()
|
||||
|
||||
|
||||
def main(vmail_root: Path, layout: FolderLayout, dry_run: bool = True):
|
||||
maildirs = {path.parent for path in vmail_root.glob("*/*/cur")}
|
||||
maybe_delete = []
|
||||
|
||||
# The old maildir will be the new home directory
|
||||
for homedir in maildirs:
|
||||
maildir = homedir / "mail"
|
||||
mkdir(maildir, dry_run)
|
||||
|
||||
for path in homedir.iterdir():
|
||||
if is_maildir_related(path, layout):
|
||||
move(path, maildir / path.name, dry_run)
|
||||
else:
|
||||
maybe_delete.append(path)
|
||||
|
||||
# Files that are part of the previous home directory, but now obsolete
|
||||
for path in [
|
||||
vmail_root / ".dovecot.lda-dupes",
|
||||
vmail_root / ".dovecot.lda-dupes.locks",
|
||||
]:
|
||||
delete(path, dry_run)
|
||||
|
||||
# The remaining files are likely obsolete, but should still be checked with care
|
||||
for path in maybe_delete:
|
||||
print(f"# rm {str(path)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="""
|
||||
NixOS Mailserver Migration #3: Dovecot mail directory migration
|
||||
(https://nixos-mailserver.readthedocs.io/en/latest/migrations.html#dovecot-mail-directory-migration)
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
"vmail_root", type=Path, help="Path to the `mailserver.mailDirectory`"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--layout",
|
||||
choices=["default", "folder"],
|
||||
required=True,
|
||||
help="Folder layout: 'default' unless `mailserver.useFsLayout` was enabled, then'folder'",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--execute", action="store_true", help="Actually perform changes"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
layout = FolderLayout.Default if args.layout == "default" else FolderLayout.Folder
|
||||
|
||||
check_user(args.vmail_root)
|
||||
main(args.vmail_root, layout, not args.execute)
|
Loading…
Add table
Add a link
Reference in a new issue