discover_mail_stack.py (4954B)
1 #!/usr/bin/env python3 2 """ 3 Inventário read-only do stack de email no servidor (Postfix, Dovecot, Roundcube, vmail). 4 5 Não altera configuração. Use na VPS antes de activar sync_member_email_aliases. 6 """ 7 8 from __future__ import annotations 9 10 import argparse 11 import shutil 12 import subprocess 13 import sys 14 from pathlib import Path 15 16 17 def run(cmd: list[str], *, timeout: int = 30) -> tuple[int, str]: 18 if shutil.which(cmd[0]) is None: 19 return 127, f"(comando ausente: {cmd[0]})" 20 try: 21 proc = subprocess.run( 22 cmd, 23 capture_output=True, 24 text=True, 25 timeout=timeout, 26 ) 27 out = (proc.stdout or "") + (proc.stderr or "") 28 return proc.returncode, out.strip() 29 except subprocess.TimeoutExpired: 30 return 124, "(timeout)" 31 except OSError as e: 32 return 1, str(e) 33 34 35 def section(title: str) -> None: 36 print(f"\n=== {title} ===") 37 38 39 def main() -> int: 40 p = argparse.ArgumentParser(description="Inventário do stack de email (read-only)") 41 p.parse_args() 42 43 if sys.platform == "win32": 44 print("Execute este script na VPS Linux (Debian).", file=sys.stderr) 45 return 2 46 47 section("Pacotes Debian (dpkg)") 48 code, out = run( 49 [ 50 "dpkg-query", 51 "-W", 52 "-f=${Package}\t${Status}\n", 53 "postfix", 54 "dovecot-core", 55 "dovecot-imapd", 56 "roundcube-core", 57 "roundcube", 58 "postfix-mysql", 59 "postfix-ldap", 60 ], 61 ) 62 if code == 127: 63 code, out = run(["dpkg", "-l"]) 64 if code == 0: 65 for line in out.splitlines(): 66 low = line.lower() 67 if any( 68 k in low 69 for k in ( 70 "postfix", 71 "dovecot", 72 "roundcube", 73 "rspamd", 74 "clamav", 75 "spamassassin", 76 ) 77 ): 78 print(line) 79 else: 80 print(out) 81 else: 82 print(out or "(nenhum pacote listado com esses nomes exactos)") 83 84 section("Serviços (systemctl is-active)") 85 for unit in ( 86 "postfix", 87 "dovecot", 88 "apache2", 89 "nginx", 90 "php8.2-fpm", 91 "php8.3-fpm", 92 ): 93 code, out = run(["systemctl", "is-active", unit]) 94 if code != 127: 95 print(f"{unit}: {out}") 96 97 section("Postfix (postconf)") 98 for key in ( 99 "myhostname", 100 "mydomain", 101 "virtual_mailbox_domains", 102 "virtual_mailbox_maps", 103 "virtual_alias_maps", 104 "relay_domains", 105 "transport_maps", 106 ): 107 code, out = run(["postconf", "-h", key]) 108 if code == 127: 109 print("postconf não instalado") 110 break 111 print(f"{key} = {out}") 112 113 section("Ficheiros comuns") 114 paths = [ 115 "/etc/postfix/main.cf", 116 "/etc/postfix/virtual", 117 "/etc/postfix/virtual.db", 118 "/etc/postfix/mysql-virtual-alias-maps.cf", 119 "/etc/dovecot/dovecot.conf", 120 "/var/vmail", 121 "/etc/roundcube", 122 "/usr/share/roundcube", 123 "/var/www/roundcube", 124 "/etc/runv-email.json", 125 "/etc/runv-member-mail.json", 126 "/var/lib/runv/email-aliases.json", 127 ] 128 for path in paths: 129 pth = Path(path) 130 if pth.is_dir(): 131 print(f"{path}/ [dir]") 132 elif pth.is_file(): 133 print(f"{path} [file]") 134 else: 135 print(f"{path} (ausente)") 136 137 section("RunV aliases de membros") 138 aliases = Path("/var/lib/runv/email-aliases.json") 139 if aliases.is_file(): 140 print(f"{aliases} existe ({aliases.stat().st_size} bytes)") 141 else: 142 print(f"{aliases} ausente") 143 144 cfg = Path("/etc/runv-member-mail.json") 145 if cfg.is_file(): 146 print(f"sync MTA configurado: {cfg}") 147 else: 148 print( 149 f"sync MTA não configurado ({cfg} ausente). " 150 "Ver email/config/runv-member-mail.example.json" 151 ) 152 153 section("Mailgun transacional (só leitura de paths)") 154 for path in ("/etc/runv-email.json", "/etc/runv-email.secrets.json"): 155 pth = Path(path) 156 print(f"{path}: {'presente' if pth.is_file() else 'ausente'}") 157 158 maps = "" 159 code, maps = run(["postconf", "-h", "virtual_alias_maps"]) 160 if code == 0 and "mysql:" in maps: 161 print( 162 "\nPróximo passo (MySQL): sudo python3 scripts/admin/inspect_postfix_mysql_aliases.py\n" 163 " depois copiar email/config/runv-member-mail.example.json para /etc/runv-member-mail.json\n" 164 " e: sudo runv-admin-email-alias sync" 165 ) 166 else: 167 print( 168 "\nPróximo passo: configurar /etc/runv-member-mail.json e runv-admin-email-alias sync" 169 ) 170 return 0 171 172 173 if __name__ == "__main__": 174 raise SystemExit(main())