ZonicIsAlive
← all projects

BitProxy

sing-box Android app fork: per-app VPN/Direct/Block routing with WireGuard, auto-updated from upstream

Shell★ 0updated Oct 14, 2026view on github ↗

BitProxy

Personal fork of sing-box for Android (SFA), the Android client of sing-box by SagerNet. All the heavy lifting (the proxy engine, WireGuard, the VPN service and the app itself) is their work; BitProxy only adds a layer on top. It imports WireGuard configs (for example Proton VPN), gives every app and clone its own VPN / Direct / Block switch, and generates, checks and applies the sing-box config inside the app. GPLv3, like upstream.

Upstream isn't stored in this repo. versions.env pins sing-box and SFA, and patches/ holds the BitProxy changes as git format-patch files. They're applied on top of every build.

App id: com.zonicisalive.bitproxy. It installs next to the official SFA app.

Download: Releases. Use BitProxy-*-arm64-v8a.apk for current phones. After that the app updates itself: it checks these releases (Settings → App → Automatic Update Check), shows what's new, downloads the APK for your phone and installs it over the current version. F-Droid isn't offered as an update source: it only has the original sing-box app.

Features

  • Servers: every server type sing-box supports:

    • WireGuard .conf files.
    • OpenVPN .ovpn profiles. The username and password are added in the app.
    • Share links: vless://, vmess://, trojan://, ss://, hy2://, hysteria://, tuic://, anytls://, socks://, http(s)://, naive+https:// and ssh://.
    • Subscription URLs.
    • sing-box JSON: single outbounds, or a whole config.

    Import files, paste links, or share them to BitProxy. Everything is stored in private app storage only. Pick a server, or choose Fastest (auto) with a check interval of 1–30 min. The Dashboard card shows latency, or "No response" when a server is down.

  • VPN provider login: get servers without downloading files. Log in, pick locations, and BitProxy writes one server per location. Log out removes the key or device again.

    • Mullvad (account number), IVPN (account ID), Private Internet Access (username and password), AirVPN (API key), AzireVPN (username and password), NordVPN (access token from Nord Account → Manual setup), Cloudflare WARP (free, no account).
    • PIA forgets keys that stay unused for a while: if a PIA server stops working, choose its locations again to register fresh keys.
  • Share to import: share a server file, links or a subscription URL to BitProxy, or open a file with BitProxy from a file manager. It's imported and applied immediately.

  • Apps: a VPN / Direct / Block switch for each app, clone (user 999) and work profile, on the Dashboard and in the app picker. Rules match on Android user id, so reinstalled apps keep their setting. Blocked apps get no traffic and no DNS.

  • Everything stays in the tunnel: Android's per-app VPN exclude lists are never used, so Always-on with "Block connections without VPN" works for every app. The routing happens inside sing-box.

  • No DNS leaks: VPN apps resolve through the server's DNS inside the tunnel (with other server types: 1.1.1.1 over TCP through the server). Direct apps use normal DNS.

  • Automatic IPv6: on networks with real IPv6, the server's IPv6 address is added to the tunnel. Otherwise it runs IPv4-only.

  • Trusted Wi-Fi: networks where every app goes Direct (blocked apps stay blocked). Needs location permission set to "Allow all the time".

  • Always through VPN: a domain list that uses the VPN even for apps not in the list.

  • Quick Settings tiles: three tiles, each flipping one app between VPN and Direct.

  • New apps: a notification asks "VPN, Direct or Block?" when an app is installed.

  • Safety:

    • Apply checks the config with sing-box before using it, and the previous config can be restored.
    • The Dashboard shows the Always-on / Block state, and asks before stopping under lockdown.
    • Recovery:
      • WireGuard servers get a small in-tunnel check: every 3 min with the screen on, every 15 min with it off, never in Doze, and right after unlocking. Two failures in a row rebuild sing-box.
      • After 2+ minutes without any network, sing-box is restarted cleanly.
      • Every reload (Apply, an app installed or removed) is watched: a hang or a dead tunnel afterwards restarts the VPN in a fresh process.
      • If Android kills the app while the VPN runs, a backup alarm starts it again within 30 min.
      • libbox is built with a Go whose timers keep counting in deep sleep (as in WireGuard's and Proton's apps), so the tunnel isn't stale after the night.
      • Android (seen on OxygenOS 16) can destroy the socket sing-box uses for TCP while the VPN is rebuilt; every new TCP connection was then refused until a restart. The patched sing-tun listens again within 0.1 s.
      • Apply with an unchanged config doesn't reload, so connections aren't interrupted.
  • Diagnostics: tunnel, DNS, IPv6 and Always-on state, clone detection, last Apply, and a Recent events log that survives the night. The export removes all private keys.

