package main import ( "encoding/json" "fmt" "net" "os" "os/exec" "time" ) // Client communicates with the vmux daemon over a Unix socket. type Client struct { sockPath string } // NewClient creates a client targeting the given socket path. func NewClient(sockPath string) *Client { return &Client{sockPath: sockPath} } // Send transmits a Request to the daemon and returns the Response. func (c *Client) Send(req Request) (*Response, error) { conn, err := net.DialTimeout("unix", c.sockPath, 2*time.Second) if err != nil { return nil, fmt.Errorf("dial daemon: %w", err) } defer conn.Close() conn.SetDeadline(time.Now().Add(5 * time.Second)) if err := json.NewEncoder(conn).Encode(req); err != nil { return nil, fmt.Errorf("send request: %w", err) } var resp Response if err := json.NewDecoder(conn).Decode(&resp); err != nil { return nil, fmt.Errorf("read response: %w", err) } return &resp, nil } // EnsureDaemon checks if a daemon is already running on sockPath. // If not, it spawns one in the background and waits until it's reachable. func EnsureDaemon(sockPath string) error { // Already running? conn, err := net.DialTimeout("unix", sockPath, 200*time.Millisecond) if err == nil { conn.Close() return nil } // Spawn daemon in background exe, err := os.Executable() if err != nil { return fmt.Errorf("resolve executable: %w", err) } cmd := exec.Command(exe, "daemon") cmd.SysProcAttr = newSysProcAttr() cmd.Stdout = nil cmd.Stderr = nil cmd.Stdin = nil if err := cmd.Start(); err != nil { return fmt.Errorf("start daemon: %w", err) } // Detach: don't wait for the child go cmd.Wait() // Retry until daemon is reachable (50ms x 20 = 1s max) for i := 0; i < 20; i++ { time.Sleep(50 * time.Millisecond) conn, err := net.DialTimeout("unix", sockPath, 200*time.Millisecond) if err == nil { conn.Close() return nil } } return fmt.Errorf("daemon did not start within 1s") }