mirror of
https://github.com/chatmail/relay.git
synced 2026-05-10 16:04:37 +00:00
move dovecot deployer out to dovecot/ directory
This commit is contained in:
@@ -1,13 +1,63 @@
|
|||||||
import importlib.resources
|
import importlib.resources
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from pyinfra.operations import server
|
from pyinfra.operations import files, server, systemd
|
||||||
|
|
||||||
|
|
||||||
def get_resource(arg, pkg=__package__):
|
def get_resource(arg, pkg=__package__):
|
||||||
return importlib.resources.files(pkg).joinpath(arg)
|
return importlib.resources.files(pkg).joinpath(arg)
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_remote_units(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"
|
||||||
|
root_owned = dict(user="root", group="root", mode="644")
|
||||||
|
|
||||||
|
# install systemd units
|
||||||
|
for fn in units:
|
||||||
|
execpath = fn if fn != "filtermail-incoming" else "filtermail"
|
||||||
|
params = dict(
|
||||||
|
execpath=f"{remote_venv_dir}/bin/{execpath}",
|
||||||
|
config_path=remote_chatmail_inipath,
|
||||||
|
remote_venv_dir=remote_venv_dir,
|
||||||
|
mail_domain=mail_domain,
|
||||||
|
)
|
||||||
|
|
||||||
|
basename = fn if "." in fn else f"{fn}.service"
|
||||||
|
|
||||||
|
source_path = get_resource(f"service/{basename}.f")
|
||||||
|
content = source_path.read_text().format(**params).encode()
|
||||||
|
|
||||||
|
files.put(
|
||||||
|
name=f"Upload {basename}",
|
||||||
|
src=io.BytesIO(content),
|
||||||
|
dest=f"/etc/systemd/system/{basename}",
|
||||||
|
**root_owned,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _activate_remote_units(units) -> None:
|
||||||
|
# activate systemd units
|
||||||
|
for fn in units:
|
||||||
|
basename = fn if "." in fn else f"{fn}.service"
|
||||||
|
|
||||||
|
if fn == "chatmail-expire" or fn == "chatmail-fsreport":
|
||||||
|
# don't auto-start but let the corresponding timer trigger execution
|
||||||
|
enabled = False
|
||||||
|
else:
|
||||||
|
enabled = True
|
||||||
|
systemd.service(
|
||||||
|
name=f"Setup {basename}",
|
||||||
|
service=basename,
|
||||||
|
running=enabled,
|
||||||
|
enabled=enabled,
|
||||||
|
restarted=enabled,
|
||||||
|
daemon_reload=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Deployment:
|
class Deployment:
|
||||||
def install(self, deployer):
|
def install(self, deployer):
|
||||||
# optional 'required_users' contains a list of (user, group, secondary-group-list) tuples.
|
# optional 'required_users' contains a list of (user, group, secondary-group-list) tuples.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
Chat Mail pyinfra deploy.
|
Chat Mail pyinfra deploy.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import io
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -13,14 +12,20 @@ from chatmaild.config import Config, read_config
|
|||||||
from pyinfra import facts, host, logger
|
from pyinfra import facts, host, logger
|
||||||
from pyinfra.api import FactBase
|
from pyinfra.api import FactBase
|
||||||
from pyinfra.facts.files import Sha256File
|
from pyinfra.facts.files import Sha256File
|
||||||
from pyinfra.facts.server import Sysctl
|
|
||||||
from pyinfra.facts.systemd import SystemdEnabled
|
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 .acmetool import AcmetoolDeployer
|
from .acmetool import AcmetoolDeployer
|
||||||
from .basedeploy import Deployer, Deployment, get_resource
|
from .basedeploy import (
|
||||||
|
Deployer,
|
||||||
|
Deployment,
|
||||||
|
_activate_remote_units,
|
||||||
|
_configure_remote_units,
|
||||||
|
get_resource,
|
||||||
|
)
|
||||||
|
from .dovecot.deployer import DovecotDeployer
|
||||||
from .opendkim.deployer import OpendkimDeployer
|
from .opendkim.deployer import OpendkimDeployer
|
||||||
from .postfix.deployer import PostfixDeployer
|
from .postfix.deployer import PostfixDeployer
|
||||||
from .www import build_webpages, find_merge_conflict, get_paths
|
from .www import build_webpages, find_merge_conflict, get_paths
|
||||||
@@ -132,55 +137,6 @@ def _configure_remote_venv_with_chatmaild(config) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _configure_remote_units(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"
|
|
||||||
root_owned = dict(user="root", group="root", mode="644")
|
|
||||||
|
|
||||||
# install systemd units
|
|
||||||
for fn in units:
|
|
||||||
execpath = fn if fn != "filtermail-incoming" else "filtermail"
|
|
||||||
params = dict(
|
|
||||||
execpath=f"{remote_venv_dir}/bin/{execpath}",
|
|
||||||
config_path=remote_chatmail_inipath,
|
|
||||||
remote_venv_dir=remote_venv_dir,
|
|
||||||
mail_domain=mail_domain,
|
|
||||||
)
|
|
||||||
|
|
||||||
basename = fn if "." in fn else f"{fn}.service"
|
|
||||||
|
|
||||||
source_path = get_resource(f"service/{basename}.f")
|
|
||||||
content = source_path.read_text().format(**params).encode()
|
|
||||||
|
|
||||||
files.put(
|
|
||||||
name=f"Upload {basename}",
|
|
||||||
src=io.BytesIO(content),
|
|
||||||
dest=f"/etc/systemd/system/{basename}",
|
|
||||||
**root_owned,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _activate_remote_units(units) -> None:
|
|
||||||
# activate systemd units
|
|
||||||
for fn in units:
|
|
||||||
basename = fn if "." in fn else f"{fn}.service"
|
|
||||||
|
|
||||||
if fn == "chatmail-expire" or fn == "chatmail-fsreport":
|
|
||||||
# don't auto-start but let the corresponding timer trigger execution
|
|
||||||
enabled = False
|
|
||||||
else:
|
|
||||||
enabled = True
|
|
||||||
systemd.service(
|
|
||||||
name=f"Setup {basename}",
|
|
||||||
service=basename,
|
|
||||||
running=enabled,
|
|
||||||
enabled=enabled,
|
|
||||||
restarted=enabled,
|
|
||||||
daemon_reload=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UnboundDeployer(Deployer):
|
class UnboundDeployer(Deployer):
|
||||||
def install(self):
|
def install(self):
|
||||||
# Run local DNS resolver `unbound`.
|
# Run local DNS resolver `unbound`.
|
||||||
@@ -252,131 +208,6 @@ class MtastsDeployer(Deployer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _install_dovecot_package(package: str, arch: str):
|
|
||||||
arch = "amd64" if arch == "x86_64" else arch
|
|
||||||
arch = "arm64" if arch == "aarch64" else arch
|
|
||||||
url = f"https://download.delta.chat/dovecot/dovecot-{package}_2.3.21%2Bdfsg1-3_{arch}.deb"
|
|
||||||
deb_filename = "/root/" + url.split("/")[-1]
|
|
||||||
|
|
||||||
match (package, arch):
|
|
||||||
case ("core", "amd64"):
|
|
||||||
sha256 = "dd060706f52a306fa863d874717210b9fe10536c824afe1790eec247ded5b27d"
|
|
||||||
case ("core", "arm64"):
|
|
||||||
sha256 = "e7548e8a82929722e973629ecc40fcfa886894cef3db88f23535149e7f730dc9"
|
|
||||||
case ("imapd", "amd64"):
|
|
||||||
sha256 = "8d8dc6fc00bbb6cdb25d345844f41ce2f1c53f764b79a838eb2a03103eebfa86"
|
|
||||||
case ("imapd", "arm64"):
|
|
||||||
sha256 = "178fa877ddd5df9930e8308b518f4b07df10e759050725f8217a0c1fb3fd707f"
|
|
||||||
case ("lmtpd", "amd64"):
|
|
||||||
sha256 = "2f69ba5e35363de50962d42cccbfe4ed8495265044e244007d7ccddad77513ab"
|
|
||||||
case ("lmtpd", "arm64"):
|
|
||||||
sha256 = "89f52fb36524f5877a177dff4a713ba771fd3f91f22ed0af7238d495e143b38f"
|
|
||||||
case _:
|
|
||||||
apt.packages(packages=[f"dovecot-{package}"])
|
|
||||||
return
|
|
||||||
|
|
||||||
files.download(
|
|
||||||
name=f"Download dovecot-{package}",
|
|
||||||
src=url,
|
|
||||||
dest=deb_filename,
|
|
||||||
sha256sum=sha256,
|
|
||||||
cache_time=60 * 60 * 24 * 365 * 10, # never redownload the package
|
|
||||||
)
|
|
||||||
|
|
||||||
apt.deb(name=f"Install dovecot-{package}", src=deb_filename)
|
|
||||||
|
|
||||||
|
|
||||||
def _configure_dovecot(config: Config, debug: bool = False) -> bool:
|
|
||||||
"""Configures Dovecot IMAP server."""
|
|
||||||
need_restart = False
|
|
||||||
|
|
||||||
main_config = files.template(
|
|
||||||
src=get_resource("dovecot/dovecot.conf.j2"),
|
|
||||||
dest="/etc/dovecot/dovecot.conf",
|
|
||||||
user="root",
|
|
||||||
group="root",
|
|
||||||
mode="644",
|
|
||||||
config=config,
|
|
||||||
debug=debug,
|
|
||||||
disable_ipv6=config.disable_ipv6,
|
|
||||||
)
|
|
||||||
need_restart |= main_config.changed
|
|
||||||
auth_config = files.put(
|
|
||||||
src=get_resource("dovecot/auth.conf"),
|
|
||||||
dest="/etc/dovecot/auth.conf",
|
|
||||||
user="root",
|
|
||||||
group="root",
|
|
||||||
mode="644",
|
|
||||||
)
|
|
||||||
need_restart |= auth_config.changed
|
|
||||||
lua_push_notification_script = files.put(
|
|
||||||
src=get_resource("dovecot/push_notification.lua"),
|
|
||||||
dest="/etc/dovecot/push_notification.lua",
|
|
||||||
user="root",
|
|
||||||
group="root",
|
|
||||||
mode="644",
|
|
||||||
)
|
|
||||||
need_restart |= lua_push_notification_script.changed
|
|
||||||
|
|
||||||
# as per https://doc.dovecot.org/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,
|
|
||||||
)
|
|
||||||
|
|
||||||
timezone_env = files.line(
|
|
||||||
name="Set TZ environment variable",
|
|
||||||
path="/etc/environment",
|
|
||||||
line="TZ=:/etc/localtime",
|
|
||||||
)
|
|
||||||
need_restart |= timezone_env.changed
|
|
||||||
|
|
||||||
return need_restart
|
|
||||||
|
|
||||||
|
|
||||||
class DovecotDeployer(Deployer):
|
|
||||||
def __init__(self, config, disable_mail):
|
|
||||||
self.config = config
|
|
||||||
self.disable_mail = disable_mail
|
|
||||||
self.units = ["doveauth"]
|
|
||||||
|
|
||||||
def install(self):
|
|
||||||
arch = host.get_fact(facts.server.Arch)
|
|
||||||
if not "dovecot.service" in host.get_fact(SystemdEnabled):
|
|
||||||
_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)
|
|
||||||
self.need_restart = _configure_dovecot(self.config)
|
|
||||||
|
|
||||||
def activate(self):
|
|
||||||
_activate_remote_units(self.units)
|
|
||||||
|
|
||||||
restart = False if self.disable_mail else self.need_restart
|
|
||||||
|
|
||||||
systemd.service(
|
|
||||||
name="disable dovecot for now"
|
|
||||||
if self.disable_mail
|
|
||||||
else "Start and enable Dovecot",
|
|
||||||
service="dovecot.service",
|
|
||||||
running=False if self.disable_mail else True,
|
|
||||||
enabled=False if self.disable_mail else True,
|
|
||||||
restarted=restart,
|
|
||||||
)
|
|
||||||
self.need_restart = False
|
|
||||||
|
|
||||||
|
|
||||||
def _configure_nginx(config: Config, debug: bool = False) -> bool:
|
def _configure_nginx(config: Config, debug: bool = False) -> bool:
|
||||||
"""Configures nginx HTTP server."""
|
"""Configures nginx HTTP server."""
|
||||||
need_restart = False
|
need_restart = False
|
||||||
|
|||||||
1
cmdeploy/src/cmdeploy/dovecot/__init__.py
Normal file
1
cmdeploy/src/cmdeploy/dovecot/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#
|
||||||
137
cmdeploy/src/cmdeploy/dovecot/deployer.py
Normal file
137
cmdeploy/src/cmdeploy/dovecot/deployer.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
from chatmaild.config import Config
|
||||||
|
from pyinfra import host
|
||||||
|
from pyinfra.facts.server import Arch, Sysctl
|
||||||
|
from pyinfra.facts.systemd import SystemdEnabled
|
||||||
|
from pyinfra.operations import apt, files, server, systemd
|
||||||
|
|
||||||
|
from cmdeploy.basedeploy import (
|
||||||
|
Deployer,
|
||||||
|
_activate_remote_units,
|
||||||
|
_configure_remote_units,
|
||||||
|
get_resource,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DovecotDeployer(Deployer):
|
||||||
|
def __init__(self, config, disable_mail):
|
||||||
|
self.config = config
|
||||||
|
self.disable_mail = disable_mail
|
||||||
|
self.units = ["doveauth"]
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
arch = host.get_fact(Arch)
|
||||||
|
if not "dovecot.service" in host.get_fact(SystemdEnabled):
|
||||||
|
_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)
|
||||||
|
self.need_restart = _configure_dovecot(self.config)
|
||||||
|
|
||||||
|
def activate(self):
|
||||||
|
_activate_remote_units(self.units)
|
||||||
|
|
||||||
|
restart = False if self.disable_mail else self.need_restart
|
||||||
|
|
||||||
|
systemd.service(
|
||||||
|
name="disable dovecot for now"
|
||||||
|
if self.disable_mail
|
||||||
|
else "Start and enable Dovecot",
|
||||||
|
service="dovecot.service",
|
||||||
|
running=False if self.disable_mail else True,
|
||||||
|
enabled=False if self.disable_mail else True,
|
||||||
|
restarted=restart,
|
||||||
|
)
|
||||||
|
self.need_restart = False
|
||||||
|
|
||||||
|
|
||||||
|
def _install_dovecot_package(package: str, arch: str):
|
||||||
|
arch = "amd64" if arch == "x86_64" else arch
|
||||||
|
arch = "arm64" if arch == "aarch64" else arch
|
||||||
|
url = f"https://download.delta.chat/dovecot/dovecot-{package}_2.3.21%2Bdfsg1-3_{arch}.deb"
|
||||||
|
deb_filename = "/root/" + url.split("/")[-1]
|
||||||
|
|
||||||
|
match (package, arch):
|
||||||
|
case ("core", "amd64"):
|
||||||
|
sha256 = "dd060706f52a306fa863d874717210b9fe10536c824afe1790eec247ded5b27d"
|
||||||
|
case ("core", "arm64"):
|
||||||
|
sha256 = "e7548e8a82929722e973629ecc40fcfa886894cef3db88f23535149e7f730dc9"
|
||||||
|
case ("imapd", "amd64"):
|
||||||
|
sha256 = "8d8dc6fc00bbb6cdb25d345844f41ce2f1c53f764b79a838eb2a03103eebfa86"
|
||||||
|
case ("imapd", "arm64"):
|
||||||
|
sha256 = "178fa877ddd5df9930e8308b518f4b07df10e759050725f8217a0c1fb3fd707f"
|
||||||
|
case ("lmtpd", "amd64"):
|
||||||
|
sha256 = "2f69ba5e35363de50962d42cccbfe4ed8495265044e244007d7ccddad77513ab"
|
||||||
|
case ("lmtpd", "arm64"):
|
||||||
|
sha256 = "89f52fb36524f5877a177dff4a713ba771fd3f91f22ed0af7238d495e143b38f"
|
||||||
|
case _:
|
||||||
|
apt.packages(packages=[f"dovecot-{package}"])
|
||||||
|
return
|
||||||
|
|
||||||
|
files.download(
|
||||||
|
name=f"Download dovecot-{package}",
|
||||||
|
src=url,
|
||||||
|
dest=deb_filename,
|
||||||
|
sha256sum=sha256,
|
||||||
|
cache_time=60 * 60 * 24 * 365 * 10, # never redownload the package
|
||||||
|
)
|
||||||
|
|
||||||
|
apt.deb(name=f"Install dovecot-{package}", src=deb_filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_dovecot(config: Config, debug: bool = False) -> bool:
|
||||||
|
"""Configures Dovecot IMAP server."""
|
||||||
|
need_restart = False
|
||||||
|
|
||||||
|
main_config = files.template(
|
||||||
|
src=get_resource("dovecot/dovecot.conf.j2"),
|
||||||
|
dest="/etc/dovecot/dovecot.conf",
|
||||||
|
user="root",
|
||||||
|
group="root",
|
||||||
|
mode="644",
|
||||||
|
config=config,
|
||||||
|
debug=debug,
|
||||||
|
disable_ipv6=config.disable_ipv6,
|
||||||
|
)
|
||||||
|
need_restart |= main_config.changed
|
||||||
|
auth_config = files.put(
|
||||||
|
src=get_resource("dovecot/auth.conf"),
|
||||||
|
dest="/etc/dovecot/auth.conf",
|
||||||
|
user="root",
|
||||||
|
group="root",
|
||||||
|
mode="644",
|
||||||
|
)
|
||||||
|
need_restart |= auth_config.changed
|
||||||
|
lua_push_notification_script = files.put(
|
||||||
|
src=get_resource("dovecot/push_notification.lua"),
|
||||||
|
dest="/etc/dovecot/push_notification.lua",
|
||||||
|
user="root",
|
||||||
|
group="root",
|
||||||
|
mode="644",
|
||||||
|
)
|
||||||
|
need_restart |= lua_push_notification_script.changed
|
||||||
|
|
||||||
|
# as per https://doc.dovecot.org/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,
|
||||||
|
)
|
||||||
|
|
||||||
|
timezone_env = files.line(
|
||||||
|
name="Set TZ environment variable",
|
||||||
|
path="/etc/environment",
|
||||||
|
line="TZ=:/etc/localtime",
|
||||||
|
)
|
||||||
|
need_restart |= timezone_env.changed
|
||||||
|
|
||||||
|
return need_restart
|
||||||
Reference in New Issue
Block a user