# run_mitmweb
#!/usr/bin/env python3
"""Ephemeral mitmweb launcher optimized for Tailscale and Ubuntu Server."""

import os
import shutil
import signal
import socket
import subprocess
import sys

PROXY_PORT = 8080
WEB_PORT = 8081


def get_tailscale_ip() -> str | None:
  """Retrieve Tailscale IPv4 address via CLI or interface query."""
  if shutil.which("tailscale"):
    try:
      res = subprocess.run(
          ["tailscale", "ip", "-4"],
          capture_output=True,
          text=True,
          check=True,
          timeout=3,
      )
      ip = res.stdout.strip()
      if ip:
        return ip
    except Exception:
      pass
  return None


def get_network_ips() -> dict[str, str]:
  """Return a dictionary of interface IP addresses."""
  ips = {}
  ts_ip = get_tailscale_ip()
  if ts_ip:
    ips["Tailscale"] = ts_ip

  # Primary routing IP (LAN/Default Gateway)
  try:
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
      s.connect(("8.8.8.8", 80))
      primary = s.getsockname()[0]
      if primary and not primary.startswith("127."):
        ips["Default Route"] = primary
  except Exception:
    pass

  return ips


def is_port_in_use(port: int) -> bool:
  """Check if a local TCP port is already bound."""
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    return s.connect_ex(("127.0.0.1", port)) == 0


def find_ephemeral_runner() -> list[str] | None:
  """Locate uvx or pipx in current PATH or standard user directories."""
  extended_path = (
      os.environ.get("PATH", "")
      + ":"
      + os.path.expanduser("~/.local/bin")
      + ":"
      + os.path.expanduser("~/.cargo/bin")
  )

  if shutil.which("uvx", path=extended_path):
    uvx_path = shutil.which("uvx", path=extended_path)
    return [uvx_path, "--from", "mitmproxy", "mitmweb"]

  if shutil.which("pipx", path=extended_path):
    pipx_path = shutil.which("pipx", path=extended_path)
    return [pipx_path, "run", "--spec", "mitmproxy", "mitmweb"]

  return None

def main():
  # 1. Pre-flight Port Checks
  for port in (PROXY_PORT, WEB_PORT):
    if is_port_in_use(port):
      print(
          f"[-] Port {port} is already in use. Terminate the existing process"
          " first.",
          file=sys.stderr,
      )
      sys.exit(1)

  # 2. Runner Detection
  runner = find_ephemeral_runner()
  if not runner:
    print("[-] Neither 'uvx' nor 'pipx' found in PATH.", file=sys.stderr)
    print("    Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh")
    sys.exit(1)

  # 3. Network Detection
  ip_map = get_network_ips()
  best_ip = (
      ip_map.get("Tailscale")
      or ip_map.get("Default Route")
      or "<your-server-ip>"
  )

  # 4. Construct Command
  cmd = runner + [
    "--web-host",
    "0.0.0.0",
    "--web-port",
    str(WEB_PORT),
    "-p",
    str(PROXY_PORT),
    "--set",
    "block_global=false",
    "--ignore-hosts",
    r"^(.+\.)?apple\.com:443$",
    "--ignore-hosts",
    r"^(.+\.)?icloud\.com:443$",
    "--ignore-hosts",
    r"^(.+\.)?google(apis)?\.com:443$",
  ]

cmd = runner + [
    "--web-host",
    "0.0.0.0",
    "--web-port",
    str(WEB_PORT),
    "-p",
    str(PROXY_PORT),
    "--set",
    "block_global=false",
    "--set",
    "web_password=stjames2026",  # Set your fixed password here
]
  
  print("=" * 64)
  print(f"[+] Runner:         {' '.join(runner[:2])}")
  if "Tailscale" in ip_map:
    print(f"[+] Tailscale IP:   {ip_map['Tailscale']}")
  if "Default Route" in ip_map and ip_map["Default Route"] != ip_map.get(
      "Tailscale"
  ):
    print(f"[+] LAN / Local IP: {ip_map['Default Route']}")
  print(f"[+] Web Dashboard:  http://{best_ip}:{WEB_PORT}")
  print(f"[+] Proxy Target:   {best_ip}:{PROXY_PORT}")
  print("=" * 64)
  print("\nConfiguration for iOS client:")
  print(f"  • Proxy Server : {best_ip}")
  print(f"  • Proxy Port   : {PROXY_PORT}")
  print("  • CA Install   : Open Safari -> http://mitm.it\n")

  # 5. Process Lifecycle Execution
  proc = None

  def handle_exit(signum, frame):
    if proc and proc.poll() is None:
      print("\n[+] Terminating mitmweb cleanly...")
      proc.terminate()
      try:
        proc.wait(timeout=5)
      except subprocess.TimeoutExpired:
        proc.kill()
    sys.exit(0)

  signal.signal(signal.SIGINT, handle_exit)
  signal.signal(signal.SIGTERM, handle_exit)

  try:
    proc = subprocess.Popen(cmd)
    proc.wait()
  except Exception as e:
    print(f"[-] Execution error: {e}", file=sys.stderr)
    if proc:
      proc.kill()
    sys.exit(1)


if __name__ == "__main__":
  main()