Release 3.8.2.13: iOS Packet Tunnel client and Clash URL subscriptions.
ci / test (macos-latest) (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run

Add SwiftUI + Network Extension (LibXray/hev), parse Clash Meta YAML from subscription URLs into vless/trojan/hy2 share links, keep the selected server on connect, and ship Navis-3.8.2.13.ipa with versioned changelog.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-08-01 22:46:32 +03:00
co-authored by Cursor
parent 8b564110d5
commit 7ceb3a5148
44 changed files with 4165 additions and 72 deletions
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env bash
# Build Navis iOS device IPA (+ optional simulator zip).
#
# Usage:
# ./scripts/build-ios.sh
# # device IPA (unsigned if no Team) + simulator zip
# NAVIS_IOS_TEAM_ID=XXXXXXXXXX ./scripts/build-ios.sh
# # signed archive + exported IPA
# NAVIS_IOS_SKIP_SIMULATOR=1 ./scripts/build-ios.sh
# # device only
#
# Optional:
# NAVIS_IOS_SIGN_IDENTITY="Apple Development: …"
# NAVIS_IOS_EXPORT_METHOD=development|ad-hoc|enterprise|app-store
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IOS="$ROOT/ios"
OUT="$ROOT/dist/navis-release/ios"
PROJECT="$IOS/Navis.xcodeproj"
SCHEME=Navis
CONFIG=Release
# Keep VERSION_FULL in sync with internal/update/update.go (also via sync-version.py).
VERSION_FULL="$(
python3 - <<PY
import re
from pathlib import Path
text = Path(r"$ROOT/internal/update/update.go").read_text(encoding="utf-8")
ver = re.search(r'CurrentVersion\s*=\s*"([^"]+)"', text).group(1)
build = re.search(r"const BuildNumber\s*=\s*(\d+)", text).group(1)
print(f"{ver}.{build}")
PY
)"
# Fail early if parsing broke.
[[ "$VERSION_FULL" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "failed to parse version from update.go" >&2
exit 1
}
echo "==> Navis iOS build $VERSION_FULL"
# Ensure Info.plist stays aligned with update.go.
python3 "$ROOT/scripts/sync-version.py"
# Ensure LibXray + Hev are present
"$ROOT/scripts/fetch-ios-cores.sh"
mkdir -p "$OUT"
find "$OUT" -mindepth 1 -maxdepth 1 ! -name '.gitkeep' -exec rm -rf {} +
TEAM="${NAVIS_IOS_TEAM_ID:-}"
EXPORT_METHOD="${NAVIS_IOS_EXPORT_METHOD:-development}"
DERIVED="${TMPDIR:-/tmp}/navis-ios-derived"
rm -rf "$DERIVED"
mkdir -p "$DERIVED"
package_ipa() {
local app_path="$1"
local ipa_path="$2"
local stage
stage="$(mktemp -d)"
mkdir -p "$stage/Payload"
ditto "$app_path" "$stage/Payload/Navis.app"
(
cd "$stage"
zip -qry "$ipa_path" Payload
)
rm -rf "$stage"
}
# --- Device (iphoneos) ---
echo "==> Building iOS device ($CONFIG, sdk=iphoneos)"
DEV_ARGS=(
-project "$PROJECT"
-scheme "$SCHEME"
-configuration "$CONFIG"
-destination "generic/platform=iOS"
-derivedDataPath "$DERIVED"
-sdk iphoneos
ONLY_ACTIVE_ARCH=NO
)
if [[ -n "$TEAM" ]]; then
DEV_ARGS+=(DEVELOPMENT_TEAM="$TEAM")
if [[ -n "${NAVIS_IOS_SIGN_IDENTITY:-}" ]]; then
DEV_ARGS+=(CODE_SIGN_IDENTITY="$NAVIS_IOS_SIGN_IDENTITY")
fi
echo "==> Archiving (Team=$TEAM)"
ARCHIVE="$OUT/Navis.xcarchive"
xcodebuild "${DEV_ARGS[@]}" -archivePath "$ARCHIVE" archive
EXPORT_PLIST="$OUT/ExportOptions.plist"
cat >"$EXPORT_PLIST" <<EOF
<?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">
<plist version="1.0">
<dict>
<key>method</key>
<string>${EXPORT_METHOD}</string>
<key>teamID</key>
<string>${TEAM}</string>
<key>signingStyle</key>
<string>automatic</string>
<key>compileBitcode</key>
<false/>
<key>stripSwiftSymbols</key>
<true/>
</dict>
</plist>
EOF
EXPORT_DIR="$OUT/export"
rm -rf "$EXPORT_DIR"
mkdir -p "$EXPORT_DIR"
echo "==> Exporting signed IPA (method=$EXPORT_METHOD)"
xcodebuild -exportArchive \
-archivePath "$ARCHIVE" \
-exportPath "$EXPORT_DIR" \
-exportOptionsPlist "$EXPORT_PLIST"
IPA="$(find "$EXPORT_DIR" -name '*.ipa' | head -n1 || true)"
if [[ -n "${IPA:-}" ]]; then
cp "$IPA" "$OUT/Navis-${VERSION_FULL}.ipa"
cp "$IPA" "$OUT/Navis.ipa"
echo "wrote $OUT/Navis-${VERSION_FULL}.ipa (signed)"
else
echo "error: signed export produced no .ipa" >&2
ls -la "$EXPORT_DIR" || true
exit 1
fi
else
DEV_ARGS+=(
CODE_SIGNING_ALLOWED=NO
CODE_SIGNING_REQUIRED=NO
CODE_SIGN_IDENTITY=-
EXPAND_SDK_VARIABLES=YES
)
xcodebuild "${DEV_ARGS[@]}" build
DEV_APP="$(find "$DERIVED/Build/Products" -path '*iphoneos/Navis.app' -type d | head -n1 || true)"
if [[ -z "${DEV_APP:-}" || ! -d "$DEV_APP" ]]; then
echo "error: device Navis.app not found under $DERIVED" >&2
find "$DERIVED/Build/Products" -name 'Navis.app' -type d 2>/dev/null || true
exit 1
fi
cp -R "$DEV_APP" "$OUT/Navis-device.app"
package_ipa "$DEV_APP" "$OUT/Navis-${VERSION_FULL}.ipa"
cp "$OUT/Navis-${VERSION_FULL}.ipa" "$OUT/Navis.ipa"
echo "wrote $OUT/Navis-device.app"
echo "wrote $OUT/Navis-${VERSION_FULL}.ipa (UNSIGNED — re-sign before install)"
echo "wrote $OUT/Navis.ipa"
fi
# --- Simulator (optional) ---
if [[ "${NAVIS_IOS_SKIP_SIMULATOR:-}" != "1" ]]; then
echo "==> Building iOS Simulator ($CONFIG)"
SIM_ARGS=(
-project "$PROJECT"
-scheme "$SCHEME"
-configuration "$CONFIG"
-destination "generic/platform=iOS Simulator"
-derivedDataPath "$DERIVED"
ONLY_ACTIVE_ARCH=NO
)
if [[ -n "$TEAM" ]]; then
SIM_ARGS+=(DEVELOPMENT_TEAM="$TEAM")
else
SIM_ARGS+=(CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO)
fi
xcodebuild "${SIM_ARGS[@]}" build
SIM_APP="$(find "$DERIVED/Build/Products" -path '*iphonesimulator/Navis.app' -type d | head -n1 || true)"
if [[ -n "${SIM_APP:-}" && -d "$SIM_APP" ]]; then
cp -R "$SIM_APP" "$OUT/Navis-simulator.app"
(
cd "$OUT"
ditto -c -k --sequesterRsrc --keepParent Navis-simulator.app "Navis-${VERSION_FULL}-simulator.app.zip"
)
echo "wrote $OUT/Navis-simulator.app"
fi
fi
echo "done → $OUT"
ls -lh "$OUT"/*.ipa "$OUT"/*.app 2>/dev/null || ls -lh "$OUT"
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Increment BuildNumber in update.go, then sync versioninfo / macOS bat / Android."""
"""Increment BuildNumber in update.go, then sync versioninfo / macOS bat / Android / iOS."""
from __future__ import annotations
import re
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Fetch prebuilt LibXray + HevSocks5Tunnel into ios/Vendor.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VENDOR="$ROOT/ios/Vendor"
mkdir -p "$VENDOR"
cd "$VENDOR"
LIBXRAY_VER="${NAVIS_LIBXRAY_VERSION:-26.7.28}"
HEV_VER="${NAVIS_HEV_VERSION:-2.10.0}"
if [[ ! -d LibXray.xcframework ]]; then
echo "==> LibXray $LIBXRAY_VER"
curl -fsSL -o LibXray.xcframework.zip \
"https://github.com/wanliyunyan/LibXray/releases/download/${LIBXRAY_VER}/LibXray.xcframework.zip"
unzip -qo LibXray.xcframework.zip
rm -f LibXray.xcframework.zip
else
echo "LibXray.xcframework present"
fi
if [[ ! -d HevSocks5Tunnel.xcframework ]]; then
echo "==> HevSocks5Tunnel $HEV_VER"
curl -fsSL -o HevSocks5Tunnel.xcframework.zip \
"https://github.com/wanliyunyan/HevSocks5Tunnel/releases/download/${HEV_VER}/HevSocks5Tunnel.xcframework.zip"
unzip -qo HevSocks5Tunnel.xcframework.zip
rm -f HevSocks5Tunnel.xcframework.zip
else
echo "HevSocks5Tunnel.xcframework present"
fi
echo "Vendor ready:"
ls -d *.xcframework
+41
View File
@@ -12,6 +12,11 @@ VERSIONINFO = ROOT / "versioninfo.json"
BUILD_BAT = ROOT / "build-macos.bat"
ANDROID_GRADLE = ROOT / "android" / "app" / "build.gradle.kts"
INDEX_HTML = ROOT / "internal" / "appui" / "index.html"
IOS_README = ROOT / "ios" / "README.md"
IOS_PLISTS = (
ROOT / "ios" / "Navis" / "Info.plist",
ROOT / "ios" / "NavisTunnel" / "Info.plist",
)
def read_consts() -> tuple[str, int]:
@@ -77,6 +82,42 @@ def main() -> None:
else:
print("index.html badge already in sync")
for plist in IOS_PLISTS:
if not plist.exists():
continue
p = plist.read_text(encoding="utf-8")
p2 = re.sub(
r"(<key>CFBundleShortVersionString</key>\s*<string>)[^<]+",
rf"\g<1>{ver}",
p,
count=1,
)
p2 = re.sub(
r"(<key>CFBundleVersion</key>\s*<string>)[^<]+",
rf"\g<1>{full}",
p2,
count=1,
)
if p2 != p:
plist.write_text(p2, encoding="utf-8")
print("updated", plist, "->", ver, full)
else:
print(plist.name, "already in sync")
if IOS_README.exists():
r = IOS_README.read_text(encoding="utf-8")
r2 = re.sub(
r"(Версия:\s*\*\*)[^*]+(\*\*)",
rf"\g<1>{display}\2",
r,
count=1,
)
if r2 != r:
IOS_README.write_text(r2, encoding="utf-8")
print("updated", IOS_README, "->", display)
else:
print("ios/README.md already in sync")
if __name__ == "__main__":
main()