mirror of
https://github.com/fscarmen/sing-box.git
synced 2026-08-10 17:55:16 +08:00
v1.3.19 feat: add real-time traffic stats and speed up CDN probe
- add real-time traffic stats via clash_api: total in/out shown in `sb -n` output and main menu status line - inject `experimental.clash_api` into the base config, with a fallback patch plus SIGHUP reload for legacy configs and cleanup of stale `v2ray_api` - add shared `find_free_port()` for both the nginx default port and the clash_api port, probing for unused ports within the script port range - add `format_traffic()` and `ensure_stats_data()` helpers, fetching `/connections` with curl-first, wget fallback - speed up the CDN status probe with HEAD requests and explicit timeouts in `get_code()` v1.3.19 feat: 新增实时流量统计,加速优选地址探测 - 新增基于 clash_api 的实时流量统计:`sb -n` 输出与主菜单状态行展示总入站/出站流量 - 基础配置注入 `experimental.clash_api`,旧配置自动补全并 SIGHUP 热加载,同时清理残留的 `v2ray_api` - 新增公共 `find_free_port()`,nginx 默认端口与 clash_api 端口共用,在脚本端口范围内探测未被占用的端口 - 新增 `format_traffic()` 与 `ensure_stats_data()` 辅助函数,优先 curl、wget 兜底拉取 `/connections` 数据 - 加速优选地址状态探测:`get_code()` 改用 HEAD 请求并显式设置超时
This commit is contained in:
@@ -24,16 +24,18 @@
|
||||
|
||||
* * *
|
||||
## 1.更新信息
|
||||
2026.07.31 v1.3.19 Add real-time traffic stats (-n / main menu); 添加实时流量统计 (-n / 主菜单)
|
||||
|
||||
2026.07.25 v1.3.18 Add enable/disable subscriptions option in sb -d menu 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj; sb -d 菜单新增 「订阅开关」,可随时开启关闭订阅
|
||||
|
||||
2026.07.20 v1.3.17 1. Add SIGHUP hot-reload support, replace restart sequences with reload; 2. Force base-config regeneration on upgrade to guarantee sing-box check passes; 1. 新增 SIGHUP 热更支持,用 reload 替换重启流程; 2. 升级时强制重置基础配置至新版格式,保证兼容性
|
||||
|
||||
2026.07.16 v1.3.16 1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj; 1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj
|
||||
|
||||
<details>
|
||||
<summary>历史更新 history(点击即可展开或收起)</summary>
|
||||
<br>
|
||||
|
||||
>2026.07.16 v1.3.16 1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj; 1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj
|
||||
>
|
||||
>2026.07.01 v1.3.15 Add v2rayN Finalmask field for hysteria2 realm subscription output; v2rayN 订阅输出增加 Hysteria2 Realm 的 Finalmask 字段
|
||||
>
|
||||
>2026.05.29 v1.3.14 Added optional custom warp route rule management, available after installation via [sb -d] without affecting the main installation flow; 增加可选的自定义 warp 路由规则管理,不影响主程序安装流程,安装后可通过 [sb -d] 按需管理
|
||||
|
||||
+258
-89
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 当前脚本版本号
|
||||
VERSION='v1.3.18 (2026.07.28)'
|
||||
VERSION='v1.3.19 (2026.07.31)'
|
||||
|
||||
# Github 反代加速代理
|
||||
GITHUB_PROXY=('https://hub.glowp.xyz/' 'https://proxy.vvvv.ee/')
|
||||
@@ -40,8 +40,8 @@ mkdir -p "$TEMP_DIR"
|
||||
|
||||
E[0]="Language:\n 1. English (default) \n 2. 简体中文"
|
||||
C[0]="${E[0]}"
|
||||
E[1]="1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Add enable/disable subscriptions option in sb -d menu 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj; 3. add SIGHUP hot-reload support, replace restart sequences with reload; 4. force base-config regeneration on upgrade to guarantee sing-box check passes"
|
||||
C[1]="1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. sb -d 菜单新增 「订阅开关」,可随时开启关闭订阅; 3. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj; 4. 新增 SIGHUP 热更支持,用 reload 替换重启流程; 4. 新增 SIGHUP 热更支持,用 reload 替换重启流程; 5. 升级时强制重置基础配置至新版格式,保证兼容性"
|
||||
E[1]="1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Add enable/disable subscriptions option in sb -d menu 3. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj; 4. add SIGHUP hot-reload support, replace restart sequences with reload; 5. force base-config regeneration on upgrade to guarantee sing-box check passes; 6. Add real-time traffic stats (-n / main menu)"
|
||||
C[1]="1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. sb -d 菜单新增 「订阅开关」,可随时开启关闭订阅; 3. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj; 4. 新增 SIGHUP 热更支持,用 reload 替换重启流程; 4. 新增 SIGHUP 热更支持,用 reload 替换重启流程; 5. 升级时强制重置基础配置至新版格式,保证兼容性; 6. 添加实时流量统计 (-n / 主菜单)"
|
||||
E[2]="Downloading Sing-box. Please wait a seconds ..."
|
||||
C[2]="下载 Sing-box 中,请稍等 ..."
|
||||
E[3]="Input errors up to 5 times.The script is aborted."
|
||||
@@ -98,8 +98,8 @@ E[28]="open"
|
||||
C[28]="开启"
|
||||
E[29]="View links (sb -n)"
|
||||
C[29]="查看节点信息 (sb -n)"
|
||||
E[30]="Listen ports (current: \${_val})"
|
||||
C[30]="监听端口 (当前: \${_val})"
|
||||
E[30]="Listen ports (current: \${VAL_ITEM})"
|
||||
C[30]="监听端口 (当前: \${VAL_ITEM})"
|
||||
E[31]="Sync Sing-box to the latest version (sb -v)"
|
||||
C[31]="同步 Sing-box 至最新版本 (sb -v)"
|
||||
E[32]="Upgrade kernel, turn on BBR, change Linux system (sb -b)"
|
||||
@@ -134,8 +134,8 @@ E[46]="Warp / warp-go was detected to be running. Please enter the correct serve
|
||||
C[46]="检测到 warp / warp-go 正在运行,请输入确认的服务器 IP:"
|
||||
E[47]="No server ip, script exits. Feedback:[https://github.com/fscarmen/sing-box/issues]"
|
||||
C[47]="没有 server ip,脚本退出,问题反馈:[https://github.com/fscarmen/sing-box/issues]"
|
||||
E[48]="Client Fingerprint (current: \${_val})"
|
||||
C[48]="客户端指纹 (当前: \${_val})"
|
||||
E[48]="Client Fingerprint (current: \${VAL_ITEM})"
|
||||
C[48]="客户端指纹 (当前: \${VAL_ITEM})"
|
||||
E[49]="Select more protocols to install (e.g. hgbd). The order of the port numbers of the protocols is related to the ordering of the multiple choices:\n a. all (default)"
|
||||
C[49]="多选需要安装协议(比如 hgbd),协议的端口号次序与多选的排序有关:\n a. all (默认)"
|
||||
E[50]="Please enter the \$TYPE domain name:"
|
||||
@@ -172,8 +172,8 @@ E[65]="Open Realm"
|
||||
C[65]="开启 Realm"
|
||||
E[66]="Please select the protocols to be added (multiple choices possible. Press Enter to skip):"
|
||||
C[66]="请选择需要增加的协议(可以多选,回车跳过):"
|
||||
E[67]="Bind network interface (current: \${_val:-default})"
|
||||
C[67]="指定网络出口 (当前: \${_val:-默认})"
|
||||
E[67]="Bind network interface (current: \${VAL_ITEM:-default})"
|
||||
C[67]="指定网络出口 (当前: \${VAL_ITEM:-默认})"
|
||||
E[68]="Press [n] if there is an error, other keys to continue:"
|
||||
C[68]="如有错误请按 [n],其他键继续:"
|
||||
E[69]="Install sba scripts (argo + sing-box) [https://github.com/fscarmen/sba]"
|
||||
@@ -294,16 +294,16 @@ E[126]="Network request URL structure is wrong. Missing Zone ID"
|
||||
C[126]="网络请求地址(URL)结构不对,缺少 Zone ID"
|
||||
E[127]="Please select what to modify:"
|
||||
C[127]="请选择修改项目:"
|
||||
E[128]="Preferred CDN (current: \${_val})"
|
||||
C[128]="优选域名/IP (当前: \${_val})"
|
||||
E[129]="Reality SNI (current: \${_val})"
|
||||
C[129]="Reality SNI (当前: \${_val})"
|
||||
E[130]="Node name (current: \${_val})"
|
||||
C[130]="节点名称 (当前: \${_val})"
|
||||
E[131]="UUID / Password (current: \${_val})"
|
||||
C[131]="UUID / 密码 (当前: \${_val})"
|
||||
E[132]="Server IP (current: \${_val})"
|
||||
C[132]="服务器 IP (当前: \${_val})"
|
||||
E[128]="Preferred CDN (current: \${VAL_ITEM})"
|
||||
C[128]="优选域名/IP (当前: \${VAL_ITEM})"
|
||||
E[129]="Reality SNI (current: \${VAL_ITEM})"
|
||||
C[129]="Reality SNI (当前: \${VAL_ITEM})"
|
||||
E[130]="Node name (current: \${VAL_ITEM})"
|
||||
C[130]="节点名称 (当前: \${VAL_ITEM})"
|
||||
E[131]="UUID / Password (current: \${VAL_ITEM})"
|
||||
C[131]="UUID / 密码 (当前: \${VAL_ITEM})"
|
||||
E[132]="Server IP (current: \${VAL_ITEM})"
|
||||
C[132]="服务器 IP (当前: \${VAL_ITEM})"
|
||||
E[133]="Invalid IP address format"
|
||||
C[133]="IP 地址格式错误"
|
||||
E[134]="Please enter new value (press Enter to skip):"
|
||||
@@ -370,44 +370,44 @@ reading() { read -rp "$(info "$1")" "$2"; }
|
||||
|
||||
# 预处理:扫描 E/C 数组,把含 $ 的条目下标记录到关联数组,避免 text() 每次调用都启动 grep 子进程
|
||||
declare -A TEXT_NEEDS_EVAL
|
||||
for _text_i in "${!E[@]}"; do
|
||||
[[ "${E[${_text_i}]}" == *'$'* || "${C[${_text_i}]}" == *'$'* ]] && TEXT_NEEDS_EVAL[${_text_i}]=1
|
||||
for TEXT_I in "${!E[@]}"; do
|
||||
[[ "${E[${TEXT_I}]}" == *'$'* || "${C[${TEXT_I}]}" == *'$'* ]] && TEXT_NEEDS_EVAL[${TEXT_I}]=1
|
||||
done
|
||||
unset _text_i
|
||||
unset TEXT_I
|
||||
|
||||
# text <index>:输出当前语言对应的字符串,含 $ 变量的条目用 eval 展开,其余直接 printf
|
||||
text() {
|
||||
local -n _text_arr="${L}" # nameref 指向 E 或 C,零子进程
|
||||
local _text_val="${_text_arr[$*]}"
|
||||
local -n TEXT_ARR="${L}" # nameref 指向 E 或 C,零子进程
|
||||
local TEXT_VAL="${TEXT_ARR[$*]}"
|
||||
if [[ -n "${TEXT_NEEDS_EVAL[$*]}" ]]; then
|
||||
eval "printf '%s' \"${_text_val}\""
|
||||
eval "printf '%s' \"${TEXT_VAL}\""
|
||||
else
|
||||
printf '%s' "${_text_val}"
|
||||
printf '%s' "${TEXT_VAL}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 根据 INSTALL_PROTOCOLS 计算安装流程总步骤数
|
||||
# sing-box 协议分类:Reality 类 (b/j/k)、Hysteria2(c)、WS 类 (h/i)
|
||||
calc_install_steps() {
|
||||
local _total=5 # 固定步骤:协议选择、起始端口、VPS IP、UUID、节点名
|
||||
local STEP_TOTAL=5 # 固定步骤:协议选择、起始端口、VPS IP、UUID、节点名
|
||||
local HAS_REALITY=false HAS_WS=false HAS_HY2=false
|
||||
for _P in "${INSTALL_PROTOCOLS[@]}"; do
|
||||
[[ "$_P" =~ ^[bjk]$ ]] && HAS_REALITY=true
|
||||
[[ "$_P" =~ ^[hi]$ ]] && HAS_WS=true
|
||||
[[ "$_P" == 'c' ]] && HAS_HY2=true
|
||||
for PROTO in "${INSTALL_PROTOCOLS[@]}"; do
|
||||
[[ "$PROTO" =~ ^[bjk]$ ]] && HAS_REALITY=true
|
||||
[[ "$PROTO" =~ ^[hi]$ ]] && HAS_WS=true
|
||||
[[ "$PROTO" == 'c' ]] && HAS_HY2=true
|
||||
done
|
||||
[[ "$IS_SUB" = 'is_sub' || "$IS_ARGO" = 'is_argo' ]] && (( _total++ )) # nginx 端口
|
||||
$HAS_REALITY && (( _total++ )) # Reality 私钥
|
||||
$HAS_WS && (( _total++ )) # CDN / 域名
|
||||
[[ "$IS_SUB" = 'is_sub' || "$IS_ARGO" = 'is_argo' ]] && (( STEP_TOTAL++ )) # nginx 端口
|
||||
$HAS_REALITY && (( STEP_TOTAL++ )) # Reality 私钥
|
||||
$HAS_WS && (( STEP_TOTAL++ )) # CDN / 域名
|
||||
# Hysteria2 Realm / WARP / Port Hopping are protocol sub-options and are not counted as install steps.
|
||||
[ "$IS_ARGO" = 'is_argo' ] && (( _total++ )) # Argo 域名
|
||||
TOTAL_STEPS=$_total
|
||||
[ "$IS_ARGO" = 'is_argo' ] && (( STEP_TOTAL++ )) # Argo 域名
|
||||
TOTAL_STEPS=$STEP_TOTAL
|
||||
}
|
||||
|
||||
# 检测是否需要启用 Github CDN,如能直接连通 api.github.com,则不使用
|
||||
check_cdn() {
|
||||
local PROXY CODE PID CMD
|
||||
local _WAIT_COUNT=40
|
||||
local WAIT_COUNT=40
|
||||
local PIDS=()
|
||||
local API_URL='https://api.github.com/repos/SagerNet/sing-box/releases'
|
||||
|
||||
@@ -421,13 +421,13 @@ check_cdn() {
|
||||
return
|
||||
fi
|
||||
|
||||
# 获取 HTTP 状态码
|
||||
# 获取 HTTP 状态码(HEAD 探测:只取响应头,不下载 body;--tries=1 避免被墙时 wget 默认 20 次重试放大延迟)
|
||||
get_code() {
|
||||
local url=$1
|
||||
if [ "$CMD" = 'wget' ]; then
|
||||
wget -qT5 -O /dev/null --server-response "$url" 2>&1 | awk '/HTTP\//{code=$2} END{print code}'
|
||||
wget -q --spider --tries=1 -T5 -O /dev/null --server-response "$url" 2>&1 | awk '/HTTP\//{code=$2} END{print code}'
|
||||
else
|
||||
curl -skL -w "%{http_code}" "$url" -o /dev/null
|
||||
curl -skL -I --connect-timeout 3 --max-time 5 -o /dev/null -w '%{http_code}' "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -448,9 +448,9 @@ check_cdn() {
|
||||
done
|
||||
|
||||
# 等第一个返回 200 的代理,超时则回退为直连,避免无限等待卡死
|
||||
while [ ! -e "${TEMP_DIR}/cdn_proxy" ] && [ "$_WAIT_COUNT" -gt 0 ]; do
|
||||
while [ ! -e "${TEMP_DIR}/cdn_proxy" ] && [ "$WAIT_COUNT" -gt 0 ]; do
|
||||
sleep 0.05
|
||||
(( _WAIT_COUNT-- )) || true
|
||||
(( WAIT_COUNT-- )) || true
|
||||
done
|
||||
|
||||
[ -e "${TEMP_DIR}/cdn_proxy" ] && GH_PROXY=$(cat "${TEMP_DIR}/cdn_proxy") || GH_PROXY=''
|
||||
@@ -704,11 +704,11 @@ change_config() {
|
||||
|
||||
# 显示动态菜单
|
||||
hint "\n $(text 127)\n"
|
||||
for _i in "${!MENU_IDX[@]}"; do
|
||||
local _val="${MENU_VAL[_i]}"
|
||||
local _raw
|
||||
eval "_raw=\"\${${L}[${MENU_IDX[_i]}]}\""
|
||||
eval "hint \" $(printf '%2d' $(( _i+1 ))). ${_raw}\""
|
||||
for MENU_INDEX in "${!MENU_IDX[@]}"; do
|
||||
local VAL_ITEM="${MENU_VAL[MENU_INDEX]}"
|
||||
local RAW_ITEM
|
||||
eval "RAW_ITEM=\"\${${L}[${MENU_IDX[MENU_INDEX]}]}\""
|
||||
eval "hint \" $(printf '%2d' $(( MENU_INDEX+1 ))). ${RAW_ITEM}\""
|
||||
done
|
||||
hint ""
|
||||
reading " $(text 24) " CHOOSE_NODE_INFO
|
||||
@@ -864,9 +864,9 @@ change_config() {
|
||||
[ "$iface" != "lo" ] && IFACE_LIST+=("$iface")
|
||||
done < <(ifconfig -a 2>/dev/null | awk '/^[a-zA-Z]/')
|
||||
else
|
||||
for _if in /sys/class/net/*; do
|
||||
_if="${_if##*/}"
|
||||
[ "$_if" != "lo" ] && IFACE_LIST+=("$_if")
|
||||
for IFACE_ITEM in /sys/class/net/*; do
|
||||
IFACE_ITEM="${IFACE_ITEM##*/}"
|
||||
[ "$IFACE_ITEM" != "lo" ] && IFACE_LIST+=("$IFACE_ITEM")
|
||||
done
|
||||
fi
|
||||
mapfile -t IFACE_LIST < <(printf '%s\n' "${IFACE_LIST[@]}" | sort -u)
|
||||
@@ -874,8 +874,8 @@ change_config() {
|
||||
|
||||
hint "\n $(text 77) \n"
|
||||
hint " $(text 78) "
|
||||
for _if in "${IFACE_LIST[@]}"; do
|
||||
hint " $IDX. $_if"
|
||||
for IFACE_ITEM in "${IFACE_LIST[@]}"; do
|
||||
hint " $IDX. $IFACE_ITEM"
|
||||
((IDX++))
|
||||
done
|
||||
hint " 0. $(text 35)"
|
||||
@@ -1181,8 +1181,8 @@ create_argo_tunnel() {
|
||||
input_nginx_port() {
|
||||
local NUM=$1
|
||||
local PORT_ERROR_TIME=6
|
||||
# 生成 1000 - 65535 随机默认端口数
|
||||
local PORT_NGINX_DEFAULT=$(shuf -i ${MIN_PORT}-${MAX_PORT} -n 1)
|
||||
# 在脚本端口范围(MIN_PORT-MAX_PORT)内随机生成一个未被系统占用的默认端口(与 clash_api 共用 find_free_port)
|
||||
local PORT_NGINX_DEFAULT=$(find_free_port)
|
||||
[[ "$IS_FAST_INSTALL" = 'is_fast_install' && -z "$PORT_NGINX" ]] && PORT_NGINX="$PORT_NGINX_DEFAULT"
|
||||
while true; do
|
||||
[[ "$PORT_ERROR_TIME" > 1 && "$PORT_ERROR_TIME" < 6 ]] && unset IN_USED PORT_NGINX
|
||||
@@ -1590,12 +1590,12 @@ custom_route_add() {
|
||||
|
||||
# 将逗号分隔输入转为数组:支持半角/全角逗号、顿号、分号、竖线;不使用 IFS
|
||||
custom_route_csv_to_array() {
|
||||
local _input="$1"
|
||||
local -n _out_array="$2"
|
||||
_out_array=()
|
||||
local INPUT_CSV="$1"
|
||||
local -n OUT_ARRAY="$2"
|
||||
OUT_ARRAY=()
|
||||
|
||||
mapfile -t _out_array < <(
|
||||
printf '%s\n' "$_input" |
|
||||
mapfile -t OUT_ARRAY < <(
|
||||
printf '%s\n' "$INPUT_CSV" |
|
||||
sed 's/\x1b\[[0-9;?]*[A-Za-z]//g; s/\^\[\[[0-9;?]*[A-Za-z]//g; s/[,、;;|]/,/g; s/[[:space:]]//g; s/,/\n/g; /^$/d'
|
||||
)
|
||||
}
|
||||
@@ -2127,13 +2127,9 @@ cmd_systemctl() {
|
||||
rc-service "$2" restart >/dev/null 2>&1
|
||||
;;
|
||||
reload )
|
||||
rc-service "$2" reload >/dev/null 2>&1 || rc-service "$2" restart >/dev/null 2>&1
|
||||
local MAINPID=$(cat /var/run/sing-box.pid 2>/dev/null)
|
||||
if [ -n "$MAINPID" ] && kill -0 "$MAINPID" 2>/dev/null; then
|
||||
kill -HUP "$MAINPID" 2>/dev/null
|
||||
info "\n $(text 95) \n"
|
||||
else
|
||||
rc-service "$2" restart >/dev/null 2>&1
|
||||
fi
|
||||
[ -n "$MAINPID" ] && info "\n $(text 95) \n"
|
||||
;;
|
||||
status )
|
||||
rc-service "$2" status
|
||||
@@ -2158,13 +2154,9 @@ cmd_systemctl() {
|
||||
[ "$IS_CENTOS" = 'CentOS7' ] && [ "$2" = 'sing-box' ] && [ -s $WORK_DIR/nginx.conf ] && nginx_run
|
||||
;;
|
||||
reload )
|
||||
systemctl reload sing-box >/dev/null 2>&1 || systemctl restart sing-box >/dev/null 2>&1
|
||||
local MAINPID=$(systemctl show -p MainPID sing-box 2>/dev/null | awk -F= '{print $2}')
|
||||
if [ -n "$MAINPID" ] && [ "$MAINPID" -gt 0 ] 2>/dev/null; then
|
||||
systemctl kill -s HUP sing-box >/dev/null 2>&1
|
||||
info "\n $(text 95) \n"
|
||||
else
|
||||
systemctl restart sing-box >/dev/null 2>&1
|
||||
fi
|
||||
[ -n "$MAINPID" ] && [ "$MAINPID" -gt 0 ] 2>/dev/null && info "\n $(text 95) \n"
|
||||
;;
|
||||
status )
|
||||
systemctl is-active "$2"
|
||||
@@ -2474,10 +2466,10 @@ input_start_port() {
|
||||
sing-box_variables() {
|
||||
STEP_NUM=0
|
||||
# 预先用全选协议计算最大总步骤数,用于协议选择提示时显示 (1/?)
|
||||
local _saved_protocols=("${INSTALL_PROTOCOLS[@]}")
|
||||
local SAVED_PROTOCOLS=("${INSTALL_PROTOCOLS[@]}")
|
||||
INSTALL_PROTOCOLS=(b c d e f g h i j k l m)
|
||||
calc_install_steps
|
||||
INSTALL_PROTOCOLS=("${_saved_protocols[@]}")
|
||||
INSTALL_PROTOCOLS=("${SAVED_PROTOCOLS[@]}")
|
||||
|
||||
if grep -qi 'cloudflare' <<< "$ASNORG4$ASNORG6"; then
|
||||
if grep -qi 'cloudflare' <<< "$ASNORG6" && [ -n "$WAN4" ] && ! grep -qi 'cloudflare' <<< "$ASNORG4"; then
|
||||
@@ -3495,6 +3487,76 @@ http {
|
||||
echo "$NGINX_CONF" > ${WORK_DIR}/nginx.conf
|
||||
}
|
||||
|
||||
# ==================== 流量统计 ====================
|
||||
# 流量与单位换算:四舍五入保留 1 位小数
|
||||
format_traffic() {
|
||||
local BYTES=$1
|
||||
[ "$BYTES" -lt 1024 ] && { echo "${BYTES} B"; return; }
|
||||
local DIV UNIT
|
||||
if [ "$BYTES" -lt $((1024 * 1024)) ]; then
|
||||
DIV=1024; UNIT=KB
|
||||
elif [ "$BYTES" -lt $((1024 * 1024 * 1024)) ]; then
|
||||
DIV=$((1024 * 1024)); UNIT=MB
|
||||
elif [ "$BYTES" -lt $((1024 * 1024 * 1024 * 1024)) ]; then
|
||||
DIV=$((1024 * 1024 * 1024)); UNIT=GB
|
||||
else
|
||||
DIV=$((1024 * 1024 * 1024 * 1024)); UNIT=TB
|
||||
fi
|
||||
local IDX=$((BYTES / DIV))
|
||||
local REM=$(( ((BYTES % DIV) * 10 + DIV / 2) / DIV ))
|
||||
[ "$REM" -ge 10 ] && { IDX=$((IDX + 1)); REM=0; }
|
||||
echo "${IDX}.${REM} ${UNIT}"
|
||||
}
|
||||
|
||||
# 在脚本限制的端口范围内(MIN_PORT-MAX_PORT)随机找一个未被系统占用的端口。
|
||||
# 逻辑统一为数组承载候选端口:一次性随机生成 16 个端口装入数组,
|
||||
# 逐个用 ss -nltp 探测占用情况,返回第一个空闲端口;全部占用则返回 1。
|
||||
# 用两次 RANDOM 组合成 0-65535 的随机值再取模,避免单次 RANDOM(0-32767) 无法覆盖完整范围。
|
||||
# 供 nginx 默认端口(input_nginx_port)与 clash_api 端口(find_free_api_port)共用。
|
||||
find_free_port() {
|
||||
local CAND=() SPAN=$((MAX_PORT - MIN_PORT + 1)) IDX PORT
|
||||
for IDX in $(seq 1 16); do
|
||||
CAND+=("$((MIN_PORT + ((RANDOM * 2) + (RANDOM % 2)) % SPAN))")
|
||||
done
|
||||
for PORT in "${CAND[@]}"; do
|
||||
if ! ss -nltp 2>/dev/null | grep -q ":${PORT} "; then
|
||||
echo "$PORT"; return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# 在脚本限制的端口范围内随机找一个未被占用的空闲端口,供 clash_api 监听(复用 find_free_port)。
|
||||
find_free_api_port() {
|
||||
local PORT
|
||||
PORT=$(find_free_port) || PORT=10000 # 探测失败则回退默认值
|
||||
echo "$PORT"
|
||||
}
|
||||
|
||||
# 获取 /connections 流量数据并缓存到全局变量 STATS_JSON(静默降级:任一前置不满足即返回 1)
|
||||
# clash_api 由官方二进制默认编译(with_clash_api),无需版本门控;
|
||||
# /connections 返回 { downloadTotal, uploadTotal, connections: [...] },
|
||||
# downloadTotal / uploadTotal 为进程生命周期累计值
|
||||
ensure_stats_data() {
|
||||
[ -n "$STATS_JSON" ] && return 0
|
||||
[ "${STATUS[0]}" != "$(text 28)" ] && return 1 # Sing-box 未运行
|
||||
[ ! -x "$WORK_DIR/sing-box" ] && return 1
|
||||
|
||||
local API_PORT
|
||||
# 从 04_experimental.json 解析 clash_api 监听端口(external_controller 形如 127.0.0.1:<port>)。
|
||||
# 单条 sed 正则提取(排除 // 注释行),不依赖 jq / 多段管道。
|
||||
API_PORT=$(sed -n '/^[[:space:]]*\/\//!s/.*"external_controller"[[:space:]]*:[[:space:]]*"127\.0\.0\.1:\([0-9][0-9]*\)".*/\1/p' "$WORK_DIR/conf/04_experimental.json" 2>/dev/null | head -1)
|
||||
[ -z "$API_PORT" ] && return 1
|
||||
|
||||
# curl 优先,wget 兜底(脚本安装时已依赖 wget,必存在)
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
STATS_JSON=$(curl -fsS --max-time 3 "http://127.0.0.1:${API_PORT}/connections" 2>/dev/null) || return 1
|
||||
else
|
||||
STATS_JSON=$(wget -qO- --timeout=3 "http://127.0.0.1:${API_PORT}/connections" 2>/dev/null) || return 1
|
||||
fi
|
||||
[ -n "$STATS_JSON" ] || return 1
|
||||
}
|
||||
|
||||
# 生成 sing-box 基础配置
|
||||
generate_sing_box_base_conf() {
|
||||
# 生成 log 配置
|
||||
@@ -3601,13 +3663,21 @@ EOF
|
||||
}
|
||||
EOF
|
||||
|
||||
# 生成缓存文件
|
||||
# 生成缓存文件 + clash_api 流量统计。clash_api 是官方二进制默认编译功能
|
||||
# (with_clash_api 在 DEFAULT_BUILD_TAGS 中),无版本门控;无需枚举
|
||||
# inbound/outbound tag(clash_api 默认统计所有流量)。
|
||||
# 新装场景(generate_sing_box_base_conf 由 sing-box_json 首次调用)inbound 尚未生成
|
||||
# 也不影响 clash_api 注入,此处统一直接写入。
|
||||
CLASH_API_PORT=$(find_free_api_port)
|
||||
cat > ${WORK_DIR}/conf/04_experimental.json << EOF
|
||||
{
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"path": "${WORK_DIR}/cache.db"
|
||||
},
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:${CLASH_API_PORT}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4495,6 +4565,25 @@ fetch_nodes_value() {
|
||||
|
||||
# 获取 naive key-value
|
||||
[ -s ${WORK_DIR}/conf/*_${NODE_TAG[11]}_inbounds.json ] && local JSON=$(cat ${WORK_DIR}/conf/*_${NODE_TAG[11]}_inbounds.json) && NODE_NAME[22]=$(sed -n "s/.*\"tag\":\"\(.*\) ${NODE_TAG[11]}.*/\1/p" <<< "$JSON") && PORT_NAIVE=$(sed -n 's/.*"listen_port":\([0-9]\+\),/\1/gp' <<< "$JSON") && UUID[22]=$(awk -F '"' '/"username"/{print $4; exit}' <<< "$JSON")
|
||||
|
||||
# 兜底:极早期版本 04_experimental.json 无 clash_api(cache_file-only),补全注入。
|
||||
# 同时剥离可能残留的 v2ray_api(官方 release 二进制默认不编译该功能,
|
||||
# 若旧版脚本已注入会在启动时报 "v2ray api is not included in this build")。
|
||||
# 升级/change 场景 generate_sing_box_base_conf 已统一写入 clash_api,这里无需重复。
|
||||
if ! grep -q 'clash_api' ${WORK_DIR}/conf/04_experimental.json 2>/dev/null; then
|
||||
CLASH_API_PORT=$(find_free_api_port)
|
||||
local EXP_JSON=$(cat ${WORK_DIR}/conf/04_experimental.json)
|
||||
printf '%s\n' "$EXP_JSON" | "$DIR/jq" 'del(.experimental.v2ray_api) | .experimental += {
|
||||
"clash_api": {
|
||||
"external_controller": "127.0.0.1:'"$CLASH_API_PORT"'"
|
||||
}
|
||||
}' > ${WORK_DIR}/conf/04_experimental.json
|
||||
# 补全后立即热加载(SIGHUP)使 clash_api 监听马上生效;
|
||||
# 前台同步执行确保信号送达(SIGHUP reload 不断连 SSH);仅当 sing-box 运行中才 reload
|
||||
if pgrep -x sing-box >/dev/null 2>&1; then
|
||||
cmd_systemctl reload sing-box
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 获取 Argo 临时隧道域名
|
||||
@@ -5295,6 +5384,29 @@ $(hint "$(text 82) 2:")
|
||||
$(${WORK_DIR}/qrencode $SUBSCRIBE_ADDRESS/${UUID_CONFIRM}/auto2)
|
||||
"
|
||||
|
||||
# === 流量统计块(仅 clash_api 可用时显示;流量为 0 时显示 0 B) ===
|
||||
# downloadTotal / uploadTotal 为 clash_api 提供的进程生命周期累计值
|
||||
STATS_JSON='' # 每次调用 export_list 都是独立的用户请求,强制刷新获取实时数据
|
||||
if ensure_stats_data; then
|
||||
local IN_SUM OUT_SUM
|
||||
IN_SUM=$(echo "$STATS_JSON" | $WORK_DIR/jq '.downloadTotal // 0' 2>/dev/null)
|
||||
OUT_SUM=$(echo "$STATS_JSON" | $WORK_DIR/jq '.uploadTotal // 0' 2>/dev/null)
|
||||
EXPORT_LIST_FILE="${EXPORT_LIST_FILE}
|
||||
|
||||
*******************************************
|
||||
┌────────────────┐
|
||||
│ │
|
||||
│ $(warning "Traffic Stats") │
|
||||
│ │
|
||||
└────────────────┘
|
||||
---------------------------
|
||||
|
||||
$(info "⬇ Inbound (total): $(format_traffic $IN_SUM)")
|
||||
$(hint "⬆ Outbound (total): $(format_traffic $OUT_SUM)")
|
||||
"
|
||||
fi
|
||||
# === 结束 ===
|
||||
|
||||
# 生成并显示节点信息
|
||||
echo "$EXPORT_LIST_FILE" > ${WORK_DIR}/list
|
||||
cat ${WORK_DIR}/list
|
||||
@@ -5327,7 +5439,7 @@ change_protocols() {
|
||||
INSTALLED_PROTOCOLS_LIST=$(awk -F '"' '/"tag":/{print $4}' ${WORK_DIR}/conf/*_inbounds.json | grep -v 'shadowtls-in' | awk '{print $NF}')
|
||||
for f in ${!NODE_TAG[@]}; do [[ $INSTALLED_PROTOCOLS_LIST =~ "${NODE_TAG[f]}" ]] && EXISTED_PROTOCOLS+=("${PROTOCOL_LIST[f]}") || NOT_EXISTED_PROTOCOLS+=("${PROTOCOL_LIST[f]}"); done
|
||||
|
||||
# 列出已安装协议
|
||||
# 列出已安装协议(保持原有样式,仅显示协议名;F2 流量显示已移除,见需求文档 3.3 节)
|
||||
hint "\n $(text 136) (${#EXISTED_PROTOCOLS[@]})"
|
||||
for h in "${!EXISTED_PROTOCOLS[@]}"; do
|
||||
hint " $(asc $(( h+97 ))). ${EXISTED_PROTOCOLS[h]} "
|
||||
@@ -5857,10 +5969,22 @@ menu() {
|
||||
printf '%-11s' "$s"
|
||||
fi
|
||||
}
|
||||
local _SBV; printf -v _SBV '%-26s' "$SING_BOX_VERSION"
|
||||
local _AV; printf -v _AV '%-26s' "$ARGO_VERSION"
|
||||
local _NV; printf -v _NV '%-26s' "$NGINX_VERSION"
|
||||
info "\t Sing-box: $(_sv "${STATUS[0]}") ${_SBV}${SING_BOX_MEMORY_USAGE}\n\t Argo: $(_sv "${STATUS[1]}") ${_AV}${ARGO_MEMORY_USAGE}\n\t Nginx: $(_sv "${STATUS[2]}") ${_NV}${NGINX_MEMORY_USAGE}"
|
||||
local SBV; printf -v SBV '%-26s' "$SING_BOX_VERSION"
|
||||
local AV; printf -v AV '%-26s' "$ARGO_VERSION"
|
||||
local NV; printf -v NV '%-26s' "$NGINX_VERSION"
|
||||
# === 计算 Sing-box 行流量(仅数据可用时显示;流量为 0 时显示 0 B) ===
|
||||
# downloadTotal / uploadTotal 为 clash_api 提供的进程生命周期累计值
|
||||
local SB_TRAFFIC=""
|
||||
if ensure_stats_data 2>/dev/null; then
|
||||
local IN_SUM OUT_SUM
|
||||
IN_SUM=$(echo "$STATS_JSON" | $WORK_DIR/jq '.downloadTotal // 0' 2>/dev/null)
|
||||
OUT_SUM=$(echo "$STATS_JSON" | $WORK_DIR/jq '.uploadTotal // 0' 2>/dev/null)
|
||||
SB_TRAFFIC=" ⬇$(format_traffic $IN_SUM) ⬆$(format_traffic $OUT_SUM)"
|
||||
fi
|
||||
# === 结束 ===
|
||||
info "\t Sing-box: $(_sv "${STATUS[0]}") ${SBV}${SING_BOX_MEMORY_USAGE}${SB_TRAFFIC}"
|
||||
info "\t Argo: $(_sv "${STATUS[1]}") ${AV}${ARGO_MEMORY_USAGE}"
|
||||
info "\t Nginx: $(_sv "${STATUS[2]}") ${NV}${NGINX_MEMORY_USAGE}"
|
||||
echo -e "\n======================================================================================================================\n"
|
||||
for ((b=1;b<=${#OPTION[*]};b++)); do [ "$b" = "${#OPTION[*]}" ] && hint " ${OPTION[0]} " || hint " ${OPTION[b]} "; done
|
||||
reading "\n $(text 24) " CHOOSE
|
||||
@@ -5876,6 +6000,51 @@ menu() {
|
||||
check_cdn
|
||||
statistics_of_run_times update sing-box.sh 2>/dev/null
|
||||
|
||||
###### 为了给旧版本 04_experimental.json 补全 clash_api 配置并剥离 v2ray_api,将于 2026年12月31日移除
|
||||
if [ -x "$WORK_DIR/jq" ] && [ -s "$WORK_DIR/conf/04_experimental.json" ] && [ -x "$WORK_DIR/sing-box" ] && [[ "$(date +%Y%m%d)" < "20261231" ]]; then
|
||||
# 旧版本 04_experimental.json 可能缺 clash_api、或含 v2ray_api(旧版脚本注入的)。
|
||||
# clash_api 是官方 release 二进制默认编译功能(with_clash_api),直接补全;
|
||||
# v2ray_api 官方 release 二进制默认不编译,残留会导致启动失败,必须剥离。
|
||||
if ! grep -q 'clash_api' "$WORK_DIR/conf/04_experimental.json" || grep -q 'v2ray_api' "$WORK_DIR/conf/04_experimental.json"; then
|
||||
API_PORT=$(find_free_api_port)
|
||||
if grep -q 'clash_api' "$WORK_DIR/conf/04_experimental.json"; then
|
||||
# 已有 clash_api(可能由新版脚本生成),仅剥离残留的 v2ray_api
|
||||
grep -v '^//' "$WORK_DIR/conf/04_experimental.json" | $WORK_DIR/jq 'del(.experimental.v2ray_api)' > "$TEMP_DIR/exp_clash_api_tmp.json" 2>/dev/null
|
||||
else
|
||||
# 缺失 clash_api,剥离 v2ray_api 并补全 clash_api
|
||||
grep -v '^//' "$WORK_DIR/conf/04_experimental.json" | $WORK_DIR/jq --arg ec "127.0.0.1:${API_PORT}" '
|
||||
del(.experimental.v2ray_api) | .experimental += {
|
||||
"clash_api": { "external_controller": $ec }
|
||||
}
|
||||
' > "$TEMP_DIR/exp_clash_api_tmp.json" 2>/dev/null
|
||||
fi
|
||||
[ -s "$TEMP_DIR/exp_clash_api_tmp.json" ] && mv "$TEMP_DIR/exp_clash_api_tmp.json" "$WORK_DIR/conf/04_experimental.json" && {
|
||||
# 修改了 experimental 配置,用 SIGHUP 热加载使 API 监听生效(PID 不变,SSH 连接不断)。
|
||||
# 此处在 check_system_info() 之前(SYSTEM 未设置)且 select_language() 之前(L 未设置),
|
||||
# 注意:不能调用 cmd_systemctl reload——其成功分支会调用 info/text(nameref 依赖 L),
|
||||
# 与 Alpine 分支直接 kill -HUP 对称。前台同步执行确保信号送达(SIGHUP 不断连 SSH)。
|
||||
if [ -d /run/openrc ] || command -v rc-service >/dev/null 2>&1; then
|
||||
# Alpine:kill -HUP 主进程(与 cmd_systemctl reload 的 Alpine 分支一致);PID 不存在时降级 restart。
|
||||
SB_PID=$(cat /var/run/sing-box.pid 2>/dev/null)
|
||||
if [ -n "$SB_PID" ] && kill -0 "$SB_PID" 2>/dev/null; then
|
||||
kill -HUP "$SB_PID" 2>/dev/null
|
||||
else
|
||||
rc-service sing-box restart >/dev/null 2>&1
|
||||
fi
|
||||
else
|
||||
# systemd 等:复刻 cmd_systemctl reload 的 systemd 分支(SIGHUP 热加载);PID 不存在时降级 restart。
|
||||
SB_MAINPID=$(systemctl show -p MainPID sing-box 2>/dev/null | awk -F= '{print $2}')
|
||||
if [ -n "$SB_MAINPID" ] && [ "$SB_MAINPID" -gt 0 ] 2>/dev/null; then
|
||||
systemctl kill -s HUP sing-box >/dev/null 2>&1
|
||||
else
|
||||
systemctl restart sing-box >/dev/null 2>&1
|
||||
fi
|
||||
unset SB_MAINPID
|
||||
fi
|
||||
}
|
||||
fi
|
||||
fi
|
||||
|
||||
###### 为了把原来的 nekobox 换成 Throne 做的处理,将于 2026年9月30日移除
|
||||
if [ -s $WORK_DIR/nginx.conf ] && grep -q 'Neko|Throne' $WORK_DIR/nginx.conf; then
|
||||
sed -i 's@~\*Neko|Throne.*@~*Throne|Neko /throne; # 匹配 Throne / Neko 客户端@g' "$WORK_DIR/nginx.conf"
|
||||
@@ -5888,21 +6057,21 @@ fi
|
||||
[[ "${*^^}" =~ '-E'|'-K' ]] && L=E
|
||||
[[ "${*^^}" =~ '-C'|'-B'|'-L' ]] && L=C
|
||||
# 支持在 select_language 前识别 --LANGUAGE,避免 KV 无交互安装仍弹出语言选择。
|
||||
for ((_param_i=1; _param_i<=$#; _param_i++)); do
|
||||
eval "_param_v=\${${_param_i}}"
|
||||
case "${_param_v^^}" in
|
||||
for ((PARAM_I=1; PARAM_I<=$#; PARAM_I++)); do
|
||||
eval "PARAM_V=\${${PARAM_I}}"
|
||||
case "${PARAM_V^^}" in
|
||||
--LANGUAGE )
|
||||
_param_n=$((_param_i+1))
|
||||
eval "_param_lang=\${${_param_n}}"
|
||||
[[ "${_param_lang^^}" =~ ^C ]] && L=C || L=E
|
||||
PARAM_N=$((PARAM_I+1))
|
||||
eval "PARAM_LANG=\${${PARAM_N}}"
|
||||
[[ "${PARAM_LANG^^}" =~ ^C ]] && L=C || L=E
|
||||
;;
|
||||
--LANGUAGE=* )
|
||||
_param_lang="${_param_v#*=}"
|
||||
[[ "${_param_lang^^}" =~ ^C ]] && L=C || L=E
|
||||
PARAM_LANG="${PARAM_V#*=}"
|
||||
[[ "${PARAM_LANG^^}" =~ ^C ]] && L=C || L=E
|
||||
;;
|
||||
esac
|
||||
done
|
||||
unset _param_i _param_v _param_n _param_lang
|
||||
unset PARAM_I PARAM_V PARAM_N PARAM_LANG
|
||||
|
||||
# 获取 -F 参数的值
|
||||
CONFIG_FILE=$(awk '-F[ =]' 'tolower($1) ~ /^-f$/{print $2}' <<< "$*")
|
||||
@@ -6081,4 +6250,4 @@ elif [ "$IS_FAST_INSTALL" = 'is_fast_install' ]; then
|
||||
else
|
||||
menu_setting
|
||||
menu
|
||||
fi
|
||||
fi
|
||||
Reference in New Issue
Block a user