Skip to content

Commit 0b202d9

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents d125e57 + 7191329 commit 0b202d9

File tree

24 files changed

+304
-106
lines changed

24 files changed

+304
-106
lines changed

.github/workflows/test-and-deploy-ipv4only.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ jobs:
9393
ssh root@ns.testrun.org systemctl reload nsd
9494
9595
- name: cmdeploy test
96-
run: CHATMAIL_DOMAIN2=nine.testrun.org cmdeploy test --slow
96+
run: CHATMAIL_DOMAIN2=ci-chatmail.testrun.org cmdeploy test --slow
9797

9898
- name: cmdeploy dns
9999
run: cmdeploy dns -v

.github/workflows/test-and-deploy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ jobs:
9494
ssh root@ns.testrun.org systemctl reload nsd
9595
9696
- name: cmdeploy test
97-
run: CHATMAIL_DOMAIN2=nine.testrun.org cmdeploy test --slow
97+
run: CHATMAIL_DOMAIN2=ci-chatmail.testrun.org cmdeploy test --slow
9898

9999
- name: cmdeploy dns
100100
run: cmdeploy dns -v

CHANGELOG.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
11
# Changelog for chatmail deployment
22

3-
## untagged
3+
## 1.9.0 2025-12-18
44

5-
- Add imap_compress option to chatmail.ini (#760)
5+
### Documentation
6+
7+
- Add RELEASE.md and CONTRIBUTING.md
8+
- README update, mention Chatmail Cookbook project
9+
10+
### Bug Fixes
11+
12+
- Expire messages also from IMAP subfolders
13+
- Use absolute path instead of relative path in message expiration script
14+
- Restart Postfix and Dovecot automatically on failure
15+
- acmetool: Use a fixed name and `reconcile` instead of `want`
16+
17+
### Features
18+
19+
- Report DKIM error code in SMTP response
20+
- Remove development notice from the web pages
21+
22+
### Miscellaneous Tasks
23+
24+
- Update the heading in the CHANGELOG.md
25+
- Setup git-cliff
26+
- Run tests against ci-chatmail.testrun.org instead of nine.testrun.org
27+
- Cleanup remaining echobot code, remove echobot user from deployment and passthrough recipients
28+
29+
## 1.8.0 2025-12-12
30+
31+
- Add imap_compress option to chatmail.ini
32+
([#760](https://github.com/chatmail/relay/pull/760))
633

734
- Remove echobot from relays
835
([#753](https://github.com/chatmail/relay/pull/753))
936

37+
- Fix `cmdeploy webdev`
38+
([#743](https://github.com/chatmail/relay/pull/743))
39+
1040
- Add robots.txt to exclude all web crawlers
1141
([#732](https://github.com/chatmail/relay/pull/732))
1242

CONTRIBUTING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Contributing to the chatmail relay
2+
3+
Commit messages follow the [Conventional Commits] notation.
4+
We use [git-cliff] to generate the changelog from commit messages before the release.
5+
6+
[Conventional Commits]: https://www.conventionalcommits.org/
7+
[git-cliff]: https://git-cliff.org/

RELEASE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Releasing a new version of chatmail relay
2+
3+
For example, to release version 1.9.0 of chatmail relay, do the following steps.
4+
5+
1. Update the changelog: `git cliff --unreleased --tag 1.9.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.9.0 -p CHANGELOG.md`.
6+
7+
2. Open the changelog in the editor, edit it if required.
8+
9+
3. Commit the changes to the changelog with a commit message `chore(release): prepare for 1.9.0`.
10+
11+
3. Tag the release: `git tag --annotate 1.9.0`.
12+
13+
4. Push the release tag: `git push origin 1.9.0`.
14+
15+
5. Create a GitHub release: `gh release create 1.9.0`.

chatmaild/src/chatmaild/expire.py

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from chatmaild.config import read_config
1616

17-
FileEntry = namedtuple("FileEntry", ("relpath", "mtime", "size"))
17+
FileEntry = namedtuple("FileEntry", ("path", "mtime", "size"))
1818

1919

2020
def iter_mailboxes(basedir, maxnum):
@@ -51,33 +51,27 @@ class MailboxStat:
5151

5252
def __init__(self, basedir):
5353
self.basedir = str(basedir)
54-
# all detected messages in cur/new/tmp folders
5554
self.messages = []
56-
57-
# all detected files in mailbox top dir
5855
self.extrafiles = []
56+
self.scandir(self.basedir)
5957

60-
# scan all relevant files (without recursion)
61-
old_cwd = os.getcwd()
62-
try:
63-
os.chdir(self.basedir)
64-
except FileNotFoundError:
65-
return
66-
for name in os_listdir_if_exists("."):
58+
def scandir(self, folderdir):
59+
for name in os_listdir_if_exists(folderdir):
60+
path = f"{folderdir}/{name}"
6761
if name in ("cur", "new", "tmp"):
68-
for msg_name in os_listdir_if_exists(name):
69-
entry = get_file_entry(f"{name}/{msg_name}")
62+
for msg_name in os_listdir_if_exists(path):
63+
entry = get_file_entry(f"{path}/{msg_name}")
7064
if entry is not None:
7165
self.messages.append(entry)
72-
66+
elif os.path.isdir(path):
67+
self.scandir(path)
7368
else:
74-
entry = get_file_entry(name)
69+
entry = get_file_entry(path)
7570
if entry is not None:
7671
self.extrafiles.append(entry)
7772
if name == "password":
7873
self.last_login = entry.mtime
7974
self.extrafiles.sort(key=lambda x: -x.size)
80-
os.chdir(old_cwd)
8175

8276

8377
def print_info(msg):
@@ -130,13 +124,6 @@ def process_mailbox_stat(self, mbox):
130124
self.remove_mailbox(mbox.basedir)
131125
return
132126

133-
# all to-be-removed files are relative to the mailbox basedir
134-
try:
135-
os.chdir(mbox.basedir)
136-
except FileNotFoundError:
137-
print_info(f"mailbox not found/vanished {mbox.basedir}")
138-
return
139-
140127
mboxname = os.path.basename(mbox.basedir)
141128
if self.verbose:
142129
date = datetime.fromtimestamp(mbox.last_login) if mbox.last_login else None
@@ -147,11 +134,12 @@ def process_mailbox_stat(self, mbox):
147134
self.all_files += len(mbox.messages)
148135
for message in mbox.messages:
149136
if message.mtime < cutoff_mails:
150-
self.remove_file(message.relpath, mtime=message.mtime)
137+
self.remove_file(message.path, mtime=message.mtime)
151138
elif message.size > 200000 and message.mtime < cutoff_large_mails:
152139
# we only remove noticed large files (not unnoticed ones in new/)
153-
if message.relpath.startswith("cur/"):
154-
self.remove_file(message.relpath, mtime=message.mtime)
140+
parts = message.path.split("/")
141+
if len(parts) >= 2 and parts[-2] == "cur":
142+
self.remove_file(message.path, mtime=message.mtime)
155143
else:
156144
continue
157145
changed = True

chatmaild/src/chatmaild/ini/chatmail.ini.f

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
# (space-separated, item may start with "@" to whitelist whole recipient domains)
4646
passthrough_recipients =
4747

48-
# path to www directory - documented here: https://github.com/chatmail/relay/#custom-web-pages
48+
# path to www directory - documented here: https://chatmail.at/doc/relay/getting_started.html#custom-web-pages
4949
#www_folder = www
5050

5151
#

chatmaild/src/chatmaild/lastlogin.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ def handle_set(self, addr, parts):
1313
keyname = parts[1].split("/")
1414
value = parts[2] if len(parts) > 2 else ""
1515
if keyname[0] == "shared" and keyname[1] == "last-login":
16-
if addr.startswith("echo@"):
17-
return True
1816
addr = keyname[2]
1917
timestamp = int(value)
2018
user = self.config.get_user(addr)

chatmaild/src/chatmaild/tests/test_expire.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,17 @@
1717
from chatmaild.fsreport import main as report_main
1818

1919

20-
def fill_mbox(basedir):
21-
basedir1 = basedir.joinpath("mailbox1@example.org")
22-
basedir1.mkdir()
23-
password = basedir1.joinpath("password")
20+
def fill_mbox(folderdir):
21+
password = folderdir.joinpath("password")
2422
password.write_text("xxx")
25-
basedir1.joinpath("maildirsize").write_text("xxx")
23+
folderdir.joinpath("maildirsize").write_text("xxx")
2624

27-
garbagedir = basedir1.joinpath("garbagedir")
25+
garbagedir = folderdir.joinpath("garbagedir")
2826
garbagedir.mkdir()
27+
garbagedir.joinpath("bimbum").write_text("hello")
2928

30-
create_new_messages(basedir1, ["cur/msg1"], size=500)
31-
create_new_messages(basedir1, ["new/msg2"], size=600)
32-
return basedir1
29+
create_new_messages(folderdir, ["cur/msg1"], size=500)
30+
create_new_messages(folderdir, ["new/msg2"], size=600)
3331

3432

3533
def create_new_messages(basedir, relpaths, size=1000, days=0):
@@ -45,8 +43,21 @@ def create_new_messages(basedir, relpaths, size=1000, days=0):
4543

4644
@pytest.fixture
4745
def mbox1(example_config):
48-
basedir1 = fill_mbox(example_config.mailboxes_dir)
49-
return MailboxStat(basedir1)
46+
mboxdir = example_config.mailboxes_dir.joinpath("mailbox1@example.org")
47+
mboxdir.mkdir()
48+
fill_mbox(mboxdir)
49+
return MailboxStat(mboxdir)
50+
51+
52+
def test_deltachat_folder(example_config):
53+
"""Test old setups that might have a .DeltaChat folder where messages also need to get removed."""
54+
mboxdir = example_config.mailboxes_dir.joinpath("mailbox1@example.org")
55+
mboxdir.mkdir()
56+
mbox2dir = mboxdir.joinpath(".DeltaChat")
57+
mbox2dir.mkdir()
58+
fill_mbox(mbox2dir)
59+
mb = MailboxStat(mboxdir)
60+
assert len(mb.messages) == 2
5061

5162

5263
def test_filentry_ordering(tmp_path):
@@ -76,7 +87,7 @@ def test_stats_mailbox(mbox1):
7687
create_new_messages(mbox1.basedir, ["large-extra"], size=1000)
7788
create_new_messages(mbox1.basedir, ["index-something"], size=3)
7889
mbox2 = MailboxStat(mbox1.basedir)
79-
assert len(mbox2.extrafiles) == 4
90+
assert len(mbox2.extrafiles) == 5
8091
assert mbox2.extrafiles[0].size == 1000
8192

8293
# cope well with mailbox dirs that have no password (for whatever reason)

chatmaild/src/chatmaild/user.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def __init__(self, maildir, addr, password_path, uid, gid):
1919

2020
@property
2121
def can_track(self):
22-
return "@" in self.addr and not self.addr.startswith("echo@")
22+
return "@" in self.addr
2323

2424
def get_userdb_dict(self):
2525
"""Return a non-empty dovecot 'userdb' style dict
@@ -55,10 +55,8 @@ def set_password(self, enc_password):
5555
try:
5656
write_bytes_atomic(self.password_path, password)
5757
except PermissionError:
58-
if not self.addr.startswith("echo@"):
59-
logging.error(f"could not write password for: {self.addr}")
60-
raise
61-
# if not self.addr.startswith("echo@"):
58+
logging.error(f"could not write password for: {self.addr}")
59+
raise
6260
self.enforce_E2EE_path.touch()
6361

6462
def set_last_login_timestamp(self, timestamp):

0 commit comments

Comments
 (0)