Skip to content
Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2,155 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Get it on IzzyOnDroid

Husi (θ™Žε…•)

Husi is a non-professional and recreational proxy tool integration, aiming at promoting proxy customization.

πŸ› οΈ Contribution

🧭 Guide

CONTRIBUTING

πŸ“š Localization

Is husi not in your language, or the translation is incorrect or incomplete? Get involved in the translations on our Weblate.

Translation status

πŸ”¨ Learn to Compilation

In Linux, you can build husi reproducibly for release version.

For this, you should use the same version of JDK, NDK as below. And Go version should as same as version.sh.

🧰 Get the Source Code

git clone https://github.com/xchacha20-poly1305/husi.git --depth=1
cd husi/
./run lib source # Will help you to get submodules

βš–οΈ libcore

Environment:

  • These versions need to apply patch.

    Unfold

    1.22.5: Apply this patch to ${GOROOT}/src/runtime/cgocall.go

    1.23.0-1.23.3: Apply this patch to${GOROOT}. make patch_go1230

    1.23.4: Apply this patch to $(GOROOT). make patch_go1234

  • Openjdk-21 (Later may OK, too.)

For Android:

make libcore_android

This will generate composeApp/libs/libcore.aar.

For desktop, build libcore for your host platform:

make libcore

This will generate composeApp/libs/libcore-desktop-<host-platform>-<host-arch>.jar.

Or for specific targets:

make libcore_desktop DESKTOP_TARGETS=linux/amd64,darwin/arm64

If desktop build needs an explicit JNI headers directory, pass JNI_INCLUDE:

make libcore_desktop DESKTOP_TARGETS=linux/amd64 JNI_INCLUDE=/path/to/jni

For Darwin targets on non-Darwin hosts, also pass the macOS SDK explicitly:

make libcore_desktop DESKTOP_TARGETS=darwin/arm64 JNI_INCLUDE=/path/to/jni DARWIN_SDK=/path/to/MacOSX.sdk

Common desktop targets:

  • linux/amd64
  • linux/arm64
  • darwin/arm64

darwin/amd64 is no longer supported and windows/arm64 is temporarily not shipped: androidx sqlite-bundled has no binary for them, so the app cannot open its database there. See issuetracker 495864182 for osx_x64 and issuetracker 426464784 for windows_arm64.

For Linux desktop targets, the build includes with_naive_outbound and consults a cronet-go checkout via build-naive env. If CRONET_GO_ROOT is unset, libcore/build.sh first checks ../../cronet-go, then falls back to $HOME/cronet-go:

CRONET_GO_ROOT=/path/to/cronet-go make libcore

For Linux targets, cronet-go exports the naiveproxy cross-toolchain environment directly. For Darwin targets on a Darwin host, libcore/build.sh keeps with_naive_outbound and derives CC/CXX/CGO_* from the Chromium clang and hermetic Xcode toolchain inside the cronet-go checkout. If the Darwin SDK/linker tree is missing, the desktop build fails immediately. For Darwin targets on non-Darwin hosts, libcore/build.sh uses zig cc / zig c++, requires an explicit macOS SDK path via DARWIN_SDK or --darwinsdk, exports the matching CGO_* sysroot/library flags, keeps with_naive_outbound, and does not require a cronet-go checkout, so zig must be available in PATH.

If cronet-go isn't available and you don't need the naive outbound protocol (e.g. in a restricted build environment), pass NO_NAIVE=1 (or --no-naive to libcore/build.sh directly) to drop with_naive_outbound from the build tags and skip the cronet-go toolchain setup entirely:

NO_NAIVE=1 make libcore_desktop DESKTOP_TARGETS=linux/amd64

Desktop Gradle builds select composeApp/libs/libcore-desktop-<platform>-<arch>.jar automatically from the current os.name and os.arch.

You can override it explicitly:

./gradlew -p composeApp run -PdesktopTarget=linux/amd64

If the selected jar is missing, the build fails immediately.

If you run libcore/build.sh directly:

  • --android: build Android only
  • --desktop: build desktop only (default target: host)
  • --android --desktop: build both
  • --jniinclude <path>: pass JNI headers include path to desktop anja bind -target=jvm
  • --darwinsdk <path>: pass a macOS SDK path for Darwin desktop targets on non-Darwin hosts
  • no platform args: defaults to Android only

If anja is not in GOPATH, it will be automatically downloaded and compiled.

πŸŽ€ Rename package name (optional)

If you don't want to use the same package name, you can run ./run rename target_name.

🎁 APK

Environment:

  • jdk-21
  • ndk 29.0.14206865

If the environment variables $ANDROID_HOME and $ANDROID_NDK_HOME are not set, source buildScript/init/env_ndk.sh to set them:

source buildScript/init/env_ndk.sh

Then write the SDK path to local.properties:

echo "sdk.dir=${ANDROID_HOME}" > local.properties

Signing preparation (optional, it is recommended to sign after compilation): Replace release.keystore with your own keystore.

