Release 2.7.3.1: harden proxy recovery, updates and local API.

Restore orphaned system proxy after crash, require update SHA-256, add macOS /api auth token, fix UDP ping false positives, HTTPS-only subscriptions, and keep the UI responsive during connect.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-07-29 18:48:54 +03:00
co-authored by Cursor
parent dc700f2bac
commit 041cbb1250
32 changed files with 346 additions and 108 deletions
+67 -25
View File
@@ -43,13 +43,29 @@ func NewManager(cfgPath string, cfg *config.Config, stderr io.Writer) (*Manager,
if err != nil {
return nil, err
}
return &Manager{
m := &Manager{
cfgPath: cfgPath,
cfg: cfg,
sys: sysproxy.New(),
stderr: stderr,
binDir: binDir,
}, nil
}
m.RecoverSystemProxy()
return m, nil
}
// RecoverSystemProxy clears a system proxy left on after an unclean exit.
func (m *Manager) RecoverSystemProxy() {
if m == nil || m.sys == nil {
return
}
if !sysproxy.HasSentinel(m.cfgPath) {
return
}
if err := m.sys.ForceDisable(); err != nil {
fmt.Fprintf(m.stderr, "recover system proxy: %v\n", err)
}
sysproxy.ClearSentinel(m.cfgPath)
}
func (m *Manager) Connect(ctx context.Context, profileName string) error {
@@ -90,6 +106,8 @@ func (m *Manager) Connect(ctx context.Context, profileName string) error {
_ = engine.Stop()
return fmt.Errorf("enable system proxy: %w", err)
}
} else {
sysproxy.WriteSentinel(m.cfgPath, httpProxy)
}
}
@@ -107,10 +125,21 @@ func (m *Manager) Disconnect() error {
m.mu.Unlock()
var first error
if sys != nil && sys.Enabled() {
if err := sys.Disable(); err != nil && first == nil {
first = err
if sys != nil && (sys.Enabled() || sysproxy.HasSentinel(m.cfgPath)) {
var err error
if sys.Enabled() {
err = sys.Disable()
} else {
err = sys.ForceDisable()
}
if err != nil {
if err2 := sys.ForceDisable(); err2 != nil && first == nil {
first = err2
} else if first == nil {
first = err
}
}
sysproxy.ClearSentinel(m.cfgPath)
}
if engine != nil {
done := make(chan error, 1)
@@ -507,26 +536,39 @@ func (m *Manager) EnsureCore(proto config.Protocol) (string, error) {
// EnsureAllCores installs naive + hysteria2 + xray cores (AWG is embedded).
func (m *Manager) EnsureAllCores() (map[string]string, error) {
type item struct {
key string
path string
err error
}
jobs := []struct {
key string
fn func() (string, error)
}{
{"naive", func() (string, error) { return naive.EnsureBinary(m.binDir) }},
{"hysteria2", func() (string, error) { return hysteria2.EnsureBinary(m.binDir) }},
{"awg", func() (string, error) { return awg.EnsureBinary(m.binDir) }},
{"xray", func() (string, error) { return xray.EnsureBinary(m.binDir) }},
}
ch := make(chan item, len(jobs))
for _, j := range jobs {
j := j
go func() {
path, err := j.fn()
ch <- item{key: j.key, path: path, err: err}
}()
}
out := map[string]string{}
p1, err := naive.EnsureBinary(m.binDir)
if err != nil {
return out, fmt.Errorf("naive: %w", err)
var first error
for range jobs {
it := <-ch
if it.err != nil {
if first == nil {
first = fmt.Errorf("%s: %w", it.key, it.err)
}
continue
}
out[it.key] = it.path
}
out["naive"] = p1
p2, err := hysteria2.EnsureBinary(m.binDir)
if err != nil {
return out, fmt.Errorf("hysteria2: %w", err)
}
out["hysteria2"] = p2
p3, err := awg.EnsureBinary(m.binDir)
if err != nil {
return out, fmt.Errorf("awg: %w", err)
}
out["awg"] = p3
p4, err := xray.EnsureBinary(m.binDir)
if err != nil {
return out, fmt.Errorf("xray: %w", err)
}
out["xray"] = p4
return out, nil
return out, first
}