mirror of
https://github.com/chatmail/relay.git
synced 2026-08-11 19:10:52 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fa92db546 | |||
| 5f6c976ce4 | |||
| dc8e0a34a2 | |||
| efc24fcdf3 | |||
| 9a9bda80b1 | |||
| 74f4721f2b |
@@ -20,8 +20,6 @@ concurrency:
|
||||
jobs:
|
||||
no-dns:
|
||||
name: LXC deploy and test
|
||||
permissions:
|
||||
contents: read
|
||||
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@main
|
||||
with:
|
||||
cmlxc_version: main
|
||||
|
||||
@@ -57,8 +57,6 @@ jobs:
|
||||
|
||||
lxc-test:
|
||||
name: LXC deploy and test
|
||||
permissions:
|
||||
contents: read
|
||||
uses: chatmail/cmlxc/.github/workflows/lxc-test.yml@main
|
||||
with:
|
||||
cmlxc_version: main
|
||||
|
||||
@@ -47,5 +47,5 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.CHATMAIL_STAGING_SSHKEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -rILvh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/doc/build/ "${{ secrets.USERNAME }}@chatmail.at:/var/www/html/chatmail.at/doc/relay/"
|
||||
rsync -rILvh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/doc/build/ "${{ secrets.USERNAME }}@chatmail.at:"
|
||||
|
||||
|
||||
@@ -1,5 +1,60 @@
|
||||
# Changelog for chatmail deployment
|
||||
|
||||
## [1.12.0] - 2026-07-31
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [**breaking**] Introduce configurable system limits to reject new address creation and limit imap/smtp connections.
|
||||
Dovecot default connection limit lowered from 50k to 10k,
|
||||
Postfix default connection limit lowered from 5k to 1k,
|
||||
larger relays need to adjust their settings.
|
||||
|
||||
### Features
|
||||
|
||||
- Reduce maximal_queue_lifetime from 5d to 2d
|
||||
- Disable negative cache in unbound (#992)
|
||||
- *(mtail)* Add incoming_mailer_daemon_mail_count
|
||||
- *(postfix)* Disable processing of MIME headers
|
||||
- *(dovecot)* Advertise privacy_mail as admin contact, drop server comment
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Set relay restrictions per smtpd service with default reject
|
||||
- Reduce maxproc for filtermail-transport LMTP client to 500
|
||||
- Core 2.50.0 does not have delete_server_after config anymore.
|
||||
- Check if all required ports are available for filtermail (#983)
|
||||
- Always deploy unbound.conf.d/chatmail.conf (#993)
|
||||
- Expire empty directories (#994)
|
||||
- Crypt-r dependency was declared for wrong Python version
|
||||
- Always overwrite /etc/resolv.conf, even if it is a symbolic link
|
||||
- Pass kwargs to files.put()
|
||||
- List Iroh proxy endpoints used by 0.35 and 1.0, drop stale /relay/probe from earlier versions
|
||||
- Fix port discovery when ss -tulpn shows dovecot before stats
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add scripts/initenv.sh to upgrade instructions
|
||||
- Update overview diagrams (#995)
|
||||
- *(overview)* Remove mermaid styles from 'Accepting and delivering mail' (#1009)
|
||||
- *(README.md)* Clarify security enforcement (#1011)
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- *(ci)* Auto-trigger docker build on release tag push
|
||||
- *(acmetool)* Update let's encrypt ToS link to 1.8
|
||||
- *(ci)* Update doc staging upload path
|
||||
- *(ci)* Fix docs upload path
|
||||
|
||||
### Refactor
|
||||
|
||||
- *(postfix)* Remove unused "filter" lmtp service
|
||||
- Install dns-root-data instead of using unbound-anchor
|
||||
- *(deps)* Remove domain-validator dependency
|
||||
|
||||
### Testing
|
||||
|
||||
- Set socket security for IMAP and SMTP to "TLS" in "dclogin"
|
||||
|
||||
## [1.11.0] - 2026-05-15
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
+4
-6
@@ -1,15 +1,13 @@
|
||||
# Releasing a new version of chatmail relay
|
||||
|
||||
For example, to release version 1.9.0 of chatmail relay, do the following steps.
|
||||
For example, to release version 1.13.0 of chatmail relay, do the following steps.
|
||||
|
||||
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`.
|
||||
1. Update the changelog: `git cliff --unreleased --tag 1.13.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.13.0 -p CHANGELOG.md`.
|
||||
|
||||
2. Open the changelog in the editor, edit it if required.
|
||||
|
||||
3. Commit the changes to the changelog with a commit message `chore(release): prepare for 1.9.0`.
|
||||
|
||||
3. Tag the release: `git tag --annotate 1.9.0`.
|
||||
4. Open a PR with the new commit, merge it to main after review.
|
||||
|
||||
4. Push the release tag: `git push origin 1.9.0`.
|
||||
|
||||
5. Create a GitHub release: `gh release create 1.9.0`.
|
||||
5. In the web interface, create a GitHub release, tell it to create a new tag.
|
||||
|
||||
@@ -32,7 +32,6 @@ from .external.deployer import ExternalTlsDeployer
|
||||
from .filtermail.deployer import FiltermailDeployer
|
||||
from .mtail.deployer import MtailDeployer
|
||||
from .nginx.deployer import NginxDeployer
|
||||
from .websockify.deployer import WebsockifyDeployer
|
||||
from .opendkim.deployer import OpendkimDeployer
|
||||
from .postfix.deployer import PostfixDeployer
|
||||
from .selfsigned.deployer import SelfSignedTlsDeployer
|
||||
@@ -521,10 +520,10 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
|
||||
("nginx", 443),
|
||||
(["master", "smtpd"], 465),
|
||||
(["master", "smtpd"], 587),
|
||||
(["imap-login", "dovecot"], 993),
|
||||
(["dovecot", "imap-login"], 993),
|
||||
("iroh-relay", 3340),
|
||||
("mtail", 3903),
|
||||
("stats", 3904),
|
||||
(["dovecot", "stats"], 3904),
|
||||
("nginx", 8443),
|
||||
(["master", "smtpd"], config.postfix_reinject_port),
|
||||
(["master", "smtpd"], config.postfix_reinject_port_incoming),
|
||||
@@ -566,7 +565,6 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
|
||||
PostfixDeployer(config, disable_mail),
|
||||
FcgiwrapDeployer(),
|
||||
NginxDeployer(config),
|
||||
WebsockifyDeployer(config),
|
||||
MtailDeployer(config.mtail_address),
|
||||
GithashDeployer(),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from pyinfra import facts, host
|
||||
from pyinfra.operations import apt
|
||||
from pyinfra.operations import apt, server
|
||||
|
||||
from cmdeploy.basedeploy import Deployer
|
||||
|
||||
@@ -37,7 +37,20 @@ class MtailDeployer(Deployer):
|
||||
address=self.mtail_address or "127.0.0.1",
|
||||
port=3903,
|
||||
)
|
||||
self.put_file("mtail/delivered_mail.mtail", "/etc/mtail/delivered_mail.mtail")
|
||||
if self.mtail_address:
|
||||
self.put_file("mtail/delivered_mail.mtail", "/etc/mtail/delivered_mail.mtail")
|
||||
self.put_file("mtail/filtermail.mtail", "/etc/mtail/filtermail.mtail")
|
||||
if self.need_restart:
|
||||
# Check if all installed mtail rules compile or fail early
|
||||
# --one_shot to exit, --port 0 to not clash with running mtail.
|
||||
server.shell(
|
||||
name="Validate mtail programs",
|
||||
commands=[
|
||||
"timeout 30 /usr/local/bin/mtail --compile_only --one_shot"
|
||||
" --progs /etc/mtail --logs /dev/null"
|
||||
" --address 127.0.0.1 --port 0"
|
||||
],
|
||||
)
|
||||
|
||||
def activate(self):
|
||||
active = bool(self.mtail_address)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# filtermail.mtail: filtermail process event counters
|
||||
|
||||
#
|
||||
# Counters
|
||||
#
|
||||
|
||||
# Connection errors in the transport path
|
||||
# Error::Io / Error::ConnectionFailed / Error::Tls: the reason string filtermail strips
|
||||
counter filtermail_transport_error_total by reason
|
||||
|
||||
# Unexpected transport errors: DNS failure, HTTP error, config error.
|
||||
counter filtermail_transport_unexpected_total
|
||||
|
||||
# Silent drops: inbound
|
||||
# "unencrypted" overlaps with rejected_unencrypted_mail_count in delivered_mail.mtail.
|
||||
counter filtermail_inbound_drop_total by reason
|
||||
|
||||
# Silent drops: outbound
|
||||
# "unencrypted" overlaps with rejected_unencrypted_mail_count in delivered_mail.mtail;
|
||||
# "sender_disabled" and "all_recipients_disabled" are new.
|
||||
counter filtermail_outbound_drop_total by reason
|
||||
|
||||
# Reinject failures
|
||||
counter filtermail_reinject_error_total by direction
|
||||
|
||||
# SMTP-level connection errors (client dropped mid-session)
|
||||
counter filtermail_smtp_error_total by reason
|
||||
|
||||
#
|
||||
# filtermail::transport
|
||||
#
|
||||
|
||||
/filtermail\[\d+\]: \[WARN\s+filtermail::transport::worker\] Connection error relaying to mail server \S+: / {
|
||||
/timed out/ {
|
||||
filtermail_transport_error_total["connection_timeout"]++
|
||||
} otherwise {
|
||||
/Failed to connect to any of the following addresses/ {
|
||||
filtermail_transport_error_total["connection_refused"]++
|
||||
} otherwise {
|
||||
/Connection refused/ {
|
||||
filtermail_transport_error_total["connection_refused"]++
|
||||
} otherwise {
|
||||
/invalid certificate/ {
|
||||
filtermail_transport_error_total["tls_cert_not_verified"]++
|
||||
} otherwise {
|
||||
/peer sent fatal alert|handshake/ {
|
||||
filtermail_transport_error_total["tls_handshake"]++
|
||||
} otherwise {
|
||||
/close_notify|connection closed/ {
|
||||
filtermail_transport_error_total["lost_connection"]++
|
||||
} otherwise {
|
||||
filtermail_transport_error_total["other"]++
|
||||
}}}}}}}
|
||||
|
||||
/filtermail\[\d+\]: \[WARN\s+filtermail::transport::worker\] Unexpected error while delivering/ {
|
||||
filtermail_transport_unexpected_total++
|
||||
}
|
||||
|
||||
#
|
||||
# filtermail::smtp_server
|
||||
#
|
||||
|
||||
/filtermail(?:-incoming)?\[\d+\]: \[WARN\s+filtermail::smtp_server\] Unexpected EoF while receiving DATA/ {
|
||||
filtermail_smtp_error_total["unexpected_eof"]++
|
||||
}
|
||||
|
||||
/filtermail(?:-incoming)?\[\d+\]: \[WARN\s+filtermail::smtp_server\] Malformed DATA line without CRLF/ {
|
||||
filtermail_smtp_error_total["malformed_data_line"]++
|
||||
}
|
||||
|
||||
#
|
||||
# filtermail::outbound
|
||||
#
|
||||
|
||||
/filtermail\[\d+\]: \[WARN\s+filtermail::outbound\] Rejected unencrypted mail/ {
|
||||
filtermail_outbound_drop_total["unencrypted"]++
|
||||
}
|
||||
|
||||
/filtermail\[\d+\]: \[WARN\s+filtermail::outbound\] Dropping mail; Sender .* is disabled/ {
|
||||
filtermail_outbound_drop_total["sender_disabled"]++
|
||||
}
|
||||
|
||||
/filtermail\[\d+\]: \[WARN\s+filtermail::outbound\] Dropping mail; All recipients disabled/ {
|
||||
filtermail_outbound_drop_total["all_recipients_disabled"]++
|
||||
}
|
||||
|
||||
/filtermail\[\d+\]: \[WARN\s+filtermail::outbound\] Failed to re.inject mail/ {
|
||||
filtermail_reinject_error_total["outbound"]++
|
||||
}
|
||||
|
||||
#
|
||||
# filtermail::inbound
|
||||
#
|
||||
|
||||
/filtermail(?:-incoming)?\[\d+\]: \[WARN\s+filtermail::inbound\] Rejected unencrypted mail/ {
|
||||
filtermail_inbound_drop_total["unencrypted"]++
|
||||
}
|
||||
|
||||
/filtermail(?:-incoming)?\[\d+\]: \[WARN\s+filtermail::inbound\] Failed to re.inject mail/ {
|
||||
filtermail_reinject_error_total["inbound"]++
|
||||
}
|
||||
@@ -140,20 +140,6 @@ http {
|
||||
proxy_pass http://127.0.0.1:3340;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
location /imap {
|
||||
proxy_pass http://127.0.0.1:8143;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
location /smtp {
|
||||
proxy_pass http://127.0.0.1:8587;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
|
||||
# Redirect www. to non-www
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from pyinfra.operations import apt, server
|
||||
|
||||
from cmdeploy.basedeploy import Deployer
|
||||
|
||||
|
||||
class WebsockifyDeployer(Deployer):
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def install(self):
|
||||
apt.packages(name="Install websockify", packages=["websockify"])
|
||||
|
||||
def configure(self):
|
||||
self.ensure_systemd_unit("websockify/websockify-imap.service")
|
||||
self.ensure_systemd_unit("websockify/websockify-submission.service")
|
||||
|
||||
def activate(self):
|
||||
self.ensure_service("websockify-imap.service")
|
||||
self.ensure_service("websockify-submission.service")
|
||||
@@ -1,12 +0,0 @@
|
||||
[Unit]
|
||||
Description=WebSocket proxy for IMAP
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/websockify 127.0.0.1:8143 localhost:143
|
||||
DynamicUser=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,12 +0,0 @@
|
||||
[Unit]
|
||||
Description=WebSocket proxy for SMTP
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/websockify 127.0.0.1:8587 localhost:587
|
||||
DynamicUser=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user