|
2 | 2 | Chat Mail pyinfra deploy. |
3 | 3 | """ |
4 | 4 |
|
5 | | -import io |
6 | 5 | import shutil |
7 | 6 | import subprocess |
8 | 7 | import sys |
|
13 | 12 | from pyinfra import facts, host, logger |
14 | 13 | from pyinfra.api import FactBase |
15 | 14 | from pyinfra.facts.files import Sha256File |
16 | | -from pyinfra.facts.server import Sysctl |
17 | 15 | from pyinfra.facts.systemd import SystemdEnabled |
18 | 16 | from pyinfra.operations import apt, files, pip, server, systemd |
19 | 17 |
|
20 | 18 | from cmdeploy.cmdeploy import Out |
21 | 19 |
|
22 | 20 | from .acmetool import AcmetoolDeployer |
23 | | -from .basedeploy import Deployer, Deployment, get_resource |
| 21 | +from .basedeploy import ( |
| 22 | + Deployer, |
| 23 | + Deployment, |
| 24 | + _activate_remote_units, |
| 25 | + _configure_remote_units, |
| 26 | + get_resource, |
| 27 | +) |
| 28 | +from .dovecot.deployer import DovecotDeployer |
24 | 29 | from .opendkim.deployer import OpendkimDeployer |
25 | 30 | from .postfix.deployer import PostfixDeployer |
26 | 31 | from .www import build_webpages, find_merge_conflict, get_paths |
@@ -132,55 +137,6 @@ def _configure_remote_venv_with_chatmaild(config) -> None: |
132 | 137 | ) |
133 | 138 |
|
134 | 139 |
|
135 | | -def _configure_remote_units(mail_domain, units) -> None: |
136 | | - remote_base_dir = "/usr/local/lib/chatmaild" |
137 | | - remote_venv_dir = f"{remote_base_dir}/venv" |
138 | | - remote_chatmail_inipath = f"{remote_base_dir}/chatmail.ini" |
139 | | - root_owned = dict(user="root", group="root", mode="644") |
140 | | - |
141 | | - # install systemd units |
142 | | - for fn in units: |
143 | | - execpath = fn if fn != "filtermail-incoming" else "filtermail" |
144 | | - params = dict( |
145 | | - execpath=f"{remote_venv_dir}/bin/{execpath}", |
146 | | - config_path=remote_chatmail_inipath, |
147 | | - remote_venv_dir=remote_venv_dir, |
148 | | - mail_domain=mail_domain, |
149 | | - ) |
150 | | - |
151 | | - basename = fn if "." in fn else f"{fn}.service" |
152 | | - |
153 | | - source_path = get_resource(f"service/{basename}.f") |
154 | | - content = source_path.read_text().format(**params).encode() |
155 | | - |
156 | | - files.put( |
157 | | - name=f"Upload {basename}", |
158 | | - src=io.BytesIO(content), |
159 | | - dest=f"/etc/systemd/system/{basename}", |
160 | | - **root_owned, |
161 | | - ) |
162 | | - |
163 | | - |
164 | | -def _activate_remote_units(units) -> None: |
165 | | - # activate systemd units |
166 | | - for fn in units: |
167 | | - basename = fn if "." in fn else f"{fn}.service" |
168 | | - |
169 | | - if fn == "chatmail-expire" or fn == "chatmail-fsreport": |
170 | | - # don't auto-start but let the corresponding timer trigger execution |
171 | | - enabled = False |
172 | | - else: |
173 | | - enabled = True |
174 | | - systemd.service( |
175 | | - name=f"Setup {basename}", |
176 | | - service=basename, |
177 | | - running=enabled, |
178 | | - enabled=enabled, |
179 | | - restarted=enabled, |
180 | | - daemon_reload=True, |
181 | | - ) |
182 | | - |
183 | | - |
184 | 140 | class UnboundDeployer(Deployer): |
185 | 141 | def install(self): |
186 | 142 | # Run local DNS resolver `unbound`. |
@@ -252,131 +208,6 @@ def activate(self): |
252 | 208 | ) |
253 | 209 |
|
254 | 210 |
|
255 | | -def _install_dovecot_package(package: str, arch: str): |
256 | | - arch = "amd64" if arch == "x86_64" else arch |
257 | | - arch = "arm64" if arch == "aarch64" else arch |
258 | | - url = f"https://download.delta.chat/dovecot/dovecot-{package}_2.3.21%2Bdfsg1-3_{arch}.deb" |
259 | | - deb_filename = "/root/" + url.split("/")[-1] |
260 | | - |
261 | | - match (package, arch): |
262 | | - case ("core", "amd64"): |
263 | | - sha256 = "dd060706f52a306fa863d874717210b9fe10536c824afe1790eec247ded5b27d" |
264 | | - case ("core", "arm64"): |
265 | | - sha256 = "e7548e8a82929722e973629ecc40fcfa886894cef3db88f23535149e7f730dc9" |
266 | | - case ("imapd", "amd64"): |
267 | | - sha256 = "8d8dc6fc00bbb6cdb25d345844f41ce2f1c53f764b79a838eb2a03103eebfa86" |
268 | | - case ("imapd", "arm64"): |
269 | | - sha256 = "178fa877ddd5df9930e8308b518f4b07df10e759050725f8217a0c1fb3fd707f" |
270 | | - case ("lmtpd", "amd64"): |
271 | | - sha256 = "2f69ba5e35363de50962d42cccbfe4ed8495265044e244007d7ccddad77513ab" |
272 | | - case ("lmtpd", "arm64"): |
273 | | - sha256 = "89f52fb36524f5877a177dff4a713ba771fd3f91f22ed0af7238d495e143b38f" |
274 | | - case _: |
275 | | - apt.packages(packages=[f"dovecot-{package}"]) |
276 | | - return |
277 | | - |
278 | | - files.download( |
279 | | - name=f"Download dovecot-{package}", |
280 | | - src=url, |
281 | | - dest=deb_filename, |
282 | | - sha256sum=sha256, |
283 | | - cache_time=60 * 60 * 24 * 365 * 10, # never redownload the package |
284 | | - ) |
285 | | - |
286 | | - apt.deb(name=f"Install dovecot-{package}", src=deb_filename) |
287 | | - |
288 | | - |
289 | | -def _configure_dovecot(config: Config, debug: bool = False) -> bool: |
290 | | - """Configures Dovecot IMAP server.""" |
291 | | - need_restart = False |
292 | | - |
293 | | - main_config = files.template( |
294 | | - src=get_resource("dovecot/dovecot.conf.j2"), |
295 | | - dest="/etc/dovecot/dovecot.conf", |
296 | | - user="root", |
297 | | - group="root", |
298 | | - mode="644", |
299 | | - config=config, |
300 | | - debug=debug, |
301 | | - disable_ipv6=config.disable_ipv6, |
302 | | - ) |
303 | | - need_restart |= main_config.changed |
304 | | - auth_config = files.put( |
305 | | - src=get_resource("dovecot/auth.conf"), |
306 | | - dest="/etc/dovecot/auth.conf", |
307 | | - user="root", |
308 | | - group="root", |
309 | | - mode="644", |
310 | | - ) |
311 | | - need_restart |= auth_config.changed |
312 | | - lua_push_notification_script = files.put( |
313 | | - src=get_resource("dovecot/push_notification.lua"), |
314 | | - dest="/etc/dovecot/push_notification.lua", |
315 | | - user="root", |
316 | | - group="root", |
317 | | - mode="644", |
318 | | - ) |
319 | | - need_restart |= lua_push_notification_script.changed |
320 | | - |
321 | | - # as per https://doc.dovecot.org/configuration_manual/os/ |
322 | | - # it is recommended to set the following inotify limits |
323 | | - for name in ("max_user_instances", "max_user_watches"): |
324 | | - key = f"fs.inotify.{name}" |
325 | | - if host.get_fact(Sysctl)[key] > 65535: |
326 | | - # Skip updating limits if already sufficient |
327 | | - # (enables running in incus containers where sysctl readonly) |
328 | | - continue |
329 | | - server.sysctl( |
330 | | - name=f"Change {key}", |
331 | | - key=key, |
332 | | - value=65535, |
333 | | - persist=True, |
334 | | - ) |
335 | | - |
336 | | - timezone_env = files.line( |
337 | | - name="Set TZ environment variable", |
338 | | - path="/etc/environment", |
339 | | - line="TZ=:/etc/localtime", |
340 | | - ) |
341 | | - need_restart |= timezone_env.changed |
342 | | - |
343 | | - return need_restart |
344 | | - |
345 | | - |
346 | | -class DovecotDeployer(Deployer): |
347 | | - def __init__(self, config, disable_mail): |
348 | | - self.config = config |
349 | | - self.disable_mail = disable_mail |
350 | | - self.units = ["doveauth"] |
351 | | - |
352 | | - def install(self): |
353 | | - arch = host.get_fact(facts.server.Arch) |
354 | | - if not "dovecot.service" in host.get_fact(SystemdEnabled): |
355 | | - _install_dovecot_package("core", arch) |
356 | | - _install_dovecot_package("imapd", arch) |
357 | | - _install_dovecot_package("lmtpd", arch) |
358 | | - |
359 | | - def configure(self): |
360 | | - _configure_remote_units(self.config.mail_domain, self.units) |
361 | | - self.need_restart = _configure_dovecot(self.config) |
362 | | - |
363 | | - def activate(self): |
364 | | - _activate_remote_units(self.units) |
365 | | - |
366 | | - restart = False if self.disable_mail else self.need_restart |
367 | | - |
368 | | - systemd.service( |
369 | | - name="disable dovecot for now" |
370 | | - if self.disable_mail |
371 | | - else "Start and enable Dovecot", |
372 | | - service="dovecot.service", |
373 | | - running=False if self.disable_mail else True, |
374 | | - enabled=False if self.disable_mail else True, |
375 | | - restarted=restart, |
376 | | - ) |
377 | | - self.need_restart = False |
378 | | - |
379 | | - |
380 | 211 | def _configure_nginx(config: Config, debug: bool = False) -> bool: |
381 | 212 | """Configures nginx HTTP server.""" |
382 | 213 | need_restart = False |
|
0 commit comments