Compare commits

..

33 Commits

Author SHA1 Message Date
holger krekel
260ace5ab0 fix off-by-one 2026-03-16 13:27:12 +01:00
holger krekel
4b1dbc3d43 fix: use push_file_content for unbound and sysctl config files
fixes silent fail on invalid configuration file.
2026-03-16 13:27:12 +01:00
holger krekel
e6db359a34 fix: run cmdeploy dns and restart filtermail in lxc-start --run
lxc-start --run deployed the relay but never loaded DNS zones
or restarted filtermail-incoming, so DKIM verification and
outgoing mail failed.
2026-03-16 13:27:12 +01:00
holger krekel
a61bbcade2 fix: pass full config to ChatmailDeployer and ensure mailboxes_dir
ChatmailDeployer now receives the full config object
so it can access mailboxes_dir and create this directory
during deployment, preventing mail delivery failures.
2026-03-16 13:27:12 +01:00
holger krekel
de1a53b135 fix: separate stderr capture in incus and suppress in test plugin
Capture stderr separately in Incus.run() instead of merging
it into stdout, which corrupted JSON parsing in run_json().
Add --quiet flag to reduce incus noise.
Suppress stderr in Remote subprocess to prevent pytest-xdist
communication issues.
2026-03-16 13:27:12 +01:00
holger krekel
22a2b6a1c4 remove superflous sepchar argument 2026-03-16 13:27:12 +01:00
holger krekel
ef7e06d9f9 remove memory limits in containers 2026-03-16 13:27:12 +01:00
holger krekel
7450143c86 fix a potential hang because "journalctl -f" never finishes by itself, and handles might stay open if we don't close them during test teardown. 2026-03-16 13:27:12 +01:00
holger krekel
8a49489d54 try to discover and use host dns settings instead of 9.9.9.9 2026-03-16 13:27:12 +01:00
holger krekel
dcb9fbe73f make sure subprocesses by defaulit don't inherit stdin FD 2026-03-16 13:27:12 +01:00
holger krekel
da5c248562 try fix some test hangs potentially caused by filtermail's DNS cache 2026-03-16 13:27:12 +01:00
holger krekel
550498e936 force tcp connections for dns resolution 2026-03-16 13:27:12 +01:00
holger krekel
c4b018b7f2 change all timeouts to 60 2026-03-16 13:27:12 +01:00
holger krekel
7570c57d7e remove superflous """\ 2026-03-16 13:27:12 +01:00
holger krekel
68d1fa8f01 only use explicit server settings if the host resolves to ip address via ssh config 2026-03-16 13:27:12 +01:00
holger krekel
785efabe4c move --verbose option back to subcommands 2026-03-16 13:27:12 +01:00
holger krekel
3ba2703d04 cmdeploy: replace globals() subcommand scan with explicit SUBCOMMANDS list
The get_parser() loop that scanned globals() for *_cmd names was fragile
and forced # noqa: F401 on all lxc imports (ruff couldn't see they were
used dynamically).

Replace it with an explicit SUBCOMMANDS list of
(cmd_func, options_func, needs_config) tuples.  This makes the full set
of subcommands visible at a glance, their registration order defined,
and the imports unconditionally used (no more noqa suppressions).
2026-03-16 13:27:12 +01:00
holger krekel
6c95eeea80 lxc: dovecot sysctl: warn but skip when running in shared-kernel container
Replace the CHATMAIL_NOSYSCTL guard with an explicit systemd-detect-virt -c check.
2026-03-16 13:27:12 +01:00
holger krekel
783904f244 lxc: code cleanup and docs polish from review
Code:
- Fix check_ssh_config_include() to do a case-insensitive line match
  (read_text().splitlines() instead of filter(None, map(..., open()))).
- Drop the default help_text= from _add_name_args(); callers now must
  supply an explicit string.
- Expand the DNSContainer class docstring.
- Fix sysctl comment: note that incus provides net.* virtualization
  so the sysctl only affects the container's network namespace.

Docs (doc/source/lxc.rst):
- Remove double blank line after page title; fix missing comma.
- Replace the plain-text root-access note with a .. caution:: block.
- Tighten the Quick-start section and lxc-test CLI entry.
2026-03-16 13:27:12 +01:00
holger krekel
405dc2d1ee lxc: simplify to a single find_image(aliases) method
Replace the two-function find_relay_image / _find_relay_image pair
with Incus.find_image(aliases), which returns the first alias
that exists in the local image store, or None.

Container.launch() passes [RELAY_IMAGE_ALIAS, BASE_IMAGE_ALIAS]
and ensure_base_image() passes [BASE_IMAGE_ALIAS].
2026-03-16 13:27:12 +01:00
holger krekel
62fe113b59 lxc: extract blocked_service_startup() context manager into basedeploy
Move the policy-rc.d install/remove boilerplate into a shared context
manager in basedeploy.py so both UnboundDeployer and DovecotDeployer use
the same abstraction, and the DNS container's _install_powerdns() inline
shell uses the same pattern.

DovecotDeployer now wraps its three package installs in
blocked_service_startup() to prevent Dovecot from auto-starting on
initial install — avoiding bind conflicts on IPv4-only systems.
2026-03-16 13:27:12 +01:00
holger krekel
a102ed7d61 lxc: add Out class with --verbose, section timing, and coloured shell output
Move the Out output-printer class to cmdeploy/util.py so it is shared
across CLI modules.  All print/shell calls in lxc/cli.py, lxc/incus.py,
and dns.py now route through Out instead of bare print().

Key additions:
- Out.section() / Out.section_line(): coloured section headers scaled
  to the current terminal width (or $_CMDEPLOY_WIDTH for sub-processes).
- Out.shell(): merges stdout/stderr, prefixes each output line, and
  prints a red error line with the exit code on failure.
- Out.new_prefixed_out(): indented sub-printer that shares section_timings.
- 'cmdeploy -v / -vv' exposes the verbosity levels.
- Tests for Out added to test_util.py.
2026-03-16 13:27:12 +01:00
holger krekel
010e9de08e lxc: poll until DNS answers before continuing; reset bridge on --destroy-all
After restarting pdns/pdns-recursor, wait up to 10 s for the recursor
to actually answer a query before proceeding.  Likewise, after
configure_dns(), poll from inside the relay container until the
configured DNS IP responds.

On --destroy-all, unset the incusbr0 dns.mode and raw.dnsmasq network
options so the next lxc-start starts from a clean bridge state.

DNSConfigurationError (caught in main()) is raised on timeout so the
CLI prints a clean error instead of failing later with a cryptic message.
2026-03-16 13:27:12 +01:00
holger krekel
75c42dc8c7 ignore sysctl permission problems (likely in containers) 2026-03-16 13:27:12 +01:00
holger krekel
cd16ef8c4a address link2xt review comments 2026-03-16 13:27:12 +01:00
holger krekel
ea7b875eb1 address link2xt comments (zone parsing and turn v0.4 release 2026-03-16 13:27:12 +01:00
holger krekel
1a578ecc66 simplify start instructions 2026-03-16 13:27:12 +01:00
holger krekel
3021e47866 make helpers testable and test them, also streamline intro of docs 2026-03-16 13:27:12 +01:00
holger krekel
b59417128c fix lxc-test to not re-run deploy when nothing changed + some other beautifications 2026-03-16 13:27:12 +01:00
holger krekel
49705863d3 don't use env vars but explicit pytest options to pass ssh info around. 2026-03-16 13:27:12 +01:00
holger krekel
861fdf7a50 feat: add LXC container support for local chatmail development
Add cmdeploy "lxc-test" command to run cmdeploy against local containers,
with supplementary lxc-start, lxc-stop and lxc-status subcommands.
See doc/source/lxc.rst for full documentation including prerequisites,
DNS setup, TLS handling, DNS-free testing, and known limitations.

Apart from adding lxc-specific docs, tests, and implementation files in the cmdeploy/lxc directory,
this PR adds the --ssh-config option to cmdeploy run/dns/status/test commands and pyinfra invocations,
and also to sshexec (Execnet) handling.  This allows for the host to need no DNS entries for a relay,
and route all resolution through ssh-config.  This is used by the "lxc-test" command, which performs
a completely local setup -- again, see docs for more details.

While working on DNS/SSH things i also unified all zone-file handling
to use actual BIND format as it is easy enough to parse back.
2026-03-16 13:27:12 +01:00
holger krekel
4fabfb31f8 fix test and some linting fixes 2026-03-16 13:25:57 +01:00
Jagoda Ślązak
36478dbfcf feat(filtermail): Disable IP verification on domain-literal addresses
Disables IP verification by upgrading filtermail to v0.6,
changelog: <https://github.com/chatmail/filtermail/releases/tag/v0.6.0>

Messages using domain-literal addresses no longer require
to match the origin SMTP connection IP anymore.

This allows for example a relay using IPv4 email addresses
to send messages to other relays over IPv6.

This is not considering a breaking change as IP-address-only
relays are not considered a stable feature.

Signed-off-by: Jagoda Ślązak <jslazak@jslazak.com>
2026-03-13 20:47:10 +01:00
3 changed files with 7 additions and 8 deletions

View File

@@ -15,7 +15,7 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: download filtermail
run: curl -L https://github.com/chatmail/filtermail/releases/download/v0.5.2/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.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

View File

@@ -14,10 +14,10 @@ class FiltermailDeployer(Deployer):
def install(self):
arch = host.get_fact(facts.server.Arch)
url = f"https://github.com/chatmail/filtermail/releases/download/v0.5.2/filtermail-{arch}"
url = f"https://github.com/chatmail/filtermail/releases/download/v0.6.0/filtermail-{arch}"
sha256sum = {
"x86_64": "ce24ca0075aa445510291d775fb3aea8f4411818c7b885ae51a0fe18c5f789ce",
"aarch64": "c5d783eefa5332db3d97a0e6a23917d72849e3eb45da3d16ce908a9b4e5a797d",
"x86_64": "3fd8b18282252c75a5bbfa603d8c1b65f6563e5e920bddf3e64e451b7cdb43ce",
"aarch64": "2bd191de205f7fd60158dd8e3516ab7e3efb14627696f3d7dc186bdcd9e10a43",
}[arch]
self.need_restart |= files.download(
name="Download filtermail",

View File

@@ -89,10 +89,9 @@ def test_concurrent_logins_same_account(
assert login_results.get()
def test_no_vrfy(chatmail_config, cmfactory):
ac1 = cmfactory.get_online_account()
addr = ac1.get_config("addr")
def test_no_vrfy(cmfactory, chatmail_config):
ac = cmfactory.get_online_account()
addr = ac.get_config("addr")
domain = chatmail_config.mail_domain
s = smtplib.SMTP(domain)