Merge origin/main: Navis 2.7.2+2 with app icon and update UX.
Combine remote update check/skip flow with green N AppIcon packaging for macOS. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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`.
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
+9
-26
@@ -1,21 +1,7 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
|
||||
REM --- bump build number ---
|
||||
for /f "usebackq delims=" %%i in (`python -c "from pathlib import Path; import re; t=Path('internal/update/version.go').read_text(); print(re.search(r'BaseVersion\\s*=\\s*\"([^\"]+)\"', t).group(1))"`) do set BASE_VERSION=%%i
|
||||
if not exist buildnum.txt echo 0>buildnum.txt
|
||||
set /p BUILD_NUM=<buildnum.txt
|
||||
set /a BUILD_NUM+=1
|
||||
echo %BUILD_NUM%>buildnum.txt
|
||||
set FULL_VERSION=%BASE_VERSION%.%BUILD_NUM%
|
||||
echo Building macOS Navis %FULL_VERSION% ...
|
||||
|
||||
python -c "from pathlib import Path; import re; p=Path('internal/update/version.go'); t=p.read_text(); p.write_text(re.sub(r'(var BuildNumber = \")[^\"]*(\")', r'\g<1>%BUILD_NUM%\2', t))"
|
||||
python -c "import json; from pathlib import Path; maj,mi,pa=map(int,'%BASE_VERSION%'.split('.')[:3]); b=int('%BUILD_NUM%'); p=Path('versioninfo.json'); d=json.loads(p.read_text()); d['FixedFileInfo']['FileVersion']={'Major':maj,'Minor':mi,'Patch':pa,'Build':b}; d['FixedFileInfo']['ProductVersion']=d['FixedFileInfo']['FileVersion']; d['StringFileInfo']['FileVersion']=f'{maj}.{mi}.{pa}.{b}'; d['StringFileInfo']['ProductVersion']=d['StringFileInfo']['FileVersion']; p.write_text(json.dumps(d,indent=2)+chr(10))"
|
||||
|
||||
set LDFLAGS=-s -w -X vpnclient/internal/update.BuildNumber=%BUILD_NUM%
|
||||
|
||||
mkdir "dist\navis-release\darwin-arm64" 2>nul
|
||||
mkdir "dist\navis-release\darwin-amd64" 2>nul
|
||||
mkdir "dist\navis-release\darwin-universal" 2>nul
|
||||
@@ -23,18 +9,16 @@ mkdir "dist\navis-release\darwin-universal" 2>nul
|
||||
set CGO_ENABLED=0
|
||||
set GOOS=darwin
|
||||
set GOARCH=arm64
|
||||
go build -ldflags="%LDFLAGS%" -trimpath -o "dist\navis-release\darwin-arm64\Navis" .\cmd\vpnapp
|
||||
go build -ldflags="-s -w" -o "dist\navis-release\darwin-arm64\Navis" .\cmd\vpnapp
|
||||
if errorlevel 1 exit /b 1
|
||||
go build -ldflags="%LDFLAGS%" -trimpath -o "dist\navis-release\darwin-arm64\Navis-cli" .\cmd\vpnclient
|
||||
go build -ldflags="-s -w" -o "dist\navis-release\darwin-arm64\Navis-cli" .\cmd\vpnclient
|
||||
if errorlevel 1 exit /b 1
|
||||
echo %FULL_VERSION%> "dist\navis-release\darwin-arm64\VERSION"
|
||||
|
||||
set GOARCH=amd64
|
||||
go build -ldflags="%LDFLAGS%" -trimpath -o "dist\navis-release\darwin-amd64\Navis" .\cmd\vpnapp
|
||||
go build -ldflags="-s -w" -o "dist\navis-release\darwin-amd64\Navis" .\cmd\vpnapp
|
||||
if errorlevel 1 exit /b 1
|
||||
go build -ldflags="%LDFLAGS%" -trimpath -o "dist\navis-release\darwin-amd64\Navis-cli" .\cmd\vpnclient
|
||||
go build -ldflags="-s -w" -o "dist\navis-release\darwin-amd64\Navis-cli" .\cmd\vpnclient
|
||||
if errorlevel 1 exit /b 1
|
||||
echo %FULL_VERSION%> "dist\navis-release\darwin-amd64\VERSION"
|
||||
|
||||
set GOOS=
|
||||
set GOARCH=
|
||||
@@ -46,19 +30,18 @@ tools\mklipo\mklipo.exe "dist\navis-release\darwin-amd64\Navis" "dist\navis-rele
|
||||
if errorlevel 1 exit /b 1
|
||||
tools\mklipo\mklipo.exe "dist\navis-release\darwin-amd64\Navis-cli" "dist\navis-release\darwin-arm64\Navis-cli" "dist\navis-release\darwin-universal\Navis-cli"
|
||||
if errorlevel 1 exit /b 1
|
||||
echo %FULL_VERSION%> "dist\navis-release\darwin-universal\VERSION"
|
||||
|
||||
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 %FULL_VERSION% -build %BUILD_NUM% -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.2 -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 %FULL_VERSION% -build %BUILD_NUM% -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.2 -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 %FULL_VERSION% -build %BUILD_NUM% -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.2 -arch universal
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo Built Mac GUI + CLI %FULL_VERSION%:
|
||||
echo Built Mac GUI + CLI:
|
||||
dir "dist\navis-release\darwin-universal"
|
||||
dir "dist\navis-release\darwin-arm64"
|
||||
dir "dist\navis-release\darwin-amd64"
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
|
||||
REM --- bump build number ---
|
||||
for /f "usebackq delims=" %%i in (`python -c "from pathlib import Path; import re; t=Path('internal/update/version.go').read_text(); print(re.search(r'BaseVersion\\s*=\\s*\"([^\"]+)\"', t).group(1))"`) do set BASE_VERSION=%%i
|
||||
if not exist buildnum.txt echo 0>buildnum.txt
|
||||
set /p BUILD_NUM=<buildnum.txt
|
||||
set /a BUILD_NUM+=1
|
||||
echo %BUILD_NUM%>buildnum.txt
|
||||
set FULL_VERSION=%BASE_VERSION%.%BUILD_NUM%
|
||||
echo Building Windows Navis %FULL_VERSION% ...
|
||||
|
||||
python -c "from pathlib import Path; import re; p=Path('internal/update/version.go'); t=p.read_text(); p.write_text(re.sub(r'(var BuildNumber = \")[^\"]*(\")', r'\g<1>%BUILD_NUM%\2', t))"
|
||||
python -c "import json; from pathlib import Path; maj,mi,pa=map(int,'%BASE_VERSION%'.split('.')[:3]); b=int('%BUILD_NUM%'); p=Path('versioninfo.json'); d=json.loads(p.read_text()); d['FixedFileInfo']['FileVersion']={'Major':maj,'Minor':mi,'Patch':pa,'Build':b}; d['FixedFileInfo']['ProductVersion']=d['FixedFileInfo']['FileVersion']; d['StringFileInfo']['FileVersion']=f'{maj}.{mi}.{pa}.{b}'; d['StringFileInfo']['ProductVersion']=d['StringFileInfo']['FileVersion']; p.write_text(json.dumps(d,indent=2)+chr(10))"
|
||||
|
||||
echo Generating Windows icon resources...
|
||||
go run ./tools/mkico
|
||||
if errorlevel 1 exit /b 1
|
||||
@@ -26,21 +14,16 @@ if errorlevel 1 (
|
||||
goversioninfo -64 -icon assets\navis.ico -manifest assets\app.manifest -o cmd\vpnapp\resource.syso versioninfo.json
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
set LDFLAGS=-H windowsgui -s -w -X vpnclient/internal/update.BuildNumber=%BUILD_NUM%
|
||||
|
||||
echo Building Navis GUI and CLI...
|
||||
go build -ldflags="%LDFLAGS%" -trimpath -o Navis.exe ./cmd/vpnapp
|
||||
go build -ldflags="-H windowsgui -s -w" -trimpath -o Navis.exe ./cmd/vpnapp
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
go build -ldflags="-s -w -X vpnclient/internal/update.BuildNumber=%BUILD_NUM%" -trimpath -o vpnclient.exe ./cmd/vpnclient
|
||||
go build -ldflags="-s -w" -trimpath -o vpnclient.exe ./cmd/vpnclient
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo %FULL_VERSION%> Navis.version
|
||||
|
||||
echo.
|
||||
echo Done: Navis %FULL_VERSION%
|
||||
echo Done:
|
||||
echo Navis.exe
|
||||
echo vpnclient.exe
|
||||
echo Navis.version
|
||||
echo assets\navis.ico
|
||||
endlocal
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
4
|
||||
+16
-11
@@ -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 {
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
||||
2.7.2.4
|
||||
2.7.2+2
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.7.2.4
|
||||
2.7.2.2
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Vendored
+7
-7
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"version": "2.7.2.4",
|
||||
"notes": "Navis 2.7.2.4",
|
||||
"version": "2.7.2",
|
||||
"notes": "Navis 2.7.2+2: иконка приложения (N), macOS AppIcon; обновление только по кнопке.",
|
||||
"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": "4f1f34f57ed7cd588f9d62f24d074041be3f3b1504f18e8203c953536236c381",
|
||||
"sha256": "0a6d7297e132f9835e2b82f5f28ec1d4e2a51fc070343f8c701e019ac57a2e86",
|
||||
"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,7 +32,7 @@
|
||||
},
|
||||
"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",
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+7
-7
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"version": "2.7.2.4",
|
||||
"notes": "Navis 2.7.2.4",
|
||||
"version": "2.7.2",
|
||||
"notes": "Navis 2.7.2+2: иконка приложения (N), macOS AppIcon; обновление только по кнопке.",
|
||||
"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": "4f1f34f57ed7cd588f9d62f24d074041be3f3b1504f18e8203c953536236c381",
|
||||
"sha256": "0a6d7297e132f9835e2b82f5f28ec1d4e2a51fc070343f8c701e019ac57a2e86",
|
||||
"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,7 +32,7 @@
|
||||
},
|
||||
"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",
|
||||
|
||||
+14
-9
@@ -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 {
|
||||
|
||||
@@ -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 @@
|
||||
<div class="update-banner" id="updateBanner">
|
||||
<strong id="updateTitle">Доступно обновление</strong>
|
||||
<p id="updateNotes"></p>
|
||||
<button class="action primary" id="updateBtn" type="button">Обновить и перезапустить</button>
|
||||
<div class="update-actions">
|
||||
<button class="action primary" id="updateBtn" type="button">Обновить</button>
|
||||
<button class="action secondary" id="skipUpdateBtn" type="button">Пропустить эту версию</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header class="top">
|
||||
@@ -941,7 +953,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;
|
||||
@@ -1178,10 +1201,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");
|
||||
}
|
||||
@@ -1284,7 +1314,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;
|
||||
});
|
||||
}
|
||||
@@ -1449,6 +1479,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 });
|
||||
|
||||
+52
-25
@@ -16,9 +16,27 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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 = 2
|
||||
|
||||
// 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"`
|
||||
@@ -87,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 {
|
||||
@@ -106,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 a<b, 0 if equal (semver-ish dotted ints).
|
||||
// Compare returns 1 if a>b, -1 if a<b, 0 if equal.
|
||||
//
|
||||
// Update rule: only product semver (major.minor.patch) counts. Build metadata
|
||||
// ("+N", "-suffix", or a 4th dotted component) is ignored so a just-updated app
|
||||
// on the same product version is never treated as outdated vs the same feed.
|
||||
func Compare(a, b string) int {
|
||||
as := splitVer(a)
|
||||
bs := splitVer(b)
|
||||
n := len(as)
|
||||
if len(bs) > 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 {
|
||||
@@ -146,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)
|
||||
}
|
||||
@@ -169,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)
|
||||
@@ -192,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 {
|
||||
@@ -224,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 "<exe>.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 {
|
||||
@@ -234,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")
|
||||
@@ -247,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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package update
|
||||
|
||||
import "strings"
|
||||
|
||||
// BaseVersion is the marketing / release version (MAJOR.MINOR.PATCH).
|
||||
const BaseVersion = "2.7.2"
|
||||
|
||||
// BuildNumber is the incremental build counter.
|
||||
// Override at link time:
|
||||
//
|
||||
// -ldflags "-X vpnclient/internal/update.BuildNumber=42"
|
||||
//
|
||||
// Source default is bumped by build scripts via buildnum.txt.
|
||||
var BuildNumber = "4"
|
||||
|
||||
// CurrentVersion is BaseVersion.BuildNumber, e.g. "2.7.2.1".
|
||||
// Set in init so -ldflags BuildNumber is applied first.
|
||||
var CurrentVersion string
|
||||
|
||||
func init() {
|
||||
CurrentVersion = FullVersion()
|
||||
}
|
||||
|
||||
// FullVersion returns "MAJOR.MINOR.PATCH.BUILD".
|
||||
func FullVersion() string {
|
||||
b := strings.TrimSpace(BuildNumber)
|
||||
if b == "" {
|
||||
b = "0"
|
||||
}
|
||||
return BaseVersion + "." + b
|
||||
}
|
||||
@@ -1,67 +1,39 @@
|
||||
#!/bin/bash
|
||||
# Build Navis GUI for Apple Silicon (arm64) with auto-incremented build number.
|
||||
# Build Navis GUI for Apple Silicon (arm64).
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
BASE_VERSION="$(python3 - <<'PY'
|
||||
VERSION="$(python3 - <<'PY'
|
||||
import re
|
||||
from pathlib import Path
|
||||
t = Path('internal/update/version.go').read_text()
|
||||
m = re.search(r'BaseVersion\s*=\s*"([^"]+)"', t)
|
||||
t = Path('internal/update/update.go').read_text()
|
||||
m = re.search(r'CurrentVersion\s*=\s*"([^"]+)"', t)
|
||||
print(m.group(1) if m else '0.0.0')
|
||||
PY
|
||||
)"
|
||||
|
||||
BUILD_NUM="$(tr -d '[:space:]' < buildnum.txt 2>/dev/null || echo 0)"
|
||||
if ! [[ "$BUILD_NUM" =~ ^[0-9]+$ ]]; then BUILD_NUM=0; fi
|
||||
BUILD_NUM=$((BUILD_NUM + 1))
|
||||
echo "$BUILD_NUM" > buildnum.txt
|
||||
|
||||
FULL_VERSION="${BASE_VERSION}.${BUILD_NUM}"
|
||||
echo "Building Navis ${FULL_VERSION} (arm64)..."
|
||||
|
||||
# Keep source BuildNumber in sync for non-ldflags runs / IDE
|
||||
python3 - <<PY
|
||||
from pathlib import Path
|
||||
p = Path('internal/update/version.go')
|
||||
t = p.read_text()
|
||||
BUILD="$(python3 - <<'PY'
|
||||
import re
|
||||
t2 = re.sub(r'(var BuildNumber = ")[^"]*(")', r'\g<1>${BUILD_NUM}\2', t)
|
||||
p.write_text(t2)
|
||||
PY
|
||||
|
||||
# versioninfo.json (Windows metadata; also documents build)
|
||||
python3 - <<PY
|
||||
import json, re
|
||||
from pathlib import Path
|
||||
maj, minor, patch = [int(x) for x in "${BASE_VERSION}".split(".")[:3]]
|
||||
build = int("${BUILD_NUM}")
|
||||
full = "${FULL_VERSION}"
|
||||
p = Path('versioninfo.json')
|
||||
d = json.loads(p.read_text())
|
||||
d['FixedFileInfo']['FileVersion'] = {"Major": maj, "Minor": minor, "Patch": patch, "Build": build}
|
||||
d['FixedFileInfo']['ProductVersion'] = {"Major": maj, "Minor": minor, "Patch": patch, "Build": build}
|
||||
d['StringFileInfo']['FileVersion'] = f"{full}.0" if full.count('.')==3 else full
|
||||
d['StringFileInfo']['ProductVersion'] = d['StringFileInfo']['FileVersion']
|
||||
# Prefer 4-part a.b.c.d
|
||||
d['StringFileInfo']['FileVersion'] = f"{maj}.{minor}.{patch}.{build}"
|
||||
d['StringFileInfo']['ProductVersion'] = f"{maj}.{minor}.{patch}.{build}"
|
||||
p.write_text(json.dumps(d, indent=2) + "\n")
|
||||
print('versioninfo.json ->', d['StringFileInfo']['FileVersion'])
|
||||
t = Path('internal/update/update.go').read_text()
|
||||
m = re.search(r'const BuildNumber\s*=\s*(\d+)', t)
|
||||
print(m.group(1) if m else '0')
|
||||
PY
|
||||
)"
|
||||
FULL="${VERSION}.${BUILD}"
|
||||
echo "Building Navis ${VERSION}+${BUILD} (arm64)..."
|
||||
|
||||
OUT="dist/navis-release/darwin-arm64"
|
||||
mkdir -p "$OUT"
|
||||
LDFLAGS="-s -w -X vpnclient/internal/update.BuildNumber=${BUILD_NUM}"
|
||||
LDFLAGS="-s -w"
|
||||
|
||||
export PATH="/tmp/go-sdk/go/bin:/usr/local/go/bin:$PATH"
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \
|
||||
go build -ldflags="$LDFLAGS" -trimpath -o "$OUT/Navis" ./cmd/vpnapp
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \
|
||||
go build -ldflags="$LDFLAGS" -trimpath -o "$OUT/Navis-cli" ./cmd/vpnclient
|
||||
|
||||
# Stamp plain-text version next to binaries
|
||||
printf '%s\n' "$FULL_VERSION" > "$OUT/VERSION"
|
||||
printf '%s\n' "$FULL_VERSION" > "$OUT/Navis.version"
|
||||
printf '%s\n' "$FULL" > "$OUT/VERSION"
|
||||
printf '%s\n' "${VERSION}+${BUILD}" > "$OUT/Navis.version"
|
||||
|
||||
if command -v codesign >/dev/null 2>&1; then
|
||||
codesign -s - --force "$OUT/Navis"
|
||||
@@ -72,35 +44,35 @@ go build -o tools/packmac/packmac ./tools/packmac
|
||||
tools/packmac/packmac \
|
||||
-bin "$OUT/Navis" \
|
||||
-out "$OUT" \
|
||||
-version "$FULL_VERSION" \
|
||||
-build "$BUILD_NUM" \
|
||||
-version "$VERSION" \
|
||||
-build "$FULL" \
|
||||
-arch arm64
|
||||
|
||||
# Rename release copies with version in filename (keep unversioned too)
|
||||
cp -f "$OUT/Navis.dmg" "$OUT/Navis-${FULL_VERSION}-arm64.dmg"
|
||||
cp -f "$OUT/Navis.app.zip" "$OUT/Navis-${FULL_VERSION}-arm64.app.zip"
|
||||
cp -f "$OUT/Navis.dmg" "$OUT/Navis-${FULL}-arm64.dmg"
|
||||
cp -f "$OUT/Navis.app.zip" "$OUT/Navis-${FULL}-arm64.app.zip"
|
||||
|
||||
# update.json
|
||||
python3 - <<PY
|
||||
import hashlib, json
|
||||
from pathlib import Path
|
||||
full = "${FULL_VERSION}"
|
||||
ver = "${VERSION}"
|
||||
build = "${BUILD}"
|
||||
full = "${FULL}"
|
||||
binp = Path('dist/navis-release/darwin-arm64/Navis')
|
||||
h = hashlib.sha256(binp.read_bytes()).hexdigest()
|
||||
notes = f"Navis {ver}+{build}: иконка приложения (N), macOS AppIcon; обновление только по кнопке."
|
||||
for p in [Path('dist/update.json'), Path('dist/navis-release/update.json')]:
|
||||
if not p.exists():
|
||||
continue
|
||||
d = json.loads(p.read_text())
|
||||
d['version'] = full
|
||||
d['notes'] = f'Navis {full}'
|
||||
d['version'] = ver
|
||||
d['notes'] = notes
|
||||
plats = d.setdefault('platforms', {})
|
||||
if 'darwin-arm64' in plats:
|
||||
plats['darwin-arm64']['sha256'] = h
|
||||
p.write_text(json.dumps(d, ensure_ascii=False, indent=2) + '\n')
|
||||
print('updated', p, '->', full)
|
||||
print('updated', p, '->', ver, 'sha', h[:12])
|
||||
PY
|
||||
|
||||
echo ""
|
||||
echo "Done: Navis ${FULL_VERSION}"
|
||||
echo "Done: Navis ${VERSION}+${BUILD}"
|
||||
ls -lh "$OUT"
|
||||
file "$OUT/Navis.dmg"
|
||||
|
||||
@@ -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…"
|
||||
|
||||
+7
-7
@@ -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,7 +32,7 @@
|
||||
},
|
||||
"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",
|
||||
|
||||
+39
-49
@@ -19,33 +19,25 @@ import (
|
||||
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 (e.g. 2.7.2.3)")
|
||||
build := flag.String("build", "", "CFBundleVersion build number (default: last segment of -version)")
|
||||
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 <Navis> -out <dir> [-version 2.7.2.3] [-build 3] [-arch arm64]")
|
||||
fmt.Fprintln(os.Stderr, "usage: packmac -bin <Navis> -out <dir> [-version 2.7.2] [-build 2.7.2.1] [-arch arm64]")
|
||||
os.Exit(2)
|
||||
}
|
||||
buildNo := *build
|
||||
if buildNo == "" {
|
||||
buildNo = lastVersionSegment(*version)
|
||||
bundleVer := *build
|
||||
if bundleVer == "" {
|
||||
bundleVer = *version
|
||||
}
|
||||
if err := run(*bin, *outDir, *version, buildNo, *arch); err != nil {
|
||||
if err := run(*bin, *outDir, *version, bundleVer, *arch); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "packmac: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func lastVersionSegment(v string) string {
|
||||
v = strings.TrimSpace(v)
|
||||
if i := strings.LastIndex(v, "."); i >= 0 && i+1 < len(v) {
|
||||
return v[i+1:]
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func run(binPath, outDir, version, buildNo, arch string) error {
|
||||
func run(binPath, outDir, version, bundleVersion, arch string) error {
|
||||
if err := os.MkdirAll(outDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -56,7 +48,7 @@ func run(binPath, outDir, version, buildNo, arch string) error {
|
||||
defer os.RemoveAll(stage)
|
||||
|
||||
appRoot := filepath.Join(stage, "Navis.app")
|
||||
if err := writeAppBundle(appRoot, binPath, version, buildNo, arch); err != nil {
|
||||
if err := writeAppBundle(appRoot, binPath, version, bundleVersion, arch); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := signAppBundle(appRoot); err != nil {
|
||||
@@ -136,7 +128,7 @@ func writeHdiutilDMG(dmgPath, stageDir, volume string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeAppBundle(appRoot, binPath, version, buildNo, arch string) error {
|
||||
func writeAppBundle(appRoot, binPath, version, bundleVersion, arch string) error {
|
||||
macOSDir := filepath.Join(appRoot, "Contents", "MacOS")
|
||||
resDir := filepath.Join(appRoot, "Contents", "Resources")
|
||||
if err := os.MkdirAll(macOSDir, 0o755); err != nil {
|
||||
@@ -163,7 +155,7 @@ func writeAppBundle(appRoot, binPath, version, buildNo, arch string) error {
|
||||
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>%s
|
||||
</dict>
|
||||
</plist>
|
||||
`, version, buildNo, archPriority)
|
||||
`, version, bundleVersion, archPriority)
|
||||
if err := os.WriteFile(filepath.Join(appRoot, "Contents", "Info.plist"), []byte(plist), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -179,39 +171,9 @@ func writeAppBundle(appRoot, binPath, version, buildNo, arch string) error {
|
||||
if err := copyFile(binPath, dest, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
// Embed human-readable version next to the binary inside the .app
|
||||
_ = os.WriteFile(filepath.Join(macOSDir, "VERSION"), []byte(version+"\n"), 0o644)
|
||||
return nil
|
||||
}
|
||||
|
||||
// findAsset locates a file under assets/ relative to cwd or the packmac binary.
|
||||
func findAsset(name string) string {
|
||||
candidates := []string{
|
||||
filepath.Join("assets", name),
|
||||
}
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
dir := filepath.Dir(exe)
|
||||
candidates = append(candidates,
|
||||
filepath.Join(dir, "..", "..", "assets", name),
|
||||
filepath.Join(dir, "..", "assets", name),
|
||||
filepath.Join(dir, "assets", name),
|
||||
)
|
||||
}
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
candidates = append(candidates, filepath.Join(wd, "assets", name))
|
||||
}
|
||||
for _, c := range candidates {
|
||||
if st, err := os.Stat(c); err == nil && !st.IsDir() {
|
||||
abs, err := filepath.Abs(c)
|
||||
if err == nil {
|
||||
return abs
|
||||
}
|
||||
return c
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func signAppBundle(appRoot string) error {
|
||||
if _, err := exec.LookPath("codesign"); err != nil {
|
||||
return nil
|
||||
@@ -432,6 +394,34 @@ func mustStat(path string) os.FileInfo {
|
||||
return fi
|
||||
}
|
||||
|
||||
// findAsset locates a file under assets/ relative to cwd or the packmac binary.
|
||||
func findAsset(name string) string {
|
||||
candidates := []string{
|
||||
filepath.Join("assets", name),
|
||||
}
|
||||
if exe, err := os.Executable(); err == nil {
|
||||
dir := filepath.Dir(exe)
|
||||
candidates = append(candidates,
|
||||
filepath.Join(dir, "..", "..", "assets", name),
|
||||
filepath.Join(dir, "..", "assets", name),
|
||||
filepath.Join(dir, "assets", name),
|
||||
)
|
||||
}
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
candidates = append(candidates, filepath.Join(wd, "assets", name))
|
||||
}
|
||||
for _, c := range candidates {
|
||||
if st, err := os.Stat(c); err == nil && !st.IsDir() {
|
||||
abs, err := filepath.Abs(c)
|
||||
if err == nil {
|
||||
return abs
|
||||
}
|
||||
return c
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func copyFile(src, dst string, mode os.FileMode) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
|
||||
+5
-5
@@ -4,13 +4,13 @@
|
||||
"Major": 2,
|
||||
"Minor": 7,
|
||||
"Patch": 2,
|
||||
"Build": 4
|
||||
"Build": 2
|
||||
},
|
||||
"ProductVersion": {
|
||||
"Major": 2,
|
||||
"Minor": 7,
|
||||
"Patch": 2,
|
||||
"Build": 4
|
||||
"Build": 2
|
||||
},
|
||||
"FileFlagsMask": "3f",
|
||||
"FileFlags": "00",
|
||||
@@ -20,13 +20,13 @@
|
||||
},
|
||||
"StringFileInfo": {
|
||||
"CompanyName": "EvilFox",
|
||||
"FileDescription": "Navis 2 \u2014 VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)",
|
||||
"FileVersion": "2.7.2.4",
|
||||
"FileDescription": "Navis 2 — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)",
|
||||
"FileVersion": "2.7.2.2",
|
||||
"InternalName": "Navis",
|
||||
"LegalCopyright": "Copyright (c) EvilFox",
|
||||
"OriginalFilename": "Navis.exe",
|
||||
"ProductName": "Navis",
|
||||
"ProductVersion": "2.7.2.4",
|
||||
"ProductVersion": "2.7.2.2",
|
||||
"Comments": "Open-source VPN/proxy client. https://evilfox.win/"
|
||||
},
|
||||
"VarFileInfo": {
|
||||
|
||||
Reference in New Issue
Block a user