Compare commits

..

1 Commits

Author SHA1 Message Date
link2xt 221f5ed10e fix: pin chatmail core to 2.49.0
2.50.0 has delete_server_after config removed,
but it is still used in the tests.
2026-05-28 16:17:11 +02:00
14 changed files with 47 additions and 126 deletions
+2 -2
View File
@@ -20,9 +20,9 @@ concurrency:
jobs:
no-dns:
name: LXC deploy and test
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@main
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@v0.14.6
with:
cmlxc_version: main
cmlxc_version: v0.14.6
cmlxc_commands: |
cmlxc init
# single cmdeploy relay test
+3 -3
View File
@@ -29,7 +29,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- name: download filtermail
run: curl -L https://github.com/chatmail/filtermail/releases/download/v0.7.0/filtermail-x86_64 -o /usr/local/bin/filtermail && chmod +x /usr/local/bin/filtermail
run: curl -L https://github.com/chatmail/filtermail/releases/download/v0.6.6/filtermail-x86_64 -o /usr/local/bin/filtermail && chmod +x /usr/local/bin/filtermail
- name: run chatmaild tests
working-directory: chatmaild
run: pipx run tox
@@ -57,9 +57,9 @@ jobs:
lxc-test:
name: LXC deploy and test
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@main
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@v0.14.6
with:
cmlxc_version: main
cmlxc_version: v0.14.6
cmlxc_commands: |
cmlxc init
# single cmdeploy relay test
+1
View File
@@ -10,6 +10,7 @@ dependencies = [
"filelock",
"requests",
"crypt-r >= 3.13.1 ; python_version >= '3.11'",
"domain-validator",
]
[tool.setuptools]
+2
View File
@@ -2,6 +2,7 @@ import ipaddress
from pathlib import Path
import iniconfig
from domain_validator import DomainValidator
from chatmaild.user import User
@@ -24,6 +25,7 @@ class Config:
self.mail_domain = f"[{raw_domain}]"
self.postfix_myhostname = ipaddress.IPv4Address(raw_domain).reverse_pointer
else:
DomainValidator().validate_domain_re(raw_domain)
self.ipv4_relay = None
self.mail_domain = raw_domain
self.postfix_myhostname = raw_domain
-10
View File
@@ -168,16 +168,6 @@ class Expiry:
if mbox.last_login and mbox.last_login < cutoff_without_login:
self.remove_mailbox(mbox.basedir)
return
elif mbox.last_login is None:
try:
if not self.dry:
os.rmdir(mbox.basedir)
self.del_mboxes += 1
except OSError:
print_info(
f"Skipped deleting {mbox.basedir}, doesn't have last_login but isn't empty"
)
return
mboxname = os.path.basename(mbox.basedir)
if self.verbose:
@@ -1,7 +1,6 @@
import itertools
import os
import random
import shutil
import time
from datetime import datetime
from fnmatch import fnmatch
@@ -10,7 +9,6 @@ from pathlib import Path
import pytest
from chatmaild.expire import (
Expiry,
FileEntry,
MailboxStat,
expire_to_target,
@@ -106,32 +104,6 @@ def test_stats_mailbox(mbox1):
assert mbox3.last_login is None
def test_mbox_without_password(mbox1, example_config, capsys):
password = Path(mbox1.basedir).joinpath("password")
os.remove(password)
mbox_rescan = MailboxStat(mbox1.basedir)
assert mbox_rescan.last_login is None
exp = Expiry(
example_config, dry=False, now=datetime.now().timestamp(), verbose=False
)
exp.process_mailbox_stat(mbox_rescan)
out, err = capsys.readouterr()
assert "doesn't have last_login but isn't empty" in err
assert os.path.isdir(mbox_rescan.basedir)
for entry in os.scandir(mbox_rescan.basedir):
if os.path.isdir(entry):
shutil.rmtree(entry)
else:
os.remove(entry)
exp.process_mailbox_stat(mbox_rescan)
out, err = capsys.readouterr()
assert "doesn't have last_login but isn't empty" not in err
assert not os.path.isdir(mbox_rescan.basedir)
def test_report_no_mailboxes(example_config):
args = (str(example_config._inipath),)
report_main(args)
+2 -2
View File
@@ -19,8 +19,8 @@ dependencies = [
"pytest-xdist",
"execnet",
"imap_tools",
"deltachat-rpc-client",
"deltachat-rpc-server",
"deltachat-rpc-client==2.49.0",
"deltachat-rpc-server==2.49.0",
]
[project.scripts]
@@ -1,2 +1,2 @@
"acme-enter-email": "{{ email }}"
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.7-June-04-2026.pdf": true
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.6-August-18-2025.pdf": true
+1 -2
View File
@@ -166,7 +166,7 @@ class Deployer:
return self.put_template(src, dest, **kwargs)
return self.put_file(src, dest)
def put_file(self, src, dest, mode="644", **kwargs):
def put_file(self, src, dest, mode="644"):
if isinstance(src, str):
src = get_resource(src)
res = files.put(
@@ -176,7 +176,6 @@ class Deployer:
user="root",
group="root",
mode=mode,
**kwargs,
)
return self._update_restart_signals(dest, res)
+10 -11
View File
@@ -164,7 +164,6 @@ class UnboundDeployer(Deployer):
self.put_file(
src=BytesIO(b"nameserver 127.0.0.1\nnameserver 9.9.9.9\n"),
dest="/etc/resolv.conf",
force=True,
)
server.shell(
name="Generate root keys for validating DNSSEC",
@@ -172,14 +171,16 @@ class UnboundDeployer(Deployer):
"unbound-anchor -a /var/lib/unbound/root.key || true",
],
)
self.ensure_directory(
path="/etc/unbound/unbound.conf.d",
)
self.put_template(
"unbound/unbound.conf.j2",
"/etc/unbound/unbound.conf.d/chatmail.conf",
disable_ipv6=self.config.disable_ipv6,
)
if self.config.disable_ipv6:
self.ensure_directory(
path="/etc/unbound/unbound.conf.d",
)
self.put_template(
"unbound/unbound.conf.j2",
"/etc/unbound/unbound.conf.d/chatmail.conf",
)
else:
self.remove_file("/etc/unbound/unbound.conf.d/chatmail.conf")
def activate(self):
server.shell(
@@ -513,8 +514,6 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
(["master", "smtpd"], config.postfix_reinject_port_incoming),
("filtermail", config.filtermail_smtp_port),
("filtermail", config.filtermail_smtp_port_incoming),
("filtermail", config.filtermail_http_port_incoming),
("filtermail", config.filtermail_lmtp_port_transport),
]
for service, port in port_services:
print(f"Checking if port {port} is available for {service}...")
+3 -3
View File
@@ -20,10 +20,10 @@ class FiltermailDeployer(Deployer):
return
arch = host.get_fact(facts.server.Arch)
url = f"https://github.com/chatmail/filtermail/releases/download/v0.7.0/filtermail-{arch}"
url = f"https://github.com/chatmail/filtermail/releases/download/v0.6.6/filtermail-{arch}"
sha256sum = {
"x86_64": "451f295a85b3b12dbb0f89e18ec319f742ee46dec218f20f7923bfb017a248bd",
"aarch64": "6833061b2a2028264fdeb32f0a6123e1ff73de57dace125364016300b748452e",
"x86_64": "05c7e7ac244606c2eeb275f2d282ffdbc2403e0169f1cdd3110ffcebdb994a92",
"aarch64": "8cf8bbda4d907beca547b365cc7e6753532a74b1712492d0d2f3d2d8a553fb3d",
}[arch]
self.download_executable(url, self.bin_path, sha256sum)
+6 -4
View File
@@ -10,11 +10,13 @@ from pathlib import Path
import pytest
from chatmaild.config import is_valid_ipv4, read_config
from domain_validator import DomainValidator
def format_mail_domain(raw_domain: str) -> str:
if is_valid_ipv4(raw_domain):
return f"[{raw_domain}]"
DomainValidator().validate_domain_re(raw_domain)
return raw_domain
@@ -347,9 +349,9 @@ class ChatmailACFactory:
qr = (
f"dclogin:{addr}"
f"?p={password}&v=1"
f"&ih={domain}&ip=993&is=ssl"
f"&sh={domain}&sp=465&ss=ssl"
f"&ic=3"
f"&ih={domain}&ip=993"
f"&sh={domain}&sp=465"
f"&ic=3&ss=default"
)
future = account.add_transport_from_qr.future(qr)
else:
@@ -360,7 +362,7 @@ class ChatmailACFactory:
# ensure messages stay in INBOX so that they can be
# concurrently fetched via extra IMAP connections during tests
account.set_config("bcc_self", "1")
account.set_config("delete_server_after", "10")
accounts.append(account)
for future in futures:
@@ -1,7 +1,4 @@
# Managed by cmdeploy
# Managed by cmdeploy: disable IPv6 in unbound.
server:
{% if disable_ipv6 %}
interface: 127.0.0.1
do-ip6: no
{% endif %}
cache-max-negative-ttl: 0
+15 -56
View File
@@ -156,7 +156,6 @@ Chatmail relay dependency diagram
postfix --- |10083|filtermail-transport;
filtermail-outgoing --- |10025 reinject|postfix;
filtermail-incoming --- |10026 reinject|postfix;
postfix --- |milter opendkim.sock|OpenDKIM
dovecot --- |doveauth.socket|doveauth;
dovecot --- |message delivery|maildir["maildir
/home/vmail/.../user"];
@@ -180,66 +179,26 @@ Chatmail relay dependency diagram
style nginx-right fill:#f66;
style postfix fill:#f66;
style dovecot fill:#f66;
style OpenDKIM fill:#f66;
style notification-proxy fill:#f66;
Accepting and delivering mail
-----------------------------
Message between users on the same relay
---------------------------------------
.. mermaid::
:caption: This diagram shows all the paths a message can take.
:caption: This diagram shows the path a non-federated message takes.
flowchart LR
subgraph chatmail relay
subgraph postfix
qmgr .-> lmtp-filtermail["lmtp/lmtp-filtermail (default_transport)"]
qmgr .-> lmtp["lmtp (local_transport)"]
lmtp --> cleanup["cleanup (lmtp_header_cleanup)"]
bounce
smtpd-submission["smtpd/submission"]
smtpd-smtps["smtpd/smtps"]
smtpd-reinject-outgoing["smtpd/reinject-outgoing"] --> authclean["cleanup/authclean (submission_header_cleanup)"]
authclean --> qmgr
smtpd-smtp["smtpd/smtp"]
smtpd-reinject-incoming["smtpd/reinject-incoming"] --> qmgr
end
lmtp-filtermail --LMTP inet:10083--> filtermail-transport
cleanup --LMTP unix:private/dovecot-lmtp --> dovecot
dovecot --> maildir
smtpd-submission --SMTP inet:10080--> filtermail-outgoing
smtpd-smtps --SMTP inet:10080--> filtermail-outgoing
filtermail-outgoing --SMTP inet:10025--> smtpd-reinject-outgoing
open-dkim["OpenDKIM (signing only)"] <--milter unix:opendkim/opendkim.sock--> smtpd-reinject-outgoing
bounce <--milter unix:opendkim/opendkim.sock--> open-dkim
bounce --> qmgr
nginx
smtpd-smtp -.SMTP inet:10081.-> filtermail-incoming
nginx -.HTTP inet:10082.-> filtermail-incoming
filtermail-incoming --SMTP inet:10026--> smtpd-reinject-incoming
end
filtermail-transport -.SMTP inet:25.-> mta1[Remote relay]
filtermail-transport -.HTTPS /mxdeliv.-> mta1
client[Client] -.SMTP inet:587.-> smtpd-submission
client -.SMTP inet:465.-> smtpd-smtps
client -.SMTP inet:443.-> nginx
nginx -.SMTP inet:465.-> smtpd-smtps
mta2[Remote relay] -.SMTP inet:25.-> smtpd-smtp
mta2 -.HTTPS /mxdeliv.-> nginx
style postfix fill:#363
style qmgr fill:#252
style authclean fill:#252
style cleanup fill:#252
style lmtp-filtermail fill:#252
style lmtp fill:#252
style bounce fill:#252
style smtpd-submission fill:#252
style smtpd-smtps fill:#252
style smtpd-reinject-outgoing fill:#252
style smtpd-reinject-incoming fill:#252
style smtpd-smtp fill:#252
style filtermail-outgoing fill:#225
style filtermail-incoming fill:#225
style filtermail-transport fill:#225
graph LR;
sender --> |465|smtps/smtpd;
sender --> |587|submission/smtpd;
smtps/smtpd --> |10080|filtermail;
submission/smtpd --> |10080|filtermail;
filtermail --> |10025|smtpd_reinject;
smtpd_reinject --> cleanup;
cleanup --> qmgr;
qmgr --> smtpd_accepts_message;
qmgr --> |lmtp|dovecot;
dovecot --> recipient;
dovecot --> sender's_other_devices;
Operational details of a chatmail relay
----------------------------------------