Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions chatmaild/src/chatmaild/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def __init__(self, inipath, params):
self.privacy_mail = params.get("privacy_mail")
self.privacy_pdo = params.get("privacy_pdo")
self.privacy_supervisor = params.get("privacy_supervisor")
self.tmpfs_index = params.get("tmpfs_index", "false").lower() == "true"

# deprecated option
mbdir = params.get("mailboxes_dir", f"/home/vmail/mail/{self.mail_domain}")
Expand Down Expand Up @@ -111,10 +112,10 @@ def get_default_config_content(mail_domain, **overrides):

if mail_domain.endswith(".testrun.org"):
override_inipath = inidir.joinpath("override-testrun.ini")
privacy = iniconfig.IniConfig(override_inipath)["privacy"]
params = iniconfig.IniConfig(override_inipath)["params"]
lines = []
for line in content.split("\n"):
for key, value in privacy.items():
for key, value in params.items():
value_lines = value.format(mail_domain=mail_domain).strip().split("\n")
if not line.startswith(f"{key} =") or not value_lines:
continue
Expand Down
22 changes: 16 additions & 6 deletions chatmaild/src/chatmaild/expire.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
FileEntry = namedtuple("FileEntry", ("path", "mtime", "size"))


def iter_mailboxes(basedir, maxnum):
def iter_mailboxes(basedir, maxnum, tmpfs_index):
if not os.path.exists(basedir):
print_info(f"no mailboxes found at: {basedir}")
return

for name in os_listdir_if_exists(basedir)[:maxnum]:
if "@" in name:
yield MailboxStat(basedir + "/" + name)
yield MailboxStat(basedir + "/" + name, name, tmpfs_index)


def get_file_entry(path):
Expand All @@ -49,11 +49,14 @@ def os_listdir_if_exists(path):
class MailboxStat:
last_login = None

def __init__(self, basedir):
def __init__(self, basedir, name, tmpfs_index):
self.basedir = str(basedir)
self.name = name
self.messages = []
self.extrafiles = []
self.scandir(self.basedir)
if tmpfs_index:
self.scandir("/dev/shm/" + name)

def scandir(self, folderdir):
for name in os_listdir_if_exists(folderdir):
Expand Down Expand Up @@ -90,11 +93,13 @@ def __init__(self, config, dry, now, verbose):
self.all_files = 0
self.start = time.time()

def remove_mailbox(self, mboxdir):
def remove_mailbox(self, mboxdir, name):
if self.verbose:
print_info(f"removing {mboxdir}")
if not self.dry:
shutil.rmtree(mboxdir)
if self.config.tmpfs_index:
shutil.rmtree("/dev/shm/" + name)
self.del_mboxes += 1

def remove_file(self, path, mtime=None):
Expand All @@ -121,7 +126,7 @@ def process_mailbox_stat(self, mbox):
self.all_mboxes += 1
changed = False
if mbox.last_login and mbox.last_login < cutoff_without_login:
self.remove_mailbox(mbox.basedir)
self.remove_mailbox(mbox.basedir, mbox.name)
return

