diff --git a/README.md b/README.md index a951928..4713ae5 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,13 @@ https://evilfox.win/ Некоторые AV (Bkav, Microsoft Wacapew/Wacatac, Ikarus Trojan.WinGo.Agent, Google, Trapmine) часто помечают **любые** неподписанные Go-бинарники с сетью и сменой системного прокси. +В 2.7.2: +- убран вечный цикл обновления (проверка больше не устанавливает сама; только кнопка «Обновить»); +- номер сборки в версии (`2.7.2+N` / FileVersion `2.7.2.N`); сравнение обновлений только по product semver. + +В 2.7.1: +- обновление только по кнопке «Обновить»; можно «Пропустить эту версию» (следующую всё равно предложит). + В 2.7.0: - GUI на macOS (тот же интерфейс, что на Windows): DMG → Navis.app открывает окно клиента; CLI лежит как `Navis-cli`. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 2f0f5e1..8654969 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -12,8 +12,9 @@ android { applicationId = "win.evilfox.navis" minSdk = 26 targetSdk = 35 - versionCode = 270 - versionName = "2.7.0" + // versionCode = major*1_000_000 + minor*10_000 + patch*100 + build + versionCode = 2_070_201 + versionName = "2.7.2+1" vectorDrawables.useSupportLibrary = true ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64") diff --git a/build-macos.bat b/build-macos.bat index b5ca5e0..cc9f692 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 2.7.1 -arch arm64 +tools\packmac\packmac.exe -bin "dist\navis-release\darwin-arm64\Navis" -out "dist\navis-release\darwin-arm64" -version 2.7.2 -build 2.7.2.1 -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 2.7.1 -arch amd64 +tools\packmac\packmac.exe -bin "dist\navis-release\darwin-amd64\Navis" -out "dist\navis-release\darwin-amd64" -version 2.7.2 -build 2.7.2.1 -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 2.7.1 -arch universal +tools\packmac\packmac.exe -bin "dist\navis-release\darwin-universal\Navis" -out "dist\navis-release\darwin-universal" -version 2.7.2 -build 2.7.2.1 -arch universal if errorlevel 1 exit /b 1 echo Built Mac GUI + CLI: diff --git a/cmd/vpnapp/main_windows.go b/cmd/vpnapp/main_windows.go index ad3de2e..6c9a933 100644 --- a/cmd/vpnapp/main_windows.go +++ b/cmd/vpnapp/main_windows.go @@ -1,4 +1,4 @@ -//go:build windows +//go:build windows package main @@ -92,7 +92,7 @@ func main() { cfgPath: cfgPath, logBuf: logBuf, updateStatus: update.Status{ - Current: update.CurrentVersion, + Current: update.DisplayVersion(), ManifestURL: update.DefaultManifestURL, }, } @@ -204,7 +204,7 @@ func (a *app) getState() (uiState, error) { CorePath: corePath, ConfigPath: a.cfgPath, Profiles: cfg.ListProfiles(), - Version: update.CurrentVersion, + Version: update.DisplayVersion(), Update: a.updateStatus, Pings: append([]netcheck.Result(nil), a.pings...), Subscription: cfg.SubscriptionURL, @@ -418,7 +418,7 @@ func (a *app) checkUpdate() (update.Status, error) { st, err := update.Check(ctx, update.DefaultManifestURL) a.mu.Lock() if err != nil { - st.Current = update.CurrentVersion + st.Current = update.DisplayVersion() st.ManifestURL = update.DefaultManifestURL st.Error = err.Error() } @@ -428,6 +428,14 @@ func (a *app) checkUpdate() (update.Status, error) { } func (a *app) applyUpdate() (string, error) { + // Explicit user action only — never called from autoCheckUpdate. + st, err := a.checkUpdate() + if err != nil { + return "", err + } + if !st.Available { + return "", fmt.Errorf("обновление не требуется (текущая %s)", update.DisplayVersion()) + } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() latest, err := update.Apply(ctx, update.DefaultManifestURL) @@ -444,14 +452,11 @@ func (a *app) applyUpdate() (string, error) { } func (a *app) autoCheckUpdate() { + // Only refresh update status for the UI banner — never auto-install. + // (Previously auto-apply caused an infinite update loop when the feed version + // stayed ahead of the shipped CurrentVersion in the downloaded binary.) time.Sleep(3 * time.Second) - st, err := a.checkUpdate() - if err != nil || !st.Available { - return - } - // Auto-apply + relaunch when a newer build is published. - time.Sleep(1500 * time.Millisecond) - _, _ = a.applyUpdate() + _, _ = a.checkUpdate() } func openURL(raw string) error { diff --git a/dist/navis-release/Navis.exe b/dist/navis-release/Navis.exe index 5bd4c20..b72117e 100644 Binary files a/dist/navis-release/Navis.exe and b/dist/navis-release/Navis.exe differ diff --git a/dist/navis-release/android/Navis.apk b/dist/navis-release/android/Navis.apk index 4961a89..6678d44 100644 Binary files a/dist/navis-release/android/Navis.apk and b/dist/navis-release/android/Navis.apk differ diff --git a/dist/navis-release/darwin-amd64/Navis b/dist/navis-release/darwin-amd64/Navis index 418572f..e6f5ab6 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-cli b/dist/navis-release/darwin-amd64/Navis-cli index 814ba3a..d58ace4 100644 Binary files a/dist/navis-release/darwin-amd64/Navis-cli and b/dist/navis-release/darwin-amd64/Navis-cli differ diff --git a/dist/navis-release/darwin-amd64/Navis.app.zip b/dist/navis-release/darwin-amd64/Navis.app.zip index d221f2f..ea28e45 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 ef9d960..18c6661 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 57cd02a..2cf521f 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 67ee051..3c1031c 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 5ad25a4..ae69c21 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 8e0bce2..b5ac76f 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-universal/Navis b/dist/navis-release/darwin-universal/Navis index 0191cda..aa31ff9 100644 Binary files a/dist/navis-release/darwin-universal/Navis and b/dist/navis-release/darwin-universal/Navis differ diff --git a/dist/navis-release/darwin-universal/Navis-cli b/dist/navis-release/darwin-universal/Navis-cli index 4a6be2b..ea041e8 100644 Binary files a/dist/navis-release/darwin-universal/Navis-cli and b/dist/navis-release/darwin-universal/Navis-cli differ diff --git a/dist/navis-release/darwin-universal/Navis.app.zip b/dist/navis-release/darwin-universal/Navis.app.zip index d16bbac..7466656 100644 Binary files a/dist/navis-release/darwin-universal/Navis.app.zip and b/dist/navis-release/darwin-universal/Navis.app.zip differ diff --git a/dist/navis-release/darwin-universal/Navis.dmg b/dist/navis-release/darwin-universal/Navis.dmg index eb2e2f5..4f7d492 100644 Binary files a/dist/navis-release/darwin-universal/Navis.dmg and b/dist/navis-release/darwin-universal/Navis.dmg differ diff --git a/dist/navis-release/update.json b/dist/navis-release/update.json index 38c187e..5dd969b 100644 --- a/dist/navis-release/update.json +++ b/dist/navis-release/update.json @@ -1,22 +1,22 @@ { - "version": "2.7.1", - "notes": "macOS: UDZO DMG, Application Support cores path, naiveproxy asset name fix", + "version": "2.7.2", + "notes": "2.7.2: исправлен вечный цикл обновления (только кнопка «Обновить», без автоустановки); номер сборки 2.7.2+N; можно «Пропустить эту версию».", "platform": "windows-amd64", "os": "windows", "arch": "amd64", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "60392f389c9ed48b11124ce440d787ab7b051e7593833f15172e1c09434245dd", + "sha256": "9a64a3b059c53eb818ff696d5f446c39f52e243508f92d05365dd06a05c57a1e", "mandatory": false, "platforms": { "windows-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "60392f389c9ed48b11124ce440d787ab7b051e7593833f15172e1c09434245dd", + "sha256": "9a64a3b059c53eb818ff696d5f446c39f52e243508f92d05365dd06a05c57a1e", "os": "windows", "arch": "amd64" }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "8951c351b224d8a13e2772b6ee080d7bee3285ac162dd20c0855a8653a569ec5", + "sha256": "8fa7fcb40d4165d20d3f81840c84f729af034b54dd4698ea144c7145c85872eb", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", @@ -24,7 +24,7 @@ }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", - "sha256": "e71d7c0080e9acdcaa2355e4dd8fa3ccec8d4ed24646e27dbc1a814b0a4019cc", + "sha256": "8072cacd79fa95da3f3230b88b8e09e380ed2e9f634703f579950952b94f774d", "os": "darwin", "arch": "amd64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", @@ -32,11 +32,11 @@ }, "darwin-universal": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis", - "sha256": "6b58907bb81583cf1730b34012cce97e7f758cf3eb12e630c971d0232cce5262", + "sha256": "f50642373f615fa55117d1bbe7b2f86f2a19ab03a9ae53cc7e7d137b432ebbe5", "os": "darwin", "arch": "universal", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.dmg", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.app.zip" } } -} +} \ No newline at end of file diff --git a/dist/navis-release/vpnclient.exe b/dist/navis-release/vpnclient.exe new file mode 100644 index 0000000..00eb4d6 Binary files /dev/null and b/dist/navis-release/vpnclient.exe differ diff --git a/dist/update.json b/dist/update.json index 38c187e..5dd969b 100644 --- a/dist/update.json +++ b/dist/update.json @@ -1,22 +1,22 @@ { - "version": "2.7.1", - "notes": "macOS: UDZO DMG, Application Support cores path, naiveproxy asset name fix", + "version": "2.7.2", + "notes": "2.7.2: исправлен вечный цикл обновления (только кнопка «Обновить», без автоустановки); номер сборки 2.7.2+N; можно «Пропустить эту версию».", "platform": "windows-amd64", "os": "windows", "arch": "amd64", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "60392f389c9ed48b11124ce440d787ab7b051e7593833f15172e1c09434245dd", + "sha256": "9a64a3b059c53eb818ff696d5f446c39f52e243508f92d05365dd06a05c57a1e", "mandatory": false, "platforms": { "windows-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "60392f389c9ed48b11124ce440d787ab7b051e7593833f15172e1c09434245dd", + "sha256": "9a64a3b059c53eb818ff696d5f446c39f52e243508f92d05365dd06a05c57a1e", "os": "windows", "arch": "amd64" }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "8951c351b224d8a13e2772b6ee080d7bee3285ac162dd20c0855a8653a569ec5", + "sha256": "8fa7fcb40d4165d20d3f81840c84f729af034b54dd4698ea144c7145c85872eb", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", @@ -24,7 +24,7 @@ }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", - "sha256": "e71d7c0080e9acdcaa2355e4dd8fa3ccec8d4ed24646e27dbc1a814b0a4019cc", + "sha256": "8072cacd79fa95da3f3230b88b8e09e380ed2e9f634703f579950952b94f774d", "os": "darwin", "arch": "amd64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", @@ -32,11 +32,11 @@ }, "darwin-universal": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis", - "sha256": "6b58907bb81583cf1730b34012cce97e7f758cf3eb12e630c971d0232cce5262", + "sha256": "f50642373f615fa55117d1bbe7b2f86f2a19ab03a9ae53cc7e7d137b432ebbe5", "os": "darwin", "arch": "universal", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.dmg", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.app.zip" } } -} +} \ No newline at end of file diff --git a/internal/apphost/app.go b/internal/apphost/app.go index 94be36e..f33dcb9 100644 --- a/internal/apphost/app.go +++ b/internal/apphost/app.go @@ -70,7 +70,7 @@ func New(mgr *core.Manager, cfgPath string, logBuf *bytes.Buffer) *App { CfgPath: cfgPath, LogBuf: logBuf, UpdateStatus: update.Status{ - Current: update.CurrentVersion, + Current: update.DisplayVersion(), ManifestURL: update.DefaultManifestURL, }, } @@ -125,7 +125,7 @@ func (a *App) GetState() (UIState, error) { CorePath: corePath, ConfigPath: a.CfgPath, Profiles: cfg.ListProfiles(), - Version: update.CurrentVersion, + Version: update.DisplayVersion(), Update: a.UpdateStatus, Pings: append([]netcheck.Result(nil), a.Pings...), Subscription: cfg.SubscriptionURL, @@ -322,7 +322,7 @@ func (a *App) CheckUpdate() (update.Status, error) { st, err := update.Check(ctx, update.DefaultManifestURL) a.mu.Lock() if err != nil { - st.Current = update.CurrentVersion + st.Current = update.DisplayVersion() st.ManifestURL = update.DefaultManifestURL st.Error = err.Error() } @@ -332,6 +332,14 @@ func (a *App) CheckUpdate() (update.Status, error) { } func (a *App) ApplyUpdate() (string, error) { + // Explicit user action only — never called from AutoCheckUpdate. + st, err := a.CheckUpdate() + if err != nil { + return "", err + } + if !st.Available { + return "", fmt.Errorf("обновление не требуется (текущая %s)", update.DisplayVersion()) + } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() latest, err := update.Apply(ctx, update.DefaultManifestURL) @@ -351,13 +359,10 @@ func (a *App) ApplyUpdate() (string, error) { } func (a *App) AutoCheckUpdate() { + // Only check and populate status for the banner — never auto-apply. + // Auto-apply previously caused endless download→relaunch loops on Mac/Windows. time.Sleep(3 * time.Second) - st, err := a.CheckUpdate() - if err != nil || !st.Available { - return - } - time.Sleep(1500 * time.Millisecond) - _, _ = a.ApplyUpdate() + _, _ = a.CheckUpdate() } func (a *App) OpenShopURL(raw string) error { diff --git a/internal/appui/index.html b/internal/appui/index.html index e0ceb54..bbf8723 100644 --- a/internal/appui/index.html +++ b/internal/appui/index.html @@ -76,6 +76,15 @@ color: var(--muted); font-size: .84rem; } + .update-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + .update-actions .action { + flex: 1 1 auto; + min-width: 140px; + } .top { display: flex; @@ -712,7 +721,10 @@
Доступно обновление