echo "KEYSTORE_PASS=" >> local.properties
echo "ALIAS_NAME=" >> local.properties
echo "ALIAS_PASS=" >> local.properties

Download geo resource files:

make assets

Generate open source license metadata:

make aboutlibraries_go
make aboutlibraries_android

This writes Android metadata to composeApp/src/androidMain/composeResources/files/aboutlibraries.json.

Compile the release version:

make apk

The APK file will be located in androidApp/build/outputs/apk.

πŸ–₯️ Desktop

Environment:

  • jdk-21
  • zig 0.16

Run the desktop application:

make desktop

Generate desktop open source license metadata:

make aboutlibraries_go
make aboutlibraries_desktop

This writes desktop metadata to composeApp/src/desktopMain/composeResources/files/aboutlibraries.json.

Package a distributable for the current OS:

make desktop_package

This dispatches to the host-native packaging flow:

  • Linux: make desktop_package_linux
  • macOS: make desktop_package_macos
  • Windows/MSYS: make desktop_package_windows

Build an uber JAR that runs on system Java (no bundled JRE/runtime image):

make desktop_uberjar

Output directory:

composeApp/build/compose/jars/

Run it with system Java (JDK/JRE 21+):

java -jar composeApp/build/compose/jars/fr.husi-<platform>-<arch>-<version>.jar

Build Linux native packages (deb/rpm/pacman) with Java 21 dependency metadata:

make desktop_package_linux

This command still builds the uber jar first, then packages it with nfpm. Required host tools: zig, git, nfpm.

Package timestamps are derived from git tag v<VERSION_NAME> from husi.properties, not from local build time. Default output directory:

composeApp/build/compose/packages/linux/

You can select target formats:

make desktop_package_linux LINUX_PACKAGE_FORMATS=deb,pacman

Besides deb, rpm and pacman, there is tarball: the relocatable app subtree as a .tar.zst, for portable installs and for distributions the native formats do not cover. It is not built by default, but releases ship it. It needs tar and zstd, and unpacks to a directory holding the jar, the launcher, husi-core and libhusicore.so β€” run bin/husi-core service install from there to get the daemon. The native packages install and enable that daemon themselves, so only the tarball needs the manual step. Either way the Settings entry can do it instead, but that route goes through pkexec and so needs polkit; without it, run the command as root.

Linux desktop data directory is $XDG_CONFIG_HOME/husi/ if set, otherwise $HOME/.config/husi/.

Installed launcher supports user config files in the same Linux config directory:

  • <config-dir>/husi/desktop-java-opts.conf for JVM options
  • <config-dir>/husi/desktop-app-args.conf for application startup arguments

Linux native packages include a native launcher built with Zig from launcher/.

Build the launcher standalone:

make launcher

The default packaging flow runs make launcher first, then package.sh consumes that binary. Zig targets musl by default for static linking; no external C toolchain is needed. The launcher runs unprivileged. Privileges live in the separate husi-core daemon (husi-core service install; systemd / launchd / Windows SCM service husi-daemon). Without a daemon the UI falls back to an unprivileged session host; TUN requires the daemon.

husi-core is a second, independent Zig project under libcore/shim/, built by make core_desktop and packaged alongside libhusicore.*. It links libc dynamically, unlike the static launcher, so the two are deliberately kept apart.

You can preflight required tooling without producing packages:

./release/linux/package.sh --check-tools --formats deb,rpm,pacman

Build macOS .dmg packages with system Java runtime dependency:

make desktop_package_macos

This command builds the uber jar first, then packages it into Husi.app and a .dmg image.

The app bundle icon is a checked-in static asset generated from composeApp/src/commonMain/composeResources/drawable/ic_launcher_foreground.xml, so packaging no longer builds icons dynamically.

On macOS hosts it uses native tooling: hdiutil. On Linux hosts it uses xorrisofs and emits a compatibility .dmg (an ISO9660/HFS hybrid image that macOS can mount). For Linux fallback, DESKTOP_TARGET is required because the Gradle uber-jar task otherwise defaults to the Linux host target:

make desktop_package_macos DESKTOP_TARGET=darwin/arm64

Required host tools:

  • Common: zig, git
  • macOS host: hdiutil
  • Linux fallback: xorrisofs

Package timestamps are derived from git tag v<VERSION_NAME> from husi.properties, not from local build time. Default output directory:

composeApp/build/compose/packages/macos/

Installed app bundle uses the same native launcher from launcher/ as Linux packaging. User config files are created under:

  • ~/Library/Application Support/husi/desktop-java-opts.conf for JVM options
  • ~/Library/Application Support/husi/desktop-app-args.conf for application startup arguments

Build Windows portable zip and NSIS installer packages:

make desktop_package_windows DESKTOP_TARGET=windows/amd64

This command builds the uber jar first, then packages it with the same Zig launcher used by Linux and macOS. Required host tools:

  • zig
  • git
  • python3
  • makensis (NSIS)
  • osslsigncode (unless building with WINDOWS_NO_SIGN=1, see below)

