Local LXC/Incus container management for chatmail relay development and testing
Project description
cmlxc -- local chatmail container management and testing
Manage local Incus containers for chatmail relay development and testing.
cmlxc spins up lightweight LXC containers,
deploys chatmail relay services into them
via cmdeploy or madmail,
and runs integration tests --
all without touching the host system.
Architecture
cmlxc manages four kinds of containers, each with a distinct role:
cmlxc init / deploy-* / test-*
|
v
+-----------------+ +------------------------+ +--------------------+
| ns-localchat | | builder-localchat | | relay containers |
| (PowerDNS) | | (repos, venvs, builds) | | (cm0, mad1, ...) |
+-----------------+ +------------------------+ +--------------------+
^ | ^
| DNS zones | SSH / SCP |
+------------------------+---------------------------+
Base image (localchat-base) --
A Debian 12 image with SSH and Python pre-installed.
All other containers are launched from this image
(or from a cached relay image).
DNS container (ns-localchat) --
Runs PowerDNS authoritative + recursor.
Provides .localchat DNS resolution
so containers can reach each other by name.
Builder container (builder-localchat) --
The central workhorse.
Holds repository checkouts (/root/relay, /root/madmail),
Python virtualenvs for cmdeploy and mini-tests,
and the compiled maddy binary.
All deployment and test operations
are executed inside the builder --
the host only needs cmlxc itself.
Relay containers (e.g. cm0-localchat, mad1-localchat) --
Ephemeral containers that receive a deployed chatmail service.
Each relay is locked to a single deployment driver
(cmdeploy or madmail);
switching requires destroying and re-creating the container.
Deployment drivers
Drivers live in driver_cmdeploy.py and driver_madmail.py.
Each driver has an init_builder() function
(called during cmlxc init)
and a deploy() function
(called during cmlxc deploy-*).
-
cmdeploy -- Runs
cmdeploy runfrom the builder container over SSH into the relay. Generates DNS zones, loads them into PowerDNS, and verifies records. After the first successful deploy the relay image is cached aslocalchat-relayso subsequent containers start pre-populated. -
madmail -- Builds the
maddyGo binary on first deploy (the triggeredmakeis idempotent on reruns), then pushes it via SCP and runsmadmail install --simple --ip <IP>. No DNS entries are needed.
Prerequisites
Incus installed and configured on the host. Usually only being part of the "incus" group is necessary, as containers can run with user privileges.
Installation
With pip:
python -m venv venv
source venv/bin/activate
pip install .
Or with uv:
uv venv venv
source venv/bin/activate
uv pip install .
Usage
Initialize the environment
(base image, DNS container, builder container).
At least one of --cmdeploy or --madmail is required:
cmlxc init --cmdeploy @main
cmlxc init --madmail @main
cmlxc init --cmdeploy @main --madmail @main
The SOURCE argument controls where the code comes from:
| Form | Meaning |
|---|---|
@ref |
Clone default remote at branch/tag ref |
/path or ./path |
Sync from a local checkout |
URL@ref |
Clone a custom remote at ref |
Examples with local checkouts or feature branches:
cmlxc init --cmdeploy ../relay --madmail @lmtp-rework
cmlxc init --cmdeploy @fix-dovecot
Running init again wipes and re-clones the repositories
in the builder.
Deploy chatmail relays (creates containers if needed, then deploys):
cmlxc deploy-cmdeploy cm0 cm1
cmlxc deploy-madmail mad1
cmlxc deploy-madmail --ipv4-only mad1
Run integration tests inside the builder:
cmlxc test-mini cm0
cmlxc test-mini cm0 cm1 # cross-relay tests
cmlxc test-cmdeploy cm0 cm1
SSH into a deployed relay:
ssh -F ~/.config/cmlxc/ssh-config cm0
Lifecycle commands:
cmlxc status # show all containers
cmlxc start cm0 # restart a stopped relay
cmlxc stop cm0 cm1 # stop relays
cmlxc destroy cm0 # stop + delete
cmlxc destroy --all # destroy relays, keep DNS/builder
cmlxc destroy --reset # full teardown, requires re-init
Increase verbosity with -v or -vv:
cmlxc deploy-cmdeploy -vv cm1
Shell Completion
cmlxc supports Bash tab-completion for subcommands, options, and container names.
Enable for the current session:
eval "$(register-python-argcomplete cmlxc)"
Enable permanently:
activate-global-python-argcomplete --user
Releasing
Versions are derived from git tags
via setuptools-git-versioning.
The changelog is generated with
git-cliff
using the cliff.toml config in the repo root.
Steps
-
Preview unreleased changes:
git cliff --unreleased -
Tag the release (the tag name becomes the version):
git tag v0.1.0 -
Generate the full changelog:
git cliff -o CHANGELOG.md -
Amend the tag commit to include the changelog:
git add CHANGELOG.md git commit --amend --no-edit git tag -f v0.1.0 -
Push tag and branch:
git push origin main --tags
The release.yml GitHub workflow
triggers on pushed v* tags,
builds the sdist + wheel,
and publishes to PyPI
via trusted publishing (OIDC).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cmlxc-0.6.0.tar.gz.
File metadata
- Download URL: cmlxc-0.6.0.tar.gz
- Upload date:
- Size: 33.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db7432e0128e563d4555b4c2b56c0fc7623f11f44c571e76f1463988633dec16
|
|
| MD5 |
5fdb23623310c37f1195c01e04624945
|
|
| BLAKE2b-256 |
881f8f8f8855cf63eedcbe493345c487f754f2d98d5257ed5ce8b96853927592
|
Provenance
The following attestation bundles were made for cmlxc-0.6.0.tar.gz:
Publisher:
release.yml on chatmail/cmlxc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cmlxc-0.6.0.tar.gz -
Subject digest:
db7432e0128e563d4555b4c2b56c0fc7623f11f44c571e76f1463988633dec16 - Sigstore transparency entry: 1280795098
- Sigstore integration time:
-
Permalink:
chatmail/cmlxc@ea52a8932aa3c7f6171aa7c4f45f1dfcf033f92a -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/chatmail
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ea52a8932aa3c7f6171aa7c4f45f1dfcf033f92a -
Trigger Event:
push
-
Statement type:
File details
Details for the file cmlxc-0.6.0-py3-none-any.whl.
File metadata
- Download URL: cmlxc-0.6.0-py3-none-any.whl
- Upload date:
- Size: 32.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
679a9e544af0976022805e6f66efae5b3fa2f253c3fc898c6c3bdc34cf92a6da
|
|
| MD5 |
d06fb6855b02650265579d98166c110f
|
|
| BLAKE2b-256 |
a7006da31a2dc626af8aab69931f234e471e4a1114cd90b4b58fb950aceaaec4
|
Provenance
The following attestation bundles were made for cmlxc-0.6.0-py3-none-any.whl:
Publisher:
release.yml on chatmail/cmlxc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cmlxc-0.6.0-py3-none-any.whl -
Subject digest:
679a9e544af0976022805e6f66efae5b3fa2f253c3fc898c6c3bdc34cf92a6da - Sigstore transparency entry: 1280795106
- Sigstore integration time:
-
Permalink:
chatmail/cmlxc@ea52a8932aa3c7f6171aa7c4f45f1dfcf033f92a -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/chatmail
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ea52a8932aa3c7f6171aa7c4f45f1dfcf033f92a -
Trigger Event:
push
-
Statement type: