runv-server

server tooling for runv.club
Log | Files | Refs | README

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())