Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmdeploy/src/cmdeploy/basedeploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
from pyinfra.operations import files, server, systemd


def has_systemd():
"""Returns False during Docker image builds or any other non-systemd environment."""
return os.path.isdir("/run/systemd/system")


def get_resource(arg, pkg=__package__):
return importlib.resources.files(pkg).joinpath(arg)

Expand Down
5 changes: 4 additions & 1 deletion cmdeploy/src/cmdeploy/cmdeploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ def run_cmd(args, out):

cmd = f"{pyinf} --ssh-user root {ssh_host} {deploy_path} -y"
if ssh_host in ["localhost", "@docker"]:
if ssh_host == "@docker":
env["CHATMAIL_NOPORTCHECK"] = "True"
env["CHATMAIL_NOSYSCTL"] = "True"
cmd = f"{pyinf} @local {deploy_path} -y"

if version.parse(pyinfra.__version__) < version.parse("3"):
Expand Down Expand Up @@ -336,7 +339,7 @@ def add_config_option(parser):
"--config",
dest="inipath",
action="store",
default=Path("chatmail.ini"),
default=Path(os.environ.get("CHATMAIL_INI", "chatmail.ini")),
type=Path,
help="path to the chatmail.ini file",
)
Expand Down
77 changes: 41 additions & 36 deletions cmdeploy/src/cmdeploy/deployers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Chat Mail pyinfra deploy.
"""

import os
import shutil
import subprocess
import sys
Expand All @@ -26,6 +27,7 @@
activate_remote_units,
configure_remote_units,
get_resource,
has_systemd,
)
from .dovecot.deployer import DovecotDeployer
from .filtermail.deployer import FiltermailDeployer
Expand Down Expand Up @@ -66,6 +68,8 @@ def _build_chatmaild(dist_dir) -> None:


def remove_legacy_artifacts():
if not has_systemd():
return
# disable legacy doveauth-dictproxy.service
if host.get_fact(SystemdEnabled).get("doveauth-dictproxy.service"):
systemd.service(
Expand Down Expand Up @@ -300,7 +304,7 @@ def install(self):
present=False,
)
# remove echobot if it is still running
if host.get_fact(SystemdEnabled).get("echobot.service"):
if has_systemd() and host.get_fact(SystemdEnabled).get("echobot.service"):
systemd.service(
name="Disable echobot.service",
service="echobot.service",
Expand Down Expand Up @@ -567,41 +571,42 @@ def deploy_chatmail(config_path: Path, disable_mail: bool, website_only: bool) -
Out().red(f"Deploy failed: mtail_address {config.mtail_address} is not available (VPN up?).\n")
exit(1)

port_services = [
(["master", "smtpd"], 25),
("unbound", 53),
]
if config.tls_cert_mode == "acme":
port_services.append(("acmetool", 402))
port_services += [
(["imap-login", "dovecot"], 143),
# acmetool previously listened on port 80,
# so don't complain during upgrade that moved it to port 402
# and gave the port to nginx.
(["acmetool", "nginx"], 80),
("nginx", 443),
(["master", "smtpd"], 465),
(["master", "smtpd"], 587),
(["imap-login", "dovecot"], 993),
("iroh-relay", 3340),
("mtail", 3903),
("stats", 3904),
("nginx", 8443),
(["master", "smtpd"], config.postfix_reinject_port),
(["master", "smtpd"], config.postfix_reinject_port_incoming),
("filtermail", config.filtermail_smtp_port),
("filtermail", config.filtermail_smtp_port_incoming),
]
for service, port in port_services:
print(f"Checking if port {port} is available for {service}...")
running_service = host.get_fact(Port, port=port)
services = [service] if isinstance(service, str) else service
if running_service:
if running_service not in services:
Out().red(
f"Deploy failed: port {port} is occupied by: {running_service}"
)
exit(1)
if not os.environ.get("CHATMAIL_NOPORTCHECK"):
port_services = [
(["master", "smtpd"], 25),
("unbound", 53),
]
if config.tls_cert_mode == "acme":
port_services.append(("acmetool", 402))
port_services += [
(["imap-login", "dovecot"], 143),
# acmetool previously listened on port 80,
# so don't complain during upgrade that moved it to port 402
# and gave the port to nginx.
(["acmetool", "nginx"], 80),
("nginx", 443),
(["master", "smtpd"], 465),
(["master", "smtpd"], 587),
(["imap-login", "dovecot"], 993),
("iroh-relay", 3340),
("mtail", 3903),
("stats", 3904),
("nginx", 8443),
(["master", "smtpd"], config.postfix_reinject_port),
(["master", "smtpd"], config.postfix_reinject_port_incoming),
("filtermail", config.filtermail_smtp_port),
("filtermail", config.filtermail_smtp_port_incoming),
]
for service, port in port_services:
print(f"Checking if port {port} is available for {service}...")
running_service = host.get_fact(Port, port=port)
services = [service] if isinstance(service, str) else service
if running_service:
if running_service not in services:
Out().red(
f"Deploy failed: port {port} is occupied by: {running_service}"
)
exit(1)

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

Expand Down
37 changes: 21 additions & 16 deletions cmdeploy/src/cmdeploy/dovecot/deployer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from chatmaild.config import Config
from pyinfra import host
from pyinfra.facts.server import Arch, Sysctl
Expand All @@ -9,6 +11,7 @@
activate_remote_units,
configure_remote_units,
get_resource,
has_systemd,
)


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

def install(self):
arch = host.get_fact(Arch)
if not host.get_fact(SystemdEnabled).get("dovecot.service"):
_install_dovecot_package("core", arch)
_install_dovecot_package("imapd", arch)
_install_dovecot_package("lmtpd", arch)
if has_systemd() and "dovecot.service" in host.get_fact(SystemdEnabled):
return # already installed and running
Comment thread
j4n marked this conversation as resolved.
_install_dovecot_package("core", arch)
_install_dovecot_package("imapd", arch)
_install_dovecot_package("lmtpd", arch)

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

# as per https://doc.dovecot.org/2.3/configuration_manual/os/
# it is recommended to set the following inotify limits
for name in ("max_user_instances", "max_user_watches"):
key = f"fs.inotify.{name}"
if host.get_fact(Sysctl)[key] > 65535:
# Skip updating limits if already sufficient
# (enables running in incus containers where sysctl readonly)
continue
server.sysctl(
name=f"Change {key}",
key=key,
value=65535,
persist=True,
)
if not os.environ.get("CHATMAIL_NOSYSCTL"):
for name in ("max_user_instances", "max_user_watches"):
key = f"fs.inotify.{name}"
if host.get_fact(Sysctl)[key] > 65535:
# Skip updating limits if already sufficient
# (enables running in incus containers where sysctl readonly)
continue
server.sysctl(
name=f"Change {key}",
key=key,
value=65535,
persist=True,
)

timezone_env = files.line(
name="Set TZ environment variable",
Expand Down