Files
navi/internal/sysproxy/darwin.go
T
M4andCursor d1570b23d3 Release 3.8.2.3: cheaper poll I/O, batched macOS sysproxy, no Connect Clone.
Trust corebin cache without Stat; batch networksetup; ActiveProxyURI instead of Config.Clone.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 03:02:19 +03:00

153 lines
3.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build darwin
package sysproxy
import (
"bufio"
"bytes"
"fmt"
"net"
"os/exec"
"strings"
"sync"
)
type darwinController struct {
mu sync.Mutex
enabled bool
host string
httpPort string
socksPort string
services []string
}
func newPlatform() Controller { return &darwinController{} }
func (c *darwinController) Enable(httpHostPort string) error {
c.mu.Lock()
defer c.mu.Unlock()
host, port, err := net.SplitHostPort(httpHostPort)
if err != nil {
return fmt.Errorf("sysproxy: bad address %q: %w", httpHostPort, err)
}
if host == "" || host == "0.0.0.0" {
host = "127.0.0.1"
}
socks := "1080"
if port == "1081" {
socks = "1080"
}
services, err := listNetworkServices()
if err != nil {
return err
}
if len(services) == 0 {
return fmt.Errorf("sysproxy: no network services")
}
// One bash process for all networksetup calls (was 6 execs × N services).
// setwebproxy / setsecurewebproxy / setsocksfirewallproxy also turn the proxy on.
var b strings.Builder
b.WriteString("set -e\n")
for _, svc := range services {
q := shellQuote(svc)
fmt.Fprintf(&b, "networksetup -setwebproxy %s %s %s\n", q, shellQuote(host), shellQuote(port))
fmt.Fprintf(&b, "networksetup -setsecurewebproxy %s %s %s\n", q, shellQuote(host), shellQuote(port))
fmt.Fprintf(&b, "networksetup -setsocksfirewallproxy %s %s %s\n", q, shellQuote(host), shellQuote(socks))
}
if err := runBash(b.String()); err != nil {
return err
}
c.enabled = true
c.host = host
c.httpPort = port
c.socksPort = socks
c.services = append([]string(nil), services...)
return nil
}
func (c *darwinController) Disable() error {
c.mu.Lock()
defer c.mu.Unlock()
if !c.enabled {
return nil
}
return c.disableServices(c.services)
}
func (c *darwinController) ForceDisable() error {
c.mu.Lock()
defer c.mu.Unlock()
svcs := c.services
return c.disableServices(svcs)
}
func (c *darwinController) disableServices(services []string) error {
if len(services) == 0 {
var err error
services, err = listNetworkServices()
if err != nil {
return err
}
}
if len(services) == 0 {
c.enabled = false
c.services = nil
return nil
}
var b strings.Builder
b.WriteString("set +e\n") // best-effort: turn off what we can
for _, svc := range services {
q := shellQuote(svc)
fmt.Fprintf(&b, "networksetup -setwebproxystate %s off\n", q)
fmt.Fprintf(&b, "networksetup -setsecurewebproxystate %s off\n", q)
fmt.Fprintf(&b, "networksetup -setsocksfirewallproxystate %s off\n", q)
}
err := runBash(b.String())
c.enabled = false
c.services = nil
return err
}
func (c *darwinController) Enabled() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.enabled
}
func listNetworkServices() ([]string, error) {
out, err := exec.Command("networksetup", "-listallnetworkservices").Output()
if err != nil {
return nil, fmt.Errorf("networksetup: %w", err)
}
var services []string
sc := bufio.NewScanner(bytes.NewReader(out))
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" || strings.HasPrefix(line, "An asterisk") {
continue
}
if strings.HasPrefix(line, "*") {
continue // disabled service
}
services = append(services, line)
}
return services, sc.Err()
}
func runBash(script string) error {
cmd := exec.Command("/bin/bash", "-c", script)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("networksetup batch: %w (%s)", err, strings.TrimSpace(string(out)))
}
return nil
}
func shellQuote(s string) string {
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
}