mirror of
https://github.com/chatmail/relay.git
synced 2026-08-10 10:30:51 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b8dcdcba91 | |||
| 6a0bf5abe7 | |||
| e7829672f8 | |||
| 62d19fd910 | |||
| e1a8d798d3 |
@@ -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.4/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.7.0/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
|
||||
|
||||
@@ -51,7 +51,7 @@ 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:${STEPS_PREPARE_OUTPUTS_PRID}/"
|
||||
rsync -rILvh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/doc/build/ "${{ secrets.USERNAME }}@chatmail.at:/var/www/html/staging.chatmail.at/doc/relay/${STEPS_PREPARE_OUTPUTS_PRID}/"
|
||||
env:
|
||||
STEPS_PREPARE_OUTPUTS_PRID: ${{ steps.prepare.outputs.prid }}
|
||||
|
||||
|
||||
@@ -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:"
|
||||
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/"
|
||||
|
||||
|
||||
@@ -1,60 +1,5 @@
|
||||
# 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
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
|
||||
# Chatmail relays for end-to-end encrypted email
|
||||
|
||||
Chatmail relay servers are interoperable Mail Transport Agents (MTAs) designed for:
|
||||
Chatmail relay servers are interoperable Mail Transport Agents (MTAs) designed for:
|
||||
|
||||
- **Zero State:** no private data or metadata collected, messages are auto-deleted, low disk usage
|
||||
|
||||
- **Instant/Realtime:** sub-second message delivery, realtime P2P
|
||||
streaming, privacy-preserving Push Notifications for Apple, Google, and Huawei;
|
||||
|
||||
- **Security Enforcement**: Only connections with strict TLS are accepted;
|
||||
all messages must be correctly signed with DKIM and OpenPGP-encrypted with minimized metadata.
|
||||
There are experimental exceptions for no-DNS relays,
|
||||
which are allowed use self-signed TLS certificates
|
||||
and which do not need to DKIM-sign their messages.
|
||||
Unencrypted messages are allowed in neither case.
|
||||
- **Security Enforcement**: only strict TLS, DKIM and OpenPGP with minimized metadata accepted
|
||||
|
||||
- **Reliable Federation and Decentralization:** No spam or IP reputation checks, federating
|
||||
depends on established IETF standards and protocols.
|
||||
|
||||
This repository contains everything needed to setup a ready-to-use chatmail relay on an ssh-reachable host.
|
||||
This repository contains everything needed to setup a ready-to-use chatmail relay on an ssh-reachable host.
|
||||
For getting started and more information please refer to the web version of this repositories' documentation at
|
||||
|
||||
[https://chatmail.at/doc/relay](https://chatmail.at/doc/relay)
|
||||
|
||||
+6
-4
@@ -1,13 +1,15 @@
|
||||
# Releasing a new version of chatmail relay
|
||||
|
||||
For example, to release version 1.13.0 of chatmail relay, do the following steps.
|
||||
For example, to release version 1.9.0 of chatmail relay, do the following steps.
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
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`.
|
||||
|
||||
4. Open a PR with the new commit, merge it to main after review.
|
||||
3. Tag the release: `git tag --annotate 1.9.0`.
|
||||
|
||||
5. In the web interface, create a GitHub release, tell it to create a new tag.
|
||||
4. Push the release tag: `git push origin 1.9.0`.
|
||||
|
||||
5. Create a GitHub release: `gh release create 1.9.0`.
|
||||
|
||||
@@ -8,9 +8,9 @@ version = "0.3"
|
||||
dependencies = [
|
||||
"iniconfig",
|
||||
"filelock",
|
||||
"psutil",
|
||||
"requests",
|
||||
"crypt-r >= 3.13.1 ; python_version >= '3.13'",
|
||||
"crypt-r >= 3.13.1 ; python_version >= '3.11'",
|
||||
"domain-validator",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@@ -2,6 +2,7 @@ import ipaddress
|
||||
from pathlib import Path
|
||||
|
||||
import iniconfig
|
||||
from domain_validator import DomainValidator
|
||||
|
||||
from chatmaild.user import User
|
||||
|
||||
@@ -18,12 +19,14 @@ class Config:
|
||||
params = dict(params)
|
||||
raw_domain = params.pop("mail_domain")
|
||||
self.mail_domain_bare = raw_domain
|
||||
self.ssh_host = params.pop("ssh_host", raw_domain)
|
||||
|
||||
if is_valid_ipv4(raw_domain):
|
||||
self.ipv4_relay = raw_domain
|
||||
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
|
||||
@@ -75,16 +78,6 @@ class Config:
|
||||
self.privacy_pdo = params.pop("privacy_pdo", None)
|
||||
self.privacy_supervisor = params.pop("privacy_supervisor", None)
|
||||
|
||||
self.max_load_1m = float(params.pop("max_load_1m", 5))
|
||||
self.min_available_memory_mb = parse_size_mb(
|
||||
params.pop("min_available_memory", "200M")
|
||||
)
|
||||
self.min_free_disk_space_mb = parse_size_mb(
|
||||
params.pop("min_free_disk_space", "1G")
|
||||
)
|
||||
self.max_imap_connections = int(params.pop("max_imap_connections", 10000))
|
||||
self.max_smtp_connections = int(params.pop("max_smtp_connections", 1000))
|
||||
|
||||
# TLS certificate management.
|
||||
# If tls_external_cert_and_key is set, use externally managed certs.
|
||||
# Otherwise derived from the domain name:
|
||||
|
||||
@@ -14,7 +14,6 @@ except ImportError:
|
||||
from .config import Config, read_config
|
||||
from .dictproxy import DictProxy
|
||||
from .migrate_db import migrate_from_db_to_maildir
|
||||
from .syslimits import has_sufficient_resources
|
||||
|
||||
NOCREATE_FILE = "/etc/chatmail-nocreate"
|
||||
VALID_LOCALPART_RE = re.compile(r"^[a-z0-9._-]+$")
|
||||
@@ -148,8 +147,6 @@ class AuthDictProxy(DictProxy):
|
||||
return userdata
|
||||
if not is_allowed_to_create(self.config, addr, cleartext_password):
|
||||
return
|
||||
if not has_sufficient_resources(self.config):
|
||||
return
|
||||
|
||||
lock = filelock.FileLock(str(user.password_path) + ".lock", timeout=5)
|
||||
with lock:
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
# mail domain (MUST be set to fully qualified chat mail domain)
|
||||
mail_domain = {mail_domain}
|
||||
|
||||
# Where to deploy the relay - if unspecified, mail_domain will be used.
|
||||
ssh_host = localhost
|
||||
|
||||
#
|
||||
# If you only do private test deploys, you don't need to modify any settings below
|
||||
#
|
||||
@@ -42,33 +45,6 @@ mail_domain = {mail_domain}
|
||||
# minimum length a password must have
|
||||
#password_min_length = 9
|
||||
|
||||
#
|
||||
# System resource limits
|
||||
#
|
||||
|
||||
# The following three limits refuse creation of new addresses
|
||||
# while existing addresses keep working.
|
||||
# Rejections are logged by the doveauth service.
|
||||
|
||||
# Maximum 1-minute load average, as reported by "uptime";
|
||||
# it counts processes waiting for disk I/O as well as for CPU.
|
||||
#max_load_1m = 5
|
||||
|
||||
# Minimum memory available without swapping.
|
||||
#min_available_memory = 200M
|
||||
|
||||
# Minimum free disk space on the file system holding the mailboxes.
|
||||
#min_free_disk_space = 1G
|
||||
|
||||
# Maximum number of concurrent IMAP connections
|
||||
# (the Dovecot imap process limit).
|
||||
#max_imap_connections = 10000
|
||||
|
||||
# Maximum number of concurrent SMTP connections
|
||||
# on each of the submission and smtps ports (the Postfix process limit).
|
||||
# A single client IP may use up to a fifth of this.
|
||||
#max_smtp_connections = 1000
|
||||
|
||||
# Use externally managed TLS certificates instead of built-in acmetool.
|
||||
# Paths refer to files on the deployment server (not the build machine).
|
||||
# Both files must already exist before running cmdeploy.
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
"""Detect whether the system is at its limits."""
|
||||
|
||||
import logging
|
||||
|
||||
import psutil
|
||||
|
||||
MB = 1024 * 1024
|
||||
|
||||
|
||||
def read_value(getter):
|
||||
try:
|
||||
return getter()
|
||||
except Exception as e:
|
||||
logging.warning("ignoring unreadable system limit: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
def has_sufficient_resources(config):
|
||||
"""Return False if load, memory or disk exceeds a configured limit."""
|
||||
load = read_value(lambda: psutil.getloadavg()[0])
|
||||
mem = read_value(lambda: psutil.virtual_memory().available // MB)
|
||||
disk = read_value(lambda: psutil.disk_usage(str(config.mailboxes_dir)).free // MB)
|
||||
if load is not None and load > config.max_load_1m:
|
||||
msg = f"load avg {load:.2f} > {config.max_load_1m:.2f}"
|
||||
elif mem is not None and mem < config.min_available_memory_mb:
|
||||
msg = f"available memory {mem}MB < {config.min_available_memory_mb}MB"
|
||||
elif disk is not None and disk < config.min_free_disk_space_mb:
|
||||
msg = f"free disk {disk}MB < {config.min_free_disk_space_mb}MB"
|
||||
else:
|
||||
return True
|
||||
logging.warning("registration rejected: %s", msg)
|
||||
return False
|
||||
@@ -20,10 +20,6 @@ def make_config(tmp_path):
|
||||
basedir.mkdir(parents=True, exist_ok=True)
|
||||
overrides = settings.copy() if settings else {}
|
||||
overrides["mailboxes_dir"] = str(basedir)
|
||||
# permissive resource limits so tests never depend on host load/memory/disk
|
||||
overrides.setdefault("max_load_1m", "99999")
|
||||
overrides.setdefault("min_available_memory", "0")
|
||||
overrides.setdefault("min_free_disk_space", "0")
|
||||
write_initial_config(inipath, mail_domain, overrides=overrides)
|
||||
return read_config(inipath)
|
||||
|
||||
|
||||
@@ -45,8 +45,6 @@ def test_read_config_basic_using_defaults(tmp_path, maildomain):
|
||||
assert example_config.username_min_length == 9
|
||||
assert example_config.username_max_length == 9
|
||||
assert example_config.password_min_length == 9
|
||||
assert example_config.max_imap_connections == 10000
|
||||
assert example_config.max_smtp_connections == 1000
|
||||
assert example_config._unused_keys == []
|
||||
|
||||
|
||||
|
||||
@@ -202,17 +202,3 @@ def test_50_concurrent_lookups_different_accounts(gencreds, dictproxy):
|
||||
res = results.get()
|
||||
if res is not None:
|
||||
pytest.fail(f"concurrent lookup failed\n{res}")
|
||||
|
||||
|
||||
def test_insufficient_resources_block_creation_not_existing_logins(
|
||||
dictproxy, gencreds, monkeypatch
|
||||
):
|
||||
addr, password = gencreds()
|
||||
assert dictproxy.lookup_passdb(addr, password)
|
||||
|
||||
monkeypatch.setattr(
|
||||
chatmaild.doveauth, "has_sufficient_resources", lambda config: False
|
||||
)
|
||||
newaddr, newpassword = gencreds()
|
||||
assert not dictproxy.lookup_passdb(newaddr, newpassword)
|
||||
assert dictproxy.lookup_passdb(addr, password)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import shutil
|
||||
|
||||
import psutil
|
||||
|
||||
from chatmaild.syslimits import has_sufficient_resources
|
||||
|
||||
PERMISSIVE = {
|
||||
"max_load_1m": "99999",
|
||||
"min_available_memory": "0",
|
||||
"min_free_disk_space": "0",
|
||||
}
|
||||
|
||||
|
||||
def test_rejects_constrained_system(make_config, caplog):
|
||||
assert has_sufficient_resources(make_config("chat.example.org", PERMISSIVE))
|
||||
for settings in (
|
||||
{"max_load_1m": "-1.0"},
|
||||
{"min_available_memory": "99999999G"},
|
||||
{"min_free_disk_space": "99999999G"},
|
||||
):
|
||||
config = make_config("chat.example.org", PERMISSIVE | settings)
|
||||
caplog.clear()
|
||||
assert not has_sufficient_resources(config), settings
|
||||
assert "registration rejected" in caplog.text
|
||||
|
||||
|
||||
def test_unreadable_disk_does_not_reject(make_config, caplog):
|
||||
config = make_config(
|
||||
"chat.example.org", PERMISSIVE | {"min_free_disk_space": "99999999G"}
|
||||
)
|
||||
shutil.rmtree(config.mailboxes_dir)
|
||||
assert has_sufficient_resources(config)
|
||||
assert "ignoring" in caplog.text
|
||||
|
||||
|
||||
def test_one_unreadable_value_keeps_other_checks(make_config, monkeypatch, caplog):
|
||||
def raise_error(*args):
|
||||
raise psutil.Error("dud")
|
||||
|
||||
monkeypatch.setattr(psutil, "getloadavg", raise_error)
|
||||
config = make_config(
|
||||
"chat.example.org", PERMISSIVE | {"min_free_disk_space": "99999999G"}
|
||||
)
|
||||
assert not has_sufficient_resources(config)
|
||||
assert "ignoring" in caplog.text
|
||||
@@ -1,2 +1,2 @@
|
||||
"acme-enter-email": "{{ email }}"
|
||||
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.8-July-06-2026.pdf": true
|
||||
"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.6-August-18-2025.pdf": true
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -96,7 +96,7 @@ def _warn_unused_settings(unused_keys, out):
|
||||
def run_cmd(args, out):
|
||||
"""Deploy chatmail services on the remote server."""
|
||||
|
||||
ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain_bare
|
||||
ssh_host = args.ssh_host if args.ssh_host else args.config.ssh_host
|
||||
sshexec = get_sshexec(ssh_host)
|
||||
require_iroh = args.config.enable_iroh_relay
|
||||
strict_tls = args.config.tls_cert_mode == "acme"
|
||||
@@ -116,7 +116,7 @@ def run_cmd(args, out):
|
||||
pyinf = "pyinfra --dry" if args.dry_run else "pyinfra"
|
||||
|
||||
cmd = f"{pyinf} --ssh-user root {ssh_host} {deploy_path} -y"
|
||||
if ssh_host == "localhost":
|
||||
if ssh_host in ["localhost", "@local"]:
|
||||
cmd = f"{pyinf} @local {deploy_path} -y"
|
||||
|
||||
if version.parse(pyinfra.__version__) < version.parse("3"):
|
||||
@@ -158,7 +158,7 @@ def dns_cmd(args, out):
|
||||
ipv4 = args.config.ipv4_relay
|
||||
print(f"[WARNING] {ipv4} is not a domain, skipping DNS checks.")
|
||||
return 0
|
||||
ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain
|
||||
ssh_host = args.ssh_host if args.ssh_host else args.config.ssh_host
|
||||
sshexec = get_sshexec(ssh_host, verbose=args.verbose)
|
||||
tls_cert_mode = args.config.tls_cert_mode
|
||||
strict_tls = tls_cert_mode == "acme"
|
||||
@@ -195,7 +195,7 @@ def status_cmd_options(parser):
|
||||
def status_cmd(args, out):
|
||||
"""Display status for online chatmail instance."""
|
||||
|
||||
ssh_host = args.ssh_host if args.ssh_host else args.config.mail_domain_bare
|
||||
ssh_host = args.ssh_host if args.ssh_host else args.config.ssh_host
|
||||
sshexec = get_sshexec(ssh_host, verbose=args.verbose)
|
||||
|
||||
out.green(f"chatmail domain: {args.config.mail_domain}")
|
||||
|
||||
@@ -138,22 +138,9 @@ class UnboundDeployer(Deployer):
|
||||
# On an IPv4-only system, if unbound is started but not configured,
|
||||
# it causes subsequent steps to fail to resolve hosts.
|
||||
with blocked_service_startup():
|
||||
# dns-root-data is an optional package
|
||||
# that contains /usr/share/dns/root.key
|
||||
#
|
||||
# This file is copied into /var/lib/unbound/root.key
|
||||
# at the start of "unbound" systemd unit
|
||||
# by /usr/libexec/unbound-helper shell script
|
||||
# from the "unbound" package as of version 1.17.1-2+deb12u4
|
||||
#
|
||||
# The same /var/lib/unbound/root.key can be retrieved directly
|
||||
# following the procedure from
|
||||
# <https://www.rfc-editor.org/info/rfc7958/#section-3.1>
|
||||
# with "unbound-anchor -a /var/lib/unbound/root.key"
|
||||
# We don't install and use "unbound-anchor".
|
||||
apt.packages(
|
||||
name="Install unbound",
|
||||
packages=["unbound", "dns-root-data", "dnsutils"],
|
||||
packages=["unbound", "unbound-anchor", "dnsutils"],
|
||||
)
|
||||
|
||||
def configure(self):
|
||||
@@ -177,7 +164,12 @@ 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",
|
||||
commands=[
|
||||
"unbound-anchor -a /var/lib/unbound/root.key || true",
|
||||
],
|
||||
)
|
||||
self.ensure_directory(
|
||||
path="/etc/unbound/unbound.conf.d",
|
||||
@@ -253,13 +245,6 @@ class LegacyRemoveDeployer(Deployer):
|
||||
def install(self):
|
||||
apt.packages(name="Remove rspamd", packages="rspamd", present=False)
|
||||
|
||||
# unbound-anchor was used to download /var/lib/unbound/root.key
|
||||
# It is replaced by dns-root-data which contains /usr/share/dns/root.key.
|
||||
# unbound systemd unit copies /usr/share/dns/root.key
|
||||
# into /var/lib/unbound/root.key automatically on start
|
||||
# as long as /usr/share/dns/root.key is present.
|
||||
apt.packages(name="Remove unbound-anchor", packages="unbound-anchor", present=False)
|
||||
|
||||
# remove historic expunge script
|
||||
# which is now implemented through a systemd timer (chatmail-expire)
|
||||
self.remove_file("/etc/cron.d/expunge")
|
||||
@@ -370,8 +355,6 @@ class ChatmailVenvDeployer(Deployer):
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.units = (
|
||||
# doveauth must restart when chatmaild/ini file changes
|
||||
"doveauth",
|
||||
"chatmail-metadata",
|
||||
"lastlogin",
|
||||
"chatmail-expire",
|
||||
@@ -520,10 +503,10 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
|
||||
("nginx", 443),
|
||||
(["master", "smtpd"], 465),
|
||||
(["master", "smtpd"], 587),
|
||||
(["dovecot", "imap-login"], 993),
|
||||
(["imap-login", "dovecot"], 993),
|
||||
("iroh-relay", 3340),
|
||||
("mtail", 3903),
|
||||
(["dovecot", "stats"], 3904),
|
||||
("stats", 3904),
|
||||
("nginx", 8443),
|
||||
(["master", "smtpd"], config.postfix_reinject_port),
|
||||
(["master", "smtpd"], config.postfix_reinject_port_incoming),
|
||||
|
||||
@@ -34,7 +34,7 @@ class DovecotDeployer(Deployer):
|
||||
def __init__(self, config, disable_mail):
|
||||
self.config = config
|
||||
self.disable_mail = disable_mail
|
||||
self.units = []
|
||||
self.units = ["doveauth"]
|
||||
|
||||
def install(self):
|
||||
arch = host.get_fact(Arch)
|
||||
|
||||
@@ -37,15 +37,11 @@ default_client_limit = 20000
|
||||
# the following warning will be logged:
|
||||
# Warning: service(imap): process_limit (1024) reached, client connections are being dropped
|
||||
service imap {
|
||||
process_limit = {{ config.max_imap_connections }}
|
||||
process_limit = 50000
|
||||
}
|
||||
|
||||
{% if config.privacy_mail %}
|
||||
# Advertised to clients as IMAP METADATA /shared/admin (RFC 5464).
|
||||
# The privacy_mail contact from chatmail.ini is used because it is
|
||||
# the only address set by an operator.
|
||||
mail_server_admin = mailto:{{ config.privacy_mail }}
|
||||
{% endif %}
|
||||
mail_server_admin = mailto:root@{{ config.mail_domain }}
|
||||
mail_server_comment = Chatmail server
|
||||
|
||||
# `zlib` enables compressing messages stored in the maildir.
|
||||
# See
|
||||
|
||||
@@ -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.4/filtermail-{arch}"
|
||||
url = f"https://github.com/chatmail/filtermail/releases/download/v0.7.0/filtermail-{arch}"
|
||||
sha256sum = {
|
||||
"x86_64": "484cb8dff083134aefba9fce4a6b7ef4784a0f0e28e5108ecf8bb9e58a44fd2c",
|
||||
"aarch64": "66aa0ca2ca9add7a12d92883d76f8786384092adfde24a3d3a1d0b1f30d23a9e",
|
||||
"x86_64": "451f295a85b3b12dbb0f89e18ec319f742ee46dec218f20f7923bfb017a248bd",
|
||||
"aarch64": "6833061b2a2028264fdeb32f0a6123e1ff73de57dace125364016300b748452e",
|
||||
}[arch]
|
||||
self.download_executable(url, self.bin_path, sha256sum)
|
||||
|
||||
|
||||
@@ -28,13 +28,6 @@ counter created_nonci_accounts
|
||||
}
|
||||
}
|
||||
|
||||
# doveauth refusing new addresses because a chatmail.ini
|
||||
# system resource limit is exceeded.
|
||||
counter rejected_registrations
|
||||
/registration rejected: / {
|
||||
rejected_registrations++
|
||||
}
|
||||
|
||||
counter postfix_timeouts
|
||||
/timeout after DATA/ {
|
||||
postfix_timeouts++
|
||||
@@ -80,11 +73,6 @@ counter incoming_unencrypted_mail_count
|
||||
filtered_incoming_mail_count++
|
||||
}
|
||||
|
||||
counter incoming_mailer_daemon_mail_count
|
||||
/Incoming: Filtering mailer-daemon message from/ {
|
||||
incoming_mailer_daemon_mail_count++
|
||||
filtered_incoming_mail_count++
|
||||
}
|
||||
|
||||
counter rejected_unencrypted_mail_count
|
||||
/Rejected unencrypted mail/ {
|
||||
|
||||
@@ -128,10 +128,7 @@ http {
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# Endpoints Iroh uses for net_report probes and that clients
|
||||
# probe to tell whether this relay works. Both paths are served
|
||||
# by iroh-relay 0.35 and by the 1.0 line.
|
||||
location /ping {
|
||||
location /relay/probe {
|
||||
proxy_pass http://127.0.0.1:3340;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
/^DKIM-Signature:/ IGNORE
|
||||
/^Authentication-Results:/ IGNORE
|
||||
/^Received:/ IGNORE
|
||||
# List of headers for incoming messages
|
||||
# that must be retained for functionality and compatibility reasons
|
||||
/^From:/ DUNNO
|
||||
/^Message-Id:/ DUNNO
|
||||
/^Chat-/ DUNNO
|
||||
/^Content-Type:/ DUNNO
|
||||
|
||||
# For receiving clear-text messages (still supported in May 2026)
|
||||
/^Subject:/ DUNNO
|
||||
/^Date:/ DUNNO
|
||||
/^To:/ DUNNO
|
||||
/^CC:/ DUNNO
|
||||
/^References:/ DUNNO
|
||||
/^In-Reply-To:/ DUNNO
|
||||
|
||||
# Senders might support Autocrypt 1 but not RFC9788 (Header Protection)
|
||||
/^Autocrypt:/ DUNNO
|
||||
|
||||
# SecureJoin V2 protocol headers (for backward compatibility)
|
||||
/^Secure-Join/ DUNNO
|
||||
|
||||
# Ignore all other headers
|
||||
/.*/ IGNORE
|
||||
|
||||
@@ -85,14 +85,6 @@ inet_protocols = all
|
||||
|
||||
lmtp_header_checks = regexp:/etc/postfix/lmtp_header_cleanup
|
||||
|
||||
# Do not apply header checks to MIME headers
|
||||
# and other headers that are actually part of the message body.
|
||||
# Reference:
|
||||
# <https://www.postfix.org/postconf.5.html#disable_mime_input_processing>
|
||||
# <https://www.postfix.org/header_checks.5.html>
|
||||
# <https://stump.io/blog/2020/11/29/a-little-gotcha-with-postfixs-header_checks/>
|
||||
disable_mime_input_processing = yes
|
||||
|
||||
mua_client_restrictions = permit_sasl_authenticated, reject
|
||||
mua_sender_restrictions = reject_sender_login_mismatch, permit_sasl_authenticated, reject
|
||||
mua_helo_restrictions = permit_mynetworks, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, permit
|
||||
|
||||
@@ -18,7 +18,7 @@ smtp inet n - y - - smtpd
|
||||
-o smtpd_tls_mandatory_protocols=>=TLSv1.2
|
||||
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port_incoming }}
|
||||
-o smtpd_relay_restrictions=reject_unauth_destination
|
||||
submission inet n - y - {{ config.max_smtp_connections }} smtpd
|
||||
submission inet n - y - 5000 smtpd
|
||||
-o syslog_name=postfix/submission
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
-o smtpd_tls_mandatory_protocols=>=TLSv1.3
|
||||
@@ -32,9 +32,9 @@ submission inet n - y - {{ config.max_smtp_connections }
|
||||
-o smtpd_sender_restrictions=$mua_sender_restrictions
|
||||
-o smtpd_recipient_restrictions=
|
||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
||||
-o smtpd_client_connection_count_limit={{ config.max_smtp_connections // 5 }}
|
||||
-o smtpd_client_connection_count_limit=1000
|
||||
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }}
|
||||
smtps inet n - y - {{ config.max_smtp_connections }} smtpd
|
||||
smtps inet n - y - 5000 smtpd
|
||||
-o syslog_name=postfix/smtps
|
||||
-o smtpd_tls_wrappermode=yes
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
@@ -48,7 +48,7 @@ smtps inet n - y - {{ config.max_smtp_connections }
|
||||
-o smtpd_sender_restrictions=$mua_sender_restrictions
|
||||
-o smtpd_recipient_restrictions=
|
||||
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
|
||||
-o smtpd_client_connection_count_limit={{ config.max_smtp_connections // 5 }}
|
||||
-o smtpd_client_connection_count_limit=1000
|
||||
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }}
|
||||
#628 inet n - y - - qmqpd
|
||||
pickup unix n - y 60 1 pickup
|
||||
@@ -76,6 +76,7 @@ lmtp unix - - y - - lmtp
|
||||
anvil unix - - y - 1 anvil
|
||||
scache unix - - y - 1 scache
|
||||
postlog unix-dgram n - n - 1 postlogd
|
||||
filter unix - n n - - lmtp
|
||||
# Local SMTP server for reinjecting outgoing filtered mail.
|
||||
127.0.0.1:{{ config.postfix_reinject_port }} inet n - n - 100 smtpd
|
||||
-o syslog_name=postfix/reinject
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -60,8 +62,8 @@ def maildomain(chatmail_config):
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def sshdomain(maildomain):
|
||||
return os.environ.get("CHATMAIL_SSH", maildomain)
|
||||
def sshdomain(chatmail_config):
|
||||
return os.environ.get("CHATMAIL_SSH", chatmail_config.ssh_host)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
+30
-143
@@ -14,21 +14,14 @@ Minimal requirements and prerequisites
|
||||
|
||||
You will need the following:
|
||||
|
||||
- A Debian 12 **deployment server** with reachable SMTP/SUBMISSIONS/IMAPS/HTTPS ports.
|
||||
- Control over a domain through a DNS provider of your choice.
|
||||
(there is experimental support for :ref:`IP-only relays <iponly>`).
|
||||
|
||||
- A Debian 12 server with reachable SMTP/SUBMISSIONS/IMAPS/HTTPS ports.
|
||||
IPv6 is encouraged if available. Chatmail relay servers only require
|
||||
1GB RAM, one CPU, and perhaps 10GB storage for a few thousand active
|
||||
chatmail addresses.
|
||||
|
||||
- A Linux or Unix **build machine** with key-based SSH access to the root
|
||||
user of the deployment server.
|
||||
You must add a passphrase-protected private key to your local ssh-agent because you
|
||||
can’t type in your passphrase during deployment.
|
||||
(An ed25519 private key is required due to an `upstream bug in
|
||||
paramiko <https://github.com/paramiko/paramiko/issues/2191>`_)
|
||||
|
||||
- Control over a domain through a DNS provider of your choice
|
||||
(there is experimental support for :ref:`IP-only relays <iponly>`).
|
||||
|
||||
|
||||
.. _setup:
|
||||
|
||||
@@ -38,7 +31,7 @@ Setup with ``scripts/cmdeploy``
|
||||
We use ``chat.example.org`` as the chatmail domain in the following
|
||||
steps. Please substitute it with your own domain.
|
||||
|
||||
1. Setup the initial DNS records for your deployment server.
|
||||
1. Setup the initial DNS records for your relay.
|
||||
The following is an example in the
|
||||
familiar BIND zone file format with a TTL of 1 hour (3600 seconds).
|
||||
Please substitute your domain and IP addresses.
|
||||
@@ -58,22 +51,25 @@ steps. Please substitute it with your own domain.
|
||||
The ``mta-sts`` CNAME and ``_mta-sts`` TXT records
|
||||
are not needed for such domains.
|
||||
|
||||
2. On your local PC, clone the repository and bootstrap the Python
|
||||
2. Login to the server with SSH, clone the repository and bootstrap the Python
|
||||
virtualenv.
|
||||
|
||||
::
|
||||
|
||||
ssh root@chat.example.org
|
||||
git clone https://github.com/chatmail/relay
|
||||
cd relay
|
||||
scripts/initenv.sh
|
||||
|
||||
3. On your local build machine (PC), create a chatmail configuration file
|
||||
3. Then, create a chatmail configuration file
|
||||
``chatmail.ini``:
|
||||
|
||||
::
|
||||
|
||||
scripts/cmdeploy init chat.example.org # <-- use your domain
|
||||
|
||||
.. note::
|
||||
|
||||
To use self-signed TLS certificates
|
||||
instead of Let's Encrypt,
|
||||
use a domain name starting with ``_``
|
||||
@@ -84,13 +80,7 @@ steps. Please substitute it with your own domain.
|
||||
See the :doc:`overview`
|
||||
for details on certificate provisioning.
|
||||
|
||||
4. Verify that SSH root login to the deployment server server works:
|
||||
|
||||
::
|
||||
|
||||
ssh root@chat.example.org # <-- use your domain
|
||||
|
||||
5. From your local build machine, setup and configure the remote deployment server:
|
||||
4. Now run the deployment script to install the relay to the server:
|
||||
|
||||
::
|
||||
|
||||
@@ -102,7 +92,6 @@ steps. Please substitute it with your own domain.
|
||||
public).
|
||||
|
||||
|
||||
|
||||
Docker installation
|
||||
-------------------
|
||||
|
||||
@@ -110,27 +99,33 @@ There is experimental support for running chatmail via Docker.
|
||||
A monolithic image based on the above cmdeploy method is available `through a separate repository <https://github.com/chatmail/docker/pkgs/container/docker>`_.
|
||||
See the `chatmail/docker README <https://github.com/chatmail/docker>`_ for full setup instructions.
|
||||
|
||||
Other helpful commands
|
||||
----------------------
|
||||
|
||||
To check the status of your deployment server running the chatmail service:
|
||||
Next Steps
|
||||
----------
|
||||
|
||||
::
|
||||
|
||||
scripts/cmdeploy status
|
||||
|
||||
To display and check all recommended DNS records:
|
||||
Now you should display and check all recommended DNS records
|
||||
to enable federation with other relays:
|
||||
|
||||
::
|
||||
|
||||
scripts/cmdeploy dns
|
||||
|
||||
To test whether your chatmail service is working correctly:
|
||||
You should also test whether your chatmail service is working correctly:
|
||||
|
||||
::
|
||||
|
||||
scripts/cmdeploy test
|
||||
|
||||
Other Helpful Commands
|
||||
----------------------
|
||||
|
||||
To check the status of your chatmail relay:
|
||||
|
||||
::
|
||||
|
||||
scripts/cmdeploy status
|
||||
|
||||
|
||||
To measure the performance of your chatmail service:
|
||||
|
||||
::
|
||||
@@ -171,8 +166,9 @@ This starts a local live development cycle for chatmail web pages:
|
||||
directory and generating HTML files and copying assets to the
|
||||
``www/build`` directory.
|
||||
|
||||
- Starts a browser window automatically where you can “refresh” as
|
||||
needed.
|
||||
- if you are running scripts/cmdeploy webdev on the relay itself,
|
||||
you need to configure a route in /etc/nginx/nginx.conf
|
||||
to expose the build directory.
|
||||
|
||||
Custom web pages
|
||||
----------------
|
||||
@@ -190,7 +186,7 @@ Disable automatic address creation
|
||||
--------------------------------------------------------
|
||||
|
||||
If you need to stop address creation, e.g. because some script is wildly
|
||||
creating addresses, login with ssh to the deployment machine and run:
|
||||
creating addresses, login with ssh to the relay and run:
|
||||
|
||||
::
|
||||
|
||||
@@ -199,93 +195,6 @@ creating addresses, login with ssh to the deployment machine and run:
|
||||
Chatmail address creation will be denied while this file is present.
|
||||
|
||||
|
||||
.. _system-limits:
|
||||
|
||||
Configurable System Limits
|
||||
--------------------------
|
||||
|
||||
Limits for auto-rejecting address creation
|
||||
..........................................
|
||||
|
||||
A relay refuses creation of new addresses
|
||||
when the machine runs low on resources,
|
||||
but existing addresses keep working.
|
||||
|
||||
Three ``chatmail.ini`` settings control this,
|
||||
shown here with their defaults::
|
||||
|
||||
max_load_1m = 5
|
||||
min_available_memory = 200M
|
||||
min_free_disk_space = 1G
|
||||
|
||||
- ``max_load_1m`` is the maximum 1-minute load average,
|
||||
as reported by ``uptime``;
|
||||
it counts processes waiting for disk I/O as well as for CPU.
|
||||
It is deliberately not scaled by the number of CPUs
|
||||
because I/O rather than CPU is what typically limits a relay.
|
||||
|
||||
- ``min_available_memory`` is the minimum memory available without swapping.
|
||||
|
||||
- ``min_free_disk_space`` is the minimum free disk space
|
||||
on the file system holding the mailboxes.
|
||||
|
||||
The defaults suit the small machine described in
|
||||
`Minimal requirements and prerequisites`_.
|
||||
|
||||
.. note::
|
||||
|
||||
If you run a bigger machine,
|
||||
raise ``max_load_1m`` after watching ``uptime`` under typical load.
|
||||
|
||||
Rejections are logged by the ``doveauth`` service,
|
||||
so you can check whether a limit is set too tightly::
|
||||
|
||||
journalctl -u doveauth --grep 'registration rejected'
|
||||
|
||||
If ``mtail_address`` is set, rejections are also counted
|
||||
in the ``rejected_registrations`` metric.
|
||||
|
||||
|
||||
Overall IMAP and SMTP connection limits
|
||||
.......................................
|
||||
|
||||
Two further settings bound how many connections
|
||||
the relay accepts at all, again shown with their defaults::
|
||||
|
||||
max_imap_connections = 10000
|
||||
max_smtp_connections = 1000
|
||||
|
||||
``max_imap_connections`` becomes the Dovecot imap process limit,
|
||||
and ``max_smtp_connections`` the Postfix process limit
|
||||
on each of the submission and smtps ports.
|
||||
A single client IP may use up to a fifth of ``max_smtp_connections``.
|
||||
Each connection costs memory,
|
||||
so these limits defend the relay against running out of RAM.
|
||||
|
||||
Unless ``imap_compress`` is enabled,
|
||||
an IMAP connection that is idle for ``imap_hibernate_timeout``
|
||||
is handed over to the ``imap-hibernate`` process
|
||||
and does not count towards ``max_imap_connections``,
|
||||
which is why a relay can serve far more IMAP clients
|
||||
than this setting suggests.
|
||||
|
||||
If you run a large relay with 10k or 100k's of addresses,
|
||||
check current connection counts before upgrading
|
||||
and set the limits accordingly.
|
||||
|
||||
To see how close a running relay is to these two limits,
|
||||
copy ``scripts/check-connections.sh`` from the relay repository
|
||||
onto the relay and run it there::
|
||||
|
||||
imap 5 ports 143,993 (max_imap_connections)
|
||||
5 dovecot sessions, 0 of them in an active imap process
|
||||
submission 0 ports 465,587 (max_smtp_connections per port)
|
||||
incoming 0 port 25 (from other relays, no chatmail.ini limit)
|
||||
|
||||
It counts established sockets with ``ss``
|
||||
and cross-checks the IMAP number against ``doveadm who``.
|
||||
|
||||
|
||||
Running a relay with self-signed certificates
|
||||
----------------------------------------------
|
||||
|
||||
@@ -333,25 +242,3 @@ The deploy will verify that both files exist on the server.
|
||||
If you use such a setup, you must trigger the reload explicitly after renewal::
|
||||
|
||||
systemctl start tls-cert-reload.service
|
||||
|
||||
|
||||
Migrating to a new build machine
|
||||
----------------------------------
|
||||
|
||||
To move or add a build machine,
|
||||
clone the relay repository on the new build machine, and copy the ``chatmail.ini`` file from the old build machine.
|
||||
Make sure ``rsync`` is installed, then initialize the environment:
|
||||
|
||||
::
|
||||
|
||||
./scripts/initenv.sh
|
||||
|
||||
Run safety checks before a new deployment:
|
||||
|
||||
::
|
||||
|
||||
./scripts/cmdeploy dns
|
||||
./scripts/cmdeploy status
|
||||
|
||||
If you keep multiple build machines (ie laptop and desktop), keep ``chatmail.ini`` in sync between
|
||||
them.
|
||||
|
||||
+16
-2
@@ -1,6 +1,6 @@
|
||||
|
||||
Migrating to a new machine
|
||||
===========================
|
||||
Migrating the relay to a new server
|
||||
===================================
|
||||
|
||||
This migration tutorial provides a step-wise approach
|
||||
to safely migrate a chatmail relay from one remote machine to another.
|
||||
@@ -96,3 +96,17 @@ in this case, just run ``ssh-keygen -R "mail.example.org"`` as recommended.
|
||||
If you have lowered the Time-to-Live for DNS records in step 1,
|
||||
better use a higher value again (between 14400 and 86400 seconds) once you are sure everything works.
|
||||
|
||||
|
||||
Migrating a local chatmail/relay repository to the server
|
||||
=========================================================
|
||||
|
||||
To move the directory with your local chatmail/relay repository and ``chatmail.ini`` file,
|
||||
clone the `relay repository <https://github.com/chatmail/relay/>` to the server where the relay is running,
|
||||
and copy the ``chatmail.ini`` file from your local chatmail/relay repository there.
|
||||
|
||||
If you made local changes to the repository,
|
||||
you can store them in a file with ``git diff origin/main > local-changes.patch``,
|
||||
copy the file to the repository on the server,
|
||||
and run ``git apply local-changes.patch``.
|
||||
|
||||
Then you can proceed with the `installation steps <setup>`.
|
||||
|
||||
@@ -225,6 +225,21 @@ Accepting and delivering mail
|
||||
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
|
||||
|
||||
Operational details of a chatmail relay
|
||||
----------------------------------------
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Show current IMAP and SMTP connections on a chatmail relay,
|
||||
# to compare against the max_imap_connections and max_smtp_connections
|
||||
# settings in chatmail.ini. Run this on the relay itself.
|
||||
set -e
|
||||
|
||||
# Count established TCP connections whose local port is one of the arguments.
|
||||
established() {
|
||||
filter=$(printf 'sport = :%s or ' "$@")
|
||||
ss -Htn state established "( ${filter% or } )" | wc -l
|
||||
}
|
||||
|
||||
# doveadm prints a header line and then one line per logged-in user,
|
||||
# with that user's number of connections in the second column.
|
||||
sessions=$(doveadm who | awk 'NR > 1 { n += $2 } END { print n + 0 }')
|
||||
|
||||
# Unless imap_compress is enabled, connections idle for
|
||||
# imap_hibernate_timeout are handed over to the imap-hibernate
|
||||
# process, so they cost no imap process while idle.
|
||||
active=$(pgrep -x imap | wc -l)
|
||||
|
||||
printf 'imap %6d ports 143,993 (max_imap_connections)\n' "$(established 143 993)"
|
||||
printf ' %6d dovecot sessions, %d of them in an active imap process\n' "$sessions" "$active"
|
||||
printf 'submission %6d ports 465,587 (max_smtp_connections per port)\n' "$(established 465 587)"
|
||||
printf 'incoming %6d port 25 (from other relays, no chatmail.ini limit)\n' "$(established 25)"
|
||||
Reference in New Issue
Block a user