BootstrapPostfix.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from jinja2 import Environment, FileSystemLoader
  2. from modules.BootstrapBase import BootstrapBase
  3. from pathlib import Path
  4. import os
  5. import sys
  6. import time
  7. class Bootstrap(BootstrapBase):
  8. def bootstrap(self):
  9. # Connect to MySQL
  10. self.connect_mysql()
  11. # Wait for DNS
  12. self.wait_for_dns("mailcow.email")
  13. self.create_dir("/opt/postfix/conf/sql/")
  14. # Setup Jinja2 Environment and load vars
  15. self.env = Environment(
  16. loader=FileSystemLoader('./opt/postfix/conf/config_templates'),
  17. keep_trailing_newline=True,
  18. lstrip_blocks=True,
  19. trim_blocks=True
  20. )
  21. with open("/opt/postfix/conf/extra.cf", "r") as f:
  22. extra_config = f.read()
  23. extra_vars = {
  24. "VALID_CERT_DIRS": self.get_valid_cert_dirs(),
  25. "EXTRA_CF": extra_config
  26. }
  27. self.env_vars = self.prepare_template_vars('/overwrites.json', extra_vars)
  28. print("Set Timezone")
  29. self.set_timezone()
  30. print("Set Syslog redis")
  31. self.set_syslog_redis()
  32. print("Render config")
  33. self.render_config("aliases.j2", "/etc/aliases")
  34. self.render_config("mysql_relay_ne.cf.j2", "/opt/postfix/conf/sql/mysql_relay_ne.cf")
  35. self.render_config("mysql_relay_recipient_maps.cf.j2", "/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf")
  36. self.render_config("mysql_tls_policy_override_maps.cf.j2", "/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf")
  37. self.render_config("mysql_tls_enforce_in_policy.cf.j2", "/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf")
  38. self.render_config("mysql_sender_dependent_default_transport_maps.cf.j2", "/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf")
  39. self.render_config("mysql_transport_maps.cf.j2", "/opt/postfix/conf/sql/mysql_transport_maps.cf")
  40. self.render_config("mysql_virtual_resource_maps.cf.j2", "/opt/postfix/conf/sql/mysql_virtual_resource_maps.cf")
  41. self.render_config("mysql_sasl_passwd_maps_sender_dependent.cf.j2", "/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf")
  42. self.render_config("mysql_sasl_passwd_maps_transport_maps.cf.j2", "/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf")
  43. self.render_config("mysql_virtual_alias_domain_maps.cf.j2", "/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf")
  44. self.render_config("mysql_virtual_alias_maps.cf.j2", "/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf")
  45. self.render_config("mysql_recipient_bcc_maps.cf.j2", "/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf")
  46. self.render_config("mysql_sender_bcc_maps.cf.j2", "/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf")
  47. self.render_config("mysql_recipient_canonical_maps.cf.j2", "/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf")
  48. self.render_config("mysql_virtual_domains_maps.cf.j2", "/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf")
  49. self.render_config("mysql_virtual_mailbox_maps.cf.j2", "/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf")
  50. self.render_config("mysql_virtual_relay_domain_maps.cf.j2", "/opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf")
  51. self.render_config("mysql_virtual_sender_acl.cf.j2", "/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf")
  52. self.render_config("mysql_mbr_access_maps.cf.j2", "/opt/postfix/conf/sql/mysql_mbr_access_maps.cf")
  53. self.render_config("mysql_virtual_spamalias_maps.cf.j2", "/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf")
  54. self.render_config("sni.map.j2", "/opt/postfix/conf/sni.map")
  55. self.render_config("main.cf.j2", "/opt/postfix/conf/main.cf")
  56. # Conditional render
  57. if not Path("/opt/postfix/conf/dns_blocklists.cf").exists():
  58. self.render_config("dns_blocklists.cf.j2", "/opt/postfix/conf/dns_blocklists.cf")
  59. if not Path("/opt/postfix/conf/dns_reply.map").exists():
  60. self.render_config("dns_reply.map.j2", "/opt/postfix/conf/dns_reply.map")
  61. if not Path("/opt/postfix/conf/custom_postscreen_whitelist.cidr").exists():
  62. self.render_config("custom_postscreen_whitelist.cidr.j2", "/opt/postfix/conf/custom_postscreen_whitelist.cidr")
  63. if not Path("/opt/postfix/conf/custom_transport.pcre").exists():
  64. self.render_config("custom_transport.pcre.j2", "/opt/postfix/conf/custom_transport.pcre")
  65. # Create SNI Config
  66. self.run_command(["postmap", "-F", "hash:/opt/postfix/conf/sni.map"])
  67. # Fix Postfix permissions
  68. self.set_owner("/opt/postfix/conf/sql", user="root", group="postfix", recursive=True)
  69. self.set_owner("/opt/postfix/conf/custom_transport.pcre", user="root", group="postfix")
  70. for cf_file in Path("/opt/postfix/conf/sql").glob("*.cf"):
  71. self.set_permissions(cf_file, 0o640)
  72. self.set_permissions("/opt/postfix/conf/custom_transport.pcre", 0o640)
  73. self.set_owner("/var/spool/postfix/public", user="root", group="postdrop", recursive=True)
  74. self.set_owner("/var/spool/postfix/maildrop", user="root", group="postdrop", recursive=True)
  75. self.run_command(["postfix", "set-permissions"], check=False)
  76. # Checking if there is a leftover of a crashed postfix container before starting a new one
  77. pid_file = Path("/var/spool/postfix/pid/master.pid")
  78. if pid_file.exists():
  79. print(f"Removing stale Postfix PID file: {pid_file}")
  80. pid_file.unlink()
  81. def get_valid_cert_dirs(self):
  82. certs = {}
  83. base_path = Path("/etc/ssl/mail")
  84. if not base_path.exists():
  85. return certs
  86. for cert_dir in base_path.iterdir():
  87. if not cert_dir.is_dir():
  88. continue
  89. domains_file = cert_dir / "domains"
  90. cert_file = cert_dir / "cert.pem"
  91. key_file = cert_dir / "key.pem"
  92. if not (domains_file.exists() and cert_file.exists() and key_file.exists()):
  93. continue
  94. with open(domains_file, "r") as f:
  95. domains = [line.strip() for line in f if line.strip()]
  96. if domains:
  97. certs[str(cert_dir)] = domains
  98. return certs