Release 3.8.0.2: app.zip updates, lock hygiene, secret-safe getState.

This commit is contained in:
M4
2026-07-29 22:00:39 +03:00
parent 54b5b87990
commit 6a7480dceb
31 changed files with 2058 additions and 1537 deletions
+39 -20
View File
@@ -79,54 +79,65 @@ func (m *Manager) Connect(ctx context.Context, profileName string) error {
m.waitEngineStopped()
m.mu.Lock()
defer m.mu.Unlock()
if m.engine != nil && m.engine.Running() {
m.mu.Unlock()
return fmt.Errorf("already connected; disconnect first")
}
if m.engine != nil {
// Defunct reference (stop finished or engine died) — drop before start.
m.engine = nil
m.profile = nil
}
if profileName != "" {
m.cfg.Active = profileName
}
profile, err := m.cfg.ActiveProfile()
if err != nil {
m.mu.Unlock()
return err
}
profileCopy := *profile
binDir := m.binDir
wantSysProxy := m.cfg.SystemProxy
cfgPath := m.cfgPath
sys := m.sys
stderr := m.stderr
m.mu.Unlock()
engine, err := m.newEngine(profile.Protocol)
engine, err := m.newEngine(profileCopy.Protocol)
if err != nil {
return err
}
if err := engine.Start(ctx, *profile, m.binDir); err != nil {
if err := engine.Start(ctx, profileCopy, binDir); err != nil {
return err
}
if m.cfg.SystemProxy {
m.mu.Lock()
defer m.mu.Unlock()
if m.engine != nil && m.engine.Running() {
_ = engine.Stop()
return fmt.Errorf("already connected; disconnect first")
}
if wantSysProxy {
httpProxy, ok := engine.LocalHTTPProxy()
if !ok {
_ = engine.Stop()
return fmt.Errorf("system_proxy requires an http:// listen address in the profile")
}
if err := m.sys.Enable(httpProxy); err != nil {
if err := sys.Enable(httpProxy); err != nil {
if errors.Is(err, sysproxy.ErrUnsupported) {
fmt.Fprintf(m.stderr, "system proxy unsupported on this OS; local SOCKS/HTTP still up\n")
fmt.Fprintf(stderr, "system proxy unsupported on this OS; local SOCKS/HTTP still up\n")
} else {
_ = engine.Stop()
return fmt.Errorf("enable system proxy: %w", err)
}
} else {
sysproxy.WriteSentinel(m.cfgPath, httpProxy)
sysproxy.WriteSentinel(cfgPath, httpProxy)
}
}
m.engine = engine
m.profile = profile
m.profile = &profileCopy
return nil
}
@@ -552,25 +563,33 @@ func (m *Manager) newEngine(p config.Protocol) (Engine, error) {
// EnsureCore downloads the binary required by the active (or given) protocol.
func (m *Manager) EnsureCore(proto config.Protocol) (string, error) {
m.mu.Lock()
binDir := m.binDir
if proto == "" {
if p, err := m.cfg.ActiveProfile(); err == nil {
proto = p.Protocol
}
}
m.mu.Unlock()
switch proto {
case config.ProtocolHysteria2:
return hysteria2.EnsureBinary(m.binDir)
return hysteria2.EnsureBinary(binDir)
case config.ProtocolAWG:
return awg.EnsureBinary(m.binDir)
return awg.EnsureBinary(binDir)
case config.ProtocolVLESS, config.ProtocolVMess, config.ProtocolTrojan:
return xray.EnsureBinary(m.binDir)
return xray.EnsureBinary(binDir)
default:
return naive.EnsureBinary(m.binDir)
return naive.EnsureBinary(binDir)
}
}
// EnsureAllCores installs naive + hysteria2 + xray cores (AWG is embedded).
func (m *Manager) EnsureAllCores() (map[string]string, error) {
m.mu.Lock()
binDir := m.binDir
m.mu.Unlock()
type item struct {
key string
path string
@@ -580,10 +599,10 @@ func (m *Manager) EnsureAllCores() (map[string]string, error) {
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) }},
{"naive", func() (string, error) { return naive.EnsureBinary(binDir) }},
{"hysteria2", func() (string, error) { return hysteria2.EnsureBinary(binDir) }},
{"awg", func() (string, error) { return awg.EnsureBinary(binDir) }},
{"xray", func() (string, error) { return xray.EnsureBinary(binDir) }},
}
ch := make(chan item, len(jobs))
for _, j := range jobs {