update 2026-10-05 19:05:58

This commit is contained in:
action committed 2026-10-05 19:05:58 +08:00
1 parent ce2c617af4
commit ec960a1df0
185 files changed
+212 -41511

No files matched your search

@@ -37,6 +37,7 @@ local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | aw
local singbox_version = api.get_app_version("sing-box"):match("[^v]+")
local version_ge_1_14_0 = api.compare_versions(singbox_version, ">=", "1.14.0")
local version_ge_1_15_0 = api.compare_versions(singbox_version, ">=", "1.15.0")
o = s:option(ListValue, "protocol", translate("Protocol"))
o:value("socks", "Socks")
@@ -65,6 +66,9 @@ end
if version_ge_1_14_0 then
o:value("snell", "Snell")
end
if version_ge_1_15_0 and singbox_tags:find("with_quic") then
o:value("masque", "MASQUE")
end
o:value("_urltest", translate("URLTest"))
o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface"))
@@ -193,6 +197,7 @@ o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "ssh" })
o:depends({ protocol = "naive" })
o:depends({ protocol = "masque" })
o = s:option(Value, "password", translate("Password"))
o.password = true
@@ -204,6 +209,34 @@ o:depends({ protocol = "tuic" })
o:depends({ protocol = "anytls" })
o:depends({ protocol = "ssh" })
o:depends({ protocol = "naive" })
o:depends({ protocol = "masque" })
-- [[ MASQUE ]] --
o = s:option(Value, "masque_path", translate("MASQUE Path"))
o:depends({ protocol = "masque" })
o = s:option(TextValue, "masque_headers", translate("Custom HTTP Headers"))
o.rows = 5
o.wrap = "off"
o:depends({ protocol = "masque" })
o.cfgvalue = function(self, section)
return (m:get(section, "masque_headers") or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n")
local headers = {}
for line in value:gmatch("[^\n]+") do
if api.trim(line) ~= "" then
local key = line:match("^%s*([^:]+):")
key = key and api.trim(key):lower()
if not key or not key:match("^[!#$%%&'*+%.%^_`|~%w%-]+$") or headers[key] then
return nil, translate("Not true format, please re-enter!") .. " (" .. line .. ")"
end
headers[key] = true
end
end
return value:gsub("\n", "\\n")
end
if version_ge_1_14_0 then
-- snell
@@ -523,6 +556,7 @@ o:depends({ tls = true })
o:depends({ protocol = "hysteria"})
o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain"))
o:depends({ tls = true })
@@ -530,6 +564,7 @@ o:depends({ protocol = "hysteria"})
o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "naive" })
o:depends({ protocol = "masque" })
o = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
o.default = "0"
@@ -537,12 +572,14 @@ o:depends({ tls = true })
o:depends({ protocol = "hysteria" })
o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(Value, "tls_pinSHA256", translate("TLS Chain Fingerprint (SHA256)"))
o:depends({ tls = true })
o:depends({ protocol = "hysteria" })
o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
@@ -553,6 +590,7 @@ o:depends({ protocol = "hysteria"})
o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "naive" })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format."))
o.default = ""
@@ -569,6 +607,7 @@ end
o = s:option(Value, "cipherSuites", translate("Cipher Suites"), '<a href="https://go.dev/src/crypto/tls/cipher_suites.go#L44" target="_blank">***</a>' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
o:depends({ tls = true })
o:depends({ protocol = "masque" })
o = s:option(Flag, "ech", translate("ECH"))
o.default = "0"
@@ -577,6 +616,7 @@ o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria" })
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
o:depends({ protocol = "naive" })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "ech_config", translate("ECH Config"))
o.default = ""
@@ -750,6 +790,7 @@ o:depends({ transport = "http" })
o:depends({ transport = "ws" })
o:depends({ transport = "httpupgrade" })
o:depends({ protocol = "naive" })
o:depends({ protocol = "masque" })
-- [[ Mux ]]--
o = s:option(Flag, "mux", translate("Mux"))
@@ -44,6 +44,9 @@ o:value("wireguard", translate("WireGuard"))
if api.compare_versions(xray_version, ">=", "26.1.13") then
o:value("hysteria2", translate("Hysteria2"))
end
if api.compare_versions(xray_version, ">=", "26.9.30") then
o:value("masque", "MASQUE")
end
if api.compare_versions(xray_version, ">=", "1.8.12") then
o:value("_balancing", translate("Balancing"))
end
@@ -260,6 +263,7 @@ o:depends({ protocol = "vless" })
o = s:option(Value, "username", translate("Username"))
o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "masque" })
o = s:option(Value, "password", translate("Password"))
o.password = true
@@ -267,6 +271,34 @@ o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" })
o:depends({ protocol = "masque" })
-- [[ MASQUE ]] --
o = s:option(Value, "masque_path", translate("MASQUE Path"))
o:depends({ protocol = "masque" })
o = s:option(TextValue, "masque_headers", translate("Custom HTTP Headers"))
o.rows = 5
o.wrap = "off"
o:depends({ protocol = "masque" })
o.cfgvalue = function(self, section)
return (m:get(section, "masque_headers") or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n")
local headers = {}
for line in value:gmatch("[^\n]+") do
if api.trim(line) ~= "" then
local key = line:match("^%s*([^:]+):")
key = key and api.trim(key):lower()
if not key or not key:match("^[!#$%%&'*+%.%^_`|~%w%-]+$") or headers[key] or key == "host" or key == "capsule-protocol" then
return nil, translate("Not true format, please re-enter!") .. " (" .. line .. ")"
end
headers[key] = true
end
end
return value:gsub("\n", "\\n")
end
o = s:option(ListValue, "security", translate("Encrypt Method"))
for a, t in ipairs(security_list) do o:value(t) end
@@ -419,21 +451,25 @@ o:depends({ tls = true, reality = false })
o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain"))
o:depends({ tls = true })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(Value, "tls_pinSHA256", translate("TLS Chain Fingerprint (SHA256)"))
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
o = s:option(Value, "tls_CertByName", translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)"))
o.default = "0"
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format."))
o.default = ""
@@ -452,6 +488,7 @@ o = s:option(Flag, "ech", translate("ECH"))
o.default = "0"
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "ech_config", translate("ECH Config"))
o.default = ""
@@ -495,6 +532,7 @@ o:depends({ tls = true, reality = true })
o = s:option(Value, "cipherSuites", translate("Cipher Suites"), '<a href="https://go.dev/src/crypto/tls/cipher_suites.go#L44" target="_blank">***</a>' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
o:depends({ tls = true, reality = false })
o:depends({ protocol = "masque" })
o = s:option(Flag, "use_mldsa65Verify", translate("ML-DSA-65"))
o.default = "0"
@@ -702,6 +740,7 @@ o:depends({ transport = "ws" })
o:depends({ transport = "httpupgrade" })
o:depends({ transport = "xhttp" })
o:depends({ transport = "grpc" })
o:depends({ protocol = "masque" })
-- [[ Mux.Cool ]]--
o = s:option(Flag, "mux", "Mux", translate("Enable Mux.Cool"))
+13 -11
View File
@@ -1462,10 +1462,7 @@ function to_check_self()
release = release[1]
end
if type(release) ~= "table" or not release.tag_name then
return {
code = 1,
error = i18n.translate("Get remote version info failed.")
}
return {code = 1, error = i18n.translate("Get remote version info failed.")}
end
local local_version = get_version()
-- Keep the release suffix (-1, -2, ...) so package revisions are compared too.
@@ -1482,15 +1479,20 @@ function to_check_self()
extension = ".ipk"
end
local main, i18n_package
for _, asset in ipairs(release.assets or {}) do
local name = asset.name or ""
if name:find(prefix, 1, true) == 1 and name:sub(-#extension) == extension then
name = name:sub(#prefix + 1)
if name:match("^luci%-app%-" .. appname .. "[_%-]%d") then main = asset end
if name:match("^luci%-i18n%-" .. appname .. "%-zh%-cn[_%-]%d") then i18n_package = asset end
for _, search_prefix in ipairs({prefix, ""}) do
for _, asset in ipairs(release.assets or {}) do
local name = asset.name or ""
if name:find(search_prefix, 1, true) == 1 and name:sub(-#extension) == extension then
name = name:sub(#search_prefix + 1)
if not main and name:match("^luci%-app%-" .. appname .. "[_%-]%d") then main = asset end
if not i18n_package and name:match("^luci%-i18n%-" .. appname .. "%-zh%-cn[_%-]%d") then i18n_package = asset end
end
end
if main and i18n_package then break end
end
if not main or not i18n_package then
return {code = 1, error = i18n.translate("Get remote version info failed.")}
end
if not main or not i18n_package then return {code = 1} end
return {
code = 0, has_update = has_update, local_version = local_version,
remote_version = remote_version, html_url = release.html_url,
@@ -96,8 +96,8 @@ function gen_outbound(flag, node, tag, proxy_table)
local proxy_tag, fragment, record_fragment
if proxy_table ~= nil and type(proxy_table) == "table" then
proxy_tag = proxy_table.tag or nil
fragment = (proxy_table.fragment and node.protocol ~= "naive" and not node.hysteria2_realms) and true or nil
record_fragment = (proxy_table.record_fragment and node.protocol ~= "naive" and not node.hysteria2_realms) and true or nil
fragment = (proxy_table.fragment and node.protocol ~= "naive" and node.protocol ~= "masque" and not node.hysteria2_realms) and true or nil
record_fragment = (proxy_table.record_fragment and node.protocol ~= "naive" and node.protocol ~= "masque" and not node.hysteria2_realms) and true or nil
end
if node.type ~= "sing-box" then
@@ -227,7 +227,14 @@ function gen_outbound(flag, node, tag, proxy_table)
end
local tls = nil
if node.protocol == "hysteria" or node.protocol == "hysteria2" or node.protocol == "tuic" or node.protocol == "naive" then
if node.protocol == "masque" then
node.alpn = nil
node.utls = nil
node.reality = nil
node.transport = nil
node.mux = nil
end
if node.protocol == "hysteria" or node.protocol == "hysteria2" or node.protocol == "tuic" or node.protocol == "naive" or node.protocol == "masque" then
node.tls = "1"
end
if node.tls == "1" then
@@ -687,6 +694,25 @@ function gen_outbound(flag, node, tag, proxy_table)
}
end
if node.protocol == "masque" then
local headers = {}
if node.user_agent and node.user_agent ~= "" then
headers["user-agent"] = { node.user_agent }
end
for line in (node.masque_headers or ""):gsub("\\n", "\n"):gmatch("[^\r\n]+") do
local key, value = line:match("^%s*([^:]+):%s*(.-)%s*$")
if key then headers[api.trim(key):lower()] = { value } end
end
protocol_table = {
type = "masque-client",
username = not headers.authorization and node.username or nil,
password = not headers.authorization and node.password or nil,
path = (node.masque_path and node.masque_path ~= "") and node.masque_path or nil,
headers = next(headers) and headers or nil,
tls = tls
}
end
if node.protocol == "snell" then
protocol_table = {
version = tonumber(node.snell_version),
@@ -2389,6 +2415,9 @@ function gen_config(var)
}
endpoints[#endpoints + 1] = endpoint
table.remove(config.outbounds, i)
elseif value.type == "masque-client" then
endpoints[#endpoints + 1] = value
table.remove(config.outbounds, i)
end
end
if #endpoints > 0 then
@@ -135,6 +135,13 @@ function gen_outbound(flag, node, tag, proxy_table)
node.transport = "hysteria"
node.stream_security = "tls"
end
if node.protocol == "masque" then
node.transport = "masque"
node.stream_security = "tls"
node.alpn = nil
node.utls = nil
node.finalmask = nil
end
if node.protocol == "http" and node.stream_security == "tls" then
node.transport = "raw"
@@ -152,13 +159,13 @@ function gen_outbound(flag, node, tag, proxy_table)
_flag_proxy_tag = proxy_tag,
tag = tag,
protocol = node.protocol,
mux = {
mux = (node.protocol ~= "masque") and {
enabled = (node.mux == "1") and true or false,
concurrency = (node.mux == "1" and ((node.mux_concurrency) and tonumber(node.mux_concurrency) or -1)) or nil,
xudpConcurrency = (node.mux == "1" and ((node.xudp_concurrency) and tonumber(node.xudp_concurrency) or 8)) or nil
} or nil,
-- 底层传输配置
streamSettings = (node.streamSettings or dialer_proxy_tag or node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan" or node.protocol == "hysteria" or node.protocol == "http") and {
streamSettings = (node.streamSettings or dialer_proxy_tag or node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan" or node.protocol == "hysteria" or node.protocol == "http" or node.protocol == "masque") and {
sockopt = {
mark = 255,
domainStrategy = node.domain_strategy or "UseIP",
@@ -268,6 +275,23 @@ function gen_outbound(flag, node, tag, proxy_table)
return api.cleanEmptyTables(extra)
end)()
} or nil,
masqueSettings = (node.protocol == "masque") and {
path = (node.masque_path and node.masque_path ~= "") and node.masque_path or nil,
headers = (function()
local headers = {}
if (node.username and node.username ~= "") or (node.password and node.password ~= "") then
headers["authorization"] = "Basic " .. api.base64Encode((node.username or "") .. ":" .. (node.password or ""))
end
if node.user_agent and node.user_agent ~= "" then
headers["user-agent"] = node.user_agent
end
for line in (node.masque_headers or ""):gsub("\\n", "\n"):gmatch("[^\r\n]+") do
local key, value = line:match("^%s*([^:]+):%s*(.-)%s*$")
if key then headers[api.trim(key):lower()] = value end
end
return next(headers) and headers or nil
end)()
} or nil,
hysteriaSettings = (node.transport == "hysteria") and {
version = 2,
auth = node.hysteria2_auth_password
+6
View File
@@ -2177,3 +2177,9 @@ msgstr "更新失败"
msgid "Unknown error"
msgstr "未知错误"
msgid "MASQUE Path"
msgstr "MASQUE 路径"
msgid "Custom HTTP Headers"
msgstr "自定义 HTTP 请求头"
@@ -946,7 +946,7 @@ start_crontab() {
clean_crontab
if [ "$ENABLED" != "1" ]; then
/etc/init.d/cron restart
[ -x /etc/init.d/cron ] && /etc/init.d/cron restart >/dev/null 2>&1
return
fi
@@ -1042,13 +1042,13 @@ start_crontab() {
echolog "运行于非代理模式,仅允许服务启停的定时任务。"
fi
/etc/init.d/cron restart
[ -x /etc/init.d/cron ] && /etc/init.d/cron restart >/dev/null 2>&1
}
stop_crontab() {
[ "$1" = "cron" ] && return
clean_crontab
/etc/init.d/cron restart
[ -x /etc/init.d/cron ] && /etc/init.d/cron restart >/dev/null 2>&1
#echolog "清除定时执行命令。"
}