Skip to content

Commit da15829

Browse files
j4nKeonik1missytake
committed
cmdeploy: prepare chatmaild/cmdeploy changes for Docker support
- chatmaild: - basedeploy.py: Add has_systemd() guard. During Docker image builds there's no running systemd, so deployers that query SystemdEnabled facts would crash; this change might also be helpful for non-systemd platforms. - cmdeploy: - cmdeploy.py: - when deploying to @docker, auto-set CHATMAIL_NOPORTCHECK and CHATMAIL_NOSYSCTL since neither makes sense inside a container - --config default now reads CHATMAIL_INI env var, so Docker entrypoints can point to a mounted ini without CLI flags. - deployers.py: - skip port check / CHATMAIL_NOPORTCHECK - skip echobot systemd cleanup w/ has_systemd - dovecot/deployer.py: - Guard sysctl writes behind CHATMAIL_NOSYSCTL - invert dovecot install check so it works without systemd - sshexec.py: Add __call__ to LocalExec so cmdeploy status works with @Local target. Without it, cmdeploy status tried to call the executor directly and got TypeError. Consolidated from j4n/docker branch commits (selection): - 8953fde feat(cmdeploy): read CHATMAIL_INI env var for default --config path - 81d7782 fix(cmdeploy): add __call__ to LocalExec so status works with @Local - 8bba78e docker: disable port check if docker is running. fix #694 - 865b514 docker: replace config flags with env vars, drop docker param (instead of f26cb08) Files: cmdeploy/src/cmdeploy/{basedeploy,cmdeploy,deployers,sshexec,dovecot/deployer}.py Co-authored-by: Keonik1 <keonik.dev@gmail.com> Co-authored-by: missytake <missytake@systemli.org>
1 parent 38cc1c7 commit da15829

4 files changed

Lines changed: 67 additions & 49 deletions

File tree

cmdeploy/src/cmdeploy/basedeploy.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
from pyinfra.operations import files, server, systemd
66

77

8+
def has_systemd():
9+
"""Returns False during Docker image builds or any other non-systemd environment."""
10+
return os.path.isdir("/run/systemd/system")
11+
12+
813
def get_resource(arg, pkg=__package__):
914
return importlib.resources.files(pkg).joinpath(arg)
1015

cmdeploy/src/cmdeploy/cmdeploy.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ def run_cmd(args, out):
110110

111111
cmd = f"{pyinf} --ssh-user root {ssh_host} {deploy_path} -y"
112112
if ssh_host in ["localhost", "@docker"]:
113+
if ssh_host == "@docker":
114+
env["CHATMAIL_NOPORTCHECK"] = "True"
115+
env["CHATMAIL_NOSYSCTL"] = "True"
113116
cmd = f"{pyinf} @local {deploy_path} -y"
114117

115118
if version.parse(pyinfra.__version__) < version.parse("3"):
@@ -336,7 +339,7 @@ def add_config_option(parser):
336339
"--config",
337340
dest="inipath",
338341
action="store",
339-
default=Path("chatmail.ini"),
342+
default=Path(os.environ.get("CHATMAIL_INI", "chatmail.ini")),
340343
type=Path,
341344
help="path to the chatmail.ini file",
342345
)