Phone setup

  1. Install the APK from Releases (…-arm64-v8a.apk for current phones). Allow notifications.
  2. Settings → BitProxy → Import .conf files (or share them to BitProxy, or log in under VPN providers), then Apps, then Apply.
  3. Start the VPN from the Dashboard and allow the VPN request.
  4. Android: Settings → Network (on OxygenOS: Connection & sharing) → VPN → BitProxy ⚙ → Always-on VPN + Block connections without VPN. Turn Always-on off in other VPN apps first.
  5. Battery: allow background activity / don't optimize. This is required, for example for the automatic restart after a hang.
  6. Check it: BitProxy → Leak test in a VPN app (server IP) and in a Direct app (your own IP), then Diagnostics.

On OxygenOS, also turn off any setting that switches Wi-Fi off during sleep. BitProxy recovers from it, but the phone has no network in that time.

Troubleshooting

  • No internet but the VPN looks on: open Diagnostics and read "Recent events" (network lost/back, restarts, stops), then use Export log. It contains no keys.
  • Over USB: adb logcat --pid=$(adb shell pidof com.zonicisalive.bitproxy). OxygenOS keeps only about 90 seconds of logcat, so the events log is the long-term record.

Layout

PathWhat
versions.envPinned sing-box tag, SFA commit, Go / NDK / gomobile versions
patches/android/BitProxy commits on top of SFA (new code lives in io.nekohasekai.sfa.bitproxy)
patches/core/Commits on top of sing-box (none so far)
patches/sing-tun/Commits on top of sing-tun (sing-box's TUN library): re-listen when Android destroys the TCP socket
build-release.shFresh clone of pinned upstream, apply patches, build libbox + APK, run unit tests
.github/workflows/release.ymlThe same build on GitHub, signed and published as a release
.github/workflows/auto-update.ymlDaily: build and release new upstream versions automatically
setup-android-dev.shOne-time toolchain setup (Go, JDK 17, Android SDK/NDK, emulator) into $DEV
run.sh, gwBuild + run on the emulator (never on a connected phone); Gradle with the right toolchain
check-upstream.shTry all patches on a newer upstream: apply, build libbox + APK, run unit tests
export-patches.shRegenerate patches/ from the local bitproxy branches
prepare-sing-tun.shClones the sing-tun version sing-box requires, applies patches/sing-tun, points sing-box at it (used by every build)
find-sfa-commit.shFinds the sing-box-for-android commit released as a given version (used by auto-update)
go-boottime.shGo toolchain copy whose timers count deep sleep, used to build libbox
make-keystore.shOptional helper to create a release signing key

build-release.sh needs Go, JDK 17, the Android SDK + NDK (ANDROID_HOME) and gomobile on PATH. The other helper scripts load $DEV/env.sh, which sets JAVA_HOME, GOROOT, GOPATH, ANDROID_HOME and the caches; point DEV at your own toolchain folder.

Building a signed APK

Create a key once and back it up; every update must be signed with the same key:

keytool -genkeypair -keystore path/to/bitproxy.keystore \
  -alias bitproxy -keyalg RSA -keysize 4096 -validity 36500

Put the passwords in a signing.properties next to it (chmod 600):

KEYSTORE_PASS=...
ALIAS_NAME=bitproxy
ALIAS_PASS=...

Build:

RELEASE_KEYSTORE=path/to/bitproxy.keystore \
LOCAL_PROPERTIES="$(cat path/to/signing.properties)" \
./build-release.sh

APKs end up in build-work/sing-box-for-android/app/build/outputs/apk/other/release/. Install over USB with adb install -r <apk> (same key = settings kept).

Never copy the key into the sing-box checkout: upstream tracks a file at app/release.keystore, so git add -A would commit your key. build-release.sh only uses its own throwaway clone and deletes the copy afterwards.

Release on GitHub

  1. Repository secrets: KEYSTORE_BASE64 (base64 -w0 bitproxy.keystore), KEYSTORE_PASS, ALIAS_NAME, ALIAS_PASS.
  2. Actions → Release → Run workflow (or push a v* tag). APKs appear under Releases.

Automatic updates

auto-update.yml runs every day (and on demand under Actions → Auto update):

  1. It looks for a new stable sing-box tag (for example v1.14.3). It waits until the app's main branch reaches the same version (a "Bump version 1.14.3" commit with that VERSION_NAME). sing-box-for-android has no tags of its own.
  2. It builds on that pair with the patches in this repo: libbox, unit tests, a signed APK.
  3. Success: it publishes a release with the same tag as sing-box (v1.14.3) and commits the new versions to versions.env.
  4. Failure (a patch conflict or a failing build/test): it opens an issue "BitProxy patches fail on sing-box v1.14.3" with a link to the log, and publishes nothing.

Beta track: sing-box pre-releases (alpha, beta, rc) of a version with no stable release yet are built on the app's dev branch and published as GitHub pre-releases, only once the app has that exact version. In BitProxy, Settings → App → Update Track: Stable gets only stable releases; Beta also gets these pre-releases. A failing Beta build opens an issue and never affects Stable. versions.env always stays on stable.

Every release lists what's new since the previous one (this repo's commits, plus the new sing-box version). The app's update prompt shows that list.

