diff --git a/CHANGELOG.md b/CHANGELOG.md index b83b0a35..73af2899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ - Enforce end-to-end encryption between local addresses ([#535](https://github.com/chatmail/server/pull/535)) -- Disable NSD so it doesn't block port 53 +- unbound: check that port 53 is not occupied by a different process ([#537](https://github.com/chatmail/server/pull/537)) - Limit the bind for the HTTPS server on 8443 to 127.0.0.1 diff --git a/cmdeploy/src/cmdeploy/__init__.py b/cmdeploy/src/cmdeploy/__init__.py index d60f8820..0ac35fff 100644 --- a/cmdeploy/src/cmdeploy/__init__.py +++ b/cmdeploy/src/cmdeploy/__init__.py @@ -11,6 +11,7 @@ from pathlib import Path from chatmaild.config import Config, read_config from pyinfra import facts, host +from pyinfra.api import FactBase from pyinfra.facts.files import File from pyinfra.facts.systemd import SystemdEnabled from pyinfra.operations import apt, files, pip, server, systemd @@ -18,6 +19,21 @@ from pyinfra.operations import apt, files, pip, server, systemd from .acmetool import deploy_acmetool +class Port(FactBase): + """ + Returns the process occuping a port. + """ + + def command(self, port: int) -> str: + return ( + "ss -lptn 'src :%d' | awk 'NR>1 {print $6,$7}' | sed 's/users:((\"//;s/\".*//'" + % (port,) + ) + + def process(self, output: [str]) -> str: + return output[0] + + def _build_chatmaild(dist_dir) -> None: dist_dir = Path(dist_dir).resolve() if dist_dir.exists(): @@ -230,7 +246,6 @@ def _configure_opendkim(domain: str, dkim_selector: str = "dkim") -> bool: ) need_restart |= service_file.changed - return need_restart @@ -588,12 +603,12 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: # Run local DNS resolver `unbound`. # `resolvconf` takes care of setting up /etc/resolv.conf # to use 127.0.0.1 as the resolver. - systemd.service( - name="Disable nsd if it's running, so it doesn't block port 53", - service="nsd.service", - running=False, - enabled=False, - ) + from cmdeploy.cmdeploy import Out + + process_on_53 = host.get_fact(Port, port=53) + if process_on_53 not in (None, "unbound"): + Out().red(f"Can't install unbound: port 53 is occupied by: {process_on_53}") + exit(1) apt.packages( name="Install unbound", packages=["unbound", "unbound-anchor", "dnsutils"], diff --git a/cmdeploy/src/cmdeploy/cmdeploy.py b/cmdeploy/src/cmdeploy/cmdeploy.py index 3904e1d5..59187734 100644 --- a/cmdeploy/src/cmdeploy/cmdeploy.py +++ b/cmdeploy/src/cmdeploy/cmdeploy.py @@ -86,15 +86,19 @@ def run_cmd(args, out): out.red("Please re-run scripts/initenv.sh to update pyinfra to version 3.") return 1 - retcode = out.check_call(cmd, env=env) - if retcode == 0: - out.green("Deploy completed, call `cmdeploy dns` next.") - elif not remote_data["acme_account_url"]: - out.red("Deploy completed but letsencrypt not configured") - out.red("Run 'cmdeploy run' again") - retcode = 0 - else: + try: + retcode = out.check_call(cmd, env=env) + if retcode == 0: + out.green("Deploy completed, call `cmdeploy dns` next.") + elif not remote_data["acme_account_url"]: + out.red("Deploy completed but letsencrypt not configured") + out.red("Run 'cmdeploy run' again") + retcode = 0 + else: + out.red("Deploy failed") + except subprocess.CalledProcessError: out.red("Deploy failed") + retcode = 1 return retcode