diff --git a/README.md b/README.md index 4567fb9..17d0a7c 100644 --- a/README.md +++ b/README.md @@ -208,13 +208,17 @@ https://evilfox.win/ ## Антивирусы (ложные срабатывания) -Некоторые AV (Kaspersky, Microsoft Wacatac.B!ml, Ikarus Trojan.WinGo.Agent и др.) часто помечают **любые** Go-бинарники с сетью и сменой системного прокси. +Некоторые AV (Bkav, Microsoft Wacapew/Wacatac, Ikarus Trojan.WinGo.Agent, Google, Trapmine) часто помечают **любые** неподписанные Go-бинарники с сетью и сменой системного прокси. -В 1.7.0 уменьшили «подозрительные» паттерны: -- нет скрытой загрузки cores при старте; -- нет `.bat`-самозамены с `del /f` + hidden detached `cmd`; -- обновление через `Navis-pending.exe` + явный флаг; -- Windows application manifest (`asInvoker`); -- metadata компании EvilFox / Product Navis. +В 1.7.1 дополнительно: +- обновление только скачивается в `Downloads` (без самозамены exe); +- нет автопроверки обновлений и тихой загрузки cores при старте; +- нет вызова `netsh winhttp`; +- Windows manifest + metadata EvilFox. -Если Defender/KAV ругается: добавьте `Navis.exe` в исключения или отправьте файл как False Positive вендору. Надёжнее всего — подпись Authenticode (code signing certificate). +Если всё ещё ругается — отправьте файл как False Positive: +- Microsoft: https://www.microsoft.com/wdsi/filesubmission +- Google: https://safebrowsing.google.com/safebrowsing/report_error/ +- VirusTotal community vote: «False positive» + +Полностью убрать ML-детекты обычно можно только с **Authenticode** (code signing). diff --git a/assets/app.manifest b/assets/app.manifest index 4c7a87c..84cfd86 100644 --- a/assets/app.manifest +++ b/assets/app.manifest @@ -1,7 +1,7 @@ diff --git a/cmd/vpnapp/main_windows.go b/cmd/vpnapp/main_windows.go index 75bf8bc..866d94a 100644 --- a/cmd/vpnapp/main_windows.go +++ b/cmd/vpnapp/main_windows.go @@ -57,10 +57,6 @@ type uiState struct { } func main() { - if update.MaybeFinishUpdate(os.Args[1:]) { - return - } - log.SetFlags(log.LstdFlags | log.Lshortfile) cfgPath, err := config.LocateConfig() @@ -138,8 +134,6 @@ func main() { mustBind(w, "saveHy2", a.saveHy2) mustBind(w, "importSubscription", a.importSubscription) - go a.autoCheckUpdate() - w.SetHtml(appui.IndexHTML) w.Run() } @@ -329,33 +323,10 @@ func (a *app) checkUpdate() (update.Status, error) { return st, nil } -func (a *app) applyUpdate() error { +func (a *app) applyUpdate() (string, error) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() - latest, err := update.Apply(ctx, update.DefaultManifestURL) - if err != nil { - return err - } - _ = a.mgr.Disconnect() - go func() { - time.Sleep(400 * time.Millisecond) - if a.webview != nil { - a.webview.Terminate() - } - os.Exit(0) - }() - _ = latest - return nil -} - -func (a *app) autoCheckUpdate() { - time.Sleep(2 * time.Second) - _, _ = a.checkUpdate() - ticker := time.NewTicker(6 * time.Hour) - defer ticker.Stop() - for range ticker.C { - _, _ = a.checkUpdate() - } + return update.Apply(ctx, update.DefaultManifestURL) } func openURL(raw string) error { diff --git a/cmd/vpnclient/main.go b/cmd/vpnclient/main.go index 65d7e90..acfbd81 100644 --- a/cmd/vpnclient/main.go +++ b/cmd/vpnclient/main.go @@ -287,11 +287,11 @@ func runApplyUpdate(args []string) int { _ = args ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) defer cancel() - latest, err := update.Apply(ctx, update.DefaultManifestURL) + msg, err := update.Apply(ctx, update.DefaultManifestURL) if err != nil { fmt.Fprintf(os.Stderr, "apply-update: %v\n", err) return 1 } - fmt.Printf("updating to %s — exit the app to finish\n", latest) + fmt.Println(msg) return 0 } diff --git a/dist/navis-release/Navis.exe b/dist/navis-release/Navis.exe index 9e5f853..47cea83 100644 Binary files a/dist/navis-release/Navis.exe and b/dist/navis-release/Navis.exe differ diff --git a/dist/navis-release/darwin-amd64/Navis b/dist/navis-release/darwin-amd64/Navis index 8c4cf70..4850a36 100644 Binary files a/dist/navis-release/darwin-amd64/Navis and b/dist/navis-release/darwin-amd64/Navis differ diff --git a/dist/navis-release/darwin-amd64/Navis.app.zip b/dist/navis-release/darwin-amd64/Navis.app.zip index 75b00be..4fb9631 100644 Binary files a/dist/navis-release/darwin-amd64/Navis.app.zip and b/dist/navis-release/darwin-amd64/Navis.app.zip differ diff --git a/dist/navis-release/darwin-amd64/Navis.dmg b/dist/navis-release/darwin-amd64/Navis.dmg index 8e3de81..b58806c 100644 Binary files a/dist/navis-release/darwin-amd64/Navis.dmg and b/dist/navis-release/darwin-amd64/Navis.dmg differ diff --git a/dist/navis-release/darwin-arm64/Navis b/dist/navis-release/darwin-arm64/Navis index 09c3e4b..af9f8a8 100644 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.app.zip b/dist/navis-release/darwin-arm64/Navis.app.zip index 200a359..59c8cd6 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 ed91dd0..f8288ea 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/update.json b/dist/navis-release/update.json index 67e143c..8419cc1 100644 --- a/dist/navis-release/update.json +++ b/dist/navis-release/update.json @@ -1,38 +1,38 @@ { - "version": "1.7.0", - "notes": "Меньше ложных срабатываний AV: без bat-апдейтера и тихой загрузки cores; Windows manifest; metadata EvilFox", + "version": "1.7.1", + "notes": "AV: обновление только в Downloads без самозамены; без netsh и автопроверки при старте", "platform": "windows-amd64", "os": "windows", "arch": "amd64", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d", + "sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b", "mandatory": false, "platforms": { "windows-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d", + "sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b", "os": "windows", "arch": "amd64" }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "3e544461c1ce8ec3b508639cb549a759b773da38517c98c9ca634faaf09a601c", + "sha256": "3cb42ab226b0263c8b6566888d80c6e7a6982d8cda992d112989c7f5c8a99bab", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", - "dmg_sha256": "39e88c985867ce6ef37deb00d87e1c6669cfbc8b9f8f0589d11a3f32036a5258", + "dmg_sha256": "65b8fe8c0d294ee923d59125f3e39c37f7c8a201aa6fd4661d080da59928c2e4", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", - "zip_sha256": "213fd7563e068f60a1ae2504c6e48171b9f9e3f2fe3c14ecad75ed809fa05d06" + "zip_sha256": "6685b1f8be9f8aa87613f35b8f8a86a00a6fb667c2f7bdc7116e6908c0cb1513" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", - "sha256": "f40a711108e78bfa1a944a1d4946549aba32cb5e00ef9035be352810c700807c", + "sha256": "7bffda217e178224f3a68fed4d5fff4e37b4705b7c8291b84f1d58151ef43b9d", "os": "darwin", "arch": "amd64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", - "dmg_sha256": "c70fe7feb000eec34c0fe4ef2d05565dd0285c0c4f18a264ecad540d2c0878db", + "dmg_sha256": "959526963936ad22d3c3607fe18295018362ec0d5fedcdf7561edf5d71c3b827", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip", - "zip_sha256": "5b3e3fea5aef4ac962c18a190c1943c7cfd5357ea233abbdec1d26007e1e6eb4" + "zip_sha256": "079c2af3fe5de6b9e73bad15a15bddfc93970d27be68b6620b5be3729163fc15" } } } diff --git a/dist/update.json b/dist/update.json index 67e143c..8419cc1 100644 --- a/dist/update.json +++ b/dist/update.json @@ -1,38 +1,38 @@ { - "version": "1.7.0", - "notes": "Меньше ложных срабатываний AV: без bat-апдейтера и тихой загрузки cores; Windows manifest; metadata EvilFox", + "version": "1.7.1", + "notes": "AV: обновление только в Downloads без самозамены; без netsh и автопроверки при старте", "platform": "windows-amd64", "os": "windows", "arch": "amd64", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d", + "sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b", "mandatory": false, "platforms": { "windows-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d", + "sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b", "os": "windows", "arch": "amd64" }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "3e544461c1ce8ec3b508639cb549a759b773da38517c98c9ca634faaf09a601c", + "sha256": "3cb42ab226b0263c8b6566888d80c6e7a6982d8cda992d112989c7f5c8a99bab", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", - "dmg_sha256": "39e88c985867ce6ef37deb00d87e1c6669cfbc8b9f8f0589d11a3f32036a5258", + "dmg_sha256": "65b8fe8c0d294ee923d59125f3e39c37f7c8a201aa6fd4661d080da59928c2e4", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", - "zip_sha256": "213fd7563e068f60a1ae2504c6e48171b9f9e3f2fe3c14ecad75ed809fa05d06" + "zip_sha256": "6685b1f8be9f8aa87613f35b8f8a86a00a6fb667c2f7bdc7116e6908c0cb1513" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", - "sha256": "f40a711108e78bfa1a944a1d4946549aba32cb5e00ef9035be352810c700807c", + "sha256": "7bffda217e178224f3a68fed4d5fff4e37b4705b7c8291b84f1d58151ef43b9d", "os": "darwin", "arch": "amd64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", - "dmg_sha256": "c70fe7feb000eec34c0fe4ef2d05565dd0285c0c4f18a264ecad540d2c0878db", + "dmg_sha256": "959526963936ad22d3c3607fe18295018362ec0d5fedcdf7561edf5d71c3b827", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip", - "zip_sha256": "5b3e3fea5aef4ac962c18a190c1943c7cfd5357ea233abbdec1d26007e1e6eb4" + "zip_sha256": "079c2af3fe5de6b9e73bad15a15bddfc93970d27be68b6620b5be3729163fc15" } } } diff --git a/internal/appui/index.html b/internal/appui/index.html index 331b89a..c951604 100644 --- a/internal/appui/index.html +++ b/internal/appui/index.html @@ -842,8 +842,8 @@ updateBtn.addEventListener("click", () => withBusy(async () => { try { setMeta("Скачивание обновления…"); - await applyUpdate(); - setMeta("Обновление установлено, перезапуск…", "ok"); + const msg = await applyUpdate(); + setMeta(String(msg || "Готово"), "ok"); } catch (e) { setMeta(String(e), "err"); } })); diff --git a/internal/sysproxy/windows.go b/internal/sysproxy/windows.go index 484782a..f6f5669 100644 --- a/internal/sysproxy/windows.go +++ b/internal/sysproxy/windows.go @@ -4,9 +4,7 @@ package sysproxy import ( "fmt" - "os/exec" "strings" - "syscall" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" @@ -87,7 +85,7 @@ func (c *windowsController) Enable(httpHostPort string) error { if err := notifyInternetSettingsChanged(); err != nil { return err } - _ = setWinHTTPProxy(httpHostPort) + // Skip netsh winhttp — spawning netsh is a common AV heuristic; WinINET covers browsers. c.enabled = true return nil @@ -120,7 +118,6 @@ func (c *windowsController) Disable() error { } _ = notifyInternetSettingsChanged() - _ = resetWinHTTPProxy() c.enabled = false return nil @@ -139,18 +136,3 @@ func notifyInternetSettingsChanged() error { } return nil } - -func setWinHTTPProxy(httpHostPort string) error { - cmd := exec.Command("netsh", "winhttp", "set", "proxy", httpHostPort, - "bypass-list=localhost;127.*;10.*;192.168.*;") - const createNoWindow = 0x08000000 - cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNoWindow} - return cmd.Run() -} - -func resetWinHTTPProxy() error { - cmd := exec.Command("netsh", "winhttp", "reset", "proxy") - const createNoWindow = 0x08000000 - cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNoWindow} - return cmd.Run() -} diff --git a/internal/update/update.go b/internal/update/update.go index 04b8acc..123a311 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -9,7 +9,6 @@ import ( "io" "net/http" "os" - "path/filepath" "runtime" "strconv" "strings" @@ -17,7 +16,7 @@ import ( ) // CurrentVersion is the shipped client version. -const CurrentVersion = "1.7.0" +const CurrentVersion = "1.7.1" // 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" @@ -226,51 +225,3 @@ func fileSHA256(path string) (string, error) { } return hex.EncodeToString(h.Sum(nil)), nil } - -func prepareDownload(ctx context.Context, manifestURL string) (latest, url, sha, exePath, tmpPath string, err error) { - st, err := Check(ctx, manifestURL) - if err != nil { - return "", "", "", "", "", err - } - if !st.Available { - if st.Error != "" { - return "", "", "", "", "", fmt.Errorf("%s", st.Error) - } - return "", "", "", "", "", fmt.Errorf("обновление не найдено") - } - if !allowedDownloadURL(st.URL) { - return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.evilfox.cc или files.de4ima.uk") - } - exe, err := os.Executable() - if err != nil { - return "", "", "", "", "", err - } - exe, err = filepath.Abs(exe) - if err != nil { - return "", "", "", "", "", err - } - tmp := exe + ".new" - if err := downloadFile(ctx, st.URL, tmp); err != nil { - return "", "", "", "", "", err - } - wantSHA := st.SHA256 - if wantSHA == "" { - if man, err := fetchManifest(ctx, manifestURL); err == nil { - if _, sha2, ok := man.AssetFor(PlatformKey()); ok { - wantSHA = sha2 - } - } - } - if wantSHA != "" { - sum, err := fileSHA256(tmp) - if err != nil { - _ = os.Remove(tmp) - return "", "", "", "", "", err - } - if !strings.EqualFold(sum, wantSHA) { - _ = os.Remove(tmp) - return "", "", "", "", "", fmt.Errorf("checksum mismatch") - } - } - return st.Latest, st.URL, wantSHA, exe, tmp, nil -} diff --git a/internal/update/update_darwin.go b/internal/update/update_darwin.go index 4b2f268..0c39ec2 100644 --- a/internal/update/update_darwin.go +++ b/internal/update/update_darwin.go @@ -8,43 +8,88 @@ import ( "os" "os/exec" "path/filepath" - "strconv" - "strings" + "runtime" ) -// Apply downloads the new binary and schedules replacement after exit (macOS). +// Apply downloads the new binary into ~/Downloads (no silent self-replace). func Apply(ctx context.Context, manifestURL string) (string, error) { - latest, _, _, exe, tmp, err := prepareDownload(ctx, manifestURL) + st, err := Check(ctx, manifestURL) if err != nil { return "", err } - _ = os.Chmod(tmp, 0o755) - dir := filepath.Dir(exe) - scriptPath := filepath.Join(dir, "navis-update.sh") - pid := os.Getpid() + if !st.Available { + if st.Error != "" { + return "", fmt.Errorf("%s", st.Error) + } + return "", fmt.Errorf("обновление не найдено") + } + if !allowedDownloadURL(st.URL) { + return "", fmt.Errorf("URL обновления должен быть на git.evilfox.cc или files.de4ima.uk") + } - script := "#!/bin/bash\n" + - "set -e\n" + - "EXE=" + shellQuote(exe) + "\n" + - "NEW=" + shellQuote(tmp) + "\n" + - "PID=" + strconv.Itoa(pid) + "\n" + - "while kill -0 \"$PID\" 2>/dev/null; do sleep 0.4; done\n" + - "sleep 0.5\n" + - "mv -f \"$NEW\" \"$EXE\"\n" + - "chmod +x \"$EXE\"\n" + - "rm -f " + shellQuote(scriptPath) + "\n" - - if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil { + home, err := os.UserHomeDir() + if err != nil { return "", err } - cmd := exec.Command("/bin/bash", scriptPath) - cmd.Dir = dir - if err := cmd.Start(); err != nil { - return "", fmt.Errorf("start updater: %w", err) + dir := filepath.Join(home, "Downloads") + _ = os.MkdirAll(dir, 0o755) + dest := filepath.Join(dir, fmt.Sprintf("Navis-%s-darwin-%s", sanitizeDarwinVer(st.Latest), runtime.GOARCH)) + tmp := dest + ".part" + _ = os.Remove(tmp) + _ = os.Remove(dest) + + if err := downloadFile(ctx, st.URL, tmp); err != nil { + return "", err } - return latest, nil + if st.SHA256 != "" { + sum, err := fileSHA256(tmp) + if err != nil { + _ = os.Remove(tmp) + return "", err + } + if sum != "" && !eqFold(sum, st.SHA256) { + _ = os.Remove(tmp) + return "", fmt.Errorf("checksum mismatch") + } + } + if err := os.Rename(tmp, dest); err != nil { + _ = os.Remove(tmp) + return "", err + } + _ = os.Chmod(dest, 0o755) + _ = exec.Command("open", "-R", dest).Start() + return fmt.Sprintf("Скачано v%s:\n%s\nЗакройте Navis и запустите новый файл.", st.Latest, dest), nil } -func shellQuote(s string) string { - return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" +func sanitizeDarwinVer(v string) string { + out := make([]rune, 0, len(v)) + for _, r := range v { + switch { + case r >= '0' && r <= '9', r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r == '.', r == '-': + out = append(out, r) + } + } + if len(out) == 0 { + return "update" + } + return string(out) +} + +func eqFold(a, b string) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + ca, cb := a[i], b[i] + if ca >= 'A' && ca <= 'F' { + ca += 'a' - 'A' + } + if cb >= 'A' && cb <= 'F' { + cb += 'a' - 'A' + } + if ca != cb { + return false + } + } + return true } diff --git a/internal/update/update_stub.go b/internal/update/update_stub.go deleted file mode 100644 index 76a7181..0000000 --- a/internal/update/update_stub.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows - -package update - -// MaybeFinishUpdate is Windows-only. -func MaybeFinishUpdate(args []string) bool { - _ = args - return false -} diff --git a/internal/update/update_windows.go b/internal/update/update_windows.go index 97126f7..873457d 100644 --- a/internal/update/update_windows.go +++ b/internal/update/update_windows.go @@ -5,116 +5,113 @@ package update import ( "context" "fmt" - "io" "os" "os/exec" "path/filepath" - "strconv" "syscall" - "time" - - "golang.org/x/sys/windows" ) -const finishUpdateFlag = "--navis-finish-update" - -// Apply downloads the new exe and starts it to replace the running binary after exit. -// Avoids writing .bat self-replacer scripts (common AV false-positive pattern). +// Apply downloads the new build into the user Downloads folder. +// It does NOT replace the running exe or spawn a self-updater (AV false-positive pattern). func Apply(ctx context.Context, manifestURL string) (string, error) { - latest, _, _, exe, tmp, err := prepareDownload(ctx, manifestURL) + st, err := Check(ctx, manifestURL) if err != nil { return "", err } - pending := filepath.Join(filepath.Dir(exe), "Navis-pending.exe") - _ = os.Remove(pending) - if err := os.Rename(tmp, pending); err != nil { - if err2 := copyFile(tmp, pending); err2 != nil { - _ = os.Remove(tmp) - return "", err2 + if !st.Available { + if st.Error != "" { + return "", fmt.Errorf("%s", st.Error) } - _ = os.Remove(tmp) + return "", fmt.Errorf("обновление не найдено") + } + if !allowedDownloadURL(st.URL) { + return "", fmt.Errorf("URL обновления должен быть на git.evilfox.cc или files.de4ima.uk") } - _ = os.Chmod(pending, 0o755) - cmd := exec.Command(pending, finishUpdateFlag, strconv.Itoa(os.Getpid()), exe) - cmd.Dir = filepath.Dir(exe) - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: false} - if err := cmd.Start(); err != nil { - return "", fmt.Errorf("start updater: %w", err) + dir, err := downloadsDir() + if err != nil { + return "", err } - return latest, nil + _ = os.MkdirAll(dir, 0o755) + dest := filepath.Join(dir, fmt.Sprintf("Navis-%s-windows-amd64.exe", sanitizeFileVer(st.Latest))) + tmp := dest + ".part" + _ = os.Remove(tmp) + _ = os.Remove(dest) + + if err := downloadFile(ctx, st.URL, tmp); err != nil { + return "", err + } + if st.SHA256 != "" { + sum, err := fileSHA256(tmp) + if err != nil { + _ = os.Remove(tmp) + return "", err + } + if !equalFoldHex(sum, st.SHA256) { + _ = os.Remove(tmp) + return "", fmt.Errorf("checksum mismatch") + } + } + if err := os.Rename(tmp, dest); err != nil { + _ = os.Remove(tmp) + return "", err + } + + _ = revealInExplorer(dest) + return fmt.Sprintf("Скачано v%s:\n%s\nЗакройте Navis и запустите новый файл.", st.Latest, dest), nil } -// MaybeFinishUpdate handles: Navis-pending.exe --navis-finish-update -// Returns true if this process was the updater and should exit. -func MaybeFinishUpdate(args []string) bool { - if len(args) < 3 || args[0] != finishUpdateFlag { +func downloadsDir() (string, error) { + if u := os.Getenv("USERPROFILE"); u != "" { + return filepath.Join(u, "Downloads"), nil + } + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, "Downloads"), nil +} + +func revealInExplorer(path string) error { + path, err := filepath.Abs(path) + if err != nil { + return err + } + cmd := exec.Command("explorer.exe", "/select,", path) + const createNoWindow = 0x08000000 + cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNoWindow} + return cmd.Start() +} + +func sanitizeFileVer(v string) string { + out := make([]rune, 0, len(v)) + for _, r := range v { + switch { + case r >= '0' && r <= '9', r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r == '.', r == '-': + out = append(out, r) + } + } + if len(out) == 0 { + return "update" + } + return string(out) +} + +func equalFoldHex(a, b string) bool { + if len(a) != len(b) { return false } - pid, err := strconv.Atoi(args[1]) - if err != nil || pid <= 0 { - return true + for i := 0; i < len(a); i++ { + ca, cb := a[i], b[i] + if ca >= 'A' && ca <= 'F' { + ca += 'a' - 'A' + } + if cb >= 'A' && cb <= 'F' { + cb += 'a' - 'A' + } + if ca != cb { + return false + } } - target := args[2] - self, err := os.Executable() - if err != nil { - return true - } - self, _ = filepath.Abs(self) - target, _ = filepath.Abs(target) - - waitPIDExit(uint32(pid), 90*time.Second) - time.Sleep(500 * time.Millisecond) - - backup := target + ".bak" - _ = os.Remove(backup) - if err := os.Rename(target, backup); err != nil { - // Still locked — wait a bit more. - time.Sleep(2 * time.Second) - _ = os.Rename(target, backup) - } - if err := copyFile(self, target); err != nil { - _ = os.Rename(backup, target) - return true - } - _ = os.Chmod(target, 0o755) - _ = os.Remove(backup) - _ = os.Remove(self) - - cmd := exec.Command(target) - cmd.Dir = filepath.Dir(target) - _ = cmd.Start() return true } - -func waitPIDExit(pid uint32, timeout time.Duration) { - const synchronize = 0x00100000 - h, err := windows.OpenProcess(synchronize, false, pid) - if err != nil { - time.Sleep(1500 * time.Millisecond) - return - } - defer windows.CloseHandle(h) - ms := uint32(timeout / time.Millisecond) - if ms == 0 { - ms = 1000 - } - _, _ = windows.WaitForSingleObject(h, ms) -} - -func copyFile(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) - if err != nil { - return err - } - defer out.Close() - if _, err := io.Copy(out, in); err != nil { - return err - } - return out.Close() -} diff --git a/server/update.json b/server/update.json index 67e143c..8419cc1 100644 --- a/server/update.json +++ b/server/update.json @@ -1,38 +1,38 @@ { - "version": "1.7.0", - "notes": "Меньше ложных срабатываний AV: без bat-апдейтера и тихой загрузки cores; Windows manifest; metadata EvilFox", + "version": "1.7.1", + "notes": "AV: обновление только в Downloads без самозамены; без netsh и автопроверки при старте", "platform": "windows-amd64", "os": "windows", "arch": "amd64", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d", + "sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b", "mandatory": false, "platforms": { "windows-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "8bb8bab990292be66f169dc0a23f0aaff45cf349b6c6b5cfbef763f55cfe0d4d", + "sha256": "e08425b2eddd17a71a6e083990313811df9a1f3a55586c407798bfeeb82fd68b", "os": "windows", "arch": "amd64" }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "3e544461c1ce8ec3b508639cb549a759b773da38517c98c9ca634faaf09a601c", + "sha256": "3cb42ab226b0263c8b6566888d80c6e7a6982d8cda992d112989c7f5c8a99bab", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", - "dmg_sha256": "39e88c985867ce6ef37deb00d87e1c6669cfbc8b9f8f0589d11a3f32036a5258", + "dmg_sha256": "65b8fe8c0d294ee923d59125f3e39c37f7c8a201aa6fd4661d080da59928c2e4", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.app.zip", - "zip_sha256": "213fd7563e068f60a1ae2504c6e48171b9f9e3f2fe3c14ecad75ed809fa05d06" + "zip_sha256": "6685b1f8be9f8aa87613f35b8f8a86a00a6fb667c2f7bdc7116e6908c0cb1513" }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", - "sha256": "f40a711108e78bfa1a944a1d4946549aba32cb5e00ef9035be352810c700807c", + "sha256": "7bffda217e178224f3a68fed4d5fff4e37b4705b7c8291b84f1d58151ef43b9d", "os": "darwin", "arch": "amd64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", - "dmg_sha256": "c70fe7feb000eec34c0fe4ef2d05565dd0285c0c4f18a264ecad540d2c0878db", + "dmg_sha256": "959526963936ad22d3c3607fe18295018362ec0d5fedcdf7561edf5d71c3b827", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.app.zip", - "zip_sha256": "5b3e3fea5aef4ac962c18a190c1943c7cfd5357ea233abbdec1d26007e1e6eb4" + "zip_sha256": "079c2af3fe5de6b9e73bad15a15bddfc93970d27be68b6620b5be3729163fc15" } } } diff --git a/versioninfo.json b/versioninfo.json index 7b296dd..31d92e9 100644 --- a/versioninfo.json +++ b/versioninfo.json @@ -1,7 +1,7 @@ { "FixedFileInfo": { - "FileVersion": { "Major": 1, "Minor": 7, "Patch": 0, "Build": 0 }, - "ProductVersion": { "Major": 1, "Minor": 7, "Patch": 0, "Build": 0 }, + "FileVersion": { "Major": 1, "Minor": 7, "Patch": 1, "Build": 0 }, + "ProductVersion": { "Major": 1, "Minor": 7, "Patch": 1, "Build": 0 }, "FileFlagsMask": "3f", "FileFlags": "00", "FileOS": "40004", @@ -11,12 +11,12 @@ "StringFileInfo": { "CompanyName": "EvilFox", "FileDescription": "Navis VPN client (NaiveProxy / Hysteria 2)", - "FileVersion": "1.7.0.0", + "FileVersion": "1.7.1.0", "InternalName": "Navis", "LegalCopyright": "Copyright (c) EvilFox", "OriginalFilename": "Navis.exe", "ProductName": "Navis", - "ProductVersion": "1.7.0.0", + "ProductVersion": "1.7.1.0", "Comments": "Open-source VPN/proxy client. https://evilfox.win/" }, "VarFileInfo": {