cmdeploy/src/cmdeploy/deployers.py

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Chat Mail pyinfra deploy.
33
"""
44

5+
import os
56
import shutil
67
import subprocess
78
import sys
@@ -26,6 +27,7 @@
2627
activate_remote_units,
2728
configure_remote_units,
2829
get_resource,
30+
has_systemd,
2931
)
3032
from .dovecot.deployer import DovecotDeployer
3133
from .filtermail.deployer import FiltermailDeployer
@@ -66,6 +68,8 @@ def _build_chatmaild(dist_dir) -> None:
6668

6769

6870
def remove_legacy_artifacts():
71+
if not has_systemd():
72+
return
6973
# disable legacy doveauth-dictproxy.service
7074
if host.get_fact(SystemdEnabled).get("doveauth-dictproxy.service"):
7175
systemd.service(
@@ -300,7 +304,7 @@ def install(self):
300304
present=False,
301305
)
302306
# remove echobot if it is still running
303-
if host.get_fact(SystemdEnabled).get("echobot.service"):
307+
if has_systemd() and host.get_fact(SystemdEnabled).get("echobot.service"):
304308
systemd.service(
305309
name="Disable echobot.service",
306310
service="echobot.service",
@@ -567,37 +571,38 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
567571
Out().red(f"Deploy failed: mtail_address {config.mtail_address} is not available (VPN up?).\n")
568572
exit(1)
569573

570-
port_services = [
571-
(["master", "smtpd"], 25),
572-
("unbound", 53),
573-
]
574-
if config.tls_cert_mode == "acme":
575-
port_services.append(("acmetool", 80))
576-
port_services += [
577-
(["imap-login", "dovecot"], 143),
578-
("nginx", 443),
579-
(["master", "smtpd"], 465),
580-
(["master", "smtpd"], 587),
581-
(["imap-login", "dovecot"], 993),
582-
("iroh-relay", 3340),
583-
("mtail", 3903),
584-
("stats", 3904),
585-
("nginx", 8443),
586-
(["master", "smtpd"], config.postfix_reinject_port),
587-
(["master", "smtpd"], config.postfix_reinject_port_incoming),
588-
("filtermail", config.filtermail_smtp_port),
589-
("filtermail", config.filtermail_smtp_port_incoming),
590-
]
591-
for service, port in port_services:
592-
print(f"Checking if port {port} is available for {service}...")
593-
running_service = host.get_fact(Port, port=port)
594-
services = [service] if isinstance(service, str) else service
595-
if running_service:
596-
if running_service not in services:
597-
Out().red(
598-
f"Deploy failed: port {port} is occupied by: {running_service}"
599-
)
600-
exit(1)
574+
if not os.environ.get("CHATMAIL_NOPORTCHECK"):
575+
port_services = [
576+
(["master", "smtpd"], 25),
577+
("unbound", 53),
578+
]
579+
if config.tls_cert_mode == "acme":
580+
port_services.append(("acmetool", 80))
581+
port_services += [
582+
(["imap-login", "dovecot"], 143),
583+
("nginx", 443),
584+
(["master", "smtpd"], 465),
585+
(["master", "smtpd"], 587),
586+
(["imap-login", "dovecot"], 993),
587+
("iroh-relay", 3340),
588+
("mtail", 3903),
589+
("stats", 3904),
590+
("nginx", 8443),
591+
(["master", "smtpd"], config.postfix_reinject_port),
592+
(["master", "smtpd"], config.postfix_reinject_port_incoming),
593+
("filtermail", config.filtermail_smtp_port),
594+
("filtermail", config.filtermail_smtp_port_incoming),
595+
]
596+
for service, port in port_services:
597+
print(f"Checking if port {port} is available for {service}...")
598+
running_service = host.get_fact(Port, port=port)
599+
services = [service] if isinstance(service, str) else service
600+
if running_service:
601+
if running_service not in services:
602+
Out().red(
603+
f"Deploy failed: port {port} is occupied by: {running_service}"
604+
)
605+
exit(1)
601606

602607
tls_domains = [mail_domain, f"mta-sts.{mail_domain}", f"www.{mail_domain}"]
603608

cmdeploy/src/cmdeploy/dovecot/deployer.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
from chatmaild.config import Config
24
from pyinfra import host
35
from pyinfra.facts.server import Arch, Sysctl
@@ -9,6 +11,7 @@
911
activate_remote_units,
1012
configure_remote_units,
1113
get_resource,
14+
has_systemd,
1215
)
1316

1417

@@ -22,10 +25,11 @@ def __init__(self, config, disable_mail):
2225

2326
def install(self):
2427
arch = host.get_fact(Arch)
25-
if not host.get_fact(SystemdEnabled).get("dovecot.service"):
26-
_install_dovecot_package("core", arch)
27-
_install_dovecot_package("imapd", arch)
28-
_install_dovecot_package("lmtpd", arch)
28+
if has_systemd() and "dovecot.service" in host.get_fact(SystemdEnabled):
29+
return # already installed and running
30+
_install_dovecot_package("core", arch)
31+
_install_dovecot_package("imapd", arch)
32+
_install_dovecot_package("lmtpd", arch)
2933

3034
def configure(self):
3135
configure_remote_units(self.config.mail_domain, self.units)
@@ -116,18 +120,19 @@ def _configure_dovecot(config: Config, debug: bool = False) -> (bool, bool):
116120

117121
# as per https://doc.dovecot.org/2.3/configuration_manual/os/
118122
# it is recommended to set the following inotify limits
119-
for name in ("max_user_instances", "max_user_watches"):
120-
key = f"fs.inotify.{name}"
121-
if host.get_fact(Sysctl)[key] > 65535:
122-
# Skip updating limits if already sufficient
123-
# (enables running in incus containers where sysctl readonly)
124-
continue
125-
server.sysctl(
126-
name=f"Change {key}",
127-
key=key,
128-
value=65535,
129-
persist=True,
130-
)
123+
if not os.environ.get("CHATMAIL_NOSYSCTL"):
124+
for name in ("max_user_instances", "max_user_watches"):
125+
key = f"fs.inotify.{name}"
126+
if host.get_fact(Sysctl)[key] > 65535:
127+
# Skip updating limits if already sufficient
128+
# (enables running in incus containers where sysctl readonly)
129+
continue
130+
server.sysctl(
131+
name=f"Change {key}",
132+
key=key,
133+
value=65535,
134+
persist=True,
135+
)
131136

132137
timezone_env = files.line(
133138
name="Set TZ environment variable",

0 commit comments

Comments
 (0)