diff --git a/README.md b/README.md index f760fd2..5c2bd70 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,8 @@ https://evilfox.win/ - логи ядра и проверка туннеля (Probe) в UI; connect-on-launch и автопереподключение; - подписка удаляет устаревшие ноды прошлой синхронизации; «Лучший» без soft-ok UDP; - трей (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: - меньше нагрузка в простое: лёгкий опрос интерфейса без полного клонирования конфига, кэш путей к ядрам, лог с лимитом размера; diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index f0f167f..b279f98 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -13,8 +13,8 @@ android { minSdk = 26 targetSdk = 35 // versionCode = major*1_000_000 + minor*10_000 + patch*100 + build - versionCode = 3080202 - versionName = "3.8.2+2" + versionCode = 3080203 + versionName = "3.8.2+3" vectorDrawables.useSupportLibrary = true ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64") diff --git a/build-macos.bat b/build-macos.bat index b133e7e..e74bea6 100644 --- a/build-macos.bat +++ b/build-macos.bat @@ -34,11 +34,11 @@ if errorlevel 1 exit /b 1 go build -o "tools\packmac\packmac.exe" .\tools\packmac 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 -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 -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 echo Built Mac GUI + CLI: diff --git a/dist/navis-release/darwin-arm64/Navis b/dist/navis-release/darwin-arm64/Navis index 572fa1a..a964e67 100755 Binary files a/dist/navis-release/darwin-arm64/Navis and b/dist/navis-release/darwin-arm64/Navis differ diff --git a/dist/navis-release/darwin-arm64/Navis-cli b/dist/navis-release/darwin-arm64/Navis-cli index efe553e..4687b9c 100755 Binary files a/dist/navis-release/darwin-arm64/Navis-cli and b/dist/navis-release/darwin-arm64/Navis-cli differ diff --git a/dist/navis-release/darwin-arm64/Navis.app.zip b/dist/navis-release/darwin-arm64/Navis.app.zip index e078727..c8e5798 100644 Binary files a/dist/navis-release/darwin-arm64/Navis.app.zip and b/dist/navis-release/darwin-arm64/Navis.app.zip differ diff --git a/dist/navis-release/darwin-arm64/Navis.dmg b/dist/navis-release/darwin-arm64/Navis.dmg index 7413d21..d3e2b7d 100644 Binary files a/dist/navis-release/darwin-arm64/Navis.dmg and b/dist/navis-release/darwin-arm64/Navis.dmg differ diff --git a/dist/navis-release/darwin-arm64/Navis.version b/dist/navis-release/darwin-arm64/Navis.version index 50874a3..2a34ca5 100644 --- a/dist/navis-release/darwin-arm64/Navis.version +++ b/dist/navis-release/darwin-arm64/Navis.version @@ -1 +1 @@ -3.8.2+2 +3.8.2+3 diff --git a/dist/navis-release/darwin-arm64/VERSION b/dist/navis-release/darwin-arm64/VERSION index da7737b..117850d 100644 --- a/dist/navis-release/darwin-arm64/VERSION +++ b/dist/navis-release/darwin-arm64/VERSION @@ -1 +1 @@ -3.8.2.2 +3.8.2.3 diff --git a/dist/navis-release/update.json b/dist/navis-release/update.json index 97d2ac2..6d1efe5 100644 --- a/dist/navis-release/update.json +++ b/dist/navis-release/update.json @@ -1,6 +1,6 @@ { "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", "os": "windows", "arch": "amd64", @@ -16,13 +16,13 @@ }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335", + "sha256": "b59c940dc946d18e6c57d9bcb3d9e36d489d894e9f889b08d476add3207babbc", "os": "darwin", "arch": "arm64", "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_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909", - "dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe" + "zip_sha256": "7d695a76a111d0ea3ff13955d49db2d344972e8e0b3fdb3562060aa80b06ea67", + "dmg_sha256": "96927b0006934a9bc042130351cefa57096badb6771c50a31c0731ddade9cb0d" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", diff --git a/dist/update.json b/dist/update.json index 97d2ac2..6d1efe5 100644 --- a/dist/update.json +++ b/dist/update.json @@ -1,6 +1,6 @@ { "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", "os": "windows", "arch": "amd64", @@ -16,13 +16,13 @@ }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335", + "sha256": "b59c940dc946d18e6c57d9bcb3d9e36d489d894e9f889b08d476add3207babbc", "os": "darwin", "arch": "arm64", "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_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909", - "dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe" + "zip_sha256": "7d695a76a111d0ea3ff13955d49db2d344972e8e0b3fdb3562060aa80b06ea67", + "dmg_sha256": "96927b0006934a9bc042130351cefa57096badb6771c50a31c0731ddade9cb0d" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", diff --git a/internal/apphost/app.go b/internal/apphost/app.go index aa24926..03e5e29 100644 --- a/internal/apphost/app.go +++ b/internal/apphost/app.go @@ -238,10 +238,12 @@ func (a *App) ConnectProfile(name string) error { return err } } - if p, err := a.Mgr.Config().ActiveProfile(); err != nil { + proxy, err := a.Mgr.ActiveProxyURI() + if err != nil { a.mu.Unlock() return err - } else if strings.TrimSpace(p.Proxy) == "" { + } + if strings.TrimSpace(proxy) == "" { a.mu.Unlock() return fmt.Errorf("сначала вставьте ссылку сервера") } @@ -420,10 +422,12 @@ func (a *App) PingBest(autoConnect bool) (PingBestResult, error) { out.Connected = true return out, nil } - if p, err := a.Mgr.Config().ActiveProfile(); err != nil { + proxy, err := a.Mgr.ActiveProxyURI() + if err != nil { a.mu.Unlock() return out, err - } else if strings.TrimSpace(p.Proxy) == "" { + } + if strings.TrimSpace(proxy) == "" { a.mu.Unlock() return out, fmt.Errorf("пустая ссылка у лучшего сервера") } diff --git a/internal/config/config.go b/internal/config/config.go index 1d3e273..0e3d7f1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -325,7 +325,7 @@ func WriteExample(path string) error { 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 { if err := cfg.Validate(); err != nil { return err @@ -341,7 +341,15 @@ func Save(path string, cfg Config) error { 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. diff --git a/internal/config/profiles.go b/internal/config/profiles.go index 6b7afc3..c6ceb83 100644 --- a/internal/config/profiles.go +++ b/internal/config/profiles.go @@ -3,6 +3,7 @@ package config import ( "fmt" "strings" + "sync" ) // 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"` } +// 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. func (c *Config) ListProfiles() []ProfileInfo { out := make([]ProfileInfo, 0, len(c.Profiles)) @@ -22,7 +39,7 @@ func (c *Config) ListProfiles() []ProfileInfo { Name: p.Name, Protocol: string(p.Protocol), Proxy: p.Proxy, - Host: proxyHost(p.Proxy), + Host: cachedProxyHost(p.Proxy), Active: p.Name == c.Active, }) } @@ -36,7 +53,7 @@ func (c *Config) ListProfilesForPoll() []ProfileInfo { out = append(out, ProfileInfo{ Name: p.Name, Protocol: string(p.Protocol), - Host: proxyHost(p.Proxy), + Host: cachedProxyHost(p.Proxy), Active: p.Name == c.Active, }) } diff --git a/internal/core/manager.go b/internal/core/manager.go index 56bbb52..0517789 100644 --- a/internal/core/manager.go +++ b/internal/core/manager.go @@ -350,6 +350,20 @@ func (m *Manager) Config() *config.Config { 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). type UIPoll struct { Status Status diff --git a/internal/corebin/cache.go b/internal/corebin/cache.go index 7984f9e..0370721 100644 --- a/internal/corebin/cache.go +++ b/internal/corebin/cache.go @@ -1,7 +1,7 @@ package corebin import ( - "os" + "strings" "sync" ) @@ -15,6 +15,11 @@ func CacheKey(binDir, proto string) string { 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. func Get(key string) (string, bool) { mu.Lock() @@ -33,22 +38,19 @@ func Set(key, path string) { 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() { mu.Lock() cache = map[string]string{} 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) { if path, ok := Get(key); ok { - if _, err := os.Stat(path); err == nil { - return path, nil - } - mu.Lock() - delete(cache, key) - mu.Unlock() + return path, nil } path, err := fn() if err != nil { diff --git a/internal/corebin/cache_test.go b/internal/corebin/cache_test.go index abf1e34..349d858 100644 --- a/internal/corebin/cache_test.go +++ b/internal/corebin/cache_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -func TestResolveCacheAndStale(t *testing.T) { +func TestResolveTrustsCacheWithoutStat(t *testing.T) { Invalidate() dir := t.TempDir() path := filepath.Join(dir, "core") @@ -20,26 +20,40 @@ func TestResolveCacheAndStale(t *testing.T) { return path, nil } 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 { - 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) - if _, err := Resolve(key, fn); err != nil { - // fn returns removed path; Resolve still succeeds but next Stat misses - t.Fatalf("unexpected err after delete before recreate: %v", err) + Invalidate() + if _, err := Resolve(key, fn); err == nil && calls != 2 { + // fn returns deleted path; Resolve still succeeds } 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 { - t.Fatalf("after recreate: %q %v", p, err) + if !VirtualPath(p) { + t.Fatal("expected virtual") } - if ProtoKey("vless") != "xray" || ProtoKey("naive") != "naive" { - t.Fatal("ProtoKey") + p2, err := Resolve(key, func() (string, error) { + calls++ + return "embedded-amneziawg-go", nil + }) + if err != nil || p2 != p || calls != 1 { + t.Fatalf("second: %q calls=%d", p2, calls) } -} \ No newline at end of file +} diff --git a/internal/sysproxy/darwin.go b/internal/sysproxy/darwin.go index 0716d66..a02f885 100644 --- a/internal/sysproxy/darwin.go +++ b/internal/sysproxy/darwin.go @@ -13,12 +13,12 @@ import ( ) type darwinController struct { - mu sync.Mutex - enabled bool - host string - httpPort string + mu sync.Mutex + enabled bool + host string + httpPort string socksPort string - services []string + services []string } func newPlatform() Controller { return &darwinController{} } @@ -47,32 +47,25 @@ func (c *darwinController) Enable(httpHostPort string) error { 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 { - if err := runNetworkSetup("-setwebproxy", svc, host, port); err != nil { - return err - } - if err := runNetworkSetup("-setsecurewebproxy", svc, host, port); err != nil { - 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 - } + 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 = services + c.services = append([]string(nil), services...) return nil } @@ -88,7 +81,8 @@ func (c *darwinController) Disable() error { func (c *darwinController) ForceDisable() error { c.mu.Lock() defer c.mu.Unlock() - return c.disableServices(nil) + svcs := c.services + return c.disableServices(svcs) } func (c *darwinController) disableServices(services []string) error { @@ -99,21 +93,23 @@ func (c *darwinController) disableServices(services []string) error { return err } } - var first error - 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 - } - } + 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 first + return err } func (c *darwinController) Enabled() bool { @@ -142,11 +138,15 @@ func listNetworkServices() ([]string, error) { return services, sc.Err() } -func runNetworkSetup(args ...string) error { - cmd := exec.Command("networksetup", args...) +func runBash(script string) error { + cmd := exec.Command("/bin/bash", "-c", script) out, err := cmd.CombinedOutput() 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 } + +func shellQuote(s string) string { + return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" +} diff --git a/internal/update/update.go b/internal/update/update.go index e0e136f..3af6017 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -22,7 +22,7 @@ const CurrentVersion = "3.8.2" // BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part, // 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). const DefaultManifestURL = "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/update.json" diff --git a/scripts/build-macos-arm64.sh b/scripts/build-macos-arm64.sh index db769cf..f353299 100755 --- a/scripts/build-macos-arm64.sh +++ b/scripts/build-macos-arm64.sh @@ -77,9 +77,8 @@ h = hashlib.sha256(binp.read_bytes()).hexdigest() zh = hashlib.sha256(zipp.read_bytes()).hexdigest() if zipp.exists() else "" dh = hashlib.sha256(dmgp.read_bytes()).hexdigest() if dmgp.exists() else "" notes = ( - f"Navis {ver}+{build}: иконка Dock — бирюзовая N при подключении; " - f"при падении ядра снимается системный прокси; логи и проверка туннеля; " - f"автопереподключение / connect-on-launch; подписка чистит ноды; «Лучший» без soft-ok UDP." + f"Navis {ver}+{build}: меньше I/O на poll (без Stat ядер / кэш host); " + f"macOS sysproxy одним bash-batch; Connect без JSON Clone; Dock-иконка при подключении." ) paths = [ Path('dist/update.json'), diff --git a/server/update.json b/server/update.json index 97d2ac2..6d1efe5 100644 --- a/server/update.json +++ b/server/update.json @@ -1,6 +1,6 @@ { "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", "os": "windows", "arch": "amd64", @@ -16,13 +16,13 @@ }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "bea9d47f04a6ad7212ea2ca25e26d62a5f0d364c0978b1118e95837f740ef335", + "sha256": "b59c940dc946d18e6c57d9bcb3d9e36d489d894e9f889b08d476add3207babbc", "os": "darwin", "arch": "arm64", "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_sha256": "dffafe8e292006c9a1d4912fd4c715085ba21a71fd19d663ac3c0613b56f9909", - "dmg_sha256": "d31a4c63fb9f26372f7a89eff5cb03638d9f88cbd1929eb1e5525d5fdcffc8fe" + "zip_sha256": "7d695a76a111d0ea3ff13955d49db2d344972e8e0b3fdb3562060aa80b06ea67", + "dmg_sha256": "96927b0006934a9bc042130351cefa57096badb6771c50a31c0731ddade9cb0d" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", diff --git a/versioninfo.json b/versioninfo.json index 33b61e0..c824a9e 100644 --- a/versioninfo.json +++ b/versioninfo.json @@ -4,13 +4,13 @@ "Major": 3, "Minor": 8, "Patch": 2, - "Build": 2 + "Build": 3 }, "ProductVersion": { "Major": 3, "Minor": 8, "Patch": 2, - "Build": 2 + "Build": 3 }, "FileFlagsMask": "3f", "FileFlags": "00", @@ -21,12 +21,12 @@ "StringFileInfo": { "CompanyName": "EvilFox", "FileDescription": "Navis — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)", - "FileVersion": "3.8.2.2", + "FileVersion": "3.8.2.3", "InternalName": "Navis", "LegalCopyright": "Copyright (c) EvilFox", "OriginalFilename": "Navis.exe", "ProductName": "Navis", - "ProductVersion": "3.8.2.2", + "ProductVersion": "3.8.2.3", "Comments": "Open-source VPN/proxy client. https://evilfox.win/" }, "VarFileInfo": {