Skip to content

Commit 00d723b

Browse files
committed
refactor: deployer improvements (VM detection, mailboxes dir ensured to be there, proper unbound on ipv4)
1 parent c257bfc commit 00d723b

File tree

4 files changed

+58
-46
lines changed

4 files changed

+58
-46
lines changed

cmdeploy/src/cmdeploy/cmdeploy.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ def run_cmd(args, out):
111111
if ssh_host in ["localhost", "@docker"]:
112112
if ssh_host == "@docker":
113113
env["CHATMAIL_NOPORTCHECK"] = "True"
114-
env["CHATMAIL_NOSYSCTL"] = "True"
115114
cmd = f"{pyinf} @local {deploy_path} -y"
116115

117116
if version.parse(pyinfra.__version__) < version.parse("3"):

cmdeploy/src/cmdeploy/deployers.py

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
Deployer,
2525
Deployment,
2626
activate_remote_units,
27+
blocked_service_startup,
2728
configure_remote_units,
2829
get_resource,
2930
has_systemd,
@@ -149,33 +150,16 @@ def __init__(self, config):
149150
self.need_restart = False
150151

151152
def install(self):
152-
# Run local DNS resolver `unbound`.
153-
# `resolvconf` takes care of setting up /etc/resolv.conf
154-
# to use 127.0.0.1 as the resolver.
155-
156-
#
157-
# On an IPv4-only system, if unbound is started but not
158-
# configured, it causes subsequent steps to fail to resolve hosts.
159-
# Here, we use policy-rc.d to prevent unbound from starting up
160-
# on initial install. Later, we will configure it and start it.
161-
#
162-
# For documentation about policy-rc.d, see:
163-
# https://people.debian.org/~hmh/invokerc.d-policyrc.d-specification.txt
164-
#
165-
files.put(
166-
src=get_resource("policy-rc.d"),
167-
dest="/usr/sbin/policy-rc.d",
168-
user="root",
169-
group="root",
170-
mode="755",
171-
)
172-
173-
apt.packages(
174-
name="Install unbound",
175-
packages=["unbound", "unbound-anchor", "dnsutils"],
176-
)
177-
178-
files.file("/usr/sbin/policy-rc.d", present=False)
153+
# Run local DNS resolver `unbound`. `resolvconf` takes care of
154+
# setting up /etc/resolv.conf to use 127.0.0.1 as the resolver.
155+
156+
# On an IPv4-only system, if unbound is started but not configured,
157+
# it causes subsequent steps to fail to resolve hosts.
158+
with blocked_service_startup():
159+
apt.packages(
160+
name="Install unbound",
161+
packages=["unbound", "unbound-anchor", "dnsutils"],
162+
)
179163

180164
def configure(self):
181165
server.shell(
@@ -474,8 +458,9 @@ class ChatmailDeployer(Deployer):
474458
("iroh", None, None),
475459
]
476460

477-
def __init__(self, mail_domain):
478-
self.mail_domain = mail_domain
461+
def __init__(self, config):
462+
self.config = config
463+
self.mail_domain = config.mail_domain
479464

480465
def install(self):
481466
files.put(
@@ -500,6 +485,16 @@ def install(self):
500485
)
501486

502487
def configure(self):
488+
# metadata crashes if the mailboxes dir does not exist
489+
files.directory(
490+
name="Ensure vmail mailbox directory exists",
491+
path=str(self.config.mailboxes_dir),
492+
user="vmail",
493+
group="vmail",
494+
mode="700",
495+
present=True,
496+
)
497+
503498
# This file is used by auth proxy.
504499
# https://wiki.debian.org/EtcMailName
505500
server.shell(
@@ -629,7 +624,7 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
629624
tls_deployer = get_tls_deployer(config, mail_domain)
630625

631626
all_deployers = [
632-
ChatmailDeployer(mail_domain),
627+
ChatmailDeployer(config),
633628
LegacyRemoveDeployer(),
634629
FiltermailDeployer(),
635630
JournaldDeployer(),

cmdeploy/src/cmdeploy/dovecot/deployer.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import io
2-
import os
32
import urllib.request
43

54
from chatmaild.config import Config
65
from pyinfra import host
76
from pyinfra.facts.deb import DebPackages
8-
from pyinfra.facts.server import Arch, Sysctl
7+
from pyinfra.facts.server import Arch, Command, Sysctl
98
from pyinfra.operations import apt, files, server, systemd
109

1110
from cmdeploy.basedeploy import (
@@ -128,7 +127,18 @@ def _download_dovecot_package(package: str, arch: str):
128127
return deb_filename
129128

130129

131-
def _configure_dovecot(config: Config, debug: bool = False) -> (bool, bool):
130+
def _can_set_inotify_limits() -> bool:
131+
is_container = (
132+
host.get_fact(
133+
Command,
134+
"systemd-detect-virt --container --quiet 2>/dev/null && echo yes || true",
135+
)
136+
== "yes"
137+
)
138+
return not is_container
139+
140+
141+
def _configure_dovecot(config: Config, debug: bool = False) -> tuple[bool, bool]:
132142
"""Configures Dovecot IMAP server."""
133143
need_restart = False
134144
daemon_reload = False
@@ -163,19 +173,25 @@ def _configure_dovecot(config: Config, debug: bool = False) -> (bool, bool):
163173

164174
# as per https://doc.dovecot.org/2.3/configuration_manual/os/
165175
# it is recommended to set the following inotify limits
166-
if not os.environ.get("CHATMAIL_NOSYSCTL"):
167-
for name in ("max_user_instances", "max_user_watches"):
168-
key = f"fs.inotify.{name}"
169-
if host.get_fact(Sysctl)[key] > 65535:
170-
# Skip updating limits if already sufficient
171-
# (enables running in incus containers where sysctl readonly)
172-
continue
173-
server.sysctl(
174-
name=f"Change {key}",
175-
key=key,
176-
value=65535,
177-
persist=True,
176+
can_modify = _can_set_inotify_limits()
177+
for name in ("max_user_instances", "max_user_watches"):
178+
key = f"fs.inotify.{name}"
179+
value = host.get_fact(Sysctl)[key]
180+
if value > 65534:
181+
continue
182+
if not can_modify:
183+
print(
184+
"\n!!!! refusing to attempt sysctl setting in containers\n"
185+
f"!!!! dovecot: sysctl {key!r}={value}, should be >65534 for production setups\n"
186+
"!!!!"
178187
)
188+
continue
189+
server.sysctl(
190+
name=f"Change {key}",
191+
key=key,
192+
value=65535,
193+
persist=True,
194+
)
179195

180196
timezone_env = files.line(
181197
name="Set TZ environment variable",

cmdeploy/src/cmdeploy/selfsigned/deployer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ def openssl_selfsigned_args(domain, cert_path, key_path, days=36500):
1818
"-keyout", str(key_path),
1919
"-out", str(cert_path),
2020
"-subj", f"/CN={domain}",
21+
# Mark as end-entity cert so it cannot be used as a CA to sign others.
22+
"-addext", "basicConstraints=critical,CA:FALSE",
2123
"-addext", "extendedKeyUsage=serverAuth,clientAuth",
2224
"-addext",
2325
f"subjectAltName=DNS:{domain},DNS:www.{domain},DNS:mta-sts.{domain}",

0 commit comments

Comments
 (0)