refactor(cmdeploy): share deployed path constants

This commit is contained in:
ccclxxiii
2026-06-25 09:26:01 -05:00
parent 2681ca312e
commit d7a16d2be1
13 changed files with 285 additions and 150 deletions
+9 -7
View File
@@ -1,5 +1,7 @@
from pyinfra.operations import apt, server from pyinfra.operations import apt, server
from cmdeploy.constants import ACME_PATHS, CONFIG_FILES
from ..basedeploy import Deployer from ..basedeploy import Deployer
@@ -14,30 +16,30 @@ class AcmetoolDeployer(Deployer):
packages=["acmetool"], packages=["acmetool"],
) )
self.remove_file("/etc/cron.d/acmetool") self.remove_file(CONFIG_FILES["cron_acmetool"])
self.put_executable("acmetool/acmetool.hook", "/etc/acme/hooks/nginx") self.put_executable("acmetool/acmetool.hook", ACME_PATHS["hook_nginx"])
self.remove_file("/usr/lib/acme/hooks/nginx") self.remove_file(ACME_PATHS["legacy_hook_nginx"])
def configure(self): def configure(self):
self.put_template( self.put_template(
"acmetool/response-file.yaml.j2", "acmetool/response-file.yaml.j2",
"/var/lib/acme/conf/responses", ACME_PATHS["responses"],
email=self.email, email=self.email,
) )
self.put_template( self.put_template(
"acmetool/target.yaml.j2", "acmetool/target.yaml.j2",
"/var/lib/acme/conf/target", ACME_PATHS["target"],
) )
server.shell( server.shell(
name=f"Remove old acmetool desired files for {self.domains[0]}", name=f"Remove old acmetool desired files for {self.domains[0]}",
commands=[f"rm -f /var/lib/acme/desired/{self.domains[0]}-*"], commands=[f"rm -f {ACME_PATHS['desired_dir']}/{self.domains[0]}-*"],
) )
self.put_template( self.put_template(
"acmetool/desired.yaml.j2", "acmetool/desired.yaml.j2",
f"/var/lib/acme/desired/{self.domains[0]}", f"{ACME_PATHS['desired_dir']}/{self.domains[0]}",
domains=self.domains, domains=self.domains,
) )
+4 -3
View File
@@ -8,6 +8,8 @@ from pyinfra.facts.files import Sha256File
from pyinfra.facts.server import Command from pyinfra.facts.server import Command
from pyinfra.operations import files, server, systemd from pyinfra.operations import files, server, systemd
from cmdeploy.constants import CHATMAILD_PATHS
def has_systemd(): def has_systemd():
"""Returns False during Docker image builds or any other non-systemd environment.""" """Returns False during Docker image builds or any other non-systemd environment."""
@@ -52,9 +54,8 @@ def get_resource(arg, pkg=__package__):
def configure_remote_units(deployer, mail_domain, units) -> None: def configure_remote_units(deployer, mail_domain, units) -> None:
remote_base_dir = "/usr/local/lib/chatmaild" remote_venv_dir = CHATMAILD_PATHS["venv_dir"]
remote_venv_dir = f"{remote_base_dir}/venv" remote_chatmail_inipath = CHATMAILD_PATHS["config"]
remote_chatmail_inipath = f"{remote_base_dir}/chatmail.ini"
# install systemd units # install systemd units
for fn in units: for fn in units:
+91
View File
@@ -0,0 +1,91 @@
CHATMAILD_PATHS = {
"base_dir": "/usr/local/lib/chatmaild",
"dist_dir": "/usr/local/lib/chatmaild/dist",
"venv_dir": "/usr/local/lib/chatmaild/venv",
"config": "/usr/local/lib/chatmaild/chatmail.ini",
}
BINARY_PATHS = {
"chatmail_turn": "/usr/local/bin/chatmail-turn",
"filtermail": "/usr/local/bin/filtermail",
"iroh_relay": "/usr/local/bin/iroh-relay",
"mtail": "/usr/local/bin/mtail",
}
CONFIG_DIRS = {
"dkimkeys": "/etc/dkimkeys",
"dovecot": "/etc/dovecot",
"mtail": "/etc/mtail",
"nginx": "/etc/nginx",
"opendkim": "/etc/opendkim",
"postfix": "/etc/postfix",
"unbound": "/etc/unbound",
}
CONFIG_FILES = {
"apt_install_recommends": "/etc/apt/apt.conf.d/00InstallRecommends",
"apt_obs_keyring": "/etc/apt/keyrings/obs-home-deltachat.gpg",
"apt_pin_dovecot": "/etc/apt/preferences.d/pin-dovecot",
"chatmail_nocreate": "/etc/chatmail-nocreate",
"chatmail_version": "/etc/chatmail-version",
"cron_acmetool": "/etc/cron.d/acmetool",
"cron_chatmail_metrics": "/etc/cron.d/chatmail-metrics",
"cron_expunge": "/etc/cron.d/expunge",
"dovecot_auth": "/etc/dovecot/auth.conf",
"dovecot_conf": "/etc/dovecot/dovecot.conf",
"dovecot_push_notification": "/etc/dovecot/push_notification.lua",
"iroh_relay": "/etc/iroh-relay.toml",
"journald": "/etc/systemd/journald.conf",
"mailname": "/etc/mailname",
"mtail_program": "/etc/mtail/delivered_mail.mtail",
"mtasts_daemon": "/etc/mta-sts-daemon.yml",
"nginx_conf": "/etc/nginx/nginx.conf",
"opendkim_conf": "/etc/opendkim.conf",
"policy_rc_d": "/usr/sbin/policy-rc.d",
"postfix_lmtp_header_cleanup": "/etc/postfix/lmtp_header_cleanup",
"postfix_login_map": "/etc/postfix/login_map",
"postfix_main": "/etc/postfix/main.cf",
"postfix_master": "/etc/postfix/master.cf",
"postfix_smtp_tls_policy_map": "/etc/postfix/smtp_tls_policy_map",
"postfix_smtp_tls_policy_map_db": "/etc/postfix/smtp_tls_policy_map.db",
"postfix_submission_header_cleanup": "/etc/postfix/submission_header_cleanup",
"systemd_dovecot_restart": "/etc/systemd/system/dovecot.service.d/10_restart.conf",
"systemd_mtasts_daemon": "/etc/systemd/system/mta-sts-daemon.service",
"systemd_opendkim_restart": "/etc/systemd/system/opendkim.service.d/10-prevent-memory-leak.conf",
"systemd_postfix_restart": "/etc/systemd/system/postfix@.service.d/10_restart.conf",
"unbound_chatmail": "/etc/unbound/unbound.conf.d/chatmail.conf",
}
WEB_PATHS = {
"html_dir": "/var/www/html",
"autoconfig": "/var/www/html/.well-known/autoconfig/mail/config-v1.1.xml",
"mta_sts": "/var/www/html/.well-known/mta-sts.txt",
"metrics": "/var/www/html/metrics",
"newemail_cgi": "/usr/lib/cgi-bin/newemail.py",
}
ACME_PATHS = {
"etc_dir": "/etc/acme",
"var_dir": "/var/lib/acme",
"hook_nginx": "/etc/acme/hooks/nginx",
"legacy_hook_nginx": "/usr/lib/acme/hooks/nginx",
"responses": "/var/lib/acme/conf/responses",
"target": "/var/lib/acme/conf/target",
"desired_dir": "/var/lib/acme/desired",
}
TLS_PATHS = {
"self_cert": "/etc/ssl/certs/mailserver.pem",
"self_key": "/etc/ssl/private/mailserver.key",
}
STATE_DIRS = {
"from_cmdeploy": "/root/from-cmdeploy",
"postfix_mta_sts_resolver": "/usr/local/lib/postfix-mta-sts-resolver",
"var_lib_dovecot": "/var/lib/dovecot",
"var_lib_nginx": "/var/lib/nginx",
"var_lib_postfix": "/var/lib/postfix",
"var_lib_unbound": "/var/lib/unbound",
"var_log_nginx": "/var/log/nginx",
"var_spool_postfix": "/var/spool/postfix",
}
+37 -26
View File
@@ -16,6 +16,14 @@ from pyinfra.facts.systemd import SystemdEnabled
from pyinfra.operations import apt, files, pip, server, systemd from pyinfra.operations import apt, files, pip, server, systemd
from cmdeploy.cmdeploy import Out from cmdeploy.cmdeploy import Out
from cmdeploy.constants import (
BINARY_PATHS,
CHATMAILD_PATHS,
CONFIG_DIRS,
CONFIG_FILES,
STATE_DIRS,
WEB_PATHS,
)
from .acmetool import AcmetoolDeployer from .acmetool import AcmetoolDeployer
from .basedeploy import ( from .basedeploy import (
@@ -83,16 +91,15 @@ def remove_legacy_artifacts():
def _install_remote_venv_with_chatmaild(deployer) -> None: def _install_remote_venv_with_chatmaild(deployer) -> None:
remove_legacy_artifacts() remove_legacy_artifacts()
dist_file = _build_chatmaild(dist_dir=Path("chatmaild/dist")) dist_file = _build_chatmaild(dist_dir=Path("chatmaild/dist"))
remote_base_dir = "/usr/local/lib/chatmaild" remote_dist_file = f"{CHATMAILD_PATHS['dist_dir']}/{dist_file.name}"
remote_dist_file = f"{remote_base_dir}/dist/{dist_file.name}" remote_venv_dir = CHATMAILD_PATHS["venv_dir"]
remote_venv_dir = f"{remote_base_dir}/venv"
apt.packages( apt.packages(
name="apt install python3-virtualenv", name="apt install python3-virtualenv",
packages=["python3-virtualenv"], packages=["python3-virtualenv"],
) )
deployer.ensure_directory(f"{remote_base_dir}/dist") deployer.ensure_directory(CHATMAILD_PATHS["dist_dir"])
deployer.put_file( deployer.put_file(
src=dist_file.open("rb"), src=dist_file.open("rb"),
dest=remote_dist_file, dest=remote_dist_file,
@@ -118,16 +125,15 @@ def _install_remote_venv_with_chatmaild(deployer) -> None:
def _configure_remote_venv_with_chatmaild(deployer, config) -> None: def _configure_remote_venv_with_chatmaild(deployer, config) -> None:
remote_base_dir = "/usr/local/lib/chatmaild" remote_chatmail_inipath = CHATMAILD_PATHS["config"]
remote_chatmail_inipath = f"{remote_base_dir}/chatmail.ini"
deployer.put_file( deployer.put_file(
src=config._getbytefile(), src=config._getbytefile(),
dest=remote_chatmail_inipath, dest=remote_chatmail_inipath,
) )
deployer.remove_file("/etc/cron.d/chatmail-metrics") deployer.remove_file(CONFIG_FILES["cron_chatmail_metrics"])
deployer.remove_file("/var/www/html/metrics") deployer.remove_file(WEB_PATHS["metrics"])
class UnboundDeployer(Deployer): class UnboundDeployer(Deployer):
@@ -169,15 +175,15 @@ class UnboundDeployer(Deployer):
server.shell( server.shell(
name="Generate root keys for validating DNSSEC", name="Generate root keys for validating DNSSEC",
commands=[ commands=[
"unbound-anchor -a /var/lib/unbound/root.key || true", f"unbound-anchor -a {STATE_DIRS['var_lib_unbound']}/root.key || true",
], ],
) )
self.ensure_directory( self.ensure_directory(
path="/etc/unbound/unbound.conf.d", path=f"{CONFIG_DIRS['unbound']}/unbound.conf.d",
) )
self.put_template( self.put_template(
"unbound/unbound.conf.j2", "unbound/unbound.conf.j2",
"/etc/unbound/unbound.conf.d/chatmail.conf", CONFIG_FILES["unbound_chatmail"],
disable_ipv6=self.config.disable_ipv6, disable_ipv6=self.config.disable_ipv6,
) )
@@ -201,9 +207,9 @@ class UnboundDeployer(Deployer):
class MtastsDeployer(Deployer): class MtastsDeployer(Deployer):
def configure(self): def configure(self):
# Remove configuration. # Remove configuration.
self.remove_file("/etc/mta-sts-daemon.yml") self.remove_file(CONFIG_FILES["mtasts_daemon"])
self.remove_directory("/usr/local/lib/postfix-mta-sts-resolver") self.remove_directory(STATE_DIRS["postfix_mta_sts_resolver"])
self.remove_file("/etc/systemd/system/mta-sts-daemon.service") self.remove_file(CONFIG_FILES["systemd_mtasts_daemon"])
def activate(self): def activate(self):
self.ensure_service( self.ensure_service(
@@ -218,7 +224,7 @@ class WebsiteDeployer(Deployer):
self.config = config self.config = config
def install(self): def install(self):
self.ensure_directory("/var/www") self.ensure_directory(str(Path(WEB_PATHS["html_dir"]).parent))
def configure(self): def configure(self):
www_path, src_dir, build_dir = get_paths(self.config) www_path, src_dir, build_dir = get_paths(self.config)
@@ -238,7 +244,9 @@ class WebsiteDeployer(Deployer):
return return
# if it is not a hugo page, upload it as is # if it is not a hugo page, upload it as is
files.rsync( files.rsync(
f"{www_path}/", "/var/www/html", flags=["-avz", "--chown=www-data"] f"{www_path}/",
WEB_PATHS["html_dir"],
flags=["-avz", "--chown=www-data"],
) )
@@ -248,10 +256,10 @@ class LegacyRemoveDeployer(Deployer):
# remove historic expunge script # remove historic expunge script
# which is now implemented through a systemd timer (chatmail-expire) # which is now implemented through a systemd timer (chatmail-expire)
self.remove_file("/etc/cron.d/expunge") self.remove_file(CONFIG_FILES["cron_expunge"])
# Remove OBS repository key that is no longer used. # Remove OBS repository key that is no longer used.
self.remove_file("/etc/apt/keyrings/obs-home-deltachat.gpg") self.remove_file(CONFIG_FILES["apt_obs_keyring"])
self.ensure_line( self.ensure_line(
path="/etc/apt/sources.list", path="/etc/apt/sources.list",
line="deb [signed-by=/etc/apt/keyrings/obs-home-deltachat.gpg] https://download.opensuse.org/repositories/home:/deltachat/Debian_12/ ./", line="deb [signed-by=/etc/apt/keyrings/obs-home-deltachat.gpg] https://download.opensuse.org/repositories/home:/deltachat/Debian_12/ ./",
@@ -302,7 +310,7 @@ class TurnDeployer(Deployer):
"0fb3e792419494e21ecad536464929dba706bb2c88884ed8f1788141d26fc756", "0fb3e792419494e21ecad536464929dba706bb2c88884ed8f1788141d26fc756",
), ),
}[host.get_fact(facts.server.Arch)] }[host.get_fact(facts.server.Arch)]
self.download_executable(url, "/usr/local/bin/chatmail-turn", sha256sum) self.download_executable(url, BINARY_PATHS["chatmail_turn"], sha256sum)
def configure(self): def configure(self):
configure_remote_units(self, self.mail_domain, self.units) configure_remote_units(self, self.mail_domain, self.units)
@@ -328,14 +336,14 @@ class IrohDeployer(Deployer):
}[host.get_fact(facts.server.Arch)] }[host.get_fact(facts.server.Arch)]
self.download_executable( self.download_executable(
url, url,
"/usr/local/bin/iroh-relay", BINARY_PATHS["iroh_relay"],
sha256sum, sha256sum,
extract="gunzip | tar -xf - ./iroh-relay -O", extract="gunzip | tar -xf - ./iroh-relay -O",
) )
def configure(self): def configure(self):
self.ensure_systemd_unit("iroh-relay.service") self.ensure_systemd_unit("iroh-relay.service")
self.put_file("iroh-relay.toml", "/etc/iroh-relay.toml") self.put_file("iroh-relay.toml", CONFIG_FILES["iroh_relay"])
def activate(self): def activate(self):
self.ensure_service( self.ensure_service(
@@ -346,7 +354,7 @@ class IrohDeployer(Deployer):
class JournaldDeployer(Deployer): class JournaldDeployer(Deployer):
def configure(self): def configure(self):
self.put_file("journald.conf", "/etc/systemd/journald.conf") self.put_file("journald.conf", CONFIG_FILES["journald"])
def activate(self): def activate(self):
self.ensure_service("systemd-journald.service") self.ensure_service("systemd-journald.service")
@@ -388,7 +396,7 @@ class ChatmailDeployer(Deployer):
def install(self): def install(self):
self.put_file( self.put_file(
src=BytesIO(b'APT::Install-Recommends "false";\n'), src=BytesIO(b'APT::Install-Recommends "false";\n'),
dest="/etc/apt/apt.conf.d/00InstallRecommends", dest=CONFIG_FILES["apt_install_recommends"],
) )
apt.update(name="apt update", cache_time=24 * 3600) apt.update(name="apt update", cache_time=24 * 3600)
apt.upgrade(name="upgrade apt packages", auto_remove=True) apt.upgrade(name="upgrade apt packages", auto_remove=True)
@@ -414,9 +422,10 @@ class ChatmailDeployer(Deployer):
# This file is used by auth proxy. # This file is used by auth proxy.
# https://wiki.debian.org/EtcMailName # https://wiki.debian.org/EtcMailName
server.shell( server.shell(
name="Setup /etc/mailname", name=f"Setup {CONFIG_FILES['mailname']}",
commands=[ commands=[
f"echo {self.mail_domain} >/etc/mailname; chmod 644 /etc/mailname" f"echo {self.mail_domain} >{CONFIG_FILES['mailname']}; "
f"chmod 644 {CONFIG_FILES['mailname']}"
], ],
) )
@@ -442,7 +451,9 @@ class GithashDeployer(Deployer):
git_diff = subprocess.check_output(["git", "diff"]).decode() git_diff = subprocess.check_output(["git", "diff"]).decode()
except Exception: except Exception:
git_diff = "" git_diff = ""
self.put_file(src=StringIO(git_hash + git_diff), dest="/etc/chatmail-version") self.put_file(
src=StringIO(git_hash + git_diff), dest=CONFIG_FILES["chatmail_version"]
)
def get_tls_deployer(config, mail_domain): def get_tls_deployer(config, mail_domain):
+7 -5
View File
@@ -14,6 +14,7 @@ from cmdeploy.basedeploy import (
configure_remote_units, configure_remote_units,
is_in_container, is_in_container,
) )
from cmdeploy.constants import CONFIG_FILES
DOVECOT_ARCHIVE_VERSION = "2.3.21+dfsg1-3" DOVECOT_ARCHIVE_VERSION = "2.3.21+dfsg1-3"
DOVECOT_PACKAGE_VERSION = f"1:{DOVECOT_ARCHIVE_VERSION}" DOVECOT_PACKAGE_VERSION = f"1:{DOVECOT_ARCHIVE_VERSION}"
@@ -64,7 +65,7 @@ class DovecotDeployer(Deployer):
"Pin: version *\n" "Pin: version *\n"
"Pin-Priority: -1\n" "Pin-Priority: -1\n"
), ),
dest="/etc/apt/preferences.d/pin-dovecot", dest=CONFIG_FILES["apt_pin_dovecot"],
) )
def configure(self): def configure(self):
@@ -134,18 +135,19 @@ def _download_dovecot_package(package: str, arch: str) -> tuple[str | None, bool
return deb_filename, True return deb_filename, True
def _configure_dovecot(deployer, config: Config, debug: bool = False): def _configure_dovecot(deployer, config: Config, debug: bool = False):
"""Configures Dovecot IMAP server.""" """Configures Dovecot IMAP server."""
deployer.put_template( deployer.put_template(
"dovecot/dovecot.conf.j2", "dovecot/dovecot.conf.j2",
"/etc/dovecot/dovecot.conf", CONFIG_FILES["dovecot_conf"],
config=config, config=config,
debug=debug, debug=debug,
disable_ipv6=config.disable_ipv6, disable_ipv6=config.disable_ipv6,
) )
deployer.put_file("dovecot/auth.conf", "/etc/dovecot/auth.conf") deployer.put_file("dovecot/auth.conf", CONFIG_FILES["dovecot_auth"])
deployer.put_file( deployer.put_file(
"dovecot/push_notification.lua", "/etc/dovecot/push_notification.lua" "dovecot/push_notification.lua", CONFIG_FILES["dovecot_push_notification"]
) )
# as per https://doc.dovecot.org/2.3/configuration_manual/os/ # as per https://doc.dovecot.org/2.3/configuration_manual/os/
@@ -178,7 +180,7 @@ def _configure_dovecot(deployer, config: Config, debug: bool = False):
deployer.put_file( deployer.put_file(
"service/10_restart_on_failure.conf", "service/10_restart_on_failure.conf",
"/etc/systemd/system/dovecot.service.d/10_restart.conf", CONFIG_FILES["systemd_dovecot_restart"],
) )
# Validate dovecot configuration before restart # Validate dovecot configuration before restart
+3 -2
View File
@@ -3,12 +3,13 @@ import os
from pyinfra import facts, host from pyinfra import facts, host
from cmdeploy.basedeploy import Deployer from cmdeploy.basedeploy import Deployer
from cmdeploy.constants import BINARY_PATHS, CHATMAILD_PATHS
class FiltermailDeployer(Deployer): class FiltermailDeployer(Deployer):
services = ["filtermail", "filtermail-incoming", "filtermail-transport"] services = ["filtermail", "filtermail-incoming", "filtermail-transport"]
bin_path = "/usr/local/bin/filtermail" bin_path = BINARY_PATHS["filtermail"]
config_path = "/usr/local/lib/chatmaild/chatmail.ini" config_path = CHATMAILD_PATHS["config"]
def install(self): def install(self):
local_bin = os.environ.get("CHATMAIL_FILTERMAIL_BINARY") local_bin = os.environ.get("CHATMAIL_FILTERMAIL_BINARY")
+3 -2
View File
@@ -2,6 +2,7 @@ from pyinfra import facts, host
from pyinfra.operations import apt from pyinfra.operations import apt
from cmdeploy.basedeploy import Deployer from cmdeploy.basedeploy import Deployer
from cmdeploy.constants import BINARY_PATHS, CONFIG_FILES
class MtailDeployer(Deployer): class MtailDeployer(Deployer):
@@ -24,7 +25,7 @@ class MtailDeployer(Deployer):
}[host.get_fact(facts.server.Arch)] }[host.get_fact(facts.server.Arch)]
self.download_executable( self.download_executable(
url, url,
"/usr/local/bin/mtail", BINARY_PATHS["mtail"],
sha256sum, sha256sum,
extract="gunzip | tar -xf - mtail -O", extract="gunzip | tar -xf - mtail -O",
) )
@@ -37,7 +38,7 @@ class MtailDeployer(Deployer):
address=self.mtail_address or "127.0.0.1", address=self.mtail_address or "127.0.0.1",
port=3903, port=3903,
) )
self.put_file("mtail/delivered_mail.mtail", "/etc/mtail/delivered_mail.mtail") self.put_file("mtail/delivered_mail.mtail", CONFIG_FILES["mtail_program"])
def activate(self): def activate(self):
active = bool(self.mtail_address) active = bool(self.mtail_address)
+8 -7
View File
@@ -5,6 +5,7 @@ from cmdeploy.basedeploy import (
Deployer, Deployer,
get_resource, get_resource,
) )
from cmdeploy.constants import CONFIG_FILES, WEB_PATHS
class NginxDeployer(Deployer): class NginxDeployer(Deployer):
@@ -31,14 +32,14 @@ class NginxDeployer(Deployer):
# For documentation about policy-rc.d, see: # For documentation about policy-rc.d, see:
# https://people.debian.org/~hmh/invokerc.d-policyrc.d-specification.txt # https://people.debian.org/~hmh/invokerc.d-policyrc.d-specification.txt
# #
self.put_executable(src="policy-rc.d", dest="/usr/sbin/policy-rc.d") self.put_executable(src="policy-rc.d", dest=CONFIG_FILES["policy_rc_d"])
apt.packages( apt.packages(
name="Install nginx", name="Install nginx",
packages=["nginx", "libnginx-mod-stream"], packages=["nginx", "libnginx-mod-stream"],
) )
self.remove_file("/usr/sbin/policy-rc.d") self.remove_file(CONFIG_FILES["policy_rc_d"])
def configure(self): def configure(self):
_configure_nginx(self, self.config) _configure_nginx(self, self.config)
@@ -52,29 +53,29 @@ def _configure_nginx(deployer, config: Config, debug: bool = False):
deployer.put_template( deployer.put_template(
"nginx/nginx.conf.j2", "nginx/nginx.conf.j2",
"/etc/nginx/nginx.conf", CONFIG_FILES["nginx_conf"],
config=config, config=config,
disable_ipv6=config.disable_ipv6, disable_ipv6=config.disable_ipv6,
) )
deployer.put_template( deployer.put_template(
"nginx/autoconfig.xml.j2", "nginx/autoconfig.xml.j2",
"/var/www/html/.well-known/autoconfig/mail/config-v1.1.xml", WEB_PATHS["autoconfig"],
config=config, config=config,
) )
deployer.put_template( deployer.put_template(
"nginx/mta-sts.txt.j2", "nginx/mta-sts.txt.j2",
"/var/www/html/.well-known/mta-sts.txt", WEB_PATHS["mta_sts"],
config=config, config=config,
) )
# install CGI newemail script # install CGI newemail script
# #
cgi_dir = "/usr/lib/cgi-bin" cgi_dir = WEB_PATHS["newemail_cgi"].rsplit("/", 1)[0]
deployer.ensure_directory(cgi_dir) deployer.ensure_directory(cgi_dir)
deployer.put_executable( deployer.put_executable(
src=get_resource("newemail.py", pkg="chatmaild").open("rb"), src=get_resource("newemail.py", pkg="chatmaild").open("rb"),
dest=f"{cgi_dir}/newemail.py", dest=WEB_PATHS["newemail_cgi"],
) )
+14 -12
View File
@@ -7,6 +7,7 @@ from pyinfra.facts.files import File
from pyinfra.operations import apt, files, server from pyinfra.operations import apt, files, server
from cmdeploy.basedeploy import Deployer from cmdeploy.basedeploy import Deployer
from cmdeploy.constants import CONFIG_DIRS, CONFIG_FILES, STATE_DIRS
class OpendkimDeployer(Deployer): class OpendkimDeployer(Deployer):
@@ -28,43 +29,44 @@ class OpendkimDeployer(Deployer):
self.put_template( self.put_template(
"opendkim/opendkim.conf", "opendkim/opendkim.conf",
"/etc/opendkim.conf", CONFIG_FILES["opendkim_conf"],
config={"domain_name": domain, "opendkim_selector": dkim_selector}, config={"domain_name": domain, "opendkim_selector": dkim_selector},
) )
self.remove_file("/etc/opendkim/screen.lua") self.remove_file(f"{CONFIG_DIRS['opendkim']}/screen.lua")
self.remove_file("/etc/opendkim/final.lua") self.remove_file(f"{CONFIG_DIRS['opendkim']}/final.lua")
self.ensure_directory( self.ensure_directory(
"/etc/opendkim", CONFIG_DIRS["opendkim"],
owner="opendkim", owner="opendkim",
mode="750", mode="750",
) )
self.put_template( self.put_template(
"opendkim/KeyTable", "opendkim/KeyTable",
"/etc/dkimkeys/KeyTable", f"{CONFIG_DIRS['dkimkeys']}/KeyTable",
owner="opendkim", owner="opendkim",
config={"domain_name": domain, "opendkim_selector": dkim_selector}, config={"domain_name": domain, "opendkim_selector": dkim_selector},
) )
self.put_template( self.put_template(
"opendkim/SigningTable", "opendkim/SigningTable",
"/etc/dkimkeys/SigningTable", f"{CONFIG_DIRS['dkimkeys']}/SigningTable",
owner="opendkim", owner="opendkim",
config={"domain_name": domain, "opendkim_selector": dkim_selector}, config={"domain_name": domain, "opendkim_selector": dkim_selector},
) )
self.ensure_directory( self.ensure_directory(
"/var/spool/postfix/opendkim", f"{STATE_DIRS['var_spool_postfix']}/opendkim",
owner="opendkim", owner="opendkim",
mode="750", mode="750",
) )
if not host.get_fact(File, f"/etc/dkimkeys/{dkim_selector}.private"): dkim_private_key = f"{CONFIG_DIRS['dkimkeys']}/{dkim_selector}.private"
if not host.get_fact(File, dkim_private_key):
server.shell( server.shell(
name="Generate OpenDKIM domain keys", name="Generate OpenDKIM domain keys",
commands=[ commands=[
f"/usr/sbin/opendkim-genkey -D /etc/dkimkeys -d {domain} -s {dkim_selector}" f"/usr/sbin/opendkim-genkey -D {CONFIG_DIRS['dkimkeys']} -d {domain} -s {dkim_selector}"
], ],
_use_su_login=True, _use_su_login=True,
_su_user="opendkim", _su_user="opendkim",
@@ -72,12 +74,12 @@ class OpendkimDeployer(Deployer):
self.put_file( self.put_file(
"opendkim/systemd.conf", "opendkim/systemd.conf",
"/etc/systemd/system/opendkim.service.d/10-prevent-memory-leak.conf", CONFIG_FILES["systemd_opendkim_restart"],
) )
files.file( files.file(
name="chown opendkim: /etc/dkimkeys/opendkim.private", name=f"chown opendkim: {dkim_private_key}",
path="/etc/dkimkeys/opendkim.private", path=dkim_private_key,
user="opendkim", user="opendkim",
group="opendkim", group="opendkim",
) )
+13 -8
View File
@@ -1,6 +1,7 @@
from pyinfra.operations import apt, server from pyinfra.operations import apt, server
from cmdeploy.basedeploy import Deployer from cmdeploy.basedeploy import Deployer
from cmdeploy.constants import CONFIG_FILES
class PostfixDeployer(Deployer): class PostfixDeployer(Deployer):
@@ -21,39 +22,43 @@ class PostfixDeployer(Deployer):
self.put_template( self.put_template(
"postfix/main.cf.j2", "postfix/main.cf.j2",
"/etc/postfix/main.cf", CONFIG_FILES["postfix_main"],
config=config, config=config,
disable_ipv6=config.disable_ipv6, disable_ipv6=config.disable_ipv6,
) )
self.put_template( self.put_template(
"postfix/master.cf.j2", "postfix/master.cf.j2",
"/etc/postfix/master.cf", CONFIG_FILES["postfix_master"],
debug=False, debug=False,
config=config, config=config,
) )
self.put_file( self.put_file(
"postfix/submission_header_cleanup", "postfix/submission_header_cleanup",
"/etc/postfix/submission_header_cleanup", CONFIG_FILES["postfix_submission_header_cleanup"],
)
self.put_file(
"postfix/lmtp_header_cleanup",
CONFIG_FILES["postfix_lmtp_header_cleanup"],
) )
self.put_file("postfix/lmtp_header_cleanup", "/etc/postfix/lmtp_header_cleanup")
res = self.put_file( res = self.put_file(
"postfix/smtp_tls_policy_map", "/etc/postfix/smtp_tls_policy_map" "postfix/smtp_tls_policy_map",
CONFIG_FILES["postfix_smtp_tls_policy_map"],
) )
tls_policy_changed = res.changed tls_policy_changed = res.changed
if tls_policy_changed: if tls_policy_changed:
server.shell( server.shell(
commands=["postmap /etc/postfix/smtp_tls_policy_map"], commands=[f"postmap {CONFIG_FILES['postfix_smtp_tls_policy_map']}"],
) )
# Login map that 1:1 maps email address to login. # Login map that 1:1 maps email address to login.
self.put_file("postfix/login_map", "/etc/postfix/login_map") self.put_file("postfix/login_map", CONFIG_FILES["postfix_login_map"])
self.put_file( self.put_file(
"service/10_restart_on_failure.conf", "service/10_restart_on_failure.conf",
"/etc/systemd/system/postfix@.service.d/10_restart.conf", CONFIG_FILES["systemd_postfix_restart"],
) )
# Validate postfix configuration before restart # Validate postfix configuration before restart
+69 -58
View File
@@ -4,6 +4,17 @@ from pathlib import Path
from chatmaild.config import read_config from chatmaild.config import read_config
from pyinfra.operations import apt, files, server from pyinfra.operations import apt, files, server
from cmdeploy.constants import (
ACME_PATHS,
BINARY_PATHS,
CHATMAILD_PATHS,
CONFIG_DIRS,
CONFIG_FILES,
STATE_DIRS,
TLS_PATHS,
WEB_PATHS,
)
CHATMAIL_UNITS = [ CHATMAIL_UNITS = [
"doveauth.service", "doveauth.service",
"lastlogin.service", "lastlogin.service",
@@ -63,66 +74,66 @@ PACKAGE_NAMES = [
] ]
RELAY_FILES = [ RELAY_FILES = [
"/etc/apt/apt.conf.d/00InstallRecommends", CONFIG_FILES["apt_install_recommends"],
"/etc/apt/keyrings/obs-home-deltachat.gpg", CONFIG_FILES["apt_obs_keyring"],
"/etc/apt/preferences.d/pin-dovecot", CONFIG_FILES["apt_pin_dovecot"],
"/etc/chatmail-nocreate", CONFIG_FILES["chatmail_nocreate"],
"/etc/chatmail-version", CONFIG_FILES["chatmail_version"],
"/etc/cron.d/acmetool", CONFIG_FILES["cron_acmetool"],
"/etc/cron.d/chatmail-metrics", CONFIG_FILES["cron_chatmail_metrics"],
"/etc/cron.d/expunge", CONFIG_FILES["cron_expunge"],
"/etc/dovecot/auth.conf", CONFIG_FILES["dovecot_auth"],
"/etc/dovecot/dovecot.conf", CONFIG_FILES["dovecot_conf"],
"/etc/dovecot/push_notification.lua", CONFIG_FILES["dovecot_push_notification"],
"/etc/iroh-relay.toml", CONFIG_FILES["iroh_relay"],
"/etc/mailname", CONFIG_FILES["journald"],
"/etc/mta-sts-daemon.yml", CONFIG_FILES["mailname"],
"/etc/mtail/delivered_mail.mtail", CONFIG_FILES["mtail_program"],
"/etc/nginx/nginx.conf", CONFIG_FILES["mtasts_daemon"],
"/etc/opendkim.conf", CONFIG_FILES["nginx_conf"],
"/etc/postfix/lmtp_header_cleanup", CONFIG_FILES["opendkim_conf"],
"/etc/postfix/login_map", CONFIG_FILES["policy_rc_d"],
"/etc/postfix/main.cf", CONFIG_FILES["postfix_lmtp_header_cleanup"],
"/etc/postfix/master.cf", CONFIG_FILES["postfix_login_map"],
"/etc/postfix/smtp_tls_policy_map", CONFIG_FILES["postfix_main"],
"/etc/postfix/smtp_tls_policy_map.db", CONFIG_FILES["postfix_master"],
"/etc/postfix/submission_header_cleanup", CONFIG_FILES["postfix_smtp_tls_policy_map"],
"/etc/systemd/journald.conf", CONFIG_FILES["postfix_smtp_tls_policy_map_db"],
"/etc/systemd/system/mta-sts-daemon.service", CONFIG_FILES["postfix_submission_header_cleanup"],
"/etc/systemd/system/dovecot.service.d/10_restart.conf", CONFIG_FILES["systemd_dovecot_restart"],
"/etc/systemd/system/opendkim.service.d/10-prevent-memory-leak.conf", CONFIG_FILES["systemd_mtasts_daemon"],
"/etc/systemd/system/postfix@.service.d/10_restart.conf", CONFIG_FILES["systemd_opendkim_restart"],
"/etc/unbound/unbound.conf.d/chatmail.conf", CONFIG_FILES["systemd_postfix_restart"],
"/usr/lib/cgi-bin/newemail.py", CONFIG_FILES["unbound_chatmail"],
"/usr/local/bin/chatmail-turn", WEB_PATHS["metrics"],
"/usr/local/bin/filtermail", WEB_PATHS["newemail_cgi"],
"/usr/local/bin/iroh-relay", BINARY_PATHS["chatmail_turn"],
"/usr/local/bin/mtail", BINARY_PATHS["filtermail"],
"/usr/sbin/policy-rc.d", BINARY_PATHS["iroh_relay"],
"/var/www/html/metrics", BINARY_PATHS["mtail"],
] ]
PACKAGE_CONFIG_DIRS = [ PACKAGE_CONFIG_DIRS = [
"/etc/dkimkeys", CONFIG_DIRS["dkimkeys"],
"/etc/dovecot", CONFIG_DIRS["dovecot"],
"/etc/nginx", CONFIG_DIRS["nginx"],
"/etc/opendkim", CONFIG_DIRS["opendkim"],
"/etc/postfix", CONFIG_DIRS["postfix"],
"/etc/unbound", CONFIG_DIRS["unbound"],
] ]
RELAY_DIRS = [ RELAY_DIRS = [
"/etc/mtail", CONFIG_DIRS["mtail"],
"/root/from-cmdeploy", STATE_DIRS["from_cmdeploy"],
"/usr/local/lib/chatmaild", CHATMAILD_PATHS["base_dir"],
"/usr/local/lib/postfix-mta-sts-resolver", STATE_DIRS["postfix_mta_sts_resolver"],
"/var/lib/dovecot", STATE_DIRS["var_lib_dovecot"],
"/var/lib/nginx", STATE_DIRS["var_lib_nginx"],
"/var/lib/postfix", STATE_DIRS["var_lib_postfix"],
"/var/lib/unbound", STATE_DIRS["var_lib_unbound"],
"/var/log/nginx", STATE_DIRS["var_log_nginx"],
"/var/spool/postfix", STATE_DIRS["var_spool_postfix"],
"/var/www/html", WEB_PATHS["html_dir"],
] ]
EMPTY_SYSTEMD_DIRS = [ EMPTY_SYSTEMD_DIRS = [
@@ -220,11 +231,11 @@ def _remove_dynamic_state(config, keep_packages: bool):
_remove_dir(str(home_vmail)) _remove_dir(str(home_vmail))
if config.tls_cert_mode == "self": if config.tls_cert_mode == "self":
_remove_file("/etc/ssl/certs/mailserver.pem") _remove_file(TLS_PATHS["self_cert"])
_remove_file("/etc/ssl/private/mailserver.key") _remove_file(TLS_PATHS["self_key"])
elif config.tls_cert_mode == "acme": elif config.tls_cert_mode == "acme":
_remove_dir("/etc/acme") _remove_dir(ACME_PATHS["etc_dir"])
_remove_dir("/var/lib/acme") _remove_dir(ACME_PATHS["var_dir"])
def _restore_basic_resolver(): def _restore_basic_resolver():
+4 -5
View File
@@ -2,6 +2,8 @@ import shlex
from pyinfra.operations import server from pyinfra.operations import server
from cmdeploy.constants import TLS_PATHS
from ..basedeploy import Deployer from ..basedeploy import Deployer
@@ -31,9 +33,8 @@ class SelfSignedTlsDeployer(Deployer):
def __init__(self, mail_domain): def __init__(self, mail_domain):
self.mail_domain = mail_domain self.mail_domain = mail_domain
self.cert_path = "/etc/ssl/certs/mailserver.pem" self.cert_path = TLS_PATHS["self_cert"]
self.key_path = "/etc/ssl/private/mailserver.key" self.key_path = TLS_PATHS["self_key"]
def configure(self): def configure(self):
@@ -48,5 +49,3 @@ class SelfSignedTlsDeployer(Deployer):
def activate(self): def activate(self):
pass pass
+23 -15
View File
@@ -1,6 +1,14 @@
from unittest.mock import call from unittest.mock import call
from cmdeploy import removers from cmdeploy import removers
from cmdeploy.constants import (
ACME_PATHS,
CHATMAILD_PATHS,
CONFIG_DIRS,
CONFIG_FILES,
STATE_DIRS,
TLS_PATHS,
)
def test_remove_chatmail_purges_packages_and_state(make_config, monkeypatch): def test_remove_chatmail_purges_packages_and_state(make_config, monkeypatch):
@@ -28,20 +36,20 @@ def test_remove_chatmail_purges_packages_and_state(make_config, monkeypatch):
"purge": True, "purge": True,
} }
] ]
assert call(path="/usr/local/lib/chatmaild", present=False) in [ assert call(path=CHATMAILD_PATHS["base_dir"], present=False) in [
call(path=entry["path"], present=entry["present"]) for entry in dir_calls call(path=entry["path"], present=entry["present"]) for entry in dir_calls
] ]
assert call(path=str(config.mailboxes_dir), present=False) in [ assert call(path=str(config.mailboxes_dir), present=False) in [
call(path=entry["path"], present=entry["present"]) for entry in dir_calls call(path=entry["path"], present=entry["present"]) for entry in dir_calls
] ]
assert any(entry["path"] == "/var/lib/acme" for entry in dir_calls) assert any(entry["path"] == ACME_PATHS["var_dir"] for entry in dir_calls)
assert any( assert any(
entry["path"] == "/etc/systemd/system/doveauth.service" for entry in file_calls entry["path"] == "/etc/systemd/system/doveauth.service" for entry in file_calls
) )
assert any(entry["path"] == "/etc/postfix/main.cf" for entry in file_calls) assert any(entry["path"] == CONFIG_FILES["postfix_main"] for entry in file_calls)
assert any(entry["path"] == "/etc/opendkim.conf" for entry in file_calls) assert any(entry["path"] == CONFIG_FILES["opendkim_conf"] for entry in file_calls)
assert any(entry["path"] == "/etc/postfix" for entry in dir_calls) assert any(entry["path"] == CONFIG_DIRS["postfix"] for entry in dir_calls)
assert any(entry["path"] == "/var/log/nginx" for entry in dir_calls) assert any(entry["path"] == STATE_DIRS["var_log_nginx"] for entry in dir_calls)
assert any( assert any(
"userdel -r vmail" in command "userdel -r vmail" in command
for entry in shell_calls for entry in shell_calls
@@ -73,13 +81,13 @@ def test_remove_chatmail_keep_packages_and_external_tls(make_config, monkeypatch
removed_dirs = {entry["path"] for entry in dir_calls} removed_dirs = {entry["path"] for entry in dir_calls}
assert "/certs/fullchain.pem" not in removed_files assert "/certs/fullchain.pem" not in removed_files
assert "/certs/privkey.pem" not in removed_files assert "/certs/privkey.pem" not in removed_files
assert "/var/lib/acme" not in removed_dirs assert ACME_PATHS["var_dir"] not in removed_dirs
assert "/etc/nginx" not in removed_dirs assert CONFIG_DIRS["nginx"] not in removed_dirs
assert "/etc/unbound" not in removed_dirs assert CONFIG_DIRS["unbound"] not in removed_dirs
assert "/etc/postfix" not in removed_dirs assert CONFIG_DIRS["postfix"] not in removed_dirs
assert "/etc/dovecot" not in removed_dirs assert CONFIG_DIRS["dovecot"] not in removed_dirs
assert "/etc/nginx/nginx.conf" in removed_files assert CONFIG_FILES["nginx_conf"] in removed_files
assert "/etc/unbound/unbound.conf.d/chatmail.conf" in removed_files assert CONFIG_FILES["unbound_chatmail"] in removed_files
def test_remove_chatmail_removes_self_signed_tls(make_config, monkeypatch): def test_remove_chatmail_removes_self_signed_tls(make_config, monkeypatch):
@@ -95,5 +103,5 @@ def test_remove_chatmail_removes_self_signed_tls(make_config, monkeypatch):
removers.remove_chatmail(config._inipath) removers.remove_chatmail(config._inipath)
removed = {entry["path"] for entry in file_calls} removed = {entry["path"] for entry in file_calls}
assert "/etc/ssl/certs/mailserver.pem" in removed assert TLS_PATHS["self_cert"] in removed
assert "/etc/ssl/private/mailserver.key" in removed assert TLS_PATHS["self_key"] in removed