mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-07 00:49:16 +03:00
Compare commits
57 Commits
v1.13.11-e
...
dev-test-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1189d88048 | ||
|
|
82bc416985 | ||
|
|
276584be09 | ||
|
|
5d8f43cc6b | ||
|
|
ef92ed6795 | ||
|
|
e257b19bb3 | ||
|
|
e0e7a9ee96 | ||
|
|
170d22c4c5 | ||
|
|
b0a36b9fac | ||
|
|
079c491064 | ||
|
|
6794db201e | ||
|
|
a36080c51f | ||
|
|
324073f5dd | ||
|
|
ece54e8f2b | ||
|
|
566b97d5e5 | ||
|
|
d698e23a4d | ||
|
|
af1db56132 | ||
|
|
7a22d507b1 | ||
|
|
e9fb5faef6 | ||
|
|
7d6adf6933 | ||
|
|
f47803ca36 | ||
|
|
19b388fee4 | ||
|
|
d533f3e5c6 | ||
|
|
6435808a44 | ||
|
|
4975c3acac | ||
|
|
5c6fcc82ff | ||
|
|
652ea40c08 | ||
|
|
3488877fb9 | ||
|
|
a1547b337c | ||
|
|
d9dc7da43d | ||
|
|
a2fc5a04ce | ||
|
|
1a3c9ec2a4 | ||
|
|
3bbb0f4439 | ||
|
|
f982c02c34 | ||
|
|
ea77932276 | ||
|
|
3ef7cb1333 | ||
|
|
7035d803fc | ||
|
|
462587696b | ||
|
|
e4e0744da5 | ||
|
|
0c261c205b | ||
|
|
6249f076c6 | ||
|
|
9559d7b375 | ||
|
|
a160309182 | ||
|
|
16c3c682f8 | ||
|
|
f49536012e | ||
|
|
773cb3dcec | ||
|
|
8aaaa06d92 | ||
|
|
ce0ae1fa29 | ||
|
|
cf8889ba5d | ||
|
|
dbe8239ed9 | ||
|
|
28f937e13e | ||
|
|
a3c44f7160 | ||
|
|
469faff07c | ||
|
|
067ea0dccb | ||
|
|
e515c91bd2 | ||
|
|
2c648b7fbe | ||
|
|
b3ea0bd833 |
31
.fpm_openwrt
31
.fpm_openwrt
@@ -1,31 +0,0 @@
|
|||||||
-s dir
|
|
||||||
--name sing-box
|
|
||||||
--category net
|
|
||||||
--license GPL-3.0-or-later
|
|
||||||
--description "The universal proxy platform."
|
|
||||||
--url "https://sing-box.sagernet.org/"
|
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
|
||||||
--no-deb-generate-changes
|
|
||||||
|
|
||||||
--config-files /etc/config/sing-box
|
|
||||||
--config-files /etc/sing-box/config.json
|
|
||||||
|
|
||||||
--depends ca-bundle
|
|
||||||
--depends kmod-inet-diag
|
|
||||||
--depends kmod-tun
|
|
||||||
--depends firewall4
|
|
||||||
--depends kmod-nft-queue
|
|
||||||
|
|
||||||
--before-remove release/config/openwrt.prerm
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
|
||||||
|
|
||||||
release/config/openwrt.conf=/etc/config/sing-box
|
|
||||||
release/config/openwrt.init=/etc/init.d/sing-box
|
|
||||||
release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box
|
|
||||||
|
|
||||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
|
||||||
23
.fpm_pacman
23
.fpm_pacman
@@ -1,23 +0,0 @@
|
|||||||
-s dir
|
|
||||||
--name sing-box
|
|
||||||
--category net
|
|
||||||
--license GPL-3.0-or-later
|
|
||||||
--description "The universal proxy platform."
|
|
||||||
--url "https://sing-box.sagernet.org/"
|
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
|
||||||
--config-files etc/sing-box/config.json
|
|
||||||
--after-install release/config/sing-box.postinst
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
|
||||||
|
|
||||||
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
|
||||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
|
||||||
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
|
||||||
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
|
||||||
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
|
||||||
|
|
||||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
|
||||||
26
.fpm_systemd
26
.fpm_systemd
@@ -1,26 +0,0 @@
|
|||||||
-s dir
|
|
||||||
--name sing-box
|
|
||||||
--category net
|
|
||||||
--license GPL-3.0-or-later
|
|
||||||
--description "The universal proxy platform."
|
|
||||||
--url "https://sing-box.sagernet.org/"
|
|
||||||
--vendor SagerNet
|
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
|
||||||
--no-deb-generate-changes
|
|
||||||
--config-files /etc/sing-box/config.json
|
|
||||||
--after-install release/config/sing-box.postinst
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
|
||||||
|
|
||||||
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
|
||||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
|
||||||
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
|
||||||
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
|
||||||
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
|
||||||
|
|
||||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
|
||||||
1
.github/CRONET_GO_VERSION
vendored
1
.github/CRONET_GO_VERSION
vendored
@@ -1 +0,0 @@
|
|||||||
e4926ba205fae5351e3d3eeafff7e7029654424a
|
|
||||||
94
.github/build_alpine_apk.sh
vendored
94
.github/build_alpine_apk.sh
vendored
@@ -1,94 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
prepare_apk_root() {
|
|
||||||
# apk mkpkg resolves owner/group names through --root/etc/{passwd,group}.
|
|
||||||
APK_ROOT_DIR=$(mktemp -d)
|
|
||||||
mkdir -p "$APK_ROOT_DIR/etc"
|
|
||||||
cat > "$APK_ROOT_DIR/etc/passwd" <<EOF
|
|
||||||
root:x:$(id -u):$(id -g):root:/root:/sbin/nologin
|
|
||||||
EOF
|
|
||||||
cat > "$APK_ROOT_DIR/etc/group" <<EOF
|
|
||||||
root:x:$(id -g):root
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
ARCHITECTURE="$1"
|
|
||||||
VERSION="$2"
|
|
||||||
BINARY_PATH="$3"
|
|
||||||
OUTPUT_PATH="$4"
|
|
||||||
|
|
||||||
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
|
|
||||||
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
|
||||||
|
|
||||||
# Convert version to APK format:
|
|
||||||
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
|
|
||||||
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
|
|
||||||
# 1.13.0 -> 1.13.0-r0
|
|
||||||
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
|
|
||||||
APK_VERSION="${APK_VERSION}-r0"
|
|
||||||
|
|
||||||
ROOT_DIR=$(mktemp -d)
|
|
||||||
prepare_apk_root
|
|
||||||
trap 'rm -rf "$ROOT_DIR" "$APK_ROOT_DIR"' EXIT
|
|
||||||
|
|
||||||
# Binary
|
|
||||||
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
|
|
||||||
|
|
||||||
# Config files
|
|
||||||
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
|
|
||||||
install -Dm755 "$PROJECT/release/config/sing-box.initd" "$ROOT_DIR/etc/init.d/sing-box"
|
|
||||||
install -Dm644 "$PROJECT/release/config/sing-box.confd" "$ROOT_DIR/etc/conf.d/sing-box"
|
|
||||||
|
|
||||||
# Service files
|
|
||||||
install -Dm644 "$PROJECT/release/config/sing-box.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box.service"
|
|
||||||
install -Dm644 "$PROJECT/release/config/sing-box@.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box@.service"
|
|
||||||
|
|
||||||
# Completions
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
|
|
||||||
|
|
||||||
# License
|
|
||||||
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
|
|
||||||
|
|
||||||
# APK metadata
|
|
||||||
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
|
|
||||||
mkdir -p "$PACKAGES_DIR"
|
|
||||||
|
|
||||||
# .conffiles
|
|
||||||
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
|
|
||||||
/etc/conf.d/sing-box
|
|
||||||
/etc/init.d/sing-box
|
|
||||||
/etc/sing-box/config.json
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# .conffiles_static (sha256 checksums)
|
|
||||||
while IFS= read -r conffile; do
|
|
||||||
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
|
|
||||||
echo "$conffile $sha256"
|
|
||||||
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
|
||||||
|
|
||||||
# .list (all files, excluding lib/apk/packages/ metadata)
|
|
||||||
(cd "$ROOT_DIR" && find . -type f -o -type l) \
|
|
||||||
| sed 's|^\./|/|' \
|
|
||||||
| grep -v '^/lib/apk/packages/' \
|
|
||||||
| sort > "$PACKAGES_DIR/.list"
|
|
||||||
|
|
||||||
# Build APK
|
|
||||||
apk --root "$APK_ROOT_DIR" mkpkg \
|
|
||||||
--info "name:sing-box" \
|
|
||||||
--info "version:${APK_VERSION}" \
|
|
||||||
--info "description:The universal proxy platform." \
|
|
||||||
--info "arch:${ARCHITECTURE}" \
|
|
||||||
--info "license:GPL-3.0-or-later with name use or association addition" \
|
|
||||||
--info "origin:sing-box" \
|
|
||||||
--info "url:https://sing-box.sagernet.org/" \
|
|
||||||
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
|
||||||
--files "$ROOT_DIR" \
|
|
||||||
--output "$OUTPUT_PATH"
|
|
||||||
93
.github/build_openwrt_apk.sh
vendored
93
.github/build_openwrt_apk.sh
vendored
@@ -1,93 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
prepare_apk_root() {
|
|
||||||
# apk mkpkg resolves owner/group names through --root/etc/{passwd,group}.
|
|
||||||
APK_ROOT_DIR=$(mktemp -d)
|
|
||||||
mkdir -p "$APK_ROOT_DIR/etc"
|
|
||||||
cat > "$APK_ROOT_DIR/etc/passwd" <<EOF
|
|
||||||
root:x:$(id -u):$(id -g):root:/root:/sbin/nologin
|
|
||||||
EOF
|
|
||||||
cat > "$APK_ROOT_DIR/etc/group" <<EOF
|
|
||||||
root:x:$(id -g):root
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
ARCHITECTURE="$1"
|
|
||||||
VERSION="$2"
|
|
||||||
BINARY_PATH="$3"
|
|
||||||
OUTPUT_PATH="$4"
|
|
||||||
|
|
||||||
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
|
|
||||||
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
|
||||||
|
|
||||||
# Convert version to APK format:
|
|
||||||
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
|
|
||||||
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
|
|
||||||
# 1.13.0 -> 1.13.0-r0
|
|
||||||
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
|
|
||||||
APK_VERSION="${APK_VERSION}-r0"
|
|
||||||
|
|
||||||
ROOT_DIR=$(mktemp -d)
|
|
||||||
prepare_apk_root
|
|
||||||
trap 'rm -rf "$ROOT_DIR" "$APK_ROOT_DIR"' EXIT
|
|
||||||
|
|
||||||
# Binary
|
|
||||||
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
|
|
||||||
|
|
||||||
# Config files
|
|
||||||
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
|
|
||||||
install -Dm644 "$PROJECT/release/config/openwrt.conf" "$ROOT_DIR/etc/config/sing-box"
|
|
||||||
install -Dm755 "$PROJECT/release/config/openwrt.init" "$ROOT_DIR/etc/init.d/sing-box"
|
|
||||||
install -Dm644 "$PROJECT/release/config/openwrt.keep" "$ROOT_DIR/lib/upgrade/keep.d/sing-box"
|
|
||||||
|
|
||||||
# Completions
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
|
|
||||||
|
|
||||||
# License
|
|
||||||
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
|
|
||||||
|
|
||||||
# APK metadata
|
|
||||||
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
|
|
||||||
mkdir -p "$PACKAGES_DIR"
|
|
||||||
|
|
||||||
# .conffiles
|
|
||||||
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
|
|
||||||
/etc/config/sing-box
|
|
||||||
/etc/sing-box/config.json
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# .conffiles_static (sha256 checksums)
|
|
||||||
while IFS= read -r conffile; do
|
|
||||||
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
|
|
||||||
echo "$conffile $sha256"
|
|
||||||
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
|
||||||
|
|
||||||
# .list (all files, excluding lib/apk/packages/ metadata)
|
|
||||||
(cd "$ROOT_DIR" && find . -type f -o -type l) \
|
|
||||||
| sed 's|^\./|/|' \
|
|
||||||
| grep -v '^/lib/apk/packages/' \
|
|
||||||
| sort > "$PACKAGES_DIR/.list"
|
|
||||||
|
|
||||||
# Build APK
|
|
||||||
apk --root "$APK_ROOT_DIR" mkpkg \
|
|
||||||
--info "name:sing-box" \
|
|
||||||
--info "version:${APK_VERSION}" \
|
|
||||||
--info "description:The universal proxy platform." \
|
|
||||||
--info "arch:${ARCHITECTURE}" \
|
|
||||||
--info "license:GPL-3.0-or-later" \
|
|
||||||
--info "origin:sing-box" \
|
|
||||||
--info "url:https://sing-box.sagernet.org/" \
|
|
||||||
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
|
||||||
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
|
|
||||||
--info "provider-priority:100" \
|
|
||||||
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
|
|
||||||
--files "$ROOT_DIR" \
|
|
||||||
--output "$OUTPUT_PATH"
|
|
||||||
28
.github/deb2ipk.sh
vendored
28
.github/deb2ipk.sh
vendored
@@ -1,28 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
PROJECT=$(dirname "$0")/../..
|
|
||||||
TMP_PATH=`mktemp -d`
|
|
||||||
cp $2 $TMP_PATH
|
|
||||||
pushd $TMP_PATH
|
|
||||||
|
|
||||||
DEB_NAME=`ls *.deb`
|
|
||||||
ar x $DEB_NAME
|
|
||||||
|
|
||||||
mkdir control
|
|
||||||
pushd control
|
|
||||||
tar xf ../control.tar.gz
|
|
||||||
rm md5sums
|
|
||||||
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
|
|
||||||
cat control
|
|
||||||
tar czf ../control.tar.gz ./*
|
|
||||||
popd
|
|
||||||
|
|
||||||
DEB_NAME=${DEB_NAME%.deb}
|
|
||||||
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
|
|
||||||
popd
|
|
||||||
|
|
||||||
cp $TMP_PATH/$DEB_NAME.ipk $3
|
|
||||||
rm -r $TMP_PATH
|
|
||||||
33
.github/detect_track.sh
vendored
33
.github/detect_track.sh
vendored
@@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
branches=$(git branch -r --contains HEAD)
|
|
||||||
if echo "$branches" | grep -q 'origin/stable'; then
|
|
||||||
track=stable
|
|
||||||
elif echo "$branches" | grep -q 'origin/testing'; then
|
|
||||||
track=testing
|
|
||||||
elif echo "$branches" | grep -q 'origin/oldstable'; then
|
|
||||||
track=oldstable
|
|
||||||
else
|
|
||||||
echo "ERROR: HEAD is not on any known release branch (stable/testing/oldstable)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$track" == "stable" ]]; then
|
|
||||||
tag=$(git describe --tags --exact-match HEAD 2>/dev/null || true)
|
|
||||||
if [[ -n "$tag" && "$tag" == *"-"* ]]; then
|
|
||||||
track=beta
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$track" in
|
|
||||||
stable) name=sing-box; docker_tag=latest ;;
|
|
||||||
beta) name=sing-box-beta; docker_tag=latest-beta ;;
|
|
||||||
testing) name=sing-box-testing; docker_tag=latest-testing ;;
|
|
||||||
oldstable) name=sing-box-oldstable; docker_tag=latest-oldstable ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "track=${track} name=${name} docker_tag=${docker_tag}" >&2
|
|
||||||
echo "TRACK=${track}" >> "$GITHUB_ENV"
|
|
||||||
echo "NAME=${name}" >> "$GITHUB_ENV"
|
|
||||||
echo "DOCKER_TAG=${docker_tag}" >> "$GITHUB_ENV"
|
|
||||||
3
.github/goreleaser/README.md
vendored
Normal file
3
.github/goreleaser/README.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# goreleaser
|
||||||
|
|
||||||
|
I'm sorry for this, but I can't afford to renew for now because the subscription is too expensive for an open source project.
|
||||||
BIN
.github/goreleaser/ca.crt
vendored
Normal file
BIN
.github/goreleaser/ca.crt
vendored
Normal file
Binary file not shown.
87
.github/goreleaser/config.json
vendored
Normal file
87
.github/goreleaser/config.json
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "tls",
|
||||||
|
"server": "8.8.8.8"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "tun",
|
||||||
|
"address": [
|
||||||
|
"172.19.0.1/30",
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
],
|
||||||
|
"auto_route": true,
|
||||||
|
"auto_redirect": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"certificate": {
|
||||||
|
"tls_decryption": {
|
||||||
|
"enabled": true,
|
||||||
|
"key_pair_p12": "MIIKYQIBAzCCChEGCSqGSIb3DQEHAaCCCgIEggn+MIIJ+jCCBGgGCSqGSIb3DQEHBqCCBFkwggRVAgEAMIIETgYJKoZIhvcNAQcBMF0GCSqGSIb3DQEFDTBQMC8GCSqGSIb3DQEFDDAiBBBxLjkB6wrMHpRNPnq8KUnXAgIIADAKBggqhkiG9w0CCTAdBglghkgBZQMEASoEEHFou8IR0ZPb9O4NaLDC5LKAggPgL/7EoJRMEx5ZDVm2ZUQRuGyjS+lMB4JDZiykYfvfzMtQ2LZ+aO90rLxYFh4uBpbu+mmA0WDF/HU3GbE0nyY9beo0RAh0/u2Ak2kkfDSntRPVTl5zNBrT9hEtH9oSlN7tok9SMhWEJlsoIRhGinJwsDnDbXcIqkIj/oqtXlSJc6gA7CYf6AJRrVjP1Wtk80GMrMfYNvQw9bich5fs4biddf0xtR13YFV80rCPb+HtTT4KYa7Rzo5qR/cNHsMP/3v5BT2UszpaSIokPoW8ta1RWcQNXuH3OHjG4GMjg88w6xtyudIKrTyP0BTRfIJ2S2EtsWGHU2Gmr/MUY0a7abbtG+LVdSCRTgDoNeiY4C7lkQEOpefoZHWa3+jeGu17812YZHxfCZuhFy33rZgqngWRN1cdxoAbhozChtKmn0Uhdox7jqUw5M/Sj4DWHm0RNB8Ffvf39i/zvlfORzljIiwAKiB26FwpcKKRfx7rrjx4xRLkTLWl0DnJKxOcVz/oXSjglpHJvUSMgbpzXEHHQ7+d+K/WTnoj+dONifxiWBt1hQA8qoPiQceYWGY37oeWvGZI/Qv3ZSO5Mm/yVAuAFyOzJdpW5aC3Kq3gwNVbKNeeV5fWDtvP2K9XcgZFv8OqpNnvLmaL+iWHTPg5wYGvf0iWPr8NVU6OQpSZCOTodwOGfcpQ2YlCnkBgkjkJFLNuM4mi1U9kyTZWAYyZ6zVort0eezJcBoQGBBV2/GkFmwDNa9Q8mT8S7QTf7ZqAtyMnM9rBch7zIscBk6swG/KhgFRtUmDLpY6tpMb6vHHueu4duaUvIXvdjgTe4oE2Ou36VZ1+dC+RswmGCMwFlHqsZiIfU26SDiC3G9wH0iIg6th3LrDJYYD57l5Ps0pVjS7RAYYzu1lA2d2wGEFBJ3UEpJp257Wv2I6foeoTYXSX/XM1JUuFv1516qSqwPk4a1E6N6J+d+iWvM7BBcwakMG1XSUT4zhHrBzPPxXCCBeJHTcOoiaqXwaqsBBButSxViysvGZcBbyAxZNtmXCDh33a760XF4tb1f0mb2jW13CMARGOeubM3Z21eoc16tFkoKSD3wlzT2VlxVuUIgBT+wx4GOWgldngn2aXWInOkaEFdwABBLh5egxNBAI2tzirk6ijpRCq+gquTbEhxIwJavCfdYc0lqMevsEiZxqjoZHEf1EoId/rd3TEdclRf5OzLjbSbDICtFI82S5A+wDMXltVmB+Rw1mBZZUhvbUUOC7ARQ0pkE8DfwgPviFo2z8/i++3Mb02D57V1Mz1k6PB56QzlEOTJrmaBXSQs7U8Aiuln6CA+McwggWKBgkqhkiG9w0BBwGgggV7BIIFdzCCBXMwggVvBgsqhkiG9w0BDAoBAqCCBTcwggUzMF0GCSqGSIb3DQEFDTBQMC8GCSqGSIb3DQEFDDAiBBCl+G6epsuiNjP2afUFOwazAgIIADAKBggqhkiG9w0CCTAdBglghkgBZQMEASoEEG2FLRo+Ud+dbzCVbrer71YEggTQ81fiT0+gLnYWZpNq0MV/kPma4P+sws4wRd5CVG5rCMwmmr3JUCVk66uYLZTBXqHJ0qy3CPE2K1siImQJNS3DMD1q9WVCLPFEPLbO1ycsV73AOMc2UNJMkY7AgGCMpK+u/afMewsnAk/fmwjTw5qOm21TeesahwVvIMb3pQrkFu8FSIWK9IPRX7VCiYSa/KajiFKi0/lWEk9/LJEfikqGOB3FWYQkrV4jhhh+SNMm5LATgNgZ3FyhleruJZup0PN25W2IrpjcEBr9gHVU6gsCyB4PTTrVfopLq7goDWnOQeeAa4Y98QN6nT0EyqkfKU678/JeLz0gW8zijgdqzLwwucLg6cGE379d/2igE7/SJO8qa/JAjD3RDe88N97ysKW7vOOvIH6DnmkgQc8Cq/KKOyVlrDNx65YEft1oqVE3L5IfnmHT5ycbzyMJpdB6uL6OT9KqVLB2bHWDH47XfI8I8z56mzmKSXrWGm93beYV8u908Rokj82LHGEf9th6ttBZykWZgS+hQjc3jIU8xpa2/7mpPVFBCTiphBtp3+fCEVKmnubiiwe28Lw+xEvX8oAEGXhi5fNIGrAXvMk/rgpoh44wQwET6WnyiO8Ad8hOxvPtwgGD0m0FNFlv+yIGzY1PZeevquLKEwtvllo/A3g0OUbeGC2qC5s8VGkv11FRQPdUnOV2oXvosAWqxh6SnVrG8xbxc5L2xjJuUH8b70ne4iXzcfXo5FubtLuuJ6WNFWO9UasmvKaMqFZDlMK8FMcNTq6X0m8ilRZf056C3FDQAMxIa9mKyWebm3+4+LfxjgWo1dxvXR5HnMpzCbcoz/TIbSiUzSTaihxpzMi7Cvkc/JqPTTSkqjR+jLw7tOZucP8VtpQmQvqg2fd1hBgqam37qVC45D2765/V5v74+gtn5nc6HrGOEwpLlqcy5kojrjhQkNUkS7x5vg1KOFP/9uoC67qRFaGH5EM0XAdTVAyt1gn+StXVCXsNKvX93BPaNwL7we/zYZPpERFHaVD9R1Fw2Bz0+RzcdNQqP9yiq3mmmGNZHS0KSAKP3cmA3pwt9gPjpt/L1VNFgkVti2/YIDF37c3yuU9ZBI6kA7LhkcH5j0APr1ppS+Zxw6UKhsZDSGySqPyz3C2k4wy+R2+8mO1dN9haRW0smWWnziHWh8OFGhG+ghvc2HiX1tg2dTrByIFr9wixs4Kn9wDg0Qc1mS0+2+KacO8todl3jVYsLhcSTt5d2b/ZHuyAx4UPFtWPPF1vFdRMnf1jq83q/OEcTSfqkiEpEzs6NXpDEy1E0neq+LVHXi37IHzTGjjIvBnE2KZUoUdiFYitfDoUQpdhSpWKZTsmpqVXi/b7TZ20scvt5Qb6nfEWNds7hyGhnzAGQIV64xaDhKDB1p3QpDYxsJHvAGC1Yj9CY5w+sYpOjsfUo0qKeaFmu0fWX44s388GjZbid92/UvIxN9Lt/jri2xq+XPjJR194hc2ITUDrZvaaqeZ4odH4HXUC7FMqL6NVeX6MIv4g2QQkrt9DO35LokztOQCeuaA4rOZiM7mR3JJZIXf2jFNwElU0bvUJY2eYcQwTSQPXBzMz0AvhXxJiOFx3IQHHl55j4KMpab/NNbHChDUWJ7ptLX0/x8R3scJjCqwxJTAjBgkqhkiG9w0BCRUxFgQU6W6dFe0wnwEJqyaK7H5cUfUvxzQwRzAvMAsGCWCGSAFlAwQCAQQghQoDpo4gS46c+xoCeAykL69ZRT3zYrgNkgvL6s2UIVsEEHkNJl2cMvSFmZ8gi+cS/vQCAggA",
|
||||||
|
"key_pair_p12_password": "D173A3D9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mitm": {
|
||||||
|
"enabled": true,
|
||||||
|
"http2_enabled": true
|
||||||
|
},
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"action": "sniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "logical",
|
||||||
|
"mode": "or",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"network": "udp",
|
||||||
|
"port": 53
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"protocol": "dns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"action": "hijack-dns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip_is_private": true,
|
||||||
|
"outbound": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "resolve"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "goreleaser.com",
|
||||||
|
"action": "route-options",
|
||||||
|
"mitm": {
|
||||||
|
"enabled": true,
|
||||||
|
"surge_map_local": [
|
||||||
|
"^https://goreleaser\\.com/static/latest-pro data-type=text data=\"(update check disabled)\""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "api.gumroad.com",
|
||||||
|
"action": "route-options",
|
||||||
|
"mitm": {
|
||||||
|
"enabled": true,
|
||||||
|
"surge_map_local": [
|
||||||
|
"^https://api\\.gumroad\\.com/v2/licenses/verify data-type=file data=.github/goreleaser/response.json header=\"Content-Type:application/json\""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
11
.github/goreleaser/configure.sh
vendored
Executable file
11
.github/goreleaser/configure.sh
vendored
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
release/local/install_minimal.sh
|
||||||
|
sudo cp .github/goreleaser/config.json /usr/local/etc/sing-box/config.json
|
||||||
|
sudo mkdir -p /var/lib/sing-box/.github/goreleaser
|
||||||
|
sudo cp .github/goreleaser/response.json /var/lib/sing-box/.github/goreleaser/response.json
|
||||||
|
go run -v ./cmd/sing-box tools install-ca .github/goreleaser/ca.crt
|
||||||
|
sudo systemctl start sing-box
|
||||||
|
sleep 5
|
||||||
12
.github/goreleaser/response.json
vendored
Normal file
12
.github/goreleaser/response.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"purchase": {
|
||||||
|
"license_key": "fake-key",
|
||||||
|
"subscription_id": "fake-id",
|
||||||
|
"product_id": "7ev6hHL7RZc753daE5bRNw==",
|
||||||
|
"product_permalink": "https:\/\/beckersoft.gumroad.com\/l\/goreleaser",
|
||||||
|
"seller_id": "A2wDalJj66fJdFU_jwy_oA==",
|
||||||
|
"short_product_id": "CadfZ",
|
||||||
|
"permalink": "goreleaser"
|
||||||
|
}
|
||||||
|
}
|
||||||
2
.github/renovate.json
vendored
2
.github/renovate.json
vendored
@@ -6,7 +6,7 @@
|
|||||||
":disableRateLimiting"
|
":disableRateLimiting"
|
||||||
],
|
],
|
||||||
"baseBranches": [
|
"baseBranches": [
|
||||||
"unstable"
|
"dev-next"
|
||||||
],
|
],
|
||||||
"golang": {
|
"golang": {
|
||||||
"enabled": false
|
"enabled": false
|
||||||
|
|||||||
45
.github/setup_go_for_macos1013.sh
vendored
45
.github/setup_go_for_macos1013.sh
vendored
@@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION="1.25.9"
|
|
||||||
PATCH_COMMITS=(
|
|
||||||
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
|
||||||
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
|
||||||
)
|
|
||||||
CURL_ARGS=(
|
|
||||||
-fL
|
|
||||||
--silent
|
|
||||||
--show-error
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
||||||
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$HOME/go"
|
|
||||||
cd "$HOME/go"
|
|
||||||
wget "https://dl.google.com/go/go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
tar -xzf "go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
#cp -a go go_bootstrap
|
|
||||||
mv go go_osx
|
|
||||||
cd go_osx
|
|
||||||
|
|
||||||
# these patch URLs only work on golang1.25.x
|
|
||||||
# that means after golang1.26 release it must be changed
|
|
||||||
# see: https://github.com/SagerNet/go/commits/release-branch.go1.25/
|
|
||||||
# revert:
|
|
||||||
# 33d3f603c1: "cmd/link/internal/ld: use 12.0.0 OS/SDK versions for macOS linking"
|
|
||||||
# 937368f84e: "crypto/x509: change how we retrieve chains on darwin"
|
|
||||||
|
|
||||||
for patch_commit in "${PATCH_COMMITS[@]}"; do
|
|
||||||
curl "${CURL_ARGS[@]}" "https://github.com/SagerNet/go/commit/${patch_commit}.diff" | patch --verbose -p 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Rebuild is not needed: we build with CGO_ENABLED=1, so Apple's external
|
|
||||||
# linker handles LC_BUILD_VERSION via MACOSX_DEPLOYMENT_TARGET, and the
|
|
||||||
# stdlib (crypto/x509) is compiled from patched src automatically.
|
|
||||||
#cd src
|
|
||||||
#GOROOT_BOOTSTRAP="$HOME/go/go_bootstrap" ./make.bash
|
|
||||||
#cd ../..
|
|
||||||
#rm -rf go_bootstrap "go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
46
.github/setup_go_for_windows7.sh
vendored
46
.github/setup_go_for_windows7.sh
vendored
@@ -1,46 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION="1.25.9"
|
|
||||||
PATCH_COMMITS=(
|
|
||||||
"466f6c7a29bc098b0d4c987b803c779222894a11"
|
|
||||||
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
|
|
||||||
"a90777dcf692dd2168577853ba743b4338721b06"
|
|
||||||
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
|
|
||||||
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
|
|
||||||
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
|
|
||||||
)
|
|
||||||
CURL_ARGS=(
|
|
||||||
-fL
|
|
||||||
--silent
|
|
||||||
--show-error
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
||||||
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$HOME/go"
|
|
||||||
cd "$HOME/go"
|
|
||||||
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
|
||||||
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
|
||||||
mv go go_win7
|
|
||||||
cd go_win7
|
|
||||||
|
|
||||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
|
||||||
# these patch URLs only work on golang1.25.x
|
|
||||||
# that means after golang1.26 release it must be changed
|
|
||||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
|
||||||
# revert:
|
|
||||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
|
||||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
|
||||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
|
||||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
|
||||||
# fixes:
|
|
||||||
# bed309eff415bcb3c77dd4bc3277b682b89a388d: "Fix os.RemoveAll not working on Windows7"
|
|
||||||
# 34b899c2fb39b092db4fa67c4417e41dc046be4b: "Revert \"os: remove 5ms sleep on Windows in (*Process).Wait\""
|
|
||||||
|
|
||||||
for patch_commit in "${PATCH_COMMITS[@]}"; do
|
|
||||||
curl "${CURL_ARGS[@]}" "https://github.com/MetaCubeX/go/commit/${patch_commit}.diff" | patch --verbose -p 1
|
|
||||||
done
|
|
||||||
22
.github/setup_legacy_go.sh
vendored
Executable file
22
.github/setup_legacy_go.sh
vendored
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
VERSION="1.23.6"
|
||||||
|
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||||
|
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||||
|
mv go $HOME/go/go_legacy
|
||||||
|
cd $HOME/go/go_legacy
|
||||||
|
|
||||||
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
|
# this patch file only works on golang1.23.x
|
||||||
|
# that means after golang1.24 release it must be changed
|
||||||
|
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
||||||
|
# revert:
|
||||||
|
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||||
|
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||||
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
|
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
||||||
13
.github/update_cronet.sh
vendored
13
.github/update_cronet.sh
vendored
@@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR=$(dirname "$0")
|
|
||||||
PROJECTS=$SCRIPT_DIR/../..
|
|
||||||
|
|
||||||
git -C $PROJECTS/cronet-go fetch origin main
|
|
||||||
git -C $PROJECTS/cronet-go fetch origin go
|
|
||||||
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
|
||||||
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
|
||||||
go mod tidy
|
|
||||||
git -C $PROJECTS/cronet-go rev-parse origin/go > "$SCRIPT_DIR/CRONET_GO_VERSION"
|
|
||||||
13
.github/update_cronet_dev.sh
vendored
13
.github/update_cronet_dev.sh
vendored
@@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR=$(dirname "$0")
|
|
||||||
PROJECTS=$SCRIPT_DIR/../..
|
|
||||||
|
|
||||||
git -C $PROJECTS/cronet-go fetch origin dev
|
|
||||||
git -C $PROJECTS/cronet-go fetch origin go_dev
|
|
||||||
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev)
|
|
||||||
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev)
|
|
||||||
go mod tidy
|
|
||||||
git -C $PROJECTS/cronet-go rev-parse origin/dev > "$SCRIPT_DIR/CRONET_GO_VERSION"
|
|
||||||
755
.github/workflows/build.yml
vendored
755
.github/workflows/build.yml
vendored
@@ -25,9 +25,8 @@ on:
|
|||||||
- publish-android
|
- publish-android
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- stable
|
- main-next
|
||||||
- testing
|
- dev-next
|
||||||
- unstable
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
||||||
@@ -41,22 +40,22 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
echo "version=${{ inputs.version }}"
|
echo "version=${{ inputs.version }}"
|
||||||
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Calculate version
|
- name: Calculate version
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/read_tag --ci --nightly
|
go run -v ./cmd/internal/read_tag --nightly
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
id: outputs
|
id: outputs
|
||||||
run: |-
|
run: |-
|
||||||
@@ -70,578 +69,153 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, variant: purego, naive: true }
|
- name: linux_386
|
||||||
- { os: linux, arch: amd64, variant: glibc, naive: true }
|
goos: linux
|
||||||
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, alpine: x86_64, openwrt: "x86_64" }
|
goarch: 386
|
||||||
|
- name: linux_amd64
|
||||||
- { os: linux, arch: arm64, variant: purego, naive: true }
|
goos: linux
|
||||||
- { os: linux, arch: arm64, variant: glibc, naive: true }
|
goarch: amd64
|
||||||
- { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, alpine: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
- name: linux_arm64
|
||||||
|
goos: linux
|
||||||
- { os: linux, arch: "386", go386: sse2 }
|
goarch: arm64
|
||||||
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 }
|
- name: linux_arm
|
||||||
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, alpine: x86, openwrt: "i386_pentium4" }
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
- { os: linux, arch: arm, goarm: "7" }
|
goarm: 6
|
||||||
- { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" }
|
- name: linux_arm_v7
|
||||||
- { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, alpine: armv7, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
- { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc }
|
goarm: 7
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
- name: linux_s390x
|
||||||
- { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el }
|
goos: linux
|
||||||
- { os: linux, arch: riscv64, naive: true, variant: glibc }
|
goarch: s390x
|
||||||
- { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: "riscv64_generic" }
|
- name: linux_riscv64
|
||||||
- { os: linux, arch: loong64, naive: true, variant: glibc }
|
goos: linux
|
||||||
- { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, alpine: loongarch64, openwrt: "loongarch64_generic" }
|
goarch: riscv64
|
||||||
|
- name: linux_mips64le
|
||||||
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
goos: linux
|
||||||
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
goarch: mips64le
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
- name: windows_amd64
|
||||||
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
goos: windows
|
||||||
- { os: linux, arch: mipsle, gomips: hardfloat, openwrt: "mipsel_24kc_24kf" }
|
goarch: amd64
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat }
|
require_legacy_go: true
|
||||||
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
- name: windows_386
|
||||||
- { os: linux, arch: mips64le, gomips: hardfloat }
|
goos: windows
|
||||||
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
goarch: 386
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
require_legacy_go: true
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- name: windows_arm64
|
||||||
- { os: linux, arch: riscv64 }
|
goos: windows
|
||||||
- { os: linux, arch: loong64 }
|
goarch: arm64
|
||||||
|
- name: darwin_arm64
|
||||||
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
goos: darwin
|
||||||
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
goarch: arm64
|
||||||
|
- name: darwin_amd64
|
||||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android23" }
|
goos: darwin
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi23" }
|
goarch: amd64
|
||||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android23" }
|
- name: android_arm64
|
||||||
- { os: android, arch: "386", ndk: "i686-linux-android23" }
|
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:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! matrix.legacy_win7 }}
|
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24
|
||||||
- name: Cache Go for Windows 7
|
- name: Cache legacy Go
|
||||||
if: matrix.legacy_win7
|
if: matrix.require_legacy_go
|
||||||
id: cache-go-for-windows7
|
id: cache-legacy-go
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_win7
|
~/go/go_legacy
|
||||||
key: go_win7_1258
|
key: go_legacy_1236
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup legacy Go
|
||||||
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
env:
|
run: bash .github/setup_legacy_go.sh
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |-
|
|
||||||
.github/setup_go_for_windows7.sh
|
|
||||||
- name: Setup Go for Windows 7
|
|
||||||
if: matrix.legacy_win7
|
|
||||||
run: |-
|
|
||||||
echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV
|
|
||||||
echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV
|
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.goos == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28
|
||||||
local-cache: true
|
local-cache: true
|
||||||
- name: Clone cronet-go
|
- name: Setup Goreleaser
|
||||||
if: matrix.naive
|
uses: goreleaser/goreleaser-action@v6
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
|
||||||
git init ~/cronet-go
|
|
||||||
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
|
||||||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
|
||||||
git -C ~/cronet-go checkout FETCH_HEAD
|
|
||||||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
|
||||||
- name: Regenerate Debian keyring
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
|
||||||
cd ~/cronet-go
|
|
||||||
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
|
||||||
- name: Cache Chromium toolchain
|
|
||||||
if: matrix.naive
|
|
||||||
id: cache-chromium-toolchain
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
with:
|
||||||
path: |
|
distribution: goreleaser-pro
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
version: '~> v2'
|
||||||
~/cronet-go/naiveproxy/src/gn/out/
|
install-only: true
|
||||||
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
- name: Extract signing key
|
||||||
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
run: |-
|
||||||
key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
mkdir -p $HOME/.gnupg
|
||||||
- name: Download Chromium toolchain
|
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||||
if: matrix.naive
|
${{ secrets.GPG_KEY }}
|
||||||
run: |
|
EOF
|
||||||
set -xeuo pipefail
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
cd ~/cronet-go
|
|
||||||
if [[ "${{ matrix.variant }}" == "musl" ]]; then
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
|
||||||
else
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain
|
|
||||||
fi
|
|
||||||
- name: Set Chromium toolchain environment
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd ~/cronet-go
|
|
||||||
if [[ "${{ matrix.variant }}" == "musl" ]]; then
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
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
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
- name: Set build tags
|
- name: Build
|
||||||
run: |
|
if: matrix.goos != 'android'
|
||||||
set -xeuo pipefail
|
run: |-
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
goreleaser release --clean --split
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
|
|
||||||
else
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
|
||||||
if [[ "${{ matrix.variant }}" == "purego" ]]; then
|
|
||||||
TAGS="${TAGS},with_purego"
|
|
||||||
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
|
|
||||||
TAGS="${TAGS},with_musl"
|
|
||||||
fi
|
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (purego)
|
|
||||||
if: matrix.variant == 'purego'
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
GOOS: ${{ matrix.goos }}
|
||||||
GOOS: ${{ matrix.os }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOPATH: ${{ env.HOME }}/go
|
||||||
GO386: ${{ matrix.go386 }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Extract libcronet.so
|
|
||||||
if: matrix.variant == 'purego' && matrix.naive
|
|
||||||
run: |
|
|
||||||
cd ~/cronet-go
|
|
||||||
CGO_ENABLED=0 go run -v ./cmd/build-naive extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist
|
|
||||||
- name: Build (glibc)
|
|
||||||
if: matrix.variant == 'glibc'
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GO386: ${{ matrix.go386 }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build (musl)
|
|
||||||
if: matrix.variant == 'musl'
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GO386: ${{ matrix.go386 }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build (non-variant)
|
|
||||||
if: matrix.os != 'android' && matrix.variant == ''
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
GOOS: ${{ matrix.os }}
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GO386: ${{ matrix.go386 }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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
|
- name: Build Android
|
||||||
if: matrix.os == 'android'
|
if: matrix.goos == 'android'
|
||||||
run: |
|
run: |-
|
||||||
set -xeuo pipefail
|
|
||||||
go install -v ./cmd/internal/build
|
go install -v ./cmd/internal/build
|
||||||
export CC='${{ matrix.ndk }}-clang'
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
|
||||||
export CXX="${CC}++"
|
|
||||||
mkdir -p dist
|
|
||||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
BUILD_GOOS: ${{ matrix.goos }}
|
||||||
BUILD_GOOS: ${{ matrix.os }}
|
BUILD_GOARCH: ${{ matrix.goarch }}
|
||||||
BUILD_GOARCH: ${{ matrix.arch }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set name
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
run: |-
|
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
|
||||||
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
|
||||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
|
||||||
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
|
||||||
fi
|
|
||||||
if [[ "${{ matrix.variant }}" == "glibc" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-glibc"
|
|
||||||
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-musl"
|
|
||||||
fi
|
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
|
||||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Package DEB
|
|
||||||
if: matrix.debian != ''
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y debsigs
|
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t deb \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
|
||||||
--architecture ${{ matrix.debian }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
|
||||||
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
|
||||||
rm -rf $HOME/.gnupg
|
|
||||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
|
||||||
EOF
|
|
||||||
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
|
||||||
- name: Package RPM
|
|
||||||
if: matrix.rpm != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t rpm \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
|
||||||
--architecture ${{ matrix.rpm }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
|
||||||
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
|
||||||
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
|
||||||
EOF
|
|
||||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
|
||||||
EOF
|
|
||||||
rpmsign --addsign dist/*.rpm
|
|
||||||
- name: Package Pacman
|
|
||||||
if: matrix.pacman != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libarchive-tools
|
|
||||||
cp .fpm_pacman .fpm
|
|
||||||
fpm -t pacman \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
|
||||||
--architecture ${{ matrix.pacman }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
- name: Package OpenWrt
|
|
||||||
if: matrix.openwrt != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
cp .fpm_openwrt .fpm
|
|
||||||
fpm -t deb \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/openwrt.deb" \
|
|
||||||
--architecture all \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
for architecture in ${{ matrix.openwrt }}; do
|
|
||||||
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
|
||||||
done
|
|
||||||
rm "dist/openwrt.deb"
|
|
||||||
- name: Install apk-tools
|
|
||||||
if: matrix.openwrt != '' || matrix.alpine != ''
|
|
||||||
run: |-
|
|
||||||
docker run --rm -v /usr/local/bin:/mnt alpine:edge sh -c "apk add --no-cache apk-tools-static && cp /sbin/apk.static /mnt/apk && chmod +x /mnt/apk"
|
|
||||||
- name: Package OpenWrt APK
|
|
||||||
if: matrix.openwrt != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
for architecture in ${{ matrix.openwrt }}; do
|
|
||||||
.github/build_openwrt_apk.sh \
|
|
||||||
"$architecture" \
|
|
||||||
"${{ needs.calculate_version.outputs.version }}" \
|
|
||||||
"dist/sing-box" \
|
|
||||||
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.apk"
|
|
||||||
done
|
|
||||||
- name: Package Alpine APK
|
|
||||||
if: matrix.alpine != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
.github/build_alpine_apk.sh \
|
|
||||||
"${{ matrix.alpine }}" \
|
|
||||||
"${{ needs.calculate_version.outputs.version }}" \
|
|
||||||
"dist/sing-box" \
|
|
||||||
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.alpine }}.apk"
|
|
||||||
- name: Archive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd dist
|
|
||||||
mkdir -p "${DIR_NAME}"
|
|
||||||
cp ../LICENSE "${DIR_NAME}"
|
|
||||||
if [ '${{ matrix.os }}' = 'windows' ]; then
|
|
||||||
cp sing-box "${DIR_NAME}/sing-box.exe"
|
|
||||||
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
|
||||||
else
|
|
||||||
cp sing-box "${DIR_NAME}"
|
|
||||||
if [ -f libcronet.so ]; then
|
|
||||||
cp libcronet.so "${DIR_NAME}"
|
|
||||||
fi
|
|
||||||
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
|
||||||
fi
|
|
||||||
rm -r "${DIR_NAME}"
|
|
||||||
- name: Cleanup
|
|
||||||
run: rm -f dist/sing-box dist/libcronet.so
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}${{ matrix.variant && format('-{0}', matrix.variant) }}
|
name: binary-${{ matrix.name }}
|
||||||
path: "dist"
|
path: 'dist'
|
||||||
build_darwin:
|
|
||||||
name: Build Darwin binaries
|
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
|
||||||
runs-on: macos-latest
|
|
||||||
needs:
|
|
||||||
- calculate_version
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- { arch: amd64 }
|
|
||||||
- { arch: arm64 }
|
|
||||||
- { arch: amd64, legacy_osx: true, legacy_name: "macos-10.13" }
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
if: ${{ ! matrix.legacy_osx }}
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.25.3
|
|
||||||
- name: Cache Go for macOS 10.13
|
|
||||||
if: matrix.legacy_osx
|
|
||||||
id: cache-go-for-macos1013
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/go_osx
|
|
||||||
key: go_osx_1258
|
|
||||||
- name: Setup Go for macOS 10.13
|
|
||||||
if: matrix.legacy_osx && steps.cache-go-for-macos1013.outputs.cache-hit != 'true'
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |-
|
|
||||||
.github/setup_go_for_macos1013.sh
|
|
||||||
- name: Setup Go for macOS 10.13
|
|
||||||
if: matrix.legacy_osx
|
|
||||||
run: |-
|
|
||||||
echo "PATH=$HOME/go/go_osx/bin:$PATH" >> $GITHUB_ENV
|
|
||||||
echo "GOROOT=$HOME/go/go_osx" >> $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: Set build tags
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
if [[ "${{ matrix.legacy_osx }}" != "true" ]]; then
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
|
|
||||||
else
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
GOOS: darwin
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.legacy_osx && '10.13' || '' }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Set name
|
|
||||||
run: |-
|
|
||||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-darwin-${{ matrix.arch }}"
|
|
||||||
if [[ -n "${{ matrix.legacy_name }}" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
|
||||||
fi
|
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Archive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd dist
|
|
||||||
mkdir -p "${DIR_NAME}"
|
|
||||||
cp ../LICENSE "${DIR_NAME}"
|
|
||||||
cp sing-box "${DIR_NAME}"
|
|
||||||
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
|
||||||
rm -r "${DIR_NAME}"
|
|
||||||
- name: Cleanup
|
|
||||||
run: rm dist/sing-box
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
|
||||||
path: "dist"
|
|
||||||
build_windows:
|
|
||||||
name: Build Windows binaries
|
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
|
||||||
runs-on: windows-latest
|
|
||||||
needs:
|
|
||||||
- calculate_version
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- { arch: amd64, naive: true }
|
|
||||||
- { arch: "386" }
|
|
||||||
- { arch: arm64, naive: true }
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.25.4
|
|
||||||
- name: Set tag
|
|
||||||
run: |-
|
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$env:GITHUB_ENV"
|
|
||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
|
||||||
- name: Build
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_WINDOWS
|
|
||||||
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" `
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" `
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
GOOS: windows
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
if: ${{ !matrix.naive }}
|
|
||||||
run: |
|
|
||||||
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_OTHERS
|
|
||||||
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" `
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" `
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
GOOS: windows
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Extract libcronet.dll
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
$CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION
|
|
||||||
$env:CGO_ENABLED = "0"
|
|
||||||
go run -v "github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION" extract-lib --target windows/${{ matrix.arch }} -o dist
|
|
||||||
- name: Archive
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
$DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}"
|
|
||||||
mkdir "dist/$DIR_NAME"
|
|
||||||
Copy-Item LICENSE "dist/$DIR_NAME"
|
|
||||||
Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME"
|
|
||||||
Copy-Item "dist/libcronet.dll" "dist/$DIR_NAME"
|
|
||||||
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
|
|
||||||
Remove-Item -Recurse "dist/$DIR_NAME"
|
|
||||||
- name: Archive
|
|
||||||
if: ${{ !matrix.naive }}
|
|
||||||
run: |
|
|
||||||
$DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}"
|
|
||||||
mkdir "dist/$DIR_NAME"
|
|
||||||
Copy-Item LICENSE "dist/$DIR_NAME"
|
|
||||||
Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME"
|
|
||||||
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
|
|
||||||
Remove-Item -Recurse "dist/$DIR_NAME"
|
|
||||||
- name: Cleanup
|
|
||||||
if: matrix.naive
|
|
||||||
run: Remove-Item dist/sing-box.exe, dist/libcronet.dll
|
|
||||||
- name: Cleanup
|
|
||||||
if: ${{ !matrix.naive }}
|
|
||||||
run: Remove-Item dist/sing-box.exe
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: binary-windows_${{ matrix.arch }}
|
|
||||||
path: "dist"
|
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android') && github.ref != 'refs/heads/oldstable'
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -664,12 +238,12 @@ jobs:
|
|||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'
|
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: github.ref == 'refs/heads/testing'
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -689,49 +263,41 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |-
|
run: |-
|
||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp *.aar clients/android/app/libs
|
cp libbox.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
./gradlew :app:assembleOtherRelease :app:assembleOtherLegacyRelease
|
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
||||||
env:
|
env:
|
||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
- name: Prepare upload
|
- name: Prepare upload
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p dist
|
mkdir -p dist/release
|
||||||
#cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
||||||
cp clients/android/app/build/outputs/apk/other/release/*.apk dist
|
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
|
||||||
cp clients/android/app/build/outputs/apk/otherLegacy/release/*.apk dist
|
|
||||||
VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2)
|
|
||||||
VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2)
|
|
||||||
cat > dist/SFA-version-metadata.json << EOF
|
|
||||||
{
|
|
||||||
"version_code": ${VERSION_CODE},
|
|
||||||
"version_name": "${VERSION_NAME}"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
cat dist/SFA-version-metadata.json
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-android-apks
|
name: binary-android-apks
|
||||||
path: 'dist'
|
path: 'dist'
|
||||||
publish_android:
|
publish_android:
|
||||||
name: Publish Android
|
name: Publish Android
|
||||||
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' && github.ref != 'refs/heads/oldstable'
|
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -754,12 +320,12 @@ jobs:
|
|||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'
|
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: github.ref == 'refs/heads/testing'
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -772,7 +338,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/update_android_version --ci
|
go run -v ./cmd/internal/update_android_version --ci
|
||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp *.aar clients/android/app/libs
|
cp libbox.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
||||||
./gradlew :app:publishPlayReleaseBundle
|
./gradlew :app:publishPlayReleaseBundle
|
||||||
@@ -783,8 +349,7 @@ jobs:
|
|||||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||||
build_apple:
|
build_apple:
|
||||||
name: Build Apple clients
|
name: Build Apple clients
|
||||||
runs-on: macos-26
|
runs-on: macos-15
|
||||||
if: false # github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'
|
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
@@ -822,7 +387,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
@@ -830,7 +395,15 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24
|
||||||
|
- 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
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -838,12 +411,12 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: matrix.if && github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'
|
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: matrix.if && github.ref == 'refs/heads/testing'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -862,19 +435,19 @@ jobs:
|
|||||||
|
|
||||||
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
|
||||||
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
|
||||||
|
|
||||||
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||||
mkdir -p "$PROFILES_PATH"
|
mkdir -p "$PROFILES_PATH"
|
||||||
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
|
||||||
|
|
||||||
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
|
||||||
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
|
||||||
|
|
||||||
xcrun notarytool store-credentials "notarytool-password" \
|
xcrun notarytool store-credentials "notarytool-password" \
|
||||||
--key $ASC_KEY_PATH \
|
--key $ASC_KEY_PATH \
|
||||||
--key-id $ASC_KEY_ID \
|
--key-id $ASC_KEY_ID \
|
||||||
--issuer $ASC_KEY_ISSUER_ID
|
--issuer $ASC_KEY_ISSUER_ID
|
||||||
|
|
||||||
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
|
||||||
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
|
||||||
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
|
||||||
@@ -899,13 +472,10 @@ jobs:
|
|||||||
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
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"
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||||
- name: Update version
|
|
||||||
if: matrix.if && matrix.name != 'iOS'
|
|
||||||
run: |-
|
|
||||||
go run -v ./cmd/internal/update_apple_version --ci
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_apple_version --ci
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
xcodebuild archive \
|
xcodebuild archive \
|
||||||
-scheme "${{ matrix.scheme }}" \
|
-scheme "${{ matrix.scheme }}" \
|
||||||
@@ -929,7 +499,7 @@ jobs:
|
|||||||
-authenticationKeyID $ASC_KEY_ID \
|
-authenticationKeyID $ASC_KEY_ID \
|
||||||
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||||
- name: Publish to TestFlight
|
- name: Publish to TestFlight
|
||||||
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/testing'
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
||||||
- name: Build image
|
- name: Build image
|
||||||
@@ -949,14 +519,14 @@ jobs:
|
|||||||
--app-drop-link 0 0 \
|
--app-drop-link 0 0 \
|
||||||
--skip-jenkins \
|
--skip-jenkins \
|
||||||
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
||||||
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
||||||
cd "${{ matrix.archive }}"
|
cd "${{ matrix.archive }}"
|
||||||
zip -r SFM.dSYMs.zip dSYMs
|
zip -r SFM.dSYMs.zip dSYMs
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p dist
|
mkdir -p dist/release
|
||||||
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
|
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
|
||||||
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
|
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
- name: Upload image
|
- name: Upload image
|
||||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -965,20 +535,24 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
|
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
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
- build
|
- build
|
||||||
- build_darwin
|
|
||||||
- build_windows
|
|
||||||
- build_android
|
- build_android
|
||||||
- build_apple
|
- build_apple
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
- name: Setup Goreleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
distribution: goreleaser-pro
|
||||||
|
version: '~> v2'
|
||||||
|
install-only: true
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
id: cache-ghr
|
id: cache-ghr
|
||||||
@@ -999,21 +573,30 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
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
|
- name: Upload builds
|
||||||
if: ${{ env.PUBLISHED == 'false' }}
|
if: ${{ env.PUBLISHED == 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Replace builds
|
- name: Replace builds
|
||||||
if: ${{ env.PUBLISHED != 'false' }}
|
if: ${{ env.PUBLISHED != 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace -p 5 "v${VERSION}" dist
|
ghr --replace -p 5 "v${VERSION}" dist/release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
208
.github/workflows/docker.yml
vendored
208
.github/workflows/docker.yml
vendored
@@ -1,10 +1,6 @@
|
|||||||
name: Publish Docker Images
|
name: Publish Docker Images
|
||||||
|
|
||||||
on:
|
on:
|
||||||
#push:
|
|
||||||
# branches:
|
|
||||||
# - stable
|
|
||||||
# - testing
|
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -17,25 +13,20 @@ env:
|
|||||||
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_binary:
|
build:
|
||||||
name: Build binary
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
platform:
|
||||||
# Naive-enabled builds (musl)
|
- linux/amd64
|
||||||
- { arch: amd64, naive: true, docker_platform: "linux/amd64" }
|
- linux/arm/v6
|
||||||
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
|
- linux/arm/v7
|
||||||
- { arch: "386", naive: true, docker_platform: "linux/386" }
|
- linux/arm64
|
||||||
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
|
- linux/386
|
||||||
- { arch: mipsle, gomips: softfloat, naive: true, docker_platform: "linux/mipsle" }
|
- linux/ppc64le
|
||||||
- { arch: riscv64, naive: true, docker_platform: "linux/riscv64" }
|
- linux/riscv64
|
||||||
- { arch: loong64, naive: true, docker_platform: "linux/loong64" }
|
- linux/s390x
|
||||||
# Non-naive builds
|
|
||||||
- { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" }
|
|
||||||
- { arch: ppc64le, docker_platform: "linux/ppc64le" }
|
|
||||||
- { arch: s390x, docker_platform: "linux/s390x" }
|
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -48,146 +39,7 @@ jobs:
|
|||||||
echo "ref=$ref"
|
echo "ref=$ref"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.25.9
|
|
||||||
- name: Clone cronet-go
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
|
||||||
git init ~/cronet-go
|
|
||||||
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
|
||||||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
|
||||||
git -C ~/cronet-go checkout FETCH_HEAD
|
|
||||||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
|
||||||
- name: Regenerate Debian keyring
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
|
||||||
cd ~/cronet-go
|
|
||||||
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
|
||||||
- name: Cache Chromium toolchain
|
|
||||||
if: matrix.naive
|
|
||||||
id: cache-chromium-toolchain
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
|
||||||
~/cronet-go/naiveproxy/src/gn/out/
|
|
||||||
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
|
||||||
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
|
||||||
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
|
||||||
- name: Download Chromium toolchain
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd ~/cronet-go
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
|
||||||
- name: Set version
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
VERSION=$(go run ./cmd/internal/read_tag)
|
|
||||||
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Set Chromium toolchain environment
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd ~/cronet-go
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
|
||||||
- name: Set build tags
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
|
||||||
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
|
|
||||||
else
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (naive)
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
- name: Build (non-naive)
|
|
||||||
if: ${{ ! matrix.naive }}
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
- name: Prepare artifact
|
|
||||||
run: |
|
|
||||||
platform=${{ matrix.docker_platform }}
|
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
|
||||||
# Rename binary to include arch info for Dockerfile.binary
|
|
||||||
BINARY_NAME="sing-box-${{ matrix.arch }}"
|
|
||||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
|
||||||
BINARY_NAME="${BINARY_NAME}v${{ matrix.goarm }}"
|
|
||||||
fi
|
|
||||||
mv sing-box "${BINARY_NAME}"
|
|
||||||
echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV
|
|
||||||
- name: Upload binary
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: binary-${{ env.PLATFORM_PAIR }}
|
|
||||||
path: ${{ env.BINARY_NAME }}
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
build_docker:
|
|
||||||
name: Build Docker image
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- build_binary
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- { platform: "linux/amd64" }
|
|
||||||
- { platform: "linux/arm/v6" }
|
|
||||||
- { platform: "linux/arm/v7" }
|
|
||||||
- { platform: "linux/arm64" }
|
|
||||||
- { platform: "linux/386" }
|
|
||||||
# mipsle: no base Docker image available for this platform
|
|
||||||
- { platform: "linux/ppc64le" }
|
|
||||||
- { platform: "linux/riscv64" }
|
|
||||||
- { platform: "linux/s390x" }
|
|
||||||
- { platform: "linux/loong64", base_image: "ghcr.io/loong64/alpine:edge" }
|
|
||||||
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
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -195,16 +47,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
platform=${{ matrix.platform }}
|
platform=${{ matrix.platform }}
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
- name: Download binary
|
|
||||||
uses: actions/download-artifact@v5
|
|
||||||
with:
|
|
||||||
name: binary-${{ env.PLATFORM_PAIR }}
|
|
||||||
path: .
|
|
||||||
- name: Prepare binary
|
|
||||||
run: |
|
|
||||||
# Find and make the binary executable
|
|
||||||
chmod +x sing-box-*
|
|
||||||
ls -la sing-box-*
|
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
@@ -226,9 +68,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile.binary
|
|
||||||
build-args: |
|
build-args: |
|
||||||
BASE_IMAGE=${{ matrix.base_image || 'alpine' }}
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
@@ -244,10 +85,9 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
merge:
|
merge:
|
||||||
if: github.event_name != 'push'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- build_docker
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -259,15 +99,15 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "ref=$ref"
|
echo "ref=$ref"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
if [[ $ref == *"-"* ]]; then
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
latest=latest-beta
|
||||||
with:
|
else
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
latest=latest
|
||||||
fetch-depth: 0
|
fi
|
||||||
- name: Detect track
|
echo "latest=$latest"
|
||||||
run: bash .github/detect_track.sh
|
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
@@ -281,15 +121,13 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create manifest list and push
|
- name: Create manifest list and push
|
||||||
if: github.event_name != 'push'
|
|
||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
-t "${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}" \
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
|
||||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
if: github.event_name != 'push'
|
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||||
|
|||||||
23
.github/workflows/lint.yml
vendored
23
.github/workflows/lint.yml
vendored
@@ -3,20 +3,18 @@ name: Lint
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- oldstable
|
- stable-next
|
||||||
- stable
|
- main-next
|
||||||
- testing
|
- dev-next
|
||||||
- unstable
|
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- '!.github/workflows/lint.yml'
|
- '!.github/workflows/lint.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- oldstable
|
- stable-next
|
||||||
- stable
|
- main-next
|
||||||
- testing
|
- dev-next
|
||||||
- unstable
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -24,17 +22,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25
|
go-version: ^1.24
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v8
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
verify: false
|
|
||||||
241
.github/workflows/linux.yml
vendored
241
.github/workflows/linux.yml
vendored
@@ -1,243 +1,38 @@
|
|||||||
name: Build Linux Packages
|
name: Release to Linux repository
|
||||||
|
|
||||||
on:
|
on:
|
||||||
#push:
|
|
||||||
# branches:
|
|
||||||
# - stable
|
|
||||||
# - testing
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: "Version name"
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
calculate_version:
|
|
||||||
name: Calculate version
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.outputs.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.25.9
|
|
||||||
- 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 --ci --nightly
|
|
||||||
- name: Set outputs
|
|
||||||
id: outputs
|
|
||||||
run: |-
|
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
||||||
build:
|
build:
|
||||||
name: Build binary
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
|
||||||
- calculate_version
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
# Naive-enabled builds (musl)
|
|
||||||
- { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
|
||||||
- { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
|
||||||
- { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 }
|
|
||||||
- { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, debian: mipsel, rpm: mipsel }
|
|
||||||
- { os: linux, arch: riscv64, naive: true, debian: riscv64, rpm: riscv64 }
|
|
||||||
- { os: linux, arch: loong64, naive: true, debian: loongarch64, rpm: loongarch64 }
|
|
||||||
# Non-naive builds (unsupported architectures)
|
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
|
||||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24
|
||||||
- name: Clone cronet-go
|
- name: Extract signing key
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
|
||||||
git init ~/cronet-go
|
|
||||||
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
|
||||||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
|
||||||
git -C ~/cronet-go checkout FETCH_HEAD
|
|
||||||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
|
||||||
- name: Regenerate Debian keyring
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
|
||||||
cd ~/cronet-go
|
|
||||||
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
|
||||||
- name: Cache Chromium toolchain
|
|
||||||
if: matrix.naive
|
|
||||||
id: cache-chromium-toolchain
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
|
||||||
~/cronet-go/naiveproxy/src/gn/out/
|
|
||||||
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
|
||||||
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
|
||||||
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
|
||||||
- name: Download Chromium toolchain
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd ~/cronet-go
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
|
||||||
- name: Set Chromium toolchain environment
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd ~/cronet-go
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
|
||||||
- name: Set tag
|
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
mkdir -p $HOME/.gnupg
|
||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||||
- name: Set build tags
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
|
||||||
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
|
|
||||||
else
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (naive)
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build (non-naive)
|
|
||||||
if: ${{ ! matrix.naive }}
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
GOOS: ${{ matrix.os }}
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Set mtime
|
|
||||||
run: |-
|
|
||||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
|
||||||
- name: Detect track
|
|
||||||
run: bash .github/detect_track.sh
|
|
||||||
- name: Set version
|
|
||||||
run: |-
|
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
|
||||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Package DEB
|
|
||||||
if: matrix.debian != ''
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
sudo apt-get install -y debsigs
|
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t deb \
|
|
||||||
--name "${NAME}" \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
|
||||||
--architecture ${{ matrix.debian }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
|
||||||
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
|
||||||
rm -rf $HOME/.gnupg
|
|
||||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
${{ secrets.GPG_KEY }}
|
||||||
EOF
|
EOF
|
||||||
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
- name: Package RPM
|
- name: Publish release
|
||||||
if: matrix.rpm != ''
|
uses: goreleaser/goreleaser-action@v6
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t rpm \
|
|
||||||
--name "${NAME}" \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
|
||||||
--architecture ${{ matrix.rpm }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
|
||||||
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
|
||||||
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
|
||||||
EOF
|
|
||||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
|
||||||
EOF
|
|
||||||
rpmsign --addsign dist/*.rpm
|
|
||||||
- name: Cleanup
|
|
||||||
run: rm dist/sing-box
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
distribution: goreleaser-pro
|
||||||
path: "dist"
|
version: '~> v2'
|
||||||
upload:
|
args: release -f .goreleaser.fury.yaml --clean
|
||||||
name: Upload builds
|
env:
|
||||||
runs-on: ubuntu-latest
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
needs:
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
- calculate_version
|
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
||||||
- build
|
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||||
steps:
|
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- 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@v5
|
|
||||||
with:
|
|
||||||
path: dist
|
|
||||||
merge-multiple: true
|
|
||||||
- name: Publish packages
|
|
||||||
if: github.event_name != 'push'
|
|
||||||
run: |-
|
|
||||||
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
/.idea/
|
/.idea/
|
||||||
/vendor/
|
/vendor/
|
||||||
/*.json
|
/*.json
|
||||||
|
/*.js
|
||||||
/*.srs
|
/*.srs
|
||||||
/*.db
|
/*.db
|
||||||
/site/
|
/site/
|
||||||
@@ -12,12 +13,7 @@
|
|||||||
/*.jar
|
/*.jar
|
||||||
/*.aar
|
/*.aar
|
||||||
/*.xcframework/
|
/*.xcframework/
|
||||||
/experimental/libbox/*.aar
|
|
||||||
/experimental/libbox/*.xcframework/
|
|
||||||
/experimental/libbox/*.nupkg
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/config.d/
|
/config.d/
|
||||||
/venv/
|
/venv/
|
||||||
CLAUDE.md
|
|
||||||
AGENTS.md
|
|
||||||
/.claude/
|
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -0,0 +1,6 @@
|
|||||||
|
[submodule "clients/apple"]
|
||||||
|
path = clients/apple
|
||||||
|
url = https://github.com/SagerNet/sing-box-for-apple.git
|
||||||
|
[submodule "clients/android"]
|
||||||
|
path = clients/android
|
||||||
|
url = https://github.com/SagerNet/sing-box-for-android.git
|
||||||
|
|||||||
@@ -1,64 +1,38 @@
|
|||||||
version: "2"
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- gofumpt
|
||||||
|
- govet
|
||||||
|
- gci
|
||||||
|
- staticcheck
|
||||||
|
- paralleltest
|
||||||
|
- ineffassign
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
custom-order: true
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- prefix(github.com/sagernet/)
|
||||||
|
- default
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
- -SA1003
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.25"
|
go: "1.24"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_script
|
||||||
- with_ccm
|
|
||||||
- with_ocm
|
issues:
|
||||||
- badlinkname
|
exclude-dirs:
|
||||||
- tfogo_checklinkname0
|
- transport/simple-obfs
|
||||||
linters:
|
|
||||||
default: none
|
|
||||||
enable:
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- paralleltest
|
|
||||||
- staticcheck
|
|
||||||
settings:
|
|
||||||
staticcheck:
|
|
||||||
checks:
|
|
||||||
- all
|
|
||||||
- -S1000
|
|
||||||
- -S1008
|
|
||||||
- -S1017
|
|
||||||
- -ST1003
|
|
||||||
- -QF1001
|
|
||||||
- -QF1003
|
|
||||||
- -QF1008
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
presets:
|
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
|
||||||
- std-error-handling
|
|
||||||
paths:
|
|
||||||
- transport/simple-obfs
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- gci
|
|
||||||
- gofumpt
|
|
||||||
settings:
|
|
||||||
gci:
|
|
||||||
sections:
|
|
||||||
- standard
|
|
||||||
- prefix(github.com/sagernet/)
|
|
||||||
- default
|
|
||||||
custom-order: true
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
paths:
|
|
||||||
- transport/simple-obfs
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
|
|||||||
98
.goreleaser.fury.yaml
Normal file
98
.goreleaser.fury.yaml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
project_name: sing-box
|
||||||
|
builds:
|
||||||
|
- id: main
|
||||||
|
main: ./cmd/sing-box
|
||||||
|
flags:
|
||||||
|
- -v
|
||||||
|
- -trimpath
|
||||||
|
ldflags:
|
||||||
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
|
- -s
|
||||||
|
- -buildid=
|
||||||
|
tags:
|
||||||
|
- with_gvisor
|
||||||
|
- with_quic
|
||||||
|
- with_dhcp
|
||||||
|
- with_wireguard
|
||||||
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
- with_tailscale
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
targets:
|
||||||
|
- linux_386
|
||||||
|
- linux_amd64_v1
|
||||||
|
- linux_arm64
|
||||||
|
- linux_arm_7
|
||||||
|
- linux_s390x
|
||||||
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||||
|
nfpms:
|
||||||
|
- &template
|
||||||
|
id: package
|
||||||
|
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 }}'
|
||||||
|
builds:
|
||||||
|
- main
|
||||||
|
homepage: https://sing-box.sagernet.org/
|
||||||
|
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||||
|
description: The universal proxy platform.
|
||||||
|
license: GPLv3 or later
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
priority: extra
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: config
|
||||||
|
|
||||||
|
- src: release/config/sing-box.service
|
||||||
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
|
- src: release/config/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
|
||||||
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
deb:
|
||||||
|
signature:
|
||||||
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
|
fields:
|
||||||
|
Bugs: https://github.com/SagerNet/sing-box/issues
|
||||||
|
rpm:
|
||||||
|
signature:
|
||||||
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
|
conflicts:
|
||||||
|
- sing-box-beta
|
||||||
|
- id: package_beta
|
||||||
|
<<: *template
|
||||||
|
package_name: sing-box-beta
|
||||||
|
file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
conflicts:
|
||||||
|
- sing-box
|
||||||
|
release:
|
||||||
|
disable: true
|
||||||
|
furies:
|
||||||
|
- account: sagernet
|
||||||
|
ids:
|
||||||
|
- package
|
||||||
|
disable: "{{ not (not .Prerelease) }}"
|
||||||
|
- account: sagernet
|
||||||
|
ids:
|
||||||
|
- package_beta
|
||||||
|
disable: "{{ not .Prerelease }}"
|
||||||
154
.goreleaser.yaml
154
.goreleaser.yaml
@@ -17,11 +17,11 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_script
|
||||||
- with_mtproxy
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOTOOLCHAIN=local
|
- GOTOOLCHAIN=local
|
||||||
@@ -33,56 +33,6 @@ builds:
|
|||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
- linux_mips
|
|
||||||
- linux_mips_softfloat
|
|
||||||
- linux_mipsle
|
|
||||||
- linux_mipsle_softfloat
|
|
||||||
- linux_mips64
|
|
||||||
- linux_mips64le
|
|
||||||
- windows_amd64_v1
|
|
||||||
- windows_386
|
|
||||||
- windows_arm64
|
|
||||||
- darwin_amd64_v1
|
|
||||||
- darwin_arm64
|
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
|
||||||
- id: manager
|
|
||||||
main: ./cmd/sing-box
|
|
||||||
flags:
|
|
||||||
- -v
|
|
||||||
- -trimpath
|
|
||||||
ldflags:
|
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
|
||||||
- -s
|
|
||||||
- -buildid=
|
|
||||||
tags:
|
|
||||||
- with_gvisor
|
|
||||||
- with_quic
|
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
|
||||||
- with_utls
|
|
||||||
- with_acme
|
|
||||||
- with_clash_api
|
|
||||||
- with_tailscale
|
|
||||||
- with_masque
|
|
||||||
- with_mtproxy
|
|
||||||
- with_manager
|
|
||||||
- with_admin_panel
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
- GOTOOLCHAIN=local
|
|
||||||
targets:
|
|
||||||
- linux_386
|
|
||||||
- linux_amd64_v1
|
|
||||||
- linux_arm64
|
|
||||||
- linux_arm_6
|
|
||||||
- linux_arm_7
|
|
||||||
- linux_s390x
|
|
||||||
- linux_riscv64
|
|
||||||
- linux_mips
|
|
||||||
- linux_mips_softfloat
|
|
||||||
- linux_mipsle
|
|
||||||
- linux_mipsle_softfloat
|
|
||||||
- linux_mips64
|
|
||||||
- linux_mips64le
|
- linux_mips64le
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
@@ -98,13 +48,15 @@ builds:
|
|||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_script
|
||||||
- with_mtproxy
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
|
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
||||||
|
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
|
||||||
targets:
|
targets:
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
@@ -156,25 +108,85 @@ archives:
|
|||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
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 }}'
|
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_with_manager
|
|
||||||
builds:
|
|
||||||
- manager
|
|
||||||
formats:
|
|
||||||
- tar.gz
|
|
||||||
format_overrides:
|
|
||||||
- goos: windows
|
|
||||||
formats:
|
|
||||||
- zip
|
|
||||||
wrap_in_directory: true
|
|
||||||
files:
|
|
||||||
- LICENSE
|
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-with-manager-{{ .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:
|
||||||
- legacy
|
- legacy
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||||
|
nfpms:
|
||||||
|
- id: package
|
||||||
|
package_name: sing-box
|
||||||
|
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:
|
||||||
|
- main
|
||||||
|
homepage: https://sing-box.sagernet.org/
|
||||||
|
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||||
|
description: The universal proxy platform.
|
||||||
|
license: GPLv3 or later
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
- archlinux
|
||||||
|
# - apk
|
||||||
|
# - ipk
|
||||||
|
priority: extra
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: config
|
||||||
|
|
||||||
|
- src: release/config/sing-box.service
|
||||||
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
|
- src: release/config/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
|
||||||
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
deb:
|
||||||
|
signature:
|
||||||
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
|
fields:
|
||||||
|
Bugs: https://github.com/SagerNet/sing-box/issues
|
||||||
|
rpm:
|
||||||
|
signature:
|
||||||
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
|
overrides:
|
||||||
|
apk:
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: config
|
||||||
|
|
||||||
|
- 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'
|
||||||
@@ -186,8 +198,8 @@ signs:
|
|||||||
- artifacts: checksum
|
- artifacts: checksum
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: shtorm-7
|
owner: SagerNet
|
||||||
name: sing-box-extended
|
name: sing-box
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
mode: replace
|
mode: replace
|
||||||
@@ -195,3 +207,5 @@ release:
|
|||||||
- archive
|
- archive
|
||||||
- package
|
- package
|
||||||
skip_upload: true
|
skip_upload: true
|
||||||
|
partial:
|
||||||
|
by: target
|
||||||
24
DONATE.md
24
DONATE.md
@@ -1,24 +0,0 @@
|
|||||||
# Support the project
|
|
||||||
|
|
||||||
If you want to support the project, you can donate to the following addresses.
|
|
||||||
|
|
||||||
### TRX (Tron)
|
|
||||||
```
|
|
||||||
TSWU6VUZ4FcUghYDmbbhK15gRVvhvBgW3F
|
|
||||||
```
|
|
||||||
### TON
|
|
||||||
```
|
|
||||||
UQAyD2UuT5kCP6lZQlhFL0hyNibDXNE4nIo_RSLVSYAtD7N1
|
|
||||||
```
|
|
||||||
### Solana
|
|
||||||
```
|
|
||||||
CJu8ickwRCwNE71uVFjYf1UveyCkRp9Xo44rhPcQpeFL
|
|
||||||
```
|
|
||||||
### Bitcoin
|
|
||||||
```
|
|
||||||
bc1qqx97p8k4dchqkyd47s4vf74hrqdfnmhqvcja7x
|
|
||||||
```
|
|
||||||
### Ethereum
|
|
||||||
```
|
|
||||||
0xAcc5919C22F2B3fAa0ec7E8BaD142da5B375FBF6
|
|
||||||
```
|
|
||||||
17
Dockerfile
17
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||||
LABEL maintainer="shtorm-7"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
ARG TARGETOS TARGETARCH
|
ARG TARGETOS TARGETARCH
|
||||||
@@ -12,15 +12,16 @@ RUN set -ex \
|
|||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
|
&& go build -v -trimpath -tags \
|
||||||
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||||
&& go build -v -trimpath -tags "$TAGS" \
|
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" $LDFLAGS_SHARED -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="shtorm-7"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
|
&& apk upgrade \
|
||||||
|
&& apk add bash tzdata ca-certificates nftables \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
||||||
ENTRYPOINT ["sing-box"]
|
ENTRYPOINT ["sing-box"]
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
ARG BASE_IMAGE=alpine
|
|
||||||
FROM ${BASE_IMAGE}
|
|
||||||
ARG TARGETARCH
|
|
||||||
ARG TARGETVARIANT
|
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
|
||||||
RUN set -ex \
|
|
||||||
&& if command -v apk > /dev/null; then \
|
|
||||||
apk add --no-cache --upgrade bash tzdata ca-certificates nftables; \
|
|
||||||
else \
|
|
||||||
apt-get update && apt-get install -y --no-install-recommends bash tzdata ca-certificates nftables \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*; \
|
|
||||||
fi
|
|
||||||
COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box
|
|
||||||
ENTRYPOINT ["sing-box"]
|
|
||||||
162
Makefile
162
Makefile
@@ -1,18 +1,18 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= $(shell cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||||
|
TAGS_GO123 = with_tailscale,with_script
|
||||||
|
TAGS ?= $(TAGS_GO120),$(TAGS_GO123)
|
||||||
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls,with_reality_server
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||||
|
|
||||||
LDFLAGS_SHARED = $(shell cat release/LDFLAGS)
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' $(LDFLAGS_SHARED) -s -w -buildid="
|
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
||||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
SING_FFI ?= sing-ffi
|
|
||||||
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
|
|
||||||
|
|
||||||
.PHONY: test release docs build
|
.PHONY: test release docs build
|
||||||
|
|
||||||
@@ -20,9 +20,10 @@ build:
|
|||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
race:
|
ci_build_go120:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build -race $(MAIN_PARAMS) $(MAIN)
|
go build $(PARAMS) $(MAIN) && \
|
||||||
|
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
@@ -30,7 +31,7 @@ ci_build:
|
|||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
|
go run -v --tags $(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)
|
||||||
@@ -40,9 +41,6 @@ fmt:
|
|||||||
@gofmt -s -w .
|
@gofmt -s -w .
|
||||||
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
||||||
|
|
||||||
fmt_docs:
|
|
||||||
go run ./cmd/internal/format_docs
|
|
||||||
|
|
||||||
fmt_install:
|
fmt_install:
|
||||||
go install -v mvdan.cc/gofumpt@latest
|
go install -v mvdan.cc/gofumpt@latest
|
||||||
go install -v github.com/daixiang0/gci@latest
|
go install -v github.com/daixiang0/gci@latest
|
||||||
@@ -55,7 +53,7 @@ lint:
|
|||||||
GOOS=freebsd golangci-lint run ./...
|
GOOS=freebsd golangci-lint run ./...
|
||||||
|
|
||||||
lint_install:
|
lint_install:
|
||||||
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
@go run ./cmd/internal/protogen
|
@go run ./cmd/internal/protogen
|
||||||
@@ -70,10 +68,14 @@ update_certificates:
|
|||||||
go run ./cmd/internal/update_certificates
|
go run ./cmd/internal/update_certificates
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
|
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz \
|
mv dist/*.tar.gz \
|
||||||
dist/*.zip \
|
dist/*.zip \
|
||||||
|
dist/*.deb \
|
||||||
|
dist/*.rpm \
|
||||||
|
dist/*_amd64.pkg.tar.zst \
|
||||||
|
dist/*_arm64.pkg.tar.zst \
|
||||||
dist/release
|
dist/release
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
rm -r dist/release
|
rm -r dist/release
|
||||||
@@ -88,16 +90,16 @@ update_android_version:
|
|||||||
go run ./cmd/internal/update_android_version
|
go run ./cmd/internal/update_android_version
|
||||||
|
|
||||||
build_android:
|
build_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:clean :app:assembleOtherRelease && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./gradlew --stop
|
||||||
|
|
||||||
upload_android:
|
upload_android:
|
||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/other/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/otherLegacy/release/*.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 5 "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
|
release_android: lib_android update_android_version build_android upload_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||||
@@ -108,28 +110,18 @@ build_ios:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFI.xcarchive && \
|
rm -rf build/SFI.xcarchive && \
|
||||||
xcodebuild clean -scheme SFI && \
|
xcodebuild clean -scheme SFI && \
|
||||||
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
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 && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_ios_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
|
||||||
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
|
||||||
|
|
||||||
upload_ios_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
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 -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
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 && \
|
||||||
@@ -138,65 +130,59 @@ upload_macos_app_store:
|
|||||||
release_macos: build_macos upload_macos_app_store
|
release_macos: build_macos upload_macos_app_store
|
||||||
|
|
||||||
build_macos_standalone:
|
build_macos_standalone:
|
||||||
$(MAKE) -C ../sing-box-for-apple archive_macos_standalone
|
cd ../sing-box-for-apple && \
|
||||||
|
rm -rf build/SFM.System.xcarchive && \
|
||||||
|
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
build_macos_dmg:
|
build_macos_dmg:
|
||||||
$(MAKE) -C ../sing-box-for-apple build_macos_dmg
|
rm -rf dist/SFM
|
||||||
|
mkdir -p dist/SFM
|
||||||
build_macos_pkg:
|
cd ../sing-box-for-apple && \
|
||||||
$(MAKE) -C ../sing-box-for-apple build_macos_pkg
|
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"
|
||||||
|
|
||||||
notarize_macos_dmg:
|
notarize_macos_dmg:
|
||||||
$(MAKE) -C ../sing-box-for-apple notarize_macos_dmg
|
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
|
||||||
|
--keychain-profile "notarytool-password" \
|
||||||
notarize_macos_pkg:
|
--no-s3-acceleration
|
||||||
$(MAKE) -C ../sing-box-for-apple notarize_macos_pkg
|
|
||||||
|
|
||||||
upload_macos_dmg:
|
upload_macos_dmg:
|
||||||
mkdir -p dist/SFM
|
cd dist/SFM && \
|
||||||
cp ../sing-box-for-apple/build/SFM-Apple.dmg "dist/SFM/SFM-${VERSION}-Apple.dmg"
|
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||||
cp ../sing-box-for-apple/build/SFM-Intel.dmg "dist/SFM/SFM-${VERSION}-Intel.dmg"
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
||||||
cp ../sing-box-for-apple/build/SFM-Universal.dmg "dist/SFM/SFM-${VERSION}-Universal.dmg"
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.dmg"
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.dmg"
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.dmg"
|
|
||||||
|
|
||||||
upload_macos_pkg:
|
|
||||||
mkdir -p dist/SFM
|
|
||||||
cp ../sing-box-for-apple/build/SFM-Apple.pkg "dist/SFM/SFM-${VERSION}-Apple.pkg"
|
|
||||||
cp ../sing-box-for-apple/build/SFM-Intel.pkg "dist/SFM/SFM-${VERSION}-Intel.pkg"
|
|
||||||
cp ../sing-box-for-apple/build/SFM-Universal.pkg "dist/SFM/SFM-${VERSION}-Universal.pkg"
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg"
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
|
|
||||||
|
|
||||||
upload_macos_dsyms:
|
upload_macos_dsyms:
|
||||||
mkdir -p dist/SFM
|
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
||||||
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
|
zip -r SFM.dSYMs.zip dSYMs && \
|
||||||
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
|
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
|
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_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
|
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 -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
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 && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_tvos_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
|
||||||
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
|
||||||
|
|
||||||
upload_tvos_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
@@ -205,12 +191,12 @@ update_apple_version:
|
|||||||
update_macos_version:
|
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
|
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_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
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:
|
publish_testflight:
|
||||||
go run -v ./cmd/internal/app_store_connect publish_testflight $(filter-out $@,$(MAKECMDGOALS))
|
go run -v ./cmd/internal/app_store_connect publish_testflight
|
||||||
|
|
||||||
prepare_app_store:
|
prepare_app_store:
|
||||||
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
||||||
@@ -233,21 +219,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:
|
lib_apple:
|
||||||
go run ./cmd/internal/build_libbox -target apple
|
go run ./cmd/internal/build_libbox -target apple
|
||||||
|
|
||||||
lib_windows:
|
lib_ios:
|
||||||
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp
|
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
||||||
|
|
||||||
lib_android_new:
|
lib:
|
||||||
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android
|
go run ./cmd/internal/build_libbox -target android
|
||||||
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
lib_apple_new:
|
|
||||||
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple
|
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.5
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.5
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
@@ -256,8 +243,8 @@ publish_docs:
|
|||||||
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||||
|
|
||||||
docs_install:
|
docs_install:
|
||||||
python3 -m venv venv
|
python -m venv venv
|
||||||
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.7.2" mkdocs-static-i18n=="1.2.*"
|
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
@@ -266,7 +253,4 @@ clean:
|
|||||||
update:
|
update:
|
||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
|
||||||
%:
|
|
||||||
@:
|
|
||||||
79
README.md
79
README.md
@@ -1,79 +1,12 @@
|
|||||||
# sing-box-extended
|
# sing-box
|
||||||
|
|
||||||
Sing-box with extended features.
|
The universal proxy platform.
|
||||||
|
|
||||||
## 🔥 Features
|
[](https://repology.org/project/sing-box/versions)
|
||||||
|
|
||||||
### Protocols
|
## Documentation
|
||||||
- WARP
|
|
||||||
- MASQUE
|
|
||||||
- MTProxy
|
|
||||||
- Mieru
|
|
||||||
- VPN
|
|
||||||
- Bond
|
|
||||||
- Fallback
|
|
||||||
|
|
||||||
### Limiters
|
https://sing-box.sagernet.org
|
||||||
- Bandwidth Limiter
|
|
||||||
- Connection Limiter
|
|
||||||
|
|
||||||
### Encryption & Obfuscation
|
|
||||||
- Amnezia 2.0
|
|
||||||
- VLESS encryption
|
|
||||||
|
|
||||||
### Transports
|
|
||||||
- mKCP
|
|
||||||
- XHTTP
|
|
||||||
|
|
||||||
### Services
|
|
||||||
- Admin Panel
|
|
||||||
- Manager
|
|
||||||
- Node Manager
|
|
||||||
|
|
||||||
### Miscellaneous
|
|
||||||
- Link parser
|
|
||||||
- SDNS (DNSCrypt)
|
|
||||||
- Extended WireGuard options
|
|
||||||
- Unified Delay
|
|
||||||
|
|
||||||
## 📚 Examples
|
|
||||||
|
|
||||||
Configuration examples are available here:
|
|
||||||
|
|
||||||
https://github.com/shtorm-7/sing-box-extended/tree/extended/examples
|
|
||||||
|
|
||||||
## Support the Project
|
|
||||||
|
|
||||||
If you want to support the project, you can donate to the following addresses.
|
|
||||||
|
|
||||||
#### Tribute
|
|
||||||
|
|
||||||
**[RUB Donate](https://web.tribute.tg/d/JxY)**
|
|
||||||
|
|
||||||
**[EUR Donate](https://web.tribute.tg/d/JxZ)**
|
|
||||||
|
|
||||||
**[USD Donate](https://web.tribute.tg/d/Jy1)**
|
|
||||||
|
|
||||||
#### TRX (Tron)
|
|
||||||
```
|
|
||||||
TSWU6VUZ4FcUghYDmbbhK15gRVvhvBgW3F
|
|
||||||
```
|
|
||||||
#### TON
|
|
||||||
```
|
|
||||||
UQAyD2UuT5kCP6lZQlhFL0hyNibDXNE4nIo_RSLVSYAtD7N1
|
|
||||||
```
|
|
||||||
#### Solana
|
|
||||||
```
|
|
||||||
CJu8ickwRCwNE71uVFjYf1UveyCkRp9Xo44rhPcQpeFL
|
|
||||||
```
|
|
||||||
#### Bitcoin
|
|
||||||
```
|
|
||||||
bc1qqx97p8k4dchqkyd47s4vf74hrqdfnmhqvcja7x
|
|
||||||
```
|
|
||||||
#### Ethereum
|
|
||||||
```
|
|
||||||
0xAcc5919C22F2B3fAa0ec7E8BaD142da5B375FBF6
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -95,4 +28,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
In addition, no derivative work may use the name or imply association
|
||||||
with this application without prior consent.
|
with this application without prior consent.
|
||||||
```
|
```
|
||||||
@@ -10,6 +10,9 @@ import (
|
|||||||
type CertificateStore interface {
|
type CertificateStore interface {
|
||||||
LifecycleService
|
LifecycleService
|
||||||
Pool() *x509.CertPool
|
Pool() *x509.CertPool
|
||||||
|
TLSDecryptionEnabled() bool
|
||||||
|
TLSDecryptionCertificate() *x509.Certificate
|
||||||
|
TLSDecryptionPrivateKey() any
|
||||||
}
|
}
|
||||||
|
|
||||||
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
|
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ import (
|
|||||||
|
|
||||||
type ConnectionManager interface {
|
type ConnectionManager interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
Count() int
|
|
||||||
CloseAll()
|
|
||||||
TrackConn(conn net.Conn) net.Conn
|
|
||||||
TrackPacketConn(conn net.PacketConn) net.PacketConn
|
|
||||||
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
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"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -27,34 +25,17 @@ type DNSClient interface {
|
|||||||
Start()
|
Start()
|
||||||
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
||||||
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||||
|
LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool)
|
||||||
|
ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool)
|
||||||
ClearCache()
|
ClearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSQueryOptions struct {
|
type DNSQueryOptions struct {
|
||||||
Transport DNSTransport
|
Transport DNSTransport
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
LookupStrategy C.DomainStrategy
|
DisableCache bool
|
||||||
DisableCache bool
|
RewriteTTL *uint32
|
||||||
RewriteTTL *uint32
|
ClientSubnet netip.Prefix
|
||||||
ClientSubnet netip.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
|
||||||
if options == nil {
|
|
||||||
return &DNSQueryOptions{}, nil
|
|
||||||
}
|
|
||||||
transportManager := service.FromContext[DNSTransportManager](ctx)
|
|
||||||
transport, loaded := transportManager.Transport(options.Server)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("domain resolver not found: " + options.Server)
|
|
||||||
}
|
|
||||||
return &DNSQueryOptions{
|
|
||||||
Transport: transport,
|
|
||||||
Strategy: C.DomainStrategy(options.Strategy),
|
|
||||||
DisableCache: options.DisableCache,
|
|
||||||
RewriteTTL: options.RewriteTTL,
|
|
||||||
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RDRCStore interface {
|
type RDRCStore interface {
|
||||||
@@ -68,9 +49,6 @@ type DNSTransport interface {
|
|||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
// Reset closes the transport's existing connections so later requests use fresh connections.
|
|
||||||
// Exchanges that are currently using those connections may fail.
|
|
||||||
Reset()
|
|
||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Ma
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
@@ -42,18 +43,12 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
m.stage = stage
|
m.stage = stage
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
// started with outbound manager
|
// started with outbound manager
|
||||||
m.access.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
endpoints := m.endpoints
|
for _, endpoint := range m.endpoints {
|
||||||
m.access.Unlock()
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err := adapter.LegacyStart(endpoint, stage)
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -71,14 +66,11 @@ func (m *Manager) Close() error {
|
|||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
done := adapter.LogElapsed(m.logger, "close ", name)
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, endpoint.Close(), func(err error) error {
|
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", name)
|
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -127,13 +119,10 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err = adapter.LegacyStart(endpoint, stage)
|
err = adapter.LegacyStart(endpoint, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/observable"
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +14,6 @@ type ClashServer interface {
|
|||||||
ConnectionTracker
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
SetModeUpdateHook(hook *observable.Subscriber[struct{}])
|
|
||||||
HistoryStorage() URLTestHistoryStorage
|
HistoryStorage() URLTestHistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +23,7 @@ type URLTestHistory struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type URLTestHistoryStorage interface {
|
type URLTestHistoryStorage interface {
|
||||||
SetHook(hook *observable.Subscriber[struct{}])
|
SetHook(hook chan<- struct{})
|
||||||
LoadURLTestHistory(tag string) *URLTestHistory
|
LoadURLTestHistory(tag string) *URLTestHistory
|
||||||
DeleteURLTestHistory(tag string)
|
DeleteURLTestHistory(tag string)
|
||||||
StoreURLTestHistory(tag string, history *URLTestHistory)
|
StoreURLTestHistory(tag string, history *URLTestHistory)
|
||||||
@@ -47,9 +44,6 @@ type CacheFile interface {
|
|||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
StoreWARPConfig() bool
|
|
||||||
StoreMASQUEConfig() bool
|
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
@@ -58,12 +52,10 @@ type CacheFile interface {
|
|||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedBinary
|
LoadRuleSet(tag string) *SavedBinary
|
||||||
SaveRuleSet(tag string, set *SavedBinary) error
|
SaveRuleSet(tag string, set *SavedBinary) error
|
||||||
LoadWARPConfig(tag string) *SavedBinary
|
LoadScript(tag string) *SavedBinary
|
||||||
SaveWARPConfig(tag string, set *SavedBinary) error
|
SaveScript(tag string, script *SavedBinary) error
|
||||||
LoadMASQUEConfig(tag string) *SavedBinary
|
SurgePersistentStoreRead(key string) string
|
||||||
SaveMASQUEConfig(tag string, set *SavedBinary) error
|
SurgePersistentStoreWrite(key string, value string) error
|
||||||
LoadSubscription(tag string) *SavedBinary
|
|
||||||
SaveSubscription(tag string, sub *SavedBinary) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedBinary struct {
|
||||||
@@ -78,11 +70,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.Content)))
|
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = buffer.Write(s.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -90,11 +78,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.LastEtag)))
|
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = buffer.WriteString(s.LastEtag)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -108,12 +92,7 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentLength, err := binary.ReadUvarint(reader)
|
err = varbin.Read(reader, binary.BigEndian, &s.Content)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Content = make([]byte, contentLength)
|
|
||||||
_, err = io.ReadFull(reader, s.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -123,16 +102,10 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.LastUpdated = time.Unix(lastUpdated, 0)
|
s.LastUpdated = time.Unix(lastUpdated, 0)
|
||||||
etagLength, err := binary.ReadUvarint(reader)
|
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
etagBytes := make([]byte, etagLength)
|
|
||||||
_, err = io.ReadFull(reader, etagBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.LastEtag = string(etagBytes)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FakeIPStore interface {
|
type FakeIPStore interface {
|
||||||
SimpleLifecycle
|
Service
|
||||||
Contains(address netip.Addr) bool
|
Contains(address netip.Addr) bool
|
||||||
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
||||||
Lookup(address netip.Addr) (string, bool)
|
Lookup(address netip.Addr) (string, bool)
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/process"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
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"
|
||||||
@@ -30,7 +33,6 @@ type UDPInjectableInbound interface {
|
|||||||
type InboundRegistry interface {
|
type InboundRegistry interface {
|
||||||
option.InboundOptionsRegistry
|
option.InboundOptionsRegistry
|
||||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
||||||
UnsafeCreate(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundManager interface {
|
type InboundManager interface {
|
||||||
@@ -48,32 +50,35 @@ type InboundContext struct {
|
|||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
Gateway *netip.Addr
|
|
||||||
User string
|
User string
|
||||||
Outbound string
|
Outbound string
|
||||||
|
|
||||||
// sniffer
|
// sniffer
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SnifferNames []string
|
PacketSniffError error
|
||||||
SniffError error
|
HTTPRequest *http.Request
|
||||||
|
ClientHello *tls.ClientHelloInfo
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
// Deprecated: implement in rule action
|
// Deprecated: implement in rule action
|
||||||
InboundDetour string
|
InboundDetour string
|
||||||
LastInbound string
|
LastInbound string
|
||||||
OriginDestination M.Socksaddr
|
OriginDestination M.Socksaddr
|
||||||
RouteOriginalDestination M.Socksaddr
|
RouteOriginalDestination M.Socksaddr
|
||||||
|
// Deprecated: to be removed
|
||||||
|
//nolint:staticcheck
|
||||||
|
InboundOptions option.InboundOptions
|
||||||
UDPDisableDomainUnmapping bool
|
UDPDisableDomainUnmapping bool
|
||||||
UDPConnect bool
|
UDPConnect bool
|
||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
TLSFragment bool
|
TLSFragment bool
|
||||||
TLSFragmentFallbackDelay time.Duration
|
TLSFragmentFallbackDelay time.Duration
|
||||||
TLSRecordFragment bool
|
MITM *option.MITMRouteOptions
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
@@ -83,7 +88,7 @@ type InboundContext struct {
|
|||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *ConnectionOwner
|
ProcessInfo *process.Info
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
|
||||||
@@ -103,10 +108,6 @@ type InboundContext struct {
|
|||||||
func (c *InboundContext) ResetRuleCache() {
|
func (c *InboundContext) ResetRuleCache() {
|
||||||
c.IPCIDRMatchSource = false
|
c.IPCIDRMatchSource = false
|
||||||
c.IPCIDRAcceptEmpty = false
|
c.IPCIDRAcceptEmpty = false
|
||||||
c.ResetRuleMatchCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InboundContext) ResetRuleMatchCache() {
|
|
||||||
c.SourceAddressMatch = false
|
c.SourceAddressMatch = false
|
||||||
c.SourcePortMatch = false
|
c.SourcePortMatch = false
|
||||||
c.DestinationAddressMatch = false
|
c.DestinationAddressMatch = false
|
||||||
@@ -138,7 +139,8 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|||||||
|
|
||||||
func OverrideContext(ctx context.Context) context.Context {
|
func OverrideContext(ctx context.Context) context.Context {
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
newMetadata := *metadata
|
var newMetadata InboundContext
|
||||||
|
newMetadata = *metadata
|
||||||
return WithContext(ctx, &newMetadata)
|
return WithContext(ctx, &newMetadata)
|
||||||
}
|
}
|
||||||
return ctx
|
return ctx
|
||||||
|
|||||||
@@ -37,20 +37,16 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
inbounds := m.inbounds
|
for _, inbound := range m.inbounds {
|
||||||
m.access.Unlock()
|
|
||||||
for _, inbound := range inbounds {
|
|
||||||
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -68,14 +64,11 @@ func (m *Manager) Close() error {
|
|||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
done := adapter.LogElapsed(m.logger, "close ", name)
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, inbound.Close(), func(err error) error {
|
err = E.Append(err, inbound.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", name)
|
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -127,13 +120,10 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err = adapter.LegacyStart(inbound, stage)
|
err = adapter.LegacyStart(inbound, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,6 @@ func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
|||||||
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
return m.UnsafeCreate(ctx, router, logger, tag, outboundType, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) UnsafeCreate(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
|
||||||
constructor, loaded := m.constructor[outboundType]
|
constructor, loaded := m.constructor[outboundType]
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
|||||||
@@ -1,20 +1,9 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SimpleLifecycle interface {
|
|
||||||
Start() error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type StartStage uint8
|
type StartStage uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -56,30 +45,12 @@ type LifecycleService interface {
|
|||||||
Lifecycle
|
Lifecycle
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServiceName(service any) string {
|
func Start(stage StartStage, services ...Lifecycle) error {
|
||||||
if named, ok := service.(interface {
|
|
||||||
Type() string
|
|
||||||
Tag() string
|
|
||||||
}); ok {
|
|
||||||
tag := named.Tag()
|
|
||||||
if tag != "" {
|
|
||||||
return named.Type() + "[" + tag + "]"
|
|
||||||
}
|
|
||||||
return named.Type()
|
|
||||||
}
|
|
||||||
t := reflect.TypeOf(service)
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
return strings.ToLower(t.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {
|
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
name := getServiceName(service)
|
if service == nil {
|
||||||
done := LogElapsed(logger, stage, " ", name)
|
continue
|
||||||
|
}
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,28 +58,12 @@ func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
|
func StartNamed(stage StartStage, services []LifecycleService) error {
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
done := LogElapsed(logger, stage, " ", service.Name())
|
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage.String(), " ", service.Name())
|
return E.Cause(err, stage.String(), " ", service.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogElapsed(logger log.ContextLogger, description ...any) func() {
|
|
||||||
prefix := F.ToString(description...)
|
|
||||||
startTime := time.Now()
|
|
||||||
timer := time.AfterFunc(time.Second, func() {
|
|
||||||
logger.Trace(prefix, "...")
|
|
||||||
})
|
|
||||||
return func() {
|
|
||||||
if timer.Stop() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Trace(prefix, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lifecycleServiceWrapper struct {
|
type lifecycleServiceWrapper struct {
|
||||||
SimpleLifecycle
|
Service
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
|
func NewLifecycleService(service Service, name string) LifecycleService {
|
||||||
return &lifecycleServiceWrapper{
|
return &lifecycleServiceWrapper{
|
||||||
SimpleLifecycle: service,
|
Service: service,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||||
return LegacyStart(l.SimpleLifecycle, stage)
|
return LegacyStart(l.Service, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Close() error {
|
func (l *lifecycleServiceWrapper) Close() error {
|
||||||
return l.SimpleLifecycle.Close()
|
return l.Service.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
13
adapter/mitm.go
Normal file
13
adapter/mitm.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MITMEngine interface {
|
||||||
|
Lifecycle
|
||||||
|
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@@ -13,7 +10,6 @@ import (
|
|||||||
|
|
||||||
type NetworkManager interface {
|
type NetworkManager interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
Initialize(ruleSets []RuleSet)
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
UpdateInterfaces() error
|
UpdateInterfaces() error
|
||||||
DefaultNetworkInterface() *NetworkInterface
|
DefaultNetworkInterface() *NetworkInterface
|
||||||
@@ -24,14 +20,12 @@ type NetworkManager interface {
|
|||||||
DefaultOptions() NetworkOptions
|
DefaultOptions() NetworkOptions
|
||||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
AutoRedirectOutputMark() uint32
|
AutoRedirectOutputMark() uint32
|
||||||
AutoRedirectOutputMarkFunc() control.Func
|
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
NeedWIFIState() bool
|
|
||||||
WIFIState() WIFIState
|
WIFIState() WIFIState
|
||||||
UpdateWIFIState()
|
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
|
UpdateWIFIState()
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkOptions struct {
|
type NetworkOptions struct {
|
||||||
@@ -54,24 +48,6 @@ type WIFIState struct {
|
|||||||
BSSID string
|
BSSID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NormalizeWIFIBSSID(bssid string) string {
|
|
||||||
bssid = strings.TrimSpace(bssid)
|
|
||||||
if bssid == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
parsed, err := net.ParseMAC(bssid)
|
|
||||||
if err == nil && len(parsed) == 6 {
|
|
||||||
return parsed.String()
|
|
||||||
}
|
|
||||||
if len(bssid) == 12 {
|
|
||||||
decoded, err := hex.DecodeString(bssid)
|
|
||||||
if err == nil {
|
|
||||||
return net.HardwareAddr(decoded).String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bssid
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkInterface struct {
|
type NetworkInterface struct {
|
||||||
control.Interface
|
control.Interface
|
||||||
Type C.InterfaceType
|
Type C.InterfaceType
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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-tun"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,21 +18,9 @@ type Outbound interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundWithPreferredRoutes interface {
|
|
||||||
Outbound
|
|
||||||
PreferredDomain(domain string) bool
|
|
||||||
PreferredAddress(address netip.Addr) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type DirectRouteOutbound interface {
|
|
||||||
Outbound
|
|
||||||
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
UnsafeCreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundManager interface {
|
type OutboundManager interface {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type Manager struct {
|
|||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
dependByTag map[string][]string
|
dependByTag map[string][]string
|
||||||
defaultOutbound adapter.Outbound
|
defaultOutbound adapter.Outbound
|
||||||
defaultOutboundFallback func() (adapter.Outbound, error)
|
defaultOutboundFallback adapter.Outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||||
@@ -44,7 +44,7 @@ func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {
|
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
||||||
m.defaultOutboundFallback = defaultOutboundFallback
|
m.defaultOutboundFallback = defaultOutboundFallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,38 +55,22 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.access.Unlock()
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
m.access.Unlock()
|
|
||||||
return E.New("default outbound not found: ", m.defaultTag)
|
return E.New("default outbound not found: ", m.defaultTag)
|
||||||
}
|
}
|
||||||
m.defaultOutbound = defaultEndpoint
|
m.defaultOutbound = defaultEndpoint
|
||||||
}
|
}
|
||||||
if m.defaultOutbound == nil {
|
|
||||||
directOutbound, err := m.defaultOutboundFallback()
|
|
||||||
if err != nil {
|
|
||||||
m.access.Unlock()
|
|
||||||
return E.Cause(err, "create direct outbound for fallback")
|
|
||||||
}
|
|
||||||
m.outbounds = append(m.outbounds, directOutbound)
|
|
||||||
m.outboundByTag[directOutbound.Tag()] = directOutbound
|
|
||||||
m.defaultOutbound = directOutbound
|
|
||||||
}
|
|
||||||
outbounds := m.outbounds
|
|
||||||
m.access.Unlock()
|
|
||||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||||
} else {
|
} else {
|
||||||
outbounds := m.outbounds
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,26 +96,21 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
|||||||
}
|
}
|
||||||
started[outboundTag] = true
|
started[outboundTag] = true
|
||||||
canContinue = true
|
canContinue = true
|
||||||
name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]"
|
|
||||||
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
||||||
done := adapter.LogElapsed(m.logger, "start ", name)
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
monitor.Start("start ", name)
|
|
||||||
err := starter.Start(adapter.StartStateStart)
|
err := starter.Start(adapter.StartStateStart)
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", name)
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
}
|
}
|
||||||
} else if starter, isStarter := outboundToStart.(interface {
|
} else if starter, isStarter := outboundToStart.(interface {
|
||||||
Start() error
|
Start() error
|
||||||
}); isStarter {
|
}); isStarter {
|
||||||
done := adapter.LogElapsed(m.logger, "start ", name)
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
monitor.Start("start ", name)
|
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", name)
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,14 +158,11 @@ func (m *Manager) Close() error {
|
|||||||
var err error
|
var err error
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
if closer, isCloser := outbound.(io.Closer); isCloser {
|
if closer, isCloser := outbound.(io.Closer); isCloser {
|
||||||
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
done := adapter.LogElapsed(m.logger, "close ", name)
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, closer.Close(), func(err error) error {
|
err = E.Append(err, closer.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", name)
|
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -211,7 +187,11 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
func (m *Manager) Default() adapter.Outbound {
|
func (m *Manager) Default() adapter.Outbound {
|
||||||
m.access.RLock()
|
m.access.RLock()
|
||||||
defer m.access.RUnlock()
|
defer m.access.RUnlock()
|
||||||
return m.defaultOutbound
|
if m.defaultOutbound != nil {
|
||||||
|
return m.defaultOutbound
|
||||||
|
} else {
|
||||||
|
return m.defaultOutboundFallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
func (m *Manager) Remove(tag string) error {
|
||||||
@@ -267,13 +247,10 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if m.started {
|
if m.started {
|
||||||
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err = adapter.LegacyStart(outbound, stage)
|
err = adapter.LegacyStart(outbound, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,6 @@ func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
|||||||
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
||||||
r.access.Lock()
|
r.access.Lock()
|
||||||
defer r.access.Unlock()
|
defer r.access.Unlock()
|
||||||
return r.UnsafeCreateOutbound(ctx, router, logger, tag, outboundType, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) UnsafeCreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
|
||||||
constructor, loaded := r.constructors[outboundType]
|
constructor, loaded := r.constructors[outboundType]
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PlatformInterface interface {
|
|
||||||
Initialize(networkManager NetworkManager) error
|
|
||||||
|
|
||||||
UsePlatformAutoDetectInterfaceControl() bool
|
|
||||||
AutoDetectInterfaceControl(fd int) error
|
|
||||||
|
|
||||||
UsePlatformInterface() bool
|
|
||||||
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
|
||||||
|
|
||||||
UsePlatformDefaultInterfaceMonitor() bool
|
|
||||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
|
||||||
|
|
||||||
UsePlatformNetworkInterfaces() bool
|
|
||||||
NetworkInterfaces() ([]NetworkInterface, error)
|
|
||||||
|
|
||||||
UnderNetworkExtension() bool
|
|
||||||
NetworkExtensionIncludeAllNetworks() bool
|
|
||||||
|
|
||||||
ClearDNSCache()
|
|
||||||
RequestPermissionForWIFIState() error
|
|
||||||
ReadWIFIState() WIFIState
|
|
||||||
SystemCertificates() []string
|
|
||||||
|
|
||||||
UsePlatformConnectionOwnerFinder() bool
|
|
||||||
FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)
|
|
||||||
|
|
||||||
UsePlatformWIFIMonitor() bool
|
|
||||||
|
|
||||||
UsePlatformNotification() bool
|
|
||||||
SendNotification(notification *Notification) error
|
|
||||||
|
|
||||||
MyInterfaceAddress() []netip.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
type FindConnectionOwnerRequest struct {
|
|
||||||
IpProtocol int32
|
|
||||||
SourceAddress string
|
|
||||||
SourcePort int32
|
|
||||||
DestinationAddress string
|
|
||||||
DestinationPort int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionOwner struct {
|
|
||||||
ProcessID uint32
|
|
||||||
UserId int32
|
|
||||||
UserName string
|
|
||||||
ProcessPath string
|
|
||||||
AndroidPackageNames []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Notification struct {
|
|
||||||
Identifier string
|
|
||||||
TypeName string
|
|
||||||
TypeID int32
|
|
||||||
Title string
|
|
||||||
Subtitle string
|
|
||||||
Body string
|
|
||||||
OpenURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SystemProxyStatus struct {
|
|
||||||
Available bool
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Provider interface {
|
|
||||||
Type() string
|
|
||||||
Tag() string
|
|
||||||
Outbounds() []Outbound
|
|
||||||
Outbound(tag string) (Outbound, bool)
|
|
||||||
UpdatedAt() time.Time
|
|
||||||
HealthCheck(ctx context.Context) (map[string]uint16, error)
|
|
||||||
RegisterCallback(callback ProviderUpdateCallback) *list.Element[ProviderUpdateCallback]
|
|
||||||
UnregisterCallback(element *list.Element[ProviderUpdateCallback])
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderUpdater interface {
|
|
||||||
Update() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderSubscriptionInfo interface {
|
|
||||||
SubscriptionInfo() SubscriptionInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderRegistry interface {
|
|
||||||
option.ProviderOptionsRegistry
|
|
||||||
CreateProvider(ctx context.Context, router Router, logFactory log.Factory, tag string, providerType string, options any) (Provider, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Providers() []Provider
|
|
||||||
Get(tag string) (Provider, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, router Router, logFactory log.Factory, tag string, providerType string, options any) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubscriptionInfo struct {
|
|
||||||
Upload int64
|
|
||||||
Download int64
|
|
||||||
Total int64
|
|
||||||
Expire int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderUpdateCallback = func(tag string) error
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/batch"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
ctx context.Context
|
|
||||||
outbound adapter.OutboundManager
|
|
||||||
router adapter.Router
|
|
||||||
logFactory log.Factory
|
|
||||||
logger log.ContextLogger
|
|
||||||
providerType string
|
|
||||||
providerTag string
|
|
||||||
outbounds []adapter.Outbound
|
|
||||||
outboundsByTag map[string]adapter.Outbound
|
|
||||||
ticker *time.Ticker
|
|
||||||
checking atomic.Bool
|
|
||||||
history adapter.URLTestHistoryStorage
|
|
||||||
callbackAccess sync.Mutex
|
|
||||||
callbacks list.List[adapter.ProviderUpdateCallback]
|
|
||||||
|
|
||||||
link string
|
|
||||||
enabled bool
|
|
||||||
timeout time.Duration
|
|
||||||
interval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.OutboundManager, logFactory log.Factory, logger log.ContextLogger, providerTag string, providerType string, options option.ProviderHealthCheckOptions) Adapter {
|
|
||||||
timeout := time.Duration(options.Timeout)
|
|
||||||
if timeout == 0 {
|
|
||||||
timeout = 3 * time.Second
|
|
||||||
}
|
|
||||||
interval := time.Duration(options.Interval)
|
|
||||||
if interval == 0 {
|
|
||||||
interval = 10 * time.Minute
|
|
||||||
}
|
|
||||||
if interval < time.Minute {
|
|
||||||
interval = time.Minute
|
|
||||||
}
|
|
||||||
return Adapter{
|
|
||||||
ctx: ctx,
|
|
||||||
outbound: outbound,
|
|
||||||
router: router,
|
|
||||||
logFactory: logFactory,
|
|
||||||
logger: logger,
|
|
||||||
providerType: providerType,
|
|
||||||
providerTag: providerTag,
|
|
||||||
|
|
||||||
enabled: options.Enabled,
|
|
||||||
link: options.URL,
|
|
||||||
timeout: timeout,
|
|
||||||
interval: interval,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Start() error {
|
|
||||||
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
|
|
||||||
if a.history == nil {
|
|
||||||
if clashServer := service.FromContext[adapter.ClashServer](a.ctx); clashServer != nil {
|
|
||||||
a.history = clashServer.HistoryStorage()
|
|
||||||
} else {
|
|
||||||
a.history = urltest.NewHistoryStorage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go a.loopCheck()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.providerType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.providerTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Outbounds() []adapter.Outbound {
|
|
||||||
return a.outbounds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Outbound(tag string) (adapter.Outbound, bool) {
|
|
||||||
if a.outboundsByTag == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
detour, ok := a.outboundsByTag[tag]
|
|
||||||
return detour, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
|
|
||||||
a.removeUseless(newOpts)
|
|
||||||
var (
|
|
||||||
oldOptByTag = make(map[string]option.Outbound)
|
|
||||||
outbounds = make([]adapter.Outbound, 0, len(newOpts))
|
|
||||||
outboundsByTag = make(map[string]adapter.Outbound)
|
|
||||||
)
|
|
||||||
for _, opt := range oldOpts {
|
|
||||||
oldOptByTag[opt.Tag] = opt
|
|
||||||
}
|
|
||||||
for i, opt := range newOpts {
|
|
||||||
var tag string
|
|
||||||
if opt.Tag != "" {
|
|
||||||
tag = F.ToString(a.providerTag, "/", opt.Tag)
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(a.providerTag, "/", i)
|
|
||||||
}
|
|
||||||
outbound, exist := a.outbound.Outbound(tag)
|
|
||||||
if !exist || !reflect.DeepEqual(opt, oldOptByTag[opt.Tag]) {
|
|
||||||
err := a.outbound.Create(
|
|
||||||
adapter.WithContext(a.ctx, &adapter.InboundContext{
|
|
||||||
Outbound: tag,
|
|
||||||
}),
|
|
||||||
a.router,
|
|
||||||
a.logFactory.NewLogger(F.ToString("outbound/", opt.Type, "[", tag, "]")),
|
|
||||||
tag,
|
|
||||||
opt.Type,
|
|
||||||
opt.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Warn(err, " in ", tag, ", skip create this outbound")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
outbound, _ = a.outbound.Outbound(tag)
|
|
||||||
}
|
|
||||||
outbounds = append(outbounds, outbound)
|
|
||||||
outboundsByTag[tag] = outbound
|
|
||||||
}
|
|
||||||
if a.enabled && a.history != nil {
|
|
||||||
go a.HealthCheck(a.ctx)
|
|
||||||
}
|
|
||||||
a.outbounds = outbounds
|
|
||||||
a.outboundsByTag = outboundsByTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) HealthCheck(ctx context.Context) (map[string]uint16, error) {
|
|
||||||
if a.ticker != nil {
|
|
||||||
a.ticker.Reset(a.interval)
|
|
||||||
}
|
|
||||||
return a.healthcheck(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) RegisterCallback(callback adapter.ProviderUpdateCallback) *list.Element[adapter.ProviderUpdateCallback] {
|
|
||||||
a.callbackAccess.Lock()
|
|
||||||
defer a.callbackAccess.Unlock()
|
|
||||||
return a.callbacks.PushBack(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) UnregisterCallback(element *list.Element[adapter.ProviderUpdateCallback]) {
|
|
||||||
a.callbackAccess.Lock()
|
|
||||||
defer a.callbackAccess.Unlock()
|
|
||||||
a.callbacks.Remove(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) UpdateGroups() {
|
|
||||||
for element := a.callbacks.Front(); element != nil; element = element.Next() {
|
|
||||||
element.Value(a.providerTag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Close() error {
|
|
||||||
if a.ticker != nil {
|
|
||||||
a.ticker.Stop()
|
|
||||||
}
|
|
||||||
outbounds := a.outbounds
|
|
||||||
a.outbounds = nil
|
|
||||||
var err error
|
|
||||||
for _, ob := range outbounds {
|
|
||||||
if err2 := a.outbound.Remove(ob.Tag()); err2 != nil {
|
|
||||||
err = E.Append(err, err2, func(err error) error {
|
|
||||||
return E.Cause(err, "close outbound [", ob.Tag(), "]")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) loopCheck() {
|
|
||||||
if !a.enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
a.ticker = time.NewTicker(a.interval)
|
|
||||||
a.healthcheck(a.ctx)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-a.ctx.Done():
|
|
||||||
return
|
|
||||||
case <-a.ticker.C:
|
|
||||||
a.healthcheck(a.ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) healthcheck(ctx context.Context) (map[string]uint16, error) {
|
|
||||||
result := make(map[string]uint16)
|
|
||||||
if a.checking.Swap(true) {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
defer a.checking.Store(false)
|
|
||||||
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
|
||||||
var resultAccess sync.Mutex
|
|
||||||
checked := make(map[string]bool)
|
|
||||||
for _, detour := range a.outbounds {
|
|
||||||
tag := detour.Tag()
|
|
||||||
if checked[tag] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
checked[tag] = true
|
|
||||||
b.Go(tag, func() (any, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(a.ctx, a.timeout)
|
|
||||||
defer cancel()
|
|
||||||
t, err := urltest.URLTest(ctx, a.link, detour)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Debug("outbound ", tag, " unavailable: ", err)
|
|
||||||
a.history.DeleteURLTestHistory(tag)
|
|
||||||
} else {
|
|
||||||
a.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
|
||||||
a.history.StoreURLTestHistory(tag, &adapter.URLTestHistory{
|
|
||||||
Time: time.Now(),
|
|
||||||
Delay: t,
|
|
||||||
})
|
|
||||||
resultAccess.Lock()
|
|
||||||
result[tag] = t
|
|
||||||
resultAccess.Unlock()
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
b.Wait()
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) removeUseless(newOpts []option.Outbound) {
|
|
||||||
if len(a.outbounds) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exists := make(map[string]bool)
|
|
||||||
for i, opt := range newOpts {
|
|
||||||
var tag string
|
|
||||||
if opt.Tag != "" {
|
|
||||||
tag = F.ToString(a.providerTag, "/", opt.Tag)
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(a.providerTag, "/", i)
|
|
||||||
}
|
|
||||||
exists[tag] = true
|
|
||||||
}
|
|
||||||
for _, opt := range a.outbounds {
|
|
||||||
if !exists[opt.Tag()] {
|
|
||||||
if err := a.outbound.Remove(opt.Tag()); err != nil {
|
|
||||||
a.logger.Error(err, "close outbound [", opt.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.ProviderManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.ProviderRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
providers []adapter.Provider
|
|
||||||
providerByTag map[string]adapter.Provider
|
|
||||||
wg sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger logger.ContextLogger, registry adapter.ProviderRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
providerByTag: make(map[string]adapter.Provider),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Initialize() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
|
||||||
m.access.Lock()
|
|
||||||
if m.started && m.stage >= stage {
|
|
||||||
panic("already started")
|
|
||||||
}
|
|
||||||
m.started = true
|
|
||||||
m.stage = stage
|
|
||||||
providers := m.providers
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, provider := range providers {
|
|
||||||
err := adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " provider/", provider.Type(), "[", provider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
m.access.Lock()
|
|
||||||
if !m.started {
|
|
||||||
m.access.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
providers := m.providers
|
|
||||||
m.providers = nil
|
|
||||||
m.access.Unlock()
|
|
||||||
var err error
|
|
||||||
for _, provider := range providers {
|
|
||||||
if closer, isCloser := provider.(io.Closer); isCloser {
|
|
||||||
monitor.Start("close provider/", provider.Type(), "[", provider.Tag(), "]")
|
|
||||||
err = E.Append(err, closer.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close provider/", provider.Type(), "[", provider.Tag(), "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Providers() []adapter.Provider {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.Provider, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
provider, found := m.providerByTag[tag]
|
|
||||||
m.access.Unlock()
|
|
||||||
return provider, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
provider, found := m.providerByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.providerByTag, tag)
|
|
||||||
index := common.Index(m.providers, func(it adapter.Provider) bool {
|
|
||||||
return it == provider
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:index], m.providers[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return common.Close(provider)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, providerType string, options any) error {
|
|
||||||
if tag == "" {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
provider, err := m.registry.CreateProvider(ctx, router, logFactory, tag, providerType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
err = adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " provider/", provider.Type(), "[", provider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsProvider, loaded := m.providerByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = common.Close(existsProvider)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close provider", provider.Type(), "[", existsProvider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.providers, func(it adapter.Provider) bool {
|
|
||||||
return it == existsProvider
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers, provider)
|
|
||||||
m.providerByTag[tag] = provider
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options T) (adapter.Provider, error)
|
|
||||||
|
|
||||||
func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
|
|
||||||
registry.register(providerType, func() any {
|
|
||||||
return new(Options)
|
|
||||||
}, func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, rawOptions any) (adapter.Provider, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, router, logFactory, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.ProviderRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options any) (adapter.Provider, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructors map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructors: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) CreateOptions(providerType string) (any, bool) {
|
|
||||||
r.access.Lock()
|
|
||||||
defer r.access.Unlock()
|
|
||||||
optionsConstructor, loaded := r.optionsType[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return optionsConstructor(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) CreateProvider(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, providerType string, options any) (adapter.Provider, error) {
|
|
||||||
r.access.Lock()
|
|
||||||
defer r.access.Unlock()
|
|
||||||
constructor, loaded := r.constructors[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("provider type not found: '" + providerType + "'")
|
|
||||||
}
|
|
||||||
return constructor(ctx, router, logFactory, tag, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
|
||||||
r.access.Lock()
|
|
||||||
defer r.access.Unlock()
|
|
||||||
r.optionsType[providerType] = optionsConstructor
|
|
||||||
r.constructors[providerType] = constructor
|
|
||||||
}
|
|
||||||
@@ -6,10 +6,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
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"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -21,12 +19,12 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
|
PreMatch(metadata InboundContext) error
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
|
NeedWIFIState() bool
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
NeedFindProcess() bool
|
SetTracker(tracker ConnectionTracker)
|
||||||
AppendTracker(tracker ConnectionTracker)
|
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type HeadlessRule interface {
|
|||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
SimpleLifecycle
|
Service
|
||||||
Type() string
|
Type() string
|
||||||
Action() RuleAction
|
Action() RuleAction
|
||||||
}
|
}
|
||||||
|
|||||||
54
adapter/script.go
Normal file
54
adapter/script.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ScriptManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Scripts() []Script
|
||||||
|
Script(name string) (Script, bool)
|
||||||
|
SurgeCache() *SurgeInMemoryCache
|
||||||
|
}
|
||||||
|
|
||||||
|
type SurgeInMemoryCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Script interface {
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||||
|
PostStart() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SurgeScript interface {
|
||||||
|
Script
|
||||||
|
ExecuteGeneric(ctx context.Context, scriptType string, timeout time.Duration, arguments []string) error
|
||||||
|
ExecuteHTTPRequest(ctx context.Context, timeout time.Duration, request *http.Request, body []byte, binaryBody bool, arguments []string) (*HTTPRequestScriptResult, error)
|
||||||
|
ExecuteHTTPResponse(ctx context.Context, timeout time.Duration, request *http.Request, response *http.Response, body []byte, binaryBody bool, arguments []string) (*HTTPResponseScriptResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPRequestScriptResult struct {
|
||||||
|
URL string
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
Response *HTTPRequestScriptResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPRequestScriptResponse struct {
|
||||||
|
Status int
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPResponseScriptResult struct {
|
||||||
|
Status int
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
@@ -1,27 +1,6 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Lifecycle
|
Start() error
|
||||||
Type() string
|
Close() error
|
||||||
Tag() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceRegistry interface {
|
|
||||||
option.ServiceOptionsRegistry
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Services() []Service
|
|
||||||
Get(tag string) (Service, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
serviceType string
|
|
||||||
serviceTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(serviceType string, serviceTag string) Adapter {
|
|
||||||
return Adapter{
|
|
||||||
serviceType: serviceType,
|
|
||||||
serviceTag: serviceTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.serviceType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.serviceTag
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.ServiceManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.ServiceRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
services []adapter.Service
|
|
||||||
serviceByTag map[string]adapter.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
serviceByTag: make(map[string]adapter.Service),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
|
||||||
m.access.Lock()
|
|
||||||
if m.started && m.stage >= stage {
|
|
||||||
panic("already started")
|
|
||||||
}
|
|
||||||
m.started = true
|
|
||||||
m.stage = stage
|
|
||||||
services := m.services
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, service := range services {
|
|
||||||
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err := adapter.LegacyStart(service, stage)
|
|
||||||
done()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if !m.started {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
services := m.services
|
|
||||||
m.services = nil
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
var err error
|
|
||||||
for _, service := range services {
|
|
||||||
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, "close ", name)
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", name)
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Services() []adapter.Service {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.services
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.Service, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
service, found := m.serviceByTag[tag]
|
|
||||||
m.access.Unlock()
|
|
||||||
return service, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
service, found := m.serviceByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.serviceByTag, tag)
|
|
||||||
index := common.Index(m.services, func(it adapter.Service) bool {
|
|
||||||
return it == service
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid service index")
|
|
||||||
}
|
|
||||||
m.services = append(m.services[:index], m.services[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return service.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
|
|
||||||
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err = adapter.LegacyStart(service, stage)
|
|
||||||
done()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = existsService.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
|
|
||||||
return it == existsService
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid service index")
|
|
||||||
}
|
|
||||||
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.services = append(m.services, service)
|
|
||||||
m.serviceByTag[tag] = service
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
|
|
||||||
|
|
||||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
|
||||||
registry.register(outboundType, func() any {
|
|
||||||
return new(Options)
|
|
||||||
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.ServiceRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructor map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructor: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return optionsConstructor(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
constructor, loaded := m.constructor[outboundType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.optionsType[outboundType] = optionsConstructor
|
|
||||||
m.constructor[outboundType] = constructor
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ManagedSSMServer interface {
|
|
||||||
Inbound
|
|
||||||
SetTracker(tracker SSMTracker)
|
|
||||||
UpdateUsers(users []string, uPSKs []string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSMTracker interface {
|
|
||||||
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
|
|
||||||
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,6 @@ package adapter
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type TimeService interface {
|
type TimeService interface {
|
||||||
SimpleLifecycle
|
Service
|
||||||
TimeFunc() func() time.Time
|
TimeFunc() func() time.Time
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func NewUpstreamContextHandlerEx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
_, myMetadata := ExtendContext(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
_, myMetadata := ExtendContext(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ type routeContextHandlerWrapperEx struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
_, metadata := ExtendContext(ctx)
|
metadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
_, metadata := ExtendContext(ctx)
|
metadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
|||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
return M.Metadata{
|
return M.Metadata{
|
||||||
Source: metadata.Source.Unwrap(),
|
Source: metadata.Source,
|
||||||
Destination: metadata.Destination.Unwrap(),
|
Destination: metadata.Destination,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
277
box.go
277
box.go
@@ -12,21 +12,22 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
"github.com/sagernet/sing-box/adapter/provider"
|
|
||||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
|
||||||
"github.com/sagernet/sing-box/common/certificate"
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"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/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/mitm"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
|
"github.com/sagernet/sing-box/script"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
@@ -35,24 +36,24 @@ import (
|
|||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
var _ adapter.Service = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
network *route.NetworkManager
|
||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
provider *provider.Manager
|
dnsTransport *dns.TransportManager
|
||||||
service *boxService.Manager
|
dnsRouter *dns.Router
|
||||||
dnsTransport *dns.TransportManager
|
connection *route.ConnectionManager
|
||||||
dnsRouter *dns.Router
|
router *route.Router
|
||||||
connection *route.ConnectionManager
|
script *script.Manager
|
||||||
router *route.Router
|
mitm adapter.MITMEngine //*mitm.Engine
|
||||||
internalService []adapter.LifecycleService
|
services []adapter.LifecycleService
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -66,9 +67,7 @@ func Context(
|
|||||||
inboundRegistry adapter.InboundRegistry,
|
inboundRegistry adapter.InboundRegistry,
|
||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
providerRegistry adapter.ProviderRegistry,
|
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
serviceRegistry adapter.ServiceRegistry,
|
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -85,19 +84,10 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||||
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||||
}
|
}
|
||||||
if service.FromContext[option.ProviderOptionsRegistry](ctx) == nil ||
|
|
||||||
service.FromContext[adapter.ProviderRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.ProviderOptionsRegistry](ctx, providerRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.ProviderRegistry](ctx, providerRegistry)
|
|
||||||
}
|
|
||||||
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
||||||
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
}
|
}
|
||||||
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
|
||||||
}
|
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,9 +102,7 @@ func New(options Options) (*Box, error) {
|
|||||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
providerRegistry := service.FromContext[adapter.ProviderRegistry](ctx)
|
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@@ -125,22 +113,10 @@ func New(options Options) (*Box, error) {
|
|||||||
if outboundRegistry == nil {
|
if outboundRegistry == nil {
|
||||||
return nil, E.New("missing outbound registry in context")
|
return nil, E.New("missing outbound registry in context")
|
||||||
}
|
}
|
||||||
if providerRegistry == nil {
|
|
||||||
return nil, E.New("missing provider registry in context")
|
|
||||||
}
|
|
||||||
if dnsTransportRegistry == nil {
|
|
||||||
return nil, E.New("missing DNS transport registry in context")
|
|
||||||
}
|
|
||||||
if serviceRegistry == nil {
|
|
||||||
return nil, E.New("missing service registry in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var needCacheFile bool
|
var needCacheFile bool
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
@@ -153,10 +129,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
|
||||||
}
|
|
||||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
@@ -172,44 +145,33 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
service.MustRegister[log.Factory](ctx, logFactory)
|
|
||||||
|
|
||||||
var internalServices []adapter.LifecycleService
|
var services []adapter.LifecycleService
|
||||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), common.PtrValueOrDefault(options.Certificate))
|
||||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
if err != nil {
|
||||||
len(certificateOptions.Certificate) > 0 ||
|
return nil, err
|
||||||
len(certificateOptions.CertificatePath) > 0 ||
|
|
||||||
len(certificateOptions.CertificateDirectoryPath) > 0 {
|
|
||||||
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
|
||||||
internalServices = append(internalServices, certificateStore)
|
|
||||||
}
|
}
|
||||||
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
|
services = append(services, certificateStore)
|
||||||
|
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
providerManager := provider.NewManager(logFactory.NewLogger("provider"), providerRegistry)
|
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
service.MustRegister[adapter.ProviderManager](ctx, providerManager)
|
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection"))
|
||||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
||||||
service.MustRegister[adapter.Router](ctx, router)
|
service.MustRegister[adapter.Router](ctx, router)
|
||||||
@@ -217,8 +179,8 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize router")
|
return nil, E.Cause(err, "initialize router")
|
||||||
}
|
}
|
||||||
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
|
||||||
var timeService *tls.TimeServiceWrapper
|
var timeService *tls.TimeServiceWrapper
|
||||||
|
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||||
if ntpOptions.Enabled {
|
if ntpOptions.Enabled {
|
||||||
timeService = new(tls.TimeServiceWrapper)
|
timeService = new(tls.TimeServiceWrapper)
|
||||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
@@ -290,10 +252,6 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
options.Outbounds = append(options.Outbounds, option.Outbound{
|
|
||||||
Tag: "Compatible",
|
|
||||||
Type: C.TypeDirect,
|
|
||||||
})
|
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
@@ -320,61 +278,27 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, providerOptions := range options.Providers {
|
outboundManager.Initialize(common.Must1(
|
||||||
var tag string
|
direct.NewOutbound(
|
||||||
if providerOptions.Tag != "" {
|
|
||||||
tag = providerOptions.Tag
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
err = providerManager.Create(
|
|
||||||
ctx,
|
|
||||||
router,
|
|
||||||
logFactory,
|
|
||||||
tag,
|
|
||||||
providerOptions.Type,
|
|
||||||
providerOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize provider[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, serviceOptions := range options.Services {
|
|
||||||
var tag string
|
|
||||||
if serviceOptions.Tag != "" {
|
|
||||||
tag = serviceOptions.Tag
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
err = serviceManager.Create(
|
|
||||||
ctx,
|
|
||||||
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
|
||||||
tag,
|
|
||||||
serviceOptions.Type,
|
|
||||||
serviceOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
|
||||||
return direct.NewOutbound(
|
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger("outbound/direct"),
|
logFactory.NewLogger("outbound/direct"),
|
||||||
"direct",
|
"direct",
|
||||||
option.DirectOutboundOptions{},
|
option.DirectOutboundOptions{},
|
||||||
)
|
),
|
||||||
})
|
))
|
||||||
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
dnsTransportManager.Initialize(common.Must1(
|
||||||
return dnsTransportRegistry.CreateDNSTransport(
|
local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger("dns/local"),
|
logFactory.NewLogger("dns/local"),
|
||||||
"local",
|
"local",
|
||||||
C.DNSTypeLocal,
|
option.LocalDNSServerOptions{},
|
||||||
&option.LocalDNSServerOptions{},
|
)))
|
||||||
)
|
scriptManager, err := script.NewManager(ctx, logFactory, options.Scripts)
|
||||||
})
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize script manager")
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.ScriptManager](ctx, scriptManager)
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -384,7 +308,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
internalServices = append(internalServices, cacheFile)
|
services = append(services, cacheFile)
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
@@ -393,9 +317,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash-server")
|
return nil, E.Cause(err, "create clash-server")
|
||||||
}
|
}
|
||||||
router.AppendTracker(clashServer)
|
router.SetTracker(clashServer)
|
||||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||||
internalServices = append(internalServices, clashServer)
|
services = append(services, clashServer)
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
@@ -403,8 +327,8 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create v2ray-server")
|
return nil, E.Cause(err, "create v2ray-server")
|
||||||
}
|
}
|
||||||
if v2rayServer.StatsService() != nil {
|
if v2rayServer.StatsService() != nil {
|
||||||
router.AppendTracker(v2rayServer.StatsService())
|
router.SetTracker(v2rayServer.StatsService())
|
||||||
internalServices = append(internalServices, v2rayServer)
|
services = append(services, v2rayServer)
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,24 +346,34 @@ func New(options Options) (*Box, error) {
|
|||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
})
|
})
|
||||||
timeService.TimeService = ntpService
|
timeService.TimeService = ntpService
|
||||||
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
|
}
|
||||||
|
mitmOptions := common.PtrValueOrDefault(options.MITM)
|
||||||
|
var mitmEngine adapter.MITMEngine
|
||||||
|
if mitmOptions.Enabled {
|
||||||
|
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create MITM engine")
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.MITMEngine](ctx, engine)
|
||||||
|
mitmEngine = engine
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
provider: providerManager,
|
dnsTransport: dnsTransportManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsRouter: dnsRouter,
|
||||||
service: serviceManager,
|
connection: connectionManager,
|
||||||
dnsRouter: dnsRouter,
|
router: router,
|
||||||
connection: connectionManager,
|
script: scriptManager,
|
||||||
router: router,
|
mitm: mitmEngine,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
internalService: internalServices,
|
services: services,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,15 +423,15 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.provider, s.service)
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.provider, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.script, s.mitm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -509,27 +443,31 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService)
|
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
err = s.inbound.Start(adapter.StartStateStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.provider, s.service)
|
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService)
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.provider, s.service)
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -543,40 +481,17 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
var err error
|
err := common.Close(
|
||||||
for _, closeItem := range []struct {
|
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
name string
|
)
|
||||||
service adapter.Lifecycle
|
for _, lifecycleService := range s.services {
|
||||||
}{
|
|
||||||
{"service", s.service},
|
|
||||||
{"endpoint", s.endpoint},
|
|
||||||
{"inbound", s.inbound},
|
|
||||||
{"provider", s.provider},
|
|
||||||
{"outbound", s.outbound},
|
|
||||||
{"router", s.router},
|
|
||||||
{"connection", s.connection},
|
|
||||||
{"dns-router", s.dnsRouter},
|
|
||||||
{"dns-transport", s.dnsTransport},
|
|
||||||
{"network", s.network},
|
|
||||||
} {
|
|
||||||
done := adapter.LogElapsed(s.logger, "close ", closeItem.name)
|
|
||||||
err = E.Append(err, closeItem.service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", closeItem.name)
|
|
||||||
})
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
for _, lifecycleService := range s.internalService {
|
|
||||||
done := adapter.LogElapsed(s.logger, "close ", lifecycleService.Name())
|
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
})
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
done := adapter.LogElapsed(s.logger, "close logger")
|
|
||||||
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close logger")
|
return E.Cause(err, "close logger")
|
||||||
})
|
})
|
||||||
done()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,11 +510,3 @@ func (s *Box) Inbound() adapter.InboundManager {
|
|||||||
func (s *Box) Outbound() adapter.OutboundManager {
|
func (s *Box) Outbound() adapter.OutboundManager {
|
||||||
return s.outbound
|
return s.outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Endpoint() adapter.EndpointManager {
|
|
||||||
return s.endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) LogFactory() log.Factory {
|
|
||||||
return s.logFactory
|
|
||||||
}
|
|
||||||
|
|||||||
Submodule clients/android updated: eb87216961...aefe3c0290
Submodule clients/apple updated: c19945f65b...ae5818ee5a
@@ -100,33 +100,12 @@ findVersion:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func publishTestflight(ctx context.Context) error {
|
func publishTestflight(ctx context.Context) error {
|
||||||
if len(os.Args) < 3 {
|
|
||||||
return E.New("platform required: ios, macos, or tvos")
|
|
||||||
}
|
|
||||||
var platform asc.Platform
|
|
||||||
switch os.Args[2] {
|
|
||||||
case "ios":
|
|
||||||
platform = asc.PlatformIOS
|
|
||||||
case "macos":
|
|
||||||
platform = asc.PlatformMACOS
|
|
||||||
case "tvos":
|
|
||||||
platform = asc.PlatformTVOS
|
|
||||||
default:
|
|
||||||
return E.New("unknown platform: ", os.Args[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
tagVersion, err := build_shared.ReadTagVersion()
|
tagVersion, err := build_shared.ReadTagVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
|
client := createClient(10 * time.Minute)
|
||||||
releaseNotes := F.ToString("sing-box ", tagVersion.String())
|
|
||||||
if len(os.Args) >= 4 {
|
|
||||||
releaseNotes = strings.Join(os.Args[3:], " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := createClient(20 * time.Minute)
|
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
@@ -136,76 +115,95 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
||||||
return it.ID
|
return it.ID
|
||||||
})
|
})
|
||||||
|
var platforms []asc.Platform
|
||||||
waitingForProcess := false
|
if len(os.Args) == 3 {
|
||||||
log.Info(string(platform), " list builds")
|
switch os.Args[2] {
|
||||||
for {
|
case "ios":
|
||||||
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
platforms = []asc.Platform{asc.PlatformIOS}
|
||||||
FilterApp: []string{appID},
|
case "macos":
|
||||||
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
platforms = []asc.Platform{asc.PlatformMACOS}
|
||||||
})
|
case "tvos":
|
||||||
if err != nil {
|
platforms = []asc.Platform{asc.PlatformTVOS}
|
||||||
return err
|
default:
|
||||||
|
return E.New("unknown platform: ", os.Args[2])
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
} else {
|
||||||
log.Info(string(platform), " ", tag, " found build: ", build.ID, " (", *build.Attributes.Version, ")")
|
platforms = []asc.Platform{
|
||||||
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
asc.PlatformIOS,
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
asc.PlatformMACOS,
|
||||||
time.Sleep(15 * time.Second)
|
asc.PlatformTVOS,
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if *build.Attributes.ProcessingState != "VALID" {
|
}
|
||||||
waitingForProcess = true
|
for _, platform := range platforms {
|
||||||
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
log.Info(string(platform), " list builds")
|
||||||
time.Sleep(15 * time.Second)
|
for {
|
||||||
continue
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
}
|
FilterApp: []string{appID},
|
||||||
log.Info(string(platform), " ", tag, " list localizations")
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
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(releaseNotes))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
build := builds.Data[0]
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
time.Sleep(15 * time.Second)
|
||||||
log.Info("waiting for process")
|
continue
|
||||||
time.Sleep(15 * time.Second)
|
}
|
||||||
continue
|
if *build.Attributes.ProcessingState != "VALID" {
|
||||||
} else if err != nil {
|
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||||
return err
|
time.Sleep(15 * time.Second)
|
||||||
}
|
continue
|
||||||
log.Info(string(platform), " ", tag, " list submissions")
|
}
|
||||||
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
log.Info(string(platform), " ", tag, " list localizations")
|
||||||
FilterBuild: []string{build.ID},
|
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
||||||
})
|
|
||||||
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 {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
return err
|
||||||
log.Error(err)
|
}
|
||||||
break
|
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
|
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 {
|
||||||
|
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
||||||
|
log.Error(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
_ "github.com/sagernet/gomobile"
|
_ "github.com/sagernet/gomobile"
|
||||||
@@ -20,14 +19,12 @@ var (
|
|||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
// withTailscale bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
// flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -47,9 +44,8 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
iosTags []string
|
||||||
// memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,38 +56,18 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_masque", "with_mtproxy", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_script")
|
||||||
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
// memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
type AndroidBuildConfig struct {
|
func buildAndroid() {
|
||||||
AndroidAPI int
|
build_shared.FindSDK()
|
||||||
OutputName string
|
|
||||||
Tags []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterTags(tags []string, exclude ...string) []string {
|
|
||||||
excludeMap := make(map[string]bool)
|
|
||||||
for _, tag := range exclude {
|
|
||||||
excludeMap[tag] = true
|
|
||||||
}
|
|
||||||
var result []string
|
|
||||||
for _, tag := range tags {
|
|
||||||
if !excludeMap[tag] {
|
|
||||||
result = append(result, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkJavaVersion() {
|
|
||||||
var javaPath string
|
var javaPath string
|
||||||
javaHome := os.Getenv("JAVA_HOME")
|
javaHome := os.Getenv("JAVA_HOME")
|
||||||
if javaHome == "" {
|
if javaHome == "" {
|
||||||
@@ -107,24 +83,21 @@ func checkJavaVersion() {
|
|||||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||||
log.Fatal("java version should be openjdk 17")
|
log.Fatal("java version should be openjdk 17")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func getAndroidBindTarget() string {
|
var bindTarget string
|
||||||
if platform != "" {
|
if platform != "" {
|
||||||
return platform
|
bindTarget = platform
|
||||||
} else if debugEnabled {
|
} else if debugEnabled {
|
||||||
return "android/arm64"
|
bindTarget = "android/arm64"
|
||||||
|
} else {
|
||||||
|
bindTarget = "android"
|
||||||
}
|
}
|
||||||
return "android"
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildAndroidVariant(config AndroidBuildConfig, bindTarget string) {
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
"-o", config.OutputName,
|
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-androidapi", strconv.Itoa(config.AndroidAPI),
|
"-androidapi", "21",
|
||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
@@ -135,59 +108,34 @@ func buildAndroidVariant(config AndroidBuildConfig, bindTarget string) {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-tags", strings.Join(config.Tags, ","))
|
tags := append(sharedTags, memcTags...)
|
||||||
|
if debugEnabled {
|
||||||
|
tags = append(tags, debugTags...)
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, "-tags", strings.Join(tags, ","))
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const name = "libbox.aar"
|
||||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
||||||
if rw.IsDir(copyPath) {
|
if rw.IsDir(copyPath) {
|
||||||
copyPath, _ = filepath.Abs(copyPath)
|
copyPath, _ = filepath.Abs(copyPath)
|
||||||
err = rw.CopyFile(config.OutputName, filepath.Join(copyPath, config.OutputName))
|
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Info("copied ", config.OutputName, " to ", copyPath)
|
log.Info("copied to ", copyPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAndroid() {
|
|
||||||
build_shared.FindSDK()
|
|
||||||
checkJavaVersion()
|
|
||||||
|
|
||||||
bindTarget := getAndroidBindTarget()
|
|
||||||
|
|
||||||
// Build main variant (SDK 23)
|
|
||||||
mainTags := append([]string{}, sharedTags...)
|
|
||||||
// mainTags = append(mainTags, memcTags...)
|
|
||||||
if debugEnabled {
|
|
||||||
mainTags = append(mainTags, debugTags...)
|
|
||||||
}
|
|
||||||
buildAndroidVariant(AndroidBuildConfig{
|
|
||||||
AndroidAPI: 23,
|
|
||||||
OutputName: "libbox.aar",
|
|
||||||
Tags: mainTags,
|
|
||||||
}, bindTarget)
|
|
||||||
|
|
||||||
// Build legacy variant (SDK 21, no naive outbound)
|
|
||||||
legacyTags := filterTags(sharedTags, "with_naive_outbound")
|
|
||||||
// legacyTags = append(legacyTags, memcTags...)
|
|
||||||
if debugEnabled {
|
|
||||||
legacyTags = append(legacyTags, debugTags...)
|
|
||||||
}
|
|
||||||
buildAndroidVariant(AndroidBuildConfig{
|
|
||||||
AndroidAPI: 21,
|
|
||||||
OutputName: "libbox-legacy.aar",
|
|
||||||
Tags: legacyTags,
|
|
||||||
}, bindTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildApple() {
|
func buildApple() {
|
||||||
var bindTarget string
|
var bindTarget string
|
||||||
if platform != "" {
|
if platform != "" {
|
||||||
@@ -195,7 +143,7 @@ func buildApple() {
|
|||||||
} else if debugEnabled {
|
} else if debugEnabled {
|
||||||
bindTarget = "ios"
|
bindTarget = "ios"
|
||||||
} else {
|
} else {
|
||||||
bindTarget = "ios,iossimulator,tvos,tvossimulator,macos"
|
bindTarget = "ios,tvos,macos"
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
@@ -203,11 +151,8 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
}
|
}
|
||||||
//if !withTailscale {
|
|
||||||
// args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
|
||||||
//}
|
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
@@ -215,10 +160,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
//if withTailscale {
|
|
||||||
// tags = append(tags, memcTags...)
|
|
||||||
//}
|
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := filepath.Walk("docs", func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(path, ".md") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return processFile(path)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func processFile(path string) error {
|
|
||||||
content, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(string(content), "\n")
|
|
||||||
modified := false
|
|
||||||
result := make([]string, 0, len(lines))
|
|
||||||
|
|
||||||
inQuoteBlock := false
|
|
||||||
materialLines := []int{} // indices of :material- lines in the block
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
// Check for quote block start
|
|
||||||
if strings.HasPrefix(line, "!!! quote \"") && strings.Contains(line, "sing-box") {
|
|
||||||
inQuoteBlock = true
|
|
||||||
materialLines = nil
|
|
||||||
result = append(result, line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inside a quote block
|
|
||||||
if inQuoteBlock {
|
|
||||||
trimmed := strings.TrimPrefix(line, " ")
|
|
||||||
isMaterialLine := strings.HasPrefix(trimmed, ":material-")
|
|
||||||
isEmpty := strings.TrimSpace(line) == ""
|
|
||||||
isIndented := strings.HasPrefix(line, " ")
|
|
||||||
|
|
||||||
if isMaterialLine {
|
|
||||||
materialLines = append(materialLines, len(result))
|
|
||||||
result = append(result, line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block ends when:
|
|
||||||
// - Empty line AFTER we've seen material lines, OR
|
|
||||||
// - Non-indented, non-empty line
|
|
||||||
blockEnds := (isEmpty && len(materialLines) > 0) || (!isEmpty && !isIndented)
|
|
||||||
if blockEnds {
|
|
||||||
// Process collected material lines
|
|
||||||
if len(materialLines) > 0 {
|
|
||||||
for j, idx := range materialLines {
|
|
||||||
isLast := j == len(materialLines)-1
|
|
||||||
resultLine := strings.TrimRight(result[idx], " ")
|
|
||||||
if !isLast {
|
|
||||||
// Add trailing two spaces for non-last lines
|
|
||||||
resultLine += " "
|
|
||||||
}
|
|
||||||
if result[idx] != resultLine {
|
|
||||||
modified = true
|
|
||||||
result[idx] = resultLine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inQuoteBlock = false
|
|
||||||
materialLines = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle case where file ends while still in a block
|
|
||||||
if inQuoteBlock && len(materialLines) > 0 {
|
|
||||||
for j, idx := range materialLines {
|
|
||||||
isLast := j == len(materialLines)-1
|
|
||||||
resultLine := strings.TrimRight(result[idx], " ")
|
|
||||||
if !isLast {
|
|
||||||
resultLine += " "
|
|
||||||
}
|
|
||||||
if result[idx] != resultLine {
|
|
||||||
modified = true
|
|
||||||
result[idx] = resultLine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if modified {
|
|
||||||
newContent := strings.Join(result, "\n")
|
|
||||||
if !bytes.Equal(content, []byte(newContent)) {
|
|
||||||
log.Info("formatted: ", path)
|
|
||||||
return os.WriteFile(path, []byte(newContent), 0o644)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -5,49 +5,40 @@ import (
|
|||||||
"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/common/badversion"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var nightly bool
|
||||||
flagRunInCI bool
|
|
||||||
flagRunNightly bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
||||||
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
var (
|
if nightly {
|
||||||
versionStr string
|
version, err := build_shared.ReadTagVersionRev()
|
||||||
err error
|
|
||||||
)
|
|
||||||
if flagRunNightly {
|
|
||||||
var version badversion.Version
|
|
||||||
version, err = build_shared.ReadTagVersion()
|
|
||||||
if err == nil {
|
|
||||||
versionStr = version.String()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
versionStr, err = build_shared.ReadTag()
|
|
||||||
}
|
|
||||||
if flagRunInCI {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
var versionStr string
|
||||||
|
if version.PreReleaseIdentifier != "" {
|
||||||
|
versionStr = version.VersionString() + "-nightly"
|
||||||
|
} else {
|
||||||
|
version.Patch++
|
||||||
|
versionStr = version.VersionString() + "-nightly"
|
||||||
|
}
|
||||||
err = setGitHubEnv("version", versionStr)
|
err = setGitHubEnv("version", versionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
os.Stdout.WriteString("unknown\n")
|
os.Stdout.WriteString("unknown\n")
|
||||||
} else {
|
} else {
|
||||||
os.Stdout.WriteString(versionStr + "\n")
|
os.Stdout.WriteString(tag + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
|
||||||
|
|
||||||
var iperf3Path string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := main0()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main0() error {
|
|
||||||
err := shell.Exec("sudo", "ls").Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
results, err := runTests()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoder := json.NewEncoder(os.Stdout)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
return encoder.Encode(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTests() ([]TestResult, error) {
|
|
||||||
boxPaths := []string{
|
|
||||||
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
|
|
||||||
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
|
||||||
"./sing-box",
|
|
||||||
}
|
|
||||||
stacks := []string{
|
|
||||||
"gvisor",
|
|
||||||
"system",
|
|
||||||
}
|
|
||||||
mtus := []int{
|
|
||||||
1500,
|
|
||||||
4064,
|
|
||||||
// 16384,
|
|
||||||
// 32768,
|
|
||||||
// 49152,
|
|
||||||
65535,
|
|
||||||
}
|
|
||||||
flagList := [][]string{
|
|
||||||
{},
|
|
||||||
}
|
|
||||||
var results []TestResult
|
|
||||||
for _, boxPath := range boxPaths {
|
|
||||||
for _, stack := range stacks {
|
|
||||||
for _, mtu := range mtus {
|
|
||||||
if strings.HasPrefix(boxPath, ".") {
|
|
||||||
for _, flags := range flagList {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestResult struct {
|
|
||||||
BoxPath string `json:"box_path"`
|
|
||||||
Stack string `json:"stack"`
|
|
||||||
MTU int `json:"mtu"`
|
|
||||||
Flags []string `json:"flags"`
|
|
||||||
MultiThread bool `json:"multi_thread"`
|
|
||||||
UploadSpeed string `json:"upload_speed"`
|
|
||||||
DownloadSpeed string `json:"download_speed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
|
|
||||||
testAddress := netip.MustParseAddr("1.1.1.1")
|
|
||||||
testConfig := option.Options{
|
|
||||||
Inbounds: []option.Inbound{
|
|
||||||
{
|
|
||||||
Type: C.TypeTun,
|
|
||||||
Options: &option.TunInboundOptions{
|
|
||||||
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
|
|
||||||
AutoRoute: true,
|
|
||||||
MTU: uint32(mtu),
|
|
||||||
Stack: stackName,
|
|
||||||
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Route: &option.RouteOptions{
|
|
||||||
Rules: []option.Rule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultRule{
|
|
||||||
RawDefaultRule: option.RawDefaultRule{
|
|
||||||
IPCIDR: []string{testAddress.String()},
|
|
||||||
},
|
|
||||||
RuleAction: option.RuleAction{
|
|
||||||
Action: C.RuleActionTypeRouteOptions,
|
|
||||||
RouteOptionsOptions: option.RouteOptionsActionOptions{
|
|
||||||
OverrideAddress: "127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AutoDetectInterface: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ctx := include.Context(context.Background())
|
|
||||||
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(tempConfig.Name())
|
|
||||||
encoder := json.NewEncoderContext(ctx, tempConfig)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
err = encoder.Encode(testConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "encode test config")
|
|
||||||
}
|
|
||||||
tempConfig.Close()
|
|
||||||
var sudoArgs []string
|
|
||||||
if len(flags) > 0 {
|
|
||||||
sudoArgs = append(sudoArgs, "env")
|
|
||||||
sudoArgs = append(sudoArgs, flags...)
|
|
||||||
}
|
|
||||||
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
|
||||||
boxProcess := shell.Exec("sudo", sudoArgs...)
|
|
||||||
boxProcess.Stdout = &stderrWriter{}
|
|
||||||
boxProcess.Stderr = io.Discard
|
|
||||||
err = boxProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if C.IsDarwin {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3-darwin")
|
|
||||||
} else {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverProcess := shell.Exec(iperf3Path, "-s")
|
|
||||||
serverProcess.Stdout = io.Discard
|
|
||||||
serverProcess.Stderr = io.Discard
|
|
||||||
err = serverProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "start iperf3 server")
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
args := []string{"-c", testAddress.String()}
|
|
||||||
if multiThread {
|
|
||||||
args = append(args, "-P", "10")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadProcess := shell.Exec(iperf3Path, args...)
|
|
||||||
output, err := uploadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
|
|
||||||
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
|
|
||||||
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
|
|
||||||
|
|
||||||
result = &TestResult{
|
|
||||||
BoxPath: boxPath,
|
|
||||||
Stack: stackName,
|
|
||||||
MTU: mtu,
|
|
||||||
Flags: flags,
|
|
||||||
MultiThread: multiThread,
|
|
||||||
UploadSpeed: uploadResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
|
|
||||||
output, err = downloadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
|
|
||||||
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
|
|
||||||
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
|
|
||||||
|
|
||||||
result.DownloadSpeed = downloadResult
|
|
||||||
|
|
||||||
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
|
|
||||||
if len(flags) > 0 {
|
|
||||||
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
|
|
||||||
}
|
|
||||||
if multiThread {
|
|
||||||
printArgs = append(printArgs, "(-P 10)")
|
|
||||||
}
|
|
||||||
fmt.Println(printArgs...)
|
|
||||||
err = boxProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = serverProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
boxProcess.Cmd.Wait()
|
|
||||||
close(boxDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
serverDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
serverProcess.Process.Wait()
|
|
||||||
close(serverDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-boxDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
boxProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("box process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-serverDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
serverProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("server process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type stderrWriter struct{}
|
|
||||||
|
|
||||||
func (w *stderrWriter) Write(p []byte) (n int, err error) {
|
|
||||||
return os.Stderr.Write(p)
|
|
||||||
}
|
|
||||||
@@ -71,12 +71,12 @@ func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDLi
|
|||||||
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
|
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
|
||||||
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
|
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
|
||||||
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
|
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
|
||||||
version := strings.Trim(projectContent[versionStart:versionEnd], "\"")
|
version := projectContent[versionStart:versionEnd]
|
||||||
if version == newVersion {
|
if version == newVersion {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
updated = true
|
updated = true
|
||||||
projectContent = projectContent[:versionStart] + "\"" + newVersion + "\"" + projectContent[versionEnd:]
|
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
|
||||||
}
|
}
|
||||||
return projectContent, updated
|
return projectContent, updated
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
err = updateChromeIncludedRootCAs()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMozillaIncludedRootCAs() error {
|
func updateMozillaIncludedRootCAs() error {
|
||||||
@@ -73,94 +69,3 @@ func init() {
|
|||||||
generated.WriteString("}\n")
|
generated.WriteString("}\n")
|
||||||
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchChinaFingerprints() (map[string]bool, error) {
|
|
||||||
response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/AllCertificateRecordsCSVFormatv4")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
reader := csv.NewReader(response.Body)
|
|
||||||
header, err := reader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
countryIndex := slices.Index(header, "Country")
|
|
||||||
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
|
|
||||||
|
|
||||||
chinaFingerprints := make(map[string]bool)
|
|
||||||
for {
|
|
||||||
record, err := reader.Read()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if record[countryIndex] == "China" {
|
|
||||||
chinaFingerprints[record[fingerprintIndex]] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return chinaFingerprints, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateChromeIncludedRootCAs() error {
|
|
||||||
chinaFingerprints, err := fetchChinaFingerprints()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/RootCACertificatesIncludedByRSReportCSV")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
reader := csv.NewReader(response.Body)
|
|
||||||
header, err := reader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
subjectIndex := slices.Index(header, "Subject")
|
|
||||||
statusIndex := slices.Index(header, "Google Chrome Status")
|
|
||||||
certIndex := slices.Index(header, "X.509 Certificate (PEM)")
|
|
||||||
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
|
|
||||||
|
|
||||||
generated := strings.Builder{}
|
|
||||||
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
|
|
||||||
|
|
||||||
package certificate
|
|
||||||
|
|
||||||
import "crypto/x509"
|
|
||||||
|
|
||||||
var chromeIncluded *x509.CertPool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
chromeIncluded = x509.NewCertPool()
|
|
||||||
`)
|
|
||||||
for {
|
|
||||||
record, err := reader.Read()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if record[statusIndex] != "Included" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if chinaFingerprints[record[fingerprintIndex]] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
generated.WriteString("\n // ")
|
|
||||||
generated.WriteString(record[subjectIndex])
|
|
||||||
generated.WriteString("\n")
|
|
||||||
generated.WriteString(" chromeIncluded.AppendCertsFromPEM([]byte(`")
|
|
||||||
cert := record[certIndex]
|
|
||||||
// Remove single quotes if present
|
|
||||||
if len(cert) > 0 && cert[0] == '\'' {
|
|
||||||
cert = cert[1 : len(cert)-1]
|
|
||||||
}
|
|
||||||
generated.WriteString(cert)
|
|
||||||
generated.WriteString("`))\n")
|
|
||||||
}
|
|
||||||
generated.WriteString("}\n")
|
|
||||||
return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -67,5 +68,6 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||||
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
121
cmd/sing-box/cmd_generate_ca.go
Normal file
121
cmd/sing-box/cmd_generate_ca.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagGenerateCAName string
|
||||||
|
flagGenerateCAPKCS12Password string
|
||||||
|
flagGenerateOutput string
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandGenerateCAKeyPair = &cobra.Command{
|
||||||
|
Use: "ca-keypair",
|
||||||
|
Short: "Generate CA key pair",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := generateCAKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateCAName, "name", "n", "", "Set custom CA name")
|
||||||
|
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateCAPKCS12Password, "p12-password", "p", "", "Set custom PKCS12 password")
|
||||||
|
commandGenerateCAKeyPair.Flags().StringVarP(&flagGenerateOutput, "output", "o", ".", "Set output directory")
|
||||||
|
commandGenerate.AddCommand(commandGenerateCAKeyPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCAKeyPair() error {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
spkiASN1, err := x509.MarshalPKIXPublicKey(privateKey.Public())
|
||||||
|
var spki struct {
|
||||||
|
Algorithm pkix.AlgorithmIdentifier
|
||||||
|
SubjectPublicKey asn1.BitString
|
||||||
|
}
|
||||||
|
_, err = asn1.Unmarshal(spkiASN1, &spki)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
skid := sha1.Sum(spki.SubjectPublicKey.Bytes)
|
||||||
|
var caName string
|
||||||
|
if flagGenerateCAName != "" {
|
||||||
|
caName = flagGenerateCAName
|
||||||
|
} else {
|
||||||
|
caName = "sing-box Generated CA " + strings.ToUpper(hex.EncodeToString(skid[:4]))
|
||||||
|
}
|
||||||
|
caTpl := &x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{caName},
|
||||||
|
CommonName: caName,
|
||||||
|
},
|
||||||
|
SubjectKeyId: skid[:],
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
KeyUsage: x509.KeyUsageCertSign,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
MaxPathLenZero: true,
|
||||||
|
}
|
||||||
|
publicDer, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, privateKey.Public(), privateKey)
|
||||||
|
var caPassword string
|
||||||
|
if flagGenerateCAPKCS12Password != "" {
|
||||||
|
caPassword = flagGenerateCAPKCS12Password
|
||||||
|
} else {
|
||||||
|
caPassword = strings.ToUpper(hex.EncodeToString(skid[:4]))
|
||||||
|
}
|
||||||
|
caTpl.Raw = publicDer
|
||||||
|
p12Bytes, err := pkcs12.Modern.Encode(privateKey, caTpl, nil, caPassword)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
privateDer, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".pem"), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
|
||||||
|
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".private.pem"), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDer}), 0o644)
|
||||||
|
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".crt"), publicDer, 0o644)
|
||||||
|
os.WriteFile(filepath.Join(flagGenerateOutput, caName+".p12"), p12Bytes, 0o644)
|
||||||
|
var tlsDecryptionOptions option.TLSDecryptionOptions
|
||||||
|
tlsDecryptionOptions.Enabled = true
|
||||||
|
tlsDecryptionOptions.KeyPair = base64.StdEncoding.EncodeToString(p12Bytes)
|
||||||
|
tlsDecryptionOptions.KeyPairPassword = caPassword
|
||||||
|
var certificateOptions option.CertificateOptions
|
||||||
|
certificateOptions.TLSDecryption = &tlsDecryptionOptions
|
||||||
|
encoder := json.NewEncoder(os.Stdout)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
return encoder.Encode(certificateOptions)
|
||||||
|
}
|
||||||
@@ -6,10 +6,8 @@ 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-box/route/rule"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -71,7 +69,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options))
|
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
@@ -80,18 +78,3 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
|
||||||
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
|
||||||
len(rule.DefaultInterfaceAddress) > 0
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion3
|
|
||||||
}
|
|
||||||
if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion2
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/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"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
var rules []option.HeadlessRule
|
var rules []option.HeadlessRule
|
||||||
switch flagRuleSetConvertType {
|
switch flagRuleSetConvertType {
|
||||||
case "adguard":
|
case "adguard":
|
||||||
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
rules, err = adguard.Convert(reader)
|
||||||
case "":
|
case "":
|
||||||
return E.New("source type is required")
|
return E.New("source type is required")
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -6,10 +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"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.AdGuardDomain) > 0
|
|
||||||
}) {
|
|
||||||
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if hasRule(rule.LogicalOptions.Rules, cond) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
@@ -57,14 +56,6 @@ 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")
|
||||||
}
|
}
|
||||||
if flagRuleSetMatchFormat == "" {
|
|
||||||
switch filepath.Ext(sourcePath) {
|
|
||||||
case ".json":
|
|
||||||
flagRuleSetMatchFormat = C.RuleSetFormatSource
|
|
||||||
case ".srs":
|
|
||||||
flagRuleSetMatchFormat = C.RuleSetFormatBinary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ruleSet option.PlainRuleSetCompat
|
var ruleSet option.PlainRuleSetCompat
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
case C.RuleSetFormatSource:
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,36 +12,5 @@ var commandTools = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
|
|
||||||
mainCommand.AddCommand(commandTools)
|
mainCommand.AddCommand(commandTools)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPreStartedClient() (*box.Box, error) {
|
|
||||||
options, err := readConfigAndMerge()
|
|
||||||
if err != nil {
|
|
||||||
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create service")
|
|
||||||
}
|
|
||||||
err = instance.PreStart()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "start service")
|
|
||||||
}
|
|
||||||
return instance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
|
|
||||||
if outboundTag == "" {
|
|
||||||
return instance.Outbound().Default(), nil
|
|
||||||
} else {
|
|
||||||
outbound, loaded := instance.Outbound().Outbound(outboundTag)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound not found: ", outboundTag)
|
|
||||||
}
|
|
||||||
return outbound, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/task"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandConnectFlagNetwork string
|
|
||||||
|
|
||||||
var commandConnect = &cobra.Command{
|
|
||||||
Use: "connect <address>",
|
|
||||||
Short: "Connect to an address",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := connect(args[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
|
|
||||||
commandTools.AddCommand(commandConnect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect(address string) error {
|
|
||||||
switch N.NetworkName(commandConnectFlagNetwork) {
|
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
|
||||||
default:
|
|
||||||
return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)
|
|
||||||
}
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "connect to server")
|
|
||||||
}
|
|
||||||
var group task.Group
|
|
||||||
group.Append("upload", func(ctx context.Context) error {
|
|
||||||
return common.Error(bufio.Copy(conn, os.Stdin))
|
|
||||||
})
|
|
||||||
group.Append("download", func(ctx context.Context) error {
|
|
||||||
return common.Error(bufio.Copy(os.Stdout, conn))
|
|
||||||
})
|
|
||||||
group.Cleanup(func() {
|
|
||||||
conn.Close()
|
|
||||||
})
|
|
||||||
err = group.Run(context.Background())
|
|
||||||
if E.IsClosed(err) {
|
|
||||||
log.Info(err)
|
|
||||||
} else {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandFetch = &cobra.Command{
|
|
||||||
Use: "fetch",
|
|
||||||
Short: "Fetch an URL",
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := fetch(args)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandTools.AddCommand(commandFetch)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
httpClient *http.Client
|
|
||||||
http3Client *http.Client
|
|
||||||
)
|
|
||||||
|
|
||||||
func fetch(args []string) error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
if C.WithQUIC {
|
|
||||||
err = initializeHTTP3Client(instance)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer http3Client.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
for _, urlString := range args {
|
|
||||||
var parsedURL *url.URL
|
|
||||||
parsedURL, err = url.Parse(urlString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch parsedURL.Scheme {
|
|
||||||
case "":
|
|
||||||
parsedURL.Scheme = "http"
|
|
||||||
fallthrough
|
|
||||||
case "http", "https":
|
|
||||||
err = fetchHTTP(httpClient, parsedURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "http3":
|
|
||||||
if !C.WithQUIC {
|
|
||||||
return C.ErrQUICNotIncluded
|
|
||||||
}
|
|
||||||
parsedURL.Scheme = "https"
|
|
||||||
err = fetchHTTP(http3Client, parsedURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return E.New("unsupported scheme: ", parsedURL.Scheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
|
|
||||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
request.Header.Add("User-Agent", "curl/7.88.0")
|
|
||||||
response, err := httpClient.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = bufio.Copy(os.Stdout, response.Body)
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
|
||||||
"github.com/sagernet/quic-go/http3"
|
|
||||||
box "github.com/sagernet/sing-box"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initializeHTTP3Client(instance *box.Box) error {
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
http3Client = &http.Client{
|
|
||||||
Transport: &http3.Transport{
|
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
|
||||||
destination := M.ParseSocksaddr(addr)
|
|
||||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
|
||||||
if dErr != nil {
|
|
||||||
return nil, dErr
|
|
||||||
}
|
|
||||||
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//go:build !with_quic
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
box "github.com/sagernet/sing-box"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initializeHTTP3Client(instance *box.Box) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchHTTP3(parsedURL *url.URL) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
108
cmd/sing-box/cmd_tools_install_ca.go
Normal file
108
cmd/sing-box/cmd_tools_install_ca.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/shell"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandInstallCACertificate = &cobra.Command{
|
||||||
|
Use: "install-ca <path to certificate>",
|
||||||
|
Short: "Install CA certificate to system",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := installCACertificate(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandTools.AddCommand(commandInstallCACertificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installCACertificate(path string) error {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
return shell.Exec("powershell", "-Command", "Import-Certificate -FilePath \""+path+"\" -CertStoreLocation Cert:\\LocalMachine\\Root").Attach().Run()
|
||||||
|
case "darwin":
|
||||||
|
return shell.Exec("sudo", "security", "add-trusted-cert", "-d", "-r", "trustRoot", "-k", "/Library/Keychains/System.keychain", path).Attach().Run()
|
||||||
|
case "linux":
|
||||||
|
updateCertPath, updateCertPathNotFoundErr := exec.LookPath("update-ca-certificates")
|
||||||
|
if updateCertPathNotFoundErr == nil {
|
||||||
|
publicDer, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll("/usr/local/share/ca-certificates", 0o755)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
log.Info("Try running with sudo")
|
||||||
|
return shell.Exec("sudo", os.Args...).Attach().Run()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileName := filepath.Base(updateCertPath)
|
||||||
|
if !strings.HasSuffix(fileName, ".crt") {
|
||||||
|
fileName = fileName + ".crt"
|
||||||
|
}
|
||||||
|
filePath, _ := filepath.Abs(filepath.Join("/usr/local/share/ca-certificates", fileName))
|
||||||
|
err = os.WriteFile(filePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
log.Info("Try running with sudo")
|
||||||
|
return shell.Exec("sudo", os.Args...).Attach().Run()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("certificate written to " + filePath + "\n")
|
||||||
|
err = shell.Exec(updateCertPath).Attach().Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("certificate installed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
updateTrustPath, updateTrustPathNotFoundErr := exec.LookPath("update-ca-trust")
|
||||||
|
if updateTrustPathNotFoundErr == nil {
|
||||||
|
publicDer, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileName := filepath.Base(updateTrustPath)
|
||||||
|
fileExt := filepath.Ext(path)
|
||||||
|
if fileExt != "" {
|
||||||
|
fileName = fileName[:len(fileName)-len(fileExt)]
|
||||||
|
}
|
||||||
|
filePath, _ := filepath.Abs(filepath.Join("/etc/pki/ca-trust/source/anchors/", fileName+".pem"))
|
||||||
|
err = os.WriteFile(filePath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: publicDer}), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
log.Info("Try running with sudo")
|
||||||
|
return shell.Exec("sudo", os.Args...).Attach().Run()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("certificate written to " + filePath + "\n")
|
||||||
|
err = shell.Exec(updateTrustPath, "extract").Attach().Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("certificate installed")
|
||||||
|
}
|
||||||
|
return E.New("update-ca-certificates or update-ca-trust not found")
|
||||||
|
default:
|
||||||
|
return E.New("unsupported operating system: ", runtime.GOOS)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
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"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -39,20 +40,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func syncTime() error {
|
func syncTime() error {
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
||||||
if serverAddress.Port == 0 {
|
if serverAddress.Port == 0 {
|
||||||
serverAddress.Port = 123
|
serverAddress.Port = 123
|
||||||
}
|
}
|
||||||
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
|
response, err := ntp.Exchange(context.Background(), N.SystemDialer, serverAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,10 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
|
|||||||
isImportant bool
|
isImportant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
|
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
var (
|
var (
|
||||||
ruleLines []agdguardRuleLine
|
ruleLines []agdguardRuleLine
|
||||||
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
|
|||||||
parseLine:
|
parseLine:
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ruleLine := scanner.Text()
|
ruleLine := scanner.Text()
|
||||||
if ruleLine == "" {
|
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originRuleLine := ruleLine
|
originRuleLine := ruleLine
|
||||||
@@ -96,7 +92,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if !ignored {
|
if !ignored {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||||
continue parseLine
|
continue parseLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,35 +120,27 @@ parseLine:
|
|||||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||||
if ignoreIPCIDRRegexp(ruleLine) {
|
if ignoreIPCIDRRegexp(ruleLine) {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isRegexp = true
|
isRegexp = true
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(ruleLine, "://") {
|
if strings.Contains(ruleLine, "://") {
|
||||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||||
isSuffix = true
|
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "/") {
|
if strings.Contains(ruleLine, "/") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
if strings.Contains(ruleLine, "##") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
|
if strings.Contains(ruleLine, "#$#") {
|
||||||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
|
|
||||||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
|
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(ruleLine, "~") {
|
|
||||||
ignoredLines++
|
|
||||||
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var domainCheck string
|
var domainCheck string
|
||||||
@@ -163,7 +151,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if ruleLine == "" {
|
if ruleLine == "" {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||||
@@ -171,13 +159,13 @@ parseLine:
|
|||||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||||
if ipErr == nil {
|
if ipErr == nil {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||||
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||||
}
|
}
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
continue
|
continue
|
||||||
@@ -295,112 +283,10 @@ parseLine:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ignoredLines > 0 {
|
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||||
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
|
||||||
}
|
|
||||||
return []option.HeadlessRule{currentRule}, nil
|
return []option.HeadlessRule{currentRule}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
|
||||||
|
|
||||||
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
|
||||||
if len(rules) != 1 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule := rules[0]
|
|
||||||
var (
|
|
||||||
importantDomain []string
|
|
||||||
importantDomainRegex []string
|
|
||||||
importantExcludeDomain []string
|
|
||||||
importantExcludeDomainRegex []string
|
|
||||||
domain []string
|
|
||||||
domainRegex []string
|
|
||||||
excludeDomain []string
|
|
||||||
excludeDomainRegex []string
|
|
||||||
)
|
|
||||||
parse:
|
|
||||||
for {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
|
||||||
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule = rule.LogicalOptions.Rules[1]
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
domain = rule.DefaultOptions.AdGuardDomain
|
|
||||||
domainRegex = rule.DefaultOptions.DomainRegex
|
|
||||||
if len(domain)+len(domainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
break parse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var output bytes.Buffer
|
|
||||||
for _, ruleLine := range importantDomain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantDomainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
return output.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||||
ruleLine = ruleLine[12:]
|
ruleLine = ruleLine[12:]
|
||||||
@@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
|||||||
ruleLine = ruleLine[13:]
|
ruleLine = ruleLine[13:]
|
||||||
} else if strings.HasPrefix(ruleLine, "^") {
|
} else if strings.HasPrefix(ruleLine, "^") {
|
||||||
ruleLine = ruleLine[1:]
|
ruleLine = ruleLine[1:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
||||||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
return parseErr == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
||||||
@@ -454,5 +342,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
|
|||||||
for len(ruleParts) < 4 {
|
for len(ruleParts) < 4 {
|
||||||
ruleParts = append(ruleParts, 0)
|
ruleParts = append(ruleParts, 0)
|
||||||
}
|
}
|
||||||
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
|
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
|
||||||
}
|
}
|
||||||
@@ -7,15 +7,13 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
func TestConverter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ruleString := `||sagernet.org^$important
|
rules, err := Convert(strings.NewReader(`
|
||||||
@@|sing-box.sagernet.org^$important
|
|
||||||
||example.org^
|
||example.org^
|
||||||
|example.com^
|
|example.com^
|
||||||
example.net^
|
example.net^
|
||||||
@@ -23,9 +21,10 @@ example.net^
|
|||||||
||example.edu.tw^
|
||example.edu.tw^
|
||||||
|example.gov
|
|example.gov
|
||||||
example.arpa
|
example.arpa
|
||||||
@@|sagernet.example.org^
|
@@|sagernet.example.org|
|
||||||
`
|
||sagernet.org^$important
|
||||||
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
@@|sing-box.sagernet.org^$important
|
||||||
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -76,18 +75,15 @@ example.arpa
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}), domain)
|
}), domain)
|
||||||
}
|
}
|
||||||
ruleFromOptions, err := FromOptions(rules)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, ruleString, string(ruleFromOptions))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
::1 localhost #[IPv6]
|
::1 localhost #[IPv6]
|
||||||
0.0.0.0 google.com
|
0.0.0.0 google.com
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimpleHosts(t *testing.T) {
|
func TestSimpleHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
example.com
|
example.com
|
||||||
www.example.org
|
www.example.org
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
//go:build go1.25 && badlinkname
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync/atomic"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RawConn struct {
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
|
|
||||||
IsClient *bool
|
|
||||||
IsHandshakeComplete *atomic.Bool
|
|
||||||
Vers *uint16
|
|
||||||
CipherSuite *uint16
|
|
||||||
|
|
||||||
RawInput *bytes.Buffer
|
|
||||||
Input *bytes.Reader
|
|
||||||
Hand *bytes.Buffer
|
|
||||||
|
|
||||||
CloseNotifySent *bool
|
|
||||||
CloseNotifyErr *error
|
|
||||||
|
|
||||||
In *RawHalfConn
|
|
||||||
Out *RawHalfConn
|
|
||||||
|
|
||||||
BytesSent *int64
|
|
||||||
PacketsSent *int64
|
|
||||||
|
|
||||||
ActiveCall *atomic.Int32
|
|
||||||
Tmp *[16]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) {
|
|
||||||
var (
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
loaded bool
|
|
||||||
)
|
|
||||||
for _, tlsCreator := range methodRegistry {
|
|
||||||
pointer, methods, loaded = tlsCreator(rawTLSConn)
|
|
||||||
if loaded {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !loaded {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &RawConn{
|
|
||||||
pointer: pointer,
|
|
||||||
methods: methods,
|
|
||||||
}
|
|
||||||
|
|
||||||
rawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn))
|
|
||||||
|
|
||||||
rawIsClient := rawConn.FieldByName("isClient")
|
|
||||||
if !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool {
|
|
||||||
return nil, E.New("invalid Conn.isClient")
|
|
||||||
}
|
|
||||||
conn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
|
|
||||||
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.isHandshakeComplete")
|
|
||||||
}
|
|
||||||
conn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawVers := rawConn.FieldByName("vers")
|
|
||||||
if !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("invalid Conn.vers")
|
|
||||||
}
|
|
||||||
conn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCipherSuite := rawConn.FieldByName("cipherSuite")
|
|
||||||
if !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("invalid Conn.cipherSuite")
|
|
||||||
}
|
|
||||||
conn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawRawInput := rawConn.FieldByName("rawInput")
|
|
||||||
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.rawInput")
|
|
||||||
}
|
|
||||||
conn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawInput := rawConn.FieldByName("input")
|
|
||||||
if !rawInput.IsValid() || rawInput.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.input")
|
|
||||||
}
|
|
||||||
conn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawHand := rawConn.FieldByName("hand")
|
|
||||||
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.hand")
|
|
||||||
}
|
|
||||||
conn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
|
|
||||||
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
|
|
||||||
return nil, E.New("invalid Conn.closeNotifySent")
|
|
||||||
}
|
|
||||||
conn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCloseNotifyErr := rawConn.FieldByName("closeNotifyErr")
|
|
||||||
if !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("invalid Conn.closeNotifyErr")
|
|
||||||
}
|
|
||||||
conn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIn := rawConn.FieldByName("in")
|
|
||||||
if !rawIn.IsValid() || rawIn.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.in")
|
|
||||||
}
|
|
||||||
halfIn, err := NewRawHalfConn(rawIn, methods)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "invalid Conn.in")
|
|
||||||
}
|
|
||||||
conn.In = halfIn
|
|
||||||
|
|
||||||
rawOut := rawConn.FieldByName("out")
|
|
||||||
if !rawOut.IsValid() || rawOut.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.out")
|
|
||||||
}
|
|
||||||
halfOut, err := NewRawHalfConn(rawOut, methods)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "invalid Conn.out")
|
|
||||||
}
|
|
||||||
conn.Out = halfOut
|
|
||||||
|
|
||||||
rawBytesSent := rawConn.FieldByName("bytesSent")
|
|
||||||
if !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 {
|
|
||||||
return nil, E.New("invalid Conn.bytesSent")
|
|
||||||
}
|
|
||||||
conn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawPacketsSent := rawConn.FieldByName("packetsSent")
|
|
||||||
if !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 {
|
|
||||||
return nil, E.New("invalid Conn.packetsSent")
|
|
||||||
}
|
|
||||||
conn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawActiveCall := rawConn.FieldByName("activeCall")
|
|
||||||
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.activeCall")
|
|
||||||
}
|
|
||||||
conn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawTmp := rawConn.FieldByName("tmp")
|
|
||||||
if !rawTmp.IsValid() || rawTmp.Kind() != reflect.Array || rawTmp.Len() != 16 || rawTmp.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("invalid Conn.tmp")
|
|
||||||
}
|
|
||||||
conn.Tmp = (*[16]byte)(unsafe.Pointer(rawTmp.UnsafeAddr()))
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) ReadRecord() error {
|
|
||||||
return c.methods.readRecord(c.pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) HandlePostHandshakeMessage() error {
|
|
||||||
return c.methods.handlePostHandshakeMessage(c.pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) {
|
|
||||||
return c.methods.writeRecordLocked(c.pointer, typ, data)
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
//go:build go1.25 && badlinkname
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RawHalfConn struct {
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
*sync.Mutex
|
|
||||||
Err *error
|
|
||||||
Version *uint16
|
|
||||||
Cipher *any
|
|
||||||
Seq *[8]byte
|
|
||||||
ScratchBuf *[13]byte
|
|
||||||
TrafficSecret *[]byte
|
|
||||||
Mac *hash.Hash
|
|
||||||
RawKey *[]byte
|
|
||||||
RawIV *[]byte
|
|
||||||
RawMac *[]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) {
|
|
||||||
halfConn := &RawHalfConn{
|
|
||||||
pointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()),
|
|
||||||
methods: methods,
|
|
||||||
}
|
|
||||||
|
|
||||||
rawMutex := rawHalfConn.FieldByName("Mutex")
|
|
||||||
if !rawMutex.IsValid() || rawMutex.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.Mutex")
|
|
||||||
}
|
|
||||||
halfConn.Mutex = (*sync.Mutex)(unsafe.Pointer(rawMutex.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawErr := rawHalfConn.FieldByName("err")
|
|
||||||
if !rawErr.IsValid() || rawErr.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.err")
|
|
||||||
}
|
|
||||||
halfConn.Err = (*error)(unsafe.Pointer(rawErr.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawVersion := rawHalfConn.FieldByName("version")
|
|
||||||
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.version")
|
|
||||||
}
|
|
||||||
halfConn.Version = (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCipher := rawHalfConn.FieldByName("cipher")
|
|
||||||
if !rawCipher.IsValid() || rawCipher.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.cipher")
|
|
||||||
}
|
|
||||||
halfConn.Cipher = (*any)(unsafe.Pointer(rawCipher.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawSeq := rawHalfConn.FieldByName("seq")
|
|
||||||
if !rawSeq.IsValid() || rawSeq.Kind() != reflect.Array || rawSeq.Len() != 8 || rawSeq.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.seq")
|
|
||||||
}
|
|
||||||
halfConn.Seq = (*[8]byte)(unsafe.Pointer(rawSeq.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawScratchBuf := rawHalfConn.FieldByName("scratchBuf")
|
|
||||||
if !rawScratchBuf.IsValid() || rawScratchBuf.Kind() != reflect.Array || rawScratchBuf.Len() != 13 || rawScratchBuf.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.scratchBuf")
|
|
||||||
}
|
|
||||||
halfConn.ScratchBuf = (*[13]byte)(unsafe.Pointer(rawScratchBuf.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawTrafficSecret := rawHalfConn.FieldByName("trafficSecret")
|
|
||||||
if !rawTrafficSecret.IsValid() || rawTrafficSecret.Kind() != reflect.Slice || rawTrafficSecret.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.trafficSecret")
|
|
||||||
}
|
|
||||||
halfConn.TrafficSecret = (*[]byte)(unsafe.Pointer(rawTrafficSecret.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawMac := rawHalfConn.FieldByName("mac")
|
|
||||||
if !rawMac.IsValid() || rawMac.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.mac")
|
|
||||||
}
|
|
||||||
halfConn.Mac = (*hash.Hash)(unsafe.Pointer(rawMac.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawKey := rawHalfConn.FieldByName("rawKey")
|
|
||||||
if rawKey.IsValid() {
|
|
||||||
if /*!rawKey.IsValid() || */ rawKey.Kind() != reflect.Slice || rawKey.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawKey")
|
|
||||||
}
|
|
||||||
halfConn.RawKey = (*[]byte)(unsafe.Pointer(rawKey.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIV := rawHalfConn.FieldByName("rawIV")
|
|
||||||
if !rawIV.IsValid() || rawIV.Kind() != reflect.Slice || rawIV.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawIV")
|
|
||||||
}
|
|
||||||
halfConn.RawIV = (*[]byte)(unsafe.Pointer(rawIV.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawMAC := rawHalfConn.FieldByName("rawMac")
|
|
||||||
if !rawMAC.IsValid() || rawMAC.Kind() != reflect.Slice || rawMAC.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawMac")
|
|
||||||
}
|
|
||||||
halfConn.RawMac = (*[]byte)(unsafe.Pointer(rawMAC.UnsafeAddr()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return halfConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) Decrypt(record []byte) ([]byte, uint8, error) {
|
|
||||||
return hc.methods.decrypt(hc.pointer, record)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) SetErrorLocked(err error) error {
|
|
||||||
return hc.methods.setErrorLocked(hc.pointer, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) SetTrafficSecret(suite unsafe.Pointer, level int, secret []byte) {
|
|
||||||
hc.methods.setTrafficSecret(hc.pointer, suite, level, secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) ExplicitNonceLen() int {
|
|
||||||
return hc.methods.explicitNonceLen(hc.pointer)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,18 @@
|
|||||||
//go:build go1.25 && badlinkname
|
//go:build go1.21 && !without_badtls
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/tls"
|
"github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
@@ -12,21 +21,63 @@ var _ N.ReadWaiter = (*ReadWaitConn)(nil)
|
|||||||
|
|
||||||
type ReadWaitConn struct {
|
type ReadWaitConn struct {
|
||||||
tls.Conn
|
tls.Conn
|
||||||
rawConn *RawConn
|
halfAccess *sync.Mutex
|
||||||
readWaitOptions N.ReadWaitOptions
|
rawInput *bytes.Buffer
|
||||||
|
input *bytes.Reader
|
||||||
|
hand *bytes.Buffer
|
||||||
|
readWaitOptions N.ReadWaitOptions
|
||||||
|
tlsReadRecord func() error
|
||||||
|
tlsHandlePostHandshakeMessage func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
||||||
if _, isReadWaitConn := conn.(N.ReadWaiter); isReadWaitConn {
|
var (
|
||||||
return conn, nil
|
loaded bool
|
||||||
|
tlsReadRecord func() error
|
||||||
|
tlsHandlePostHandshakeMessage func() error
|
||||||
|
)
|
||||||
|
for _, tlsCreator := range tlsRegistry {
|
||||||
|
loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn)
|
||||||
|
if loaded {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rawConn, err := NewRawConn(conn)
|
if !loaded {
|
||||||
if err != nil {
|
return nil, os.ErrInvalid
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||||
|
rawHalfConn := rawConn.FieldByName("in")
|
||||||
|
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid half conn")
|
||||||
|
}
|
||||||
|
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
|
||||||
|
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid half mutex")
|
||||||
|
}
|
||||||
|
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
|
||||||
|
rawRawInput := rawConn.FieldByName("rawInput")
|
||||||
|
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid raw input")
|
||||||
|
}
|
||||||
|
rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
|
||||||
|
rawInput0 := rawConn.FieldByName("input")
|
||||||
|
if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid input")
|
||||||
|
}
|
||||||
|
input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr()))
|
||||||
|
rawHand := rawConn.FieldByName("hand")
|
||||||
|
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid hand")
|
||||||
|
}
|
||||||
|
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
||||||
return &ReadWaitConn{
|
return &ReadWaitConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
rawConn: rawConn,
|
halfAccess: halfAccess,
|
||||||
|
rawInput: rawInput,
|
||||||
|
input: input,
|
||||||
|
hand: hand,
|
||||||
|
tlsReadRecord: tlsReadRecord,
|
||||||
|
tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,36 +87,36 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||||
//err = c.HandshakeContext(context.Background())
|
err = c.HandshakeContext(context.Background())
|
||||||
//if err != nil {
|
if err != nil {
|
||||||
// return
|
return
|
||||||
//}
|
}
|
||||||
c.rawConn.In.Lock()
|
c.halfAccess.Lock()
|
||||||
defer c.rawConn.In.Unlock()
|
defer c.halfAccess.Unlock()
|
||||||
for c.rawConn.Input.Len() == 0 {
|
for c.input.Len() == 0 {
|
||||||
err = c.rawConn.ReadRecord()
|
err = c.tlsReadRecord()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for c.rawConn.Hand.Len() > 0 {
|
for c.hand.Len() > 0 {
|
||||||
err = c.rawConn.HandlePostHandshakeMessage()
|
err = c.tlsHandlePostHandshakeMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer = c.readWaitOptions.NewBuffer()
|
buffer = c.readWaitOptions.NewBuffer()
|
||||||
n, err := c.rawConn.Input.Read(buffer.FreeBytes())
|
n, err := c.input.Read(buffer.FreeBytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buffer.Release()
|
buffer.Release()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buffer.Truncate(n)
|
buffer.Truncate(n)
|
||||||
|
|
||||||
if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 &&
|
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
|
||||||
// recordType(c.RawInput.Bytes()[0]) == recordTypeAlert {
|
// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
|
||||||
c.rawConn.RawInput.Bytes()[0] == 21 {
|
c.rawInput.Bytes()[0] == 21 {
|
||||||
_ = c.rawConn.ReadRecord()
|
_ = c.tlsReadRecord()
|
||||||
// return n, err // will be io.EOF on closeNotify
|
// return n, err // will be io.EOF on closeNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +128,24 @@ func (c *ReadWaitConn) Upstream() any {
|
|||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ReadWaitConn) ReaderReplaceable() bool {
|
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
||||||
return true
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||||
|
tlsConn, loaded := conn.(*tls.STDConn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, func() error {
|
||||||
|
return stdTLSReadRecord(tlsConn)
|
||||||
|
}, func() error {
|
||||||
|
return stdTLSHandlePostHandshakeMessage(tlsConn)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
||||||
|
func stdTLSReadRecord(c *tls.STDConn) error
|
||||||
|
|
||||||
|
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !go1.25 || !badlinkname
|
//go:build !go1.21 || without_badtls
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
|
|||||||
31
common/badtls/read_wait_utls.go
Normal file
31
common/badtls/read_wait_utls.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//go:build go1.21 && !without_badtls && with_utls
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/utls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||||
|
tlsConn, loaded := common.Cast[*tls.UConn](conn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, func() error {
|
||||||
|
return utlsReadRecord(tlsConn.Conn)
|
||||||
|
}, func() error {
|
||||||
|
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
|
||||||
|
func utlsReadRecord(c *tls.Conn) error
|
||||||
|
|
||||||
|
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
//go:build go1.25 && badlinkname
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Methods struct {
|
|
||||||
readRecord func(c unsafe.Pointer) error
|
|
||||||
handlePostHandshakeMessage func(c unsafe.Pointer) error
|
|
||||||
writeRecordLocked func(c unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
setErrorLocked func(hc unsafe.Pointer, err error) error
|
|
||||||
decrypt func(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
setTrafficSecret func(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
explicitNonceLen func(hc unsafe.Pointer) int
|
|
||||||
}
|
|
||||||
|
|
||||||
var methodRegistry []func(conn net.Conn) (unsafe.Pointer, *Methods, bool)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {
|
|
||||||
tlsConn, loaded := conn.(*tls.Conn)
|
|
||||||
if !loaded {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return unsafe.Pointer(tlsConn), &Methods{
|
|
||||||
readRecord: stdTLSReadRecord,
|
|
||||||
handlePostHandshakeMessage: stdTLSHandlePostHandshakeMessage,
|
|
||||||
writeRecordLocked: stdWriteRecordLocked,
|
|
||||||
|
|
||||||
setErrorLocked: stdSetErrorLocked,
|
|
||||||
decrypt: stdDecrypt,
|
|
||||||
setTrafficSecret: stdSetTrafficSecret,
|
|
||||||
explicitNonceLen: stdExplicitNonceLen,
|
|
||||||
}, true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
|
||||||
func stdTLSReadRecord(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func stdTLSHandlePostHandshakeMessage(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname stdWriteRecordLocked crypto/tls.(*Conn).writeRecordLocked
|
|
||||||
func stdWriteRecordLocked(c unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
//go:linkname stdSetErrorLocked crypto/tls.(*halfConn).setErrorLocked
|
|
||||||
func stdSetErrorLocked(hc unsafe.Pointer, err error) error
|
|
||||||
|
|
||||||
//go:linkname stdDecrypt crypto/tls.(*halfConn).decrypt
|
|
||||||
func stdDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
|
|
||||||
//go:linkname stdSetTrafficSecret crypto/tls.(*halfConn).setTrafficSecret
|
|
||||||
func stdSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
|
|
||||||
//go:linkname stdExplicitNonceLen crypto/tls.(*halfConn).explicitNonceLen
|
|
||||||
func stdExplicitNonceLen(hc unsafe.Pointer) int
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
//go:build go1.25 && badlinkname
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/metacubex/utls"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {
|
|
||||||
var pointer unsafe.Pointer
|
|
||||||
if uConn, loaded := N.CastReader[*tls.Conn](conn); loaded {
|
|
||||||
pointer = unsafe.Pointer(uConn)
|
|
||||||
} else if uConn, loaded := N.CastReader[*tls.UConn](conn); loaded {
|
|
||||||
pointer = unsafe.Pointer(uConn.Conn)
|
|
||||||
} else {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return pointer, &Methods{
|
|
||||||
readRecord: utlsReadRecord,
|
|
||||||
handlePostHandshakeMessage: utlsHandlePostHandshakeMessage,
|
|
||||||
writeRecordLocked: utlsWriteRecordLocked,
|
|
||||||
|
|
||||||
setErrorLocked: utlsSetErrorLocked,
|
|
||||||
decrypt: utlsDecrypt,
|
|
||||||
setTrafficSecret: utlsSetTrafficSecret,
|
|
||||||
explicitNonceLen: utlsExplicitNonceLen,
|
|
||||||
}, true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
|
||||||
func utlsReadRecord(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func utlsHandlePostHandshakeMessage(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname utlsWriteRecordLocked github.com/metacubex/utls.(*Conn).writeRecordLocked
|
|
||||||
func utlsWriteRecordLocked(hc unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
//go:linkname utlsSetErrorLocked github.com/metacubex/utls.(*halfConn).setErrorLocked
|
|
||||||
func utlsSetErrorLocked(hc unsafe.Pointer, err error) error
|
|
||||||
|
|
||||||
//go:linkname utlsDecrypt github.com/metacubex/utls.(*halfConn).decrypt
|
|
||||||
func utlsDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
|
|
||||||
//go:linkname utlsSetTrafficSecret github.com/metacubex/utls.(*halfConn).setTrafficSecret
|
|
||||||
func utlsSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
|
|
||||||
//go:linkname utlsExplicitNonceLen github.com/metacubex/utls.(*halfConn).explicitNonceLen
|
|
||||||
func utlsExplicitNonceLen(hc unsafe.Pointer) int
|
|
||||||
@@ -5,8 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
|
||||||
"golang.org/x/mod/semver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
@@ -18,19 +16,7 @@ type Version struct {
|
|||||||
PreReleaseVersion int
|
PreReleaseVersion int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Version) LessThan(anotherVersion Version) bool {
|
func (v Version) After(anotherVersion Version) bool {
|
||||||
return !v.GreaterThanOrEqual(anotherVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) LessThanOrEqual(anotherVersion Version) bool {
|
|
||||||
return v == anotherVersion || anotherVersion.GreaterThan(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) GreaterThanOrEqual(anotherVersion Version) bool {
|
|
||||||
return v == anotherVersion || v.GreaterThan(anotherVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) GreaterThan(anotherVersion Version) bool {
|
|
||||||
if v.Major > anotherVersion.Major {
|
if v.Major > anotherVersion.Major {
|
||||||
return true
|
return true
|
||||||
} else if v.Major < anotherVersion.Major {
|
} else if v.Major < anotherVersion.Major {
|
||||||
@@ -58,29 +44,19 @@ func (v Version) GreaterThan(anotherVersion Version) bool {
|
|||||||
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
} else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" {
|
||||||
preReleaseIdentifier := parsePreReleaseIdentifier(v.PreReleaseIdentifier)
|
|
||||||
anotherPreReleaseIdentifier := parsePreReleaseIdentifier(anotherVersion.PreReleaseIdentifier)
|
|
||||||
if preReleaseIdentifier < anotherPreReleaseIdentifier {
|
|
||||||
return true
|
return true
|
||||||
} else if preReleaseIdentifier > anotherPreReleaseIdentifier {
|
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" {
|
||||||
|
return false
|
||||||
|
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
|
||||||
|
return true
|
||||||
|
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePreReleaseIdentifier(identifier string) int {
|
|
||||||
if strings.HasPrefix(identifier, "rc") {
|
|
||||||
return 1
|
|
||||||
} else if strings.HasPrefix(identifier, "beta") {
|
|
||||||
return 2
|
|
||||||
} else if strings.HasPrefix(identifier, "alpha") {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) VersionString() string {
|
func (v Version) VersionString() string {
|
||||||
return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
||||||
}
|
}
|
||||||
@@ -107,10 +83,6 @@ func (v Version) BadString() string {
|
|||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValid(versionName string) bool {
|
|
||||||
return semver.IsValid("v" + versionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(versionName string) (version Version) {
|
func Parse(versionName string) (version Version) {
|
||||||
if strings.HasPrefix(versionName, "v") {
|
if strings.HasPrefix(versionName, "v") {
|
||||||
versionName = versionName[1:]
|
versionName = versionName[1:]
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ func TestCompareVersion(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
||||||
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
||||||
require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3-beta1")))
|
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
|
||||||
require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3.0-beta1")))
|
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
|
||||||
require.True(t, Parse("1.3.0-beta1").GreaterThan(Parse("1.3.0-alpha1")))
|
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
|
||||||
require.True(t, Parse("1.3.1").GreaterThan(Parse("1.3.0")))
|
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
|
||||||
require.True(t, Parse("1.4").GreaterThan(Parse("1.3")))
|
require.True(t, Parse("1.4").After(Parse("1.3")))
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user