Compare commits
10 Commits
4a331b89f1
...
v1.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6db9f6941c | ||
|
|
182cd7d0d6 | ||
|
|
6bc7900b27 | ||
|
|
e94db9dfd0 | ||
|
|
f279e70ea5 | ||
|
|
948c0a713f | ||
|
|
0f03da97c6 | ||
|
|
a69d1cd4ad | ||
|
|
551935c819 | ||
|
|
0416696599 |
118
.gitea/workflows/autobuild.yml
Normal file
118
.gitea/workflows/autobuild.yml
Normal file
@@ -0,0 +1,118 @@
|
||||
name: Auto Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable
|
||||
- testing
|
||||
- unstable
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Override embedded version
|
||||
required: false
|
||||
type: string
|
||||
targets:
|
||||
description: Space separated build targets, for example "linux-amd64 windows-amd64"
|
||||
required: false
|
||||
default: all
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build binaries
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout source
|
||||
shell: bash
|
||||
env:
|
||||
CI_SERVER_URL: ${{ gitea.server_url }}
|
||||
CI_REPOSITORY: ${{ gitea.repository }}
|
||||
CI_REF: ${{ gitea.ref }}
|
||||
CI_SHA: ${{ gitea.sha }}
|
||||
CI_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
workspace="${GITHUB_WORKSPACE:-$PWD}"
|
||||
repo_url="${CI_SERVER_URL}/${CI_REPOSITORY}.git"
|
||||
|
||||
rm -rf "$workspace/.git"
|
||||
git init -b ci "$workspace"
|
||||
git -C "$workspace" remote add origin "$repo_url"
|
||||
|
||||
if [[ -n "${CI_TOKEN}" ]]; then
|
||||
auth_header="Authorization: token ${CI_TOKEN}"
|
||||
git -C "$workspace" -c http.extraHeader="$auth_header" fetch --depth=1 origin "${CI_REF}"
|
||||
else
|
||||
git -C "$workspace" fetch --depth=1 origin "${CI_REF}"
|
||||
fi
|
||||
|
||||
git -C "$workspace" checkout --detach FETCH_HEAD
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.25.9
|
||||
|
||||
- name: Prepare Go cache
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$PWD/.gitea-cache/go-build" "$PWD/.gitea-cache/gomod"
|
||||
echo "GOCACHE=$PWD/.gitea-cache/go-build" >> "$GITHUB_ENV"
|
||||
echo "GOMODCACHE=$PWD/.gitea-cache/gomod" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Download Go modules
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
go mod download
|
||||
|
||||
- name: Verify Go modules
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
go mod verify
|
||||
|
||||
- name: Resolve build metadata
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
version_input="${{ inputs.version }}"
|
||||
targets_input="${{ inputs.targets }}"
|
||||
|
||||
if [[ -n "$version_input" ]]; then
|
||||
version="$version_input"
|
||||
else
|
||||
version="$(go run ./cmd/internal/read_tag)"
|
||||
if [[ -z "$version" || "$version" == "unknown" ]]; then
|
||||
version="$(git describe --tags --always 2>/dev/null || git rev-parse --short HEAD)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$targets_input" ]]; then
|
||||
targets_input="all"
|
||||
fi
|
||||
|
||||
echo "VERSION=$version" >> "$GITHUB_ENV"
|
||||
echo "BUILD_TARGETS=$targets_input" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
chmod +x ./building-linux.sh
|
||||
rm -rf dist
|
||||
mkdir -p dist
|
||||
|
||||
VERSION="$VERSION" DIST_DIR="$PWD/dist" ./building-linux.sh ${BUILD_TARGETS}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sing-box-${{ env.VERSION }}
|
||||
path: dist
|
||||
if-no-files-found: error
|
||||
1
.github/CRONET_GO_VERSION
vendored
1
.github/CRONET_GO_VERSION
vendored
@@ -1 +0,0 @@
|
||||
e4926ba205fae5351e3d3eeafff7e7029654424a
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
github: nekohasekai
|
||||
88
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
88
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,88 +0,0 @@
|
||||
name: Bug report
|
||||
description: "Report sing-box bug"
|
||||
body:
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: Operating system type
|
||||
options:
|
||||
- iOS
|
||||
- macOS
|
||||
- Apple tvOS
|
||||
- Android
|
||||
- Windows
|
||||
- Linux
|
||||
- Others
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: System version
|
||||
description: Please provide the operating system version
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Installation type
|
||||
description: Please provide the sing-box installation type
|
||||
options:
|
||||
- Original sing-box Command Line
|
||||
- sing-box for iOS Graphical Client
|
||||
- sing-box for macOS Graphical Client
|
||||
- sing-box for Apple tvOS Graphical Client
|
||||
- sing-box for Android Graphical Client
|
||||
- Third-party graphical clients that advertise themselves as using sing-box (Windows)
|
||||
- Third-party graphical clients that advertise themselves as using sing-box (Android)
|
||||
- Others
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
description: Graphical client version
|
||||
label: If you are using a graphical client, please provide the version of the client.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Version
|
||||
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide a detailed description of the error.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Please provide the steps to reproduce the error, including the configuration files and procedures that can locally (not dependent on the remote server) reproduce the error using the original command line program of sing-box.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Logs
|
||||
description: |-
|
||||
In addition, if you encounter a crash with the graphical client, please also provide crash logs.
|
||||
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
|
||||
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
||||
render: shell
|
||||
- type: checkboxes
|
||||
id: supporter
|
||||
attributes:
|
||||
label: Supporter
|
||||
options:
|
||||
- label: I am a [sponsor](https://github.com/sponsors/nekohasekai/)
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Integrity requirements
|
||||
description: |-
|
||||
Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.
|
||||
Sing-box is not a project aimed to please users who can't make any meaningful contributions and gain unethical influence. If you deceive here to deliberately waste the time of the developers, you will be permanently blocked.
|
||||
options:
|
||||
- label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
|
||||
required: true
|
||||
- label: I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.
|
||||
required: true
|
||||
- label: I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.
|
||||
required: true
|
||||
- label: I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.
|
||||
required: true
|
||||
88
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
88
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
@@ -1,88 +0,0 @@
|
||||
name: 错误反馈
|
||||
description: "提交 sing-box 漏洞"
|
||||
body:
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: 操作系统
|
||||
description: 请提供操作系统类型
|
||||
options:
|
||||
- iOS
|
||||
- macOS
|
||||
- Apple tvOS
|
||||
- Android
|
||||
- Windows
|
||||
- Linux
|
||||
- 其他
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: 系统版本
|
||||
description: 请提供操作系统版本
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: 安装类型
|
||||
description: 请提供该 sing-box 安装类型
|
||||
options:
|
||||
- sing-box 原始命令行程序
|
||||
- sing-box for iOS 图形客户端程序
|
||||
- sing-box for macOS 图形客户端程序
|
||||
- sing-box for Apple tvOS 图形客户端程序
|
||||
- sing-box for Android 图形客户端程序
|
||||
- 宣传使用 sing-box 的第三方图形客户端程序 (Windows)
|
||||
- 宣传使用 sing-box 的第三方图形客户端程序 (Android)
|
||||
- 其他
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
description: 图形客户端版本
|
||||
label: 如果您使用图形客户端程序,请提供该程序版本。
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 版本
|
||||
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 描述
|
||||
description: 请提供错误的详细描述。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 重现方式
|
||||
description: 请提供重现错误的步骤,必须包括可以在本地(不依赖与远程服务器)使用 sing-box 原始命令行程序重现错误的配置文件与流程。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 日志
|
||||
description: |-
|
||||
此外,如果您遭遇图形界面应用程序崩溃,请附加提供崩溃日志。
|
||||
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
|
||||
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
||||
render: shell
|
||||
- type: checkboxes
|
||||
id: supporter
|
||||
attributes:
|
||||
label: 支持我们
|
||||
options:
|
||||
- label: 我已经 [赞助](https://github.com/sponsors/nekohasekai/)
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 完整性要求
|
||||
description: |-
|
||||
请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
|
||||
sing-box 不是讨好无法作出任何意义上的贡献的最终用户并获取非道德影响力的项目,如果您在此处欺骗以故意浪费开发者的时间,您将被永久封锁。
|
||||
options:
|
||||
- label: 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
|
||||
required: true
|
||||
- label: 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
|
||||
required: true
|
||||
- label: 我保证提供了可用于重现我报告的错误的最简配置,而不是依赖远程服务器、TUN、图形界面客户端或者其他闭源软件。
|
||||
required: true
|
||||
- label: 我保证提供了完整的配置文件与日志,而不是出于对自身智力的自信而仅提供了部分认为有用的部分。
|
||||
required: true
|
||||
81
.github/build_alpine_apk.sh
vendored
81
.github/build_alpine_apk.sh
vendored
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
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)
|
||||
trap 'rm -rf "$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 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"
|
||||
80
.github/build_openwrt_apk.sh
vendored
80
.github/build_openwrt_apk.sh
vendored
@@ -1,80 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
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)
|
||||
trap 'rm -rf "$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 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"
|
||||
28
.github/renovate.json
vendored
28
.github/renovate.json
vendored
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"commitMessagePrefix": "[dependencies]",
|
||||
"extends": [
|
||||
"config:base",
|
||||
":disableRateLimiting"
|
||||
],
|
||||
"baseBranches": [
|
||||
"unstable"
|
||||
],
|
||||
"golang": {
|
||||
"enabled": false
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"groupName": "github-actions"
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"dockerfile"
|
||||
],
|
||||
"groupName": "Dockerfile"
|
||||
}
|
||||
]
|
||||
}
|
||||
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
|
||||
14
.github/update_clients.sh
vendored
14
.github/update_clients.sh
vendored
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PROJECTS=$(dirname "$0")/../..
|
||||
|
||||
function updateClient() {
|
||||
pushd clients/$1
|
||||
git fetch
|
||||
git reset FETCH_HEAD --hard
|
||||
popd
|
||||
git add clients/$1
|
||||
}
|
||||
|
||||
updateClient "apple"
|
||||
updateClient "android"
|
||||
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"
|
||||
5
.github/update_dependencies.sh
vendored
5
.github/update_dependencies.sh
vendored
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PROJECTS=$(dirname "$0")/../..
|
||||
go get -x github.com/sagernet/$1@$(git -C $PROJECTS/$1 rev-parse HEAD)
|
||||
go mod tidy
|
||||
1019
.github/workflows/build.yml
vendored
1019
.github/workflows/build.yml
vendored
File diff suppressed because it is too large
Load Diff
295
.github/workflows/docker.yml
vendored
295
.github/workflows/docker.yml
vendored
@@ -1,295 +0,0 @@
|
||||
name: Publish Docker Images
|
||||
|
||||
on:
|
||||
#push:
|
||||
# branches:
|
||||
# - stable
|
||||
# - testing
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The tag version you want to build"
|
||||
|
||||
env:
|
||||
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
||||
|
||||
jobs:
|
||||
build_binary:
|
||||
name: Build binary
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
# Naive-enabled builds (musl)
|
||||
- { arch: amd64, naive: true, docker_platform: "linux/amd64" }
|
||||
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
|
||||
- { arch: "386", naive: true, docker_platform: "linux/386" }
|
||||
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
|
||||
- { arch: mipsle, gomips: softfloat, naive: true, docker_platform: "linux/mipsle" }
|
||||
- { arch: riscv64, naive: true, docker_platform: "linux/riscv64" }
|
||||
- { arch: loong64, naive: true, docker_platform: "linux/loong64" }
|
||||
# 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:
|
||||
- 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:
|
||||
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:
|
||||
ref: ${{ steps.ref.outputs.ref }}
|
||||
fetch-depth: 0
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
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
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
context: .
|
||||
file: Dockerfile.binary
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ matrix.base_image || 'alpine' }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
merge:
|
||||
if: github.event_name != 'push'
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build_docker
|
||||
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:
|
||||
ref: ${{ steps.ref.outputs.ref }}
|
||||
fetch-depth: 0
|
||||
- name: Detect track
|
||||
run: bash .github/detect_track.sh
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create manifest list and push
|
||||
if: github.event_name != 'push'
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}" \
|
||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||
- name: Inspect image
|
||||
if: github.event_name != 'push'
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||
40
.github/workflows/lint.yml
vendored
40
.github/workflows/lint.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- oldstable
|
||||
- stable
|
||||
- testing
|
||||
- unstable
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/lint.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- oldstable
|
||||
- stable
|
||||
- testing
|
||||
- unstable
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=30m
|
||||
install-mode: binary
|
||||
verify: false
|
||||
243
.github/workflows/linux.yml
vendored
243
.github/workflows/linux.yml
vendored
@@ -1,243 +0,0 @@
|
||||
name: Build Linux Packages
|
||||
|
||||
on:
|
||||
#push:
|
||||
# branches:
|
||||
# - stable
|
||||
# - testing
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version name"
|
||||
required: true
|
||||
type: string
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
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:
|
||||
name: Build binary
|
||||
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:
|
||||
- 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: 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 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: |-
|
||||
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.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 }}
|
||||
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 \
|
||||
--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:
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
path: "dist"
|
||||
upload:
|
||||
name: Upload builds
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- calculate_version
|
||||
- build
|
||||
steps:
|
||||
- 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/
|
||||
16
.github/workflows/stale.yml
vendored
16
.github/workflows/stale.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||
days-before-stale: 60
|
||||
days-before-close: 5
|
||||
exempt-issue-labels: 'bug,enhancement'
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,3 +21,4 @@
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
/.claude/
|
||||
/.codex-*/
|
||||
26
README.md
26
README.md
@@ -49,7 +49,8 @@
|
||||
- `acmedns`
|
||||
- 安装脚本默认生成:
|
||||
- `/etc/sing-box/config.d/10-base.json`
|
||||
- `/etc/sing-box/config.d/20-outbounds.json`
|
||||
- `/etc/sing-box/config.d/route.json`
|
||||
- `/etc/sing-box/config.d/outbound.json`
|
||||
- 安装后的服务名为:
|
||||
- `singbox.service`
|
||||
|
||||
@@ -57,6 +58,8 @@
|
||||
|
||||
- [install.sh](./install.sh)
|
||||
Linux 安装脚本
|
||||
- [update.sh](./update.sh)
|
||||
Linux 升级脚本
|
||||
- [option/xboard.go](./option/xboard.go)
|
||||
`services.xboard` 配置结构
|
||||
- [service/xboard/service.go](./service/xboard/service.go)
|
||||
@@ -75,6 +78,13 @@ curl -fsSL https://s3.cloudyun.top/downloads/singbox/install.sh | bash
|
||||
```
|
||||
`install.sh` 默认会从 `https://s3.cloudyun.top/downloads/singbox` 下载对应架构的预编译 `sing-box` 二进制,再继续进入面板和服务配置流程。
|
||||
|
||||
升级已安装的 sing-box:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://s3.cloudyun.top/downloads/singbox/update.sh | bash
|
||||
```
|
||||
`update.sh` 会从同一发布地址下载对应架构的 `sing-box` 二进制,并自动重启已检测到的 `singbox` 或 `sing-box` 服务。
|
||||
|
||||
|
||||
脚本会做这些事情:
|
||||
|
||||
@@ -139,7 +149,13 @@ sing-box -D /var/lib/sing-box -C /etc/sing-box/config.d run
|
||||
- `services`
|
||||
- 基础路由规则
|
||||
|
||||
### `20-outbounds.json`
|
||||
### `route.json`
|
||||
|
||||
- `route.rules`
|
||||
- `route.auto_detect_interface`
|
||||
- common DNS hijack rules
|
||||
|
||||
### `outbound.json`
|
||||
|
||||
放这些内容:
|
||||
|
||||
@@ -157,7 +173,8 @@ sing-box -D /var/lib/sing-box -C /etc/sing-box/config.d run
|
||||
|
||||
- [configs/10-base.single-node.json](./configs/10-base.single-node.json)
|
||||
- [configs/10-base.multi-node.json](./configs/10-base.multi-node.json)
|
||||
- [configs/20-outbounds.example.json](./configs/20-outbounds.example.json)
|
||||
- [configs/route.json](./configs/route.json)
|
||||
- [configs/outbound.json](./configs/outbound.json)
|
||||
|
||||
## `services.xboard` 配置说明
|
||||
|
||||
@@ -362,7 +379,8 @@ Xboard setup error: missing certificate
|
||||
单节点基础配置
|
||||
- [configs/10-base.multi-node.json](./configs/10-base.multi-node.json)
|
||||
多节点基础配置
|
||||
- [configs/20-outbounds.example.json](./configs/20-outbounds.example.json)
|
||||
- [configs/route.json](./configs/route.json)
|
||||
- [configs/outbound.json](./configs/outbound.json)
|
||||
出站配置模板
|
||||
- [configs/panel-response.vless-reality.json](./configs/panel-response.vless-reality.json)
|
||||
VLESS REALITY 面板回包
|
||||
|
||||
@@ -15,7 +15,8 @@ NC='\033[0m'
|
||||
CONFIG_DIR="/etc/sing-box"
|
||||
CONFIG_MERGE_DIR="$CONFIG_DIR/config.d"
|
||||
CONFIG_BASE_FILE="$CONFIG_MERGE_DIR/10-base.json"
|
||||
CONFIG_OUTBOUNDS_FILE="$CONFIG_MERGE_DIR/20-outbounds.json"
|
||||
CONFIG_ROUTE_FILE="$CONFIG_MERGE_DIR/route.json"
|
||||
CONFIG_OUTBOUNDS_FILE="$CONFIG_MERGE_DIR/outbound.json"
|
||||
WORK_DIR="/var/lib/sing-box"
|
||||
BINARY_PATH="/usr/local/bin/sing-box"
|
||||
SERVICE_NAME="singbox"
|
||||
@@ -332,7 +333,12 @@ ${DNS_SERVER_JSON}
|
||||
"services": [
|
||||
${SERVICE_JSON}
|
||||
],
|
||||
"inbounds": [],
|
||||
"inbounds": []
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$CONFIG_ROUTE_FILE" <<EOF
|
||||
{
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
@@ -357,6 +363,7 @@ cat > "$CONFIG_OUTBOUNDS_FILE" <<EOF
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}Base configuration written to $CONFIG_BASE_FILE${NC}"
|
||||
echo -e "${GREEN}Route configuration written to $CONFIG_ROUTE_FILE${NC}"
|
||||
echo -e "${GREEN}Outbound configuration written to $CONFIG_OUTBOUNDS_FILE${NC}"
|
||||
echo -e "${YELLOW}Edit $CONFIG_OUTBOUNDS_FILE when adding custom sing-box outbounds.${NC}"
|
||||
|
||||
|
||||
@@ -37,14 +37,5 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"inbounds": [],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
],
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
"inbounds": []
|
||||
}
|
||||
|
||||
@@ -29,14 +29,5 @@
|
||||
"node_id": 286
|
||||
}
|
||||
],
|
||||
"inbounds": [],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
],
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
"inbounds": []
|
||||
}
|
||||
|
||||
12
configs/outbound.json
Normal file
12
configs/outbound.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
configs/route.json
Normal file
11
configs/route.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
],
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
2
go.sum
2
go.sum
@@ -122,6 +122,8 @@ github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0v
|
||||
github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/tencentcloud v1.4.3 h1:xJHYLL1TdPeOtUr6Bu6dHTd1TU6/VFm7BFc2EAzAlvc=
|
||||
github.com/libdns/tencentcloud v1.4.3/go.mod h1:Be9gY3tDa12DuAPU79RV9NZIcjY6qg5s7zKPsP26yAM=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
|
||||
|
||||
488
install.sh
488
install.sh
@@ -10,7 +10,8 @@ NC='\033[0m'
|
||||
CONFIG_DIR="/etc/sing-box"
|
||||
CONFIG_MERGE_DIR="$CONFIG_DIR/config.d"
|
||||
CONFIG_BASE_FILE="$CONFIG_MERGE_DIR/10-base.json"
|
||||
CONFIG_OUTBOUNDS_FILE="$CONFIG_MERGE_DIR/20-outbounds.json"
|
||||
CONFIG_ROUTE_FILE="$CONFIG_MERGE_DIR/route.json"
|
||||
CONFIG_OUTBOUNDS_FILE="$CONFIG_MERGE_DIR/outbound.json"
|
||||
WORK_DIR="/var/lib/sing-box"
|
||||
BINARY_PATH="/usr/local/bin/sing-box"
|
||||
SERVICE_NAME="singbox"
|
||||
@@ -21,8 +22,14 @@ PUBLISHED_SCRIPT_URL="${PUBLISHED_SCRIPT_URL:-https://s3.cloudyun.top/downloads/
|
||||
V2BX_DETECTED=0
|
||||
V2BX_CONFIG_PATH=""
|
||||
UNINSTALL_V2BX_DEFAULT="${UNINSTALL_V2BX_DEFAULT:-n}"
|
||||
SCRIPT_VERSION="${SCRIPT_VERSION:-v1.2.4}"
|
||||
SCRIPT_VERSION="${SCRIPT_VERSION:-v1.3.0}"
|
||||
declare -a V2BX_IMPORTED_NODE_IDS=()
|
||||
declare -a EXISTING_IMPORTED_NODE_IDS=()
|
||||
declare -a NODE_IDS=()
|
||||
EXISTING_INSTALL=0
|
||||
EXISTING_CONFIG_SOURCE=""
|
||||
PROMPT_FOR_CONFIG=1
|
||||
CONFIG_BACKUP_DIR=""
|
||||
|
||||
echo -e "${GREEN}Welcome to singbox Release Installation Script${NC}"
|
||||
echo -e "${GREEN}Script version: ${SCRIPT_VERSION}${NC}"
|
||||
@@ -135,6 +142,279 @@ detect_v2bx() {
|
||||
return 0
|
||||
}
|
||||
|
||||
detect_existing_installation() {
|
||||
if [[ -x "$BINARY_PATH" || -f "$SERVICE_FILE" || -f "$CONFIG_BASE_FILE" || -f "$CONFIG_ROUTE_FILE" || -f "$CONFIG_OUTBOUNDS_FILE" || -f "$CONFIG_DIR/config.json" ]]; then
|
||||
EXISTING_INSTALL=1
|
||||
fi
|
||||
|
||||
if [[ -f "$CONFIG_BASE_FILE" ]]; then
|
||||
EXISTING_CONFIG_SOURCE="$CONFIG_BASE_FILE"
|
||||
elif [[ -f "$CONFIG_DIR/config.json" ]]; then
|
||||
EXISTING_CONFIG_SOURCE="$CONFIG_DIR/config.json"
|
||||
fi
|
||||
|
||||
if [[ "$EXISTING_INSTALL" -eq 1 ]]; then
|
||||
echo -e "${YELLOW}Detected existing sing-box installation. Switching to update flow...${NC}"
|
||||
if [[ -n "$EXISTING_CONFIG_SOURCE" ]]; then
|
||||
echo -e "${YELLOW}Existing config source: ${EXISTING_CONFIG_SOURCE}${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
load_existing_install_defaults() {
|
||||
if [[ -z "$EXISTING_CONFIG_SOURCE" || ! -f "$EXISTING_CONFIG_SOURCE" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Loading defaults from existing sing-box config...${NC}"
|
||||
EXISTING_IMPORTED_NODE_IDS=()
|
||||
|
||||
local parsed=""
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
parsed="$(python3 - "$EXISTING_CONFIG_SOURCE" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
path = sys.argv[1]
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
xboard = {}
|
||||
for service in data.get("services") or []:
|
||||
if isinstance(service, dict) and service.get("type") == "xboard":
|
||||
xboard = service
|
||||
break
|
||||
|
||||
print(f"PANEL_URL={xboard.get('panel_url', '')}")
|
||||
print(f"PANEL_TOKEN={xboard.get('key', '')}")
|
||||
|
||||
node_id = xboard.get("node_id")
|
||||
if node_id not in (None, ""):
|
||||
print(f"NODE_ID={node_id}")
|
||||
for node in xboard.get("nodes") or []:
|
||||
if isinstance(node, dict):
|
||||
current_node_id = node.get("node_id")
|
||||
if current_node_id not in (None, ""):
|
||||
print(f"NODE_ID={current_node_id}")
|
||||
|
||||
servers = ((data.get("dns") or {}).get("servers") or [])
|
||||
dns_server = servers[0] if servers else {}
|
||||
if isinstance(dns_server, dict):
|
||||
print(f"DNS_MODE={dns_server.get('type', '')}")
|
||||
print(f"DNS_SERVER={dns_server.get('server', '')}")
|
||||
print(f"DNS_SERVER_PORT={dns_server.get('server_port', '')}")
|
||||
PY
|
||||
)"
|
||||
elif command -v jq >/dev/null 2>&1; then
|
||||
parsed="$(jq -r '
|
||||
([.services[]? | select(.type == "xboard")] | .[0] // {}) as $xboard |
|
||||
"PANEL_URL=" + ($xboard.panel_url // ""),
|
||||
"PANEL_TOKEN=" + ($xboard.key // ""),
|
||||
(if ($xboard.node_id // empty) != empty then "NODE_ID=" + ($xboard.node_id | tostring) else empty end),
|
||||
(($xboard.nodes // [])[]? | "NODE_ID=" + (.node_id | tostring)),
|
||||
(.dns.servers[0] // {}) as $dns |
|
||||
"DNS_MODE=" + ($dns.type // ""),
|
||||
"DNS_SERVER=" + ($dns.server // ""),
|
||||
"DNS_SERVER_PORT=" + (($dns.server_port // "") | tostring)
|
||||
' "$EXISTING_CONFIG_SOURCE" 2>/dev/null || true)"
|
||||
else
|
||||
echo -e "${YELLOW}Neither python3 nor jq found, unable to auto-load existing config.${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -z "$parsed" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local parsed_line
|
||||
while IFS= read -r parsed_line; do
|
||||
parsed_line="$(sanitize_value "$parsed_line")"
|
||||
case "$parsed_line" in
|
||||
PANEL_URL=*)
|
||||
PANEL_URL="$(sanitize_value "${parsed_line#PANEL_URL=}")"
|
||||
;;
|
||||
PANEL_TOKEN=*)
|
||||
PANEL_TOKEN="$(sanitize_value "${parsed_line#PANEL_TOKEN=}")"
|
||||
;;
|
||||
NODE_ID=*)
|
||||
append_unique_node_id_from_existing "${parsed_line#NODE_ID=}"
|
||||
;;
|
||||
DNS_MODE=*)
|
||||
DNS_MODE="$(echo "${parsed_line#DNS_MODE=}" | tr '[:upper:]' '[:lower:]')"
|
||||
;;
|
||||
DNS_SERVER=*)
|
||||
DNS_SERVER="$(sanitize_value "${parsed_line#DNS_SERVER=}")"
|
||||
;;
|
||||
DNS_SERVER_PORT=*)
|
||||
DNS_SERVER_PORT="$(sanitize_value "${parsed_line#DNS_SERVER_PORT=}")"
|
||||
;;
|
||||
esac
|
||||
done <<< "$parsed"
|
||||
|
||||
if [[ "${#EXISTING_IMPORTED_NODE_IDS[@]}" -gt 0 ]]; then
|
||||
NODE_IDS=("${EXISTING_IMPORTED_NODE_IDS[@]}")
|
||||
NODE_ID="${EXISTING_IMPORTED_NODE_IDS[0]}"
|
||||
fi
|
||||
|
||||
if [[ -n "${DNS_MODE:-}" ]]; then
|
||||
DNS_MODE="$(sanitize_value "$DNS_MODE")"
|
||||
fi
|
||||
|
||||
if [[ -n "${PANEL_URL:-}" && -n "${PANEL_TOKEN:-}" && "${#NODE_IDS[@]}" -gt 0 && ( "${DNS_MODE:-}" == "local" || ( "${DNS_MODE:-}" == "udp" && -n "${DNS_SERVER:-}" && -n "${DNS_SERVER_PORT:-}" ) ) ]]; then
|
||||
echo -e "${YELLOW}Loaded existing config: PanelURL=${PANEL_URL}, NodeIDs=$(IFS=,; echo "${NODE_IDS[*]}"), DNS=${DNS_MODE}${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Existing config detected, but some fields are incomplete. The installer will ask for the missing values.${NC}"
|
||||
return 1
|
||||
}
|
||||
|
||||
append_unique_node_id_from_existing() {
|
||||
local normalized_value
|
||||
local node_id_part
|
||||
local existing_node_id
|
||||
|
||||
normalized_value="$(normalize_node_id_input "$1")"
|
||||
if [[ -z "$normalized_value" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
read -r -a NORMALIZED_NODE_ID_PARTS <<< "$normalized_value"
|
||||
for node_id_part in "${NORMALIZED_NODE_ID_PARTS[@]}"; do
|
||||
if ! [[ "$node_id_part" =~ ^[0-9]+$ ]]; then
|
||||
continue
|
||||
fi
|
||||
for existing_node_id in "${EXISTING_IMPORTED_NODE_IDS[@]}"; do
|
||||
if [[ "$existing_node_id" == "$node_id_part" ]]; then
|
||||
node_id_part=""
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -n "$node_id_part" ]]; then
|
||||
EXISTING_IMPORTED_NODE_IDS+=("$node_id_part")
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
ensure_config_backup_dir() {
|
||||
if [[ -n "$CONFIG_BACKUP_DIR" ]]; then
|
||||
return 0
|
||||
fi
|
||||
CONFIG_BACKUP_DIR="$CONFIG_DIR/backup/$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p "$CONFIG_BACKUP_DIR"
|
||||
echo -e "${YELLOW}Backing up existing configuration to ${CONFIG_BACKUP_DIR}${NC}"
|
||||
}
|
||||
|
||||
backup_path_if_exists() {
|
||||
local path="$1"
|
||||
if [[ ! -e "$path" ]]; then
|
||||
return 0
|
||||
fi
|
||||
ensure_config_backup_dir
|
||||
cp -a "$path" "$CONFIG_BACKUP_DIR/"
|
||||
}
|
||||
|
||||
archive_legacy_config_json() {
|
||||
local legacy_config_path="$CONFIG_DIR/config.json"
|
||||
if [[ ! -f "$legacy_config_path" ]]; then
|
||||
return 0
|
||||
fi
|
||||
ensure_config_backup_dir
|
||||
mv "$legacy_config_path" "$CONFIG_BACKUP_DIR/config.json.migrated"
|
||||
echo -e "${YELLOW}Legacy single-file config has been archived to ${CONFIG_BACKUP_DIR}/config.json.migrated${NC}"
|
||||
}
|
||||
|
||||
extract_legacy_config_sections() {
|
||||
local source_path="$1"
|
||||
local route_written=0
|
||||
local outbound_written=0
|
||||
|
||||
if [[ ! -f "$source_path" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
local parsed
|
||||
parsed="$(python3 - "$source_path" "$CONFIG_ROUTE_FILE" "$CONFIG_OUTBOUNDS_FILE" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
source_path, route_path, outbounds_path = sys.argv[1:4]
|
||||
with open(source_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
route_written = 0
|
||||
if isinstance(data.get("route"), dict):
|
||||
with open(route_path, "w", encoding="utf-8") as f:
|
||||
json.dump({"route": data["route"]}, f, ensure_ascii=False, indent=2)
|
||||
f.write("\n")
|
||||
route_written = 1
|
||||
|
||||
outbound_written = 0
|
||||
if isinstance(data.get("outbounds"), list):
|
||||
with open(outbounds_path, "w", encoding="utf-8") as f:
|
||||
json.dump({"outbounds": data["outbounds"]}, f, ensure_ascii=False, indent=2)
|
||||
f.write("\n")
|
||||
outbound_written = 1
|
||||
|
||||
print(f"ROUTE_WRITTEN={route_written}")
|
||||
print(f"OUTBOUNDS_WRITTEN={outbound_written}")
|
||||
PY
|
||||
)"
|
||||
while IFS= read -r parsed_line; do
|
||||
case "$parsed_line" in
|
||||
ROUTE_WRITTEN=1) route_written=1 ;;
|
||||
OUTBOUNDS_WRITTEN=1) outbound_written=1 ;;
|
||||
esac
|
||||
done <<< "$parsed"
|
||||
elif command -v jq >/dev/null 2>&1; then
|
||||
if jq -e '.route | type == "object"' "$source_path" >/dev/null 2>&1; then
|
||||
jq '{route: .route}' "$source_path" > "$CONFIG_ROUTE_FILE"
|
||||
route_written=1
|
||||
fi
|
||||
if jq -e '.outbounds | type == "array"' "$source_path" >/dev/null 2>&1; then
|
||||
jq '{outbounds: .outbounds}' "$source_path" > "$CONFIG_OUTBOUNDS_FILE"
|
||||
outbound_written=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$route_written" -eq 1 || "$outbound_written" -eq 1 ]]; then
|
||||
echo -e "${YELLOW}Extracted legacy config sections from ${source_path}${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
write_default_route_config() {
|
||||
cat > "$CONFIG_ROUTE_FILE" <<EOF
|
||||
{
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
],
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
write_default_outbound_config() {
|
||||
cat > "$CONFIG_OUTBOUNDS_FILE" <<EOF
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
load_v2bx_defaults() {
|
||||
if [[ -z "$V2BX_CONFIG_PATH" ]] && ! find_v2bx_config; then
|
||||
return 1
|
||||
@@ -318,6 +598,7 @@ cleanup_legacy_service() {
|
||||
}
|
||||
|
||||
detect_v2bx
|
||||
detect_existing_installation
|
||||
stop_v2bx_if_present
|
||||
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
@@ -334,80 +615,107 @@ load_v2bx_defaults || true
|
||||
PANEL_URL="$(sanitize_value "${PANEL_URL:-}")"
|
||||
PANEL_TOKEN="$(sanitize_value "${PANEL_TOKEN:-}")"
|
||||
NODE_ID="$(sanitize_value "${NODE_ID:-}")"
|
||||
DNS_MODE="$(sanitize_value "${DNS_MODE:-}")"
|
||||
DNS_SERVER="$(sanitize_value "${DNS_SERVER:-}")"
|
||||
DNS_SERVER_PORT="$(sanitize_value "${DNS_SERVER_PORT:-}")"
|
||||
ENABLE_PROXY_PROTOCOL_HINT="$(sanitize_value "${ENABLE_PROXY_PROTOCOL_HINT:-n}")"
|
||||
|
||||
if [[ "$EXISTING_INSTALL" -eq 1 ]]; then
|
||||
if load_existing_install_defaults; then
|
||||
PROMPT_FOR_CONFIG=0
|
||||
fi
|
||||
fi
|
||||
|
||||
download_binary
|
||||
cleanup_legacy_service
|
||||
|
||||
read -u 3 -p "Enter Panel URL [${PANEL_URL}]: " INPUT_URL
|
||||
PANEL_URL="$(sanitize_value "${INPUT_URL:-$PANEL_URL}")"
|
||||
if [[ "$PROMPT_FOR_CONFIG" -eq 1 ]]; then
|
||||
read -u 3 -p "Enter Panel URL [${PANEL_URL}]: " INPUT_URL
|
||||
PANEL_URL="$(sanitize_value "${INPUT_URL:-$PANEL_URL}")"
|
||||
|
||||
read -u 3 -p "Enter Panel Token (Node Key) [${PANEL_TOKEN}]: " INPUT_TOKEN
|
||||
PANEL_TOKEN="$(sanitize_value "${INPUT_TOKEN:-$PANEL_TOKEN}")"
|
||||
read -u 3 -p "Enter Panel Token (Node Key) [${PANEL_TOKEN}]: " INPUT_TOKEN
|
||||
PANEL_TOKEN="$(sanitize_value "${INPUT_TOKEN:-$PANEL_TOKEN}")"
|
||||
|
||||
read -u 3 -p "This node is behind an L4 proxy/LB that sends PROXY protocol? [${ENABLE_PROXY_PROTOCOL_HINT:-n}]: " INPUT_PROXY_PROTOCOL
|
||||
ENABLE_PROXY_PROTOCOL_HINT="$(sanitize_value "${INPUT_PROXY_PROTOCOL:-${ENABLE_PROXY_PROTOCOL_HINT:-n}}")"
|
||||
read -u 3 -p "This node is behind an L4 proxy/LB that sends PROXY protocol? [${ENABLE_PROXY_PROTOCOL_HINT:-n}]: " INPUT_PROXY_PROTOCOL
|
||||
ENABLE_PROXY_PROTOCOL_HINT="$(sanitize_value "${INPUT_PROXY_PROTOCOL:-${ENABLE_PROXY_PROTOCOL_HINT:-n}}")"
|
||||
|
||||
declare -a NODE_IDS
|
||||
|
||||
i=1
|
||||
while true; do
|
||||
DEFAULT_NODE_ID=""
|
||||
if [[ "$i" -le "${#V2BX_IMPORTED_NODE_IDS[@]}" ]]; then
|
||||
DEFAULT_NODE_ID="${V2BX_IMPORTED_NODE_IDS[$((i-1))]}"
|
||||
elif [[ "$i" -eq 1 && -n "${NODE_ID:-}" ]]; then
|
||||
DEFAULT_NODE_ID="$NODE_ID"
|
||||
fi
|
||||
if [[ -n "$DEFAULT_NODE_ID" ]]; then
|
||||
read -u 3 -p "Enter Node ID for node #$i [${DEFAULT_NODE_ID}] (type N/NO to finish): " INPUT_ID
|
||||
else
|
||||
read -u 3 -p "Enter Node ID for node #$i (press Enter or type N/NO to finish): " INPUT_ID
|
||||
fi
|
||||
CURRENT_NODE_ID="$(sanitize_value "${INPUT_ID:-$DEFAULT_NODE_ID}")"
|
||||
if [[ -z "$DEFAULT_NODE_ID" && -z "$CURRENT_NODE_ID" && "${#NODE_IDS[@]}" -gt 0 ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ "$CURRENT_NODE_ID" =~ ^([nN]|[nN][oO])$ ]]; then
|
||||
if [[ "${#NODE_IDS[@]}" -eq 0 ]]; then
|
||||
echo -e "${RED}At least one Node ID is required${NC}"
|
||||
NODE_IDS=()
|
||||
i=1
|
||||
while true; do
|
||||
DEFAULT_NODE_ID=""
|
||||
if [[ "$i" -le "${#EXISTING_IMPORTED_NODE_IDS[@]}" ]]; then
|
||||
DEFAULT_NODE_ID="${EXISTING_IMPORTED_NODE_IDS[$((i-1))]}"
|
||||
elif [[ "$i" -le "${#V2BX_IMPORTED_NODE_IDS[@]}" ]]; then
|
||||
DEFAULT_NODE_ID="${V2BX_IMPORTED_NODE_IDS[$((i-1))]}"
|
||||
elif [[ "$i" -eq 1 && -n "${NODE_ID:-}" ]]; then
|
||||
DEFAULT_NODE_ID="$NODE_ID"
|
||||
fi
|
||||
if [[ -n "$DEFAULT_NODE_ID" ]]; then
|
||||
read -u 3 -p "Enter Node ID for node #$i [${DEFAULT_NODE_ID}] (type N/NO to finish): " INPUT_ID
|
||||
else
|
||||
read -u 3 -p "Enter Node ID for node #$i (press Enter or type N/NO to finish): " INPUT_ID
|
||||
fi
|
||||
CURRENT_NODE_ID="$(sanitize_value "${INPUT_ID:-$DEFAULT_NODE_ID}")"
|
||||
if [[ -z "$DEFAULT_NODE_ID" && -z "$CURRENT_NODE_ID" && "${#NODE_IDS[@]}" -gt 0 ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ "$CURRENT_NODE_ID" =~ ^([nN]|[nN][oO])$ ]]; then
|
||||
if [[ "${#NODE_IDS[@]}" -eq 0 ]]; then
|
||||
echo -e "${RED}At least one Node ID is required${NC}"
|
||||
exit 1
|
||||
fi
|
||||
break
|
||||
fi
|
||||
CURRENT_NODE_ID="$(normalize_node_id_input "$CURRENT_NODE_ID")"
|
||||
if [[ -z "$CURRENT_NODE_ID" ]]; then
|
||||
echo -e "${RED}Node ID is required for node #$i${NC}"
|
||||
exit 1
|
||||
fi
|
||||
break
|
||||
fi
|
||||
CURRENT_NODE_ID="$(normalize_node_id_input "$CURRENT_NODE_ID")"
|
||||
if [[ -z "$CURRENT_NODE_ID" ]]; then
|
||||
echo -e "${RED}Node ID is required for node #$i${NC}"
|
||||
exit 1
|
||||
fi
|
||||
read -r -a CURRENT_NODE_ID_PARTS <<< "$CURRENT_NODE_ID"
|
||||
if [[ "${#CURRENT_NODE_ID_PARTS[@]}" -eq 0 ]]; then
|
||||
echo -e "${RED}Node ID is required for node #$i${NC}"
|
||||
exit 1
|
||||
fi
|
||||
for CURRENT_NODE_ID_PART in "${CURRENT_NODE_ID_PARTS[@]}"; do
|
||||
if ! [[ "$CURRENT_NODE_ID_PART" =~ ^[0-9]+$ ]]; then
|
||||
echo -e "${RED}Node ID must be a positive integer, got: ${CURRENT_NODE_ID_PART}${NC}"
|
||||
read -r -a CURRENT_NODE_ID_PARTS <<< "$CURRENT_NODE_ID"
|
||||
if [[ "${#CURRENT_NODE_ID_PARTS[@]}" -eq 0 ]]; then
|
||||
echo -e "${RED}Node ID is required for node #$i${NC}"
|
||||
exit 1
|
||||
fi
|
||||
NODE_IDS+=("$CURRENT_NODE_ID_PART")
|
||||
for CURRENT_NODE_ID_PART in "${CURRENT_NODE_ID_PARTS[@]}"; do
|
||||
if ! [[ "$CURRENT_NODE_ID_PART" =~ ^[0-9]+$ ]]; then
|
||||
echo -e "${RED}Node ID must be a positive integer, got: ${CURRENT_NODE_ID_PART}${NC}"
|
||||
exit 1
|
||||
fi
|
||||
NODE_IDS+=("$CURRENT_NODE_ID_PART")
|
||||
done
|
||||
((i++))
|
||||
done
|
||||
((i++))
|
||||
done
|
||||
|
||||
DNS_MODE_DEFAULT=${DNS_MODE:-udp}
|
||||
read -u 3 -p "Enter DNS mode [${DNS_MODE_DEFAULT}] (udp/local): " INPUT_DNS_MODE
|
||||
DNS_MODE=$(echo "${INPUT_DNS_MODE:-$DNS_MODE_DEFAULT}" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "$DNS_MODE" in
|
||||
udp)
|
||||
DNS_SERVER_DEFAULT=${DNS_SERVER:-1.1.1.1}
|
||||
DNS_SERVER_PORT_DEFAULT=${DNS_SERVER_PORT:-53}
|
||||
read -u 3 -p "Enter DNS server [${DNS_SERVER_DEFAULT}]: " INPUT_DNS_SERVER
|
||||
DNS_SERVER="$(sanitize_value "${INPUT_DNS_SERVER:-$DNS_SERVER_DEFAULT}")"
|
||||
read -u 3 -p "Enter DNS server port [${DNS_SERVER_PORT_DEFAULT}]: " INPUT_DNS_SERVER_PORT
|
||||
DNS_SERVER_PORT="$(sanitize_value "${INPUT_DNS_SERVER_PORT:-$DNS_SERVER_PORT_DEFAULT}")"
|
||||
;;
|
||||
local)
|
||||
DNS_SERVER=""
|
||||
DNS_SERVER_PORT=""
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unsupported DNS mode: $DNS_MODE. Supported values: udp, local${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo -e "${YELLOW}Reusing existing install settings for update.${NC}"
|
||||
fi
|
||||
|
||||
NODE_COUNT=${#NODE_IDS[@]}
|
||||
|
||||
DNS_MODE_DEFAULT=${DNS_MODE:-udp}
|
||||
read -u 3 -p "Enter DNS mode [${DNS_MODE_DEFAULT}] (udp/local): " INPUT_DNS_MODE
|
||||
DNS_MODE=$(echo "${INPUT_DNS_MODE:-$DNS_MODE_DEFAULT}" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "$DNS_MODE" in
|
||||
case "${DNS_MODE:-udp}" in
|
||||
udp)
|
||||
DNS_SERVER_DEFAULT=${DNS_SERVER:-1.1.1.1}
|
||||
DNS_SERVER_PORT_DEFAULT=${DNS_SERVER_PORT:-53}
|
||||
read -u 3 -p "Enter DNS server [${DNS_SERVER_DEFAULT}]: " INPUT_DNS_SERVER
|
||||
DNS_SERVER=${INPUT_DNS_SERVER:-$DNS_SERVER_DEFAULT}
|
||||
read -u 3 -p "Enter DNS server port [${DNS_SERVER_PORT_DEFAULT}]: " INPUT_DNS_SERVER_PORT
|
||||
DNS_SERVER_PORT=${INPUT_DNS_SERVER_PORT:-$DNS_SERVER_PORT_DEFAULT}
|
||||
if [[ -z "$DNS_SERVER" ]]; then
|
||||
echo -e "${RED}DNS server is required in udp mode${NC}"
|
||||
exit 1
|
||||
@@ -487,6 +795,12 @@ fi
|
||||
SERVICE_JSON+=$'\n }'
|
||||
|
||||
echo -e "${YELLOW}Generating configuration...${NC}"
|
||||
backup_path_if_exists "$CONFIG_BASE_FILE"
|
||||
backup_path_if_exists "$CONFIG_ROUTE_FILE"
|
||||
backup_path_if_exists "$CONFIG_OUTBOUNDS_FILE"
|
||||
backup_path_if_exists "$CONFIG_DIR/config.json"
|
||||
backup_path_if_exists "$SERVICE_FILE"
|
||||
|
||||
cat > "$CONFIG_BASE_FILE" <<EOF
|
||||
{
|
||||
"log": {
|
||||
@@ -507,32 +821,39 @@ ${DNS_SERVER_JSON}
|
||||
"services": [
|
||||
${SERVICE_JSON}
|
||||
],
|
||||
"inbounds": [],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
],
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
"inbounds": []
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$CONFIG_OUTBOUNDS_FILE" <<EOF
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
if [[ -f "$CONFIG_DIR/config.json" ]]; then
|
||||
rm -f "$CONFIG_ROUTE_FILE" "$CONFIG_OUTBOUNDS_FILE"
|
||||
if ! extract_legacy_config_sections "$CONFIG_DIR/config.json"; then
|
||||
echo -e "${YELLOW}Legacy config detected but route/outbounds could not be extracted automatically. Writing default route/outbound files.${NC}"
|
||||
fi
|
||||
if [[ ! -f "$CONFIG_ROUTE_FILE" ]]; then
|
||||
write_default_route_config
|
||||
fi
|
||||
if [[ ! -f "$CONFIG_OUTBOUNDS_FILE" ]]; then
|
||||
write_default_outbound_config
|
||||
fi
|
||||
archive_legacy_config_json
|
||||
else
|
||||
if [[ -f "$CONFIG_ROUTE_FILE" ]]; then
|
||||
echo -e "${YELLOW}Preserving existing route config: ${CONFIG_ROUTE_FILE}${NC}"
|
||||
else
|
||||
write_default_route_config
|
||||
fi
|
||||
|
||||
if [[ -f "$CONFIG_OUTBOUNDS_FILE" ]]; then
|
||||
echo -e "${YELLOW}Preserving existing outbound config: ${CONFIG_OUTBOUNDS_FILE}${NC}"
|
||||
else
|
||||
write_default_outbound_config
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Base configuration written to $CONFIG_BASE_FILE${NC}"
|
||||
echo -e "${GREEN}Outbound configuration written to $CONFIG_OUTBOUNDS_FILE${NC}"
|
||||
echo -e "${GREEN}Route configuration ready at $CONFIG_ROUTE_FILE${NC}"
|
||||
echo -e "${GREEN}Outbound configuration ready at $CONFIG_OUTBOUNDS_FILE${NC}"
|
||||
echo -e "${YELLOW}Edit $CONFIG_OUTBOUNDS_FILE when adding custom sing-box outbounds.${NC}"
|
||||
|
||||
if [[ "$ENABLE_PROXY_PROTOCOL_HINT" =~ ^([yY][eE][sS]|[yY]|1|true|TRUE)$ ]]; then
|
||||
@@ -570,7 +891,12 @@ systemctl restart "$SERVICE_NAME"
|
||||
|
||||
prompt_uninstall_v2bx
|
||||
|
||||
echo -e "${GREEN}Service installed and started successfully.${NC}"
|
||||
if [[ "$EXISTING_INSTALL" -eq 1 ]]; then
|
||||
echo -e "${GREEN}Service updated and restarted successfully.${NC}"
|
||||
echo -e "${GREEN}Configuration has been normalized to the split config.d layout.${NC}"
|
||||
else
|
||||
echo -e "${GREEN}Service installed and started successfully.${NC}"
|
||||
fi
|
||||
echo -e "${GREEN}Check status with: systemctl status ${SERVICE_NAME}${NC}"
|
||||
echo -e "${GREEN}View logs with: journalctl -u ${SERVICE_NAME} -f${NC}"
|
||||
echo -e "${GREEN}Panel config endpoint must control PROXY protocol via accept_proxy_protocol when needed.${NC}"
|
||||
|
||||
141
update.sh
Normal file
141
update.sh
Normal file
@@ -0,0 +1,141 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
BINARY_PATH="${BINARY_PATH:-/usr/local/bin/sing-box}"
|
||||
RELEASE_BASE_URL="${RELEASE_BASE_URL:-https://s3.cloudyun.top/downloads/singbox}"
|
||||
SERVICE_CANDIDATES=("${SERVICE_NAME:-}" "singbox" "sing-box")
|
||||
|
||||
echo -e "${GREEN}sing-box binary update script${NC}"
|
||||
|
||||
if [[ ${EUID} -ne 0 ]]; then
|
||||
echo -e "${RED}This script must be run as root${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OS="$(uname -s)"
|
||||
if [[ "$OS" != "Linux" ]]; then
|
||||
echo -e "${RED}This update script currently supports Linux only. Current OS: ${OS}${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
x86_64) BINARY_ARCH="amd64" ;;
|
||||
aarch64|arm64) BINARY_ARCH="arm64" ;;
|
||||
armv7l|armv7) BINARY_ARCH="armv7" ;;
|
||||
*)
|
||||
echo -e "${RED}Unsupported architecture: ${ARCH}${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
DOWNLOAD_TARGET="${DOWNLOAD_TARGET:-linux-${BINARY_ARCH}}"
|
||||
DOWNLOAD_URL="${DOWNLOAD_URL:-${RELEASE_BASE_URL}/sing-box-${DOWNLOAD_TARGET}}"
|
||||
TMP_BINARY="$(mktemp)"
|
||||
BACKUP_BINARY="$(mktemp)"
|
||||
|
||||
cleanup() {
|
||||
rm -f "${TMP_BINARY}" "${BACKUP_BINARY}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
detect_service_name() {
|
||||
local candidate
|
||||
for candidate in "${SERVICE_CANDIDATES[@]}"; do
|
||||
if [[ -z "${candidate}" ]]; then
|
||||
continue
|
||||
fi
|
||||
if systemctl list-unit-files --type=service --no-legend 2>/dev/null | awk '{print $1}' | grep -Fxq "${candidate}.service"; then
|
||||
printf '%s\n' "${candidate}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
download_binary() {
|
||||
echo -e "${YELLOW}Downloading sing-box binary...${NC}"
|
||||
echo -e "${YELLOW}Target: ${DOWNLOAD_TARGET}${NC}"
|
||||
echo -e "${YELLOW}URL: ${DOWNLOAD_URL}${NC}"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fL "${DOWNLOAD_URL}" -o "${TMP_BINARY}"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -O "${TMP_BINARY}" "${DOWNLOAD_URL}"
|
||||
else
|
||||
echo -e "${RED}Neither curl nor wget is installed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod 0755 "${TMP_BINARY}"
|
||||
}
|
||||
|
||||
validate_binary() {
|
||||
if ! "${TMP_BINARY}" version >/dev/null 2>&1; then
|
||||
echo -e "${RED}Downloaded file is not a valid sing-box binary.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
current_version() {
|
||||
if [[ -x "${BINARY_PATH}" ]]; then
|
||||
"${BINARY_PATH}" version 2>/dev/null | head -n 1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
new_version() {
|
||||
"${TMP_BINARY}" version 2>/dev/null | head -n 1 || true
|
||||
}
|
||||
|
||||
rollback() {
|
||||
echo -e "${RED}Update failed, rolling back to previous binary...${NC}"
|
||||
install -m 0755 "${BACKUP_BINARY}" "${BINARY_PATH}"
|
||||
if [[ -n "${SERVICE_NAME_DETECTED:-}" ]]; then
|
||||
systemctl restart "${SERVICE_NAME_DETECTED}" || true
|
||||
fi
|
||||
}
|
||||
|
||||
download_binary
|
||||
validate_binary
|
||||
|
||||
OLD_VERSION="$(current_version)"
|
||||
NEW_VERSION="$(new_version)"
|
||||
|
||||
if [[ -n "${OLD_VERSION}" ]]; then
|
||||
echo -e "${YELLOW}Current version: ${OLD_VERSION}${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Current version: not installed or unreadable${NC}"
|
||||
fi
|
||||
echo -e "${YELLOW}New version: ${NEW_VERSION}${NC}"
|
||||
|
||||
if [[ -x "${BINARY_PATH}" ]]; then
|
||||
cp -f "${BINARY_PATH}" "${BACKUP_BINARY}"
|
||||
else
|
||||
: > "${BACKUP_BINARY}"
|
||||
fi
|
||||
|
||||
install -m 0755 "${TMP_BINARY}" "${BINARY_PATH}"
|
||||
|
||||
SERVICE_NAME_DETECTED="$(detect_service_name || true)"
|
||||
if [[ -n "${SERVICE_NAME_DETECTED}" ]]; then
|
||||
echo -e "${YELLOW}Restarting service: ${SERVICE_NAME_DETECTED}${NC}"
|
||||
if ! systemctl restart "${SERVICE_NAME_DETECTED}"; then
|
||||
if [[ -s "${BACKUP_BINARY}" ]]; then
|
||||
rollback
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}Service restarted successfully.${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}No systemd service detected. Binary updated only.${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}sing-box has been updated successfully.${NC}"
|
||||
echo -e "${GREEN}Binary: ${BINARY_PATH}${NC}"
|
||||
echo -e "${GREEN}Version: $(current_version)${NC}"
|
||||
Reference in New Issue
Block a user