update 2026-06-29 23:37:40

This commit is contained in:
action committed 2026-06-29 23:37:40 +08:00
1 parent c8ceebd509
commit f769dc4727
15 files changed
+1645 -1217

No files matched your search

+1 -13
View File
@@ -1,26 +1,14 @@
#!/bin/sh
NSS_PATH="/sys/kernel/debug/qca-nss-drv/stats"
WIFILI_PATH="$NSS_PATH/wifili"
ATH11K_NSS_PEER_STATS_GLOB="/sys/kernel/debug/ath11k/*/*/nss_peer_stats_config"
cpu_usage="$(busybox top -n1 | awk '/^CPU/ {printf("%d%", 100 - $8)}')"
[ ! -d "$NSS_PATH" ] || \
npu_usage="$(grep '%' "$NSS_PATH"/cpu_load_ubi | awk -F ' ' '{print $2}')"
wifi_nss_status=""
if [ -e "$WIFILI_PATH" ]; then
if grep -q '1' $ATH11K_NSS_PEER_STATS_GLOB 2>/dev/null; then
wifi_nss_status=", WIFI NSS: ON"
else
wifi_nss_status=", WIFI NSS: OFF"
fi
fi
if [ -d "$NSS_PATH" ]; then
echo -n "CPU: ${cpu_usage}, NPU: ${npu_usage}${wifi_nss_status}"
echo -n "CPU: ${cpu_usage}, NPU: ${npu_usage}"
else
echo -n "CPU: ${cpu_usage}"
fi
@@ -179,19 +179,17 @@ if has_xray then
o:value("1-5", "1-5")
o:depends("fragment", true)
o = s_xray:option(Value, "fragment_length", translate("Fragment Length"), translate("Fragmented packet length (byte)"))
o.datatype = "or(uinteger,portrange)"
o.default = "100-200"
o = s_xray:option(Value, "fragment_lengths", translate("Fragment Length"), translate("Fragmented packet length (byte)"))
o.default = "3-5,6-8,10-20"
o:depends("fragment", true)
o = s_xray:option(Value, "fragment_delay", translate("Fragment Delay"), translate("Fragmentation interval (ms)"))
o.datatype = "or(uinteger,portrange)"
o = s_xray:option(Value, "fragment_delays", translate("Fragment Delay"), translate("Fragmentation interval (ms)"))
o.default = "10-20"
o:depends("fragment", true)
o = s_xray:option(Value, "fragment_maxSplit", translate("Max Split"), translate("Limit the maximum number of splits."))
o.datatype = "or(uinteger,portrange)"
o.default = "100-200"
o.default = "3-6"
o:depends("fragment", true)
o = s_xray:option(Flag, "noise", translate("Noise"), translate("UDP noise, Under some circumstances it can bypass some UDP based protocol restrictions."))
@@ -883,13 +883,21 @@ function gen_config(var)
local xray_settings = uci:get_all(appname, "@global_xray[0]") or {}
if xray_settings.fragment == "1" then
local delay = xray_settings.fragment_delay
local lengths, delays = {}, {}
api.trim(xray_settings.fragment_lengths):gsub("[^,]+", function(w)
w = w:gsub("%s+", "")
if w ~= "" then lengths[#lengths+1] = w end
end)
api.trim(xray_settings.fragment_delays):gsub("[^,]+", function(w)
w = w:gsub("%s+", "")
if w ~= "" then delays[#delays+1] = w end
end)
fragment_table = {
type = "fragment",
settings = {
packets = xray_settings.fragment_packets,
length = xray_settings.fragment_length,
delay = delay and (delay:find("-", 1, true) and delay or tonumber(delay)) or nil,
lengths = #lengths > 0 and lengths or nil,
delays = #delays > 0 and delays or nil,
maxSplit = xray_settings.fragment_maxSplit
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=196
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
@@ -339,12 +339,12 @@ local function global_client_running()
return true
end
if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or global_type == "ss")
if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or global_type == "ss" or global_type == "ss-rust")
and process_list:find("ssr%-retcp") then
return true
end
if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or global_type == "ss")
if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or global_type == "ss" or global_type == "ss-rust")
and process_list:find("mihomo")
and (process_list:find("/clash%-") or process_list:find("/v2ray%-") or process_list:find("/tuic%-") or process_list:find("/ss%-")) then
return true
@@ -373,7 +373,7 @@ local function get_active_node_runtime(sid)
if stype == "ss" then
backend = translate("Mihomo")
protocol = translate("Shadowsocks")
protocol = translate("ShadowSocks")
elseif stype == "clash" then
backend = translate("Mihomo")
protocol = translate("Clash")
@@ -383,7 +383,12 @@ local function get_active_node_runtime(sid)
elseif stype == "ssr" then
backend = translate("ShadowsocksR")
elseif stype == "ss-rust" then
backend = translate("Shadowsocks-rust")
if is_mihomo_running then
backend = translate("Mihomo")
protocol = translate("ShadowSocks-Rust")
else
backend = translate("ShadowSocks-Rust")
end
elseif stype == "v2ray" then
local proto_map = {
vmess = "VMess",
@@ -75,6 +75,40 @@ local function base64Encode(text)
end
end
local function parse_realm_uri(uri)
uri = trim(uri)
if uri == "" then return nil end
-- realm[+http]://token@server/realm_id?query
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+)$") --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,
token = token,
server_url = server_url,
address = address,
port = port,
realm_id = realm_id
}
-- 解析 query 中的 stun=
local stun_servers
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
end
-- 获取 Xray 版本号
if is_finded("xray") then
local version = luci.sys.exec("xray version 2>&1")
@@ -617,6 +651,12 @@ end
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o:depends("hysteria2_realms", true)
o.validate = function(self, value)
value = trim(value)
local realm = parse_realm_uri(value)
if realm then return value end
return nil, translate("Invalid Realm URL.")
end
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
@@ -679,6 +719,20 @@ o.rmempty = true
o:depends({type = "hysteria2", flag_obfs = true})
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true})
o = s:option(Value, "obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
o.datatype = "uinteger"
o.placeholder = "512"
o.default = "512"
o:depends({type = "hysteria2", flag_obfs = true, obfs_type = "gecko"})
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true, obfs_type = "gecko"})
o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
o.datatype = "uinteger"
o.placeholder = "1200"
o.default = "1200"
o:depends({type = "hysteria2", flag_obfs = true, obfs_type = "gecko"})
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true, obfs_type = "gecko"})
o = s:option(Value, "salamander", translate("Obfuscation Password"))
o.password = true
o.rmempty = true
@@ -546,10 +546,16 @@ if has_mihomo then
o.template = "shadowsocksr/clash_yaml_upload"
o.description = translate("Upload a custom Clash/Mihomo YAML file. The file will be preprocessed and saved as a local Clash node.")
o = s:option(Flag, "enable_mihomo", string.format("<b><span style='color:red;'>%s</span></b>", translate("Enable Mihomo core")))
o.default = "0"
o.rmempty = true
o.description = translate("Disabled means use Xray or ShadowSocks-Rust core.")
o = s:option(Flag, "sub_convert", translate("Subscribe Convert Online"))
o.description = translate("Convert subscriptions to Clash/Mihomo YAML with a subscription template URL.")
o.default = "0"
o.rmempty = false
o:depends("enable_mihomo", "1")
o = s:option(Value, "convert_address", translate("Convert Address"))
o.default = "https://api.asailor.org/sub"
@@ -563,6 +569,7 @@ if has_mihomo then
o.default = "https://raw.githubusercontent.com/Fzlwhyc/OpenClash-Templates/main//fzlwhyc-openclash.ini"
o.placeholder = "https://raw.githubusercontent.com/Fzlwhyc/OpenClash-Templates/main//fzlwhyc-openclash.ini"
o:value("https://raw.githubusercontent.com/Fzlwhyc/OpenClash-Templates/main//fzlwhyc-openclash.ini", "Fzlwhyc")
o:value("https://gist.githubusercontent.com/vernesong/4c27ed54ab2a5fedd9c4011389ac11ed/raw/lhie1_clash.ini", "lhie1")
o.rmempty = true
o:depends("sub_convert", "1")
end
@@ -122,7 +122,7 @@ if global_type == "socks5" and Process_list:find("ipt2socks") then
end
end
if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or global_type == "ss")
if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or global_type == "ss" or global_type == "ss-rust")
and Process_list:find("ssr%-retcp") then
redir_run = 1
reudp_run = 1
@@ -131,7 +131,7 @@ if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or
end
end
if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or global_type == "ss")
if (global_type == "clash" or global_type == "v2ray" or global_type == "tuic" or global_type == "ss" or global_type == "ss-rust")
and Process_list:find("mihomo")
and (Process_list:find("/clash%-") or Process_list:find("/v2ray%-") or Process_list:find("/tuic%-") or Process_list:find("/ss%-")) then
redir_run = 1
@@ -206,8 +206,13 @@ function import_ssr_url(btn, urlname, sid) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_obfs')[0].checked = true; // 设置 flag_obfs 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_obfs')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_type')[0].value = params.get("obfs");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.salamander')[0].value = params.get("obfs-password") || params.get("obfs_password");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_type')[0].value = params.get("obfs");
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_type')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.salamander')[0].value = params.get("obfs-password") || params.get("obfs_password");
if (params.get("obfs") === "gecko") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_MinPacketSize')[0].value = params.get("minPacketSize") || "512";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_MaxPacketSize')[0].value = params.get("maxPacketSize") || "1200";
}
}
var allowInsecureValue = params.get("allowInsecure") || params.get("insecure");
if (allowInsecureValue === "1" || allowInsecureValue === "true") {
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+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
@@ -54,12 +54,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 +79,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
@@ -649,10 +657,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
@@ -959,6 +975,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
@@ -1000,7 +1019,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),
@@ -1049,6 +1068,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()