inspect_postfix_mysql_aliases.py (3159B)
1 #!/usr/bin/env python3 2 """ 3 Lê mysql-virtual-alias-maps.cf e mostra estrutura da tabela (read-only). 4 5 Ajuda a preencher /etc/runv-member-mail.json para backend postfix-mysql. 6 """ 7 8 from __future__ import annotations 9 10 import argparse 11 import re 12 import subprocess 13 import sys 14 from pathlib import Path 15 16 _SCRIPT_DIR = Path(__file__).resolve().parent 17 _REPO_TOOLS_LIB = _SCRIPT_DIR.parent.parent / "tools" / "lib" 18 if str(_REPO_TOOLS_LIB) not in sys.path: 19 sys.path.insert(0, str(_REPO_TOOLS_LIB)) 20 21 import runv_mail_sync as ms # noqa: E402 22 23 QUERY_HINT = re.compile( 24 r"SELECT\s+[`']?(\w+)[`']?\s+FROM\s+[`']?(\w+)[`']?\s+WHERE\s+[`']?(\w+)[`']?\s*=", 25 re.IGNORECASE, 26 ) 27 28 29 def main() -> int: 30 p = argparse.ArgumentParser(description="Inspecionar mapa MySQL de aliases Postfix") 31 p.add_argument( 32 "--map-file", 33 default="/etc/postfix/mysql-virtual-alias-maps.cf", 34 help="ficheiro .cf do Postfix", 35 ) 36 p.add_argument("--table", default="", help="forçar nome da tabela") 37 args = p.parse_args() 38 39 if sys.platform == "win32": 40 print("Execute na VPS Linux.", file=sys.stderr) 41 return 2 42 43 map_path = Path(args.map_file) 44 if not map_path.is_file(): 45 print(f"ausente: {map_path}", file=sys.stderr) 46 return 1 47 48 parsed = ms.parse_postfix_mysql_cf(map_path) 49 print(f"=== {map_path} ===") 50 for key in ("hosts", "user", "dbname", "query"): 51 val = parsed.get(key, "") 52 if key == "password": 53 continue 54 print(f"{key} = {val}") 55 print("password = ***") 56 57 query = parsed.get("query", "") 58 table = args.table.strip() 59 dest_col = "" 60 addr_col = "" 61 m = QUERY_HINT.search(query.replace("\n", " ")) 62 if m: 63 dest_col, table, addr_col = m.group(1), m.group(2), m.group(3) 64 print(f"\ninferido da query: tabela={table!r} col_destino={dest_col!r} col_endereco={addr_col!r}") 65 66 if not table: 67 print("\nNão foi possível inferir a tabela; use --table NOME", file=sys.stderr) 68 return 1 69 70 sql = f"DESCRIBE `{table}`;" 71 print(f"\n=== {sql} ===") 72 try: 73 out = ms.mysql_exec(parsed, sql, dry_run=False) 74 print(out or "(sem saída)") 75 except SystemExit as e: 76 print(e, file=sys.stderr) 77 return 1 78 79 sample = f"SELECT * FROM `{table}` LIMIT 5;" 80 print(f"\n=== {sample} ===") 81 try: 82 print(ms.mysql_exec(parsed, sample, dry_run=False) or "(vazio)") 83 except SystemExit: 84 print("(amostra indisponível)", file=sys.stderr) 85 86 print( 87 "\nSugestão /etc/runv-member-mail.json:\n" 88 "{\n" 89 ' "enabled": true,\n' 90 ' "backend": "postfix-mysql",\n' 91 f' "mysql_map_file": "{map_path}",\n' 92 " \"mysql\": {\n" 93 f' "table": "{table}",\n' 94 f' "address_column": "{addr_col or "address"}",\n' 95 f' "goto_column": "{dest_col or "goto"}",\n' 96 ' "active_column": "active",\n' 97 ' "active_value": "1"\n' 98 " },\n" 99 ' "reload_postfix": true,\n' 100 ' "auto_sync_on_approve": true\n' 101 "}" 102 ) 103 return 0 104 105 106 if __name__ == "__main__": 107 raise SystemExit(main())