Manual runs of Release use the tag from versions.env, or v1.14.2-r<run> when that release already exists (patch changes on the same upstream).

Upgrading to a new upstream release by hand

  1. Try it first: ./check-upstream.sh checks the newest sing-box release with SFA main. ./check-upstream.sh v1.15.0 dev checks a specific pair. It says whether the patches apply, the build works and the tests pass, and shows where it failed if not. Tested: all patches apply and pass on sing-box 1.15.0-alpha.8 + SFA dev, and sing-box 1.15 accepts every config BitProxy generates.
  2. Read the sing-box changelog for removed or deprecated config fields. Update ConfigGenerator and the golden tests if anything BitProxy writes changed.
  3. In the local checkouts: git switch -c bitproxy-new <new tag / commit> and git am -3 <this repo>/patches/…/*.patch. Fix conflicts, rebuild libbox, and run the unit tests. For sing-tun, the base is the version in sing-box's go.mod (prepare-sing-tun.sh and export-patches.sh read it).
  4. Update versions.env, run ./export-patches.sh, commit.

Testing checklist (before each release)

  • Unit tests and release lint pass (./gw :app:testOtherDebugUnitTest :app:lintVitalOtherRelease).
  • Emulator:
    • fresh install, import confs, pick apps, Apply, then start without reopening the app
    • reboot with Always-on
    • a second user (adb shell pm create-user test)
    • reinstall a listed app
    • switch networks
    • a 3-minute outage (svc wifi disable; svc data disable): the events log shows "network back … restarting VPN"
    • server down, IPv6 on and off
  • Phone:
    • an overnight run
    • leak check: a VPN app shows the server IP, a Direct app your own IP, no IPv6 leak
    • clones (user 999) are detected in Diagnostics and routed
  • The signing key is backed up. Changing it forces an uninstall.

Source code and license

BitProxy is free software under the GNU General Public License v3.0 (see LICENSE), as are sing-box and sing-box-for-android, which it is built from. The complete source of each release is the upstream code at the versions in versions.env plus the patches in patches/. build-release.sh rebuilds it exactly. BitProxy isn't affiliated with or endorsed by SagerNet / sing-box; "sing-box" names the upstream project it's based on.

Thanks to nekohasekai and the SagerNet contributors for sing-box and its Android app, a great piece of free software. If BitProxy is useful to you, consider supporting the upstream project.