2 Commits
Author SHA1 Message Date
M4andCursor dc700f2bac 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>
2026-07-29 18:08:01 +03:00
M4andCursor 43882b95ad Release 2.7.2.4: macOS app icon, glaze UI and auto build numbers.
Add green N AppIcon to the .app bundle, keep WKWebView GUI stable, and stamp builds via BaseVersion/BuildNumber.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 17:57:18 +03:00
21 changed files with 183 additions and 45 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+3 -3
View File
@@ -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.2 -build 2.7.2.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.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 2.7.2 -build 2.7.2.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.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 2.7.2 -build 2.7.2.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.2 -arch universal
if errorlevel 1 exit /b 1
echo Built Mac GUI + CLI:
+25 -26
View File
@@ -10,10 +10,11 @@ import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/crgimenes/glaze"
"vpnclient/internal/apphost"
"vpnclient/internal/config"
"vpnclient/internal/core"
@@ -67,30 +68,31 @@ func main() {
}()
go a.AutoCheckUpdate()
if err := openAppWindow(uiURL); err != nil {
log.Printf("open UI: %v — откройте вручную: %s", err, uiURL)
fmt.Println(uiURL)
} else {
log.Printf("Navis UI: %s", uiURL)
}
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
_ = mgr.Disconnect()
_ = srv.Close()
}
func openAppWindow(uiURL string) error {
// Prefer Chromium-based --app window (looks like a desktop client).
for _, app := range []string{"Google Chrome", "Chromium", "Microsoft Edge", "Brave Browser", "Arc"} {
cmd := exec.Command("open", "-na", app, "--args", "--app="+uiURL, "--new-window")
if err := cmd.Start(); err == nil {
return nil
w, err := glaze.New(false)
if err != nil || w == nil {
log.Printf("webview unavailable (%v); opening default browser", err)
if err2 := exec.Command("open", uiURL).Run(); err2 != nil {
fatalf("Не удалось открыть интерфейс:\n%v\n%v\n%s", err, err2, uiURL)
}
log.Printf("Navis UI: %s", uiURL)
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
<-sig
_ = mgr.Disconnect()
_ = srv.Close()
return
}
// Fallback: default browser
return exec.Command("open", uiURL).Start()
defer func() {
_ = mgr.Disconnect()
_ = srv.Close()
w.Destroy()
}()
w.SetTitle("Navis 2")
w.SetSize(500, 900, glaze.HintNone)
w.Navigate(uiURL)
log.Printf("Navis UI: %s", uiURL)
w.Run()
}
func fatalf(format string, args ...any) {
@@ -99,6 +101,3 @@ func fatalf(format string, args ...any) {
_ = exec.Command("osascript", "-e", fmt.Sprintf(`display alert "Navis" message %q`, msg)).Run()
os.Exit(1)
}
// Ensure we keep a reference for potential future dock icon path helpers.
var _ = filepath.Separator
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
2.7.2+2
+1
View File
@@ -0,0 +1 @@
2.7.2.2
+3 -3
View File
@@ -1,6 +1,6 @@
{
"version": "2.7.2",
"notes": "2.7.2: исправлен вечный цикл обновления (только кнопка «Обновить», без автоустановки); номер сборки 2.7.2+N; можно «Пропустить эту версию».",
"notes": "Navis 2.7.2+2: иконка приложения (N), macOS AppIcon; обновление только по кнопке.",
"platform": "windows-amd64",
"os": "windows",
"arch": "amd64",
@@ -16,7 +16,7 @@
},
"darwin-arm64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
"sha256": "8fa7fcb40d4165d20d3f81840c84f729af034b54dd4698ea144c7145c85872eb",
"sha256": "0a6d7297e132f9835e2b82f5f28ec1d4e2a51fc070343f8c701e019ac57a2e86",
"os": "darwin",
"arch": "arm64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
@@ -39,4 +39,4 @@
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.app.zip"
}
}
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"version": "2.7.2",
"notes": "2.7.2: исправлен вечный цикл обновления (только кнопка «Обновить», без автоустановки); номер сборки 2.7.2+N; можно «Пропустить эту версию».",
"notes": "Navis 2.7.2+2: иконка приложения (N), macOS AppIcon; обновление только по кнопке.",
"platform": "windows-amd64",
"os": "windows",
"arch": "amd64",
@@ -16,7 +16,7 @@
},
"darwin-arm64": {
"url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis",
"sha256": "8fa7fcb40d4165d20d3f81840c84f729af034b54dd4698ea144c7145c85872eb",
"sha256": "0a6d7297e132f9835e2b82f5f28ec1d4e2a51fc070343f8c701e019ac57a2e86",
"os": "darwin",
"arch": "arm64",
"dmg_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-arm64/Navis.dmg",
@@ -39,4 +39,4 @@
"zip_url": "https://git.evilfox.cc/test2/navi/raw/branch/main/dist/navis-release/darwin-universal/Navis.app.zip"
}
}
}
}
+3 -1
View File
@@ -1,6 +1,6 @@
module vpnclient
go 1.25.0
go 1.26.5
require (
github.com/amnezia-vpn/amneziawg-go v0.2.19
@@ -12,7 +12,9 @@ require (
require (
github.com/anchore/go-lzo v0.1.0 // indirect
github.com/crgimenes/glaze v0.0.33 // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/ebitengine/purego v0.10.1 // indirect
github.com/elliotwutingfeng/asciiset v0.0.0-20260129054604-cfde2086bc57 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/uuid v1.6.0 // indirect
+4
View File
@@ -2,12 +2,16 @@ github.com/amnezia-vpn/amneziawg-go v0.2.19 h1:l3rOmrA4o5z38kpgnA5iSk1yOm7Cv3Aaf
github.com/amnezia-vpn/amneziawg-go v0.2.19/go.mod h1:aMgOk9MuX0xI7b5TKAYp8pLM54RlXcOPzDvYw3YEO5A=
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
github.com/crgimenes/glaze v0.0.33 h1:XZm2cFTSFSY7UarC4w/ziCMJ7Zwkce5Dh1NaO1Koj5Q=
github.com/crgimenes/glaze v0.0.33/go.mod h1:ZuCIST0F5U6wJLw5ZtqfcTIQA7LI/m2MHv7iMOBmu6U=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diskfs/go-diskfs v1.9.4 h1:0j2d7eG4IjyxL6+ChWbDPocdBCF6HQ4HBWU2WDYWVnc=
github.com/diskfs/go-diskfs v1.9.4/go.mod h1:TePJORO83Adh5pb2SqsxAwaP0fofFxKLkxctiS/9OQc=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elliotwutingfeng/asciiset v0.0.0-20260129054604-cfde2086bc57 h1:x5yxNrq8XffV/OoNUeFPM6hxHVi5OTspSTBxr/9pemg=
github.com/elliotwutingfeng/asciiset v0.0.0-20260129054604-cfde2086bc57/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
+9 -4
View File
@@ -123,7 +123,7 @@
font-size: .68rem;
font-weight: 700;
letter-spacing: .04em;
text-transform: uppercase;
text-transform: none;
color: var(--accent-deep);
background: var(--accent-soft);
border: 1px solid rgba(13,138,102,.18);
@@ -730,14 +730,17 @@
<header class="top">
<div class="logo" aria-hidden="true">
<svg viewBox="0 0 32 32" fill="none">
<path d="M8 26V6h4.2L20 18.4V6H24v20h-4.2L12 13.6V26H8z" fill="#f4fff9"/>
<path d="M22.5 8.2l3.2-1.1-1.1 3.2-5.6 5.6 1.6 1.6 5.6-5.6 3.2-1.1-1.1 3.2" fill="#b8f0d8" opacity=".9"/>
<!-- Bold N -->
<path fill="#f4fff9" d="M8 25.2V7.6h4.4l7.4 11.2V7.6H24v17.6h-4.4L12.2 14V25.2H8z"/>
<!-- Plug prongs on left stem -->
<rect x="9.05" y="4.2" width="1.55" height="3.6" rx=".45" fill="#f4fff9"/>
<rect x="11.55" y="4.2" width="1.55" height="3.6" rx=".45" fill="#f4fff9"/>
</svg>
</div>
<div class="brand-wrap">
<div class="brand-row">
<h1 class="brand">Navis</h1>
<span class="badge-ver">2.7.1</span>
<span class="badge-ver" id="badgeVer">2.7.2</span>
</div>
<p class="tagline">Быстрый клиент · Naive · Hy2 · AWG · Xray</p>
</div>
@@ -1192,6 +1195,8 @@
function renderUpdate(u, version) {
verLabel.textContent = "Navis v" + (version || "?");
const badge = $("badgeVer");
if (badge && version) badge.textContent = version;
if (!u) {
updateBanner.classList.remove("show");
return;
+1 -1
View File
@@ -22,7 +22,7 @@ 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
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"
+78
View File
@@ -0,0 +1,78 @@
#!/bin/bash
# Build Navis GUI for Apple Silicon (arm64).
set -euo pipefail
cd "$(dirname "$0")/.."
VERSION="$(python3 - <<'PY'
import re
from pathlib import Path
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="$(python3 - <<'PY'
import re
from pathlib import Path
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"
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
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"
codesign -s - --force "$OUT/Navis-cli"
fi
go build -o tools/packmac/packmac ./tools/packmac
tools/packmac/packmac \
-bin "$OUT/Navis" \
-out "$OUT" \
-version "$VERSION" \
-build "$FULL" \
-arch arm64
cp -f "$OUT/Navis.dmg" "$OUT/Navis-${FULL}-arm64.dmg"
cp -f "$OUT/Navis.app.zip" "$OUT/Navis-${FULL}-arm64.app.zip"
python3 - <<PY
import hashlib, json
from pathlib import Path
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'] = 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, '->', ver, 'sha', h[:12])
PY
echo ""
echo "Done: Navis ${VERSION}+${BUILD}"
ls -lh "$OUT"
+38
View File
@@ -130,9 +130,13 @@ func writeHdiutilDMG(dmgPath, stageDir, volume 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 {
return err
}
if err := os.MkdirAll(resDir, 0o755); err != nil {
return err
}
archPriority := architecturePriorityXML(arch)
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -142,6 +146,7 @@ func writeAppBundle(appRoot, binPath, version, bundleVersion, arch string) error
<key>CFBundleIdentifier</key><string>win.evilfox.navis</string>
<key>CFBundleName</key><string>Navis</string>
<key>CFBundleDisplayName</key><string>Navis</string>
<key>CFBundleIconFile</key><string>AppIcon</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>%s</string>
<key>CFBundleVersion</key><string>%s</string>
@@ -157,6 +162,11 @@ func writeAppBundle(appRoot, binPath, version, bundleVersion, arch string) error
if err := os.WriteFile(filepath.Join(appRoot, "Contents", "PkgInfo"), []byte("APPL????"), 0o644); err != nil {
return err
}
if icns := findAsset("navis.icns"); icns != "" {
if err := copyFile(icns, filepath.Join(resDir, "AppIcon.icns"), 0o644); err != nil {
return fmt.Errorf("app icon: %w", err)
}
}
dest := filepath.Join(macOSDir, "Navis")
if err := copyFile(binPath, dest, 0o755); err != nil {
return err
@@ -384,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 {
+14 -4
View File
@@ -1,7 +1,17 @@
{
"FixedFileInfo": {
"FileVersion": { "Major": 2, "Minor": 7, "Patch": 2, "Build": 1 },
"ProductVersion": { "Major": 2, "Minor": 7, "Patch": 2, "Build": 1 },
"FileVersion": {
"Major": 2,
"Minor": 7,
"Patch": 2,
"Build": 2
},
"ProductVersion": {
"Major": 2,
"Minor": 7,
"Patch": 2,
"Build": 2
},
"FileFlagsMask": "3f",
"FileFlags": "00",
"FileOS": "40004",
@@ -11,12 +21,12 @@
"StringFileInfo": {
"CompanyName": "EvilFox",
"FileDescription": "Navis 2 — VPN client (Naive / Hy2 / AWG / VLESS / VMess / Trojan)",
"FileVersion": "2.7.2.1",
"FileVersion": "2.7.2.2",
"InternalName": "Navis",
"LegalCopyright": "Copyright (c) EvilFox",
"OriginalFilename": "Navis.exe",
"ProductName": "Navis",
"ProductVersion": "2.7.2.1",
"ProductVersion": "2.7.2.2",
"Comments": "Open-source VPN/proxy client. https://evilfox.win/"
},
"VarFileInfo": {