From d7a16d2be1d99fa4569785a2780f4a4e8ae074c1 Mon Sep 17 00:00:00 2001 From: ccclxxiii <151577046+ccclxxiii@users.noreply.github.com> Date: Thu, 25 Jun 2026 09:26:01 -0500 Subject: [PATCH] refactor(cmdeploy): share deployed path constants --- cmdeploy/src/cmdeploy/acmetool/__init__.py | 16 ++- cmdeploy/src/cmdeploy/basedeploy.py | 7 +- cmdeploy/src/cmdeploy/constants.py | 91 +++++++++++++ cmdeploy/src/cmdeploy/deployers.py | 63 +++++---- cmdeploy/src/cmdeploy/dovecot/deployer.py | 12 +- cmdeploy/src/cmdeploy/filtermail/deployer.py | 5 +- cmdeploy/src/cmdeploy/mtail/deployer.py | 5 +- cmdeploy/src/cmdeploy/nginx/deployer.py | 15 ++- cmdeploy/src/cmdeploy/opendkim/deployer.py | 26 ++-- cmdeploy/src/cmdeploy/postfix/deployer.py | 21 +-- cmdeploy/src/cmdeploy/removers.py | 127 ++++++++++--------- cmdeploy/src/cmdeploy/selfsigned/deployer.py | 9 +- cmdeploy/src/cmdeploy/tests/test_removers.py | 38 +++--- 13 files changed, 285 insertions(+), 150 deletions(-) create mode 100644 cmdeploy/src/cmdeploy/constants.py diff --git a/cmdeploy/src/cmdeploy/acmetool/__init__.py b/cmdeploy/src/cmdeploy/acmetool/__init__.py index 9e274668..76c807c5 100644 --- a/cmdeploy/src/cmdeploy/acmetool/__init__.py +++ b/cmdeploy/src/cmdeploy/acmetool/__init__.py @@ -1,5 +1,7 @@ from pyinfra.operations import apt, server +from cmdeploy.constants import ACME_PATHS, CONFIG_FILES + from ..basedeploy import Deployer @@ -14,30 +16,30 @@ class AcmetoolDeployer(Deployer): 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.remove_file("/usr/lib/acme/hooks/nginx") + self.put_executable("acmetool/acmetool.hook", ACME_PATHS["hook_nginx"]) + self.remove_file(ACME_PATHS["legacy_hook_nginx"]) def configure(self): self.put_template( "acmetool/response-file.yaml.j2", - "/var/lib/acme/conf/responses", + ACME_PATHS["responses"], email=self.email, ) self.put_template( "acmetool/target.yaml.j2", - "/var/lib/acme/conf/target", + ACME_PATHS["target"], ) server.shell( 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( "acmetool/desired.yaml.j2", - f"/var/lib/acme/desired/{self.domains[0]}", + f"{ACME_PATHS['desired_dir']}/{self.domains[0]}", domains=self.domains, ) diff --git a/cmdeploy/src/cmdeploy/basedeploy.py b/cmdeploy/src/cmdeploy/basedeploy.py index c6801e87..1b430a91 100644 --- a/cmdeploy/src/cmdeploy/basedeploy.py +++ b/cmdeploy/src/cmdeploy/basedeploy.py @@ -8,6 +8,8 @@ from pyinfra.facts.files import Sha256File from pyinfra.facts.server import Command from pyinfra.operations import files, server, systemd +from cmdeploy.constants import CHATMAILD_PATHS + def has_systemd(): """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: - remote_base_dir = "/usr/local/lib/chatmaild" - remote_venv_dir = f"{remote_base_dir}/venv" - remote_chatmail_inipath = f"{remote_base_dir}/chatmail.ini" + remote_venv_dir = CHATMAILD_PATHS["venv_dir"] + remote_chatmail_inipath = CHATMAILD_PATHS["config"] # install systemd units for fn in units: diff --git a/cmdeploy/src/cmdeploy/constants.py b/cmdeploy/src/cmdeploy/constants.py new file mode 100644 index 00000000..1c4766b1 --- /dev/null +++ b/cmdeploy/src/cmdeploy/constants.py @@ -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", +} diff --git a/cmdeploy/src/cmdeploy/deployers.py b/cmdeploy/src/cmdeploy/deployers.py index 8567ac4f..102a7db7 100644 --- a/cmdeploy/src/cmdeploy/deployers.py +++ b/cmdeploy/src/cmdeploy/deployers.py @@ -16,6 +16,14 @@ from pyinfra.facts.systemd import SystemdEnabled from pyinfra.operations import apt, files, pip, server, systemd 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 .basedeploy import ( @@ -83,16 +91,15 @@ def remove_legacy_artifacts(): def _install_remote_venv_with_chatmaild(deployer) -> None: remove_legacy_artifacts() dist_file = _build_chatmaild(dist_dir=Path("chatmaild/dist")) - remote_base_dir = "/usr/local/lib/chatmaild" - remote_dist_file = f"{remote_base_dir}/dist/{dist_file.name}" - remote_venv_dir = f"{remote_base_dir}/venv" + remote_dist_file = f"{CHATMAILD_PATHS['dist_dir']}/{dist_file.name}" + remote_venv_dir = CHATMAILD_PATHS["venv_dir"] apt.packages( name="apt install python3-virtualenv", packages=["python3-virtualenv"], ) - deployer.ensure_directory(f"{remote_base_dir}/dist") + deployer.ensure_directory(CHATMAILD_PATHS["dist_dir"]) deployer.put_file( src=dist_file.open("rb"), 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: - remote_base_dir = "/usr/local/lib/chatmaild" - remote_chatmail_inipath = f"{remote_base_dir}/chatmail.ini" + remote_chatmail_inipath = CHATMAILD_PATHS["config"] deployer.put_file( src=config._getbytefile(), dest=remote_chatmail_inipath, ) - deployer.remove_file("/etc/cron.d/chatmail-metrics") - deployer.remove_file("/var/www/html/metrics") + deployer.remove_file(CONFIG_FILES["cron_chatmail_metrics"]) + deployer.remove_file(WEB_PATHS["metrics"]) class UnboundDeployer(Deployer): @@ -169,15 +175,15 @@ class UnboundDeployer(Deployer): server.shell( name="Generate root keys for validating DNSSEC", 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( - path="/etc/unbound/unbound.conf.d", + path=f"{CONFIG_DIRS['unbound']}/unbound.conf.d", ) self.put_template( "unbound/unbound.conf.j2", - "/etc/unbound/unbound.conf.d/chatmail.conf", + CONFIG_FILES["unbound_chatmail"], disable_ipv6=self.config.disable_ipv6, ) @@ -201,9 +207,9 @@ class UnboundDeployer(Deployer): class MtastsDeployer(Deployer): def configure(self): # Remove configuration. - self.remove_file("/etc/mta-sts-daemon.yml") - self.remove_directory("/usr/local/lib/postfix-mta-sts-resolver") - self.remove_file("/etc/systemd/system/mta-sts-daemon.service") + self.remove_file(CONFIG_FILES["mtasts_daemon"]) + self.remove_directory(STATE_DIRS["postfix_mta_sts_resolver"]) + self.remove_file(CONFIG_FILES["systemd_mtasts_daemon"]) def activate(self): self.ensure_service( @@ -218,7 +224,7 @@ class WebsiteDeployer(Deployer): self.config = config def install(self): - self.ensure_directory("/var/www") + self.ensure_directory(str(Path(WEB_PATHS["html_dir"]).parent)) def configure(self): www_path, src_dir, build_dir = get_paths(self.config) @@ -238,7 +244,9 @@ class WebsiteDeployer(Deployer): return # if it is not a hugo page, upload it as is 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 # 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. - self.remove_file("/etc/apt/keyrings/obs-home-deltachat.gpg") + self.remove_file(CONFIG_FILES["apt_obs_keyring"]) self.ensure_line( 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/ ./", @@ -302,7 +310,7 @@ class TurnDeployer(Deployer): "0fb3e792419494e21ecad536464929dba706bb2c88884ed8f1788141d26fc756", ), }[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): configure_remote_units(self, self.mail_domain, self.units) @@ -328,14 +336,14 @@ class IrohDeployer(Deployer): }[host.get_fact(facts.server.Arch)] self.download_executable( url, - "/usr/local/bin/iroh-relay", + BINARY_PATHS["iroh_relay"], sha256sum, extract="gunzip | tar -xf - ./iroh-relay -O", ) def configure(self): 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): self.ensure_service( @@ -346,7 +354,7 @@ class IrohDeployer(Deployer): class JournaldDeployer(Deployer): def configure(self): - self.put_file("journald.conf", "/etc/systemd/journald.conf") + self.put_file("journald.conf", CONFIG_FILES["journald"]) def activate(self): self.ensure_service("systemd-journald.service") @@ -388,7 +396,7 @@ class ChatmailDeployer(Deployer): def install(self): self.put_file( 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.upgrade(name="upgrade apt packages", auto_remove=True) @@ -414,9 +422,10 @@ class ChatmailDeployer(Deployer): # This file is used by auth proxy. # https://wiki.debian.org/EtcMailName server.shell( - name="Setup /etc/mailname", + name=f"Setup {CONFIG_FILES['mailname']}", 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() except Exception: 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): diff --git a/cmdeploy/src/cmdeploy/dovecot/deployer.py b/cmdeploy/src/cmdeploy/dovecot/deployer.py index debb7904..1569354a 100644 --- a/cmdeploy/src/cmdeploy/dovecot/deployer.py +++ b/cmdeploy/src/cmdeploy/dovecot/deployer.py @@ -14,6 +14,7 @@ from cmdeploy.basedeploy import ( configure_remote_units, is_in_container, ) +from cmdeploy.constants import CONFIG_FILES DOVECOT_ARCHIVE_VERSION = "2.3.21+dfsg1-3" DOVECOT_PACKAGE_VERSION = f"1:{DOVECOT_ARCHIVE_VERSION}" @@ -64,7 +65,7 @@ class DovecotDeployer(Deployer): "Pin: version *\n" "Pin-Priority: -1\n" ), - dest="/etc/apt/preferences.d/pin-dovecot", + dest=CONFIG_FILES["apt_pin_dovecot"], ) def configure(self): @@ -134,18 +135,19 @@ def _download_dovecot_package(package: str, arch: str) -> tuple[str | None, bool return deb_filename, True + def _configure_dovecot(deployer, config: Config, debug: bool = False): """Configures Dovecot IMAP server.""" deployer.put_template( "dovecot/dovecot.conf.j2", - "/etc/dovecot/dovecot.conf", + CONFIG_FILES["dovecot_conf"], config=config, debug=debug, 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( - "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/ @@ -178,7 +180,7 @@ def _configure_dovecot(deployer, config: Config, debug: bool = False): deployer.put_file( "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 diff --git a/cmdeploy/src/cmdeploy/filtermail/deployer.py b/cmdeploy/src/cmdeploy/filtermail/deployer.py index 4e5f433d..02b9ea32 100644 --- a/cmdeploy/src/cmdeploy/filtermail/deployer.py +++ b/cmdeploy/src/cmdeploy/filtermail/deployer.py @@ -3,12 +3,13 @@ import os from pyinfra import facts, host from cmdeploy.basedeploy import Deployer +from cmdeploy.constants import BINARY_PATHS, CHATMAILD_PATHS class FiltermailDeployer(Deployer): services = ["filtermail", "filtermail-incoming", "filtermail-transport"] - bin_path = "/usr/local/bin/filtermail" - config_path = "/usr/local/lib/chatmaild/chatmail.ini" + bin_path = BINARY_PATHS["filtermail"] + config_path = CHATMAILD_PATHS["config"] def install(self): local_bin = os.environ.get("CHATMAIL_FILTERMAIL_BINARY") diff --git a/cmdeploy/src/cmdeploy/mtail/deployer.py b/cmdeploy/src/cmdeploy/mtail/deployer.py index d524f85b..4019beb7 100644 --- a/cmdeploy/src/cmdeploy/mtail/deployer.py +++ b/cmdeploy/src/cmdeploy/mtail/deployer.py @@ -2,6 +2,7 @@ from pyinfra import facts, host from pyinfra.operations import apt from cmdeploy.basedeploy import Deployer +from cmdeploy.constants import BINARY_PATHS, CONFIG_FILES class MtailDeployer(Deployer): @@ -24,7 +25,7 @@ class MtailDeployer(Deployer): }[host.get_fact(facts.server.Arch)] self.download_executable( url, - "/usr/local/bin/mtail", + BINARY_PATHS["mtail"], sha256sum, extract="gunzip | tar -xf - mtail -O", ) @@ -37,7 +38,7 @@ class MtailDeployer(Deployer): address=self.mtail_address or "127.0.0.1", 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): active = bool(self.mtail_address) diff --git a/cmdeploy/src/cmdeploy/nginx/deployer.py b/cmdeploy/src/cmdeploy/nginx/deployer.py index c88464d4..372d9fa7 100644 --- a/cmdeploy/src/cmdeploy/nginx/deployer.py +++ b/cmdeploy/src/cmdeploy/nginx/deployer.py @@ -5,6 +5,7 @@ from cmdeploy.basedeploy import ( Deployer, get_resource, ) +from cmdeploy.constants import CONFIG_FILES, WEB_PATHS class NginxDeployer(Deployer): @@ -31,14 +32,14 @@ class NginxDeployer(Deployer): # For documentation about policy-rc.d, see: # 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( name="Install nginx", packages=["nginx", "libnginx-mod-stream"], ) - self.remove_file("/usr/sbin/policy-rc.d") + self.remove_file(CONFIG_FILES["policy_rc_d"]) def configure(self): _configure_nginx(self, self.config) @@ -52,29 +53,29 @@ def _configure_nginx(deployer, config: Config, debug: bool = False): deployer.put_template( "nginx/nginx.conf.j2", - "/etc/nginx/nginx.conf", + CONFIG_FILES["nginx_conf"], config=config, disable_ipv6=config.disable_ipv6, ) deployer.put_template( "nginx/autoconfig.xml.j2", - "/var/www/html/.well-known/autoconfig/mail/config-v1.1.xml", + WEB_PATHS["autoconfig"], config=config, ) deployer.put_template( "nginx/mta-sts.txt.j2", - "/var/www/html/.well-known/mta-sts.txt", + WEB_PATHS["mta_sts"], config=config, ) # 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.put_executable( src=get_resource("newemail.py", pkg="chatmaild").open("rb"), - dest=f"{cgi_dir}/newemail.py", + dest=WEB_PATHS["newemail_cgi"], ) diff --git a/cmdeploy/src/cmdeploy/opendkim/deployer.py b/cmdeploy/src/cmdeploy/opendkim/deployer.py index 27b3876a..e32a7b45 100644 --- a/cmdeploy/src/cmdeploy/opendkim/deployer.py +++ b/cmdeploy/src/cmdeploy/opendkim/deployer.py @@ -7,6 +7,7 @@ from pyinfra.facts.files import File from pyinfra.operations import apt, files, server from cmdeploy.basedeploy import Deployer +from cmdeploy.constants import CONFIG_DIRS, CONFIG_FILES, STATE_DIRS class OpendkimDeployer(Deployer): @@ -28,43 +29,44 @@ class OpendkimDeployer(Deployer): self.put_template( "opendkim/opendkim.conf", - "/etc/opendkim.conf", + CONFIG_FILES["opendkim_conf"], config={"domain_name": domain, "opendkim_selector": dkim_selector}, ) - self.remove_file("/etc/opendkim/screen.lua") - self.remove_file("/etc/opendkim/final.lua") + self.remove_file(f"{CONFIG_DIRS['opendkim']}/screen.lua") + self.remove_file(f"{CONFIG_DIRS['opendkim']}/final.lua") self.ensure_directory( - "/etc/opendkim", + CONFIG_DIRS["opendkim"], owner="opendkim", mode="750", ) self.put_template( "opendkim/KeyTable", - "/etc/dkimkeys/KeyTable", + f"{CONFIG_DIRS['dkimkeys']}/KeyTable", owner="opendkim", config={"domain_name": domain, "opendkim_selector": dkim_selector}, ) self.put_template( "opendkim/SigningTable", - "/etc/dkimkeys/SigningTable", + f"{CONFIG_DIRS['dkimkeys']}/SigningTable", owner="opendkim", config={"domain_name": domain, "opendkim_selector": dkim_selector}, ) self.ensure_directory( - "/var/spool/postfix/opendkim", + f"{STATE_DIRS['var_spool_postfix']}/opendkim", owner="opendkim", 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( name="Generate OpenDKIM domain keys", 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, _su_user="opendkim", @@ -72,12 +74,12 @@ class OpendkimDeployer(Deployer): self.put_file( "opendkim/systemd.conf", - "/etc/systemd/system/opendkim.service.d/10-prevent-memory-leak.conf", + CONFIG_FILES["systemd_opendkim_restart"], ) files.file( - name="chown opendkim: /etc/dkimkeys/opendkim.private", - path="/etc/dkimkeys/opendkim.private", + name=f"chown opendkim: {dkim_private_key}", + path=dkim_private_key, user="opendkim", group="opendkim", ) diff --git a/cmdeploy/src/cmdeploy/postfix/deployer.py b/cmdeploy/src/cmdeploy/postfix/deployer.py index 53d5f2de..622809e3 100644 --- a/cmdeploy/src/cmdeploy/postfix/deployer.py +++ b/cmdeploy/src/cmdeploy/postfix/deployer.py @@ -1,6 +1,7 @@ from pyinfra.operations import apt, server from cmdeploy.basedeploy import Deployer +from cmdeploy.constants import CONFIG_FILES class PostfixDeployer(Deployer): @@ -21,39 +22,43 @@ class PostfixDeployer(Deployer): self.put_template( "postfix/main.cf.j2", - "/etc/postfix/main.cf", + CONFIG_FILES["postfix_main"], config=config, disable_ipv6=config.disable_ipv6, ) self.put_template( "postfix/master.cf.j2", - "/etc/postfix/master.cf", + CONFIG_FILES["postfix_master"], debug=False, config=config, ) self.put_file( "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( - "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 if tls_policy_changed: 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. - self.put_file("postfix/login_map", "/etc/postfix/login_map") + self.put_file("postfix/login_map", CONFIG_FILES["postfix_login_map"]) self.put_file( "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 diff --git a/cmdeploy/src/cmdeploy/removers.py b/cmdeploy/src/cmdeploy/removers.py index 9a1a7f22..c2115bc2 100644 --- a/cmdeploy/src/cmdeploy/removers.py +++ b/cmdeploy/src/cmdeploy/removers.py @@ -4,6 +4,17 @@ from pathlib import Path from chatmaild.config import read_config 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 = [ "doveauth.service", "lastlogin.service", @@ -63,66 +74,66 @@ PACKAGE_NAMES = [ ] RELAY_FILES = [ - "/etc/apt/apt.conf.d/00InstallRecommends", - "/etc/apt/keyrings/obs-home-deltachat.gpg", - "/etc/apt/preferences.d/pin-dovecot", - "/etc/chatmail-nocreate", - "/etc/chatmail-version", - "/etc/cron.d/acmetool", - "/etc/cron.d/chatmail-metrics", - "/etc/cron.d/expunge", - "/etc/dovecot/auth.conf", - "/etc/dovecot/dovecot.conf", - "/etc/dovecot/push_notification.lua", - "/etc/iroh-relay.toml", - "/etc/mailname", - "/etc/mta-sts-daemon.yml", - "/etc/mtail/delivered_mail.mtail", - "/etc/nginx/nginx.conf", - "/etc/opendkim.conf", - "/etc/postfix/lmtp_header_cleanup", - "/etc/postfix/login_map", - "/etc/postfix/main.cf", - "/etc/postfix/master.cf", - "/etc/postfix/smtp_tls_policy_map", - "/etc/postfix/smtp_tls_policy_map.db", - "/etc/postfix/submission_header_cleanup", - "/etc/systemd/journald.conf", - "/etc/systemd/system/mta-sts-daemon.service", - "/etc/systemd/system/dovecot.service.d/10_restart.conf", - "/etc/systemd/system/opendkim.service.d/10-prevent-memory-leak.conf", - "/etc/systemd/system/postfix@.service.d/10_restart.conf", - "/etc/unbound/unbound.conf.d/chatmail.conf", - "/usr/lib/cgi-bin/newemail.py", - "/usr/local/bin/chatmail-turn", - "/usr/local/bin/filtermail", - "/usr/local/bin/iroh-relay", - "/usr/local/bin/mtail", - "/usr/sbin/policy-rc.d", - "/var/www/html/metrics", + CONFIG_FILES["apt_install_recommends"], + CONFIG_FILES["apt_obs_keyring"], + CONFIG_FILES["apt_pin_dovecot"], + CONFIG_FILES["chatmail_nocreate"], + CONFIG_FILES["chatmail_version"], + CONFIG_FILES["cron_acmetool"], + CONFIG_FILES["cron_chatmail_metrics"], + CONFIG_FILES["cron_expunge"], + CONFIG_FILES["dovecot_auth"], + CONFIG_FILES["dovecot_conf"], + CONFIG_FILES["dovecot_push_notification"], + CONFIG_FILES["iroh_relay"], + CONFIG_FILES["journald"], + CONFIG_FILES["mailname"], + CONFIG_FILES["mtail_program"], + CONFIG_FILES["mtasts_daemon"], + CONFIG_FILES["nginx_conf"], + CONFIG_FILES["opendkim_conf"], + CONFIG_FILES["policy_rc_d"], + CONFIG_FILES["postfix_lmtp_header_cleanup"], + CONFIG_FILES["postfix_login_map"], + CONFIG_FILES["postfix_main"], + CONFIG_FILES["postfix_master"], + CONFIG_FILES["postfix_smtp_tls_policy_map"], + CONFIG_FILES["postfix_smtp_tls_policy_map_db"], + CONFIG_FILES["postfix_submission_header_cleanup"], + CONFIG_FILES["systemd_dovecot_restart"], + CONFIG_FILES["systemd_mtasts_daemon"], + CONFIG_FILES["systemd_opendkim_restart"], + CONFIG_FILES["systemd_postfix_restart"], + CONFIG_FILES["unbound_chatmail"], + WEB_PATHS["metrics"], + WEB_PATHS["newemail_cgi"], + BINARY_PATHS["chatmail_turn"], + BINARY_PATHS["filtermail"], + BINARY_PATHS["iroh_relay"], + BINARY_PATHS["mtail"], ] PACKAGE_CONFIG_DIRS = [ - "/etc/dkimkeys", - "/etc/dovecot", - "/etc/nginx", - "/etc/opendkim", - "/etc/postfix", - "/etc/unbound", + CONFIG_DIRS["dkimkeys"], + CONFIG_DIRS["dovecot"], + CONFIG_DIRS["nginx"], + CONFIG_DIRS["opendkim"], + CONFIG_DIRS["postfix"], + CONFIG_DIRS["unbound"], ] RELAY_DIRS = [ - "/etc/mtail", - "/root/from-cmdeploy", - "/usr/local/lib/chatmaild", - "/usr/local/lib/postfix-mta-sts-resolver", - "/var/lib/dovecot", - "/var/lib/nginx", - "/var/lib/postfix", - "/var/lib/unbound", - "/var/log/nginx", - "/var/spool/postfix", - "/var/www/html", + CONFIG_DIRS["mtail"], + STATE_DIRS["from_cmdeploy"], + CHATMAILD_PATHS["base_dir"], + STATE_DIRS["postfix_mta_sts_resolver"], + STATE_DIRS["var_lib_dovecot"], + STATE_DIRS["var_lib_nginx"], + STATE_DIRS["var_lib_postfix"], + STATE_DIRS["var_lib_unbound"], + STATE_DIRS["var_log_nginx"], + STATE_DIRS["var_spool_postfix"], + WEB_PATHS["html_dir"], ] EMPTY_SYSTEMD_DIRS = [ @@ -220,11 +231,11 @@ def _remove_dynamic_state(config, keep_packages: bool): _remove_dir(str(home_vmail)) if config.tls_cert_mode == "self": - _remove_file("/etc/ssl/certs/mailserver.pem") - _remove_file("/etc/ssl/private/mailserver.key") + _remove_file(TLS_PATHS["self_cert"]) + _remove_file(TLS_PATHS["self_key"]) elif config.tls_cert_mode == "acme": - _remove_dir("/etc/acme") - _remove_dir("/var/lib/acme") + _remove_dir(ACME_PATHS["etc_dir"]) + _remove_dir(ACME_PATHS["var_dir"]) def _restore_basic_resolver(): diff --git a/cmdeploy/src/cmdeploy/selfsigned/deployer.py b/cmdeploy/src/cmdeploy/selfsigned/deployer.py index 881718cc..8fd78ade 100644 --- a/cmdeploy/src/cmdeploy/selfsigned/deployer.py +++ b/cmdeploy/src/cmdeploy/selfsigned/deployer.py @@ -2,6 +2,8 @@ import shlex from pyinfra.operations import server +from cmdeploy.constants import TLS_PATHS + from ..basedeploy import Deployer @@ -31,9 +33,8 @@ class SelfSignedTlsDeployer(Deployer): def __init__(self, mail_domain): self.mail_domain = mail_domain - self.cert_path = "/etc/ssl/certs/mailserver.pem" - self.key_path = "/etc/ssl/private/mailserver.key" - + self.cert_path = TLS_PATHS["self_cert"] + self.key_path = TLS_PATHS["self_key"] def configure(self): @@ -48,5 +49,3 @@ class SelfSignedTlsDeployer(Deployer): def activate(self): pass - - diff --git a/cmdeploy/src/cmdeploy/tests/test_removers.py b/cmdeploy/src/cmdeploy/tests/test_removers.py index 1e41e6c7..a8f0d285 100644 --- a/cmdeploy/src/cmdeploy/tests/test_removers.py +++ b/cmdeploy/src/cmdeploy/tests/test_removers.py @@ -1,6 +1,14 @@ from unittest.mock import call 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): @@ -28,20 +36,20 @@ def test_remove_chatmail_purges_packages_and_state(make_config, monkeypatch): "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 ] assert call(path=str(config.mailboxes_dir), present=False) in [ 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( 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"] == "/etc/opendkim.conf" for entry in file_calls) - assert any(entry["path"] == "/etc/postfix" for entry in dir_calls) - assert any(entry["path"] == "/var/log/nginx" for entry in dir_calls) + assert any(entry["path"] == CONFIG_FILES["postfix_main"] for entry in file_calls) + assert any(entry["path"] == CONFIG_FILES["opendkim_conf"] for entry in file_calls) + assert any(entry["path"] == CONFIG_DIRS["postfix"] for entry in dir_calls) + assert any(entry["path"] == STATE_DIRS["var_log_nginx"] for entry in dir_calls) assert any( "userdel -r vmail" in command 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} assert "/certs/fullchain.pem" not in removed_files assert "/certs/privkey.pem" not in removed_files - assert "/var/lib/acme" not in removed_dirs - assert "/etc/nginx" not in removed_dirs - assert "/etc/unbound" not in removed_dirs - assert "/etc/postfix" not in removed_dirs - assert "/etc/dovecot" not in removed_dirs - assert "/etc/nginx/nginx.conf" in removed_files - assert "/etc/unbound/unbound.conf.d/chatmail.conf" in removed_files + assert ACME_PATHS["var_dir"] not in removed_dirs + assert CONFIG_DIRS["nginx"] not in removed_dirs + assert CONFIG_DIRS["unbound"] not in removed_dirs + assert CONFIG_DIRS["postfix"] not in removed_dirs + assert CONFIG_DIRS["dovecot"] not in removed_dirs + assert CONFIG_FILES["nginx_conf"] in removed_files + assert CONFIG_FILES["unbound_chatmail"] in removed_files 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) removed = {entry["path"] for entry in file_calls} - assert "/etc/ssl/certs/mailserver.pem" in removed - assert "/etc/ssl/private/mailserver.key" in removed + assert TLS_PATHS["self_cert"] in removed + assert TLS_PATHS["self_key"] in removed