// Command mitmuxd is the mitmux headless proxy daemon. It owns the // listening socket and the history database; a TUI or other client // attaches separately, over a Unix control socket, without interrupting // capture. package main import ( "context" "flag" "log" "net" "os" "os/signal" "path/filepath" "syscall" "time" "mitmux/internal/ca" "mitmux/internal/ipc" "mitmux/internal/proxy" "mitmux/internal/store" ) func main() { listen := flag.String("listen", "127.0.0.1:8080", "proxy listen address") caDir := flag.String("ca-dir", "", "directory for CA cert/key and history db (default: XDG config dir)") dbPath := flag.String("db", "", "path to history database (default: /history.db)") socketPath := flag.String("socket", "", "control socket path (default: $XDG_RUNTIME_DIR/mitmux.sock, else /mitmux.sock)") flag.Parse() dir := *caDir if dir == "" { d, err := ca.Dir() if err != nil { log.Fatalf("resolve CA dir: %v", err) } dir = d } root, err := ca.EnsureCA(dir) if err != nil { log.Fatalf("load CA: %v", err) } log.Printf("CA ready: %s (install %s/ca.pem in your client's trust store to avoid TLS warnings)", root.Cert.Subject.CommonName, dir) dbFile := *dbPath if dbFile == "" { dbFile = filepath.Join(dir, "history.db") } db, err := store.Open(dbFile) if err != nil { log.Fatalf("open history db: %v", err) } defer db.Close() log.Printf("history db: %s", dbFile) sockFile := *socketPath if sockFile == "" { if rt := os.Getenv("XDG_RUNTIME_DIR"); rt != "" { sockFile = filepath.Join(rt, "mitmux.sock") } else { sockFile = filepath.Join(dir, "mitmux.sock") } } os.Remove(sockFile) // stale socket from an unclean shutdown sockLn, err := net.Listen("unix", sockFile) if err != nil { log.Fatalf("listen on control socket %s: %v", sockFile, err) } defer os.Remove(sockFile) log.Printf("control socket: %s", sockFile) hub := ipc.NewHub() ipcSrv := ipc.NewServer(db, hub) go func() { if err := ipcSrv.Serve(sockLn); err != nil { log.Printf("control socket: %v", err) } }() srv := proxy.New(*listen, root, db) srv.OnEntry = ipc.LogAndBroadcast(hub) errCh := make(chan error, 1) go func() { errCh <- srv.ListenAndServe() }() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) select { case err := <-errCh: if err != nil { log.Fatalf("proxy: %v", err) } case sig := <-sigCh: log.Printf("received %s, shutting down", sig) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatalf("shutdown: %v", err) } sockLn.Close() } }