luci: add sing-box client support

This commit is contained in:
xiaorouji 2023-09-06 15:53:41 +08:00 committed by sbwml
parent 56157adaf1
commit 2638af0efa
16 changed files with 1954 additions and 13 deletions

View File

@ -23,6 +23,7 @@ PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_SingBox \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_GO \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_tuic_client \
@ -50,6 +51,7 @@ LUCI_DEPENDS:=+coreutils +coreutils-base64 +coreutils-nohup +curl \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server \
+PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs:simple-obfs \
+PACKAGE_$(PKG_NAME)_INCLUDE_SingBox:sing-box \
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_GO:trojan-go \
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus:trojan-plus \
+PACKAGE_$(PKG_NAME)_INCLUDE_tuic_client:tuic-client \
@ -136,6 +138,10 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs
bool "Include Simple-Obfs (Shadowsocks Plugin)"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_SingBox
bool "Include Sing-Box"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_GO
bool "Include Trojan-GO"
default n

View File

@ -2,6 +2,7 @@ local api = require "luci.passwall.api"
local appname = api.appname
local uci = api.uci
local datatypes = api.datatypes
local has_singbox = api.finded_com("singbox")
local has_v2ray = api.finded_com("v2ray")
local has_xray = api.finded_com("xray")
local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
@ -94,7 +95,7 @@ udp_node:value("nil", translate("Close"))
udp_node:value("tcp", translate("Same as the tcp node"))
-- 分流
if (has_v2ray or has_xray) and #nodes_table > 0 then
if (has_singbox or has_v2ray or has_xray) and #nodes_table > 0 then
local normal_list = {}
local balancing_list = {}
local shunt_list = {}
@ -127,8 +128,11 @@ if (has_v2ray or has_xray) and #nodes_table > 0 then
if #normal_list > 0 then
for k, v in pairs(shunt_list) do
local vid = v.id
-- shunt node type, V2ray or Xray
-- shunt node type, Sing-Box or V2ray or Xray
local type = s:taboption("Main", ListValue, vid .. "-type", translate("Type"))
if has_singbox then
type:value("sing-box", "Sing-Box")
end
if has_v2ray then
type:value("V2ray", translate("V2ray"))
end
@ -159,7 +163,7 @@ if (has_v2ray or has_xray) and #nodes_table > 0 then
o.cfgvalue = get_cfgvalue(v.id, "main_node")
o.write = get_write(v.id, "main_node")
if (has_v2ray and has_xray) or (v.type == "V2ray" and not has_v2ray) or (v.type == "Xray" and not has_xray) then
if (has_singbox and has_v2ray and has_xray) or (v.type == "sing-box" and not has_singbox) or (v.type == "V2ray" and not has_v2ray) or (v.type == "Xray" and not has_xray) then
type:depends("tcp_node", v.id)
else
type:depends("tcp_node", "hide") --不存在的依赖,即始终隐藏
@ -247,7 +251,7 @@ tcp_node_socks_port.default = 1070
tcp_node_socks_port.datatype = "port"
tcp_node_socks_port:depends({ tcp_node = "nil", ["!reverse"] = true })
--[[
if has_v2ray or has_xray then
if has_singbox or has_v2ray or has_xray then
tcp_node_http_port = s:taboption("Main", Value, "tcp_node_http_port", translate("TCP Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
tcp_node_http_port.default = 0
tcp_node_http_port.datatype = "port"
@ -448,7 +452,7 @@ o.rmempty = false
o = s:taboption("log", Flag, "close_log_udp", translatef("%s Node Log Close", "UDP"))
o.rmempty = false
loglevel = s:taboption("log", ListValue, "loglevel", "V2ray/Xray " .. translate("Log Level"))
loglevel = s:taboption("log", ListValue, "loglevel", "Sing-Box/V2ray/Xray " .. translate("Log Level"))
loglevel.default = "warning"
loglevel:value("debug")
loglevel:value("info")
@ -520,7 +524,7 @@ o.default = n + 1080
o.datatype = "port"
o.rmempty = false
if has_v2ray or has_xray then
if has_singbox or has_v2ray or has_xray then
o = s:option(Value, "http_port", "HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
o.default = 0
o.datatype = "port"
@ -530,7 +534,7 @@ for k, v in pairs(nodes_table) do
tcp_node:value(v.id, v["remark"])
udp_node:value(v.id, v["remark"])
if v.type == "Socks" then
if has_v2ray or has_xray then
if has_singbox or has_v2ray or has_xray then
socks_node:value(v.id, v["remark"])
end
else

View File

@ -94,7 +94,7 @@ o.cfgvalue = function(t, n)
local remarks = m:get(n, "remarks") or ""
local type = m:get(n, "type") or ""
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.type' value='%s'/>", appname, n, type)
if type == "V2ray" or type == "Xray" then
if type == "sing-box" or type == "V2ray" or type == "Xray" then
local protocol = m:get(n, "protocol")
if protocol == "_balancing" then
protocol = translate("Balancing")

View File

@ -3,6 +3,7 @@ local appname = api.appname
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_trojan_plus = api.is_finded("trojan-plus")
local has_singbox = api.finded_com("singbox")
local has_v2ray = api.finded_com("v2ray")
local has_xray = api.finded_com("xray")
local has_trojan_go = api.finded_com("trojan-go")
@ -17,6 +18,10 @@ end
if has_trojan_plus then
trojan_type[#trojan_type + 1] = "trojan-plus"
end
if has_singbox then
trojan_type[#trojan_type + 1] = "sing-box"
ss_aead_type[#ss_aead_type + 1] = "sing-box"
end
if has_v2ray then
trojan_type[#trojan_type + 1] = "v2ray"
ss_aead_type[#ss_aead_type + 1] = "v2ray"

View File

@ -4,6 +4,7 @@ local sys = api.sys
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_trojan_plus = api.is_finded("trojan-plus")
local has_singbox = api.finded_com("singbox")
local has_v2ray = api.finded_com("v2ray")
local has_xray = api.finded_com("xray")
local has_trojan_go = api.finded_com("trojan-go")
@ -18,6 +19,10 @@ end
if has_trojan_plus then
trojan_type[#trojan_type + 1] = "trojan-plus"
end
if has_singbox then
trojan_type[#trojan_type + 1] = "sing-box"
ss_aead_type[#ss_aead_type + 1] = "sing-box"
end
if has_v2ray then
trojan_type[#trojan_type + 1] = "v2ray"
ss_aead_type[#ss_aead_type + 1] = "v2ray"

View File

@ -1,6 +1,7 @@
local api = require "luci.passwall.api"
local appname = api.appname
local fs = api.fs
local has_singbox = api.finded_com("singbox")
local has_v2ray = api.finded_com("v2ray")
local has_xray = api.finded_com("xray")
local has_fw3 = api.is_finded("fw3")
@ -171,4 +172,34 @@ if has_v2ray or has_xray then
o.datatype = "uinteger"
end
end
if has_singbox then
s = m:section(TypedSection, "global_singbox", "Sing-Box " .. translate("Settings"))
s.anonymous = true
s.addremove = false
o = s:option(Flag, "sniff_override_destination", translate("Override the connection destination address"), translate("Override the connection destination address with the sniffed domain."))
o.default = 1
o.rmempty = false
o = s:option(Value, "geoip_path", translate("Custom geoip Path"))
o.default = "/tmp/singbox/geoip.db"
o.rmempty = false
o = s:option(Value, "geoip_url", translate("Custom geoip URL"))
o.default = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
o:value("https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db")
o.rmempty = false
o = s:option(Value, "geosite_path", translate("Custom geosite Path"))
o.default = "/tmp/singbox/geosite.db"
o.rmempty = false
o = s:option(Value, "geosite_url", translate("Custom geosite URL"))
o.default = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
o:value("https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db")
o.rmempty = false
end
return m

View File

@ -0,0 +1,420 @@
local m, s = ...
local api = require "luci.passwall.api"
local singbox_bin = api.finded_com("singbox")
if not singbox_bin then
return
end
local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
local appname = api.appname
local uci = api.uci
local type_name = "sing-box"
local option_prefix = "singbox_"
local function option_name(name)
return option_prefix .. name
end
local function rm_prefix_cfgvalue(self, section)
if self.option:find(option_prefix) == 1 then
return m:get(section, self.option:sub(1 + #option_prefix))
end
end
local function rm_prefix_write(self, section, value)
if s.fields["type"]:formvalue(arg[1]) == type_name then
if self.option:find(option_prefix) == 1 then
m:set(section, self.option:sub(1 + #option_prefix), value)
end
end
end
local function rm_prefix_remove(self, section, value)
if s.fields["type"]:formvalue(arg[1]) == type_name then
if self.option:find(option_prefix) == 1 then
m:del(section, self.option:sub(1 + #option_prefix))
end
end
end
local ss_method_new_list = {
"none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
local ss_method_old_list = {
"aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20",
}
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
-- [[ sing-box ]]
s.fields["type"]:value(type_name, "Sing-Box")
o = s:option(ListValue, option_name("protocol"), translate("Protocol"))
o:value("socks", "Socks")
o:value("http", "HTTP")
o:value("shadowsocks", "Shadowsocks")
o:value("vmess", "Vmess")
o:value("trojan", "Trojan")
if singbox_tags:find("with_wireguard") then
o:value("wireguard", "WireGuard")
end
o:value("vless", "VLESS")
o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface") .. " (Only Support Xray)")
o = s:option(Value, option_name("iface"), translate("Interface"))
o.default = "eth1"
o:depends({ [option_name("protocol")] = "_iface" })
local nodes_table = {}
local balancers_table = {}
local iface_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
nodes_table[#nodes_table + 1] = {
id = e[".name"],
remarks = e["remark"]
}
end
if e.protocol == "_iface" then
iface_table[#iface_table + 1] = {
id = e[".name"],
remarks = e["remark"]
}
end
end
-- [[ 分流模块 ]]
if #nodes_table > 0 then
o = s:option(Flag, option_name("preproxy_enabled"), translate("Preproxy"))
o:depends({ [option_name("protocol")] = "_shunt" })
o = s:option(Value, option_name("main_node"), string.format('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."))
o:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true })
for k, v in pairs(balancers_table) do
o:value(v.id, v.remarks)
end
for k, v in pairs(iface_table) do
o:value(v.id, v.remarks)
end
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
end
o.default = "nil"
end
uci:foreach(appname, "shunt_rules", function(e)
if e[".name"] and e.remarks then
o = s:option(Value, option_name(e[".name"]), string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", e[".name"]), e.remarks))
o:value("nil", translate("Close"))
o:value("_default", translate("Default"))
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
o:depends({ [option_name("protocol")] = "_shunt" })
if #nodes_table > 0 then
for k, v in pairs(balancers_table) do
o:value(v.id, v.remarks)
end
for k, v in pairs(iface_table) do
o:value(v.id, v.remarks)
end
local pt = s:option(ListValue, option_name(e[".name"] .. "_proxy_tag"), string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy")))
pt:value("nil", translate("Close"))
pt:value("main", translate("Preproxy Node"))
pt.default = "nil"
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
pt:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true, [option_name(e[".name"])] = v.id })
end
end
end
end)
o = s:option(DummyValue, option_name("shunt_tips"), " ")
o.not_rewrite = true
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<a style="color: red" href="../rule">%s</a>', translate("No shunt rules? Click me to go to add."))
end
o:depends({ [option_name("protocol")] = "_shunt" })
local o = s:option(Value, option_name("default_node"), string.format('* <a style="color:red">%s</a>', translate("Default")))
o:depends({ [option_name("protocol")] = "_shunt" })
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
if #nodes_table > 0 then
for k, v in pairs(balancers_table) do
o:value(v.id, v.remarks)
end
for k, v in pairs(iface_table) do
o:value(v.id, v.remarks)
end
local dpt = s:option(ListValue, option_name("default_proxy_tag"), string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
dpt:value("nil", translate("Close"))
dpt:value("main", translate("Preproxy Node"))
dpt.default = "nil"
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
dpt:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true, [option_name("default_node")] = v.id })
end
end
-- [[ 分流模块 End ]]
o = s:option(Value, option_name("address"), translate("Address (Support Domain Name)"))
o = s:option(Value, option_name("port"), translate("Port"))
o.datatype = "port"
local protocols = s.fields[option_name("protocol")].keylist
if #protocols > 0 then
for index, value in ipairs(protocols) do
if not value:find("_") then
s.fields[option_name("address")]:depends({ [option_name("protocol")] = value })
s.fields[option_name("port")]:depends({ [option_name("protocol")] = value })
end
end
end
o = s:option(Value, option_name("username"), translate("Username"))
o:depends({ [option_name("protocol")] = "http" })
o:depends({ [option_name("protocol")] = "socks" })
o = s:option(Value, option_name("password"), translate("Password"))
o.password = true
o:depends({ [option_name("protocol")] = "http" })
o:depends({ [option_name("protocol")] = "socks" })
o:depends({ [option_name("protocol")] = "shadowsocks" })
o:depends({ [option_name("protocol")] = "trojan" })
o = s:option(ListValue, option_name("security"), translate("Encrypt Method"))
for a, t in ipairs(security_list) do o:value(t) end
o:depends({ [option_name("protocol")] = "vmess" })
o = s:option(ListValue, option_name("ss_method"), translate("Encrypt Method"))
o.not_rewrite = true
for a, t in ipairs(ss_method_new_list) do o:value(t) end
for a, t in ipairs(ss_method_old_list) do o:value(t) end
o:depends({ [option_name("protocol")] = "shadowsocks" })
function o.cfgvalue(self, section)
return m:get(section, "method")
end
function o.write(self, section, value)
if s.fields["type"]:formvalue(arg[1]) == type_name then
m:set(section, "method", value)
end
end
o = s:option(Flag, option_name("uot"), translate("UDP over TCP"), translate("Need Xray-core or sing-box as server side."))
o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("ss_method")] = "2022-blake3-aes-128-gcm" })
o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("ss_method")] = "2022-blake3-aes-256-gcm" })
o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("ss_method")] = "2022-blake3-chacha20-poly1305" })
o = s:option(Value, option_name("uuid"), translate("ID"))
o.password = true
o:depends({ [option_name("protocol")] = "vmess" })
o:depends({ [option_name("protocol")] = "vless" })
o = s:option(ListValue, option_name("flow"), translate("flow"))
o.default = ""
o:value("", translate("Disable"))
o:value("xtls-rprx-vision")
o:depends({ [option_name("protocol")] = "vless", [option_name("tls")] = true })
o = s:option(Flag, option_name("tls"), translate("TLS"))
o.default = 0
o:depends({ [option_name("protocol")] = "vmess" })
o:depends({ [option_name("protocol")] = "vless" })
o:depends({ [option_name("protocol")] = "socks" })
o:depends({ [option_name("protocol")] = "trojan" })
o:depends({ [option_name("protocol")] = "shadowsocks" })
if singbox_tags:find("with_reality") then
o = s:option(Flag, option_name("reality"), translate("REALITY"))
o.default = 0
o:depends({ [option_name("protocol")] = "vless", [option_name("tls")] = true })
-- [[ REALITY部分 ]] --
o = s:option(Value, option_name("reality_publicKey"), translate("Public Key"))
o:depends({ [option_name("tls")] = true, [option_name("reality")] = true })
o = s:option(Value, option_name("reality_shortId"), translate("Short Id"))
o:depends({ [option_name("tls")] = true, [option_name("reality")] = true })
end
o = s:option(ListValue, option_name("alpn"), translate("alpn"))
o.default = "default"
o:value("default", translate("Default"))
o:value("h2,http/1.1")
o:value("h2")
o:value("http/1.1")
o:depends({ [option_name("tls")] = true })
o = s:option(Value, option_name("tls_serverName"), translate("Domain"))
o:depends({ [option_name("tls")] = true })
o = s:option(Flag, option_name("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
o.default = "0"
o:depends({ [option_name("tls")] = true })
if singbox_tags:find("with_utls") then
o = s:option(Flag, option_name("utls"), translate("uTLS"))
o.default = "0"
o:depends({ [option_name("tls")] = true, [option_name("reality")] = false })
o = s:option(ListValue, option_name("fingerprint"), translate("Finger Print"))
o:value("chrome")
o:value("firefox")
o:value("edge")
o:value("safari")
o:value("360")
o:value("qq")
o:value("ios")
o:value("android")
o:value("random")
o:value("randomized")
o.default = "chrome"
o:depends({ [option_name("tls")] = true, [option_name("utls")] = true })
o:depends({ [option_name("tls")] = true, [option_name("reality")] = true })
end
o = s:option(ListValue, option_name("transport"), translate("Transport"))
o:value("tcp", "TCP")
o:value("http", "HTTP")
o:value("ws", "WebSocket")
if singbox_tags:find("with_quic") then
o:value("quic", "QUIC")
end
if singbox_tags:find("with_grpc") then
o:value("grpc", "gRPC")
end
o:depends({ [option_name("protocol")] = "vmess" })
o:depends({ [option_name("protocol")] = "vless" })
o:depends({ [option_name("protocol")] = "socks" })
o:depends({ [option_name("protocol")] = "shadowsocks" })
o:depends({ [option_name("protocol")] = "trojan" })
if singbox_tags:find("with_wireguard") then
o = s:option(Value, option_name("wireguard_public_key"), translate("Public Key"))
o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(Value, option_name("wireguard_secret_key"), translate("Private Key"))
o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(Value, option_name("wireguard_preSharedKey"), translate("Pre shared key"))
o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(DynamicList, option_name("wireguard_local_address"), translate("Local Address"))
o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(Value, option_name("wireguard_mtu"), translate("MTU"))
o.default = "1420"
o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(Value, option_name("wireguard_reserved"), translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
o:depends({ [option_name("protocol")] = "wireguard" })
end
-- [[ HTTP部分 ]]--
o = s:option(Value, option_name("http_host"), translate("HTTP Host"))
o:depends({ [option_name("transport")] = "http" })
o = s:option(Value, option_name("http_path"), translate("HTTP Path"))
o.placeholder = "/"
o:depends({ [option_name("transport")] = "http" })
o = s:option(Flag, option_name("http_h2_health_check"), translate("Health check"))
o:depends({ [option_name("tls")] = true, [option_name("transport")] = "http" })
o = s:option(Value, option_name("http_h2_read_idle_timeout"), translate("Idle timeout"))
o.default = "10"
o:depends({ [option_name("tls")] = true, [option_name("transport")] = "http", [option_name("http_h2_health_check")] = true })
o = s:option(Value, option_name("http_h2_health_check_timeout"), translate("Health check timeout"))
o.default = "15"
o:depends({ [option_name("tls")] = true, [option_name("transport")] = "http", [option_name("http_h2_health_check")] = true })
-- [[ WebSocket部分 ]]--
o = s:option(Value, option_name("ws_host"), translate("WebSocket Host"))
o:depends({ [option_name("transport")] = "ws" })
o = s:option(Value, option_name("ws_path"), translate("WebSocket Path"))
o.placeholder = "/"
o:depends({ [option_name("transport")] = "ws" })
o = s:option(Flag, option_name("ws_enableEarlyData"), translate("Enable early data"))
o:depends({ [option_name("transport")] = "ws" })
o = s:option(Value, option_name("ws_maxEarlyData"), translate("Early data length"))
o.default = "1024"
o:depends({ [option_name("ws_enableEarlyData")] = true })
o = s:option(Value, option_name("ws_earlyDataHeaderName"), translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
o:depends({ [option_name("ws_enableEarlyData")] = true })
-- [[ gRPC部分 ]]--
if singbox_tags:find("with_grpc") then
o = s:option(Value, option_name("grpc_serviceName"), "ServiceName")
o:depends({ [option_name("transport")] = "grpc" })
o = s:option(Flag, option_name("grpc_health_check"), translate("Health check"))
o:depends({ [option_name("transport")] = "grpc" })
o = s:option(Value, option_name("grpc_idle_timeout"), translate("Idle timeout"))
o.default = "10"
o:depends({ [option_name("grpc_health_check")] = true })
o = s:option(Value, option_name("grpc_health_check_timeout"), translate("Health check timeout"))
o.default = "20"
o:depends({ [option_name("grpc_health_check")] = true })
o = s:option(Flag, option_name("grpc_permit_without_stream"), translate("Permit without stream"))
o.default = "0"
o:depends({ [option_name("grpc_health_check")] = true })
end
-- [[ Mux ]]--
o = s:option(Flag, option_name("mux"), translate("Mux"))
o.rmempty = false
o:depends({ [option_name("protocol")] = "vmess" })
o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "" })
o:depends({ [option_name("protocol")] = "http" })
o:depends({ [option_name("protocol")] = "socks" })
o:depends({ [option_name("protocol")] = "shadowsocks" })
o:depends({ [option_name("protocol")] = "trojan" })
o = s:option(ListValue, option_name("mux_type"), translate("Mux"))
o:value("smux")
o:value("yamux")
o:value("h2mux")
o:depends({ [option_name("mux")] = true })
o = s:option(Value, option_name("mux_concurrency"), translate("Mux concurrency"))
o.default = 8
o:depends({ [option_name("mux")] = true })
for key, value in pairs(s.fields) do
if key:find(option_prefix) == 1 then
if not s.fields[key].not_rewrite then
s.fields[key].cfgvalue = rm_prefix_cfgvalue
s.fields[key].write = rm_prefix_write
s.fields[key].remove = rm_prefix_remove
end
local deps = s.fields[key].deps
if #deps > 0 then
for index, value in ipairs(deps) do
deps[index]["type"] = type_name
end
else
s.fields[key]:depends({ type = type_name })
end
end
end

View File

@ -298,7 +298,7 @@ function get_valid_nodes()
local address = e.address
if is_ip(address) or datatypes.hostname(address) then
local type = e.type
if (type == "V2ray" or type == "Xray") and e.protocol then
if (type == "sing-box" or type == "V2ray" or type == "Xray") and e.protocol then
local protocol = e.protocol
if protocol == "vmess" then
protocol = "VMess"
@ -330,7 +330,7 @@ function get_node_remarks(n)
remarks = "%s[%s] " % {n.type .. " " .. i18n.translatef(n.protocol), n.remarks}
else
local type2 = n.type
if (n.type == "V2ray" or n.type == "Xray") and n.protocol then
if (n.type == "sing-box" or n.type == "V2ray" or n.type == "Xray") and n.protocol then
local protocol = n.protocol
if protocol == "vmess" then
protocol = "VMess"

View File

@ -49,6 +49,19 @@ _M["trojan-go"] = {
}
}
_M.singbox = {
name = "Sing-Box",
repo = "SagerNet/sing-box",
get_url = gh_pre_release_url,
cmd_version = "version | awk '{print $3}' | sed -n 1P",
zipped = true,
default_path = "/usr/bin/sing-box",
match_fmt_str = "linux%%-%s",
file_tree = {
x86_64 = "amd64"
}
}
_M.v2ray = {
name = "V2ray",
repo = "v2fly/v2ray-core",

File diff suppressed because it is too large Load Diff

View File

@ -1458,3 +1458,21 @@ msgstr "端口跳跃时间 "
msgid "Additional ports for hysteria hop"
msgstr "端口跳跃额外端口"
msgid "Custom geoip Path"
msgstr "自定义geoip文件路径"
msgid "Custom geoip URL"
msgstr "自定义geoip文件更新链接"
msgid "Custom geosite Path"
msgstr "自定义geosite文件路径"
msgid "Custom geosite URL"
msgstr "自定义geosite文件更新链接"
msgid "Override the connection destination address"
msgstr "覆盖连接目标地址"
msgid "Override the connection destination address with the sniffed domain."
msgstr "用探测出的域名覆盖连接目标地址。"

View File

@ -46,6 +46,17 @@ global_xray=$(uci -q get passwall.@global_xray[0])
sed -i "s#option tlsflow#option flow#g" /etc/config/passwall
global_singbox=$(uci -q get passwall.@global_singbox[0])
[ -z "${global_singbox}" ] && {
cfgid=$(uci add passwall global_singbox)
uci -q set passwall.${cfgid}.sniff_override_destination=1
uci -q set passwall.${cfgid}.geoip_path="/tmp/singbox/geoip.db"
uci -q set passwall.${cfgid}.geoip_url="https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
uci -q set passwall.${cfgid}.geosite_path="/tmp/singbox/geosite.db"
uci -q set passwall.${cfgid}.geosite_url="https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
uci -q commit passwall
}
rm -f /tmp/luci-indexcache
rm -rf /tmp/luci-modulecache/
killall -HUP rpcd 2>/dev/null

View File

@ -42,6 +42,13 @@ config global_forwarding
config global_xray
option sniffing '1'
option route_only '0'
config global_singbox
option sniff_override_destination '1'
option geoip_path '/tmp/singbox/geoip.db'
option geoip_url 'https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db'
option geosite_path '/tmp/singbox/geosite.db'
option geosite_url 'https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db'
config global_other
option nodes_ping 'auto_ping tcping'
@ -65,6 +72,7 @@ config global_rules
option v2ray_location_asset '/usr/share/v2ray/'
config global_app
option singbox_file '/usr/bin/sing-box'
option v2ray_file '/usr/bin/v2ray'
option xray_file '/usr/bin/xray'
option trojan_go_file '/usr/bin/trojan-go'

View File

@ -1125,6 +1125,7 @@ flush_ipset() {
for _name in $(ipset list | grep "Name: " | grep "passwall_" | awk '{print $2}'); do
destroy_ipset ${_name}
done
rm -rf /tmp/singbox_passwall*
rm -rf /tmp/etc/passwall_tmp/dnsmasq*
/etc/init.d/passwall reload
}

View File

@ -1166,6 +1166,7 @@ flush_nftset() {
for _name in $(nft -a list sets | grep -E "passwall" | awk -F 'set ' '{print $2}' | awk '{print $1}'); do
destroy_nftset ${_name}
done
rm -rf /tmp/singbox_passwall*
rm -rf /tmp/etc/passwall_tmp/dnsmasq*
/etc/init.d/passwall reload
}

View File

@ -24,6 +24,7 @@ uci:revert(appname)
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_trojan_plus = api.is_finded("trojan-plus")
local has_singbox = api.finded_com("singbox")
local has_v2ray = api.finded_com("v2ray")
local has_xray = api.finded_com("xray")
local has_trojan_go = api.finded_com("trojan-go")
@ -395,7 +396,9 @@ local function processData(szType, content, add_mode, add_from)
result.remarks = base64Decode(params.remarks)
elseif szType == 'vmess' then
local info = jsonParse(content)
if has_v2ray then
if has_singbox then
result.type = 'sing-box'
elseif has_v2ray then
result.type = 'V2ray'
elseif has_xray then
result.type = 'Xray'
@ -542,6 +545,9 @@ local function processData(szType, content, add_mode, add_from)
if method:lower() == "chacha20-poly1305" then
result.method = "chacha20-ietf-poly1305"
end
elseif ss_aead_type_default == "sing-box" and has_singbox and not result.plugin then
result.type = 'sing-box'
result.protocol = 'shadowsocks'
elseif ss_aead_type_default == "v2ray" and has_v2ray and not result.plugin then
result.type = 'V2ray'
result.protocol = 'shadowsocks'
@ -565,7 +571,10 @@ local function processData(szType, content, add_mode, add_from)
end
end
if aead2022 then
if ss_aead_type_default == "xray" and has_xray and not result.plugin then
if ss_aead_type_default == "sing-box" and has_singbox and not result.plugin then
result.type = 'sing-box'
result.protocol = 'shadowsocks'
elseif ss_aead_type_default == "xray" and has_xray and not result.plugin then
result.type = 'Xray'
result.protocol = 'shadowsocks'
result.transport = 'tcp'
@ -644,6 +653,9 @@ local function processData(szType, content, add_mode, add_from)
end
if trojan_type_default == "trojan-plus" and has_trojan_plus then
result.type = "Trojan-Plus"
elseif trojan_type_default == "sing-box" and has_singbox then
result.type = 'sing-box'
result.protocol = 'trojan'
elseif trojan_type_default == "v2ray" and has_v2ray then
result.type = 'V2ray'
result.protocol = 'trojan'
@ -721,7 +733,9 @@ local function processData(szType, content, add_mode, add_from)
result.group = content.airport
result.remarks = content.remarks
elseif szType == "vless" then
if has_xray then
if has_singbox then
result.type = 'sing-box'
elseif has_xray then
result.type = 'Xray'
elseif has_v2ray then
result.type = 'V2ray'