"""Email relay VM122 → VM112 — status, config e teste (Spec 004 / Infra CODE).""" from __future__ import annotations import json import os import smtplib from datetime import datetime, timezone from email.message import EmailMessage from pathlib import Path from typing import Any from app import mail_notify CONFIG_PATH = Path(os.getenv("EMAIL_RELAY_CONFIG_PATH", "/data/email_relay_config.json")) RELAY_HOST = os.getenv("DESK_RELAY_HOST", "10.10.10.112") RELAY_PORT = int(os.getenv("DESK_RELAY_PORT", "25")) SMTP_HOST = os.getenv("DESK_SMTP_HOST", "10.10.10.122") SMTP_PORT = int(os.getenv("DESK_SMTP_PORT", "25")) MAIL_FROM = os.getenv("DESK_MAIL_FROM", "ligbox-ops@ligbox.com.br") DEFAULT_CONFIG: dict[str, Any] = { "vm": "122", "vm_label": "VM122 · Ops Desk", "service_id": "vm122-email-relay", "title": "Email Relay (Postfix)", "relayhost": RELAY_HOST, "relayport": RELAY_PORT, "smtp_host": SMTP_HOST, "smtp_port": SMTP_PORT, "mail_from": MAIL_FROM, "myorigin": "ligbox.com.br", "transport_local": { "ligbox.com.br": "LMTP [10.10.10.112]:7025", "ibytera.com": "LMTP [10.10.10.112]:7025", "dratcoin.com": "LMTP [10.10.10.112]:7025", }, "external_route": "relayhost → mail.ligbox.com.br (DKIM/SPF)", "docs": "docs/postfix-vm122.md", } def _now() -> str: return datetime.now(timezone.utc).isoformat() def load_config() -> dict[str, Any]: cfg = dict(DEFAULT_CONFIG) cfg["updated_at"] = _now() if CONFIG_PATH.is_file(): try: stored = json.loads(CONFIG_PATH.read_text(encoding="utf-8")) if isinstance(stored, dict): cfg.update(stored) except Exception: pass return cfg def save_config(patch: dict[str, Any]) -> dict[str, Any]: cfg = load_config() cfg.update(patch) cfg["updated_at"] = _now() CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) CONFIG_PATH.write_text(json.dumps(cfg, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") return cfg def _probe_smtp(host: str, port: int, timeout: float = 8.0) -> dict[str, Any]: try: with smtplib.SMTP(host, port, timeout=timeout) as smtp: code, _ = smtp.ehlo() smtp.noop() ok = 200 <= code < 400 return {"ok": ok, "status": "online" if ok else "check", "detail": f"SMTP {host}:{port} EHLO {code}"} except Exception as exc: return {"ok": False, "status": "down", "detail": f"SMTP {host}:{port} — {exc}"} def build_status() -> dict[str, Any]: cfg = load_config() local = _probe_smtp(cfg["smtp_host"], int(cfg["smtp_port"])) relay = _probe_smtp(cfg["relayhost"], int(cfg["relayport"])) ok = bool(local.get("ok") and relay.get("ok")) detail_parts = [ local.get("detail", ""), f"relay {cfg['relayhost']}:{cfg['relayport']} — {relay.get('detail', '')}", ] return { "ok": ok, "status": "online" if ok else ("check" if local.get("ok") else "down"), "detail": " · ".join(p for p in detail_parts if p), "config": cfg, "checks": { "smtp_local": local, "smtp_relay": relay, }, "generated_at": _now(), } def probe_stack() -> dict[str, Any]: st = build_status() return {"ok": st["ok"], "status": st["status"], "detail": st["detail"]} def send_test_email(to: str, subject: str | None = None) -> dict[str, Any]: to = (to or "").strip() if not to: return {"ok": False, "detail": "destinatário vazio"} subj = subject or "[Ligbox Ops] Teste Email Relay VM122" body = ( "Teste do relay Postfix VM122 → VM112 (mail.ligbox.com.br).\n\n" f"Gerado em: {_now()}\n" ) ok = mail_notify.send_email(to, subj, body) return { "ok": ok, "detail": "enviado via SMTP local" if ok else "falha SMTP — verificar Postfix/relay", "to": to, "from": MAIL_FROM, "at": _now(), }