Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d71424f
feat: add Zig build support and platform-specific adjustments
kooksee Jun 5, 2026
a219ccd
feat(build): add static linking option
kooksee Jun 5, 2026
9ab4278
docs(build): document Zig cross-compile + add musl static deps script
kooksee Jun 5, 2026
1c8f5c1
docs(build): note optional UPX compression for static binary (~3.5MB …
kooksee Jun 5, 2026
f8d941c
ci: add Zig static multi-arch release workflow
kooksee Jun 5, 2026
0b33e74
chore: gitignore local reference symlink (.local)
kooksee Jun 5, 2026
ab4adc3
ci: add portable macOS release builds (darwin amd64/arm64)
kooksee Jun 5, 2026
d01a87a
build: add support for macOS SDK headers in cross-compilation
kooksee Jun 5, 2026
bb754f3
ci(macos): cross-compile both arches on arm64 runner (macos-13 deprec…
kooksee Jun 5, 2026
a4fc9aa
feat: add native TOML config and JWT login support
kooksee Jun 15, 2026
dd06a75
fix(tls): create OpenSSL bufferevent before async connect
kooksee Jun 15, 2026
21cd56c
fix(tls): load CA bundle from SSL_CERT_FILE or SSL_CERT_DIR
kooksee Jun 16, 2026
b3d2105
fix(tcpmux): keep control login on yamux stream 1
kooksee Jun 16, 2026
d14ab97
fix(tcpmux): align yamux SYN window and log tcp_mux on connect
kooksee Jun 16, 2026
6b2f7c9
fix: correct FRP wire byte order on Zig/macOS builds
kooksee Jun 16, 2026
3c7a6ab
fix(build): use Io.Dir.accessAbsolute for Homebrew path checks
kooksee Jun 16, 2026
f504beb
fix(msg): use headers field in NewProxy for Referer rewrite
kooksee Jun 16, 2026
523ec6b
feat(http): support Origin header rewrite for websocket proxies
kooksee Jun 16, 2026
300ea34
build(release): drop UPX and enable section-gc size optimizations
kooksee Jun 16, 2026
2e01895
feat(http): pass through requestHeaders.set.* in NewProxy headers
kooksee Jun 16, 2026
3d4deab
build(minimal): add plugin-lite armv7 release profile
kooksee Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
# Build one fully-static musl binary per architecture. Each target gets its
# own sysroot of cross-compiled C deps (OpenSSL/libevent/json-c/zlib) built by
# scripts/build-musl-deps.sh, then linked via `zig build -Dstatic`.
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- zig_target: x86_64-linux-musl
label: linux_amd64
- zig_target: aarch64-linux-musl
label: linux_arm64
- zig_target: arm-linux-musleabihf
label: linux_armv7
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0

- name: Parse version and channel from tag
id: version
run: |
version="${GITHUB_REF#refs/tags/}"
echo "version=${version}" >> "$GITHUB_OUTPUT"
if [[ "$version" =~ -alpha ]]; then
echo "channel=dev" >> "$GITHUB_OUTPUT"
elif [[ "$version" =~ -beta ]]; then
echo "channel=beta" >> "$GITHUB_OUTPUT"
else
echo "channel=prod" >> "$GITHUB_OUTPUT"
fi

- name: Cache cross-compiled deps sysroot
uses: actions/cache@v4
with:
path: ~/.cache/xfrpc-musl
key: musl-deps-${{ matrix.zig_target }}-${{ hashFiles('scripts/build-musl-deps.sh') }}

- name: Cross-compile C dependencies (musl static)
run: TARGET=${{ matrix.zig_target }} scripts/build-musl-deps.sh

- name: Build static xfrpc
run: |
channel="${{ steps.version.outputs.channel }}"
if [[ "$channel" == "prod" ]]; then
optimize="ReleaseSmall"
else
optimize="ReleaseSafe"
fi
minimal=""
if [[ "${{ matrix.label }}" == "linux_armv7" ]]; then
minimal="-Dminimal=true"
fi
echo "Channel: ${channel}, Optimize: ${optimize}, Target: ${{ matrix.zig_target }}"
zig build \
-Dtarget=${{ matrix.zig_target }} \
-Ddep-prefix="$HOME/.cache/xfrpc-musl/sysroot" \
-Doptimize="${optimize}" -Dstatic $minimal
file zig-out/bin/xfrpc

- name: Package artifact
run: |
mkdir -p release-artifacts
archive="xfrpc_${{ steps.version.outputs.version }}_${{ matrix.label }}.tar.gz"
tar -czf "release-artifacts/${archive}" -C zig-out/bin xfrpc
ls -l "release-artifacts/${archive}"

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: xfrpc-${{ matrix.label }}
path: release-artifacts/*.tar.gz

# Build a portable macOS binary per arch. GitHub's Intel macOS runners
# (macos-13) are deprecated, so both arches are built on the arm64 runner
# (macos-14) and the x86_64 binary is cross-compiled. Setting SDKROOT lets
# the cross build find the system SDK headers (e.g. <arpa/telnet.h>). The C
# deps are linked statically via -Ddep-static; only system libraries
# (libSystem/libz) stay dynamic, so the binary runs without Homebrew.
build-macos:
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
zig_target: x86_64-macos
label: darwin_amd64
- arch: arm64
zig_target: aarch64-macos
label: darwin_arm64
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0

- name: Parse version and channel from tag
id: version
run: |
version="${GITHUB_REF#refs/tags/}"
echo "version=${version}" >> "$GITHUB_OUTPUT"
if [[ "$version" =~ -alpha ]]; then
echo "channel=dev" >> "$GITHUB_OUTPUT"
elif [[ "$version" =~ -beta ]]; then
echo "channel=beta" >> "$GITHUB_OUTPUT"
else
echo "channel=prod" >> "$GITHUB_OUTPUT"
fi

- name: Cache macOS deps sysroot
uses: actions/cache@v4
with:
path: ~/.cache/xfrpc-macos
key: macos-deps-${{ matrix.arch }}-${{ hashFiles('scripts/build-macos-deps.sh') }}

- name: Build C dependencies (macOS static)
run: ARCH=${{ matrix.arch }} scripts/build-macos-deps.sh

- name: Build portable xfrpc
run: |
channel="${{ steps.version.outputs.channel }}"
if [[ "$channel" == "prod" ]]; then
optimize="ReleaseSmall"
else
optimize="ReleaseSafe"
fi
# SDKROOT lets the cross build resolve the macOS SDK headers.
export SDKROOT="$(xcrun --show-sdk-path)"
echo "Channel: ${channel}, Optimize: ${optimize}, Target: ${{ matrix.zig_target }}, SDKROOT: ${SDKROOT}"
zig build \
-Dtarget=${{ matrix.zig_target }} \
-Ddep-prefix="$HOME/.cache/xfrpc-macos/${{ matrix.arch }}/sysroot" \
-Doptimize="${optimize}" -Ddep-static
file zig-out/bin/xfrpc
otool -L zig-out/bin/xfrpc
# The x86_64 binary cannot run on the arm64 runner.
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
./zig-out/bin/xfrpc -v
fi

- name: Package artifact
run: |
mkdir -p release-artifacts
archive="xfrpc_${{ steps.version.outputs.version }}_${{ matrix.label }}.tar.gz"
tar -czf "release-artifacts/${archive}" -C zig-out/bin xfrpc
ls -l "release-artifacts/${archive}"

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: xfrpc-${{ matrix.label }}
path: release-artifacts/*.tar.gz

release:
needs: [build, build-macos]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Parse version and channel from tag
id: version
run: |
version="${GITHUB_REF#refs/tags/}"
echo "version=${version}" >> "$GITHUB_OUTPUT"
if [[ "$version" =~ -alpha|-beta ]]; then
echo "channel=pre" >> "$GITHUB_OUTPUT"
else
echo "channel=prod" >> "$GITHUB_OUTPUT"
fi

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true

- name: Generate checksums
run: |
cd artifacts
sha256sum *.tar.gz > checksums.txt
cat checksums.txt

- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
prerelease=""
if [[ "${{ steps.version.outputs.channel }}" != "prod" ]]; then
prerelease="--prerelease"
fi
gh release create "${{ github.ref_name }}" \
--title "xfrpc ${{ steps.version.outputs.version }}" \
--generate-notes \
$prerelease \
artifacts/*
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ Makefile
cmake_install.cmake
install_manifest.txt

# Zig build artifacts
.zig-cache/
zig-out/

# bin generated
xfrpc
xfrp_test_server
Expand All @@ -48,3 +52,4 @@ build
test/iod_client
test/iod_server
test_iod_proto
.local
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ find_package(JSON-C REQUIRED)
find_package(ZLIB REQUIRED)

include_directories(
${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/third_party/tomlc99
${JSON-C_INCLUDE_DIR}
${ZLIB_INCLUDE_DIRS}
)
Expand Down Expand Up @@ -50,6 +52,7 @@ set(CORE_SOURCES
main.c
client.c
config.c
config_toml.c
control.c
ini.c
msg.c
Expand All @@ -63,6 +66,7 @@ set(CORE_SOURCES
common.c
login.c
tls.c
third_party/tomlc99/toml.c
)

set(PROXY_SOURCES
Expand Down
110 changes: 95 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ xfrpc partially compitable with latest frp release feature, It targets to fully

the following table is detail compatible feature:

| Feature | xfrpc | frpc |
| ------------- | ------------- | ---------|
| tcp | Yes | Yes |
| tcpmux | Yes | Yes |
| http | Yes | Yes |
| https | Yes | Yes |
| custom_domains | Yes | Yes |
| subdomain | Yes | Yes |
| socks5 | Yes | No |
| use_encryption | No | Yes |
| use_compression | No | Yes |
| udp | No | Yes |
| p2p | No | Yes |
| xtcp | No | Yes |
| stcp | No | Yes |
| Feature | xfrpc | frpc |
| --------------- | ----- | ---- |
| tcp | Yes | Yes |
| tcpmux | Yes | Yes |
| http | Yes | Yes |
| https | Yes | Yes |
| custom_domains | Yes | Yes |
| subdomain | Yes | Yes |
| socks5 | Yes | No |
| use_encryption | No | Yes |
| use_compression | No | Yes |
| udp | No | Yes |
| p2p | No | Yes |
| xtcp | No | Yes |
| stcp | No | Yes |



Expand Down Expand Up @@ -113,6 +113,86 @@ make
```
This will compile xfrpc and create an executable in the build directory. You can then run xfrpc using the executable by running the appropriate command in terminal.

### Build with Zig (macOS / Linux, optional fully-static binary)

[`build.zig`](build.zig) provides an alternative to CMake that works on macOS
and Linux and can cross-compile a **fully static** Linux binary (zero runtime
dependencies, ideal for OpenWrt / containers). Requires [Zig](https://ziglang.org/) >= 0.16.

Native build (uses the system / Homebrew copies of OpenSSL, libevent, json-c, zlib):

```shell
zig build # debug build -> zig-out/bin/xfrpc
zig build -Doptimize=ReleaseSmall
./zig-out/bin/xfrpc -v
```

On macOS the script auto-detects Homebrew prefixes (`openssl@3`, `libevent`,
`json-c`, `zlib`). If your dependencies live elsewhere, point to them with
`-Ddep-prefix=<install-root>`.

Fully static Linux binary via cross-compilation. First build the dependencies
as musl static libraries (downloads + builds zlib, OpenSSL, libevent, json-c
into a sysroot under `~/.cache/xfrpc-musl`):

```shell
scripts/build-musl-deps.sh # x86_64-linux-musl
# TARGET=aarch64-linux-musl scripts/build-musl-deps.sh # other arches
```

Then link xfrpc against that sysroot:

```shell
zig build -Dtarget=x86_64-linux-musl \
-Ddep-prefix="$HOME/.cache/xfrpc-musl/sysroot" \
-Doptimize=ReleaseSmall -Dstatic
file zig-out/bin/xfrpc # ELF 64-bit ... statically linked, stripped

# smaller build (drops bundled plugins/httpd helpers)
zig build -Dtarget=x86_64-linux-musl \
-Ddep-prefix="$HOME/.cache/xfrpc-musl/sysroot" \
-Doptimize=ReleaseSmall -Dstatic -Dminimal=true
```

The resulting `ReleaseSmall` static binary is roughly 3.5 MB (mostly the
bundled OpenSSL) and runs on any matching Linux kernel without extra libraries.
Release artifacts are intentionally **not** UPX-compressed to keep runtime RSS
and page sharing behavior predictable on memory-constrained devices.

On macOS, a portable binary (deps linked statically, only the system libSystem
left dynamic — no Homebrew needed at runtime) is built with
[`scripts/build-macos-deps.sh`](scripts/build-macos-deps.sh) and `-Ddep-static`:

```shell
ARCH=arm64 scripts/build-macos-deps.sh # or ARCH=x86_64
zig build -Ddep-prefix="$HOME/.cache/xfrpc-macos/arm64/sysroot" \
-Doptimize=ReleaseSmall -Ddep-static
otool -L zig-out/bin/xfrpc # -> only system libSystem / libz
```

To cross-compile the other architecture (e.g. an `x86_64` binary on an Apple
Silicon machine), add `-Dtarget=` and export `SDKROOT` so the SDK headers
resolve:

```shell
ARCH=x86_64 scripts/build-macos-deps.sh
export SDKROOT="$(xcrun --show-sdk-path)"
zig build -Dtarget=x86_64-macos \
-Ddep-prefix="$HOME/.cache/xfrpc-macos/x86_64/sysroot" \
-Doptimize=ReleaseSmall -Ddep-static
```


Pushing a `v*` tag triggers [`.github/workflows/release.yml`](.github/workflows/release.yml),
which builds static binaries for `linux_amd64`, `linux_arm64`, `linux_armv7`
(UPX-compressed on stable tags) plus portable `darwin_amd64` / `darwin_arm64`,
and publishes them as a GitHub Release. Tags containing `-alpha` / `-beta` are
published as pre-releases.

```shell
git tag v5.06.916 && git push origin v5.06.916
```

### Build static binary in Alpine container

Under project root directory
Expand Down
Loading
Loading