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>
This commit is contained in:
@@ -248,7 +248,8 @@ https://evilfox.win/
|
|||||||
- логи ядра и проверка туннеля (Probe) в UI; connect-on-launch и автопереподключение;
|
- логи ядра и проверка туннеля (Probe) в UI; connect-on-launch и автопереподключение;
|
||||||
- подписка удаляет устаревшие ноды прошлой синхронизации; «Лучший» без soft-ok UDP;
|
- подписка удаляет устаревшие ноды прошлой синхронизации; «Лучший» без soft-ok UDP;
|
||||||
- трей (Windows), CI workflows, REQUIRE_CODESIGN для релиза; Android gated Naive/AWG + sync версии;
|
- трей (Windows), CI workflows, REQUIRE_CODESIGN для релиза; Android gated Naive/AWG + sync версии;
|
||||||
- 3.8.2+2: иконка в macOS Dock — буква N цвета морской волны при подключении, исходная при отключении.
|
- 3.8.2+2: иконка в macOS Dock — буква N цвета морской волны при подключении, исходная при отключении;
|
||||||
|
- 3.8.2+3: poll без os.Stat ядер + кэш host; Darwin sysproxy batch; Connect без Config.Clone.
|
||||||
|
|
||||||
В 3.8.1:
|
В 3.8.1:
|
||||||
- меньше нагрузка в простое: лёгкий опрос интерфейса без полного клонирования конфига, кэш путей к ядрам, лог с лимитом размера;
|
- меньше нагрузка в простое: лёгкий опрос интерфейса без полного клонирования конфига, кэш путей к ядрам, лог с лимитом размера;
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ android {
|
|||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
// versionCode = major*1_000_000 + minor*10_000 + patch*100 + build
|
// versionCode = major*1_000_000 + minor*10_000 + patch*100 + build
|
||||||
versionCode = 3080202
|
versionCode = 3080203
|
||||||
versionName = "3.8.2+2"
|
versionName = "3.8.2+3"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
|
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
|
||||||
|
|||||||
+3
-3
@@ -34,11 +34,11 @@ if errorlevel 1 exit /b 1
|
|||||||
go build -o "tools\packmac\packmac.exe" .\tools\packmac
|
go build -o "tools\packmac\packmac.exe" .\tools\packmac
|
||||||
if errorlevel 1 exit /b 1
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-arm64\Navis" -out "dist\navis-release\darwin-arm64" -version 3.8.2 -build 3.8.2.2 -arch arm64
|
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-arm64\Navis" -out "dist\navis-release\darwin-arm64" -version 3.8.2 -build 3.8.2.3 -arch arm64
|
||||||
if errorlevel 1 exit /b 1
|
if errorlevel 1 exit /b 1
|
||||||
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-amd64\Navis" -out "dist\navis-release\darwin-amd64" -version 3.8.2 -build 3.8.2.2 -arch amd64
|
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-amd64\Navis" -out "dist\navis-release\darwin-amd64" -version 3.8.2 -build 3.8.2.3 -arch amd64
|
||||||
if errorlevel 1 exit /b 1
|
if errorlevel 1 exit /b 1
|
||||||
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-universal\Navis" -out "dist\navis-release\darwin-universal" -version 3.8.2 -build 3.8.2.2 -arch universal
|
tools\packmac\packmac.exe -bin "dist\navis-release\darwin-universal\Navis" -out "dist\navis-release\darwin-universal" -version 3.8.2 -build 3.8.2.3 -arch universal
|
||||||
if errorlevel 1 exit /b 1
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
echo Built Mac GUI + CLI:
|
echo Built Mac GUI + CLI:
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
|||||||
3.8.2+2
|
3.8.2+3
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
3.8.2.2
|
3.8.2.3
|
||||||
|
|||||||
Vendored
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "3.8.2",
|
"version": "3.8.2",
|
||||||
"notes": "Navis 3.8.2+2: иконка Dock — бирюзовая N при подключении; при падении ядра снимается системный прокси; логи и проверка туннеля; автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP.",
|
"notes": "Navis 3.8.2+3: меньше I/O на poll (без Stat ядер / кэш host); macOS sysproxy одним bash-batch; Connect без JSON Clone; Dock-иконка при подключении.",
|
||||||
"platform": "windows-amd64",
|
"platform": "windows-amd64",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
},
|
},
|
||||||
"darwin-arm64": {
|
"darwin-arm64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
||||||
"sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335",
|
"sha256": "b59c940dc946d18e6c57d9bcb3d9e36d489d894e9f889b08d476add3207babbc",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "arm64",
|
"arch": "arm64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
||||||
"zip_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909",
|
"zip_sha256": "7d695a76a111d0ea3ff13955d49db2d344972e8e0b3fdb3562060aa80b06ea67",
|
||||||
"dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe"
|
"dmg_sha256": "96927b0006934a9bc042130351cefa57096badb6771c50a31c0731ddade9cb0d"
|
||||||
},
|
},
|
||||||
"darwin-amd64": {
|
"darwin-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
||||||
|
|||||||
Vendored
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "3.8.2",
|
"version": "3.8.2",
|
||||||
"notes": "Navis 3.8.2+2: иконка Dock — бирюзовая N при подключении; при падении ядра снимается системный прокси; логи и проверка туннеля; автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP.",
|
"notes": "Navis 3.8.2+3: меньше I/O на poll (без Stat ядер / кэш host); macOS sysproxy одним bash-batch; Connect без JSON Clone; Dock-иконка при подключении.",
|
||||||
"platform": "windows-amd64",
|
"platform": "windows-amd64",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
},
|
},
|
||||||
"darwin-arm64": {
|
"darwin-arm64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
||||||
"sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335",
|
"sha256": "b59c940dc946d18e6c57d9bcb3d9e36d489d894e9f889b08d476add3207babbc",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "arm64",
|
"arch": "arm64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
||||||
"zip_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909",
|
"zip_sha256": "7d695a76a111d0ea3ff13955d49db2d344972e8e0b3fdb3562060aa80b06ea67",
|
||||||
"dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe"
|
"dmg_sha256": "96927b0006934a9bc042130351cefa57096badb6771c50a31c0731ddade9cb0d"
|
||||||
},
|
},
|
||||||
"darwin-amd64": {
|
"darwin-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
||||||
|
|||||||
@@ -238,10 +238,12 @@ func (a *App) ConnectProfile(name string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p, err := a.Mgr.Config().ActiveProfile(); err != nil {
|
proxy, err := a.Mgr.ActiveProxyURI()
|
||||||
|
if err != nil {
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
return err
|
return err
|
||||||
} else if strings.TrimSpace(p.Proxy) == "" {
|
}
|
||||||
|
if strings.TrimSpace(proxy) == "" {
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
return fmt.Errorf("сначала вставьте ссылку сервера")
|
return fmt.Errorf("сначала вставьте ссылку сервера")
|
||||||
}
|
}
|
||||||
@@ -420,10 +422,12 @@ func (a *App) PingBest(autoConnect bool) (PingBestResult, error) {
|
|||||||
out.Connected = true
|
out.Connected = true
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
if p, err := a.Mgr.Config().ActiveProfile(); err != nil {
|
proxy, err := a.Mgr.ActiveProxyURI()
|
||||||
|
if err != nil {
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
return out, err
|
return out, err
|
||||||
} else if strings.TrimSpace(p.Proxy) == "" {
|
}
|
||||||
|
if strings.TrimSpace(proxy) == "" {
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
return out, fmt.Errorf("пустая ссылка у лучшего сервера")
|
return out, fmt.Errorf("пустая ссылка у лучшего сервера")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ func WriteExample(path string) error {
|
|||||||
return Save(path, Example())
|
return Save(path, Example())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save writes config as indented JSON.
|
// Save writes config as indented JSON (atomic replace).
|
||||||
func Save(path string, cfg Config) error {
|
func Save(path string, cfg Config) error {
|
||||||
if err := cfg.Validate(); err != nil {
|
if err := cfg.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -341,7 +341,15 @@ func Save(path string, cfg Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return os.WriteFile(path, data, 0o600)
|
tmp := path + ".tmp"
|
||||||
|
if err := os.WriteFile(tmp, data, 0o600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Rename(tmp, path); err != nil {
|
||||||
|
_ = os.Remove(tmp)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetActiveProxy updates the active profile proxy URI.
|
// SetActiveProxy updates the active profile proxy URI.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProfileInfo is a safe summary for UI lists (no secrets beyond proxy URI the user already owns).
|
// ProfileInfo is a safe summary for UI lists (no secrets beyond proxy URI the user already owns).
|
||||||
@@ -14,6 +15,22 @@ type ProfileInfo struct {
|
|||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hostCache avoids re-parsing large AWG conf bodies on every UI poll.
|
||||||
|
var hostCache sync.Map // proxy URI/conf → host string
|
||||||
|
|
||||||
|
func cachedProxyHost(proxy string) string {
|
||||||
|
proxy = strings.TrimSpace(proxy)
|
||||||
|
if proxy == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if v, ok := hostCache.Load(proxy); ok {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
h := proxyHost(proxy)
|
||||||
|
hostCache.Store(proxy, h)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
// ListProfiles returns UI-friendly profile summaries.
|
// ListProfiles returns UI-friendly profile summaries.
|
||||||
func (c *Config) ListProfiles() []ProfileInfo {
|
func (c *Config) ListProfiles() []ProfileInfo {
|
||||||
out := make([]ProfileInfo, 0, len(c.Profiles))
|
out := make([]ProfileInfo, 0, len(c.Profiles))
|
||||||
@@ -22,7 +39,7 @@ func (c *Config) ListProfiles() []ProfileInfo {
|
|||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Protocol: string(p.Protocol),
|
Protocol: string(p.Protocol),
|
||||||
Proxy: p.Proxy,
|
Proxy: p.Proxy,
|
||||||
Host: proxyHost(p.Proxy),
|
Host: cachedProxyHost(p.Proxy),
|
||||||
Active: p.Name == c.Active,
|
Active: p.Name == c.Active,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -36,7 +53,7 @@ func (c *Config) ListProfilesForPoll() []ProfileInfo {
|
|||||||
out = append(out, ProfileInfo{
|
out = append(out, ProfileInfo{
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Protocol: string(p.Protocol),
|
Protocol: string(p.Protocol),
|
||||||
Host: proxyHost(p.Proxy),
|
Host: cachedProxyHost(p.Proxy),
|
||||||
Active: p.Name == c.Active,
|
Active: p.Name == c.Active,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,6 +350,20 @@ func (m *Manager) Config() *config.Config {
|
|||||||
return m.cfg.Clone()
|
return m.cfg.Clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActiveProxyURI returns the active profile proxy without cloning the whole config.
|
||||||
|
func (m *Manager) ActiveProxyURI() (string, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if m.cfg == nil {
|
||||||
|
return "", fmt.Errorf("no config")
|
||||||
|
}
|
||||||
|
p, err := m.cfg.ActiveProfile()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return p.Proxy, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UIPoll is a cheap manager snapshot for GUI polling (no JSON deep-clone of all proxies).
|
// UIPoll is a cheap manager snapshot for GUI polling (no JSON deep-clone of all proxies).
|
||||||
type UIPoll struct {
|
type UIPoll struct {
|
||||||
Status Status
|
Status Status
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package corebin
|
package corebin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,6 +15,11 @@ func CacheKey(binDir, proto string) string {
|
|||||||
return binDir + "\x00" + proto
|
return binDir + "\x00" + proto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VirtualPath reports paths that are not on-disk binaries (e.g. embedded AWG).
|
||||||
|
func VirtualPath(path string) bool {
|
||||||
|
return strings.HasPrefix(path, "embedded-")
|
||||||
|
}
|
||||||
|
|
||||||
// Get returns a cached absolute path if present.
|
// Get returns a cached absolute path if present.
|
||||||
func Get(key string) (string, bool) {
|
func Get(key string) (string, bool) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@@ -33,23 +38,20 @@ func Set(key, path string) {
|
|||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate clears all cached paths (call after EnsureCore / binDir change).
|
// Invalidate clears all cached paths (call after EnsureCore / binDir change / failed Start).
|
||||||
func Invalidate() {
|
func Invalidate() {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
cache = map[string]string{}
|
cache = map[string]string{}
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve caches the result of fn under key. Stale paths (missing on disk) are refreshed.
|
// Resolve caches the result of fn under key.
|
||||||
|
// Hot-path polls trust the cache until Invalidate (no per-poll os.Stat).
|
||||||
|
// Embedded/virtual paths never touch the filesystem.
|
||||||
func Resolve(key string, fn func() (string, error)) (string, error) {
|
func Resolve(key string, fn func() (string, error)) (string, error) {
|
||||||
if path, ok := Get(key); ok {
|
if path, ok := Get(key); ok {
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
mu.Lock()
|
|
||||||
delete(cache, key)
|
|
||||||
mu.Unlock()
|
|
||||||
}
|
|
||||||
path, err := fn()
|
path, err := fn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolveCacheAndStale(t *testing.T) {
|
func TestResolveTrustsCacheWithoutStat(t *testing.T) {
|
||||||
Invalidate()
|
Invalidate()
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
path := filepath.Join(dir, "core")
|
path := filepath.Join(dir, "core")
|
||||||
@@ -20,26 +20,40 @@ func TestResolveCacheAndStale(t *testing.T) {
|
|||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
if p, err := Resolve(key, fn); err != nil || p != path || calls != 1 {
|
if p, err := Resolve(key, fn); err != nil || p != path || calls != 1 {
|
||||||
t.Fatalf("first: path=%q calls=%d err=%v", p, calls, err)
|
t.Fatalf("first: %q %v calls=%d", p, err, calls)
|
||||||
}
|
}
|
||||||
|
_ = os.Remove(path) // missing on disk — cache still trusted until Invalidate
|
||||||
if p, err := Resolve(key, fn); err != nil || p != path || calls != 1 {
|
if p, err := Resolve(key, fn); err != nil || p != path || calls != 1 {
|
||||||
t.Fatalf("cached: path=%q calls=%d err=%v", p, calls, err)
|
t.Fatalf("cached without Stat: %q %v calls=%d", p, err, calls)
|
||||||
}
|
}
|
||||||
_ = os.Remove(path)
|
Invalidate()
|
||||||
if _, err := Resolve(key, fn); err != nil {
|
if _, err := Resolve(key, fn); err == nil && calls != 2 {
|
||||||
// fn returns removed path; Resolve still succeeds but next Stat misses
|
// fn returns deleted path; Resolve still succeeds
|
||||||
t.Fatalf("unexpected err after delete before recreate: %v", err)
|
|
||||||
}
|
}
|
||||||
if calls != 2 {
|
if calls != 2 {
|
||||||
t.Fatalf("expected refresh call, got %d", calls)
|
t.Fatalf("after Invalidate expected refresh, calls=%d", calls)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(path, []byte("y"), 0o755); err != nil {
|
}
|
||||||
t.Fatal(err)
|
|
||||||
|
func TestVirtualEmbeddedAWG(t *testing.T) {
|
||||||
|
Invalidate()
|
||||||
|
key := CacheKey("/bin", "awg")
|
||||||
|
calls := 0
|
||||||
|
p, err := Resolve(key, func() (string, error) {
|
||||||
|
calls++
|
||||||
|
return "embedded-amneziawg-go", nil
|
||||||
|
})
|
||||||
|
if err != nil || p != "embedded-amneziawg-go" || calls != 1 {
|
||||||
|
t.Fatalf("%q %v calls=%d", p, err, calls)
|
||||||
}
|
}
|
||||||
if p, err := Resolve(key, fn); err != nil || p != path {
|
if !VirtualPath(p) {
|
||||||
t.Fatalf("after recreate: %q %v", p, err)
|
t.Fatal("expected virtual")
|
||||||
}
|
}
|
||||||
if ProtoKey("vless") != "xray" || ProtoKey("naive") != "naive" {
|
p2, err := Resolve(key, func() (string, error) {
|
||||||
t.Fatal("ProtoKey")
|
calls++
|
||||||
|
return "embedded-amneziawg-go", nil
|
||||||
|
})
|
||||||
|
if err != nil || p2 != p || calls != 1 {
|
||||||
|
t.Fatalf("second: %q calls=%d", p2, calls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+34
-34
@@ -47,32 +47,25 @@ func (c *darwinController) Enable(httpHostPort string) error {
|
|||||||
return fmt.Errorf("sysproxy: no network services")
|
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 {
|
for _, svc := range services {
|
||||||
if err := runNetworkSetup("-setwebproxy", svc, host, port); err != nil {
|
q := shellQuote(svc)
|
||||||
return err
|
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 := runNetworkSetup("-setsecurewebproxy", svc, host, port); err != nil {
|
if err := runBash(b.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := runNetworkSetup("-setsocksfirewallproxy", svc, host, socks); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := runNetworkSetup("-setwebproxystate", svc, "on"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := runNetworkSetup("-setsecurewebproxystate", svc, "on"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := runNetworkSetup("-setsocksfirewallproxystate", svc, "on"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.enabled = true
|
c.enabled = true
|
||||||
c.host = host
|
c.host = host
|
||||||
c.httpPort = port
|
c.httpPort = port
|
||||||
c.socksPort = socks
|
c.socksPort = socks
|
||||||
c.services = services
|
c.services = append([]string(nil), services...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +81,8 @@ func (c *darwinController) Disable() error {
|
|||||||
func (c *darwinController) ForceDisable() error {
|
func (c *darwinController) ForceDisable() error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
return c.disableServices(nil)
|
svcs := c.services
|
||||||
|
return c.disableServices(svcs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *darwinController) disableServices(services []string) error {
|
func (c *darwinController) disableServices(services []string) error {
|
||||||
@@ -99,21 +93,23 @@ func (c *darwinController) disableServices(services []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var first error
|
if len(services) == 0 {
|
||||||
for _, svc := range services {
|
|
||||||
for _, args := range [][]string{
|
|
||||||
{"-setwebproxystate", svc, "off"},
|
|
||||||
{"-setsecurewebproxystate", svc, "off"},
|
|
||||||
{"-setsocksfirewallproxystate", svc, "off"},
|
|
||||||
} {
|
|
||||||
if err := runNetworkSetup(args...); err != nil && first == nil {
|
|
||||||
first = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.enabled = false
|
c.enabled = false
|
||||||
c.services = nil
|
c.services = nil
|
||||||
return first
|
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 {
|
func (c *darwinController) Enabled() bool {
|
||||||
@@ -142,11 +138,15 @@ func listNetworkServices() ([]string, error) {
|
|||||||
return services, sc.Err()
|
return services, sc.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func runNetworkSetup(args ...string) error {
|
func runBash(script string) error {
|
||||||
cmd := exec.Command("networksetup", args...)
|
cmd := exec.Command("/bin/bash", "-c", script)
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("networksetup %v: %w (%s)", args, err, strings.TrimSpace(string(out)))
|
return fmt.Errorf("networksetup batch: %w (%s)", err, strings.TrimSpace(string(out)))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shellQuote(s string) string {
|
||||||
|
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const CurrentVersion = "3.8.2"
|
|||||||
|
|
||||||
// BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part,
|
// BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part,
|
||||||
// macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build.
|
// macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build.
|
||||||
const BuildNumber = 2
|
const BuildNumber = 3
|
||||||
|
|
||||||
// DefaultManifestURL is the update feed (hosted in the project git repo).
|
// DefaultManifestURL is the update feed (hosted in the project git repo).
|
||||||
const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json"
|
const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json"
|
||||||
|
|||||||
@@ -77,9 +77,8 @@ h = hashlib.sha256(binp.read_bytes()).hexdigest()
|
|||||||
zh = hashlib.sha256(zipp.read_bytes()).hexdigest() if zipp.exists() else ""
|
zh = hashlib.sha256(zipp.read_bytes()).hexdigest() if zipp.exists() else ""
|
||||||
dh = hashlib.sha256(dmgp.read_bytes()).hexdigest() if dmgp.exists() else ""
|
dh = hashlib.sha256(dmgp.read_bytes()).hexdigest() if dmgp.exists() else ""
|
||||||
notes = (
|
notes = (
|
||||||
f"Navis {ver}+{build}: иконка Dock — бирюзовая N при подключении; "
|
f"Navis {ver}+{build}: меньше I/O на poll (без Stat ядер / кэш host); "
|
||||||
f"при падении ядра снимается системный прокси; логи и проверка туннеля; "
|
f"macOS sysproxy одним bash-batch; Connect без JSON Clone; Dock-иконка при подключении."
|
||||||
f"автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP."
|
|
||||||
)
|
)
|
||||||
paths = [
|
paths = [
|
||||||
Path('dist/update.json'),
|
Path('dist/update.json'),
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "3.8.2",
|
"version": "3.8.2",
|
||||||
"notes": "Navis 3.8.2+2: иконка Dock — бирюзовая N при подключении; при падении ядра снимается системный прокси; логи и проверка туннеля; автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP.",
|
"notes": "Navis 3.8.2+3: меньше I/O на poll (без Stat ядер / кэш host); macOS sysproxy одним bash-batch; Connect без JSON Clone; Dock-иконка при подключении.",
|
||||||
"platform": "windows-amd64",
|
"platform": "windows-amd64",
|
||||||
"os": "windows",
|
"os": "windows",
|
||||||
"arch": "amd64",
|
"arch": "amd64",
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
},
|
},
|
||||||
"darwin-arm64": {
|
"darwin-arm64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
|
||||||
"sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335",
|
"sha256": "b59c940dc946d18e6c57d9bcb3d9e36d489d894e9f889b08d476add3207babbc",
|
||||||
"os": "darwin",
|
"os": "darwin",
|
||||||
"arch": "arm64",
|
"arch": "arm64",
|
||||||
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
|
||||||
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip",
|
||||||
"zip_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909",
|
"zip_sha256": "7d695a76a111d0ea3ff13955d49db2d344972e8e0b3fdb3562060aa80b06ea67",
|
||||||
"dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe"
|
"dmg_sha256": "96927b0006934a9bc042130351cefa57096badb6771c50a31c0731ddade9cb0d"
|
||||||
},
|
},
|
||||||
"darwin-amd64": {
|
"darwin-amd64": {
|
||||||
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis",
|
||||||
|
|||||||
+4
-4
@@ -4,13 +4,13 @@
|
|||||||
"Major": 3,
|
"Major": 3,
|
||||||
"Minor": 8,
|
"Minor": 8,
|
||||||
"Patch": 2,
|
"Patch": 2,
|
||||||
"Build": 2
|
"Build": 3
|
||||||
},
|
},
|
||||||
"ProductVersion": {
|
"ProductVersion": {
|
||||||
"Major": 3,
|
"Major": 3,
|
||||||
"Minor": 8,
|
"Minor": 8,
|
||||||
"Patch": 2,
|
"Patch": 2,
|
||||||
"Build": 2
|
"Build": 3
|
||||||
},
|
},
|
||||||
"FileFlagsMask": "3f",
|
"FileFlagsMask": "3f",
|
||||||
"FileFlags": "00",
|
"FileFlags": "00",
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
"StringFileInfo": {
|
"StringFileInfo": {
|
||||||
"CompanyName": "EvilFox",
|
"CompanyName": "EvilFox",
|
||||||
"FileDescription": "Navis — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)",
|
"FileDescription": "Navis — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)",
|
||||||
"FileVersion": "3.8.2.2",
|
"FileVersion": "3.8.2.3",
|
||||||
"InternalName": "Navis",
|
"InternalName": "Navis",
|
||||||
"LegalCopyright": "Copyright (c) EvilFox",
|
"LegalCopyright": "Copyright (c) EvilFox",
|
||||||
"OriginalFilename": "Navis.exe",
|
"OriginalFilename": "Navis.exe",
|
||||||
"ProductName": "Navis",
|
"ProductName": "Navis",
|
||||||
"ProductVersion": "3.8.2.2",
|
"ProductVersion": "3.8.2.3",
|
||||||
"Comments": "Open-source VPN/proxy client. https://evilfox.win/"
|
"Comments": "Open-source VPN/proxy client. https://evilfox.win/"
|
||||||
},
|
},
|
||||||
"VarFileInfo": {
|
"VarFileInfo": {
|
||||||
|
|||||||
Reference in New Issue
Block a user