Default output directory:

composeApp/build/compose/packages/windows/

Outputs:

  • <PACKAGE_NAME>-<VERSION_NAME>-windows-<arch>.zip
  • <PACKAGE_NAME>-<VERSION_NAME>-windows-<arch>-installer.exe

The installer is a per-user NSIS installer that installs into %LOCALAPPDATA%\Programs\Husi, creates a Start Menu shortcut, and registers the configured URL schemes for the current user. The Windows launcher is unprivileged. Privileges live in the husi-core daemon installed as a Windows service (husi-core service install); TUN requires the daemon. Without it the UI falls back to an unprivileged session host.

The UI and the daemon therefore install to two different places, and both hold a copy of husi-core.exe + husicore.dll:

What Where Installed by
UI (launcher, jar, and the core pair it ships) %LOCALAPPDATA%\Programs\Husi the NSIS installer, per user, no elevation
Daemon (the core pair the service runs) %ProgramFiles%\husi husi-core service install, elevated
Daemon state (snapshots, ownership) %ProgramData%\husi the daemon itself

The installer runs husi-core.exe service install from its own directory at the end, so the per-user copy is what seeds the Program Files copy β€” that is why the same two files exist twice. The uninstaller reverses both, calling husi-core.exe service uninstall before removing its own directory. It does not pass --purge, so %ProgramData%\husi survives an uninstall; remove it by hand, or run husi-core service uninstall --purge yourself beforehand.

πŸ” Windows code signing

Windows releases are Authenticode signed: the launcher, husi-core.exe, husicore.dll and the installer all carry the same self-signed certificate, published as release/windows/husi-signing-cert.pem. Its fingerprints and how to check a download against it are in release/windows/README.md.

Being self-signed, it earns no SmartScreen reputation β€” Windows still calls the publisher unknown, and that is expected. The signature is there so the privileged daemon can tell that husi-core.exe and the husicore.dll it loads are the pair that shipped together: the shim loads that DLL by absolute path and runs it as SYSTEM, so a swapped DLL would be a swapped SYSTEM process. The check lives in daemonhost.VerifyCorePairSignature and follows sing-box's boxdd.

If you build Windows packages yourself, read this:

  • Signing is on by default and packaging fails without a certificate. That is deliberate β€” an unsigned release should never happen by accident. To build unsigned on purpose:

    make desktop_package_windows DESKTOP_TARGET=windows/amd64 WINDOWS_NO_SIGN=1

    Unsigned builds install and run fine: the daemon logs a warning and skips the pair check, because there is nothing to compare.

  • To sign with a certificate of your own, mint a self-signed code signing certificate (see the Windows signing entry in AGENTS.md) and point the packaging at it:

    export WINDOWS_SIGNING_P12=/path/to/your-signing.p12
    export WINDOWS_SIGNING_P12_PASSWORD=...
    make desktop_package_windows DESKTOP_TARGET=windows/amd64

    Your own certificate works fine. The daemon requires the shim and the library to share a signer, not to match any particular certificate.

  • Do not mix payloads from different builds. Taking husi-core.exe from an official release and pairing it with a husicore.dll you signed yourself (or the reverse) makes the daemon refuse to start. Ship whole packages.

  • Give your certificate a long life. The validity window is checked against the current time rather than against the signature timestamp, so an expired certificate starts failing already-installed daemons.

  • If you redistribute your build, say so. The published fingerprint above is how users tell an official release from a rebuild, and nothing in the daemon makes that distinction for them.

🌈 Plugins

make plugin PLUGIN=<Plugin name>

Plugin name list:

  • hysteria2
  • juicity
  • naive (Deprecated. Build official repository directly, please. )
  • mieru
  • shadowquic

πŸƒβ€β™‚οΈ Run

Desktop

Requirement: >= Java Runtime 21

No bundled JRE for end users

$ fr.husi --help
Usage: fr.husi [<options>] [<deep-link>]...

Options:
  -d, --dir=<path>       Data directory
  -l, --log-level=<int>  Log level override (0-6)
  -m, --many             Ignore exist instance
  -b, --background       Start without opening the main window
  -h, --help             Show this message and exit

Arguments:
  <deep-link>  Deep links

Arguments

URLs to import.

☠️ End users

Wiki

πŸ“– License

GPL-3.0 or later

🌐 GeoIP data

Husi can generate and use GeoIP rule-set assets derived from MaxMind GeoLite2 data. GeoLite2 data is created by MaxMind and is subject to the GeoLite2 End User License Agreement and related attribution and update requirements.

Third-party mirrors or re-published GeoLite2 database files do not grant additional rights beyond MaxMind's terms. If you build, redistribute, or use Husi with generated GeoIP assets, you are responsible for ensuring that your use and redistribution of those assets complies with the applicable MaxMind license terms.

🀝 Acknowledgements

Developing

  • JetBrains' powerful IDE.

About

Mirror only.

Resources

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Contributors

Languages