diff --git a/cmdeploy/src/cmdeploy/lxc/cli.py b/cmdeploy/src/cmdeploy/lxc/cli.py index f22bed96..5dc098d0 100644 --- a/cmdeploy/src/cmdeploy/lxc/cli.py +++ b/cmdeploy/src/cmdeploy/lxc/cli.py @@ -430,14 +430,8 @@ def _deploy_status(ct, local_hash, ix): return f"IN-SYNC ({short})" -def _add_name_args(parser, help_text=None): - """Add optional positional NAME arguments.""" - parser.add_argument( - "names", - nargs="*", - metavar="NAME", - help=help_text or "Relay name(s) to operate on.", - ) +def _add_name_args(parser, help_text): + parser.add_argument("names", nargs="*", metavar="NAME", help=help_text) def _run_cmdeploy(subcmd, ct, ix, out, extra=None, **kwargs): diff --git a/cmdeploy/src/cmdeploy/lxc/incus.py b/cmdeploy/src/cmdeploy/lxc/incus.py index a2fa6a2a..97f2c661 100644 --- a/cmdeploy/src/cmdeploy/lxc/incus.py +++ b/cmdeploy/src/cmdeploy/lxc/incus.py @@ -95,8 +95,9 @@ class Incus: user_ssh_config = Path.home() / ".ssh" / "config" if not user_ssh_config.exists(): return False - lines = filter(None, map(str.strip, user_ssh_config.open("r"))) - return f"Include {self.ssh_config_path}" in lines + lines = user_ssh_config.read_text().splitlines() + target = f"include {self.ssh_config_path}".lower() + return any(line.strip().lower() == target for line in lines) def run(self, args, check=True, capture=True, input=None): """Run an incus command. @@ -190,7 +191,7 @@ class Incus: def delete_images(self): """Delete the cached base and relay images.""" for alias in (RELAY_IMAGE_ALIAS, BASE_IMAGE_ALIAS): - self.run(["image", "delete", alias], check=False) + self.run(["image", "delete", alias], check=False) # ok if absent def list_managed(self): """Return list of dicts with name, ip, ipv6, domain, status, memory_usage.""" @@ -433,7 +434,8 @@ class RelayContainer(Container): def disable_ipv6(self): """Disable IPv6 inside the container via sysctl.""" - # incus provides net.* virtalization + # incus provides net.* virtualization for LXC containers so that + # these sysctls only affect the container's network namespace. self.bash("""\ sysctl -w net.ipv6.conf.all.disable_ipv6=1 sysctl -w net.ipv6.conf.default.disable_ipv6=1 @@ -485,7 +487,7 @@ class RelayContainer(Container): return shell(cmd, timeout=15).returncode == 0 def configure_dns(self, dns_ip): - """Point this container's resolver at *dns_ip* and verify it works.""" + """Point this container's resolver at *dns_ip* and verify DNS is reachable.""" self.bash(f"""\ systemctl disable --now systemd-resolved 2>/dev/null || true rm -f /etc/resolv.conf @@ -535,7 +537,11 @@ class RelayContainer(Container): class DNSContainer(Container): - """Container handle for the PowerDNS name server.""" + """Container handle for the PowerDNS name server. + + Manages the authoritative and recursive DNS services required for + name resolution in the local testing environment. + """ def __init__(self, incus): super().__init__(incus, DNS_CONTAINER_NAME, domain=DNS_DOMAIN) diff --git a/cmdeploy/src/cmdeploy/tests/test_lxc.py b/cmdeploy/src/cmdeploy/tests/test_lxc.py index c9f40249..18c70300 100644 --- a/cmdeploy/src/cmdeploy/tests/test_lxc.py +++ b/cmdeploy/src/cmdeploy/tests/test_lxc.py @@ -12,7 +12,7 @@ from cmdeploy.util import Out pytestmark = pytest.mark.skipif( not shutil.which("incus"), - reason="incus/lxc not installed", + reason="incus not installed", ) diff --git a/doc/source/lxc.rst b/doc/source/lxc.rst index da0a8fab..5662d305 100644 --- a/doc/source/lxc.rst +++ b/doc/source/lxc.rst @@ -1,21 +1,15 @@ Local testing with LXC/Incus ============================ -.. warning:: - - cmdeploy LXC support is geared towards local testing and CI, only. - Do not base production setups on it. - - The ``cmdeploy`` tool includes support for running chatmail relays inside local `Incus `_ LXC containers. -This is useful for development, testing, and CI +This is meant for development, testing, and CI without requiring a remote server. -LXC system containers behave like lightweight virtual machines. -They share the host's kernel but run their own init system -(systemd), package manager, and network stack, -so the cmdeploy deployment scripts work exactly +LXC system containers are lightweight virtual machines +that share the host's kernel but run their own init system, +package manager, and network stack, +so the cmdeploy deployment scripts work pretty much as they would on a real Debian server or cloud VPS. Prerequisites @@ -32,6 +26,16 @@ After installing incus, initialise and grant yourself access:: sudo incus admin init --minimal sudo usermod -aG incus-admin $USER + +.. caution:: + + Adding yourself to ``incus-admin`` grants effective root access + to the host: any member can mount host directories into a container + and manipulate them as root. + This is fine for local testing of your own relay branches, + but do **not** use it for production setups + or for testing untrusted relay branches from others. + .. warning:: You **must now log out and back in** (or run ``newgrp incus-admin``) @@ -53,11 +57,13 @@ Quick start source venv/bin/activate # activate venv cmdeploy lxc-test # create containers, deploy, test - -The ``lxc-test`` command executes each ``cmdeploy`` subprocess command -so you can copy-paste and run them individually. +The ``lxc-test`` command provides an automated way +to run the full deployment and test pipeline. +It executes several ``cmdeploy`` subcommands in sequential steps. +If a step fails, you can copy-paste the printed command +and run it manually to debug. No host DNS delegation or ``~/.ssh/config`` changes are needed -because lxc-test passes ssh-related CLI options to "cmdeploy run" and "cmdeploy test" commands. +because ``lxc-test`` passes the required SSH and DNS options directly. CLI reference @@ -86,20 +92,6 @@ CLI reference User containers are **never** destroyed unless named explicitly. ``lxc-test [--one]`` - Idempotent full pipeline: - - 1. ``lxc-start``: create ``test0`` + ``test1`` - containers, minimal DNS - - 2. ``cmdeploy run``: deploy chatmail services on each relay - - 3. locally cache ``localchat-relay`` image after first successful deploy - - 4. ``cmdeploy dns --zonefile``: generate standard - BIND-format zone files, load full DNS records - - 5. ``cmdeploy test``: run full test suite - By default creates, deploys, and tests both ``test0`` and ``test1`` for dual-domain federation testing (sets ``CHATMAIL_DOMAIN2=_test1.localchat``). test0 runs dual-stack (IPv4 + IPv6) while test1 runs IPv4-only (``disable_ipv6 = True``).