feat(macOS): full macOS support — platform backend, helper, DNS, packaging - #9
Closed
asgarihope wants to merge 5 commits into
Closed
asgarihope wants to merge 5 commits into
asgarihope wants to merge 5 commits into
Conversation
…mihomo support - New crate iran-split-platform-macos implementing PlatformBackend with macOS paths, Hiddify/Happ .app bundle discovery, system_proxy via networksetup, TUN detection via GET /configs (ADR 0104 mirror) - New helper module macos.rs: launchd daemon, socket permissions, apply_system_dns/restore_system_dns (networksetup → 127.0.0.1), using absolute /usr/sbin/networksetup path for launchd's minimal PATH - Config: normalize TUN name to utun9 and DNS port to 53 on macOS (kernel rejects non-utun names; LAN router DNS bypasses TUN) - Mihomo: add Platform::Macos, routing-mark Linux-only, process bypass - Clients: add DriverPlatform::Macos variant - Core: add helper_version to RuntimeHealth/StackSnapshot for auto-reinstall on version mismatch; populate on all backends + CLI - Helper: start() calls apply_system_dns, cleanup() calls restore_system_dns (cfg macOS); spawn_local_proxy uses open for .app bundles; reject Linux ELF binaries in resolve_macos_binary; double start timeout Co-authored-by: Cursor <cursoragent@cursor.com>
…ironment - helper_install: install_macos stages helper+mihomo+plist via osascript admin prompt, computes real mihomo_sha256 (not empty), writes tun_name into helper.toml - lib.rs: prepare_stack_start version-mismatch check auto-reinstalls stale helper on Connect; macOS NativeBackend + paths wiring - diagnostics: macOS gate for environment snapshot trigger - deps: macOS dependency discovery (Hiddify/Happ .app bundles) - github_update: macOS update flow (DMG/AppImage path) - hiddify_reset: macOS Fresh Hiddify start (kill by Mach-O image match) - environment/mod.rs: macOS collector gate - tauri.macos.conf.json: .app/.dmg bundle config, icon.icns - tauri.conf.json: macOS bundle resources Co-authored-by: Cursor <cursoragent@cursor.com>
- build.sh: ci-macos target, macOS packaging stages (compile/dmg/collect), prefetch, xwin alignment - scripts/build-plan.mjs: macOS bundle config - scripts/check-bundled-assets.mjs: macOS asset verification - scripts/stage-helper.sh: macOS helper staging (no cross-compile needed) - resources/external-assets.toml: macOS mihomo asset entry - Cargo.toml/Cargo.lock: iran-split-platform-macos workspace member - vendor/mihomo/darwin/mihomo: macOS arm64 mihomo binary Co-authored-by: Cursor <cursoragent@cursor.com>
…ent lessons - presets.ts: macOS download URLs for Hiddify, OpenVPN, Happ, v2rayN, Windscribe - AGENTS.md: append macOS lessons — utun naming, .app launch via open, mihomo_sha256 fix, networksetup absolute path for launchd PATH, DNS system resolver redirect, Mach-O binary validation, start timeout - version: 6.2.52 → 6.2.54 (pnpm version:sync) Co-authored-by: Cursor <cursoragent@cursor.com>
Skip fake-ip for localhost, exclude loopback from the TUN, and actually turn Hiddify's system proxy off so browsers stop getting 502 on local dev servers. Also make the DMG fallback survive leftover hdiutil files. Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
Author
|
Superseded by a new PR with 6.2.55: localhost/fake-ip, Hiddify system-proxy clear, and a working macOS DMG. Closing this 6.2.54 draft so the latest branch can be reviewed in one place. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds full macOS support to BiFlow. Previously the app only built and ran on Linux and Windows; macOS had zero support. This change ports the entire stack — platform backend, privileged helper, config normalization, Mihomo integration, desktop wiring, build/packaging, and frontend presets — so macOS users get the same transparent TUN split-routing experience as Linux and Windows.
What changed
1. New crate
iran-split-platform-macos(crates/iran-split-platform-macos/)PlatformBackendwith macOS paths (~/Library/Application Support/biflow/), Hiddify/Happ.appbundle discovery via Launch Services,system_proxy.rsvianetworksetup, TUN detection viaGET /configstun.enable(mirrors the Windows ADR 0104 approach —ifconfigis unreliable because the liveutundevice name is kernel-assigned).spawn_local_proxydetects the enclosing.appbundle and launches it viaopen <bundle>.app(Launch Services). Running the inner Mach-O binary directly starts the process but the Flutter/Electron app never opens its proxy port.resolve_macos_binaryrejects Linux ELF binaries (e.g., a staleAppImagesymlink) by checking the Mach-O magic (0xfeedfacf/0xfeedface), so discovery never picks a binary that cannot run on macOS.client_start_timeoutdoubles the configured budget on macOS (Flutter.appbundles can take over a minute to open their proxy port on a cold start).2. macOS helper module (
crates/iran-split-helper/src/macos.rs— new)run_macos: launchd daemon, Unix socket with root:authorized_gid mode0o660permissions (workspace forbidsunsafe, sogetpeereid(2)is not called directly — access control is enforced by the socket itself).apply_system_dns/restore_system_dns: snapshot every network service's DNS vianetworksetup, point them all at127.0.0.1on Connect, restore on Disconnect. Uses the absolute path/usr/sbin/networksetupbecause launchd's minimalPATHcan cause a barenetworksetuplookup to fail silently.start()callsapply_system_dns()(cfg macOS),cleanup()callsrestore_system_dns()(cfg macOS).3. Config normalization (
crates/iran-split-config/src/lib.rs)normalize_tun_name_for_platform(): rewritestun_nametoMACOS_TUN_NAME = \"utun9\"at config load. The macOS kernelcom.apple.net.utuncontrol rejects arbitrary names (e.g.,clash-iran), so Mihomo never creates the TUN and readiness fails.normalize_dns_port_for_platform(): rewritesdns_porttoMACOS_DNS_PORT = 53. The macOS system DNS always uses port 53, and the LAN/router resolver bypasses the TUN (it is on the directly-connecteden0subnet), so Mihomo'sdns-hijack: any:53never sees DNS queries. Mihomo runs as root via the helper, so it can bind127.0.0.1:53; the helper then points the system DNS at127.0.0.1so queries flow through the TUN.4. Mihomo macOS support (
crates/iran-split-mihomo/src/lib.rs)Platform::Macosvariant.routing-markis now Linux-only (platform == Platform::Linuxinstead ofplatform != Platform::Windows) — it is a Linux fwmark that does not steer sockets on macOS.DriverPlatform::Macosmapping and macOS process bypass rules (tailscaled,iran-split-desktop,BiFlow).5.
helper_versionplumbing (core + all backends + CLI)RuntimeHealthandStackSnapshotgained ahelper_version: Option<String>field (#[serde(default)]).runtime_health()populates it from the helper status. Windowsconnect_progress_health()caches it.prepare_stack_start(desktop) checkshelper_versionvsapp_version(); on mismatch it auto-reinstalls the helper so a new app with DNS/TUN code does not silently use a stale installed daemon.6. Desktop macOS wiring (
src-tauri/)helper_install.rs:install_macosstages helper + mihomo + plist + helper.toml and runs an install shell script viaosascript … with administrator privileges. Computes the realmihomo_sha256(an empty hash made launchd crash-loop the helper). Writestun_nameintohelper.toml.lib.rs:prepare_stack_startversion-mismatch reinstall check; macOSNativeBackend+ paths wiring.diagnostics.rs: macOS gate for environment snapshot.deps.rs: macOS dependency discovery (Hiddify/Happ.appbundles).github_update.rs: macOS update flow (DMG).hiddify_reset.rs: macOS Fresh Hiddify start (kill by Mach-O image match, not command-line substring).environment/mod.rs: macOS collector gate.tauri.macos.conf.json(new):.app/.dmgbundle config,icon.icns.7. Build/packaging
build.sh:ci-macostarget, macOS packaging stages (compile/dmg/collect).scripts/stage-helper.sh: macOS helper staging (native build, no cross-compile).scripts/build-plan.mjs,scripts/check-bundled-assets.mjs: macOS bundle config and asset verification.resources/external-assets.toml: macOS mihomo asset entry.vendor/mihomo/darwin/mihomo: macOS arm64 mihomo binary.8. Frontend
apps/desktop/src/lib/presets.ts: macOS download URLs for all presets (Hiddify, OpenVPN, Happ, v2rayN, Windscribe).9. Docs
AGENTS.md: appended macOS lessons (utun naming,.applaunch,mihomo_sha256fix,networksetupabsolute path, DNS system resolver redirect, Mach-O binary validation, start timeout doubling).Testing
cargo clippy --workspace --all-targets -- -D warnings— cleancargo test --workspace— all pass (core, config, helper, mihomo, clients, platform-macos, desktop, cli)cargo fmt --all --check— cleanpnpm check— 90 tests passpnpm build— cleanopen, TUN (utun9) routes correctly, DNS points to127.0.0.1:53, foreign sites (facebook.com, google.com, youtube.com) open without manual browser proxy.Cross-platform
routing-markchange only affects macOS (Linux unchanged, Windows unchanged),helper_versionfield has#[serde(default)]so existing saved state remains readable, all macOS-specific code iscfg(target_os = \"macos\")-gated.DemoBackend::runtime_health()iniran-split-cliwas missing the newhelper_versionfield (would have broken the Windows build).Privacy
No telemetry, analytics, or tracking added. Browsing data (live connection hosts) stays in memory and is never logged to
debug.logor transmitted to any external server. The only external requests are the same as before (GitHub Releases for updates/rules,api.country.is/api.ipify.orgfor IP detection, Cloudflare DoH for DNS).Made with Cursor