mboxname = os.path.basename(mbox.basedir)
Expand All @@ -145,6 +150,9 @@ def process_mailbox_stat(self, mbox):
changed = True
if changed:
self.remove_file(f"{mbox.basedir}/maildirsize")
for file in mbox.extrafiles:
if "dovecot.index" in file.path.split("/")[-1] and file.size > 500 * 1024:
self.remove_file(file.path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches broadly and according to my brief research triggers a full index rebuild where the old max_size flag just pruned the cache, I wonder if this won't mean a lot of I/O churn.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thinking about it again, we should only remove dovecot.index.cache files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only that, on second thought it's the only useful thing in this PR. I'll close this one and create a new 3-line PR.


def get_summary(self):
return (
Expand Down Expand Up @@ -197,7 +205,9 @@ def main(args=None):

maxnum = int(args.maxnum) if args.maxnum else None
exp = Expiry(config, dry=not args.remove, now=now, verbose=args.verbose)
for mailbox in iter_mailboxes(str(config.mailboxes_dir), maxnum=maxnum):
for mailbox in iter_mailboxes(
str(config.mailboxes_dir), maxnum, config.tmpfs_index
):
exp.process_mailbox_stat(mailbox)
print(exp.get_summary())

Expand Down
2 changes: 1 addition & 1 deletion chatmaild/src/chatmaild/fsreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def main(args=None):

maxnum = int(args.maxnum) if args.maxnum else None
rep = Report(now=now, min_login_age=int(args.min_login_age), mdir=args.mdir)
for mbox in iter_mailboxes(str(config.mailboxes_dir), maxnum=maxnum):
for mbox in iter_mailboxes(str(config.mailboxes_dir), maxnum, config.tmpfs_index):
rep.process_mailbox_stat(mbox)
rep.dump_summary()

Expand Down
3 changes: 3 additions & 0 deletions chatmaild/src/chatmaild/ini/chatmail.ini.f
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
# (space-separated, item may start with "@" to whitelist whole recipient domains)
passthrough_recipients =

# store index files in tmpfs (good for disk size and I/O, bad for ram)
Comment thread
missytake marked this conversation as resolved.
tmpfs_index = false

# path to www directory - documented here: https://chatmail.at/doc/relay/getting_started.html#custom-web-pages
#www_folder = www

Expand Down
3 changes: 2 additions & 1 deletion chatmaild/src/chatmaild/ini/override-testrun.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[params]

[privacy]
tmpfs_index = true
Comment thread
missytake marked this conversation as resolved.

passthrough_recipients = privacy@testrun.org echo@{mail_domain}

Expand Down
20 changes: 13 additions & 7 deletions chatmaild/src/chatmaild/tests/test_expire.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,22 @@ def create_new_messages(basedir, relpaths, size=1000, days=0):

@pytest.fixture
def mbox1(example_config):
mboxdir = example_config.mailboxes_dir.joinpath("mailbox1@example.org")
addr = "mailbox1@example.org"
mboxdir = example_config.mailboxes_dir.joinpath(addr)
mboxdir.mkdir()
fill_mbox(mboxdir)
return MailboxStat(mboxdir)
return MailboxStat(mboxdir, addr, False)


def test_deltachat_folder(example_config):
"""Test old setups that might have a .DeltaChat folder where messages also need to get removed."""
mboxdir = example_config.mailboxes_dir.joinpath("mailbox1@example.org")
addr = "mailbox1@example.org"
mboxdir = example_config.mailboxes_dir.joinpath(addr)
mboxdir.mkdir()
mbox2dir = mboxdir.joinpath(".DeltaChat")
mbox2dir.mkdir()
fill_mbox(mbox2dir)
mb = MailboxStat(mboxdir)
mb = MailboxStat(mboxdir, addr, False)
assert len(mb.messages) == 2


Expand All @@ -69,7 +71,11 @@ def test_filentry_ordering(tmp_path):


def test_no_mailbxoes(tmp_path, capsys):
assert [] == list(iter_mailboxes(str(tmp_path.joinpath("notexists")), maxnum=10))
assert [] == list(
iter_mailboxes(
str(tmp_path.joinpath("notexists")), maxnum=10, tmpfs_index=False
)
)
out, err = capsys.readouterr()
assert "no mailboxes" in err

Expand All @@ -86,13 +92,13 @@ def test_stats_mailbox(mbox1):

create_new_messages(mbox1.basedir, ["large-extra"], size=1000)
create_new_messages(mbox1.basedir, ["index-something"], size=3)
mbox2 = MailboxStat(mbox1.basedir)
mbox2 = MailboxStat(mbox1.basedir, mbox1.name, False)
assert len(mbox2.extrafiles) == 5
assert mbox2.extrafiles[0].size == 1000

# cope well with mailbox dirs that have no password (for whatever reason)
Path(mbox1.basedir).joinpath("password").unlink()
mbox3 = MailboxStat(mbox1.basedir)
mbox3 = MailboxStat(mbox1.basedir, mbox1.name, False)
assert mbox3.last_login is None


Expand Down
10 changes: 4 additions & 6 deletions cmdeploy/src/cmdeploy/dovecot/dovecot.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,11 @@ userdb {
##

# Mailboxes are stored in the "mail" directory of the vmail user home.
{% if config.tmpfs_index %}
mail_location = maildir:{{ config.mailboxes_dir }}/%u:INDEX=/dev/shm/%u
{% else %}
mail_location = maildir:{{ config.mailboxes_dir }}/%u

# index/cache files are not very useful for chatmail relay operations
# but it's not clear how to disable them completely.
# According to https://doc.dovecot.org/2.3/settings/advanced/#core_setting-mail_cache_max_size
# if the cache file becomes larger than the specified size, it is truncated by dovecot
mail_cache_max_size = 500K
Comment thread
missytake marked this conversation as resolved.
{% endif %}

namespace inbox {
inbox = yes
Expand Down