- +
+ + +
@@ -725,7 +737,7 @@

Navis

- 2.7 + 2.7.1

Быстрый клиент · Naive · Hy2 · AWG · Xray

@@ -938,7 +950,18 @@ const updCheckBtn = $("updCheckBtn"); const updateBanner = $("updateBanner"); const updateBtn = $("updateBtn"); + const skipUpdateBtn = $("skipUpdateBtn"); const verLabel = $("verLabel"); + const SKIP_UPDATE_KEY = "navis.skipUpdateVersion"; + function skippedVersion() { + try { return localStorage.getItem(SKIP_UPDATE_KEY) || ""; } catch (_) { return ""; } + } + function setSkippedVersion(v) { + try { + if (v) localStorage.setItem(SKIP_UPDATE_KEY, String(v)); + else localStorage.removeItem(SKIP_UPDATE_KEY); + } catch (_) {} + } const quitBtn = $("quitBtn"); if (window.__navisHttp && quitBtn) { quitBtn.hidden = false; @@ -1173,10 +1196,17 @@ updateBanner.classList.remove("show"); return; } + const latest = (u.latest || "").toString(); + const skip = skippedVersion(); + if (u.available && latest && skip === latest) { + updateBanner.classList.remove("show"); + return; + } if (u.available) { updateBanner.classList.add("show"); - $("updateTitle").textContent = "Обновление " + u.latest; - $("updateNotes").textContent = u.notes || ("Текущая версия " + u.current); + $("updateTitle").textContent = "Доступно обновление " + latest; + $("updateNotes").textContent = u.notes || ("У вас " + (u.current || version || "?") + ". Обновление только по кнопке."); + updateBanner.dataset.latest = latest; } else { updateBanner.classList.remove("show"); } @@ -1279,7 +1309,7 @@ } function paintButtonsLocked(locked) { - [btn, coreBtn, saveBtn, addBtn, delBtn, profile, pingBtn, bestBtn, updCheckBtn, updateBtn, subBtn, subUrl].forEach((b) => { + [btn, coreBtn, saveBtn, addBtn, delBtn, profile, pingBtn, bestBtn, updCheckBtn, updateBtn, skipUpdateBtn, subBtn, subUrl].forEach((b) => { if (b) b.disabled = locked; }); } @@ -1444,6 +1474,13 @@ } catch (e) { setMeta(String(e), "err"); } })); + skipUpdateBtn.addEventListener("click", () => { + const latest = updateBanner.dataset.latest || ""; + if (latest) setSkippedVersion(latest); + updateBanner.classList.remove("show"); + setMeta("Версия " + (latest || "?") + " пропущена. Следующую предложим.", "ok"); + }); + (async () => { try { await refresh({ syncForm: true }); diff --git a/internal/update/update.go b/internal/update/update.go index d278be1..2dc77fc 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -16,12 +16,27 @@ import ( "time" ) -// CurrentVersion is the shipped client version. -const CurrentVersion = "2.7.1" +// CurrentVersion is the product/semver used for update eligibility (feed "version"). +// Keep major.minor.patch only — no build suffix here. +const CurrentVersion = "2.7.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 = 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" +// DisplayVersion is shown in the UI (product + build), e.g. "2.7.2+1". +func DisplayVersion() string { + return fmt.Sprintf("%s+%d", CurrentVersion, BuildNumber) +} + +// FileVersionDots is Major.Minor.Patch.Build for Windows resources / CFBundleVersion. +func FileVersionDots() string { + return fmt.Sprintf("%s.%d", CurrentVersion, BuildNumber) +} + // PlatformAsset is one OS/arch binary in the feed. type PlatformAsset struct { URL string `json:"url"` @@ -90,7 +105,7 @@ func Check(ctx context.Context, manifestURL string) (Status, error) { manifestURL = DefaultManifestURL } key := PlatformKey() - st := Status{Current: CurrentVersion, ManifestURL: manifestURL, Platform: key} + st := Status{Current: DisplayVersion(), ManifestURL: manifestURL, Platform: key} m, err := fetchManifest(ctx, manifestURL) if err != nil { @@ -109,38 +124,46 @@ func Check(ctx context.Context, manifestURL string) (Status, error) { } st.URL = url st.SHA256 = sha + // Product-only compare: build numbers must not keep offering an update after install + // when feed version equals CurrentVersion (e.g. 2.7.2 vs local 2.7.2+1). st.Available = Compare(st.Latest, CurrentVersion) > 0 && url != "" return st, nil } -// Compare returns 1 if a>b, -1 if ab, -1 if a n { - n = len(bs) - } - for i := 0; i < n; i++ { - var ai, bi int - if i < len(as) { - ai = as[i] - } - if i < len(bs) { - bi = bs[i] - } - if ai > bi { + as := productParts(a) + bs := productParts(b) + for i := 0; i < 3; i++ { + if as[i] > bs[i] { return 1 } - if ai < bi { + if as[i] < bs[i] { return -1 } } return 0 } +func productParts(v string) [3]int { + parts := splitVer(v) + var out [3]int + for i := 0; i < 3 && i < len(parts); i++ { + out[i] = parts[i] + } + return out +} + func splitVer(v string) []int { v = strings.TrimPrefix(strings.TrimSpace(v), "v") + // Strip build metadata: 2.7.2+1 / 2.7.2-beta → compare product only. + if i := strings.IndexAny(v, "+-"); i >= 0 { + v = v[:i] + } parts := strings.Split(v, ".") out := make([]int, 0, len(parts)) for _, p := range parts { @@ -149,9 +172,6 @@ func splitVer(v string) []int { out = append(out, 0) continue } - if i := strings.IndexAny(p, "-+"); i >= 0 { - p = p[:i] - } n, _ := strconv.Atoi(p) out = append(out, n) } @@ -172,7 +192,7 @@ func fetchManifest(ctx context.Context, manifestURL string) (Manifest, error) { if err != nil { return Manifest{}, err } - req.Header.Set("User-Agent", "Navis/"+CurrentVersion) + req.Header.Set("User-Agent", "Navis/"+DisplayVersion()) req.Header.Set("Accept", "application/json") client := &http.Client{Timeout: 20 * time.Second} resp, err := client.Do(req) @@ -195,7 +215,7 @@ func downloadFile(ctx context.Context, url, dest string) error { if err != nil { return err } - req.Header.Set("User-Agent", "Navis/"+CurrentVersion) + req.Header.Set("User-Agent", "Navis/"+DisplayVersion()) client := &http.Client{Timeout: 10 * time.Minute} resp, err := client.Do(req) if err != nil { @@ -227,7 +247,8 @@ func fileSHA256(path string) (string, error) { return hex.EncodeToString(h.Sum(nil)), nil } -// prepareDownload fetches the update next to the running binary as ".new". +// prepareDownload fetches the update next to the running binary as temp download. +// Refuses when already on/newer than the feed product version (no re-apply loop). func prepareDownload(ctx context.Context, manifestURL string) (latest, url, sha, exePath, tmpPath string, err error) { st, err := Check(ctx, manifestURL) if err != nil { @@ -237,7 +258,7 @@ func prepareDownload(ctx context.Context, manifestURL string) (latest, url, sha, if st.Error != "" { return "", "", "", "", "", fmt.Errorf("%s", st.Error) } - return "", "", "", "", "", fmt.Errorf("обновление не найдено") + return "", "", "", "", "", fmt.Errorf("уже актуальная версия %s (канал %s)", DisplayVersion(), st.Latest) } if !allowedDownloadURL(st.URL) { return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.evilfox.cc или files.de4ima.uk") @@ -250,6 +271,9 @@ func prepareDownload(ctx context.Context, manifestURL string) (latest, url, sha, if err != nil { return "", "", "", "", "", err } + if resolved, err := filepath.EvalSymlinks(exe); err == nil && resolved != "" { + exe = resolved + } tmp := filepath.Join(filepath.Dir(exe), ".navis-update-download") _ = os.Remove(tmp) // Also clear legacy leftover from older updaters. diff --git a/internal/update/update_test.go b/internal/update/update_test.go index 2b3a149..66664d2 100644 --- a/internal/update/update_test.go +++ b/internal/update/update_test.go @@ -15,6 +15,19 @@ func TestCompare(t *testing.T) { if Compare("1.1.0", "1.1.0") != 0 { t.Fatal("expected equal") } + // Build must not make same product look outdated or newer for update checks. + if Compare("2.7.2", "2.7.2+9") != 0 { + t.Fatal("expected equal ignoring +build") + } + if Compare("2.7.2", "2.7.2.99") != 0 { + t.Fatal("expected equal ignoring 4th component") + } + if Compare("2.7.3", "2.7.2+1") <= 0 { + t.Fatal("expected product bump to win") + } + if DisplayVersion() == "" || !strings.Contains(DisplayVersion(), "+") { + t.Fatal("DisplayVersion should include build") + } } func TestManifestAssetFor(t *testing.T) { diff --git a/scripts/install-macos.sh b/scripts/install-macos.sh index 81bd468..8b27b64 100644 --- a/scripts/install-macos.sh +++ b/scripts/install-macos.sh @@ -17,7 +17,7 @@ DEST="${1:-$HOME/Downloads/Navis.dmg}" UNIVERSAL="$BASE/darwin-universal/Navis.dmg" SPECIFIC="$BASE/$SUBDIR/Navis.dmg" -echo "Mac: $ARCH · ставим Navis GUI 2.7" +echo "Mac: $ARCH · ставим Navis GUI 2.7.1" echo "Скачиваю универсальный DMG…" if ! curl -fL -o "$DEST" "$UNIVERSAL"; then echo "Universal недоступен, скачиваю $SUBDIR…" diff --git a/server/update.json b/server/update.json index fe04ef1..5dd969b 100644 --- a/server/update.json +++ b/server/update.json @@ -1,22 +1,22 @@ { - "version": "2.7.0", - "notes": "GUI на macOS (как на Windows). Откройте Navis.app — окно клиента. CLI: Navis-cli", + "version": "2.7.2", + "notes": "2.7.2: исправлен вечный цикл обновления (только кнопка «Обновить», без автоустановки); номер сборки 2.7.2+N; можно «Пропустить эту версию».", "platform": "windows-amd64", "os": "windows", "arch": "amd64", "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "60392f389c9ed48b11124ce440d787ab7b051e7593833f15172e1c09434245dd", + "sha256": "9a64a3b059c53eb818ff696d5f446c39f52e243508f92d05365dd06a05c57a1e", "mandatory": false, "platforms": { "windows-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/Navis.exe", - "sha256": "60392f389c9ed48b11124ce440d787ab7b051e7593833f15172e1c09434245dd", + "sha256": "9a64a3b059c53eb818ff696d5f446c39f52e243508f92d05365dd06a05c57a1e", "os": "windows", "arch": "amd64" }, "darwin-arm64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis", - "sha256": "dd15b856bed836b82f2892e64db4db71771a947f3a392ce7d9f9e7f1b59edf1d", + "sha256": "8fa7fcb40d4165d20d3f81840c84f729af034b54dd4698ea144c7145c85872eb", "os": "darwin", "arch": "arm64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg", @@ -24,7 +24,7 @@ }, "darwin-amd64": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis", - "sha256": "e71d7c0080e9acdcaa2355e4dd8fa3ccec8d4ed24646e27dbc1a814b0a4019cc", + "sha256": "8072cacd79fa95da3f3230b88b8e09e380ed2e9f634703f579950952b94f774d", "os": "darwin", "arch": "amd64", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-amd64/Navis.dmg", @@ -32,11 +32,11 @@ }, "darwin-universal": { "url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis", - "sha256": "6b58907bb81583cf1730b34012cce97e7f758cf3eb12e630c971d0232cce5262", + "sha256": "f50642373f615fa55117d1bbe7b2f86f2a19ab03a9ae53cc7e7d137b432ebbe5", "os": "darwin", "arch": "universal", "dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.dmg", "zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.app.zip" } } -} +} \ No newline at end of file diff --git a/tools/packmac/main.go b/tools/packmac/main.go index e1a0dd6..553b990 100644 --- a/tools/packmac/main.go +++ b/tools/packmac/main.go @@ -20,19 +20,24 @@ func main() { bin := flag.String("bin", "", "path to darwin Navis binary") outDir := flag.String("out", "", "output directory (e.g. dist/navis-release/darwin-arm64)") version := flag.String("version", "1.4.1", "CFBundleShortVersionString") + build := flag.String("build", "", "CFBundleVersion (defaults to -version)") arch := flag.String("arch", "arm64", "arch label for volume name") flag.Parse() if *bin == "" || *outDir == "" { - fmt.Fprintln(os.Stderr, "usage: packmac -bin -out [-version 1.4.1] [-arch arm64]") + fmt.Fprintln(os.Stderr, "usage: packmac -bin -out [-version 2.7.2] [-build 2.7.2.1] [-arch arm64]") os.Exit(2) } - if err := run(*bin, *outDir, *version, *arch); err != nil { + bundleVer := *build + if bundleVer == "" { + bundleVer = *version + } + if err := run(*bin, *outDir, *version, bundleVer, *arch); err != nil { fmt.Fprintf(os.Stderr, "packmac: %v\n", err) os.Exit(1) } } -func run(binPath, outDir, version, arch string) error { +func run(binPath, outDir, version, bundleVersion, arch string) error { if err := os.MkdirAll(outDir, 0o755); err != nil { return err } @@ -43,7 +48,7 @@ func run(binPath, outDir, version, arch string) error { defer os.RemoveAll(stage) appRoot := filepath.Join(stage, "Navis.app") - if err := writeAppBundle(appRoot, binPath, version, arch); err != nil { + if err := writeAppBundle(appRoot, binPath, version, bundleVersion, arch); err != nil { return err } if err := signAppBundle(appRoot); err != nil { @@ -123,7 +128,7 @@ func writeHdiutilDMG(dmgPath, stageDir, volume string) error { return nil } -func writeAppBundle(appRoot, binPath, version, arch string) error { +func writeAppBundle(appRoot, binPath, version, bundleVersion, arch string) error { macOSDir := filepath.Join(appRoot, "Contents", "MacOS") if err := os.MkdirAll(macOSDir, 0o755); err != nil { return err @@ -145,7 +150,7 @@ func writeAppBundle(appRoot, binPath, version, arch string) error { CFBundleInfoDictionaryVersion6.0%s -`, version, version, archPriority) +`, version, bundleVersion, archPriority) if err := os.WriteFile(filepath.Join(appRoot, "Contents", "Info.plist"), []byte(plist), 0o644); err != nil { return err } diff --git a/versioninfo.json b/versioninfo.json index 844ecaf..f185da0 100644 --- a/versioninfo.json +++ b/versioninfo.json @@ -1,7 +1,7 @@ { "FixedFileInfo": { - "FileVersion": { "Major": 2, "Minor": 7, "Patch": 0, "Build": 0 }, - "ProductVersion": { "Major": 2, "Minor": 7, "Patch": 0, "Build": 0 }, + "FileVersion": { "Major": 2, "Minor": 7, "Patch": 2, "Build": 1 }, + "ProductVersion": { "Major": 2, "Minor": 7, "Patch": 2, "Build": 1 }, "FileFlagsMask": "3f", "FileFlags": "00", "FileOS": "40004", @@ -11,12 +11,12 @@ "StringFileInfo": { "CompanyName": "EvilFox", "FileDescription": "Navis 2 — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)", - "FileVersion": "2.7.1.0", + "FileVersion": "2.7.2.1", "InternalName": "Navis", "LegalCopyright": "Copyright (c) EvilFox", "OriginalFilename": "Navis.exe", "ProductName": "Navis", - "ProductVersion": "2.7.1.0", + "ProductVersion": "2.7.2.1", "Comments": "Open-source VPN/proxy client. https://evilfox.win/" }, "VarFileInfo": {