From 825831ee81f50982b4acc21e34600e117e352649 Mon Sep 17 00:00:00 2001 From: j4n Date: Mon, 20 Apr 2026 09:32:29 +0200 Subject: [PATCH] fix(cmdeploy): replace resolvconf/systemd-resolved with static resolv.conf, purge 1e376f7 explicitly installed resolvconf to fix DNS breakage after ff541b8 disabled APT recommends. But resolvconf adds dynamic resolver management and is unnecessary on a server. Similarly, systemd-resolved competes for /etc/resolv.conf. Write a static resolv.conf with unbound as primary and 9.9.9.9 as fallback. Purge resolvconf, stop and mask systemd-resolved to prevent it from overriding the static configuration. --- cmdeploy/src/cmdeploy/deployers.py | 38 ++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/cmdeploy/src/cmdeploy/deployers.py b/cmdeploy/src/cmdeploy/deployers.py index 8a67107e..0a759a5f 100644 --- a/cmdeploy/src/cmdeploy/deployers.py +++ b/cmdeploy/src/cmdeploy/deployers.py @@ -150,18 +150,40 @@ class UnboundDeployer(Deployer): self.need_restart = False def install(self): - # Run local DNS resolver `unbound`. `resolvconf` takes care of - # setting up /etc/resolv.conf to use 127.0.0.1 as the resolver. - # 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(): apt.packages( name="Install unbound", - packages=["unbound", "unbound-anchor", "dnsutils", "resolvconf"], + packages=["unbound", "unbound-anchor", "dnsutils"], ) def configure(self): + # Remove dynamic resolver managers that compete for /etc/resolv.conf. + apt.packages( + name="Purge resolvconf", + packages=["resolvconf"], + present=False, + extra_uninstall_args="--purge", + ) + # systemd-resolved can't be purged due to dependencies; stop and mask. + server.shell( + name="Stop and mask systemd-resolved", + commands=[ + "systemctl stop systemd-resolved.service || true", + "systemctl mask systemd-resolved.service", + ], + ) + # Configure unbound resolver with Quad9 fallback and a trailing newline + # (SolusVM bug). + files.put( + name="Write static resolv.conf", + src=BytesIO(b"nameserver 127.0.0.1\nnameserver 9.9.9.9\n"), + dest="/etc/resolv.conf", + user="root", + group="root", + mode="644", + ) server.shell( name="Generate root keys for validating DNSSEC", commands=[ @@ -568,14 +590,6 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) - Deployment().perform_stages([WebsiteDeployer(config)]) return - if host.get_fact(Port, port=53) != "unbound": - files.line( - name="Add 9.9.9.9 to resolv.conf", - path="/etc/resolv.conf", - # Guard against resolv.conf missing a trailing newline (SolusVM bug). - line="\nnameserver 9.9.9.9", - ) - # Check if mtail_address interface is available (if configured) if config.mtail_address and config.mtail_address not in ('127.0.0.1', '::1', 'localhost'): ipv4_addrs = host.get_fact(hardware.Ipv4Addrs)