mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-07 08:52:09 +03:00
Compare commits
134 Commits
v1.10.0-be
...
stable-nex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
253b41936e | ||
|
|
ce5b4b06b5 | ||
|
|
50f5006c43 | ||
|
|
e42ff22c2e | ||
|
|
578571b972 | ||
|
|
935beca45d | ||
|
|
3e246f1173 | ||
|
|
1bc27a32c2 | ||
|
|
bc2e3960e4 | ||
|
|
9c4ab0bf33 | ||
|
|
27bdef34c7 | ||
|
|
3c00099ed4 | ||
|
|
2babf07f9a | ||
|
|
4795ed712b | ||
|
|
d4cd564dbe | ||
|
|
1676e13d3e | ||
|
|
50576084c6 | ||
|
|
3a94e792a2 | ||
|
|
9f69f41f68 | ||
|
|
e6847ff50e | ||
|
|
2ac2589d14 | ||
|
|
64a94e8144 | ||
|
|
3ed8a5c5d1 | ||
|
|
0a922c6fe3 | ||
|
|
52f3a4226c | ||
|
|
483d9fa503 | ||
|
|
dd9de694f8 | ||
|
|
5cdf5c1d9e | ||
|
|
cec7e47086 | ||
|
|
1e6a3f1f0b | ||
|
|
f0b6818b4c | ||
|
|
3032317918 | ||
|
|
db22f61846 | ||
|
|
8c3a98faa2 | ||
|
|
1e787cb607 | ||
|
|
558585b01d | ||
|
|
6e7ecbd4f5 | ||
|
|
5a661cde67 | ||
|
|
3cc0e87cfb | ||
|
|
effea5a2b3 | ||
|
|
7f168c5ec6 | ||
|
|
0e9129ee3f | ||
|
|
1086d5e665 | ||
|
|
d9102ba599 | ||
|
|
17019f1729 | ||
|
|
6be07ed51f | ||
|
|
af58e3bec0 | ||
|
|
e58b549d0f | ||
|
|
1d81996ceb | ||
|
|
97c47e72c4 | ||
|
|
122be275b0 | ||
|
|
0bb1132034 | ||
|
|
de14337b4b | ||
|
|
1e07633914 | ||
|
|
e3e203844e | ||
|
|
84a102a6ef | ||
|
|
f1c76c4dde | ||
|
|
8df0aa5719 | ||
|
|
21faadb992 | ||
|
|
88099a304a | ||
|
|
f504fb0d46 | ||
|
|
1d517b6ca5 | ||
|
|
b702d0b67a | ||
|
|
a001e30d8b | ||
|
|
cdb93f0bb2 | ||
|
|
718cffea9a | ||
|
|
9585c53e9f | ||
|
|
d66d5cd457 | ||
|
|
8c143feec8 | ||
|
|
419058f466 | ||
|
|
1a6047a61b | ||
|
|
327bb35ddd | ||
|
|
6ed9a06394 | ||
|
|
b80ec55ba0 | ||
|
|
08718112ae | ||
|
|
956ee361df | ||
|
|
e93d0408be | ||
|
|
137832ff3e | ||
|
|
3ede29fb6d | ||
|
|
82ab68b542 | ||
|
|
e55723d84d | ||
|
|
2f4d2d97f9 | ||
|
|
926d6f769e | ||
|
|
846777cd0c | ||
|
|
06533b7a3b | ||
|
|
4a95558c53 | ||
|
|
e39a28ed5a | ||
|
|
b2c708a3e6 | ||
|
|
a9209bb3e5 | ||
|
|
9dc3bb975a | ||
|
|
3a7acaa92a | ||
|
|
6bebe2483b | ||
|
|
93cf134995 | ||
|
|
ff7d8c9ba8 | ||
|
|
50f07b42f6 | ||
|
|
db3a0c636d | ||
|
|
fec38f85cd | ||
|
|
dcb0141646 | ||
|
|
f4f5a3c925 | ||
|
|
9b8d6c1b73 | ||
|
|
2f776168de | ||
|
|
923d3222b0 | ||
|
|
bda93d516b | ||
|
|
7eec3fb57a | ||
|
|
b1d75812c5 | ||
|
|
d44e7d9834 | ||
|
|
369bc7cea3 | ||
|
|
4b7a83da16 | ||
|
|
0f7154afbd | ||
|
|
a06d10c3bc | ||
|
|
63cc6cc76c | ||
|
|
d55c5b5cab | ||
|
|
b624c2dcc7 | ||
|
|
9415444ebd | ||
|
|
95606191d8 | ||
|
|
e586d9e9bc | ||
|
|
8c7eaa4477 | ||
|
|
8464c8cb7c | ||
|
|
39d7127651 | ||
|
|
e2077009c4 | ||
|
|
700a8eb425 | ||
|
|
3b0cba0852 | ||
|
|
f5554dd8b8 | ||
|
|
4d0362d530 | ||
|
|
97ccd2ca04 | ||
|
|
1ed6654ad4 | ||
|
|
5385f75f53 | ||
|
|
ad97d4e11f | ||
|
|
09d4e91b77 | ||
|
|
3dbdda9555 | ||
|
|
1f4ed6ff8f | ||
|
|
6ddbe19bc0 | ||
|
|
d7205ecc60 | ||
|
|
9e243e0ff9 |
599
.github/workflows/build.yml
vendored
Normal file
599
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version name"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
build:
|
||||||
|
description: "Build type"
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
default: "All"
|
||||||
|
options:
|
||||||
|
- All
|
||||||
|
- Binary
|
||||||
|
- Android
|
||||||
|
- Apple
|
||||||
|
- app-store
|
||||||
|
- iOS
|
||||||
|
- macOS
|
||||||
|
- tvOS
|
||||||
|
- macOS-standalone
|
||||||
|
- publish-android
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main-next
|
||||||
|
- dev-next
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
calculate_version:
|
||||||
|
name: Calculate version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.23
|
||||||
|
- name: Check input version
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
echo "version=${{ inputs.version }}"
|
||||||
|
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Calculate version
|
||||||
|
if: github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/read_tag --nightly
|
||||||
|
- name: Set outputs
|
||||||
|
id: outputs
|
||||||
|
run: |-
|
||||||
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
build:
|
||||||
|
name: Build binary
|
||||||
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: linux_386
|
||||||
|
goos: linux
|
||||||
|
goarch: 386
|
||||||
|
- name: linux_amd64
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
- name: linux_arm64
|
||||||
|
goos: linux
|
||||||
|
goarch: arm64
|
||||||
|
- name: linux_arm
|
||||||
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: 6
|
||||||
|
- name: linux_arm_v7
|
||||||
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
|
goarm: 7
|
||||||
|
- name: linux_s390x
|
||||||
|
goos: linux
|
||||||
|
goarch: s390x
|
||||||
|
- name: linux_riscv64
|
||||||
|
goos: linux
|
||||||
|
goarch: riscv64
|
||||||
|
- name: linux_mips64le
|
||||||
|
goos: linux
|
||||||
|
goarch: mips64le
|
||||||
|
- name: windows_amd64
|
||||||
|
goos: windows
|
||||||
|
goarch: amd64
|
||||||
|
require_legacy_go: true
|
||||||
|
- name: windows_386
|
||||||
|
goos: windows
|
||||||
|
goarch: 386
|
||||||
|
require_legacy_go: true
|
||||||
|
- name: windows_arm64
|
||||||
|
goos: windows
|
||||||
|
goarch: arm64
|
||||||
|
- name: darwin_arm64
|
||||||
|
goos: darwin
|
||||||
|
goarch: arm64
|
||||||
|
- name: darwin_amd64
|
||||||
|
goos: darwin
|
||||||
|
goarch: amd64
|
||||||
|
require_legacy_go: true
|
||||||
|
- name: android_arm64
|
||||||
|
goos: android
|
||||||
|
goarch: arm64
|
||||||
|
- name: android_arm
|
||||||
|
goos: android
|
||||||
|
goarch: arm
|
||||||
|
goarm: 7
|
||||||
|
- name: android_amd64
|
||||||
|
goos: android
|
||||||
|
goarch: amd64
|
||||||
|
- name: android_386
|
||||||
|
goos: android
|
||||||
|
goarch: 386
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.23
|
||||||
|
- name: Cache legacy Go
|
||||||
|
if: matrix.require_legacy_go
|
||||||
|
id: cache-legacy-go
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/go1.20.14
|
||||||
|
key: go120
|
||||||
|
- name: Setup legacy Go
|
||||||
|
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
|
run: |-
|
||||||
|
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
||||||
|
tar -xzf go1.20.14.linux-amd64.tar.gz
|
||||||
|
mv go $HOME/go/go1.20.14
|
||||||
|
- name: Setup Android NDK
|
||||||
|
if: matrix.goos == 'android'
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28-beta2
|
||||||
|
local-cache: true
|
||||||
|
- name: Setup Goreleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
distribution: goreleaser-pro
|
||||||
|
version: latest
|
||||||
|
install-only: true
|
||||||
|
- name: Extract signing key
|
||||||
|
run: |-
|
||||||
|
mkdir -p $HOME/.gnupg
|
||||||
|
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||||
|
${{ secrets.GPG_KEY }}
|
||||||
|
EOF
|
||||||
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Build
|
||||||
|
if: matrix.goos != 'android'
|
||||||
|
run: |-
|
||||||
|
goreleaser release --clean --split
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
GOPATH: ${{ env.HOME }}/go
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
|
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||||
|
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
- name: Build Android
|
||||||
|
if: matrix.goos == 'android'
|
||||||
|
run: |-
|
||||||
|
go install -v ./cmd/internal/build
|
||||||
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
|
||||||
|
env:
|
||||||
|
BUILD_GOOS: ${{ matrix.goos }}
|
||||||
|
BUILD_GOARCH: ${{ matrix.goarch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
|
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||||
|
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
- name: Upload artifact
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-${{ matrix.name }}
|
||||||
|
path: 'dist'
|
||||||
|
build_android:
|
||||||
|
name: Build Android
|
||||||
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: 'recursive'
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.23
|
||||||
|
- name: Setup Android NDK
|
||||||
|
id: setup-ndk
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28-beta2
|
||||||
|
- name: Setup OpenJDK
|
||||||
|
run: |-
|
||||||
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
|
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Build library
|
||||||
|
run: |-
|
||||||
|
make lib_install
|
||||||
|
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||||
|
make lib_android
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
- name: Checkout main branch
|
||||||
|
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
cd clients/android
|
||||||
|
git checkout main
|
||||||
|
- name: Checkout dev branch
|
||||||
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
cd clients/android
|
||||||
|
git checkout dev
|
||||||
|
- name: Gradle cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.gradle
|
||||||
|
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
- name: Build
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_android_version --ci
|
||||||
|
mkdir clients/android/app/libs
|
||||||
|
cp libbox.aar clients/android/app/libs
|
||||||
|
cd clients/android
|
||||||
|
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
|
- name: Prepare upload
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
mkdir -p dist/release
|
||||||
|
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
||||||
|
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
|
||||||
|
- name: Upload artifact
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-android-apks
|
||||||
|
path: 'dist'
|
||||||
|
publish_android:
|
||||||
|
name: Publish Android
|
||||||
|
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: 'recursive'
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.23
|
||||||
|
- name: Setup Android NDK
|
||||||
|
id: setup-ndk
|
||||||
|
uses: nttld/setup-ndk@v1
|
||||||
|
with:
|
||||||
|
ndk-version: r28-beta2
|
||||||
|
- name: Setup OpenJDK
|
||||||
|
run: |-
|
||||||
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
|
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
- name: Build library
|
||||||
|
run: |-
|
||||||
|
make lib_install
|
||||||
|
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||||
|
make lib_android
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
- name: Checkout main branch
|
||||||
|
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
cd clients/android
|
||||||
|
git checkout main
|
||||||
|
- name: Checkout dev branch
|
||||||
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
cd clients/android
|
||||||
|
git checkout dev
|
||||||
|
- name: Gradle cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.gradle
|
||||||
|
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
- name: Build
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_android_version --ci
|
||||||
|
mkdir clients/android/app/libs
|
||||||
|
cp libbox.aar clients/android/app/libs
|
||||||
|
cd clients/android
|
||||||
|
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
||||||
|
./gradlew :app:publishPlayReleaseBundle
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
|
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||||
|
build_apple:
|
||||||
|
name: Build Apple clients
|
||||||
|
runs-on: macos-15
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: iOS
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
|
||||||
|
platform: ios
|
||||||
|
scheme: SFI
|
||||||
|
destination: 'generic/platform=iOS'
|
||||||
|
archive: build/SFI.xcarchive
|
||||||
|
upload: SFI/Upload.plist
|
||||||
|
- name: macOS
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
|
||||||
|
platform: macos
|
||||||
|
scheme: SFM
|
||||||
|
destination: 'generic/platform=macOS'
|
||||||
|
archive: build/SFM.xcarchive
|
||||||
|
upload: SFI/Upload.plist
|
||||||
|
- name: tvOS
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
|
||||||
|
platform: tvos
|
||||||
|
scheme: SFT
|
||||||
|
destination: 'generic/platform=tvOS'
|
||||||
|
archive: build/SFT.xcarchive
|
||||||
|
upload: SFI/Upload.plist
|
||||||
|
- name: macOS-standalone
|
||||||
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
|
||||||
|
platform: macos
|
||||||
|
scheme: SFM.System
|
||||||
|
destination: 'generic/platform=macOS'
|
||||||
|
archive: build/SFM.System.xcarchive
|
||||||
|
export: SFM.System/Export.plist
|
||||||
|
export_path: build/SFM.System
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
if: matrix.if
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: 'recursive'
|
||||||
|
- name: Setup Go
|
||||||
|
if: matrix.if
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ^1.23
|
||||||
|
- name: Setup Xcode stable
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
|
run: |-
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
|
- name: Setup Xcode beta
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
|
- name: Set tag
|
||||||
|
if: matrix.if
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Checkout main branch
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
cd clients/apple
|
||||||
|
git checkout main
|
||||||
|
- name: Checkout dev branch
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
cd clients/apple
|
||||||
|
git checkout dev
|
||||||
|
- name: Setup certificates
|
||||||
|
if: matrix.if
|
||||||
|
run: |-
|
||||||
|
CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12
|
||||||
|
KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db
|
||||||
|
echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH
|
||||||
|
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
|
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
|
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||||
|
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
|
||||||
|
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||||
|
|
||||||
|
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
||||||
|
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
||||||
|
|
||||||
|
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||||
|
mkdir -p "$PROFILES_PATH"
|
||||||
|
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
||||||
|
|
||||||
|
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
||||||
|
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
||||||
|
|
||||||
|
xcrun notarytool store-credentials "notarytool-password" \
|
||||||
|
--key $ASC_KEY_PATH \
|
||||||
|
--key-id $ASC_KEY_ID \
|
||||||
|
--issuer $ASC_KEY_ISSUER_ID
|
||||||
|
|
||||||
|
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
||||||
|
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
||||||
|
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
||||||
|
env:
|
||||||
|
CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
|
||||||
|
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||||
|
KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}
|
||||||
|
PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}
|
||||||
|
ASC_KEY: ${{ secrets.ASC_KEY }}
|
||||||
|
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||||
|
ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
|
||||||
|
- name: Build library
|
||||||
|
if: matrix.if
|
||||||
|
run: |-
|
||||||
|
make lib_install
|
||||||
|
export PATH="$PATH:$(go env GOPATH)/bin"
|
||||||
|
go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
|
||||||
|
mv Libbox.xcframework clients/apple
|
||||||
|
- name: Update macOS version
|
||||||
|
if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
||||||
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
||||||
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||||
|
- name: Build
|
||||||
|
if: matrix.if
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_apple_version --ci
|
||||||
|
cd clients/apple
|
||||||
|
xcodebuild archive \
|
||||||
|
-scheme "${{ matrix.scheme }}" \
|
||||||
|
-configuration Release \
|
||||||
|
-destination "${{ matrix.destination }}" \
|
||||||
|
-archivePath "${{ matrix.archive }}" \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
-authenticationKeyPath $ASC_KEY_PATH \
|
||||||
|
-authenticationKeyID $ASC_KEY_ID \
|
||||||
|
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||||
|
- name: Upload to App Store Connect
|
||||||
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
|
||||||
|
cd clients/apple
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "${{ matrix.archive }}" \
|
||||||
|
-exportOptionsPlist ${{ matrix.upload }} \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
-authenticationKeyPath $ASC_KEY_PATH \
|
||||||
|
-authenticationKeyID $ASC_KEY_ID \
|
||||||
|
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||||
|
- name: Publish to TestFlight
|
||||||
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
||||||
|
- name: Build image
|
||||||
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
|
run: |-
|
||||||
|
pushd clients/apple
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "${{ matrix.archive }}" \
|
||||||
|
-exportOptionsPlist ${{ matrix.export }} \
|
||||||
|
-exportPath "${{ matrix.export_path }}"
|
||||||
|
brew install create-dmg
|
||||||
|
create-dmg \
|
||||||
|
--volname "sing-box" \
|
||||||
|
--volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \
|
||||||
|
--icon "SFM.app" 0 0 \
|
||||||
|
--hide-extension "SFM.app" \
|
||||||
|
--app-drop-link 0 0 \
|
||||||
|
--skip-jenkins \
|
||||||
|
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
||||||
|
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
||||||
|
cd "${{ matrix.archive }}"
|
||||||
|
zip -r SFM.dSYMs.zip dSYMs
|
||||||
|
popd
|
||||||
|
|
||||||
|
mkdir -p dist/release
|
||||||
|
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
|
||||||
|
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
|
- name: Upload image
|
||||||
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-macos-dmg
|
||||||
|
path: 'dist'
|
||||||
|
upload:
|
||||||
|
name: Upload builds
|
||||||
|
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- calculate_version
|
||||||
|
- build
|
||||||
|
- build_android
|
||||||
|
- build_apple
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Goreleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
distribution: goreleaser-pro
|
||||||
|
version: latest
|
||||||
|
install-only: true
|
||||||
|
- name: Cache ghr
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-ghr
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/bin/ghr
|
||||||
|
key: ghr
|
||||||
|
- name: Setup ghr
|
||||||
|
if: steps.cache-ghr.outputs.cache-hit != 'true'
|
||||||
|
run: |-
|
||||||
|
cd $HOME
|
||||||
|
git clone https://github.com/nekohasekai/ghr ghr
|
||||||
|
cd ghr
|
||||||
|
go install -v .
|
||||||
|
- name: Set tag
|
||||||
|
run: |-
|
||||||
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
|
- name: Download builds
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Merge builds
|
||||||
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||||
|
run: |-
|
||||||
|
goreleaser continue --merge --skip publish
|
||||||
|
mkdir -p dist/release
|
||||||
|
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
|
- name: Upload builds
|
||||||
|
if: ${{ env.PUBLISHED == 'false' }}
|
||||||
|
run: |-
|
||||||
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Replace builds
|
||||||
|
if: ${{ env.PUBLISHED != 'false' }}
|
||||||
|
run: |-
|
||||||
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
|
ghr --replace -p 5 "v${VERSION}" dist/release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
219
.github/workflows/debug.yml
vendored
219
.github/workflows/debug.yml
vendored
@@ -1,219 +0,0 @@
|
|||||||
name: Debug build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- stable-next
|
|
||||||
- main-next
|
|
||||||
- dev-next
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- '.github/**'
|
|
||||||
- '!.github/workflows/debug.yml'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- stable-next
|
|
||||||
- main-next
|
|
||||||
- dev-next
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Debug build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.23
|
|
||||||
- name: Run Test
|
|
||||||
run: |
|
|
||||||
go test -v ./...
|
|
||||||
build_go120:
|
|
||||||
name: Debug build (Go 1.20)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.20
|
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go120-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: Run Test
|
|
||||||
run: make ci_build_go120
|
|
||||||
build_go121:
|
|
||||||
name: Debug build (Go 1.21)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.21
|
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go121-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: Run Test
|
|
||||||
run: make ci_build
|
|
||||||
build_go122:
|
|
||||||
name: Debug build (Go 1.22)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.22
|
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go122-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: Run Test
|
|
||||||
run: make ci_build
|
|
||||||
cross:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
# windows
|
|
||||||
- name: windows-amd64
|
|
||||||
goos: windows
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
- name: windows-amd64-v3
|
|
||||||
goos: windows
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v3
|
|
||||||
- name: windows-386
|
|
||||||
goos: windows
|
|
||||||
goarch: 386
|
|
||||||
- name: windows-arm64
|
|
||||||
goos: windows
|
|
||||||
goarch: arm64
|
|
||||||
- name: windows-arm32v7
|
|
||||||
goos: windows
|
|
||||||
goarch: arm
|
|
||||||
goarm: 7
|
|
||||||
|
|
||||||
# linux
|
|
||||||
- name: linux-amd64
|
|
||||||
goos: linux
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
- name: linux-amd64-v3
|
|
||||||
goos: linux
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v3
|
|
||||||
- name: linux-386
|
|
||||||
goos: linux
|
|
||||||
goarch: 386
|
|
||||||
- name: linux-arm64
|
|
||||||
goos: linux
|
|
||||||
goarch: arm64
|
|
||||||
- name: linux-armv5
|
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
|
||||||
goarm: 5
|
|
||||||
- name: linux-armv6
|
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
|
||||||
goarm: 6
|
|
||||||
- name: linux-armv7
|
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
|
||||||
goarm: 7
|
|
||||||
- name: linux-mips-softfloat
|
|
||||||
goos: linux
|
|
||||||
goarch: mips
|
|
||||||
gomips: softfloat
|
|
||||||
- name: linux-mips-hardfloat
|
|
||||||
goos: linux
|
|
||||||
goarch: mips
|
|
||||||
gomips: hardfloat
|
|
||||||
- name: linux-mipsel-softfloat
|
|
||||||
goos: linux
|
|
||||||
goarch: mipsle
|
|
||||||
gomips: softfloat
|
|
||||||
- name: linux-mipsel-hardfloat
|
|
||||||
goos: linux
|
|
||||||
goarch: mipsle
|
|
||||||
gomips: hardfloat
|
|
||||||
- name: linux-mips64
|
|
||||||
goos: linux
|
|
||||||
goarch: mips64
|
|
||||||
- name: linux-mips64el
|
|
||||||
goos: linux
|
|
||||||
goarch: mips64le
|
|
||||||
- name: linux-s390x
|
|
||||||
goos: linux
|
|
||||||
goarch: s390x
|
|
||||||
# darwin
|
|
||||||
- name: darwin-amd64
|
|
||||||
goos: darwin
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
- name: darwin-amd64-v3
|
|
||||||
goos: darwin
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v3
|
|
||||||
- name: darwin-arm64
|
|
||||||
goos: darwin
|
|
||||||
goarch: arm64
|
|
||||||
# freebsd
|
|
||||||
- name: freebsd-amd64
|
|
||||||
goos: freebsd
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
- name: freebsd-amd64-v3
|
|
||||||
goos: freebsd
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v3
|
|
||||||
- name: freebsd-386
|
|
||||||
goos: freebsd
|
|
||||||
goarch: 386
|
|
||||||
- name: freebsd-arm64
|
|
||||||
goos: freebsd
|
|
||||||
goarch: arm64
|
|
||||||
fail-fast: true
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GOOS: ${{ matrix.goos }}
|
|
||||||
GOARCH: ${{ matrix.goarch }}
|
|
||||||
GOAMD64: ${{ matrix.goamd64 }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
TAGS: with_clash_api,with_quic
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.21
|
|
||||||
- name: Build
|
|
||||||
id: build
|
|
||||||
run: make
|
|
||||||
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@@ -28,10 +28,21 @@ jobs:
|
|||||||
- linux/riscv64
|
- linux/riscv64
|
||||||
- linux/s390x
|
- linux/s390x
|
||||||
steps:
|
steps:
|
||||||
|
- name: Get commit to build
|
||||||
|
id: ref
|
||||||
|
run: |-
|
||||||
|
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||||
|
ref="${{ github.ref_name }}"
|
||||||
|
else
|
||||||
|
ref="${{ github.event.inputs.tag }}"
|
||||||
|
fi
|
||||||
|
echo "ref=$ref"
|
||||||
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
run: |
|
run: |
|
||||||
platform=${{ matrix.platform }}
|
platform=${{ matrix.platform }}
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
|||||||
3
.github/workflows/linux.yml
vendored
3
.github/workflows/linux.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -22,7 +22,6 @@ jobs:
|
|||||||
mkdir -p $HOME/.gnupg
|
mkdir -p $HOME/.gnupg
|
||||||
cat > $HOME/.gnupg/sagernet.key <<EOF
|
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||||
${{ secrets.GPG_KEY }}
|
${{ secrets.GPG_KEY }}
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
|
||||||
EOF
|
EOF
|
||||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ linters-settings:
|
|||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.23"
|
go: "1.23"
|
||||||
|
build-tags:
|
||||||
|
- with_gvisor
|
||||||
|
- with_quic
|
||||||
|
- with_dhcp
|
||||||
|
- with_wireguard
|
||||||
|
- with_ech
|
||||||
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-dirs:
|
exclude-dirs:
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ builds:
|
|||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||||
@@ -48,10 +49,19 @@ nfpms:
|
|||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: config
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
deb:
|
deb:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
version: 2
|
||||||
project_name: sing-box
|
project_name: sing-box
|
||||||
builds:
|
builds:
|
||||||
- &template
|
- &template
|
||||||
@@ -25,13 +26,13 @@ builds:
|
|||||||
targets:
|
targets:
|
||||||
- linux_386
|
- linux_386
|
||||||
- linux_amd64_v1
|
- linux_amd64_v1
|
||||||
- linux_amd64_v3
|
|
||||||
- linux_arm64
|
- linux_arm64
|
||||||
|
- linux_arm_6
|
||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_amd64_v3
|
|
||||||
- windows_386
|
- windows_386
|
||||||
- windows_arm64
|
- windows_arm64
|
||||||
- darwin_amd64_v1
|
- darwin_amd64_v1
|
||||||
@@ -88,8 +89,6 @@ builds:
|
|||||||
- android_arm64
|
- android_arm64
|
||||||
- android_386
|
- android_386
|
||||||
- android_amd64
|
- android_amd64
|
||||||
snapshot:
|
|
||||||
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
|
||||||
archives:
|
archives:
|
||||||
- &template
|
- &template
|
||||||
id: archive
|
id: archive
|
||||||
@@ -103,7 +102,7 @@ archives:
|
|||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
- id: archive-legacy
|
- id: archive-legacy
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
@@ -112,7 +111,7 @@ archives:
|
|||||||
nfpms:
|
nfpms:
|
||||||
- id: package
|
- id: package
|
||||||
package_name: sing-box
|
package_name: sing-box
|
||||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
builds:
|
builds:
|
||||||
- main
|
- main
|
||||||
homepage: https://sing-box.sagernet.org/
|
homepage: https://sing-box.sagernet.org/
|
||||||
@@ -123,15 +122,26 @@ nfpms:
|
|||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
- archlinux
|
||||||
|
# - apk
|
||||||
|
# - ipk
|
||||||
priority: extra
|
priority: extra
|
||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
dst: /etc/sing-box/config.json
|
dst: /etc/sing-box/config.json
|
||||||
type: config
|
type: config
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
- src: release/config/sing-box.service
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
deb:
|
deb:
|
||||||
@@ -143,13 +153,34 @@ nfpms:
|
|||||||
signature:
|
signature:
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
overrides:
|
overrides:
|
||||||
deb:
|
apk:
|
||||||
conflicts:
|
contents:
|
||||||
- sing-box-beta
|
- src: release/config/config.json
|
||||||
rpm:
|
dst: /etc/sing-box/config.json
|
||||||
conflicts:
|
type: config
|
||||||
- sing-box-beta
|
|
||||||
|
|
||||||
|
- src: release/config/sing-box.initd
|
||||||
|
dst: /etc/init.d/sing-box
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
- src: LICENSE
|
||||||
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
ipk:
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: config
|
||||||
|
|
||||||
|
- src: release/config/openwrt.init
|
||||||
|
dst: /etc/init.d/sing-box
|
||||||
|
- src: release/config/openwrt.conf
|
||||||
|
dst: /etc/config/sing-box
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
@@ -170,3 +201,5 @@ release:
|
|||||||
- archive
|
- archive
|
||||||
- package
|
- package
|
||||||
skip_upload: true
|
skip_upload: true
|
||||||
|
partial:
|
||||||
|
by: target
|
||||||
101
Makefile
101
Makefile
@@ -27,6 +27,9 @@ ci_build:
|
|||||||
go build $(PARAMS) $(MAIN)
|
go build $(PARAMS) $(MAIN)
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
|
generate_completions:
|
||||||
|
go run -v --tags generate,generate_completions $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
@@ -66,10 +69,9 @@ release:
|
|||||||
dist/*.deb \
|
dist/*.deb \
|
||||||
dist/*.rpm \
|
dist/*.rpm \
|
||||||
dist/*_amd64.pkg.tar.zst \
|
dist/*_amd64.pkg.tar.zst \
|
||||||
dist/*_amd64v3.pkg.tar.zst \
|
|
||||||
dist/*_arm64.pkg.tar.zst \
|
dist/*_arm64.pkg.tar.zst \
|
||||||
dist/release
|
dist/release
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
rm -r dist/release
|
rm -r dist/release
|
||||||
|
|
||||||
release_repo:
|
release_repo:
|
||||||
@@ -88,21 +90,21 @@ upload_android:
|
|||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
|
||||||
ghr --replace --draft --prerelease -p 3 "v${VERSION}" dist/release_android
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||||
rm -rf dist/release_android
|
rm -rf dist/release_android
|
||||||
|
|
||||||
release_android: lib_android update_android_version build_android upload_android
|
release_android: lib_android update_android_version build_android upload_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
|
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||||
|
|
||||||
publish_android_appcenter:
|
|
||||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
|
||||||
|
|
||||||
|
# TODO: find why and remove `-destination 'generic/platform=iOS'`
|
||||||
|
# TODO: remove xcode clean when fix control widget fixed
|
||||||
build_ios:
|
build_ios:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFI.xcarchive && \
|
rm -rf build/SFI.xcarchive && \
|
||||||
xcodebuild archive -scheme SFI -configuration Release -archivePath build/SFI.xcarchive
|
xcodebuild clean -scheme SFI && \
|
||||||
|
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_ios_app_store:
|
upload_ios_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -113,7 +115,7 @@ release_ios: build_ios upload_ios_app_store
|
|||||||
build_macos:
|
build_macos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFM.xcarchive && \
|
rm -rf build/SFM.xcarchive && \
|
||||||
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive
|
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_macos_app_store:
|
upload_macos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -121,36 +123,55 @@ upload_macos_app_store:
|
|||||||
|
|
||||||
release_macos: build_macos upload_macos_app_store
|
release_macos: build_macos upload_macos_app_store
|
||||||
|
|
||||||
build_macos_independent:
|
build_macos_standalone:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.System.xcarchive && \
|
rm -rf build/SFM.System.xcarchive && \
|
||||||
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive
|
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
notarize_macos_independent:
|
build_macos_dmg:
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFM.System.xcarchive" -exportOptionsPlist SFM.System/Upload.plist -allowProvisioningUpdates
|
|
||||||
|
|
||||||
wait_notarize_macos_independent:
|
|
||||||
sleep 60
|
|
||||||
|
|
||||||
export_macos_independent:
|
|
||||||
rm -rf dist/SFM
|
rm -rf dist/SFM
|
||||||
mkdir -p dist/SFM
|
mkdir -p dist/SFM
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportNotarizedApp -archivePath build/SFM.System.xcarchive -exportPath "../sing-box/dist/SFM"
|
rm -rf build/SFM.System && \
|
||||||
|
rm -rf build/SFM.dmg && \
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "build/SFM.System.xcarchive" \
|
||||||
|
-exportOptionsPlist SFM.System/Export.plist -allowProvisioningUpdates \
|
||||||
|
-exportPath "build/SFM.System" && \
|
||||||
|
create-dmg \
|
||||||
|
--volname "sing-box" \
|
||||||
|
--volicon "build/SFM.System/SFM.app/Contents/Resources/AppIcon.icns" \
|
||||||
|
--icon "SFM.app" 0 0 \
|
||||||
|
--hide-extension "SFM.app" \
|
||||||
|
--app-drop-link 0 0 \
|
||||||
|
--skip-jenkins \
|
||||||
|
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
||||||
|
|
||||||
upload_macos_independent:
|
notarize_macos_dmg:
|
||||||
|
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
|
||||||
|
--keychain-profile "notarytool-password" \
|
||||||
|
--no-s3-acceleration
|
||||||
|
|
||||||
|
upload_macos_dmg:
|
||||||
cd dist/SFM && \
|
cd dist/SFM && \
|
||||||
rm -f *.zip && \
|
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||||
zip -ry "SFM-${VERSION}-universal.zip" SFM.app && \
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" *.zip
|
|
||||||
|
|
||||||
release_macos_independent: build_macos_independent notarize_macos_independent wait_notarize_macos_independent export_macos_independent upload_macos_independent
|
upload_macos_dsyms:
|
||||||
|
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
||||||
|
zip -r SFM.dSYMs.zip dSYMs && \
|
||||||
|
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
||||||
|
popd && \
|
||||||
|
cd dist/SFM && \
|
||||||
|
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
|
|
||||||
|
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||||
|
|
||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.xcarchive && \
|
rm -rf build/SFT.xcarchive && \
|
||||||
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive
|
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_tvos_app_store:
|
upload_tvos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -161,10 +182,22 @@ release_tvos: build_tvos upload_tvos_app_store
|
|||||||
update_apple_version:
|
update_apple_version:
|
||||||
go run ./cmd/internal/update_apple_version
|
go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_independent
|
update_macos_version:
|
||||||
|
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
|
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||||
|
|
||||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||||
|
|
||||||
|
publish_testflight:
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_testflight
|
||||||
|
|
||||||
|
prepare_app_store:
|
||||||
|
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
||||||
|
|
||||||
|
publish_app_store:
|
||||||
|
go run -v ./cmd/internal/app_store_connect publish_app_store
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v ./... && \
|
@go test -v ./... && \
|
||||||
cd test && \
|
cd test && \
|
||||||
@@ -180,16 +213,22 @@ test_stdio:
|
|||||||
lib_android:
|
lib_android:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
go run ./cmd/internal/build_libbox -target android
|
||||||
|
|
||||||
|
lib_android_debug:
|
||||||
|
go run ./cmd/internal/build_libbox -target android -debug
|
||||||
|
|
||||||
|
lib_apple:
|
||||||
|
go run ./cmd/internal/build_libbox -target apple
|
||||||
|
|
||||||
lib_ios:
|
lib_ios:
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
||||||
|
|
||||||
lib:
|
lib:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
go run ./cmd/internal/build_libbox -target android
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.3
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.4
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|||||||
@@ -91,15 +91,6 @@ func ContextFrom(ctx context.Context) *InboundContext {
|
|||||||
return metadata.(*InboundContext)
|
return metadata.(*InboundContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|
||||||
metadata := ContextFrom(ctx)
|
|
||||||
if metadata != nil {
|
|
||||||
return ctx, metadata
|
|
||||||
}
|
|
||||||
metadata = new(InboundContext)
|
|
||||||
return WithContext(ctx, metadata), metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||||
var newMetadata InboundContext
|
var newMetadata InboundContext
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -98,7 +102,7 @@ type DNSRule interface {
|
|||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
Name() string
|
Name() string
|
||||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||||
PostStart() error
|
PostStart() error
|
||||||
Metadata() RuleSetMetadata
|
Metadata() RuleSetMetadata
|
||||||
ExtractIPSet() []*netipx.IPSet
|
ExtractIPSet() []*netipx.IPSet
|
||||||
@@ -118,10 +122,42 @@ type RuleSetMetadata struct {
|
|||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
|
type HTTPStartContext struct {
|
||||||
|
access sync.Mutex
|
||||||
|
httpClientCache map[string]*http.Client
|
||||||
|
}
|
||||||
|
|
||||||
type RuleSetStartContext interface {
|
func NewHTTPStartContext() *HTTPStartContext {
|
||||||
HTTPClient(detour string, dialer N.Dialer) *http.Client
|
return &HTTPStartContext{
|
||||||
Close()
|
httpClientCache: make(map[string]*http.Client),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
if httpClient, loaded := c.httpClientCache[detour]; loaded {
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: C.TCPTimeout,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.httpClientCache[detour] = httpClient
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPStartContext) Close() {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
for _, client := range c.httpClientCache {
|
||||||
|
client.CloseIdleConnections()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
|
|||||||
Submodule clients/android updated: 440aaa9a1a...b17fb6d857
Submodule clients/apple updated: aa4ce98421...64a4614aca
445
cmd/internal/app_store_connect/main.go
Normal file
445
cmd/internal/app_store_connect/main.go
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/asc-go/asc"
|
||||||
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "next_macos_project_version":
|
||||||
|
err := fetchMacOSVersion(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "publish_testflight":
|
||||||
|
err := publishTestflight(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "cancel_app_store":
|
||||||
|
err := cancelAppStore(ctx, os.Args[2])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "prepare_app_store":
|
||||||
|
err := prepareAppStore(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "publish_app_store":
|
||||||
|
err := publishAppStore(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatal("unknown action: ", os.Args[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
appID = "6673731168"
|
||||||
|
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createClient(expireDuration time.Duration) *asc.Client {
|
||||||
|
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return asc.NewClient(tokenConfig.Client())
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchMacOSVersion(ctx context.Context) error {
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{"MAC_OS"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var versionID string
|
||||||
|
findVersion:
|
||||||
|
for _, version := range versions.Data {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStateReadyForSale,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
versionID = version.ID
|
||||||
|
break findVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if versionID == "" {
|
||||||
|
return E.New("no version found")
|
||||||
|
}
|
||||||
|
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "parse version code")
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishTestflight(ctx context.Context) error {
|
||||||
|
tagVersion, err := build_shared.ReadTagVersion()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tag := tagVersion.VersionString()
|
||||||
|
client := createClient(10 * time.Minute)
|
||||||
|
|
||||||
|
log.Info(tag, " list build IDs")
|
||||||
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
||||||
|
return it.ID
|
||||||
|
})
|
||||||
|
var platforms []asc.Platform
|
||||||
|
if len(os.Args) == 3 {
|
||||||
|
switch os.Args[2] {
|
||||||
|
case "ios":
|
||||||
|
platforms = []asc.Platform{asc.PlatformIOS}
|
||||||
|
case "macos":
|
||||||
|
platforms = []asc.Platform{asc.PlatformMACOS}
|
||||||
|
case "tvos":
|
||||||
|
platforms = []asc.Platform{asc.PlatformTVOS}
|
||||||
|
default:
|
||||||
|
return E.New("unknown platform: ", os.Args[2])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
platforms = []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, platform := range platforms {
|
||||||
|
log.Info(string(platform), " list builds")
|
||||||
|
for {
|
||||||
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
|
FilterApp: []string{appID},
|
||||||
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
build := builds.Data[0]
|
||||||
|
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||||
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *build.Attributes.ProcessingState != "VALID" {
|
||||||
|
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list localizations")
|
||||||
|
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
|
||||||
|
return *it.Attributes.Locale == "en-US"
|
||||||
|
})
|
||||||
|
if localization.ID == "" {
|
||||||
|
log.Fatal(string(platform), " ", tag, " no en-US localization found")
|
||||||
|
}
|
||||||
|
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " update localization")
|
||||||
|
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
|
||||||
|
F.ToString("sing-box ", tagVersion.String()),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
|
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
||||||
|
log.Info("waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list submissions")
|
||||||
|
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
||||||
|
FilterBuild: []string{build.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(betaSubmissions.Data) == 0 {
|
||||||
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
|
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelAppStore(ctx context.Context, platform string) error {
|
||||||
|
switch platform {
|
||||||
|
case "ios":
|
||||||
|
platform = string(asc.PlatformIOS)
|
||||||
|
case "macos":
|
||||||
|
platform = string(asc.PlatformMACOS)
|
||||||
|
case "tvos":
|
||||||
|
platform = string(asc.PlatformTVOS)
|
||||||
|
}
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for {
|
||||||
|
log.Info(platform, " list versions")
|
||||||
|
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if isRetryable(response) {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
if version.ID == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Info(platform, " ", tag, " get submission")
|
||||||
|
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if response != nil && response.StatusCode == http.StatusNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if isRetryable(response) {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info(platform, " ", tag, " delete submission")
|
||||||
|
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareAppStore(ctx context.Context) error {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for _, platform := range []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
} {
|
||||||
|
log.Info(string(platform), " list versions")
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
log.Info(string(platform), " ", tag, " list builds")
|
||||||
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
|
FilterApp: []string{appID},
|
||||||
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(builds.Data) == 0 {
|
||||||
|
log.Fatal(platform, " ", tag, " no build found")
|
||||||
|
}
|
||||||
|
buildID := common.Ptr(builds.Data[0].ID)
|
||||||
|
if version.ID == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " create version")
|
||||||
|
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
|
||||||
|
Platform: platform,
|
||||||
|
VersionString: tag,
|
||||||
|
}, appID, buildID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version = newVersion.Data
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Info(string(platform), " ", tag, " check build")
|
||||||
|
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission,
|
||||||
|
asc.AppStoreVersionStateRejected,
|
||||||
|
asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if submission != nil {
|
||||||
|
log.Info(string(platform), " ", tag, " delete submission")
|
||||||
|
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " update build")
|
||||||
|
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusNoContent {
|
||||||
|
response.Write(os.Stderr)
|
||||||
|
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission,
|
||||||
|
asc.AppStoreVersionStateRejected,
|
||||||
|
asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview,
|
||||||
|
asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list localization")
|
||||||
|
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
|
||||||
|
return *it.Attributes.Locale == "en-US"
|
||||||
|
})
|
||||||
|
if localization.ID == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " no en-US localization found")
|
||||||
|
}
|
||||||
|
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " update localization")
|
||||||
|
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
|
||||||
|
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
|
||||||
|
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
|
fixSubmit:
|
||||||
|
for {
|
||||||
|
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
|
||||||
|
if err != nil {
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusCreated:
|
||||||
|
break fixSubmit
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func publishAppStore(ctx context.Context) error {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := createClient(time.Minute)
|
||||||
|
for _, platform := range []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
} {
|
||||||
|
log.Info(string(platform), " list versions")
|
||||||
|
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
|
||||||
|
FilterPlatform: []string{string(platform)},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
|
||||||
|
return *it.Attributes.VersionString == tag
|
||||||
|
})
|
||||||
|
switch *version.Attributes.AppStoreState {
|
||||||
|
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
|
||||||
|
log.Fatal(string(platform), " ", tag, " not submitted")
|
||||||
|
case asc.AppStoreVersionStateWaitingForReview,
|
||||||
|
asc.AppStoreVersionStateInReview:
|
||||||
|
log.Warn(string(platform), " ", tag, " waiting for review")
|
||||||
|
continue
|
||||||
|
case asc.AppStoreVersionStatePendingDeveloperRelease:
|
||||||
|
default:
|
||||||
|
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
|
||||||
|
}
|
||||||
|
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRetryable(response *asc.Response) bool {
|
||||||
|
if response == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,17 +10,21 @@ import (
|
|||||||
_ "github.com/sagernet/gomobile"
|
_ "github.com/sagernet/gomobile"
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/sing/common/shell"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
|
platform string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -31,8 +35,8 @@ func main() {
|
|||||||
switch target {
|
switch target {
|
||||||
case "android":
|
case "android":
|
||||||
buildAndroid()
|
buildAndroid()
|
||||||
case "ios":
|
case "apple":
|
||||||
buildiOS()
|
buildApple()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +66,35 @@ func init() {
|
|||||||
func buildAndroid() {
|
func buildAndroid() {
|
||||||
build_shared.FindSDK()
|
build_shared.FindSDK()
|
||||||
|
|
||||||
|
var javaPath string
|
||||||
|
javaHome := os.Getenv("JAVA_HOME")
|
||||||
|
if javaHome == "" {
|
||||||
|
javaPath = "java"
|
||||||
|
} else {
|
||||||
|
javaPath = filepath.Join(javaHome, "bin", "java")
|
||||||
|
}
|
||||||
|
|
||||||
|
javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(E.Cause(err, "check java version"))
|
||||||
|
}
|
||||||
|
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||||
|
log.Fatal("java version should be openjdk 17")
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindTarget string
|
||||||
|
if platform != "" {
|
||||||
|
bindTarget = platform
|
||||||
|
} else if debugEnabled {
|
||||||
|
bindTarget = "android/arm64"
|
||||||
|
} else {
|
||||||
|
bindTarget = "android"
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
|
"-target", bindTarget,
|
||||||
"-androidapi", "21",
|
"-androidapi", "21",
|
||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
@@ -86,7 +116,7 @@ func buildAndroid() {
|
|||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||||
command.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
command.Stderr = os.Stderr
|
command.Stderr = os.Stderr
|
||||||
err := command.Run()
|
err = command.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -103,11 +133,20 @@ func buildAndroid() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildiOS() {
|
func buildApple() {
|
||||||
|
var bindTarget string
|
||||||
|
if platform != "" {
|
||||||
|
bindTarget = platform
|
||||||
|
} else if debugEnabled {
|
||||||
|
bindTarget = "ios"
|
||||||
|
} else {
|
||||||
|
bindTarget = "ios,tvos,macos"
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
"-target", "ios,iossimulator,tvos,tvossimulator,macos",
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -42,14 +40,6 @@ func FindSDK() {
|
|||||||
log.Fatal("android NDK not found")
|
log.Fatal("android NDK not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
javaVersion, err := shell.Exec("java", "--version").ReadOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(E.Cause(err, "check java version"))
|
|
||||||
}
|
|
||||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
|
||||||
log.Fatal("java version should be openjdk 17")
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Setenv("ANDROID_HOME", androidSDKPath)
|
os.Setenv("ANDROID_HOME", androidSDKPath)
|
||||||
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
os.Setenv("ANDROID_SDK_HOME", androidSDKPath)
|
||||||
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
os.Setenv("ANDROID_NDK_HOME", androidNDKPath)
|
||||||
@@ -58,12 +48,16 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "26.2.11394342"
|
const fixedVersion = "28.0.12674087"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) {
|
||||||
|
androidNDKPath = ndkHomeEnv
|
||||||
|
return true
|
||||||
|
}
|
||||||
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
|
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -86,7 +80,7 @@ func findNDK() bool {
|
|||||||
})
|
})
|
||||||
for _, versionName := range versionNames {
|
for _, versionName := range versionNames {
|
||||||
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
||||||
if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) {
|
if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {
|
||||||
androidNDKPath = currentNDKPath
|
androidNDKPath = currentNDKPath
|
||||||
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ func ReadTag() (string, error) {
|
|||||||
return version.String() + "-" + shortCommit, nil
|
return version.String() + "-" + shortCommit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReadTagVersionRev() (badversion.Version, error) {
|
||||||
|
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||||
|
return badversion.Parse(currentTagRev[1:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
func ReadTagVersion() (badversion.Version, error) {
|
func ReadTagVersion() (badversion.Version, error) {
|
||||||
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
|
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
|
||||||
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
|
||||||
|
|||||||
@@ -1,21 +1,62 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var nightly bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
currentTag, err := build_shared.ReadTag()
|
flag.Parse()
|
||||||
|
if nightly {
|
||||||
|
version, err := build_shared.ReadTagVersionRev()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Fatal(err)
|
||||||
_, err = os.Stdout.WriteString("unknown\n")
|
}
|
||||||
|
var versionStr string
|
||||||
|
if version.PreReleaseIdentifier != "" {
|
||||||
|
versionStr = version.VersionString() + "-nightly"
|
||||||
} else {
|
} else {
|
||||||
_, err = os.Stdout.WriteString(currentTag + "\n")
|
version.Patch++
|
||||||
|
versionStr = version.VersionString() + "-nightly"
|
||||||
}
|
}
|
||||||
|
err = setGitHubEnv("version", versionStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
os.Stdout.WriteString("unknown\n")
|
||||||
|
} else {
|
||||||
|
os.Stdout.WriteString(tag + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setGitHubEnv(name string, value string) error {
|
||||||
|
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = outputFile.WriteString(name + "=" + value + "\n")
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = outputFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Stderr.WriteString(name + "=" + value + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -12,9 +13,22 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var flagRunInCI bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
flag.Parse()
|
||||||
androidPath, err := filepath.Abs("../sing-box-for-android")
|
newVersion := common.Must1(build_shared.ReadTag())
|
||||||
|
var androidPath string
|
||||||
|
if flagRunInCI {
|
||||||
|
androidPath = "clients/android"
|
||||||
|
} else {
|
||||||
|
androidPath = "../sing-box-for-android"
|
||||||
|
}
|
||||||
|
androidPath, err := filepath.Abs(androidPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -31,10 +45,10 @@ func main() {
|
|||||||
for _, propPair := range propsList {
|
for _, propPair := range propsList {
|
||||||
switch propPair[0] {
|
switch propPair[0] {
|
||||||
case "VERSION_NAME":
|
case "VERSION_NAME":
|
||||||
if propPair[1] != newVersion.String() {
|
if propPair[1] != newVersion {
|
||||||
versionUpdated = true
|
versionUpdated = true
|
||||||
propPair[1] = newVersion.String()
|
propPair[1] = newVersion
|
||||||
log.Info("updated version to ", newVersion.String())
|
log.Info("updated version to ", newVersion)
|
||||||
}
|
}
|
||||||
case "GO_VERSION":
|
case "GO_VERSION":
|
||||||
if propPair[1] != runtime.Version() {
|
if propPair[1] != runtime.Version() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -13,9 +14,22 @@ import (
|
|||||||
"howett.net/plist"
|
"howett.net/plist"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var flagRunInCI bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
newVersion := common.Must1(build_shared.ReadTagVersion())
|
newVersion := common.Must1(build_shared.ReadTagVersion())
|
||||||
applePath, err := filepath.Abs("../sing-box-for-apple")
|
var applePath string
|
||||||
|
if flagRunInCI {
|
||||||
|
applePath = "clients/apple"
|
||||||
|
} else {
|
||||||
|
applePath = "../sing-box-for-apple"
|
||||||
|
}
|
||||||
|
applePath, err := filepath.Abs(applePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -26,8 +40,8 @@ func main() {
|
|||||||
common.Must(decoder.Decode(&project))
|
common.Must(decoder.Decode(&project))
|
||||||
objectsMap := project["objects"].(map[string]any)
|
objectsMap := project["objects"].(map[string]any)
|
||||||
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
|
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
|
||||||
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfa"}, newVersion.VersionString())
|
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString())
|
||||||
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfa.independent", "io.nekohasekai.sfa.system"}, newVersion.String())
|
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String())
|
||||||
if updated0 || updated1 {
|
if updated0 || updated1 {
|
||||||
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
|
||||||
}
|
}
|
||||||
|
|||||||
71
cmd/sing-box/cmd.go
Normal file
71
cmd/sing-box/cmd.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
|
_ "github.com/sagernet/sing-box/include"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalCtx context.Context
|
||||||
|
configPaths []string
|
||||||
|
configDirectories []string
|
||||||
|
workingDir string
|
||||||
|
disableColor bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var mainCommand = &cobra.Command{
|
||||||
|
Use: "sing-box",
|
||||||
|
PersistentPreRun: preRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
||||||
|
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
||||||
|
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||||
|
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||||
|
}
|
||||||
|
|
||||||
|
func preRun(cmd *cobra.Command, args []string) {
|
||||||
|
globalCtx = context.Background()
|
||||||
|
sudoUser := os.Getenv("SUDO_USER")
|
||||||
|
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
|
||||||
|
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
|
||||||
|
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
|
||||||
|
sudoUserObject, _ := user.Lookup(sudoUser)
|
||||||
|
if sudoUserObject != nil {
|
||||||
|
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
|
||||||
|
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sudoUID > 0 && sudoGID > 0 {
|
||||||
|
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
|
||||||
|
}
|
||||||
|
if disableColor {
|
||||||
|
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
|
||||||
|
}
|
||||||
|
if workingDir != "" {
|
||||||
|
_, err := os.Stat(workingDir)
|
||||||
|
if err != nil {
|
||||||
|
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
|
||||||
|
}
|
||||||
|
err = os.Chdir(workingDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
|
configPaths = append(configPaths, "config.json")
|
||||||
|
}
|
||||||
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ func check() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(globalCtx)
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -56,10 +55,6 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ruleSet, err := plainRuleSet.Upgrade()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".json") {
|
if strings.HasSuffix(sourcePath, ".json") {
|
||||||
@@ -74,7 +69,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2)
|
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -77,7 +78,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outputFile.Close()
|
defer outputFile.Close()
|
||||||
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, true)
|
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -48,14 +46,10 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
plainRuleSet, err := srs.Read(reader, true)
|
ruleSet, err := srs.Read(reader, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ruleSet := option.PlainRuleSetCompat{
|
|
||||||
Version: C.RuleSetVersion1,
|
|
||||||
Options: plainRuleSet,
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
|
|||||||
@@ -55,26 +55,25 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read rule-set")
|
return E.Cause(err, "read rule-set")
|
||||||
}
|
}
|
||||||
var plainRuleSet option.PlainRuleSet
|
var ruleSet option.PlainRuleSetCompat
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
case C.RuleSetFormatSource:
|
||||||
var compat option.PlainRuleSetCompat
|
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||||
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
plainRuleSet, err = compat.Upgrade()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case C.RuleSetFormatBinary:
|
case C.RuleSetFormatBinary:
|
||||||
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
|
ruleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
||||||
}
|
}
|
||||||
|
plainRuleSet, err := ruleSet.Upgrade()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
ipAddress := M.ParseAddr(domain)
|
ipAddress := M.ParseAddr(domain)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
if ipAddress.IsValid() {
|
if ipAddress.IsValid() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func initializeHTTP3Client(instance *box.Box) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
http3Client = &http.Client{
|
http3Client = &http.Client{
|
||||||
Transport: &http3.RoundTripper{
|
Transport: &http3.Transport{
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
destination := M.ParseSocksaddr(addr)
|
destination := M.ParseSocksaddr(addr)
|
||||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||||
|
|||||||
28
cmd/sing-box/generate_completions.go
Normal file
28
cmd/sing-box/generate_completions.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
//go:build generate && generate_completions
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := generateCompletions()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCompletions() error {
|
||||||
|
err := mainCommand.GenBashCompletionFile("release/completions/sing-box.bash")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = mainCommand.GenFishCompletionFile("release/completions/sing-box.fish", true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = mainCommand.GenZshCompletionFile("release/completions/sing-box.zsh")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,74 +1,11 @@
|
|||||||
|
//go:build !generate
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/sagernet/sing-box/log"
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
globalCtx context.Context
|
|
||||||
configPaths []string
|
|
||||||
configDirectories []string
|
|
||||||
workingDir string
|
|
||||||
disableColor bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var mainCommand = &cobra.Command{
|
|
||||||
Use: "sing-box",
|
|
||||||
PersistentPreRun: preRun,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
|
||||||
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
|
||||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
|
||||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := mainCommand.Execute(); err != nil {
|
if err := mainCommand.Execute(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func preRun(cmd *cobra.Command, args []string) {
|
|
||||||
globalCtx = context.Background()
|
|
||||||
sudoUser := os.Getenv("SUDO_USER")
|
|
||||||
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
|
|
||||||
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
|
|
||||||
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
|
|
||||||
sudoUserObject, _ := user.Lookup(sudoUser)
|
|
||||||
if sudoUserObject != nil {
|
|
||||||
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
|
|
||||||
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sudoUID > 0 && sudoGID > 0 {
|
|
||||||
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
|
|
||||||
}
|
|
||||||
if disableColor {
|
|
||||||
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
|
|
||||||
}
|
|
||||||
if workingDir != "" {
|
|
||||||
_, err := os.Stat(workingDir)
|
|
||||||
if err != nil {
|
|
||||||
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
|
|
||||||
}
|
|
||||||
err = os.Chdir(workingDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
|
||||||
configPaths = append(configPaths, "config.json")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
if options.ConnectTimeout != 0 {
|
if options.ConnectTimeout != 0 {
|
||||||
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPTimeout
|
dialer.Timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
// TODO: Add an option to customize the keep alive period
|
// TODO: Add an option to customize the keep alive period
|
||||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
dialer.KeepAlive = C.TCPKeepAliveInitial
|
||||||
@@ -179,7 +179,7 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address))
|
return d.udpListener.ListenPacket(context.Background(), network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
|
|||||||
34
common/geosite/geosite_test.go
Normal file
34
common/geosite/geosite_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package geosite_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGeosite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
err := geosite.Write(&buffer, map[string][]geosite.Item{
|
||||||
|
"test": {
|
||||||
|
{
|
||||||
|
Type: geosite.RuleTypeDomain,
|
||||||
|
Value: "example.org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
reader, codes, err := geosite.NewReader(bytes.NewReader(buffer.Bytes()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []string{"test"}, codes)
|
||||||
|
items, err := reader.Read("test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []geosite.Item{{
|
||||||
|
Type: geosite.RuleTypeDomain,
|
||||||
|
Value: "example.org",
|
||||||
|
}}, items)
|
||||||
|
}
|
||||||
@@ -26,14 +26,22 @@ func Open(path string) (*Reader, []string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
reader := &Reader{
|
reader, codes, err := NewReader(content)
|
||||||
reader: content,
|
|
||||||
}
|
|
||||||
err = reader.readMetadata()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
content.Close()
|
content.Close()
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
return reader, codes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) {
|
||||||
|
reader := &Reader{
|
||||||
|
reader: readSeeker,
|
||||||
|
}
|
||||||
|
err := reader.readMetadata()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
codes := make([]string, 0, len(reader.domainIndex))
|
codes := make([]string, 0, len(reader.domainIndex))
|
||||||
for code := range reader.domainIndex {
|
for code := range reader.domainIndex {
|
||||||
codes = append(codes, code)
|
codes = append(codes, code)
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
|
|||||||
index := make(map[string]int)
|
index := make(map[string]int)
|
||||||
for _, code := range keys {
|
for _, code := range keys {
|
||||||
index[code] = content.Len()
|
index[code] = content.Len()
|
||||||
err := varbin.Write(content, binary.BigEndian, domains[code])
|
for _, item := range domains[code] {
|
||||||
|
err := varbin.Write(content, binary.BigEndian, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := writer.WriteByte(0)
|
err := writer.WriteByte(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -40,22 +40,28 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
|||||||
}
|
}
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
var errors []error
|
var errors []error
|
||||||
|
for i := 0; ; i++ {
|
||||||
err := conn.SetReadDeadline(deadline)
|
err := conn.SetReadDeadline(deadline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "set read deadline")
|
return E.Cause(err, "set read deadline")
|
||||||
}
|
}
|
||||||
defer conn.SetReadDeadline(time.Time{})
|
_, err = buffer.ReadOnceFrom(conn)
|
||||||
for _, sniffer := range sniffers {
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
if buffer.IsEmpty() {
|
if err != nil {
|
||||||
err = sniffer(ctx, metadata, io.TeeReader(conn, buffer))
|
if i > 0 {
|
||||||
} else {
|
break
|
||||||
err = sniffer(ctx, metadata, io.MultiReader(bytes.NewReader(buffer.Bytes()), io.TeeReader(conn, buffer)))
|
|
||||||
}
|
}
|
||||||
|
return E.Cause(err, "read payload")
|
||||||
|
}
|
||||||
|
errors = nil
|
||||||
|
for _, sniffer := range sniffers {
|
||||||
|
err = sniffer(ctx, metadata, bytes.NewReader(buffer.Bytes()))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return E.Errors(errors...)
|
return E.Errors(errors...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,11 @@ const (
|
|||||||
ruleItemWIFISSID
|
ruleItemWIFISSID
|
||||||
ruleItemWIFIBSSID
|
ruleItemWIFIBSSID
|
||||||
ruleItemAdGuardDomain
|
ruleItemAdGuardDomain
|
||||||
|
ruleItemProcessPathRegex
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) {
|
func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) {
|
||||||
var magicBytes [3]byte
|
var magicBytes [3]byte
|
||||||
_, err = io.ReadFull(reader, magicBytes[:])
|
_, err = io.ReadFull(reader, magicBytes[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,10 +54,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
|
|||||||
var version uint8
|
var version uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &version)
|
err = binary.Read(reader, binary.BigEndian, &version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruleSet, err
|
return ruleSetCompat, err
|
||||||
}
|
}
|
||||||
if version > C.RuleSetVersion2 {
|
if version > C.RuleSetVersionCurrent {
|
||||||
return ruleSet, E.New("unsupported version: ", version)
|
return ruleSetCompat, E.New("unsupported version: ", version)
|
||||||
}
|
}
|
||||||
compressReader, err := zlib.NewReader(reader)
|
compressReader, err := zlib.NewReader(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -67,9 +68,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ruleSet.Rules = make([]option.HeadlessRule, length)
|
ruleSetCompat.Version = version
|
||||||
|
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
|
||||||
for i := uint64(0); i < length; i++ {
|
for i := uint64(0); i < length; i++ {
|
||||||
ruleSet.Rules[i], err = readRule(bReader, recover)
|
ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = E.Cause(err, "read rule[", i, "]")
|
err = E.Cause(err, "read rule[", i, "]")
|
||||||
return
|
return
|
||||||
@@ -78,18 +80,12 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) error {
|
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error {
|
||||||
_, err := writer.Write(MagicBytes[:])
|
_, err := writer.Write(MagicBytes[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var version uint8
|
err = binary.Write(writer, binary.BigEndian, generateVersion)
|
||||||
if generateUnstable {
|
|
||||||
version = C.RuleSetVersion2
|
|
||||||
} else {
|
|
||||||
version = C.RuleSetVersion1
|
|
||||||
}
|
|
||||||
err = binary.Write(writer, binary.BigEndian, version)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -103,7 +99,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, rule := range ruleSet.Rules {
|
for _, rule := range ruleSet.Rules {
|
||||||
err = writeRule(bWriter, rule, generateUnstable)
|
err = writeRule(bWriter, rule, generateVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -134,12 +130,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateUnstable bool) error {
|
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
case C.RuleTypeDefault:
|
case C.RuleTypeDefault:
|
||||||
return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable)
|
return writeDefaultRule(writer, rule.DefaultOptions, generateVersion)
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable)
|
return writeLogicalRule(writer, rule.LogicalOptions, generateVersion)
|
||||||
default:
|
default:
|
||||||
panic("unknown rule type: " + rule.Type)
|
panic("unknown rule type: " + rule.Type)
|
||||||
}
|
}
|
||||||
@@ -207,6 +203,8 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
rule.ProcessName, err = readRuleItemString(reader)
|
rule.ProcessName, err = readRuleItemString(reader)
|
||||||
case ruleItemProcessPath:
|
case ruleItemProcessPath:
|
||||||
rule.ProcessPath, err = readRuleItemString(reader)
|
rule.ProcessPath, err = readRuleItemString(reader)
|
||||||
|
case ruleItemProcessPathRegex:
|
||||||
|
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
||||||
case ruleItemPackageName:
|
case ruleItemPackageName:
|
||||||
rule.PackageName, err = readRuleItemString(reader)
|
rule.PackageName, err = readRuleItemString(reader)
|
||||||
case ruleItemWIFISSID:
|
case ruleItemWIFISSID:
|
||||||
@@ -237,7 +235,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateUnstable bool) error {
|
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error {
|
||||||
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -261,7 +259,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, !generateUnstable).Write(writer)
|
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -326,6 +324,12 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(rule.ProcessPathRegex) > 0 {
|
||||||
|
err = writeRuleItemString(writer, ruleItemProcessPathRegex, rule.ProcessPathRegex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(rule.PackageName) > 0 {
|
if len(rule.PackageName) > 0 {
|
||||||
err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)
|
err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -345,6 +349,9 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rule.AdGuardDomain) > 0 {
|
if len(rule.AdGuardDomain) > 0 {
|
||||||
|
if generateVersion < C.RuleSetVersion2 {
|
||||||
|
return E.New("AdGuard rule items is only supported in version 2 or later")
|
||||||
|
}
|
||||||
err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain)
|
err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -448,7 +455,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateUnstable bool) error {
|
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error {
|
||||||
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -469,7 +476,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, rule := range logicalRule.Rules {
|
for _, rule := range logicalRule.Rules {
|
||||||
err = writeRule(writer, rule, generateUnstable)
|
err = writeRule(writer, rule, generateVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ type echConnWrapper struct {
|
|||||||
|
|
||||||
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
|
|||||||
pair.rawConf = b
|
pair.rawConf = b
|
||||||
|
|
||||||
secBuf, err := sec.MarshalBinary()
|
secBuf, err := sec.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH private key")
|
||||||
|
}
|
||||||
sk := []byte{}
|
sk := []byte{}
|
||||||
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
sk = append(sk, secBuf...)
|
sk = append(sk, secBuf...)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
||||||
return &http3.RoundTripper{
|
return &http3.Transport{
|
||||||
TLSClientConfig: c.config,
|
TLSClientConfig: c.config,
|
||||||
QUICConfig: quicConfig,
|
QUICConfig: quicConfig,
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ func (c *echServerConfig) startWatcher() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = watcher.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
c.watcher = watcher
|
c.watcher = watcher
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -232,7 +236,7 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
var echKey []byte
|
var echKey []byte
|
||||||
if len(options.ECH.Key) > 0 {
|
if len(options.ECH.Key) > 0 {
|
||||||
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
||||||
} else if options.KeyPath != "" {
|
} else if options.ECH.KeyPath != "" {
|
||||||
content, err := os.ReadFile(options.ECH.KeyPath)
|
content, err := os.ReadFile(options.ECH.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read ECH key")
|
return nil, E.Cause(err, "read ECH key")
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
return nil, E.New("reality verification failed")
|
return nil, E.New("reality verification failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &utlsConnWrapper{uConn}, nil
|
return &realityClientConnWrapper{uConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
@@ -249,3 +249,36 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type realityClientConnWrapper struct {
|
||||||
|
*utls.UConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
|
return tls.ConnectionState{
|
||||||
|
Version: state.Version,
|
||||||
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
|
DidResume: state.DidResume,
|
||||||
|
CipherSuite: state.CipherSuite,
|
||||||
|
NegotiatedProtocol: state.NegotiatedProtocol,
|
||||||
|
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
||||||
|
ServerName: state.ServerName,
|
||||||
|
PeerCertificates: state.PeerCertificates,
|
||||||
|
VerifiedChains: state.VerifiedChains,
|
||||||
|
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
||||||
|
OCSPResponse: state.OCSPResponse,
|
||||||
|
TLSUnique: state.TLSUnique,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityClientConnWrapper) Upstream() any {
|
||||||
|
return c.UConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
||||||
|
// We fixed it by calling Close() directly.
|
||||||
|
func (c *realityClientConnWrapper) CloseWrite() error {
|
||||||
|
return c.Close()
|
||||||
|
}
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ type realityConnWrapper struct {
|
|||||||
|
|
||||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
@@ -194,3 +195,9 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
|||||||
func (c *realityConnWrapper) Upstream() any {
|
func (c *realityConnWrapper) Upstream() any {
|
||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
||||||
|
// We fixed it by calling Close() directly.
|
||||||
|
func (c *realityConnWrapper) CloseWrite() error {
|
||||||
|
return c.Close()
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ func (c *STDServerConfig) startWatcher() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = watcher.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
c.watcher = watcher
|
c.watcher = watcher
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ type utlsConnWrapper struct {
|
|||||||
|
|
||||||
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
state := c.Conn.ConnectionState()
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
return tls.ConnectionState{
|
return tls.ConnectionState{
|
||||||
Version: state.Version,
|
Version: state.Version,
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
@@ -217,18 +218,10 @@ func init() {
|
|||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||||
switch name {
|
switch name {
|
||||||
|
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
|
||||||
|
fallthrough
|
||||||
case "chrome", "":
|
case "chrome", "":
|
||||||
return utls.HelloChrome_Auto, nil
|
return utls.HelloChrome_Auto, nil
|
||||||
case "chrome_psk":
|
|
||||||
return utls.HelloChrome_100_PSK, nil
|
|
||||||
case "chrome_psk_shuffle":
|
|
||||||
return utls.HelloChrome_112_PSK_Shuf, nil
|
|
||||||
case "chrome_padding_psk_shuffle":
|
|
||||||
return utls.HelloChrome_114_Padding_PSK_Shuf, nil
|
|
||||||
case "chrome_pq":
|
|
||||||
return utls.HelloChrome_115_PQ, nil
|
|
||||||
case "chrome_pq_psk":
|
|
||||||
return utls.HelloChrome_115_PQ_PSK, nil
|
|
||||||
case "firefox":
|
case "firefox":
|
||||||
return utls.HelloFirefox_Auto, nil
|
return utls.HelloFirefox_Auto, nil
|
||||||
case "edge":
|
case "edge":
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -113,6 +114,7 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
|
Timeout: C.TCPTimeout,
|
||||||
}
|
}
|
||||||
defer client.CloseIdleConnections()
|
defer client.CloseIdleConnections()
|
||||||
resp, err := client.Do(req.WithContext(ctx))
|
resp, err := client.Do(req.WithContext(ctx))
|
||||||
|
|||||||
@@ -21,4 +21,5 @@ const (
|
|||||||
const (
|
const (
|
||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
|
RuleSetVersionCurrent = RuleSetVersion2
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import "time"
|
|||||||
const (
|
const (
|
||||||
TCPKeepAliveInitial = 10 * time.Minute
|
TCPKeepAliveInitial = 10 * time.Minute
|
||||||
TCPKeepAliveInterval = 75 * time.Second
|
TCPKeepAliveInterval = 75 * time.Second
|
||||||
TCPTimeout = 5 * time.Second
|
TCPConnectTimeout = 5 * time.Second
|
||||||
|
TCPTimeout = 15 * time.Second
|
||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
DNSTimeout = 10 * time.Second
|
DNSTimeout = 10 * time.Second
|
||||||
QUICTimeout = 30 * time.Second
|
QUICTimeout = 30 * time.Second
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func applyDebugListenOption(options option.DebugOptions) {
|
|||||||
|
|
||||||
encoder := json.NewEncoder(writer)
|
encoder := json.NewEncoder(writer)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
encoder.Encode(memObject)
|
encoder.Encode(&memObject)
|
||||||
})
|
})
|
||||||
r.Route("/pprof", func(r chi.Router) {
|
r.Route("/pprof", func(r chi.Router) {
|
||||||
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
|||||||
@@ -2,11 +2,170 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "Help needed"
|
### 1.10.7
|
||||||
|
|
||||||
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
* Fixes and improvements
|
||||||
|
|
||||||
If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org).
|
### 1.10.2
|
||||||
|
|
||||||
|
* Add deprecated warnings
|
||||||
|
* Fix proxying websocket connections in HTTP/mixed inbounds
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.10.1
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.10.0
|
||||||
|
|
||||||
|
Important changes since 1.9:
|
||||||
|
|
||||||
|
* Introducing auto-redirect **1**
|
||||||
|
* Add AdGuard DNS Filter support **2**
|
||||||
|
* TUN address fields are merged **3**
|
||||||
|
* Add custom options for `auto-route` and `auto-redirect` **4**
|
||||||
|
* Drop support for go1.18 and go1.19 **5**
|
||||||
|
* Add tailing comma support in JSON configuration
|
||||||
|
* Improve sniffers **6**
|
||||||
|
* Add new `inline` rule-set type **7**
|
||||||
|
* Add access control options for Clash API **8**
|
||||||
|
* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **9**
|
||||||
|
* Add auto reload support for local rule-set
|
||||||
|
* Update fsnotify usages **10**
|
||||||
|
* Add IP address support for `rule-set match` command
|
||||||
|
* Add `rule-set decompile` command
|
||||||
|
* Add `process_path_regex` rule item
|
||||||
|
* Update uTLS to v1.6.7 **11**
|
||||||
|
* Optimize memory usages of rule-sets **12**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
The new auto-redirect feature allows TUN to automatically
|
||||||
|
configure connection redirection to improve proxy performance.
|
||||||
|
|
||||||
|
When auto-redirect is enabled, new route address set options will allow you to
|
||||||
|
automatically configure destination IP CIDR rules from a specified rule set to the firewall.
|
||||||
|
|
||||||
|
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
||||||
|
(for example, keep hardware offloading of direct traffics on the router).
|
||||||
|
|
||||||
|
See [TUN](/configuration/inbound/tun).
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
The new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home.
|
||||||
|
|
||||||
|
See [AdGuard DNS Filter](/configuration/rule-set/adguard/).
|
||||||
|
|
||||||
|
**3**:
|
||||||
|
|
||||||
|
See [Migration](/migration/#tun-address-fields-are-merged).
|
||||||
|
|
||||||
|
**4**:
|
||||||
|
|
||||||
|
See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index),
|
||||||
|
[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index),
|
||||||
|
[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and
|
||||||
|
[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark).
|
||||||
|
|
||||||
|
**5**:
|
||||||
|
|
||||||
|
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||||
|
|
||||||
|
**6**:
|
||||||
|
|
||||||
|
BitTorrent, DTLS, RDP, SSH sniffers are added.
|
||||||
|
|
||||||
|
Now the QUIC sniffer can correctly extract the server name from Chromium requests and
|
||||||
|
can identify common QUIC clients, including
|
||||||
|
Chromium, Safari, Firefox, quic-go (including uquic disguised as Chrome).
|
||||||
|
|
||||||
|
**7**:
|
||||||
|
|
||||||
|
The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)
|
||||||
|
allows you to write headless rules directly without creating a rule-set file.
|
||||||
|
|
||||||
|
**8**:
|
||||||
|
|
||||||
|
With the new access control options, not only can you allow Clash dashboards
|
||||||
|
to access the Clash API on your local network,
|
||||||
|
you can also manually limit the websites that can access the API instead of allowing everyone.
|
||||||
|
|
||||||
|
See [Clash API](/configuration/experimental/clash-api/).
|
||||||
|
|
||||||
|
**9**:
|
||||||
|
|
||||||
|
See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty).
|
||||||
|
|
||||||
|
**10**:
|
||||||
|
|
||||||
|
sing-box now uses fsnotify correctly and will not cancel watching
|
||||||
|
if the target file is deleted or recreated via rename (e.g. `mv`).
|
||||||
|
|
||||||
|
This affects all path options that support reload, including
|
||||||
|
`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`.
|
||||||
|
|
||||||
|
**11**:
|
||||||
|
|
||||||
|
Some legacy chrome fingerprints have been removed and will fallback to chrome,
|
||||||
|
see [utls](/configuration/shared/tls#utls).
|
||||||
|
|
||||||
|
**12**:
|
||||||
|
|
||||||
|
See [Source Format](/configuration/rule-set/source-format/#version).
|
||||||
|
|
||||||
|
### 1.9.7
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-beta.11
|
||||||
|
|
||||||
|
* Update uTLS to v1.6.7 **1**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Some legacy chrome fingerprints have been removed and will fallback to chrome,
|
||||||
|
see [utls](/configuration/shared/tls#utls).
|
||||||
|
|
||||||
|
#### 1.10.0-beta.10
|
||||||
|
|
||||||
|
* Add `process_path_regex` rule item
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_The macOS standalone versions of sing-box (>=1.9.5/<1.10.0-beta.11) now silently fail and require manual granting of
|
||||||
|
the **Full Disk Access** permission to system extension to start, probably due to Apple's changed security policy. We
|
||||||
|
will prompt users about this in feature versions._
|
||||||
|
|
||||||
|
### 1.9.6
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
### 1.9.5
|
||||||
|
|
||||||
|
* Update quic-go to v0.47.0
|
||||||
|
* Fix direct dialer not resolving domain
|
||||||
|
* Fix no error return when empty DNS cache retrieved
|
||||||
|
* Fix build with go1.23
|
||||||
|
* Fix stream sniffer
|
||||||
|
* Fix bad redirect in clash-api
|
||||||
|
* Fix wireguard events chan leak
|
||||||
|
* Fix cached conn eats up read deadlines
|
||||||
|
* Fix disconnected interface selected as default in windows
|
||||||
|
* Update Bundle Identifiers for Apple platform clients **1**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Migration](/migration/#bundle-identifier-updates-in-apple-platform-clients).
|
||||||
|
|
||||||
|
We are still working on getting all sing-box apps back on the App Store, which should be completed within a week
|
||||||
|
(SFI on the App Store and others on TestFlight are already available).
|
||||||
|
|
||||||
|
#### 1.10.0-beta.8
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be
|
||||||
|
completed within a month (TestFlight is already available)._
|
||||||
|
|
||||||
#### 1.10.0-beta.7
|
#### 1.10.0-beta.7
|
||||||
|
|
||||||
@@ -48,6 +207,11 @@ icon: material/alert-decagram
|
|||||||
* Fix UDP connnection leak when sniffing
|
* Fix UDP connnection leak when sniffing
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_Due to problems with our Apple developer account,
|
||||||
|
sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
||||||
|
If your company or organization is willing to help us return to the App Store,
|
||||||
|
please [contact us](mailto:contact@sagernet.org)._
|
||||||
|
|
||||||
#### 1.10.0-alpha.29
|
#### 1.10.0-alpha.29
|
||||||
|
|
||||||
* Update quic-go to v0.46.0
|
* Update quic-go to v0.46.0
|
||||||
@@ -106,11 +270,9 @@ See [Source Format](/configuration/rule-set/source-format/#version).
|
|||||||
|
|
||||||
**1**:
|
**1**:
|
||||||
|
|
||||||
The new [rule-set] type inline (which also becomes the default type)
|
The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)
|
||||||
allows you to write headless rules directly without creating a rule-set file.
|
allows you to write headless rules directly without creating a rule-set file.
|
||||||
|
|
||||||
[rule-set]: /configuration/rule-set/
|
|
||||||
|
|
||||||
**2**:
|
**2**:
|
||||||
|
|
||||||
sing-box now uses fsnotify correctly and will not cancel watching
|
sing-box now uses fsnotify correctly and will not cancel watching
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
|
|||||||
|-----------------------|------------------|-----------------------------------|
|
|-----------------------|------------------|-----------------------------------|
|
||||||
| `process_name` | :material-close: | No permission |
|
| `process_name` | :material-close: | No permission |
|
||||||
| `process_path` | :material-close: | No permission |
|
| `process_path` | :material-close: | No permission |
|
||||||
|
| `process_path_regex` | :material-close: | No permission |
|
||||||
| `package_name` | :material-check: | / |
|
| `package_name` | :material-check: | / |
|
||||||
| `user` | :material-close: | Use `package_name` instead |
|
| `user` | :material-close: | Use `package_name` instead |
|
||||||
| `user_id` | :material-close: | Use `package_name` instead |
|
| `user_id` | :material-close: | Use `package_name` instead |
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension
|
|||||||
|-----------------------|------------------|-----------------------|
|
|-----------------------|------------------|-----------------------|
|
||||||
| `process_name` | :material-close: | No permission |
|
| `process_name` | :material-close: | No permission |
|
||||||
| `process_path` | :material-close: | No permission |
|
| `process_path` | :material-close: | No permission |
|
||||||
|
| `process_path_regex` | :material-close: | No permission |
|
||||||
| `package_name` | :material-close: | / |
|
| `package_name` | :material-close: | / |
|
||||||
| `user` | :material-close: | No permission |
|
| `user` | :material-close: | No permission |
|
||||||
| `user_id` | :material-close: | No permission |
|
| `user_id` | :material-close: | No permission |
|
||||||
|
|||||||
@@ -7,13 +7,6 @@ icon: material/apple
|
|||||||
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
SFI/SFM/SFT allows users to manage and run local or remote sing-box configuration files, and provides
|
||||||
platform-specific function implementation, such as TUN transparent proxy implementation.
|
platform-specific function implementation, such as TUN transparent proxy implementation.
|
||||||
|
|
||||||
!!! failure "Unavailable"
|
|
||||||
|
|
||||||
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
|
||||||
|
|
||||||
If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org).
|
|
||||||
|
|
||||||
|
|
||||||
## :material-graph: Requirements
|
## :material-graph: Requirements
|
||||||
|
|
||||||
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
* iOS 15.0+ / macOS 13.0+ / Apple tvOS 17.0+
|
||||||
@@ -21,13 +14,13 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
|||||||
|
|
||||||
## :material-download: Download
|
## :material-download: Download
|
||||||
|
|
||||||
* [App Store](https://apps.apple.com/us/app/sing-box/id6451272673)
|
* [App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)
|
||||||
* ~~TestFlight (Beta)~~
|
* TestFlight (Beta)
|
||||||
|
|
||||||
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
||||||
(one-time sponsorships are accepted).
|
(one-time sponsorships are accepted).
|
||||||
Once you donate, you can get an invitation by sending us your Apple ID [via email](mailto:contact@sagernet.org),
|
Once you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot)
|
||||||
or join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot).
|
or sending us your Apple ID [via email](mailto:contact@sagernet.org).
|
||||||
|
|
||||||
## :material-file-download: Download (macOS standalone version)
|
## :material-file-download: Download (macOS standalone version)
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
Maintained by Project S to provide a unified experience and platform-specific functionality.
|
Maintained by Project S to provide a unified experience and platform-specific functionality.
|
||||||
|
|
||||||
| Platform | Client |
|
| Platform | Client |
|
||||||
| ------------------------------------- | ---------------------------------------- |
|
|---------------------------------------|------------------------------------------|
|
||||||
| :material-android: Android | [sing-box for Android](./android/) |
|
| :material-android: Android | [sing-box for Android](./android/) |
|
||||||
| :material-apple: iOS/macOS/Apple tvOS | :material-alert: [Unavailable](./apple/) |
|
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
|
||||||
| :material-laptop: Desktop | Working in progress |
|
| :material-laptop: Desktop | Working in progress |
|
||||||
|
|
||||||
Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core
|
Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
由 Project S 维护,提供统一的体验与平台特定的功能。
|
由 Project S 维护,提供统一的体验与平台特定的功能。
|
||||||
|
|
||||||
| 平台 | 客户端 |
|
| 平台 | 客户端 |
|
||||||
| ------------------------------------- | ----------------------------------- |
|
|---------------------------------------|------------------------------------------|
|
||||||
| :material-android: Android | [sing-box for Android](./android/) |
|
| :material-android: Android | [sing-box for Android](./android/) |
|
||||||
| :material-apple: iOS/macOS/Apple tvOS | :material-alert: [不可用](./apple/) |
|
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
|
||||||
| :material-laptop: Desktop | 施工中 |
|
| :material-laptop: Desktop | 施工中 |
|
||||||
|
|
||||||
此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业
|
此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ icon: material/new-box
|
|||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||||
|
:material-plus: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
@@ -103,6 +104,9 @@ icon: material/new-box
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -268,6 +272,16 @@ Match process name.
|
|||||||
|
|
||||||
Match process path.
|
Match process path.
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match process path using regular expression.
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ icon: material/new-box
|
|||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||||
|
:material-plus: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
@@ -103,6 +104,9 @@ icon: material/new-box
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -266,6 +270,16 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配进程路径。
|
匹配进程路径。
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
使用正则表达式匹配进程路径。
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
|||||||
|
|
||||||
#### fallback
|
#### fallback
|
||||||
|
|
||||||
!!! quote ""
|
!!! failure ""
|
||||||
|
|
||||||
There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature.
|
There is no evidence that GFW detects and blocks Trojan servers based on HTTP responses, and opening the standard http/s port on the server is a much bigger signature.
|
||||||
|
|
||||||
|
|||||||
@@ -110,13 +110,13 @@ icon: material/new-box
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"include_uid_range": [
|
"include_uid_range": [
|
||||||
"1000-99999"
|
"1000:99999"
|
||||||
],
|
],
|
||||||
"exclude_uid": [
|
"exclude_uid": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"exclude_uid_range": [
|
"exclude_uid_range": [
|
||||||
"1000-99999"
|
"1000:99999"
|
||||||
],
|
],
|
||||||
"include_android_user": [
|
"include_android_user": [
|
||||||
0,
|
0,
|
||||||
@@ -232,12 +232,12 @@ Automatically configure iptables/nftables to redirect connections.
|
|||||||
|
|
||||||
*In Android*:
|
*In Android*:
|
||||||
|
|
||||||
Only local connections are forwarded. To share your VPN connection over hotspot or repeater,
|
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||||
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||||
|
|
||||||
*In Linux*:
|
*In Linux*:
|
||||||
|
|
||||||
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
|
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
|
|||||||
@@ -110,13 +110,13 @@ icon: material/new-box
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"include_uid_range": [
|
"include_uid_range": [
|
||||||
"1000-99999"
|
"1000:99999"
|
||||||
],
|
],
|
||||||
"exclude_uid": [
|
"exclude_uid": [
|
||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"exclude_uid_range": [
|
"exclude_uid_range": [
|
||||||
"1000-99999"
|
"1000:99999"
|
||||||
],
|
],
|
||||||
"include_android_user": [
|
"include_android_user": [
|
||||||
0,
|
0,
|
||||||
@@ -232,7 +232,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
仅支持 Linux,且需要 `auto_route` 已启用。
|
仅支持 Linux,且需要 `auto_route` 已启用。
|
||||||
|
|
||||||
自动配置 iptables 以重定向 TCP 连接。
|
自动配置 iptables/nftables 以重定向连接。
|
||||||
|
|
||||||
*在 Android 中*:
|
*在 Android 中*:
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
*在 Linux 中*:
|
*在 Linux 中*:
|
||||||
|
|
||||||
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
带有 `auto_redirect `的 `auto_route` 可以在路由器上按预期工作,**无需干预**。
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ icon: material/alert-decagram
|
|||||||
:material-plus: [client](#client)
|
:material-plus: [client](#client)
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||||
|
:material-plus: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
@@ -101,6 +102,9 @@ icon: material/alert-decagram
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -277,6 +281,16 @@ Match process name.
|
|||||||
|
|
||||||
Match process path.
|
Match process path.
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match process path using regular expression.
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
:material-plus: [client](#client)
|
:material-plus: [client](#client)
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
:material-plus: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
@@ -99,6 +100,9 @@ icon: material/alert-decagram
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -275,6 +279,16 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
匹配进程路径。
|
匹配进程路径。
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
使用正则表达式匹配进程路径。
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|||||||
@@ -57,6 +57,9 @@
|
|||||||
"process_path": [
|
"process_path": [
|
||||||
"/usr/bin/curl"
|
"/usr/bin/curl"
|
||||||
],
|
],
|
||||||
|
"process_path_regex": [
|
||||||
|
"^/usr/bin/.+"
|
||||||
|
],
|
||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
@@ -160,6 +163,16 @@ Match process name.
|
|||||||
|
|
||||||
Match process path.
|
Match process path.
|
||||||
|
|
||||||
|
#### process_path_regex
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match process path using regular expression.
|
||||||
|
|
||||||
#### package_name
|
#### package_name
|
||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|||||||
@@ -83,7 +83,10 @@
|
|||||||
|
|
||||||
如果设置,域名将在请求发出之前解析为 IP。
|
如果设置,域名将在请求发出之前解析为 IP。
|
||||||
|
|
||||||
默认使用 `dns.strategy`。
|
| 出站 | 受影响的域名 | 默认回退值 |
|
||||||
|
|----------|--------------------------|-------------------------------------------|
|
||||||
|
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
||||||
|
| others | 服务器地址中的域名 | / |
|
||||||
|
|
||||||
#### fallback_delay
|
#### fallback_delay
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
!!! quote "Changes in sing-box 1.8.0"
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-alert-decagram: [utls](#utls)
|
:material-alert-decagram: [utls](#utls)
|
||||||
|
|
||||||
@@ -210,28 +214,25 @@ The path to the server private key, in PEM format.
|
|||||||
|
|
||||||
==Client only==
|
==Client only==
|
||||||
|
|
||||||
!!! note ""
|
!!! failure ""
|
||||||
|
|
||||||
uTLS is poorly maintained and the effect may be unproven, use at your own risk.
|
There is no evidence that GFW detects and blocks servers based on TLS client fingerprinting, and using an imperfect emulation that has not been security reviewed could pose security risks.
|
||||||
|
|
||||||
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance.
|
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance.
|
||||||
|
|
||||||
Available fingerprint values:
|
Available fingerprint values:
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! warning "Removed since sing-box 1.10.0"
|
||||||
|
|
||||||
:material-plus: chrome_psk
|
Some legacy chrome fingerprints have been removed and will fallback to chrome:
|
||||||
:material-plus: chrome_psk_shuffle
|
|
||||||
:material-plus: chrome_padding_psk_shuffle
|
:material-close: chrome_psk
|
||||||
:material-plus: chrome_pq
|
:material-close: chrome_psk_shuffle
|
||||||
:material-plus: chrome_pq_psk
|
:material-close: chrome_padding_psk_shuffle
|
||||||
|
:material-close: chrome_pq
|
||||||
|
:material-close: chrome_pq_psk
|
||||||
|
|
||||||
* chrome
|
* chrome
|
||||||
* chrome_psk
|
|
||||||
* chrome_psk_shuffle
|
|
||||||
* chrome_padding_psk_shuffle
|
|
||||||
* chrome_pq
|
|
||||||
* chrome_pq_psk
|
|
||||||
* firefox
|
* firefox
|
||||||
* edge
|
* edge
|
||||||
* safari
|
* safari
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
!!! quote "sing-box 1.8.0 中的更改"
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-alert-decagram: [utls](#utls)
|
:material-alert-decagram: [utls](#utls)
|
||||||
|
|
||||||
@@ -44,8 +48,8 @@
|
|||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
"server_port": 443,
|
"server_port": 443,
|
||||||
|
...
|
||||||
... // 拨号字段
|
// 拨号字段
|
||||||
},
|
},
|
||||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
||||||
"short_id": [
|
"short_id": [
|
||||||
@@ -202,28 +206,25 @@ TLS 版本值:
|
|||||||
|
|
||||||
==仅客户端==
|
==仅客户端==
|
||||||
|
|
||||||
!!! note ""
|
!!! failure ""
|
||||||
|
|
||||||
uTLS 维护不善且其效果可能未经证实,使用风险自负。
|
没有证据表明 GFW 根据 TLS 客户端指纹检测并阻止服务器,并且,使用一个未经安全审查的不完美模拟可能带来安全隐患。
|
||||||
|
|
||||||
uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻力。
|
uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻力。
|
||||||
|
|
||||||
可用的指纹值:
|
可用的指纹值:
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! warning "已在 sing-box 1.10.0 移除"
|
||||||
|
|
||||||
:material-plus: chrome_psk
|
一些旧 chrome 指纹已被删除,并将会退到 chrome:
|
||||||
:material-plus: chrome_psk_shuffle
|
|
||||||
:material-plus: chrome_padding_psk_shuffle
|
:material-close: chrome_psk
|
||||||
:material-plus: chrome_pq
|
:material-close: chrome_psk_shuffle
|
||||||
:material-plus: chrome_pq_psk
|
:material-close: chrome_padding_psk_shuffle
|
||||||
|
:material-close: chrome_pq
|
||||||
|
:material-close: chrome_pq_psk
|
||||||
|
|
||||||
* chrome
|
* chrome
|
||||||
* chrome_psk
|
|
||||||
* chrome_psk_shuffle
|
|
||||||
* chrome_padding_psk_shuffle
|
|
||||||
* chrome_pq
|
|
||||||
* chrome_pq_psk
|
|
||||||
* firefox
|
* firefox
|
||||||
* edge
|
* edge
|
||||||
* safari
|
* safari
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
|
#### Match source rule items are renamed
|
||||||
|
|
||||||
|
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
|
||||||
|
`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
|
||||||
|
|
||||||
#### Drop support for go1.18 and go1.19
|
#### Drop support for go1.18 and go1.19
|
||||||
|
|
||||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||||
|
|||||||
@@ -6,13 +6,18 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
|
#### Match source 规则项已重命名
|
||||||
|
|
||||||
|
`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为
|
||||||
|
`rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||||
|
|
||||||
#### TUN 地址字段已合并
|
#### TUN 地址字段已合并
|
||||||
|
|
||||||
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||||
|
|
||||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
旧字段已废弃,且将在 sing-box 1.11.0 中被移除。
|
||||||
|
|
||||||
#### 移除对 go1.18 和 go1.19 的支持
|
#### 移除对 go1.18 和 go1.19 的支持
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,6 @@ description: Welcome to the wiki page for the sing-box project.
|
|||||||
|
|
||||||
# :material-home: Home
|
# :material-home: Home
|
||||||
|
|
||||||
!!! failure "Help needed"
|
|
||||||
|
|
||||||
Due to problems with our Apple developer account, sing-box apps on Apple platforms are temporarily unavailable for download or update.
|
|
||||||
|
|
||||||
If your company or organization is willing to help us return to the App Store, please [contact us](mailto:contact@sagernet.org).
|
|
||||||
|
|
||||||
Welcome to the wiki page for the sing-box project.
|
Welcome to the wiki page for the sing-box project.
|
||||||
|
|
||||||
The universal proxy platform.
|
The universal proxy platform.
|
||||||
|
|||||||
@@ -4,13 +4,6 @@ description: 欢迎来到该 sing-box 项目的文档页。
|
|||||||
|
|
||||||
# :material-home: 开始
|
# :material-home: 开始
|
||||||
|
|
||||||
!!! failure "需要帮助"
|
|
||||||
|
|
||||||
由于我们的 Apple 开发者账户出现问题,Apple 平台上的 sing-box 应用暂时无法下载或更新。
|
|
||||||
|
|
||||||
如果您的公司或组织愿意帮助我们重返 App Store,请[联系我们](mailto:contact@sagernet.org)。
|
|
||||||
|
|
||||||
|
|
||||||
欢迎来到该 sing-box 项目的文档页。
|
欢迎来到该 sing-box 项目的文档页。
|
||||||
|
|
||||||
通用代理平台。
|
通用代理平台。
|
||||||
|
|||||||
@@ -6,26 +6,18 @@ icon: material/file-code
|
|||||||
|
|
||||||
## :material-graph: Requirements
|
## :material-graph: Requirements
|
||||||
|
|
||||||
Before sing-box 1.4.0:
|
### sing-box 1.10
|
||||||
|
|
||||||
* Go 1.18.5 - 1.20.x
|
* Go 1.20.0 - ~
|
||||||
|
|
||||||
Since sing-box 1.4.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ with tag `with_quic` enabled
|
|
||||||
|
|
||||||
Since sing-box 1.5.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ with tag `with_quic` or `with_ech` enabled
|
|
||||||
|
|
||||||
Since sing-box 1.8.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled
|
* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled
|
||||||
* Go 1.21.0 - ~ with tag `with_ech` enabled
|
* Go 1.21.0 - ~ with tag `with_ech` enabled
|
||||||
|
|
||||||
|
### sing-box 1.9
|
||||||
|
|
||||||
|
* Go 1.18.5 - 1.22.x
|
||||||
|
* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled
|
||||||
|
* Go 1.21.0 - 1.22.x with tag `with_ech` enabled
|
||||||
|
|
||||||
You can download and install Go from: https://go.dev/doc/install, latest version is recommended.
|
You can download and install Go from: https://go.dev/doc/install, latest version is recommended.
|
||||||
|
|
||||||
## :material-fast-forward: Simple Build
|
## :material-fast-forward: Simple Build
|
||||||
|
|||||||
@@ -6,25 +6,17 @@ icon: material/file-code
|
|||||||
|
|
||||||
## :material-graph: 要求
|
## :material-graph: 要求
|
||||||
|
|
||||||
sing-box 1.4.0 前:
|
### sing-box 1.10
|
||||||
|
|
||||||
* Go 1.18.5 - 1.20.x
|
* Go 1.20.0 - ~
|
||||||
|
* Go 1.20.0 - ~ with tag `with_quic`, or `with_utls` enabled
|
||||||
|
* Go 1.21.0 - ~ with tag `with_ech` enabled
|
||||||
|
|
||||||
从 sing-box 1.4.0:
|
### sing-box 1.9
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
* Go 1.18.5 - 1.22.x
|
||||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic`
|
* Go 1.20.0 - 1.22.x with tag `with_quic`, or `with_utls` enabled
|
||||||
|
* Go 1.21.0 - 1.22.x with tag `with_ech` enabled
|
||||||
从 sing-box 1.5.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_ech`
|
|
||||||
|
|
||||||
从 sing-box 1.8.0:
|
|
||||||
|
|
||||||
* Go 1.18.5 - ~
|
|
||||||
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic` 或 `with_utls`
|
|
||||||
* Go 1.20.1 - ~ 如果启用构建标记 `with_ech`
|
|
||||||
|
|
||||||
您可以从 https://go.dev/doc/install 下载并安装 Go,推荐使用最新版本。
|
您可以从 https://go.dev/doc/install 下载并安装 Go,推荐使用最新版本。
|
||||||
|
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ icon: material/package
|
|||||||
## :material-alert: Problematic Sources
|
## :material-alert: Problematic Sources
|
||||||
|
|
||||||
| Type | Platform | Link | Promblem(s) |
|
| Type | Platform | Link | Promblem(s) |
|
||||||
|------------|----------|-------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
|------------|----------|-------------------------------------------------------------------------------------------|-----------------------------------------|
|
||||||
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification; Not actively maintained |
|
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | Problematic build tag list modification |
|
||||||
| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification |
|
| Homebrew | / | [homebrew-core][brew] | Problematic build tag list modification |
|
||||||
| Termux | Android | [termux-packages][termux] | Problematic build tag list modification |
|
| Termux | Android | [termux-packages][termux] | Problematic build tag list modification |
|
||||||
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) |
|
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | Old Go (go1.20) |
|
||||||
|
|||||||
@@ -85,12 +85,11 @@ icon: material/package
|
|||||||
|------------|---------|------------------------|--------------------------------------------------------------------------------------------|
|
|------------|---------|------------------------|--------------------------------------------------------------------------------------------|
|
||||||
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
| FreshPorts | FreeBSD | `pkg install sing-box` | [][ports] |
|
||||||
|
|
||||||
|
|
||||||
## :material-alert: 存在问题的源
|
## :material-alert: 存在问题的源
|
||||||
|
|
||||||
| 类型 | 平台 | 链接 | 原因 |
|
| 类型 | 平台 | 链接 | 原因 |
|
||||||
|------------|---------|-------------------------------------------------------------------------------------------|-----------------------|
|
|------------|---------|-------------------------------------------------------------------------------------------|-----------------|
|
||||||
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改; 没有活跃维护 |
|
| DEB | AOSC | [aosc-os-abbs](https://github.com/AOSC-Dev/aosc-os-abbs/tree/stable/app-network/sing-box) | 存在问题的构建标志列表修改 |
|
||||||
| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 |
|
| Homebrew | / | [homebrew-core][brew] | 存在问题的构建标志列表修改 |
|
||||||
| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 |
|
| Termux | Android | [termux-packages][termux] | 存在问题的构建标志列表修改 |
|
||||||
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) |
|
| FreshPorts | FreeBSD | [FreeBSD ports][ports] | 太旧的 Go (go1.20) |
|
||||||
|
|||||||
@@ -70,6 +70,23 @@ Old fields are deprecated and will be removed in sing-box 1.11.0.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 1.9.5
|
||||||
|
|
||||||
|
### Bundle Identifier updates in Apple platform clients
|
||||||
|
|
||||||
|
Due to problems with our old Apple developer account,
|
||||||
|
we can only change Bundle Identifiers to re-list sing-box apps,
|
||||||
|
which means the data will not be automatically inherited.
|
||||||
|
|
||||||
|
For iOS, you need to back up your old data yourself (if you still have access to it);
|
||||||
|
for tvOS, you need to re-import profiles from your iPhone or iPad or create it manually;
|
||||||
|
for macOS, you can migrate the data folder using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Library/Group\ Containers && \
|
||||||
|
mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt
|
||||||
|
```
|
||||||
|
|
||||||
## 1.9.0
|
## 1.9.0
|
||||||
|
|
||||||
### `domain_suffix` behavior update
|
### `domain_suffix` behavior update
|
||||||
|
|||||||
@@ -70,6 +70,22 @@ icon: material/arrange-bring-forward
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 1.9.5
|
||||||
|
|
||||||
|
### Apple 平台客户端的 Bundle Identifier 更新
|
||||||
|
|
||||||
|
由于我们旧的苹果开发者账户存在问题,我们只能通过更新 Bundle Identifiers
|
||||||
|
来重新上架 sing-box 应用, 这意味着数据不会自动继承。
|
||||||
|
|
||||||
|
对于 iOS,您需要自行备份旧的数据(如果您仍然可以访问);
|
||||||
|
对于 Apple tvOS,您需要从 iPhone 或 iPad 重新导入配置或者手动创建;
|
||||||
|
对于 macOS,您可以使用以下命令迁移数据文件夹:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Library/Group\ Containers && \
|
||||||
|
mv group.io.nekohasekai.sfa group.io.nekohasekai.sfavt
|
||||||
|
```
|
||||||
|
|
||||||
## 1.9.0
|
## 1.9.0
|
||||||
|
|
||||||
### `domain_suffix` 行为更新
|
### `domain_suffix` 行为更新
|
||||||
|
|||||||
26
docs/sponsors.md
Normal file
26
docs/sponsors.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
icon: material/hand-coin
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sponsors
|
||||||
|
|
||||||
|
Do you or your friends use sing-box?
|
||||||
|
|
||||||
|
You can help keep the project bug-free and feature rich by sponsoring
|
||||||
|
the project maintainer via [GitHub Sponsors](https://github.com/sponsors/nekohasekai).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Special Sponsors
|
||||||
|
|
||||||
|
**Viral Tech, Inc.**
|
||||||
|
|
||||||
|
Helping us re-list sing-box apps on the Apple Store.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[](https://www.jetbrains.com)
|
||||||
|
|
||||||
|
Free license for the amazing IDEs.
|
||||||
|
|
||||||
|
---
|
||||||
@@ -26,6 +26,8 @@ type configSchema struct {
|
|||||||
AllowLan bool `json:"allow-lan"`
|
AllowLan bool `json:"allow-lan"`
|
||||||
BindAddress string `json:"bind-address"`
|
BindAddress string `json:"bind-address"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
|
// sing-box added
|
||||||
|
ModeList []string `json:"mode-list"`
|
||||||
LogLevel string `json:"log-level"`
|
LogLevel string `json:"log-level"`
|
||||||
IPv6 bool `json:"ipv6"`
|
IPv6 bool `json:"ipv6"`
|
||||||
Tun map[string]any `json:"tun"`
|
Tun map[string]any `json:"tun"`
|
||||||
@@ -41,6 +43,7 @@ func getConfigs(server *Server, logFactory log.Factory) func(w http.ResponseWrit
|
|||||||
}
|
}
|
||||||
render.JSON(w, r, &configSchema{
|
render.JSON(w, r, &configSchema{
|
||||||
Mode: server.mode,
|
Mode: server.mode,
|
||||||
|
ModeList: server.modeList,
|
||||||
BindAddress: "*",
|
BindAddress: "*",
|
||||||
LogLevel: log.FormatLevel(logLevel),
|
LogLevel: log.FormatLevel(logLevel),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -124,11 +124,8 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
|||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
||||||
chiRouter.Group(func(r chi.Router) {
|
chiRouter.Group(func(r chi.Router) {
|
||||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
|
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusMovedPermanently).ServeHTTP)
|
||||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
r.Handle("/ui/*", http.StripPrefix("/ui/", http.FileServer(http.Dir(server.externalUI))))
|
||||||
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fs.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return server, nil
|
return server, nil
|
||||||
@@ -316,27 +313,29 @@ func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter,
|
|||||||
tick := time.NewTicker(time.Second)
|
tick := time.NewTicker(time.Second)
|
||||||
defer tick.Stop()
|
defer tick.Stop()
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
var err error
|
uploadTotal, downloadTotal := trafficManager.Total()
|
||||||
for range tick.C {
|
for range tick.C {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
up, down := trafficManager.Now()
|
uploadTotalNew, downloadTotalNew := trafficManager.Total()
|
||||||
if err := json.NewEncoder(buf).Encode(Traffic{
|
err := json.NewEncoder(buf).Encode(Traffic{
|
||||||
Up: up,
|
Up: uploadTotalNew - uploadTotal,
|
||||||
Down: down,
|
Down: downloadTotalNew - downloadTotal,
|
||||||
}); err != nil {
|
})
|
||||||
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
_, err = w.Write(buf.Bytes())
|
_, err = w.Write(buf.Bytes())
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
} else {
|
} else {
|
||||||
err = wsutil.WriteServerText(conn, buf.Bytes())
|
err = wsutil.WriteServerText(conn, buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadTotal = uploadTotalNew
|
||||||
|
downloadTotal = downloadTotalNew
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -60,7 +60,7 @@ func (s *Server) downloadExternalUI() error {
|
|||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
TLSHandshakeTimeout: C.TCPTimeout,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,30 +16,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
uploadTemp atomic.Int64
|
|
||||||
downloadTemp atomic.Int64
|
|
||||||
uploadBlip atomic.Int64
|
|
||||||
downloadBlip atomic.Int64
|
|
||||||
uploadTotal atomic.Int64
|
uploadTotal atomic.Int64
|
||||||
downloadTotal atomic.Int64
|
downloadTotal atomic.Int64
|
||||||
|
|
||||||
connections compatible.Map[uuid.UUID, Tracker]
|
connections compatible.Map[uuid.UUID, Tracker]
|
||||||
closedConnectionsAccess sync.Mutex
|
closedConnectionsAccess sync.Mutex
|
||||||
closedConnections list.List[TrackerMetadata]
|
closedConnections list.List[TrackerMetadata]
|
||||||
ticker *time.Ticker
|
|
||||||
done chan struct{}
|
|
||||||
// process *process.Process
|
// process *process.Process
|
||||||
memory uint64
|
memory uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
manager := &Manager{
|
return &Manager{}
|
||||||
ticker: time.NewTicker(time.Second),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
// process: &process.Process{Pid: int32(os.Getpid())},
|
|
||||||
}
|
|
||||||
go manager.handle()
|
|
||||||
return manager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Join(c Tracker) {
|
func (m *Manager) Join(c Tracker) {
|
||||||
@@ -61,19 +49,13 @@ func (m *Manager) Leave(c Tracker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) PushUploaded(size int64) {
|
func (m *Manager) PushUploaded(size int64) {
|
||||||
m.uploadTemp.Add(size)
|
|
||||||
m.uploadTotal.Add(size)
|
m.uploadTotal.Add(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) PushDownloaded(size int64) {
|
func (m *Manager) PushDownloaded(size int64) {
|
||||||
m.downloadTemp.Add(size)
|
|
||||||
m.downloadTotal.Add(size)
|
m.downloadTotal.Add(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Now() (up int64, down int64) {
|
|
||||||
return m.uploadBlip.Load(), m.downloadBlip.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Total() (up int64, down int64) {
|
func (m *Manager) Total() (up int64, down int64) {
|
||||||
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
||||||
}
|
}
|
||||||
@@ -127,36 +109,10 @@ func (m *Manager) Snapshot() *Snapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) ResetStatistic() {
|
func (m *Manager) ResetStatistic() {
|
||||||
m.uploadTemp.Store(0)
|
|
||||||
m.uploadBlip.Store(0)
|
|
||||||
m.uploadTotal.Store(0)
|
m.uploadTotal.Store(0)
|
||||||
m.downloadTemp.Store(0)
|
|
||||||
m.downloadBlip.Store(0)
|
|
||||||
m.downloadTotal.Store(0)
|
m.downloadTotal.Store(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) handle() {
|
|
||||||
var uploadTemp int64
|
|
||||||
var downloadTemp int64
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-m.done:
|
|
||||||
return
|
|
||||||
case <-m.ticker.C:
|
|
||||||
}
|
|
||||||
uploadTemp = m.uploadTemp.Swap(0)
|
|
||||||
downloadTemp = m.downloadTemp.Swap(0)
|
|
||||||
m.uploadBlip.Store(uploadTemp)
|
|
||||||
m.downloadBlip.Store(downloadTemp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
m.ticker.Stop()
|
|
||||||
close(m.done)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
Download int64
|
Download int64
|
||||||
Upload int64
|
Upload int64
|
||||||
|
|||||||
94
experimental/deprecated/constants.go
Normal file
94
experimental/deprecated/constants.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package deprecated
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/badversion"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/locale"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Note struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
DeprecatedVersion string
|
||||||
|
ScheduledVersion string
|
||||||
|
EnvName string
|
||||||
|
MigrationLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Note) Impending() bool {
|
||||||
|
if n.ScheduledVersion == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !semver.IsValid("v" + C.Version) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
versionCurrent := badversion.Parse(C.Version)
|
||||||
|
versionMinor := badversion.Parse(n.ScheduledVersion).Minor - versionCurrent.Minor
|
||||||
|
if versionCurrent.PreReleaseIdentifier == "" && versionMinor < 0 {
|
||||||
|
panic("invalid deprecated note: " + n.Name)
|
||||||
|
}
|
||||||
|
return versionMinor <= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Note) Message() string {
|
||||||
|
if n.MigrationLink != "" {
|
||||||
|
return fmt.Sprintf(locale.Current().DeprecatedMessage, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf(locale.Current().DeprecatedMessageNoLink, n.Description, n.DeprecatedVersion, n.ScheduledVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Note) MessageWithLink() string {
|
||||||
|
return F.ToString(
|
||||||
|
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||||
|
" and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionBadMatchSource = Note{
|
||||||
|
Name: "bad-match-source",
|
||||||
|
Description: "legacy match source rule item",
|
||||||
|
DeprecatedVersion: "1.10.0",
|
||||||
|
ScheduledVersion: "1.11.0",
|
||||||
|
EnvName: "BAD_MATCH_SOURCE",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionGEOIP = Note{
|
||||||
|
Name: "geoip",
|
||||||
|
Description: "geoip database",
|
||||||
|
DeprecatedVersion: "1.8.0",
|
||||||
|
ScheduledVersion: "1.12.0",
|
||||||
|
EnvName: "GEOIP",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geoip-to-rule-sets",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionGEOSITE = Note{
|
||||||
|
Name: "geosite",
|
||||||
|
Description: "geosite database",
|
||||||
|
DeprecatedVersion: "1.8.0",
|
||||||
|
ScheduledVersion: "1.12.0",
|
||||||
|
EnvName: "GEOSITE",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geosite-to-rule-sets",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionTUNAddressX = Note{
|
||||||
|
Name: "tun-address-x",
|
||||||
|
Description: "legacy tun address fields",
|
||||||
|
DeprecatedVersion: "1.10.0",
|
||||||
|
ScheduledVersion: "1.12.0",
|
||||||
|
EnvName: "TUN_ADDRESS_X",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged",
|
||||||
|
}
|
||||||
|
|
||||||
|
var Options = []Note{
|
||||||
|
OptionBadMatchSource,
|
||||||
|
OptionGEOIP,
|
||||||
|
OptionGEOSITE,
|
||||||
|
OptionTUNAddressX,
|
||||||
|
}
|
||||||
19
experimental/deprecated/manager.go
Normal file
19
experimental/deprecated/manager.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package deprecated
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
ReportDeprecated(feature Note)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Report(ctx context.Context, feature Note) {
|
||||||
|
manager := service.FromContext[Manager](ctx)
|
||||||
|
if manager == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.ReportDeprecated(feature)
|
||||||
|
}
|
||||||
38
experimental/deprecated/stderr.go
Normal file
38
experimental/deprecated/stderr.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package deprecated
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stderrManager struct {
|
||||||
|
logger logger.Logger
|
||||||
|
reported map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStderrManager(logger logger.Logger) Manager {
|
||||||
|
return &stderrManager{
|
||||||
|
logger: logger,
|
||||||
|
reported: make(map[string]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stderrManager) ReportDeprecated(feature Note) {
|
||||||
|
if f.reported[feature.Name] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.reported[feature.Name] = true
|
||||||
|
if !feature.Impending() {
|
||||||
|
f.logger.Warn(feature.MessageWithLink())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName))
|
||||||
|
if enableErr == nil && enable {
|
||||||
|
f.logger.Warn(feature.MessageWithLink())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.logger.Error(feature.MessageWithLink())
|
||||||
|
f.logger.Fatal("to continuing using this feature, set ENABLE_DEPRECATED_" + feature.EnvName + "=true")
|
||||||
|
}
|
||||||
@@ -16,4 +16,5 @@ const (
|
|||||||
CommandSetSystemProxyEnabled
|
CommandSetSystemProxyEnabled
|
||||||
CommandConnections
|
CommandConnections
|
||||||
CommandCloseConnection
|
CommandCloseConnection
|
||||||
|
CommandGetDeprecatedNotes
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -113,11 +113,24 @@ func (c *CommandClient) Connect() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if sFixAndroidStack {
|
||||||
|
go func() {
|
||||||
c.handler.Connected()
|
c.handler.Connected()
|
||||||
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
||||||
if len(modeList) == 0 {
|
if len(modeList) == 0 {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
c.handler.Disconnected(os.ErrInvalid.Error())
|
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
c.handler.Connected()
|
||||||
|
c.handler.InitializeClashMode(newIterator(modeList), currentMode)
|
||||||
|
if len(modeList) == 0 {
|
||||||
|
conn.Close()
|
||||||
|
c.handler.Disconnected(os.ErrInvalid.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(modeList) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
go c.handleModeConn(conn)
|
go c.handleModeConn(conn)
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ func (c *CommandClient) CloseConnection(connId string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
err = binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnection))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
writer := bufio.NewWriter(conn)
|
writer := bufio.NewWriter(conn)
|
||||||
err = varbin.Write(writer, binary.BigEndian, connId)
|
err = varbin.Write(writer, binary.BigEndian, connId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func (c *CommandClient) handleConnectionsConn(conn net.Conn) {
|
|||||||
connections Connections
|
connections Connections
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
|
rawConnections = nil
|
||||||
err := varbin.Read(reader, binary.BigEndian, &rawConnections)
|
err := varbin.Read(reader, binary.BigEndian, &rawConnections)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.handler.Disconnected(err.Error())
|
c.handler.Disconnected(err.Error())
|
||||||
|
|||||||
46
experimental/libbox/command_deprecated_report.go
Normal file
46
experimental/libbox/command_deprecated_report.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
||||||
|
conn, err := c.directConnect()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
err = binary.Write(conn, binary.BigEndian, uint8(CommandGetDeprecatedNotes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = readError(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var features []deprecated.Note
|
||||||
|
err = varbin.Read(conn, binary.BigEndian, &features)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newIterator(common.Map(features, func(it deprecated.Note) *DeprecatedNote { return (*DeprecatedNote)(&it) })), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleGetDeprecatedNotes(conn net.Conn) error {
|
||||||
|
boxService := s.service
|
||||||
|
if boxService == nil {
|
||||||
|
return writeError(conn, E.New("service not ready"))
|
||||||
|
}
|
||||||
|
err := writeError(conn, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return varbin.Write(conn, binary.BigEndian, service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get())
|
||||||
|
}
|
||||||
@@ -174,6 +174,8 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
|||||||
return s.handleConnectionsConn(conn)
|
return s.handleConnectionsConn(conn)
|
||||||
case CommandCloseConnection:
|
case CommandCloseConnection:
|
||||||
return s.handleCloseConnection(conn)
|
return s.handleCloseConnection(conn)
|
||||||
|
case CommandGetDeprecatedNotes:
|
||||||
|
return s.handleGetDeprecatedNotes(conn)
|
||||||
default:
|
default:
|
||||||
return E.New("unknown command: ", command)
|
return E.New("unknown command: ", command)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ func (s *CommandServer) readStatus() StatusMessage {
|
|||||||
if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil {
|
if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil {
|
||||||
message.TrafficAvailable = true
|
message.TrafficAvailable = true
|
||||||
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
|
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
|
||||||
message.Uplink, message.Downlink = trafficManager.Now()
|
|
||||||
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
|
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
|
||||||
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
|
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
|
||||||
}
|
}
|
||||||
@@ -52,8 +51,11 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
|||||||
ticker := time.NewTicker(time.Duration(interval))
|
ticker := time.NewTicker(time.Duration(interval))
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
ctx := connKeepAlive(conn)
|
ctx := connKeepAlive(conn)
|
||||||
|
status := s.readStatus()
|
||||||
|
uploadTotal := status.UplinkTotal
|
||||||
|
downloadTotal := status.DownlinkTotal
|
||||||
for {
|
for {
|
||||||
err = binary.Write(conn, binary.BigEndian, s.readStatus())
|
err = binary.Write(conn, binary.BigEndian, status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -62,6 +64,13 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
}
|
}
|
||||||
|
status = s.readStatus()
|
||||||
|
upload := status.UplinkTotal - uploadTotal
|
||||||
|
download := status.DownlinkTotal - downloadTotal
|
||||||
|
uploadTotal = status.UplinkTotal
|
||||||
|
downloadTotal = status.DownlinkTotal
|
||||||
|
status.Uplink = upload
|
||||||
|
status.Downlink = download
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -54,7 +55,7 @@ func (s *platformInterfaceStub) UsePlatformAutoDetectInterfaceControl() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) AutoDetectInterfaceControl() control.Func {
|
func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,17 +135,21 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
|
|||||||
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatConfig(configContent string) (string, error) {
|
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatConfig(configContent string) (*StringBox, error) {
|
||||||
options, err := parseConfig(configContent)
|
options, err := parseConfig(configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
encoder := json.NewEncoder(&buffer)
|
encoder := json.NewEncoder(&buffer)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
err = encoder.Encode(options)
|
err = encoder.Encode(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return buffer.String(), nil
|
return wrapString(buffer.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
57
experimental/libbox/deprecated.go
Normal file
57
experimental/libbox/deprecated.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ deprecated.Manager = (*deprecatedManager)(nil)
|
||||||
|
|
||||||
|
type deprecatedManager struct {
|
||||||
|
access sync.Mutex
|
||||||
|
notes []deprecated.Note
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.notes = common.Uniq(append(m.notes, feature))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *deprecatedManager) Get() []deprecated.Note {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
notes := m.notes
|
||||||
|
m.notes = nil
|
||||||
|
return notes
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = deprecated.Note(DeprecatedNote{})
|
||||||
|
|
||||||
|
type DeprecatedNote struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
DeprecatedVersion string
|
||||||
|
ScheduledVersion string
|
||||||
|
EnvName string
|
||||||
|
MigrationLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n DeprecatedNote) Impending() bool {
|
||||||
|
return deprecated.Note(n).Impending()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n DeprecatedNote) Message() string {
|
||||||
|
return deprecated.Note(n).Message()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n DeprecatedNote) MessageWithLink() string {
|
||||||
|
return deprecated.Note(n).MessageWithLink()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeprecatedNoteIterator interface {
|
||||||
|
HasNext() bool
|
||||||
|
Next() *DeprecatedNote
|
||||||
|
}
|
||||||
@@ -17,8 +17,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -50,8 +50,7 @@ type HTTPRequest interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPResponse interface {
|
type HTTPResponse interface {
|
||||||
GetContent() ([]byte, error)
|
GetContent() (*StringBox, error)
|
||||||
GetContentString() (string, error)
|
|
||||||
WriteTo(path string) error
|
WriteTo(path string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +68,9 @@ type httpClient struct {
|
|||||||
|
|
||||||
func NewHTTPClient() HTTPClient {
|
func NewHTTPClient() HTTPClient {
|
||||||
client := new(httpClient)
|
client := new(httpClient)
|
||||||
client.client.Timeout = 15 * time.Second
|
|
||||||
client.client.Transport = &client.transport
|
client.client.Transport = &client.transport
|
||||||
|
client.transport.ForceAttemptHTTP2 = true
|
||||||
|
client.transport.TLSHandshakeTimeout = C.TCPTimeout
|
||||||
client.transport.TLSClientConfig = &client.tls
|
client.transport.TLSClientConfig = &client.tls
|
||||||
client.transport.DisableKeepAlives = true
|
client.transport.DisableKeepAlives = true
|
||||||
return client
|
return client
|
||||||
@@ -127,7 +127,6 @@ func (c *httpClient) TrySocks5(port int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpClient) KeepAlive() {
|
func (c *httpClient) KeepAlive() {
|
||||||
c.transport.ForceAttemptHTTP2 = true
|
|
||||||
c.transport.DisableKeepAlives = false
|
c.transport.DisableKeepAlives = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,27 +209,22 @@ type httpResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpResponse) errorString() string {
|
func (h *httpResponse) errorString() string {
|
||||||
content, err := h.GetContentString()
|
content, err := h.GetContent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprint("HTTP ", h.Status)
|
return fmt.Sprint("HTTP ", h.Status)
|
||||||
}
|
}
|
||||||
return fmt.Sprint("HTTP ", h.Status, ": ", content)
|
return fmt.Sprint("HTTP ", h.Status, ": ", content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpResponse) GetContent() ([]byte, error) {
|
func (h *httpResponse) GetContent() (*StringBox, error) {
|
||||||
h.getContentOnce.Do(func() {
|
h.getContentOnce.Do(func() {
|
||||||
defer h.Body.Close()
|
defer h.Body.Close()
|
||||||
h.content, h.contentError = io.ReadAll(h.Body)
|
h.content, h.contentError = io.ReadAll(h.Body)
|
||||||
})
|
})
|
||||||
return h.content, h.contentError
|
if h.contentError != nil {
|
||||||
|
return nil, h.contentError
|
||||||
}
|
}
|
||||||
|
return wrapString(string(h.content)), nil
|
||||||
func (h *httpResponse) GetContentString() (string, error) {
|
|
||||||
content, err := h.GetContent()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(content), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpResponse) WriteTo(path string) error {
|
func (h *httpResponse) WriteTo(path string) error {
|
||||||
|
|||||||
30
experimental/libbox/link_flags_linux.go
Normal file
30
experimental/libbox/link_flags_linux.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// copied from net.linkFlags
|
||||||
|
func linkFlags(rawFlags uint32) net.Flags {
|
||||||
|
var f net.Flags
|
||||||
|
if rawFlags&syscall.IFF_UP != 0 {
|
||||||
|
f |= net.FlagUp
|
||||||
|
}
|
||||||
|
if rawFlags&syscall.IFF_RUNNING != 0 {
|
||||||
|
f |= net.FlagRunning
|
||||||
|
}
|
||||||
|
if rawFlags&syscall.IFF_BROADCAST != 0 {
|
||||||
|
f |= net.FlagBroadcast
|
||||||
|
}
|
||||||
|
if rawFlags&syscall.IFF_LOOPBACK != 0 {
|
||||||
|
f |= net.FlagLoopback
|
||||||
|
}
|
||||||
|
if rawFlags&syscall.IFF_POINTOPOINT != 0 {
|
||||||
|
f |= net.FlagPointToPoint
|
||||||
|
}
|
||||||
|
if rawFlags&syscall.IFF_MULTICAST != 0 {
|
||||||
|
f |= net.FlagMulticast
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
11
experimental/libbox/link_flags_stub.go
Normal file
11
experimental/libbox/link_flags_stub.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func linkFlags(rawFlags uint32) net.Flags {
|
||||||
|
panic("stub!")
|
||||||
|
}
|
||||||
@@ -97,6 +97,14 @@ func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Eleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
||||||
|
if sFixAndroidStack {
|
||||||
|
go m.updateDefaultInterface(interfaceName, interfaceIndex32)
|
||||||
|
} else {
|
||||||
|
m.updateDefaultInterface(interfaceName, interfaceIndex32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
||||||
if interfaceName == "" || interfaceIndex32 == -1 {
|
if interfaceName == "" || interfaceIndex32 == -1 {
|
||||||
m.defaultInterfaceName = ""
|
m.defaultInterfaceName = ""
|
||||||
m.defaultInterfaceIndex = -1
|
m.defaultInterfaceIndex = -1
|
||||||
|
|||||||
12
experimental/libbox/panic.go
Normal file
12
experimental/libbox/panic.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
// https://github.com/golang/go/issues/46893
|
||||||
|
// TODO: remove after `bulkBarrierPreWrite: unaligned arguments` fixed
|
||||||
|
|
||||||
|
type StringBox struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapString(value string) *StringBox {
|
||||||
|
return &StringBox{Value: value}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ type PlatformInterface interface {
|
|||||||
IncludeAllNetworks() bool
|
IncludeAllNetworks() bool
|
||||||
ReadWIFIState() *WIFIState
|
ReadWIFIState() *WIFIState
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
|
SendNotification(notification *Notification) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunInterface interface {
|
type TunInterface interface {
|
||||||
@@ -38,6 +39,7 @@ type NetworkInterface struct {
|
|||||||
MTU int32
|
MTU int32
|
||||||
Name string
|
Name string
|
||||||
Addresses StringIterator
|
Addresses StringIterator
|
||||||
|
Flags int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type WIFIState struct {
|
type WIFIState struct {
|
||||||
@@ -54,6 +56,16 @@ type NetworkInterfaceIterator interface {
|
|||||||
HasNext() bool
|
HasNext() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
Identifier string
|
||||||
|
TypeName string
|
||||||
|
TypeID int32
|
||||||
|
Title string
|
||||||
|
Subtitle string
|
||||||
|
Body string
|
||||||
|
OpenURL string
|
||||||
|
}
|
||||||
|
|
||||||
type OnDemandRule interface {
|
type OnDemandRule interface {
|
||||||
Target() int32
|
Target() int32
|
||||||
DNSSearchDomainMatch() StringIterator
|
DNSSearchDomainMatch() StringIterator
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
type Interface interface {
|
type Interface interface {
|
||||||
Initialize(ctx context.Context, router adapter.Router) error
|
Initialize(ctx context.Context, router adapter.Router) error
|
||||||
UsePlatformAutoDetectInterfaceControl() bool
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
AutoDetectInterfaceControl() control.Func
|
AutoDetectInterfaceControl(fd int) error
|
||||||
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||||
UsePlatformDefaultInterfaceMonitor() bool
|
UsePlatformDefaultInterfaceMonitor() bool
|
||||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||||
@@ -25,4 +25,15 @@ type Interface interface {
|
|||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
ReadWIFIState() adapter.WIFIState
|
ReadWIFIState() adapter.WIFIState
|
||||||
process.Searcher
|
process.Searcher
|
||||||
|
SendNotification(notification *Notification) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
Identifier string
|
||||||
|
TypeName string
|
||||||
|
TypeID int32
|
||||||
|
Title string
|
||||||
|
Subtitle string
|
||||||
|
Body string
|
||||||
|
OpenURL string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = binary.Read(reader, binary.BigEndian, &content.Type)
|
err = binary.Read(bReader, binary.BigEndian, &content.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -233,17 +233,17 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) {
|
if content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) {
|
||||||
err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate)
|
err = binary.Read(bReader, binary.BigEndian, &content.AutoUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if version >= 1 {
|
if version >= 1 {
|
||||||
err = binary.Read(reader, binary.BigEndian, &content.AutoUpdateInterval)
|
err = binary.Read(bReader, binary.BigEndian, &content.AutoUpdateInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = binary.Read(reader, binary.BigEndian, &content.LastUpdated)
|
err = binary.Read(bReader, binary.BigEndian, &content.LastUpdated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -49,6 +50,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
|||||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||||
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||||
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||||
|
ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager))
|
||||||
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
|
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
@@ -71,23 +73,36 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) Start() error {
|
func (s *BoxService) Start() error {
|
||||||
|
if sFixAndroidStack {
|
||||||
|
var err error
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
err = s.instance.Start()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
<-done
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
return s.instance.Start()
|
return s.instance.Start()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *BoxService) Close() error {
|
func (s *BoxService) Close() error {
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case <-time.After(C.FatalStopTimeout):
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
s.cancel()
|
s.cancel()
|
||||||
s.urlTestHistoryStorage.Close()
|
s.urlTestHistoryStorage.Close()
|
||||||
return s.instance.Close()
|
var err error
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
err = s.instance.Close()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return err
|
||||||
|
case <-time.After(C.FatalStopTimeout):
|
||||||
|
os.Exit(1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoxService) NeedWIFIState() bool {
|
func (s *BoxService) NeedWIFIState() bool {
|
||||||
@@ -114,12 +129,8 @@ func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool
|
|||||||
return w.iif.UsePlatformAutoDetectInterfaceControl()
|
return w.iif.UsePlatformAutoDetectInterfaceControl()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
|
||||||
return func(network, address string, conn syscall.RawConn) error {
|
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
|
||||||
return w.iif.AutoDetectInterfaceControl(int32(fd))
|
return w.iif.AutoDetectInterfaceControl(int32(fd))
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||||
@@ -177,6 +188,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) {
|
|||||||
MTU: int(netInterface.MTU),
|
MTU: int(netInterface.MTU),
|
||||||
Name: netInterface.Name,
|
Name: netInterface.Name,
|
||||||
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
||||||
|
Flags: linkFlags(uint32(netInterface.Flags)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return interfaces, nil
|
return interfaces, nil
|
||||||
@@ -236,3 +248,7 @@ func (w *platformInterfaceWrapper) DisableColors() bool {
|
|||||||
func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) {
|
func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) {
|
||||||
w.iif.WriteLog(message)
|
w.iif.WriteLog(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) SendNotification(notification *platform.Notification) error {
|
||||||
|
return w.iif.SendNotification((*Notification)(notification))
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user