⛄ Sync 2026-06-30 01:11:33

This commit is contained in:
github-actions[bot] committed 2026-06-30 01:11:33 +08:00
1 parent fb3826f4ad
commit a09508c01e
157 files changed
+78873 -73698

No files matched your search

+217 -67
View File
@@ -741,13 +741,61 @@ has_mihomo_binary() {
}
use_mihomo_for_ss_rust_client() {
has_ss_rust_client_binary && return 1
# 检查核心是否存在 (has_ss_rust_client_binary 成功返回 0,失败返回 1)
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
has_ss_rust_client_binary
local has_ss=$?
has_mihomo_binary
local has_mh=$?
# echolog "DEBUG: sslocal_path=[$has_ss], mihomo_path=[$has_mh], enable_mihomo=[$enable_mihomo]"
if [ "$enable_mihomo" = "1" ]; then
if [ $has_mh -eq 0 ]; then
return 0 # 使用 Mihomo
elif [ $has_ss -eq 0 ]; then
return 1 # Mihomo 不存在,回退到 sslocal
fi
else
if [ $has_ss -eq 0 ]; then
return 1 # 使用 sslocal
elif [ $has_mh -eq 0 ]; then
return 0 # sslocal 不存在,回退到 Mihomo
fi
fi
# 如果两个都不存在,默认走 sslocal 逻辑
return 1
}
use_mihomo_for_ss_rust_server() {
has_ss_rust_server_binary && return 1
# 检查核心是否存在 (has_ss_rust_client_binary 成功返回 0,失败返回 1)
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
has_ss_rust_server_binary
local has_ss=$?
has_mihomo_binary
local has_mh=$?
# echolog "DEBUG: sslocal_path=[$has_ss], mihomo_path=[$has_mh], enable_mihomo=[$enable_mihomo]"
if [ "$enable_mihomo" = "1" ]; then
if [ $has_mh -eq 0 ]; then
return 0 # 使用 Mihomo
elif [ $has_ss -eq 0 ]; then
return 1 # Mihomo 不存在,回退到 ssserver
fi
else
if [ $has_ss -eq 0 ]; then
return 1 # 使用 ssserver
elif [ $has_mh -eq 0 ]; then
return 0 # ssserver 不存在,回退到 Mihomo
fi
fi
# 如果两个都不存在,默认走 ssserver 逻辑
return 1
}
start_v2ray_with_mihomo() {
@@ -1102,7 +1150,6 @@ start_dns() {
fi
}
# 生成 Xray 服务端配置
# 生成 Xray 服务端配置
generate_xray_config() {
local type="$1"
@@ -1388,7 +1435,7 @@ start_udp() {
elif [ "$udp_relay_server_type" = "ss-rust" ] && use_mihomo_for_ss_rust_client; then
redir_udp=0
ARG_UDP=""
echolog "UDP TPROXY Relay:ss-rust 主程序不存在,已回退到 Mihomo 主实例处理。"
echolog "UDP TPROXY Relay:$(get_name ${type})-Rust 内核不存在,已回退到 Mihomo ($(get_name ${type})) 主实例处理。"
else
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port
if [ "$udp_relay_server_type" = "ssr" ]; then
@@ -1402,18 +1449,37 @@ start_udp() {
rm -rf "$TMP_PATH/bin/${type}-redir"
fi
ln_start_bin $ss_program ${type}-redir -c $udp_config_file
echolog "UDP TPROXY Relay:$(get_name $type) Started!"
if [ "$udp_relay_server_type" = "ssr" ] || [ "$udp_relay_server_type" = "ss" ]; then
echolog "UDP TPROXY Relay:$(get_name $type) Started!"
elif [ "$udp_relay_server_type" = "ss-rust" ]; then
echolog "UDP TPROXY Relay:$(get_name $type)-Rust Started!"
fi
fi
;;
v2ray)
if has_mihomo_binary; then
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
if [ "$enable_mihomo" = "1" ] && has_mihomo_binary; then
redir_udp=0
ARG_UDP=""
echolog "UDP TPROXY Relay:V2Ray/Xray 单节点由主 Mihomo 实例处理。"
else
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
echolog "UDP TPROXY Relay:$($(first_type xray) version | head -1) Started!"
# enable_mihomo=0 → 尝试 Xray
local xray_bin="$(first_type xray)"
if [ -n "$xray_bin" ]; then
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
echolog "UDP TPROXY Relay:$($(first_type xray) version | head -1) Started!"
else
# Xray 不存在但 Mihomo 存在,回退到 Mihomo(当 enable_mihomo=0 时)
if has_mihomo_binary; then
redir_udp=0
ARG_UDP=""
echolog "UDP TPROXY Relay:Xray 内核不存在,已回退到 Mihomo 内核启动。"
else
echolog "UDP TPROXY Relay:V2Ray/Xray 节点无法启动,Xray 和 Mihomo 内核均不存在。"
return 1
fi
fi
fi
;;
trojan) #client
@@ -1440,11 +1506,24 @@ start_udp() {
local chain_type=$(uci_get_by_name $UDP_RELAY_SERVER chain_type)
case ${chain_type} in
vmess)
if start_v2ray_with_mihomo "$UDP_RELAY_SERVER" "$tmp_udp_port" "0" "${UDP_RELAY_SERVER}-shadowtls-udp" "redir" "shadowtls-vmess-udp" "UDP TPROXY Relay:shadow-tls chain-to"; then
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
if [ "$enable_mihomo" = "1" ] && start_v2ray_with_mihomo "$UDP_RELAY_SERVER" "$tmp_udp_port" "0" "${UDP_RELAY_SERVER}-shadowtls-udp" "redir" "shadowtls-vmess-udp" "UDP TPROXY Relay:shadow-tls chain-to"; then
:
else
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
echolog "UDP TPROXY Relay:shadow-tls chain-to $($(first_type xray) --version) Started!"
# enable_mihomo=0 或 Mihomo 启动失败 → 尝试 Xray
local xray_bin="$(first_type xray)"
if [ -n "$xray_bin" ]; then
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
echolog "UDP TPROXY Relay:shadow-tls chain-to $($(first_type xray) --version) Started!"
else
# Xray 不存在但 Mihomo 存在,回退到 Mihomo(当 enable_mihomo=0 时)
if start_v2ray_with_mihomo "$UDP_RELAY_SERVER" "$tmp_udp_port" "0" "${UDP_RELAY_SERVER}-shadowtls-udp" "redir" "shadowtls-vmess-udp" "UDP TPROXY Relay:shadow-tls chain-to"; then
echolog "UDP TPROXY Relay:shadow-tls chain-to Xray 内核不存在,已回退到 Mihomo 内核启动。。"
else
echolog "UDP TPROXY Relay:shadow-tls chain-to Xray 和 Mihomo 内核均不存在,无法启动。"
return 1
fi
fi
fi
;;
sslocal)
@@ -1505,9 +1584,9 @@ start_local() {
local ss_workdir="$(get_ss_mihomo_workdir "$instance_key")"
ln_start_bin "$mihomo_bin" ss-local -d "$ss_workdir" -f "$ss_workdir/config.yaml"
if [ "$local_server_type" = "ss-rust" ]; then
echolog "Global_Socks5:ss-rust 主程序不存在,已回退到 Mihomo (Shadowsocks) 启动。"
echolog "Global_Socks5:$(get_name $type)-Rust 内核不存在,已回退到 Mihomo ($(get_name $type)-Rust) 启动。"
else
echolog "Global_Socks5:Mihomo (Shadowsocks) Started!"
echolog "Global_Socks5:Mihomo ($(get_name $type)) Started!"
fi
else
gen_config_file $LOCAL_SERVER $type 4 $local_port
@@ -1518,21 +1597,34 @@ start_local() {
rm -rf "$TMP_PATH/bin/${type}-local"
fi
ln_start_bin $ss_program ${type}-local -c $local_config_file
echolog "Global_Socks5:Shadowsocks-rust Started!"
echolog "Global_Socks5:$(get_name $type)-Rust Started!"
fi
;;
v2ray)
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
local instance_key="${LOCAL_SERVER}-local"
[ -n "$probe_instance_key" ] && instance_key="$probe_instance_key"
if start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" "v2ray-local" "Global_Socks5"; then
if [ "$enable_mihomo" = "1" ] && start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" "v2ray-local" "Global_Socks5"; then
:
elif [ "$(uci_get_by_name "$LOCAL_SERVER" v2ray_protocol)" = "snell" ]; then
echolog "Global_Socks5:Snell 需要 Mihomo 内核,且当前 Mihomo 启动失败。"
return 1
elif [ "$_local" == "2" ]; then
gen_config_file $LOCAL_SERVER $type 4 0 $local_port
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
echolog "Global_Socks5:$($(first_type xray) version | head -1) Started!"
# 尝试 Xray
local xray_bin="$(first_type xray)"
if [ -n "$xray_bin" ]; then
gen_config_file $LOCAL_SERVER $type 4 0 $local_port
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
echolog "Global_Socks5:$($(first_type xray) version | head -1) Started!"
else
# Xray 不存在但 Mihomo 存在,回退到 Mihomo(当 enable_mihomo=0 时)
if start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" "v2ray-local" "Global_Socks5"; then
echolog "Global_Socks5:Xray 内核不存在,已回退到 Mihomo 内核启动"
else
echolog "Global_Socks5:Xray 和 Mihomo 内核均不存在,无法启动。"
return 1
fi
fi
else
return 1
fi
@@ -1560,29 +1652,43 @@ start_local() {
ln_start_bin "$mihomo_bin" tuic-local -d "$tuic_workdir" -f "$tuic_workdir/config.yaml"
echolog "Global_Socks5:Mihomo (TUIC) Started!"
;;
shadowtls)
#respective config for global socks and main node
if [ "$_local" == "2" ]; then
gen_config_file $LOCAL_SERVER $type 4 "10${tmp_tcp_local_port}"
shadowtls)
#respective config for global socks and main node
if [ "$_local" == "2" ]; then
gen_config_file $LOCAL_SERVER $type 4 "10${tmp_tcp_local_port}"
gen_config_file $LOCAL_SERVER $type 4 0 $local_port chain/"10${tmp_tcp_local_port}"
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_local_config_file
local chain_type=$(uci_get_by_name $LOCAL_SERVER chain_type)
case ${chain_type} in
vmess)
if start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "${LOCAL_SERVER}-shadowtls-local" "socks" "shadowtls-vmess-local" "Global Socks5 Proxy:shadow-tls chain-to"; then
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
# 优先尝试 Mihomo(enable_mihomo=1 时)
if [ "$enable_mihomo" = "1" ] && start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "${LOCAL_SERVER}-shadowtls-local" "socks" "shadowtls-vmess-local" "Global Socks5 Proxy:shadow-tls chain-to"; then
:
else
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type xray) --version) Started!"
# enable_mihomo=0 或 Mihomo 启动失败 → 尝试 Xray
local xray_bin="$(first_type xray)"
if [ -n "$xray_bin" ]; then
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type xray) --version) Started!"
else
# Xray 不存在但 Mihomo 存在,回退到 Mihomo(当 enable_mihomo=0 时)
if start_v2ray_with_mihomo "$LOCAL_SERVER" "$local_port" "$local_port" "${LOCAL_SERVER}-shadowtls-local" "socks" "shadowtls-vmess-local" "Global Socks5 Proxy:shadow-tls chain-to"; then
echolog "Global Socks5 Proxy:shadow-tls chain-to Xray 内核不存在,已回退到 Mihomo 内核启动。"
else
echolog "Global Socks5 Proxy:shadow-tls chain-to Xray 和 Mihomo 内核均不存在,无法启动。"
return 1
fi
fi
fi
;;
sslocal)
ln_start_bin $(first_type sslocal) sslocal -c $local_config_file
echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type sslocal) --version) Started!"
echolog "Global Socks5 Proxy:shadow-tls chain-to $($(first_type sslocal) --version) Started!"
;;
esac
fi
;;
esac
fi
;;
clash)
echolog "Global_Socks5:Clash total node is only supported when using Same as Global Server."
return 1
@@ -1854,9 +1960,9 @@ Start_Run() {
local ss_workdir="$(get_ss_mihomo_workdir "$GLOBAL_SERVER")"
ln_start_bin "$mihomo_bin" ssr-retcp -d "$ss_workdir" -f "$ss_workdir/config.yaml"
if [ "$global_server_type" = "ss-rust" ]; then
echolog "Main node:ss-rust 主程序不存在,已回退到 Mihomo (Shadowsocks) 启动。"
echolog "Main node:$(get_name ${type})-Rust 内核不存在,已回退到 Mihomo ($(get_name ${type})-Rust) 启动。"
else
echolog "Main node:Mihomo (Shadowsocks) Started!"
echolog "Main node:Mihomo ($(get_name ${type})) Started!"
fi
else
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port
@@ -1867,19 +1973,32 @@ Start_Run() {
rm -rf "$TMP_PATH/bin/${type}-redir"
fi
ln_start_bin $ss_program ${type}-redir -c $tcp_config_file
echolog "Main node:Shadowsocks-rust Started!"
echolog "Main node:$(get_name ${type})-Rust Started!"
fi
;;
v2ray)
if start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "$GLOBAL_SERVER" "redir" "ssr-retcp" "Main node"; then
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
if [ "$enable_mihomo" = "1" ] && start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "$GLOBAL_SERVER" "redir" "ssr-retcp" "Main node"; then
:
elif [ "$(uci_get_by_name "$GLOBAL_SERVER" v2ray_protocol)" = "snell" ]; then
echolog "Main node:Snell 需要 Mihomo 内核,且当前 Mihomo 启动失败。"
return 1
else
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port $socks_port
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
echolog "Main node:$($(first_type xray) version | head -1) Started!"
# enable_mihomo=0 或 Mihomo 启动失败(非 Snell)→ 优先使用 Xray
local xray_bin="$(first_type xray)"
if [ -n "$xray_bin" ]; then
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port $socks_port
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
echolog "Main node:$($(first_type xray) version | head -1) Started!"
else
# Xray 不存在但 Mihomo 存在,回退到 Mihomo(当 enable_mihomo=0 时)
if start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "$GLOBAL_SERVER" "redir" "ssr-retcp" "Main node"; then
echolog "Main node:Xray 内核不存在,已回退到 Mihomo 内核启动。"
else
echolog "Main node:Xray 和 Mihomo 内核均不存在,无法启动。"
return 1
fi
fi
fi
;;
trojan)
@@ -1920,12 +2039,25 @@ Start_Run() {
local chain_type=$(uci_get_by_name $GLOBAL_SERVER chain_type)
case ${chain_type} in
vmess)
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file
if start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "${GLOBAL_SERVER}-shadowtls" "redir" "shadowtls-vmess" "Main node:shadow-tls chain-to"; then
if [ "$enable_mihomo" = "1" ] && start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "${GLOBAL_SERVER}-shadowtls" "redir" "shadowtls-vmess" "Main node:shadow-tls chain-to"; then
:
else
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
echolog "Mian node:shadow-tls chain-to $($(first_type xray) --version) Started!"
# enable_mihomo=0 或 Mihomo 启动失败 → 尝试 Xray
local xray_bin="$(first_type xray)"
if [ -n "$xray_bin" ]; then
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
echolog "Mian node:shadow-tls chain-to $($(first_type xray) --version) Started!"
else
# Xray 不存在但 Mihomo 存在,回退到 Mihomo(当 enable_mihomo=0 时)
if start_v2ray_with_mihomo "$GLOBAL_SERVER" "$tcp_port" "$socks_port" "${GLOBAL_SERVER}-shadowtls" "redir" "shadowtls-vmess" "Main node:shadow-tls chain-to"; then
echolog "Main node:shadow-tls chain-to Xray 内核不存在,已回退到 Mihomo 内核启动。"
else
echolog "Main node:shadow-tls chain-to Xray 和 Mihomo 内核均不存在,无法启动。"
return 1
fi
fi
fi
;;
sslocal)
@@ -2119,9 +2251,9 @@ start_server() {
local ss_server_workdir="$(get_ss_server_mihomo_workdir "$1")"
ln_start_bin "$mihomo_bin" ss-server-$server_count -d "$ss_server_workdir" -f "$ss_server_workdir/config.yaml"
if [ "$node_type" = "ss-rust" ]; then
echolog "Server:ss-rust 主程序不存在,已回退到 Mihomo Shadowsocks Server$server_count 启动。"
echolog "Server:$(get_name ${type})-Rust 内核不存在,已回退到 Mihomo ($(get_name ${type})) Server$server_count 启动。"
else
echolog "Server:Mihomo Shadowsocks Server$server_count Started!"
echolog "Server:Mihomo ($(get_name ${type})) Server$server_count Started!"
fi
else
gen_service_file ${type} $1 $TMP_PATH/ssr-server$server_count.json
@@ -2135,7 +2267,11 @@ start_server() {
rm -rf "$TMP_PATH/bin/${type}-server"
fi
ln_start_bin $ss_program ${type}-server -c $TMP_PATH/ssr-server$server_count.json
echolog "Server: $(get_name ${type}) Server$server_count Started!"
if [ "$node_type" = "ssr" ] || [ "$node_type" = "ss" ]; then
echolog "Server: $(get_name ${type}) Server$server_count Started!"
elif [ "$node_type" = "ss-rust" ]; then
echolog "Server: $(get_name ${type})-Rust Server$server_count Started!"
fi
fi
;;
socks5)
@@ -2166,6 +2302,7 @@ start_server() {
local tcp_guise=$(uci_get_by_name "$1" tcp_guise "none")
local tcp_guise_http_host=$(uci_get_by_name "$1" tcp_guise_http_host "")
local tcp_guise_http_path=$(uci_get_by_name "$1" tcp_guise_http_path "")
local enable_mihomo=$(uci_get_by_type server_subscribe enable_mihomo "")
local mihomo_server_started=0
# 确定密码/UUID
@@ -2186,36 +2323,49 @@ start_server() {
return 1
}
local mihomo_bin="$(first_type mihomo)"
if [ -x "$mihomo_bin" ] && prepare_v2ray_server_mihomo_runtime_config "$1"; then
local server_workdir="$(get_v2ray_server_mihomo_workdir "$1")"
ln_start_bin "$mihomo_bin" mihomo-server-$server_count -d "$server_workdir" -f "$server_workdir/config.yaml"
echolog "Server: Mihomo $type 服务端 Server$server_count 已启动 (端口: $port)"
mihomo_server_started=1
if [ "$enable_mihomo" = "1" ]; then
local mihomo_bin="$(first_type mihomo)"
if [ -x "$mihomo_bin" ] && prepare_v2ray_server_mihomo_runtime_config "$1"; then
local server_workdir="$(get_v2ray_server_mihomo_workdir "$1")"
ln_start_bin "$mihomo_bin" mihomo-server-$server_count -d "$server_workdir" -f "$server_workdir/config.yaml"
echolog "Server: Mihomo $type 服务端 Server$server_count 已启动 (端口: $port)"
mihomo_server_started=1
fi
fi
if [ "$mihomo_server_started" != "1" ]; then
local config_file="$TMP_PATH/xray-server-$server_count.json"
# 生成 Xray 配置
generate_xray_config "$type" "$port" "$password" "$security" "$method" "$network" "$ws_path" "$ws_host" "$grpc_service" "$alter_id" "$tcp_guise" "$tcp_guise_http_host" "$tcp_guise_http_path" "$config_file"
if [ $? -ne 0 ]; then
echolog "Server: Xray $type 配置生成失败"
return 1
fi
local xray_bin=$(first_type xray)
if [ -z "$xray_bin" ]; then
echolog "Server: xray-core 未安装,无法启动 $type 服务端"
return 1
fi
if [ -n "$xray_bin" ]; then
local config_file="$TMP_PATH/xray-server-$server_count.json"
# 生成 Xray 配置
generate_xray_config "$type" "$port" "$password" "$security" "$method" "$network" "$ws_path" "$ws_host" "$grpc_service" "$alter_id" "$tcp_guise" "$tcp_guise_http_host" "$tcp_guise_http_path" "$config_file"
ln_start_bin "$xray_bin" xray-server-$server_count run -c "$config_file"
echolog "Server: Xray $type 服务端 Server$server_count 已启动 (端口: $port)"
if [ $? -ne 0 ]; then
echolog "Server: Xray $type 配置生成失败"
return 1
fi
ln_start_bin "$xray_bin" xray-server-$server_count run -c "$config_file"
echolog "Server: Xray $type 服务端 Server$server_count 已启动 (端口: $port)"
else
# Xray 不存在但 Mihomo 存在,回退到 Mihomo(当 enable_mihomo=0 时)
if [ "$enable_mihomo" != "1" ]; then
local mihomo_bin="$(first_type mihomo)"
if [ -x "$mihomo_bin" ] && prepare_v2ray_server_mihomo_runtime_config "$1"; then
local server_workdir="$(get_v2ray_server_mihomo_workdir "$1")"
ln_start_bin "$mihomo_bin" mihomo-server-$server_count -d "$server_workdir" -f "$server_workdir/config.yaml"
echolog "Server: Xray 内核不存在,已回退到 Mihomo $type 服务端 Server$server_count (端口: $port)"
else
echolog "Server: Xray 和 Mihomo 内核均不可用,无法启动 $type 服务端"
return 1
fi
else
echolog "Server: Xray 和 Mihomo 内核均不可用,无法启动 $type 服务端"
return 1
fi
fi
fi
;;
;;
esac
server_port=$(uci_get_by_name $1 server_port)
if [ "$USE_TABLES" = "nftables" ]; then
@@ -840,14 +840,26 @@ local function build_v2ray_mihomo_proxy(sid)
proxy.down = string_or_nil(get_server_field(sid, "downlink_capacity", "")) and (get_server_field(sid, "downlink_capacity", "") .. " Mbps") or nil
proxy.sni = string_or_nil(get_server_field(sid, "tls_host", ""))
proxy.fingerprint = string_or_nil(get_server_field(sid, "tls_CertSha", ""))
proxy["cert-name"] = string_or_nil(get_server_field(sid, "tls_CertByName", ""))
proxy["skip-cert-verify"] = bool_enabled(get_server_field(sid, "insecure", "0"))
local alpn = split_alpn(get_server_field(sid, "tls_alpn", ""))
if #alpn > 0 then
proxy.alpn = alpn
end
if get_server_field(sid, "flag_obfs", "0") == "1" then
proxy.obfs = string_or_nil(get_server_field(sid, "obfs_type", ""))
local obfs_type = get_server_field(sid, "obfs_type", "")
proxy.obfs = string_or_nil(obfs_type)
proxy["obfs-password"] = string_or_nil(get_server_field(sid, "salamander", ""))
if obfs_type == "gecko" then
local min = tonumber(get_server_field(sid, "obfs_MinPacketSize", "")) or 512
local max = tonumber(get_server_field(sid, "obfs_MaxPacketSize", "")) or 1200
if min <= 0 or min > max or max > 2048 then
min = 512
max = 1200
end
proxy["obfs-min-packet-size"] = min
proxy["obfs-max-packet-size"] = max
end
end
elseif protocol == "socks" then
if not can_mihomo_handle_v2ray_transport(protocol, sid) then
@@ -11,6 +11,14 @@ local socks_port = arg[4] or "0"
local chain = arg[5] or "0"
-- trim
local function trim(text)
if not text or text == "" then
return ""
end
return (text:gsub("^%s*(.-)%s*$", "%1"))
end
-- 辅助函数:拆分字符串(若 luci.util 未加载则定义)
local function split(str, pat)
local t = {}
@@ -54,12 +62,20 @@ if server.type == "ss-rust" then
end
local function parse_realm_uri(uri)
if type(uri) ~= "string" then return nil end
uri = trim(uri)
if uri == "" then return nil end
-- realm[+http]://token@server/realm_id?query
local scheme, token, server_url, realm_id, query = trim(uri):match("^(realm%+http|realm)://([^@]+)@([^/]+)/([^?]*)%??(.*)$")
if not scheme or not token or not server_url or not realm_id then return nil end
local scheme = (uri:match("^realm%+http://") and "realm+http") or (uri:match("^realm://") and "realm")
if not scheme then return nil end
uri = uri:gsub("^realm%+http://", ""):gsub("^realm://", "")
local token, server_url, realm_id, query = uri:match("^([^@]+)@([^/]+)/([^?]*)%??(.*)$")
if not token or not server_url or not realm_id then return nil end
realm_id = realm_id:gsub("/+$", "")
local address, port = server_url:match("^%[?([^%]]+)%]?:?(%d*)$")
local address, port = server_url:match("^%[([^%]]+)%]:(%d+)$") --ipv6:port
if not address then
address, port = server_url:match("^([^:]+):(%d+)$") --ipv4[domain]:port
end
address = address or server_url:match("^%[([^%]]+)%]$") or server_url
port = tonumber(port) or (scheme == "realm+http" and 80 or 443)
local realm = {
scheme = scheme,
@@ -71,9 +87,9 @@ local function parse_realm_uri(uri)
}
-- 解析 query 中的 stun=
local stun_servers
for value in (query or ""):gmatch("[?&]?[Ss][Tt][Uu][Nn]=([^&]+)") do
if not stun_servers then stun_servers = {} end
stun_servers[#stun_servers + 1] = value
for v in (query or ""):gmatch("[Ss][Tt][Uu][Nn]=([^&]+)") do
stun_servers = stun_servers or {}
stun_servers[#stun_servers + 1] = v
end
realm.stun_servers = stun_servers
return realm
@@ -646,10 +662,18 @@ Xray.outbounds = {
local o = {
type = "salamander",
settings = server.salamander and {
password = server.salamander,
packetSize = server.obfs_type == "gecko" and "512-1200" or nil
password = server.salamander
} or nil
}
if server.obfs_type == "gecko" then
local min = tonumber(server.obfs_MinPacketSize) or 512
local max = tonumber(server.obfs_MaxPacketSize) or 1200
if min <= 0 or min > max or max > 2048 then
min = 512
max = 1200
end
o.settings.packetSize = min .. "-" .. max
end
udp[#udp+1] = o
end
if server.hysteria2_realms then
@@ -713,26 +737,11 @@ Xray.outbounds = {
local n_maxsplit = xray_fragment.fragment_maxSplit
--local domainstr = xray_noise.domainStrategy
finalmask.tcp = finalmask.tcp or {}
-- 辅助函数:将逗号分隔的字符串拆分为数组
local function split_to_array(str)
if not str or str == "" then return nil end
local result = {}
for value in string.gmatch(str, "[^,]+") do
value = value:gsub("^%s*(.-)%s*$", "%1") -- 去除首尾空格
if value ~= "" then
table.insert(result, value)
end
end
return #result > 0 and result or nil
end
-- 构建 fragment settings
local fragment_settings = {
packets = (n_packets and n_packets ~= "") and n_packets or nil,
maxSplit = (n_maxsplit and n_maxsplit ~= "") and n_maxsplit or nil
}
-- 根据 Xray 版本决定使用旧格式还是新格式
if xray_version_val <= 260601 then
-- 旧版本:使用 length 和 delay(单个值)
@@ -748,18 +757,30 @@ Xray.outbounds = {
end
else
-- 新版本:使用 lengths 和 delays(数组)
-- 拆分逗号分隔的字符串
-- 将逗号分隔的字符串拆分为数组
local function split_to_array(str)
if not str or str == "" then return nil end
local result = {}
local trimmed = trim(str)
if trimmed and trimmed ~= "" then
trimmed:gsub("[^,]+", function(w)
w = w:gsub("%s+", "")
if w ~= "" then
result[#result + 1] = w
end
end)
end
return #result > 0 and result or nil
end
local lengths_array = split_to_array(n_length)
if lengths_array then
fragment_settings.lengths = lengths_array
end
local delays_array = split_to_array(n_delay)
if delays_array then
fragment_settings.delays = delays_array
end
end
finalmask.tcp[#finalmask.tcp + 1] = {
type = "fragment",
settings = fragment_settings
@@ -956,6 +977,9 @@ local hysteria2 = {
up = tonumber(server.uplink_capacity) and tonumber(server.uplink_capacity) .. " mbps" or nil,
down = tonumber(server.downlink_capacity) and tonumber(server.downlink_capacity) .. " mbps" or nil
} or nil,
realm = (server.hysteria2_realms and server.hysteria2_realm_stun) and {
stunServers = server.hysteria2_realm_stun
} or nil,
socks5 = (proto:find("tcp") and tonumber(socks_port) and tonumber(socks_port) ~= 0) and {
listen = "0.0.0.0:" .. tonumber(socks_port),
disableUDP = false
@@ -997,7 +1021,7 @@ local hysteria2 = {
} or nil,
obfs = (server.flag_obfs == "1") and {
type = server.obfs_type,
salamander = { password = server.salamander }
[server.obfs_type] = { password = server.salamander }
} or nil,
quic = (server.flag_quicparam == "1" ) and {
initStreamReceiveWindow = (server.initstreamreceivewindow and server.initstreamreceivewindow or nil),
@@ -1046,6 +1070,16 @@ local hysteria2 = {
fast_open = (server.fast_open == "1") and true or false,
lazy = (server.lazy_mode == "1") and true or false
}
if hysteria2.obfs and hysteria2.obfs.type == "gecko" then
local min = tonumber(server.obfs_MinPacketSize) or 512
local max = tonumber(server.obfs_MaxPacketSize) or 1200
if min <= 0 or min > max or max > 2048 then
min = 512
max = 1200
end
hysteria2.obfs.gecko.minPacketSize = min
hysteria2.obfs.gecko.maxPacketSize = max
end
local shadowtls = {
client = {
server_addr = server.server_port and format_host_port(server.server, server.server_port) or nil,
@@ -29,6 +29,8 @@ local allow_insecure = ucic:get_first(name, 'server_subscribe', 'allow_insecure'
local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', '过期/重置/套餐/剩余/网址/QQ群/官网/防失联/回国')
local save_words = ucic:get_first(name, 'server_subscribe', 'save_words', '')
local user_agent = ucic:get_first(name, 'server_subscribe', 'user_agent', 'v2rayN/9.99')
-- 读取 enable_mihomo 配置
local enable_mihomo = ucic:get_first(name, 'server_subscribe', 'enable_mihomo', '')
local sub_convert = ucic:get_first(name, 'server_subscribe', 'sub_convert', '0')
local convert_address = ucic:get_first(name, 'server_subscribe', 'convert_address', 'https://api.asailor.org/sub')
local template_url = ucic:get_first(name, 'server_subscribe', 'template_url', '')
@@ -44,8 +46,30 @@ local log = function(...)
print(os.date("%Y-%m-%d %H:%M:%S ") .. table.concat({...}, " "))
end
-- 检查是否需要使用 Mihomo 订阅模式
local function should_use_mihomo_mode()
-- 如果 enable_mihomo == 1,使用 Mihomo 模式
if enable_mihomo == "1" and has_mihomo then
return true
end
-- 如果 enable_mihomo ~= 1,检查是否有其他可用核心
if enable_mihomo ~= "1" then
-- 有其他核心可用,使用传统模式
if has_xray or has_ss_rust then
return false
end
-- 没有其他核心,回退到 Mihomo 模式
if has_mihomo then
log("回退: Xray/ss-rust 不存在,使用 Mihomo 订阅模式")
return true
end
end
-- 默认:如果有 Mihomo 则使用 Mihomo 模式
return has_mihomo
end
local function preferred_ss_backend()
if has_mihomo then
if should_use_mihomo_mode() and has_mihomo then
return "ss"
end
if has_ss_rust then
@@ -54,6 +78,9 @@ local function preferred_ss_backend()
if has_xray then
return "v2ray"
end
if has_mihomo then
return "ss"
end
return nil
end
@@ -89,6 +116,7 @@ local encrypt_methods_ss = {
"chacha20",
"chacha20-ietf" ]]--
}
-- 分割字符串
local function split(full, sep)
if full == nil or type(full) ~= "string" then
@@ -117,6 +145,7 @@ local function split(full, sep)
end
return result
end
-- urlencode
local function get_urlencode(c)
return sformat("%%%02X", sbyte(c))
@@ -136,6 +165,7 @@ end
local function get_urldecode(h)
return schar(tonumber(h, 16))
end
local function UrlDecode(szText)
return szText:gsub("+", " "):gsub("%%(%x%x)", get_urldecode)
end
@@ -269,12 +299,14 @@ local function parse_host_port(value, default_port)
return normalize_host(value), default_port
end
-- md5
local function md5(content)
local stdout = luci.sys.exec('echo \"' .. urlEncode(content) .. '\" | md5sum | cut -d \" \" -f1')
-- assert(nixio.errno() == 0)
return trim(stdout)
end
-- base64 解码
local function base64Decode(text)
local raw = text
@@ -296,6 +328,7 @@ local function base64Decode(text)
return raw
end
end
-- base64 编码
local function base64Encode(text)
if not text or text == "" then
@@ -309,6 +342,7 @@ local function base64Encode(text)
return text
end
end
-- 检查数组(table)中是否存在某个字符值
-- https://www.04007.cn/article/135.html
local function checkTabValue(tab)
@@ -318,6 +352,7 @@ local function checkTabValue(tab)
end
return revtab
end
-- JSON完整性检查
local function isCompleteJSON(str)
-- 检查JSON格式
@@ -349,7 +384,8 @@ local function processClashSubscription(url)
return nil
end
local alias = "Clash_" .. parsed.host
local url_hash = string.sub(md5(url), 1, 8)
local alias = "Clash_" .. parsed.host .. "_" .. url_hash
local server_port = parsed.port or ((parsed.scheme == "http") and "80" or "443")
local result = {
type = "clash",
@@ -796,6 +832,10 @@ local function can_group_into_mihomo(node)
if not node then
return false
end
-- 如果不使用 Mihomo 模式,返回 false
if not should_use_mihomo_mode() then
return false
end
if node.type == "ssr" or node.type == "ss" or node.type == "ss-rust" then
return true
end
@@ -820,6 +860,11 @@ local function can_group_into_mihomo(node)
end
local function buildMihomoSubscribeYaml(nodes, group_name)
-- 如果不使用 Mihomo 模式,返回 nil
if not should_use_mihomo_mode() then
return nil, 0
end
local proxies = {}
local names = {}
@@ -943,6 +988,10 @@ local function processData(szType, content, cfgid)
result.flag_obfs = "1"
result.obfs_type = params.obfs
result.salamander = params["obfs-password"] or params["obfs_password"]
if params.obfs == "gecko" then
result.obfs_MinPacketSize = params.minPacketSize or "512"
result.obfs_MaxPacketSize = params.maxPacketSize or "1200"
end
end
if params.allowInsecure or params.insecure then
local insecure = params.allowInsecure or params.insecure
@@ -2386,6 +2435,10 @@ local function is_mihomo_subscribe_node(section)
if not section then
return false
end
-- 如果不使用 Mihomo 模式,返回 false
if not should_use_mihomo_mode() then
return false
end
if section.type == "clash" and section.clash_path and tostring(section.clash_path):match("%.mihomo%.yaml$") then
return true
end
@@ -2413,7 +2466,7 @@ local function is_mihomo_subscribe_node(section)
end
local function group_needs_mihomo_subscribe_refresh(groupHash)
if not has_mihomo then
if not should_use_mihomo_mode() then
return false
end
@@ -2446,13 +2499,26 @@ local function preserve_unselected_groups(selected_hashes)
end)
end
-- 获取 groupHash 的公共函数(预留函数)
local function get_group_hash(url, fetch_url)
if should_use_mihomo_mode() then
return md5(fetch_url)
else
return md5(url)
end
end
local execute = function()
local updated = false
local selected_hashes = {}
for _, item in ipairs(subscribe_items) do
local fetch_url = build_convert_url(item.url)
selected_hashes[md5(fetch_url)] = true
if should_use_mihomo_mode() then
selected_hashes[md5(fetch_url)] = true
else
selected_hashes[md5(item.url)] = true
end
end
if target_subscribe_sid ~= "" then
@@ -2462,24 +2528,33 @@ local execute = function()
for _, item in ipairs(subscribe_items) do
local url = item.url
local fetch_url, converted = build_convert_url(url)
local raw, new_md5 = curl(fetch_url, user_agent)
local raw, new_md5
local groupHash
local yaml_removed_count = 0
if isClashYAML(raw) then
raw, yaml_removed_count = filterClashYamlRaw(raw)
if yaml_removed_count and yaml_removed_count > 0 then
new_md5 = md5_string(raw)
if should_use_mihomo_mode() then
raw, new_md5 = curl(fetch_url, user_agent)
if isClashYAML(raw) then
raw, yaml_removed_count = filterClashYamlRaw(raw)
if yaml_removed_count and yaml_removed_count > 0 then
new_md5 = md5_string(raw)
end
end
groupHash = md5(fetch_url)
else
raw, new_md5 = curl(url, user_agent)
groupHash = md5(url)
end
log("raw 长度: "..#raw)
local groupHash = md5(fetch_url)
local old_md5 = read_old_md5(groupHash)
log("处理订阅: " .. url)
if converted then
if should_use_mihomo_mode() and converted then
log("使用订阅转换模板: " .. template_url)
log("转换服务: " .. convert_address)
end
if yaml_removed_count and yaml_removed_count > 0 then
if should_use_mihomo_mode() and yaml_removed_count and yaml_removed_count > 0 then
log("Clash/Mihomo YAML 已按订阅过滤关键词移除节点数量: " .. tostring(yaml_removed_count))
end
log("groupHash: " .. groupHash)
@@ -2489,110 +2564,111 @@ local execute = function()
local backend_refresh = group_needs_ss_backend_refresh(groupHash)
local mihomo_subscribe_refresh = group_needs_mihomo_subscribe_refresh(groupHash)
local missing_saved_nodes = old_md5 and new_md5 == old_md5 and not group_has_saved_nodes(groupHash)
if #raw == 0 then
log(url .. ': 获取内容为空')
loadOldNodes(groupHash)
elseif old_md5 and new_md5 == old_md5 and not backend_refresh and not mihomo_subscribe_refresh and not missing_saved_nodes then
log("订阅未变化, 跳过无需更新的订阅: " .. url)
-- 防止 diff 阶段误删未更新订阅节点
loadOldNodes(groupHash)
--ucic:foreach(name, uciType, function(s)
-- if s.grouphashkey == groupHash and s.hashkey then
-- cache[groupHash][s.hashkey] = s
-- tinsert(nodeResult[index], s)
-- end
--end)
log("订阅未变化, 跳过无需更新的订阅: " .. url)
-- 防止 diff 阶段误删未更新订阅节点
loadOldNodes(groupHash)
--ucic:foreach(name, uciType, function(s)
-- if s.grouphashkey == groupHash and s.hashkey then
-- cache[groupHash][s.hashkey] = s
-- tinsert(nodeResult[index], s)
-- end
--end)
else
if backend_refresh and old_md5 and new_md5 == old_md5 then
log("检测到 SS 后端偏好变化,强制重建订阅节点: " .. url)
end
if mihomo_subscribe_refresh and old_md5 and new_md5 == old_md5 then
log("检测到可迁移订阅节点,强制重建为 Mihomo 总节点: " .. url)
end
if missing_saved_nodes then
log("订阅内容未变化但本地节点不存在,强制重建订阅节点: " .. url)
end
updated = true
-- 保存更新后的 MD5 值到以 groupHash 为标识的临时文件中,用于下次订阅更新时进行对比
write_new_md5(groupHash, new_md5)
if backend_refresh and old_md5 and new_md5 == old_md5 then
log("检测到 SS 后端偏好变化,强制重建订阅节点: " .. url)
end
if mihomo_subscribe_refresh and old_md5 and new_md5 == old_md5 then
log("检测到可迁移订阅节点,强制重建为 Mihomo 总节点: " .. url)
end
if missing_saved_nodes then
log("订阅内容未变化但本地节点不存在,强制重建订阅节点: " .. url)
end
updated = true
-- 保存更新后的 MD5 值到以 groupHash 为标识的临时文件中,用于下次订阅更新时进行对比
write_new_md5(groupHash, new_md5)
cache[groupHash] = {}
tinsert(nodeResult, {})
local index = #nodeResult
local nodes, szType
local is_clash_subscription = false
cache[groupHash] = {}
tinsert(nodeResult, {})
local index = #nodeResult
local nodes, szType
local is_clash_subscription = false
if isClashYAML(raw) then
is_clash_subscription = true
local result = processClashSubscription(fetch_url)
if result and check_filer(result) then
log('过滤 Clash 总节点: ' .. result.alias)
elseif result and not cache[groupHash][result.hashkey] then
result.grouphashkey = groupHash
table.insert(nodeResult[index], result)
cache[groupHash][result.hashkey] = result
log('成功导入 Clash 总节点: ' .. result.alias)
else
log('丢弃无效 Clash 总节点: ' .. url)
end
-- SSD 似乎是这种格式 ssd:// 开头的
elseif raw:find('ssd://') then
szType = 'ssd'
local nEnd = select(2, raw:find('ssd://'))
nodes = base64Decode(raw:sub(nEnd + 1, #raw))
nodes = jsonParse(nodes)
local extra = {
airport = nodes.airport,
port = nodes.port,
encryption = nodes.encryption,
password = nodes.password
}
local servers = {}
-- SS里面包着 干脆直接这样
for _, server in ipairs(nodes.servers or {}) do
tinsert(servers, setmetatable(server, {__index = extra}))
end
nodes = servers
-- SS SIP008 直接使用 Json 格式
elseif jsonParse(raw) then
nodes = jsonParse(raw).servers or jsonParse(raw)
if nodes[1] and nodes[1].server and nodes[1].method then
szType = 'sip008'
end
-- 其他 base64 格式
if isClashYAML(raw) then
is_clash_subscription = true
local result = processClashSubscription(fetch_url)
if result and check_filer(result) then
log('过滤 Clash 总节点: ' .. result.alias)
elseif result and not cache[groupHash][result.hashkey] then
result.grouphashkey = groupHash
table.insert(nodeResult[index], result)
cache[groupHash][result.hashkey] = result
log('成功导入 Clash 总节点: ' .. result.alias)
else
-- ssd 外的格式
nodes = split(base64Decode(raw):gsub("\r\n", "\n"), "\n")
log('丢弃无效 Clash 总节点: ' .. url)
end
-- SSD 似乎是这种格式 ssd:// 开头的
elseif raw:find('ssd://') then
szType = 'ssd'
local nEnd = select(2, raw:find('ssd://'))
nodes = base64Decode(raw:sub(nEnd + 1, #raw))
nodes = jsonParse(nodes)
local extra = {
airport = nodes.airport,
port = nodes.port,
encryption = nodes.encryption,
password = nodes.password
}
local servers = {}
-- SS里面包着 干脆直接这样
for _, server in ipairs(nodes.servers or {}) do
tinsert(servers, setmetatable(server, {__index = extra}))
end
nodes = servers
-- SS SIP008 直接使用 Json 格式
elseif jsonParse(raw) then
nodes = jsonParse(raw).servers or jsonParse(raw)
if nodes[1] and nodes[1].server and nodes[1].method then
szType = 'sip008'
end
-- 其他 base64 格式
else
-- ssd 外的格式
nodes = split(base64Decode(raw):gsub("\r\n", "\n"), "\n")
end
if not is_clash_subscription and not szType and type(nodes) == "table" then
local anytls_nodes = {}
local normal_nodes = {}
for _, node in ipairs(nodes) do
local line = trim(node or "")
if line:match("^anytls://") then
local parsed = parseAnytlsShare(line:gsub("^anytls://", ""))
if parsed then
table.insert(anytls_nodes, anytls_to_mihomo_node(parsed))
end
elseif line ~= "" then
table.insert(normal_nodes, node)
end
end
if not is_clash_subscription and not szType and type(nodes) == "table" then
local anytls_nodes = {}
local normal_nodes = {}
for _, node in ipairs(nodes) do
local line = trim(node or "")
if line:match("^anytls://") then
local parsed = parseAnytlsShare(line:gsub("^anytls://", ""))
if parsed then
table.insert(anytls_nodes, anytls_to_mihomo_node(parsed))
end
elseif line ~= "" then
table.insert(normal_nodes, node)
end
if #anytls_nodes > 0 then
for _, parsed in ipairs(anytls_nodes) do
table.insert(normal_nodes, parsed)
end
if #anytls_nodes > 0 then
for _, parsed in ipairs(anytls_nodes) do
table.insert(normal_nodes, parsed)
end
end
nodes = normal_nodes
end
-- 临时存储该订阅解析出的节点(带原始别名)
local groupRawNodes = {}
nodes = normal_nodes
end
if not is_clash_subscription then
-- 临时存储该订阅解析出的节点(带原始别名)
local groupRawNodes = {}
if not is_clash_subscription then
for _, v in ipairs(nodes) do
if v and (type(v) == "table" or not string.match(v, "^%s*$")) then
xpcall(function()
@@ -2621,52 +2697,61 @@ local execute = function()
log('跳过未知类型: ' .. szType)
end
-- log(result)
if result then
local filtered = check_filer(result)
-- 中文做地址的 也没有人拿中文域名搞,就算中文域也有Puny Code SB 机场
local invalid = not result.server or not result.server_port
or (result.type ~= "clash" and result.server == "127.0.0.1")
or result.alias == "NULL"
or (result.type ~= "clash" and result.server:match("[^0-9a-zA-Z%-_%.%s]"))
or cache[groupHash][result.hashkey]
if filtered then
log('过滤节点: ' .. result.alias)
elseif invalid then
log('丢弃无效节点: ' .. result.alias)
else
-- 暂存节点
table.insert(groupRawNodes, result)
end
if result then
local filtered = check_filer(result)
-- 中文做地址的 也没有人拿中文域名搞,就算中文域也有Puny Code SB 机场
local invalid = not result.server or not result.server_port
or (result.type ~= "clash" and result.server == "127.0.0.1")
or result.alias == "NULL"
or (result.type ~= "clash" and result.server:match("[^0-9a-zA-Z%-_%.%s]"))
or cache[groupHash][result.hashkey]
if filtered then
log('过滤节点: ' .. result.alias)
elseif invalid then
log('丢弃无效节点: ' .. result.alias)
else
-- 暂存节点
table.insert(groupRawNodes, result)
end
end
end, function(err)
log(string.format("解析节点出错: %s\n原始数据: %s", tostring(err), tostring(v)))
end)
end
end
end
end
-- 对该组节点进行别名编号:重复节点加后缀,唯一节点不加
-- 传统模式:对 groupRawNodes 进行组内编号
if not should_use_mihomo_mode() then
local freq = {}
for _, node in ipairs(groupRawNodes) do
local raw = node.raw_alias or ""
freq[raw] = (freq[raw] or 0) + 1
if raw ~= "" then
freq[raw] = (freq[raw] or 0) + 1
end
end
local aliasCount = {}
for _, node in ipairs(groupRawNodes) do
local raw = node.raw_alias or ""
if freq[raw] > 1 then
local count = (aliasCount[raw] or 0) + 1
aliasCount[raw] = count
node.alias = raw .. "_" .. count
else
node.alias = raw
if raw ~= "" then
if freq[raw] and freq[raw] > 1 then
local count = (aliasCount[raw] or 0) + 1
aliasCount[raw] = count
node.alias = raw .. "_" .. count
else
node.alias = raw
end
end
-- 清理临时字段
node.raw_alias = nil
end
end
local mihomo_nodes = {}
local legacy_nodes = {}
local mihomo_nodes = {}
local legacy_nodes = {}
-- 只有当 should_use_mihomo_mode() 为 true 时才尝试分组
if should_use_mihomo_mode() then
for _, node in ipairs(groupRawNodes) do
if has_mihomo and can_group_into_mihomo(node) then
mihomo_nodes[#mihomo_nodes + 1] = node
@@ -2674,41 +2759,50 @@ local execute = function()
legacy_nodes[#legacy_nodes + 1] = node
end
end
else
-- 传统模式:所有节点作为普通节点
for _, node in ipairs(groupRawNodes) do
legacy_nodes[#legacy_nodes + 1] = node
end
end
if #mihomo_nodes > 0 then
local parsed_url = URL.parse(url)
local alias = item.alias ~= "" and item.alias or ("Mihomo_" .. ((parsed_url and parsed_url.host) or groupHash))
local local_path = string.format("%s/%s.mihomo.yaml", local_clash_dir, groupHash)
local yaml, proxy_count = buildMihomoSubscribeYaml(mihomo_nodes, "Proxy")
if #mihomo_nodes > 0 then
local parsed_url = URL.parse(url)
local alias = item.alias ~= "" and item.alias or ("Mihomo_" .. ((parsed_url and parsed_url.host) or groupHash))
local local_path = string.format("%s/%s.mihomo.yaml", local_clash_dir, groupHash)
local yaml, proxy_count = buildMihomoSubscribeYaml(mihomo_nodes, "Proxy")
if yaml and proxy_count > 0 then
nixio.fs.mkdirr(local_clash_dir)
nixio.fs.writefile(local_path, yaml)
if yaml and proxy_count > 0 then
nixio.fs.mkdirr(local_clash_dir)
nixio.fs.writefile(local_path, yaml)
local result = processLocalClashSubscription(local_path, alias)
if result and not cache[groupHash][result.hashkey] then
result.grouphashkey = groupHash
table.insert(nodeResult[index], result)
cache[groupHash][result.hashkey] = result
log('成功导入 Mihomo 总节点: ' .. result.alias .. ',包含节点数量: ' .. proxy_count)
end
else
log('Mihomo 总节点生成失败,回退为普通订阅节点。')
for _, node in ipairs(mihomo_nodes) do
legacy_nodes[#legacy_nodes + 1] = node
end
local result = processLocalClashSubscription(local_path, alias)
if result and not cache[groupHash][result.hashkey] then
result.grouphashkey = groupHash
table.insert(nodeResult[index], result)
cache[groupHash][result.hashkey] = result
log('成功导入 Mihomo 总节点: ' .. result.alias .. ',包含节点数量: ' .. proxy_count)
end
else
log('Mihomo 总节点生成失败,回退为普通订阅节点。')
for _, node in ipairs(mihomo_nodes) do
legacy_nodes[#legacy_nodes + 1] = node
end
end
end
for _, node in ipairs(legacy_nodes) do
node.grouphashkey = groupHash
table.insert(nodeResult[index], node)
cache[groupHash][node.hashkey] = node
end
for _, node in ipairs(legacy_nodes) do
node.grouphashkey = groupHash
table.insert(nodeResult[index], node)
cache[groupHash][node.hashkey] = node
end
if not should_use_mihomo_mode() then
log('成功解析节点数量: ' .. #groupRawNodes)
end
end
end
-- 输出日志并判断是否需要进行 diff
if not updated then
log("订阅未变化,无需更新节点信息。")
@@ -2729,8 +2823,8 @@ local execute = function()
del = del + 1
else
local dat = nodeResult[old.grouphashkey][old.hashkey]
ucic:tset(name, old['.name'], dat)
-- 标记一下
ucic:tset(name, old['.name'], dat)
setmetatable(nodeResult[old.grouphashkey][old.hashkey], {__index = {_ignore = true}})
end
else
@@ -2746,11 +2840,12 @@ local execute = function()
end
end
end)
-- 1615-1653 行为生成 sid
-- 生成 sid
-- 记录已使用编号
local used_sid = {}
local next_sid = 1
-- 扫描已有 section
-- 检查已有 section
ucic:foreach(name, uciType, function(s)
local num = s[".name"]:match("^cfg(%x%x)") -- 提取两位十六进制序号
if num then
@@ -2820,6 +2915,14 @@ if subscribe_items and #subscribe_items > 0 then
else
log("当前订阅模式: 不通过代理订阅")
end
-- 记录当前使用的订阅模式
if should_use_mihomo_mode() then
log("订阅模式: Mihomo 总节点模式")
else
log("订阅模式: 传统节点模式")
end
local direct_bypass = create_direct_subscribe_bypass()
if direct_bypass then
direct_bypass:apply()
@@ -5,6 +5,7 @@ set -u
XRAY_RELEASE_PAGE="https://github.com/XTLS/Xray-core/releases/latest"
MIHOMO_RELEASE_PAGE="https://github.com/MetaCubeX/mihomo/releases/latest"
NAIVEPROXY_RELEASE_API="https://api.github.com/repos/klzgrad/naiveproxy/releases/latest"
HELLOWORLD_RELEASE_API="https://api.github.com/repos/fw876/helloworld/releases/latest"
XRAY_BINARY="/usr/bin/xray"
MIHOMO_BINARY="/usr/bin/mihomo"
NAIVEPROXY_BINARY="/usr/bin/naive"
@@ -164,6 +165,20 @@ is_openwrt_env() {
[ -r /etc/openwrt_release ] || command -v opkg >/dev/null 2>&1
}
detect_package_manager() {
if command -v apk >/dev/null 2>&1; then
printf '%s' 'apk'
return 0
fi
if command -v opkg >/dev/null 2>&1; then
printf '%s' 'opkg'
return 0
fi
return 1
}
find_mihomo_binary() {
if command -v mihomo >/dev/null 2>&1; then
command -v mihomo
@@ -831,6 +846,240 @@ get_naiveproxy_latest_info() {
return 0
}
get_mainprogram_current_version() {
local pm="$1"
local version
case "$pm" in
apk)
version="$(apk info -e luci-app-ssr-plus 2>/dev/null | sed -n 's/^luci-app-ssr-plus-\([0-9][^-[:space:]]*-[^[:space:]]*\).*$/\1/p' | sed -n '1p')"
[ -n "$version" ] || version="$(apk list -I luci-app-ssr-plus 2>/dev/null | sed -n 's/^luci-app-ssr-plus-\([0-9][^-[:space:]]*-[^[:space:]]*\).*$/\1/p' | sed -n '1p')"
;;
opkg)
version="$(opkg status luci-app-ssr-plus 2>/dev/null | sed -n 's/^Version:[[:space:]]*//p' | sed -n '1p')"
;;
esac
[ -n "$version" ] || return 1
printf '%s' "$version"
}
mainprogram_asset_suffix() {
case "$1" in
apk)
printf '%s' '.apk'
;;
opkg)
printf '%s' '.ipk'
;;
*)
return 1
;;
esac
}
select_mainprogram_asset() {
local asset_list="$1"
local pm="$2"
local suffix candidate
suffix="$(mainprogram_asset_suffix "$pm")" || return 1
candidate="$(printf '%s\n' "$asset_list" | grep -F "$suffix" | grep -E '^luci-app-ssr-plus[_-]' | sed -n '1p')"
[ -n "$candidate" ] || return 1
printf '%s' "$candidate"
}
mainprogram_asset_version() {
local asset="$1"
local version
version="$(printf '%s' "$asset" | sed -n 's/^luci-app-ssr-plus_\([^_]*\)_.*\.ipk$/\1/p')"
[ -n "$version" ] || version="$(printf '%s' "$asset" | sed -n 's/^luci-app-ssr-plus-\(.*\)\.apk$/\1/p')"
[ -n "$version" ] || return 1
printf '%s' "$version"
}
get_mainprogram_latest_info() {
local pm release_json tag version asset asset_list url
pm="$(detect_package_manager)" || return 2
release_json="$(fetch_text "$HELLOWORLD_RELEASE_API")" || return 3
tag="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | sed -n '1p')"
[ -n "$tag" ] || return 3
asset_list="$(printf '%s\n' "$release_json" | sed -n 's/.*"name":[[:space:]]*"\([^"]*\.\(ipk\|apk\)\)".*/\1/p')"
asset="$(select_mainprogram_asset "$asset_list" "$pm")" || return 4
version="$(mainprogram_asset_version "$asset" 2>/dev/null || trim_version "$tag")"
url="$(mirror_wrap_url "https://github.com/fw876/helloworld/releases/download/$tag/$asset")"
log_kv package_manager "$pm"
log_kv arch "$(get_openwrt_arch)"
log_kv asset "$asset"
log_kv latest_version "$version"
log_kv download_url "$url"
return 0
}
mainprogram_info() {
local pm current installed latest_output latest_rc latest_version arch asset can_upgrade
installed=0
current=""
pm="$(detect_package_manager 2>/dev/null || true)"
arch="$(get_openwrt_arch)"
if [ -n "$pm" ] && current="$(get_mainprogram_current_version "$pm")" && [ -n "$current" ]; then
installed=1
fi
latest_output="$(get_mainprogram_latest_info 2>/dev/null)"
latest_rc=$?
log_kv component mainprogram
log_kv installed "$installed"
log_kv current_version "$current"
log_kv package_manager "$pm"
log_kv arch "$arch"
log_kv asset ''
if [ $latest_rc -ne 0 ]; then
log_kv can_upgrade 0
case "$latest_rc" in
2) log_kv error 'unsupported_package_manager' ;;
3) log_kv error 'fetch_failed' ;;
4) log_kv error 'asset_not_found' ;;
*) log_kv error 'unknown_error' ;;
esac
return 0
fi
latest_version="$(printf '%s\n' "$latest_output" | sed -n 's/^latest_version=//p' | sed -n '1p')"
pm="$(printf '%s\n' "$latest_output" | sed -n 's/^package_manager=//p' | sed -n '1p')"
arch="$(printf '%s\n' "$latest_output" | sed -n 's/^arch=//p' | sed -n '1p')"
asset="$(printf '%s\n' "$latest_output" | sed -n 's/^asset=//p' | sed -n '1p')"
can_upgrade=0
if [ -z "$current" ] || version_gt "$latest_version" "$current"; then
can_upgrade=1
fi
printf '%s\n' "$latest_output" | sed '/^download_url=/d'
log_kv can_upgrade "$can_upgrade"
log_kv error ''
}
mainprogram_local_info() {
local pm current installed
installed=0
current=""
pm="$(detect_package_manager 2>/dev/null || true)"
if [ -n "$pm" ] && current="$(get_mainprogram_current_version "$pm")" && [ -n "$current" ]; then
installed=1
fi
log_kv component mainprogram
log_kv installed "$installed"
log_kv current_version "$current"
log_kv latest_version ''
log_kv package_manager "$pm"
log_kv arch "$(get_openwrt_arch)"
log_kv asset ''
log_kv can_upgrade 0
log_kv error ''
}
mainprogram_install_package() {
local pm="$1"
local package_file="$2"
case "$pm" in
apk)
apk add --allow-untrusted "$package_file" >/dev/null 2>&1
;;
opkg)
opkg install "$package_file" >/dev/null 2>&1
;;
*)
return 1
;;
esac
}
mainprogram_upgrade() {
local latest_output latest_rc latest_version download_url package_manager asset tmp_dir package_file current_before current_after
latest_output="$(get_mainprogram_latest_info 2>/dev/null)"
latest_rc=$?
if [ $latest_rc -ne 0 ]; then
log_kv success 0
case "$latest_rc" in
2) log_kv message 'Unsupported package manager' ;;
3) log_kv message 'Failed to fetch release metadata' ;;
4) log_kv message 'Matching release asset not found' ;;
*) log_kv message 'Unknown error' ;;
esac
return 0
fi
latest_version="$(printf '%s\n' "$latest_output" | sed -n 's/^latest_version=//p' | sed -n '1p')"
download_url="$(printf '%s\n' "$latest_output" | sed -n 's/^download_url=//p' | sed -n '1p')"
package_manager="$(printf '%s\n' "$latest_output" | sed -n 's/^package_manager=//p' | sed -n '1p')"
asset="$(printf '%s\n' "$latest_output" | sed -n 's/^asset=//p' | sed -n '1p')"
current_before="$(get_mainprogram_current_version "$package_manager" 2>/dev/null || true)"
tmp_dir="$(mktemp -d /tmp/ssrplus-mainprogram.XXXXXX)"
if [ -z "$tmp_dir" ] || [ ! -d "$tmp_dir" ]; then
log_kv success 0
log_kv message 'Failed to create temp directory'
return 0
fi
package_file="$tmp_dir/$asset"
trap "rm -rf '$tmp_dir'" EXIT INT TERM
if ! download_file "$download_url" "$package_file"; then
log_kv success 0
log_kv message 'Download failed'
return 0
fi
if [ ! -s "$package_file" ]; then
log_kv success 0
log_kv message 'Download failed'
return 0
fi
if ! mainprogram_install_package "$package_manager" "$package_file"; then
log_kv success 0
log_kv message 'Install failed'
return 0
fi
current_after="$(get_mainprogram_current_version "$package_manager" 2>/dev/null || true)"
if [ -z "$current_after" ]; then
log_kv success 0
log_kv message 'Installed package check failed'
return 0
fi
if [ -x /etc/init.d/rpcd ]; then
/etc/init.d/rpcd restart >/dev/null 2>&1 || true
fi
if [ -x /etc/init.d/uhttpd ]; then
/etc/init.d/uhttpd reload >/dev/null 2>&1 || true
fi
log_kv component mainprogram
log_kv success 1
log_kv previous_version "$current_before"
log_kv current_version "$current_after"
log_kv latest_version "$latest_version"
log_kv package_manager "$package_manager"
log_kv arch "$(get_openwrt_arch)"
log_kv asset "$asset"
log_kv can_upgrade 0
log_kv message 'Upgrade completed'
return 0
}
xray_info() {
local current installed latest_output latest_rc latest_version arch asset can_upgrade
@@ -1366,6 +1615,15 @@ naiveproxy_upgrade() {
}
case "${1:-}" in
mainprogram_info)
mainprogram_info
;;
mainprogram_local_info)
mainprogram_local_info
;;
mainprogram_upgrade)
mainprogram_upgrade
;;
xray_info)
xray_info
;;
@@ -1422,7 +1680,7 @@ case "${1:-}" in
;;
*)
log_kv success 0
log_kv message 'Usage: update_components.sh xray_info|xray_local_info|xray_upgrade|mihomo_info|mihomo_local_info|mihomo_upgrade|naiveproxy_info|naiveproxy_local_info|naiveproxy_upgrade|country_mmdb_info|country_mmdb_local_info|country_mmdb_upgrade|geosite_info|geosite_local_info|geosite_upgrade|v2ray_geo_info|v2ray_geo_local_info|v2ray_geo_upgrade'
log_kv message 'Usage: update_components.sh mainprogram_info|mainprogram_local_info|mainprogram_upgrade|xray_info|xray_local_info|xray_upgrade|mihomo_info|mihomo_local_info|mihomo_upgrade|naiveproxy_info|naiveproxy_local_info|naiveproxy_upgrade|country_mmdb_info|country_mmdb_local_info|country_mmdb_upgrade|geosite_info|geosite_local_info|geosite_upgrade|v2ray_geo_info|v2ray_geo_local_info|v2ray_geo_upgrade'
return 1 2>/dev/null || exit 1
;;
esac