diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile
index 9c503f189..7d64fba3a 100644
--- a/luci-app-passwall2/Makefile
+++ b/luci-app-passwall2/Makefile
@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
-PKG_VERSION:=1.19-6
+PKG_VERSION:=1.20-4
PKG_RELEASE:=
PKG_CONFIG_DEPENDS:= \
@@ -23,8 +23,8 @@ 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_tuic_client \
- CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
LUCI_TITLE:=LuCI support for PassWall 2
@@ -47,8 +47,8 @@ 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_tuic_client:tuic-client \
- +PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin:v2ray-plugin
define Package/$(PKG_NAME)/config
@@ -133,15 +133,15 @@ 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_tuic_client
bool "Include tuic-client"
depends on aarch64||arm||i386||x86_64
default n
-config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray
- bool "Include V2ray"
- default n
-
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
bool "Include V2ray-Plugin (Shadowsocks Plugin)"
default y if aarch64||arm||i386||x86_64
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua
index 68cced9e2..da57fc056 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua
@@ -2,7 +2,7 @@ local api = require "luci.passwall2.api"
local appname = api.appname
local uci = api.uci
local datatypes = api.datatypes
-local has_v2ray = api.is_finded("v2ray")
+local has_singbox = api.is_finded("sing-box")
local has_xray = api.is_finded("xray")
m = Map(appname)
@@ -36,6 +36,8 @@ end
m:append(Template(appname .. "/global/status"))
+local global_cfgid = uci:get_all(appname, "@global[0]")[".name"]
+
s = m:section(TypedSection, "global")
s.anonymous = true
s.addremove = false
@@ -51,7 +53,7 @@ node = s:taboption("Main", ListValue, "node", "" .. transl
node:value("nil", translate("Close"))
-- 分流
-if (has_v2ray or has_xray) and #nodes_table > 0 then
+if (has_singbox or has_xray) and #nodes_table > 0 then
local normal_list = {}
local balancing_list = {}
local shunt_list = {}
@@ -84,10 +86,10 @@ 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 Xray
local type = s:taboption("Main", ListValue, vid .. "-type", translate("Type"))
- if has_v2ray then
- type:value("V2ray", translate("V2ray"))
+ if has_singbox then
+ type:value("sing-box", translate("Sing-Box"))
end
if has_xray then
type:value("Xray", translate("Xray"))
@@ -119,7 +121,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_xray) or (v.type == "sing-box" and not has_singbox) or (v.type == "Xray" and not has_xray) then
type:depends("node", v.id)
else
type:depends("node", "hide") --不存在的依赖,即始终隐藏
@@ -212,14 +214,6 @@ node_socks_port = s:taboption("Main", Value, "node_socks_port", translate("Node"
node_socks_port.default = 1070
node_socks_port.datatype = "port"
---[[
-if has_v2ray or has_xray then
- node_http_port = s:taboption("Main", Value, "node_http_port", translate("Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
- node_http_port.default = 0
- node_http_port.datatype = "port"
-end
-]]--
-
s:tab("DNS", translate("DNS"))
o = s:taboption("DNS", ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
@@ -260,8 +254,6 @@ o = s:taboption("DNS", Value, "remote_dns_client_ip", translate("Remote DNS EDNS
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "
" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr"
-o:depends("remote_dns_protocol", "tcp")
-o:depends("remote_dns_protocol", "doh")
o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
o.default = "0"
@@ -273,9 +265,16 @@ o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
-hosts = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
-hosts.rows = 5
-hosts.wrap = "off"
+o = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
+o.rows = 5
+o.wrap = "off"
+o.remove = function(self, section)
+ local node_value = node:formvalue(global_cfgid)
+ local node_t = m:get(node_value)
+ if node_t.type == "Xray" then
+ AbstractValue.remove(self, section)
+ end
+end
o = s:taboption("DNS", Button, "clear_ipset", translate("Clear IPSET"), translate("Try this feature if the rule modification does not take effect."))
o.inputstyle = "remove"
@@ -284,6 +283,14 @@ function o.write(e, e)
luci.http.redirect(api.url("log"))
end
+for k, v in pairs(nodes_table) do
+ if v.type == "Xray" then
+ s.fields["remote_dns_client_ip"]:depends({ node = v.id, remote_dns_protocol = "tcp" })
+ s.fields["remote_dns_client_ip"]:depends({ node = v.id, remote_dns_protocol = "doh" })
+ s.fields["dns_hosts"]:depends({ node = v.id })
+ end
+end
+
s:tab("log", translate("Log"))
o = s:taboption("log", Flag, "close_log", translate("Close Node Log"))
o.rmempty = false
@@ -342,7 +349,7 @@ o.default = n + 1080
o.datatype = "port"
o.rmempty = false
-if has_v2ray or has_xray then
+if has_singbox 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"
@@ -351,7 +358,7 @@ end
for k, v in pairs(nodes_table) do
node:value(v.id, v["remark"])
if v.type == "Socks" then
- if has_v2ray or has_xray then
+ if has_singbox or has_xray then
socks_node:value(v.id, v["remark"])
end
else
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua
index af6d32533..83584fe2c 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua
@@ -83,7 +83,7 @@ o.cfgvalue = function(t, n)
local remarks = m:get(n, "remarks") or ""
local type = m:get(n, "type") or ""
str = str .. string.format("", appname, n, type)
- if type == "V2ray" or type == "Xray" then
+ if type == "sing-box" or type == "Xray" then
local protocol = m:get(n, "protocol")
if protocol == "_balancing" then
protocol = translate("Balancing")
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
index 044d3eb8c..824ef1e28 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
@@ -2,7 +2,7 @@ local api = require "luci.passwall2.api"
local appname = api.appname
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
-local has_v2ray = api.is_finded("v2ray")
+local has_singbox = api.is_finded("sing-box")
local has_xray = api.is_finded("xray")
local ss_aead_type = {}
if has_ss then
@@ -11,8 +11,8 @@ end
if has_ss_rust then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust"
end
-if has_v2ray then
- ss_aead_type[#ss_aead_type + 1] = "v2ray"
+if has_singbox then
+ ss_aead_type[#ss_aead_type + 1] = "sing-box"
end
if has_xray then
ss_aead_type[#ss_aead_type + 1] = "xray"
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
index 1a23f9f78..a833c186b 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
@@ -3,7 +3,7 @@ local appname = api.appname
local sys = api.sys
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
-local has_v2ray = api.is_finded("v2ray")
+local has_singbox = api.is_finded("sing-box")
local has_xray = api.is_finded("xray")
local ss_aead_type = {}
if has_ss then
@@ -12,8 +12,8 @@ end
if has_ss_rust then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust"
end
-if has_v2ray then
- ss_aead_type[#ss_aead_type + 1] = "v2ray"
+if has_singbox then
+ ss_aead_type[#ss_aead_type + 1] = "sing-box"
end
if has_xray then
ss_aead_type[#ss_aead_type + 1] = "xray"
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
index df29e2871..41a4e4817 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
@@ -1,7 +1,8 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local fs = api.fs
-local has_v2ray = api.is_finded("v2ray")
+local uci = api.uci
+local has_singbox = api.is_finded("sing-box")
local has_xray = api.is_finded("xray")
local has_fw3 = api.is_finded("fw3")
local has_fw4 = api.is_finded("fw4")
@@ -126,8 +127,8 @@ o = s:option(Flag, "accept_icmpv6", translate("Hijacking ICMPv6 (IPv6 PING)"))
o:depends("ipv6_tproxy", true)
o.default = 0
-if has_v2ray or has_xray then
- s = m:section(TypedSection, "global_xray", "V2Ray/Xray " .. translate("Settings"))
+if has_xray then
+ s = m:section(TypedSection, "global_xray", "Xray " .. translate("Settings"))
s.anonymous = true
s.addremove = false
@@ -158,4 +159,33 @@ if has_v2ray or has_xray then
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
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua
index 56ce0fcf9..706ac26e3 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua
@@ -41,7 +41,7 @@ for e = 0, 23 do o:value(e, e .. translate("oclock")) end
o.default = 0
o:depends("auto_update", true)
-s = m:section(TypedSection, "shunt_rules", "V2ray/Xray " .. translate("Shunt Rule"), "" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "")
+s = m:section(TypedSection, "shunt_rules", "Xray " .. translate("Shunt Rule"), "" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "")
s.template = "cbi/tblsection"
s.anonymous = false
s.addremove = true
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua
index 9fb2f9aca..77f111151 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua
@@ -2,7 +2,7 @@ local api = require "luci.passwall2.api"
local appname = api.appname
local datatypes = api.datatypes
-m = Map(appname, "V2ray/Xray " .. translate("Shunt Rule"))
+m = Map(appname, "Xray " .. translate("Shunt Rule"))
m.redirect = api.url()
s = m:section(NamedSection, arg[1], "shunt_rules", "")
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
index e0f321fde..bb4d2763e 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua
@@ -1,7 +1,7 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local uci = api.uci
-local has_v2ray = api.is_finded("v2ray")
+local has_singbox = api.is_finded("sing-box")
local has_xray = api.is_finded("xray")
m = Map(appname)
@@ -54,7 +54,7 @@ o.default = n + 1080
o.datatype = "port"
o.rmempty = false
-if has_v2ray or has_xray then
+if has_singbox 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"
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
index c8e082b23..ebbd61e0f 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua
@@ -2,13 +2,15 @@ local m, s = ...
local api = require "luci.passwall2.api"
-if not api.is_finded("xray") and not api.is_finded("v2ray") then
+if not api.is_finded("xray") then
return
end
local appname = api.appname
local uci = api.uci
+local type_name = "Xray"
+
local option_prefix = "xray_"
local function option_name(name)
@@ -21,52 +23,20 @@ local function rm_prefix_cfgvalue(self, section)
end
end
local function rm_prefix_write(self, section, value)
- if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then
+ 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]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then
+ 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 function add_xray_depends(o, field, value)
- local deps = { type = "Xray" }
- if field then
- if type(field) == "string" then
- deps[field] = value
- else
- for key, value in pairs(field) do
- deps[key] = value
- end
- end
- end
- o:depends(deps)
-end
-
-local function add_v2ray_depends(o, field, value)
- local deps = { type = "V2ray" }
- if field then
- if type(field) == "string" then
- deps[field] = value
- else
- for key, value in pairs(field) do
- deps[key] = value
- end
- end
- end
- o:depends(deps)
-end
-
-local v_ss_encrypt_method_list = {
- "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305"
-}
-
local x_ss_encrypt_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
@@ -79,12 +49,7 @@ local header_type_list = {
-- [[ Xray ]]
-if api.is_finded("xray") then
- s.fields["type"]:value("Xray", translate("Xray"))
-end
-if api.is_finded("v2ray") then
- s.fields["type"]:value("V2ray", translate("V2ray"))
-end
+s.fields["type"]:value(type_name, "Xray")
o = s:option(ListValue, option_name("protocol"), translate("Protocol"))
o:value("vmess", translate("Vmess"))
@@ -97,8 +62,6 @@ o:value("wireguard", translate("WireGuard"))
o:value("_balancing", translate("Balancing"))
o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface") .. " (Only Support Xray)")
-add_xray_depends(o)
-add_v2ray_depends(o)
o = s:option(Value, option_name("iface"), translate("Interface"))
o.default = "eth1"
@@ -131,31 +94,26 @@ end
-- 负载均衡列表
local o = s:option(DynamicList, option_name("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, document"))
o:depends({ [option_name("protocol")] = "_balancing" })
-add_v2ray_depends(o, { [option_name("protocol")] = "_balancing" })
for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
local o = s:option(ListValue, option_name("balancingStrategy"), translate("Balancing Strategy"))
-add_xray_depends(o, { [option_name("protocol")] = "_balancing" })
-add_v2ray_depends(o, { [option_name("protocol")] = "_balancing" })
+o:depends({ [option_name("protocol")] = "_balancing" })
o:value("random")
o:value("leastPing")
o.default = "random"
-- 探测地址
local o = s:option(Flag, option_name("useCustomProbeUrl"), translate("Use Custome Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
-add_xray_depends(o, { [option_name("balancingStrategy")] = "leastPing" })
-add_v2ray_depends(o, { [option_name("balancingStrategy")] = "leastPing" })
+o:depends({ [option_name("balancingStrategy")] = "leastPing" })
local o = s:option(Value, option_name("probeUrl"), translate("Probe URL"))
-add_xray_depends(o, { [option_name("useCustomProbeUrl")] = true })
-add_v2ray_depends(o, { [option_name("useCustomProbeUrl")] = true })
+o:depends({ [option_name("useCustomProbeUrl")] = true })
o.default = "https://www.google.com/generate_204"
o.description = translate("The URL used to detect the connection status.")
-- 探测间隔
local o = s:option(Value, option_name("probeInterval"), translate("Probe Interval"))
-add_xray_depends(o, { [option_name("balancingStrategy")] = "leastPing" })
-add_v2ray_depends(o, { [option_name("balancingStrategy")] = "leastPing" })
+o:depends({ [option_name("balancingStrategy")] = "leastPing" })
o.default = "1m"
o.description = translate("The interval between initiating probes. Every time this time elapses, a server status check is performed on a server. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are ns
, us
, ms
, s
, m
, h
, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.")
@@ -163,11 +121,9 @@ o.description = translate("The interval between initiating probes. Every time th
if #nodes_table > 0 then
o = s:option(Flag, option_name("preproxy_enabled"), translate("Preproxy"))
o:depends({ [option_name("protocol")] = "_shunt" })
- add_v2ray_depends(o, { [option_name("protocol")] = "_shunt" })
o = s:option(Value, option_name("main_node"), string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default
) 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 })
- add_v2ray_depends(o, { [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true })
for k, v in pairs(balancers_table) do
o:value(v.id, v.remarks)
end
@@ -187,7 +143,6 @@ uci:foreach(appname, "shunt_rules", function(e)
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
o:depends({ [option_name("protocol")] = "_shunt" })
- add_v2ray_depends(o, { [option_name("protocol")] = "_shunt" })
if #nodes_table > 0 then
for k, v in pairs(balancers_table) do
@@ -203,7 +158,6 @@ uci:foreach(appname, "shunt_rules", function(e)
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 })
- add_v2ray_depends(o, { [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true, [option_name(e[".name"])] = v.id })
end
end
end
@@ -258,134 +212,88 @@ o:depends({ [option_name("protocol")] = "_shunt" })
-- [[ 分流模块 End ]]
o = s:option(Value, option_name("address"), translate("Address (Support Domain Name)"))
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_xray_depends(o, { [option_name("protocol")] = "http" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
-add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "http" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
o = s:option(Value, option_name("port"), translate("Port"))
o.datatype = "port"
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_xray_depends(o, { [option_name("protocol")] = "http" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
-add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "http" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
+
+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"))
-add_xray_depends(o, { [option_name("protocol")] = "http" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "http" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
+o:depends({ [option_name("protocol")] = "http" })
+o:depends({ [option_name("protocol")] = "socks" })
o = s:option(Value, option_name("password"), translate("Password"))
o.password = true
-add_xray_depends(o, { [option_name("protocol")] = "http" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
-add_v2ray_depends(o, { [option_name("protocol")] = "http" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
+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
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
+o:depends({ [option_name("protocol")] = "vmess" })
o = s:option(Value, option_name("encryption"), translate("Encrypt Method"))
o.default = "none"
o:value("none")
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-
-o = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method"))
-o.not_rewrite = true
-for a, t in ipairs(v_ss_encrypt_method_list) do o:value(t) end
-add_v2ray_depends(o, { [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]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then
- m:set(section, "method", value)
- end
-end
+o:depends({ [option_name("protocol")] = "vless" })
o = s:option(ListValue, option_name("x_ss_encrypt_method"), translate("Encrypt Method"))
o.not_rewrite = true
for a, t in ipairs(x_ss_encrypt_method_list) do o:value(t) end
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
+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]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then
+ if s.fields["type"]:formvalue(arg[1]) == type_name then
m:set(section, "method", value)
end
end
o = s:option(Flag, option_name("iv_check"), translate("IV Check"))
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "aes-128-gcm" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "aes-256-gcm" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "chacha20-poly1305" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "xchacha20-poly1305" })
+o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "aes-128-gcm" })
+o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "aes-256-gcm" })
+o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "chacha20-poly1305" })
+o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "xchacha20-poly1305" })
o = s:option(Flag, option_name("uot"), translate("UDP over TCP"), translate("Need Xray-core or sing-box as server side."))
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-aes-128-gcm" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-aes-256-gcm" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-chacha20-poly1305" })
+o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-aes-128-gcm" })
+o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-aes-256-gcm" })
+o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-chacha20-poly1305" })
o = s:option(Value, option_name("uuid"), translate("ID"))
o.password = true
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
+o:depends({ [option_name("protocol")] = "vmess" })
+o:depends({ [option_name("protocol")] = "vless" })
-o = s:option(Flag, option_name("tls"), translate("TLS"))
-o.default = 0
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-
-o = s:option(Value, option_name("tlsflow"), translate("flow"))
+o = s:option(ListValue, option_name("flow"), translate("flow"))
o.default = ""
o:value("", translate("Disable"))
o:value("xtls-rprx-vision")
-o:value("xtls-rprx-vision-udp443")
-add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tls")] = true, [option_name("transport")] = "tcp" })
+o:depends({ [option_name("protocol")] = "vless" })
-o = s:option(Flag, option_name("reality"), translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision."))
+o = s:option(Flag, option_name("tls"), translate("TLS"))
o.default = 0
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("transport")] = "tcp" })
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("transport")] = "h2" })
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("transport")] = "grpc" })
+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" })
+
+o = s:option(Flag, option_name("reality"), translate("REALITY"))
+o.default = 0
+o:depends({ [option_name("tls")] = true, [option_name("transport")] = "tcp" })
+o:depends({ [option_name("tls")] = true, [option_name("transport")] = "h2" })
+o:depends({ [option_name("tls")] = true, [option_name("transport")] = "grpc" })
o = s:option(ListValue, option_name("alpn"), translate("alpn"))
o.default = "default"
@@ -393,72 +301,49 @@ o:value("default", translate("Default"))
o:value("h2,http/1.1")
o:value("h2")
o:value("http/1.1")
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = false })
-add_v2ray_depends(o, { [option_name("tls")] = true })
+o:depends({ [option_name("tls")] = true, [option_name("reality")] = false })
-- o = s:option(Value, option_name("minversion"), translate("minversion"))
-- o.default = "1.3"
-- o:value("1.3")
--- add_xray_depends(o, { [option_name("tls")] = true })
--- add_v2ray_depends(o, { [option_name("tls")] = true })
+-- o:depends({ [option_name("tls")] = true })
o = s:option(Value, option_name("tls_serverName"), translate("Domain"))
-add_xray_depends(o, { [option_name("tls")] = true })
-add_v2ray_depends(o, { [option_name("tls")] = true })
+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"
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = false })
-add_v2ray_depends(o, { [option_name("tls")] = true })
-
-o = s:option(Value, option_name("fingerprint"), translate("Finger Print"), translate("Avoid using randomized, unless you have to."))
-o:value("", translate("Disable"))
-o:value("chrome")
-o:value("firefox")
-o:value("safari")
-o:value("ios")
--- o:value("android")
-o:value("edge")
--- o:value("360")
-o:value("qq")
-o:value("random")
-o:value("randomized")
-o.default = ""
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = false })
+o:depends({ [option_name("tls")] = true, [option_name("reality")] = false })
-- [[ REALITY部分 ]] --
o = s:option(Value, option_name("reality_publicKey"), translate("Public Key"))
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = true })
+o:depends({ [option_name("tls")] = true, [option_name("reality")] = true })
o = s:option(Value, option_name("reality_shortId"), translate("Short Id"))
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = true })
+o:depends({ [option_name("tls")] = true, [option_name("reality")] = true })
o = s:option(Value, option_name("reality_spiderX"), translate("Spider X"))
o.placeholder = "/"
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = true })
+o:depends({ [option_name("tls")] = true, [option_name("reality")] = true })
-o = s:option(Value, option_name("reality_fingerprint"), translate("Finger Print"), translate("Avoid using randomized, unless you have to."))
-o.not_rewrite = true
+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("safari")
-o:value("ios")
--- o:value("android")
o:value("edge")
--- o:value("360")
+o:value("safari")
+o:value("360")
o:value("qq")
+o:value("ios")
+o:value("android")
o:value("random")
o:value("randomized")
o.default = "chrome"
-add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = true })
-function o.cfgvalue(self, section)
- return m:get(section, "fingerprint")
-end
-function o.write(self, section, value)
- if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then
- m:set(section, "fingerprint", value)
- end
-end
+o:depends({ [option_name("tls")] = true, [option_name("utls")] = true })
+o:depends({ [option_name("tls")] = true, [option_name("reality")] = true })
o = s:option(ListValue, option_name("transport"), translate("Transport"))
o:value("tcp", "TCP")
@@ -468,50 +353,44 @@ o:value("h2", "HTTP/2")
o:value("ds", "DomainSocket")
o:value("quic", "QUIC")
o:value("grpc", "gRPC")
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
+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" })
--[[
o = s:option(ListValue, option_name("ss_transport"), translate("Transport"))
o:value("ws", "WebSocket")
o:value("h2", "HTTP/2")
o:value("h2+ws", "HTTP/2 & WebSocket")
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
+o:depends({ [option_name("protocol")] = "shadowsocks" })
]]--
o = s:option(Value, option_name("wireguard_public_key"), translate("Public Key"))
-add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
+o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(Value, option_name("wireguard_secret_key"), translate("Private Key"))
-add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
+o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(Value, option_name("wireguard_preSharedKey"), translate("Pre shared key"))
-add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
+o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(DynamicList, option_name("wireguard_local_address"), translate("Local Address"))
-add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
+o:depends({ [option_name("protocol")] = "wireguard" })
o = s:option(Value, option_name("wireguard_mtu"), translate("MTU"))
o.default = "1420"
-add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
+o:depends({ [option_name("protocol")] = "wireguard" })
if api.compare_versions(api.get_app_version("xray"), ">=", "1.8.0") then
o = s:option(Value, option_name("wireguard_reserved"), translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
- add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
+ o:depends({ [option_name("protocol")] = "wireguard" })
end
o = s:option(Value, option_name("wireguard_keepAlive"), translate("Keep Alive"))
o.default = "0"
-add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
+o:depends({ [option_name("protocol")] = "wireguard" })
-- [[ TCP部分 ]]--
@@ -519,194 +398,152 @@ add_xray_depends(o, { [option_name("protocol")] = "wireguard" })
o = s:option(ListValue, option_name("tcp_guise"), translate("Camouflage Type"))
o:value("none", "none")
o:value("http", "http")
-add_xray_depends(o, { [option_name("transport")] = "tcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "tcp" })
+o:depends({ [option_name("transport")] = "tcp" })
-- HTTP域名
o = s:option(DynamicList, option_name("tcp_guise_http_host"), translate("HTTP Host"))
-add_xray_depends(o, { [option_name("tcp_guise")] = "http" })
-add_v2ray_depends(o, { [option_name("tcp_guise")] = "http" })
+o:depends({ [option_name("tcp_guise")] = "http" })
-- HTTP路径
o = s:option(DynamicList, option_name("tcp_guise_http_path"), translate("HTTP Path"))
o.placeholder = "/"
-add_xray_depends(o, { [option_name("tcp_guise")] = "http" })
-add_v2ray_depends(o, { [option_name("tcp_guise")] = "http" })
+o:depends({ [option_name("tcp_guise")] = "http" })
-- [[ mKCP部分 ]]--
o = s:option(ListValue, option_name("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)'))
for a, t in ipairs(header_type_list) do o:value(t) end
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_mtu"), translate("KCP MTU"))
o.default = "1350"
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_tti"), translate("KCP TTI"))
o.default = "20"
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_uplinkCapacity"), translate("KCP uplinkCapacity"))
o.default = "5"
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_downlinkCapacity"), translate("KCP downlinkCapacity"))
o.default = "20"
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Flag, option_name("mkcp_congestion"), translate("KCP Congestion"))
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_readBufferSize"), translate("KCP readBufferSize"))
o.default = "1"
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_writeBufferSize"), translate("KCP writeBufferSize"))
o.default = "1"
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_seed"), translate("KCP Seed"))
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
-- [[ WebSocket部分 ]]--
o = s:option(Value, option_name("ws_host"), translate("WebSocket Host"))
-add_xray_depends(o, { [option_name("transport")] = "ws" })
-add_xray_depends(o, { [option_name("ss_transport")] = "ws" })
-add_v2ray_depends(o, { [option_name("transport")] = "ws" })
-add_v2ray_depends(o, { [option_name("ss_transport")] = "ws" })
+o:depends({ [option_name("transport")] = "ws" })
+o:depends({ [option_name("ss_transport")] = "ws" })
o = s:option(Value, option_name("ws_path"), translate("WebSocket Path"))
o.placeholder = "/"
-add_xray_depends(o, { [option_name("transport")] = "ws" })
-add_xray_depends(o, { [option_name("ss_transport")] = "ws" })
-add_v2ray_depends(o, { [option_name("transport")] = "ws" })
-add_v2ray_depends(o, { [option_name("ss_transport")] = "ws" })
-
-o = s:option(Flag, "v2ray_ws_enableEarlyData", translate("Enable early data"))
-add_v2ray_depends(o, { [option_name("transport")] = "ws" })
-
-o = s:option(Value, "v2ray_ws_maxEarlyData", translate("Early data length"))
-o.default = "1024"
-add_v2ray_depends(o, { v2ray_ws_enableEarlyData = true })
-
-o = s:option(Value, "v2ray_ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
-add_v2ray_depends(o, { v2ray_ws_enableEarlyData = true })
+o:depends({ [option_name("transport")] = "ws" })
+o:depends({ [option_name("ss_transport")] = "ws" })
-- [[ HTTP/2部分 ]]--
o = s:option(Value, option_name("h2_host"), translate("HTTP/2 Host"))
-add_xray_depends(o, { [option_name("transport")] = "h2" })
-add_xray_depends(o, { [option_name("ss_transport")] = "h2" })
-add_v2ray_depends(o, { [option_name("transport")] = "h2" })
-add_v2ray_depends(o, { [option_name("ss_transport")] = "h2" })
+o:depends({ [option_name("transport")] = "h2" })
+o:depends({ [option_name("ss_transport")] = "h2" })
o = s:option(Value, option_name("h2_path"), translate("HTTP/2 Path"))
o.placeholder = "/"
-add_xray_depends(o, { [option_name("transport")] = "h2" })
-add_xray_depends(o, { [option_name("ss_transport")] = "h2" })
-add_v2ray_depends(o, { [option_name("transport")] = "h2" })
-add_v2ray_depends(o, { [option_name("ss_transport")] = "h2" })
+o:depends({ [option_name("transport")] = "h2" })
+o:depends({ [option_name("ss_transport")] = "h2" })
o = s:option(Flag, option_name("h2_health_check"), translate("Health check"))
-add_xray_depends(o, { [option_name("transport")] = "h2" })
+o:depends({ [option_name("transport")] = "h2" })
o = s:option(Value, option_name("h2_read_idle_timeout"), translate("Idle timeout"))
o.default = "10"
-add_xray_depends(o, { [option_name("h2_health_check")] = true })
+o:depends({ [option_name("h2_health_check")] = true })
o = s:option(Value, option_name("h2_health_check_timeout"), translate("Health check timeout"))
o.default = "15"
-add_xray_depends(o, { [option_name("h2_health_check")] = true })
+o:depends({ [option_name("h2_health_check")] = true })
-- [[ DomainSocket部分 ]]--
o = s:option(Value, option_name("ds_path"), "Path", translate("A legal file path. This file must not exist before running."))
-add_xray_depends(o, { [option_name("transport")] = "ds" })
-add_v2ray_depends(o, { [option_name("transport")] = "ds" })
+o:depends({ [option_name("transport")] = "ds" })
-- [[ QUIC部分 ]]--
o = s:option(ListValue, option_name("quic_security"), translate("Encrypt Method"))
o:value("none")
o:value("aes-128-gcm")
o:value("chacha20-poly1305")
-add_xray_depends(o, { [option_name("transport")] = "quic" })
-add_v2ray_depends(o, { [option_name("transport")] = "quic" })
+o:depends({ [option_name("transport")] = "quic" })
o = s:option(Value, option_name("quic_key"), translate("Encrypt Method") .. translate("Key"))
-add_xray_depends(o, { [option_name("transport")] = "quic" })
-add_v2ray_depends(o, { [option_name("transport")] = "quic" })
+o:depends({ [option_name("transport")] = "quic" })
o = s:option(ListValue, option_name("quic_guise"), translate("Camouflage Type"))
for a, t in ipairs(header_type_list) do o:value(t) end
-add_xray_depends(o, { [option_name("transport")] = "quic" })
-add_v2ray_depends(o, { [option_name("transport")] = "quic" })
+o:depends({ [option_name("transport")] = "quic" })
-- [[ gRPC部分 ]]--
o = s:option(Value, option_name("grpc_serviceName"), "ServiceName")
-add_xray_depends(o, { [option_name("transport")] = "grpc" })
-add_v2ray_depends(o, { [option_name("transport")] = "grpc" })
+o:depends({ [option_name("transport")] = "grpc" })
o = s:option(ListValue, option_name("grpc_mode"), "gRPC " .. translate("Transfer mode"))
o:value("gun")
o:value("multi")
-add_xray_depends(o, { [option_name("transport")] = "grpc" })
+o:depends({ [option_name("transport")] = "grpc" })
o = s:option(Flag, option_name("grpc_health_check"), translate("Health check"))
-add_xray_depends(o, { [option_name("transport")] = "grpc" })
+o:depends({ [option_name("transport")] = "grpc" })
o = s:option(Value, option_name("grpc_idle_timeout"), translate("Idle timeout"))
o.default = "10"
-add_xray_depends(o, { [option_name("grpc_health_check")] = true })
+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"
-add_xray_depends(o, { [option_name("grpc_health_check")] = true })
+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"
-add_xray_depends(o, { [option_name("grpc_health_check")] = true })
+o:depends({ [option_name("grpc_health_check")] = true })
o = s:option(Value, option_name("grpc_initial_windows_size"), translate("Initial Windows Size"))
o.default = "0"
-add_xray_depends(o, { [option_name("transport")] = "grpc" })
+o:depends({ [option_name("transport")] = "grpc" })
-- [[ Mux ]]--
o = s:option(Flag, option_name("mux"), translate("Mux"))
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "http" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tlsflow")] = "" })
-add_xray_depends(o, { [option_name("protocol")] = "http" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
+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(Value, option_name("mux_concurrency"), translate("Mux concurrency"))
o.default = 8
-add_xray_depends(o, { [option_name("mux")] = true })
-add_v2ray_depends(o, { [option_name("mux")] = true })
+o:depends({ [option_name("mux")] = true })
-- [[ XUDP Mux ]]--
o = s:option(Flag, option_name("xmux"), translate("xMux"))
o.default = 1
-add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tlsflow")] = "xtls-rprx-vision" })
-add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tlsflow")] = "xtls-rprx-vision-udp443" })
+o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "xtls-rprx-vision" })
+o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "xtls-rprx-vision-udp443" })
o = s:option(Value, option_name("xudp_concurrency"), translate("XUDP Mux concurrency"))
o.default = 8
-add_xray_depends(o, { [option_name("xmux")] = true })
+o:depends({ [option_name("xmux")] = true })
for key, value in pairs(s.fields) do
if key:find(option_prefix) == 1 then
@@ -715,5 +552,14 @@ for key, value in pairs(s.fields) do
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
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua
new file mode 100644
index 000000000..d460a639f
--- /dev/null
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua
@@ -0,0 +1,567 @@
+local m, s = ...
+
+local api = require "luci.passwall2.api"
+
+if not api.is_finded("sing-box") then
+ return
+end
+
+local singbox_tags = luci.sys.exec(api.finded("sing-box") .. " 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, translate("Sing-Box"))
+
+o = s:option(ListValue, option_name("protocol"), translate("Protocol"))
+o:value("socks", "Socks")
+o:value("http", "HTTP")
+o:value("shadowsocks", "Shadowsocks")
+if singbox_tags:find("with_shadowsocksr") then
+ o:value("shadowsocksr", "ShadowsocksR")
+end
+o:value("vmess", "Vmess")
+o:value("trojan", "Trojan")
+if singbox_tags:find("with_wireguard") then
+ o:value("wireguard", "WireGuard")
+end
+if singbox_tags:find("with_quic") then
+ o:value("hysteria", "Hysteria")
+end
+o:value("shadowtls", "ShadowTLS")
+o:value("vless", "VLESS")
+if singbox_tags:find("with_quic") then
+ o:value("tuic", "TUIC")
+end
+if singbox_tags:find("with_quic") then
+ o:value("hysteria2", "Hysteria2")
+end
+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('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default
) 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('* %s', 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('* %s', 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('%s', 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('* %s', 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('* %s', 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(ListValue, option_name("shadowtls_version"), translate("Version"))
+o.default = "1"
+o:value("1", "ShadowTLS v1")
+o:value("2", "ShadowTLS v2")
+o:value("3", "ShadowTLS v3")
+o:depends({ [option_name("protocol")] = "shadowtls" })
+
+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")] = "shadowsocksr" })
+o:depends({ [option_name("protocol")] = "trojan" })
+o:depends({ [option_name("protocol")] = "shadowtls", [option_name("shadowtls_version")] = "2" })
+o:depends({ [option_name("protocol")] = "shadowtls", [option_name("shadowtls_version")] = "3" })
+o:depends({ [option_name("protocol")] = "tuic" })
+
+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
+
+if singbox_tags:find("with_shadowsocksr") then
+ o = s:option(ListValue, option_name("ssr_method"), translate("Encrypt Method"))
+ o.not_rewrite = true
+ for a, t in ipairs(ss_method_old_list) do o:value(t) end
+ o:depends({ [option_name("protocol")] = "shadowsocksr" })
+ 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
+
+ local ssr_protocol_list = {
+ "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
+ "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
+ "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
+ "auth_chain_d", "auth_chain_e", "auth_chain_f"
+ }
+
+ o = s:option(ListValue, option_name("ssr_protocol"), translate("Protocol"))
+ for a, t in ipairs(ssr_protocol_list) do o:value(t) end
+ o:depends({ [option_name("protocol")] = "shadowsocksr" })
+
+ o = s:option(Value, option_name("ssr_protocol_param"), translate("Protocol_param"))
+ o:depends({ [option_name("protocol")] = "shadowsocksr" })
+
+ local ssr_obfs_list = {
+ "plain", "http_simple", "http_post", "random_head", "tls_simple",
+ "tls1.0_session_auth", "tls1.2_ticket_auth"
+ }
+
+ o = s:option(ListValue, option_name("ssr_obfs"), translate("Obfs"))
+ for a, t in ipairs(ssr_obfs_list) do o:value(t) end
+ o:depends({ [option_name("protocol")] = "shadowsocksr" })
+
+ o = s:option(Value, option_name("ssr_obfs_param"), translate("Obfs_param"))
+ o:depends({ [option_name("protocol")] = "shadowsocksr" })
+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:depends({ [option_name("protocol")] = "tuic" })
+
+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 })
+
+if singbox_tags:find("with_quic") then
+ o = s:option(Value, option_name("hysteria_obfs"), translate("Obfs Password"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(ListValue, option_name("hysteria_auth_type"), translate("Auth Type"))
+ o:value("disable", translate("Disable"))
+ o:value("string", translate("STRING"))
+ o:value("base64", translate("BASE64"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_auth_password"), translate("Auth Password"))
+ o.password = true
+ o:depends({ [option_name("protocol")] = "hysteria", [option_name("hysteria_auth_type")] = "string"})
+ o:depends({ [option_name("protocol")] = "hysteria", [option_name("hysteria_auth_type")] = "base64"})
+
+ o = s:option(Value, option_name("hysteria_up_mbps"), translate("Max upload Mbps"))
+ o.default = "10"
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_down_mbps"), translate("Max download Mbps"))
+ o.default = "50"
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_recv_window"), translate("QUIC connection receive window"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Flag, option_name("hysteria_disable_mtu_discovery"), translate("Disable MTU detection"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+end
+
+if singbox_tags:find("with_quic") then
+ o = s:option(ListValue, option_name("tuic_congestion_control"), translate("Congestion control algorithm"))
+ o.default = "cubic"
+ o:value("bbr", translate("BBR"))
+ o:value("cubic", translate("CUBIC"))
+ o:value("new_reno", translate("New Reno"))
+ o:depends({ [option_name("protocol")] = "tuic" })
+
+ o = s:option(ListValue, option_name("tuic_udp_relay_mode"), translate("UDP relay mode"))
+ o.default = "native"
+ o:value("native", translate("native"))
+ o:value("quic", translate("QUIC"))
+ o:depends({ [option_name("protocol")] = "tuic" })
+
+ --[[
+ o = s:option(Flag, option_name("tuic_udp_over_stream"), translate("UDP over stream"))
+ o:depends({ [option_name("protocol")] = "tuic" })
+ ]]--
+
+ o = s:option(Flag, option_name("tuic_zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake"))
+ o.default = 0
+ o:depends({ [option_name("protocol")] = "tuic" })
+
+ o = s:option(Value, option_name("tuic_heartbeat"), translate("Heartbeat interval(second)"))
+ o.datatype = "uinteger"
+ o.default = "3"
+ o:depends({ [option_name("protocol")] = "tuic" })
+end
+
+if singbox_tags:find("with_quic") then
+ o = s:option(Value, option_name("hysteria2_up_mbps"), translate("Max upload Mbps"))
+ o:depends({ [option_name("protocol")] = "hysteria2" })
+
+ o = s:option(Value, option_name("hysteria2_down_mbps"), translate("Max download Mbps"))
+ o:depends({ [option_name("protocol")] = "hysteria2" })
+
+ o = s:option(ListValue, option_name("hysteria2_obfs_type"), translate("Obfs Type"))
+ o:value("", translate("Disable"))
+ o:value("salamander")
+ o:depends({ [option_name("protocol")] = "hysteria2" })
+
+ o = s:option(Value, option_name("hysteria2_obfs_password"), translate("Obfs Password"))
+ o:depends({ [option_name("protocol")] = "hysteria2" })
+
+ o = s:option(Value, option_name("hysteria2_auth_password"), translate("Auth Password"))
+ o.password = true
+ o:depends({ [option_name("protocol")] = "hysteria2"})
+end
+
+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" })
+o:depends({ [option_name("protocol")] = "shadowtls" })
+
+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
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua
index fc6d2e5cc..46753e365 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua
@@ -45,15 +45,8 @@ e = t:option(DummyValue, "type", translate("Type"))
e.cfgvalue = function(t, n)
local v = Value.cfgvalue(t, n)
if v then
- if v == "V2ray" or v == "Xray" then
+ if v == "sing-box" or v == "Xray" then
local protocol = m:get(n, "protocol")
- if protocol == "vmess" then
- protocol = "VMess"
- elseif protocol == "vless" then
- protocol = "VLESS"
- else
- protocol = protocol:gsub("^%l",string.upper)
- end
return v .. " -> " .. protocol
end
return v
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
index 77daaf368..5b82782b9 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua
@@ -2,10 +2,12 @@ local m, s = ...
local api = require "luci.passwall2.api"
-if not api.is_finded("xray") and not api.is_finded("v2ray")then
+if not api.is_finded("xray") then
return
end
+local type_name = "Xray"
+
local option_prefix = "xray_"
local function option_name(name)
@@ -18,52 +20,20 @@ local function rm_prefix_cfgvalue(self, section)
end
end
local function rm_prefix_write(self, section, value)
- if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then
+ 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]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then
+ 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 function add_xray_depends(o, field, value)
- local deps = { type = "Xray" }
- if field then
- if type(field) == "string" then
- deps[field] = value
- else
- for key, value in pairs(field) do
- deps[key] = value
- end
- end
- end
- o:depends(deps)
-end
-
-local function add_v2ray_depends(o, field, value)
- local deps = { type = "V2ray" }
- if field then
- if type(field) == "string" then
- deps[field] = value
- else
- for key, value in pairs(field) do
- deps[key] = value
- end
- end
- end
- o:depends(deps)
-end
-
-local v_ss_method_list = {
- "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305"
-}
-
local x_ss_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
@@ -74,12 +44,7 @@ local header_type_list = {
-- [[ Xray ]]
-if api.is_finded("v2ray") then
- s.fields["type"]:value("V2ray", translate("V2ray"))
-end
-if api.is_finded("xray") then
- s.fields["type"]:value("Xray", translate("Xray"))
-end
+s.fields["type"]:value(type_name, "Xray")
o = s:option(ListValue, option_name("protocol"), translate("Protocol"))
o:value("vmess", "Vmess")
@@ -89,13 +54,9 @@ o:value("socks", "Socks")
o:value("shadowsocks", "Shadowsocks")
o:value("trojan", "Trojan")
o:value("dokodemo-door", "dokodemo-door")
-add_xray_depends(o)
-add_v2ray_depends(o)
o = s:option(Value, option_name("port"), translate("Listen Port"))
o.datatype = "port"
-add_xray_depends(o)
-add_v2ray_depends(o)
o = s:option(Flag, option_name("auth"), translate("Auth"))
o.validate = function(self, value, t)
@@ -108,60 +69,38 @@ o.validate = function(self, value, t)
end
return value
end
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "http" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "http" })
+o:depends({ [option_name("protocol")] = "socks" })
+o:depends({ [option_name("protocol")] = "http" })
o = s:option(Value, option_name("username"), translate("Username"))
-add_xray_depends(o, { [option_name("auth")] = true })
-add_v2ray_depends(o, { [option_name("auth")] = true })
+o:depends({ [option_name("auth")] = true })
o = s:option(Value, option_name("password"), translate("Password"))
o.password = true
-add_xray_depends(o, { [option_name("auth")] = true })
-add_v2ray_depends(o, { [option_name("auth")] = true })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
+o:depends({ [option_name("auth")] = true })
+o:depends({ [option_name("protocol")] = "shadowsocks" })
o = s:option(ListValue, option_name("d_protocol"), translate("Destination protocol"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("tcp,udp", "TCP,UDP")
-add_v2ray_depends(o, { [option_name("protocol")] = "dokodemo-door" })
-add_xray_depends(o, { [option_name("protocol")] = "dokodemo-door" })
+o:depends({ [option_name("protocol")] = "dokodemo-door" })
o = s:option(Value, option_name("d_address"), translate("Destination address"))
-add_v2ray_depends(o, { [option_name("protocol")] = "dokodemo-door" })
-add_xray_depends(o, { [option_name("protocol")] = "dokodemo-door" })
+o:depends({ [option_name("protocol")] = "dokodemo-door" })
o = s:option(Value, option_name("d_port"), translate("Destination port"))
o.datatype = "port"
-add_v2ray_depends(o, { [option_name("protocol")] = "dokodemo-door" })
-add_xray_depends(o, { [option_name("protocol")] = "dokodemo-door" })
+o:depends({ [option_name("protocol")] = "dokodemo-door" })
o = s:option(Value, option_name("decryption"), translate("Encrypt Method"))
o.default = "none"
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-
-o = s:option(ListValue, option_name("v_ss_method"), translate("Encrypt Method"))
-o.not_rewrite = true
-for a, t in ipairs(v_ss_method_list) do o:value(t) end
-add_v2ray_depends(o, { [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]) == "V2ray" then
- m:set(section, "method", value)
- end
-end
+o:depends({ [option_name("protocol")] = "vless" })
o = s:option(ListValue, option_name("x_ss_method"), translate("Encrypt Method"))
o.not_rewrite = true
for a, t in ipairs(x_ss_method_list) do o:value(t) end
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
+o:depends({ [option_name("protocol")] = "shadowsocks" })
function o.cfgvalue(self, section)
return m:get(section, "method")
end
@@ -172,33 +111,33 @@ function o.write(self, section, value)
end
o = s:option(Flag, option_name("iv_check"), translate("IV Check"))
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
+o:depends({ [option_name("protocol")] = "shadowsocks" })
o = s:option(ListValue, option_name("ss_network"), translate("Transport"))
o.default = "tcp,udp"
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("tcp,udp", "TCP,UDP")
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
+o:depends({ [option_name("protocol")] = "shadowsocks" })
o = s:option(Flag, option_name("udp_forward"), translate("UDP Forward"))
o.default = "1"
o.rmempty = false
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
+o:depends({ [option_name("protocol")] = "socks" })
o = s:option(DynamicList, option_name("uuid"), translate("ID") .. "/" .. translate("Password"))
for i = 1, 3 do
o:value(api.gen_uuid(1))
end
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
+o:depends({ [option_name("protocol")] = "vmess" })
+o:depends({ [option_name("protocol")] = "vless" })
+o:depends({ [option_name("protocol")] = "trojan" })
+
+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" })
o = s:option(Flag, option_name("tls"), translate("TLS"))
o.default = 0
@@ -214,44 +153,29 @@ o.validate = function(self, value, t)
return value
end
end
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
-
-o = s:option(Value, option_name("tlsflow"), translate("flow"))
-o.default = ""
-o:value("", translate("Disable"))
-o:value("xtls-rprx-vision")
-o:value("xtls-rprx-vision-udp443")
-add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tls")] = true })
+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" })
o = s:option(ListValue, option_name("alpn"), translate("alpn"))
o.default = "h2,http/1.1"
o:value("h2,http/1.1")
o:value("h2")
o:value("http/1.1")
-add_v2ray_depends(o, { [option_name("tls")] = true })
-add_xray_depends(o, { [option_name("tls")] = true })
+o:depends({ [option_name("tls")] = true })
-- o = s:option(Value, option_name("minversion"), translate("minversion"))
-- o.default = "1.3"
-- o:value("1.3")
---add_v2ray_depends(o, { [option_name("tls")] = true })
---add_xray_depends(o, { [option_name("tls")] = true })
+--o:depends({ [option_name("tls")] = true })
-- [[ TLS部分 ]] --
o = s:option(FileUpload, option_name("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
-add_v2ray_depends(o, { [option_name("tls")] = true })
-add_xray_depends(o, { [option_name("tls")] = true })
+o:depends({ [option_name("tls")] = true })
o.validate = function(self, value, t)
if value and value ~= "" then
if not nixio.fs.access(value) then
@@ -265,8 +189,7 @@ end
o = s:option(FileUpload, option_name("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
-add_v2ray_depends(o, { [option_name("tls")] = true })
-add_xray_depends(o, { [option_name("tls")] = true })
+o:depends({ [option_name("tls")] = true })
o.validate = function(self, value, t)
if value and value ~= "" then
if not nixio.fs.access(value) then
@@ -286,36 +209,27 @@ o:value("h2", "HTTP/2")
o:value("ds", "DomainSocket")
o:value("quic", "QUIC")
o:value("grpc", "gRPC")
-add_v2ray_depends(o, { [option_name("protocol")] = "vmess" })
-add_v2ray_depends(o, { [option_name("protocol")] = "vless" })
-add_v2ray_depends(o, { [option_name("protocol")] = "socks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan" })
-add_xray_depends(o, { [option_name("protocol")] = "vmess" })
-add_xray_depends(o, { [option_name("protocol")] = "vless" })
-add_xray_depends(o, { [option_name("protocol")] = "socks" })
-add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan" })
+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" })
-- [[ WebSocket部分 ]]--
o = s:option(Value, option_name("ws_host"), translate("WebSocket Host"))
-add_v2ray_depends(o, { [option_name("transport")] = "ws" })
-add_xray_depends(o, { [option_name("transport")] = "ws" })
+o:depends({ [option_name("transport")] = "ws" })
o = s:option(Value, option_name("ws_path"), translate("WebSocket Path"))
-add_v2ray_depends(o, { [option_name("transport")] = "ws" })
-add_xray_depends(o, { [option_name("transport")] = "ws" })
+o:depends({ [option_name("transport")] = "ws" })
-- [[ HTTP/2部分 ]]--
o = s:option(Value, option_name("h2_host"), translate("HTTP/2 Host"))
-add_v2ray_depends(o, { [option_name("transport")] = "h2" })
-add_xray_depends(o, { [option_name("transport")] = "h2" })
+o:depends({ [option_name("transport")] = "h2" })
o = s:option(Value, option_name("h2_path"), translate("HTTP/2 Path"))
-add_v2ray_depends(o, { [option_name("transport")] = "h2" })
-add_xray_depends(o, { [option_name("transport")] = "h2" })
+o:depends({ [option_name("transport")] = "h2" })
-- [[ TCP部分 ]]--
@@ -323,141 +237,111 @@ add_xray_depends(o, { [option_name("transport")] = "h2" })
o = s:option(ListValue, option_name("tcp_guise"), translate("Camouflage Type"))
o:value("none", "none")
o:value("http", "http")
-add_v2ray_depends(o, { [option_name("transport")] = "tcp" })
-add_xray_depends(o, { [option_name("transport")] = "tcp" })
+o:depends({ [option_name("transport")] = "tcp" })
-- HTTP域名
o = s:option(DynamicList, option_name("tcp_guise_http_host"), translate("HTTP Host"))
-add_v2ray_depends(o, { [option_name("tcp_guise")] = "http" })
-add_xray_depends(o, { [option_name("tcp_guise")] = "http" })
+o:depends({ [option_name("tcp_guise")] = "http" })
-- HTTP路径
o = s:option(DynamicList, option_name("tcp_guise_http_path"), translate("HTTP Path"))
-add_v2ray_depends(o, { [option_name("tcp_guise")] = "http" })
-add_xray_depends(o, { [option_name("tcp_guise")] = "http" })
+o:depends({ [option_name("tcp_guise")] = "http" })
-- [[ mKCP部分 ]]--
o = s:option(ListValue, option_name("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)'))
for a, t in ipairs(header_type_list) do o:value(t) end
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_mtu"), translate("KCP MTU"))
o.default = "1350"
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_tti"), translate("KCP TTI"))
o.default = "20"
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_uplinkCapacity"), translate("KCP uplinkCapacity"))
o.default = "5"
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_downlinkCapacity"), translate("KCP downlinkCapacity"))
o.default = "20"
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Flag, option_name("mkcp_congestion"), translate("KCP Congestion"))
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_readBufferSize"), translate("KCP readBufferSize"))
o.default = "1"
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_writeBufferSize"), translate("KCP writeBufferSize"))
o.default = "1"
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
o = s:option(Value, option_name("mkcp_seed"), translate("KCP Seed"))
-add_v2ray_depends(o, { [option_name("transport")] = "mkcp" })
-add_xray_depends(o, { [option_name("transport")] = "mkcp" })
+o:depends({ [option_name("transport")] = "mkcp" })
-- [[ DomainSocket部分 ]]--
o = s:option(Value, option_name("ds_path"), "Path", translate("A legal file path. This file must not exist before running."))
-add_v2ray_depends(o, { [option_name("transport")] = "ds" })
-add_xray_depends(o, { [option_name("transport")] = "ds" })
+o:depends({ [option_name("transport")] = "ds" })
-- [[ QUIC部分 ]]--
o = s:option(ListValue, option_name("quic_security"), translate("Encrypt Method"))
o:value("none")
o:value("aes-128-gcm")
o:value("chacha20-poly1305")
-add_v2ray_depends(o, { [option_name("transport")] = "quic" })
-add_xray_depends(o, { [option_name("transport")] = "quic" })
+o:depends({ [option_name("transport")] = "quic" })
o = s:option(Value, option_name("quic_key"), translate("Encrypt Method") .. translate("Key"))
-add_v2ray_depends(o, { [option_name("transport")] = "quic" })
-add_xray_depends(o, { [option_name("transport")] = "quic" })
+o:depends({ [option_name("transport")] = "quic" })
o = s:option(ListValue, option_name("quic_guise"), translate("Camouflage Type"))
for a, t in ipairs(header_type_list) do o:value(t) end
-add_v2ray_depends(o, { [option_name("transport")] = "quic" })
-add_xray_depends(o, { [option_name("transport")] = "quic" })
+o:depends({ [option_name("transport")] = "quic" })
-- [[ gRPC部分 ]]--
o = s:option(Value, option_name("grpc_serviceName"), "ServiceName")
-add_v2ray_depends(o, { [option_name("transport")] = "grpc" })
-add_xray_depends(o, { [option_name("transport")] = "grpc" })
+o:depends({ [option_name("transport")] = "grpc" })
o = s:option(Flag, option_name("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
-add_v2ray_depends(o, { [option_name("transport")] = "tcp" })
-add_v2ray_depends(o, { [option_name("transport")] = "ws" })
-add_xray_depends(o, { [option_name("transport")] = "tcp" })
-add_xray_depends(o, { [option_name("transport")] = "ws" })
+o:depends({ [option_name("transport")] = "tcp" })
+o:depends({ [option_name("transport")] = "ws" })
-- [[ Fallback部分 ]]--
o = s:option(Flag, option_name("fallback"), translate("Fallback"))
-add_v2ray_depends(o, { [option_name("protocol")] = "vless", [option_name("transport")] = "tcp" })
-add_v2ray_depends(o, { [option_name("protocol")] = "trojan", [option_name("transport")] = "tcp" })
-add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("transport")] = "tcp" })
-add_xray_depends(o, { [option_name("protocol")] = "trojan", [option_name("transport")] = "tcp" })
+o:depends({ [option_name("protocol")] = "vless", [option_name("transport")] = "tcp" })
+o:depends({ [option_name("protocol")] = "trojan", [option_name("transport")] = "tcp" })
--[[
o = s:option(Value, option_name("fallback_alpn"), "Fallback alpn")
-add_v2ray_depends(o, { [option_name("fallback")] = true })
-add_xray_depends(o, { [option_name("fallback")] = true })
+o:depends({ [option_name("fallback")] = true })
o = s:option(Value, option_name("fallback_path"), "Fallback path")
-add_v2ray_depends(o, { [option_name("fallback")] = true })
-add_xray_depends(o, { [option_name("fallback")] = true })
+o:depends({ [option_name("fallback")] = true })
o = s:option(Value, option_name("fallback_dest"), "Fallback dest")
-add_v2ray_depends(o, { [option_name("fallback")] = true })
-add_xray_depends(o, { [option_name("fallback")] = true })
+o:depends({ [option_name("fallback")] = true })
o = s:option(Value, option_name("fallback_xver"), "Fallback xver")
o.default = 0
-add_v2ray_depends(o, { [option_name("fallback")] = true })
-add_xray_depends(o, { [option_name("fallback")] = true })
+o:depends({ [option_name("fallback")] = true })
]]--
o = s:option(DynamicList, option_name("fallback_list"), "Fallback", translate("dest,path"))
-add_v2ray_depends(o, { [option_name("fallback")] = true })
-add_xray_depends(o, { [option_name("fallback")] = true })
+o:depends({ [option_name("fallback")] = true })
o = s:option(Flag, option_name("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed locally, It is recommended to turn on when using reverse proxies or be fallback."))
o.default = "0"
-add_v2ray_depends(o)
-add_xray_depends(o)
o = s:option(Flag, option_name("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
o.default = "0"
-add_v2ray_depends(o)
-add_xray_depends(o)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
- if e.node_type == "normal" and (e.type == "V2ray" or e.type == "Xray") then
+ if e.node_type == "normal" and e.type == type_name then
nodes_table[#nodes_table + 1] = {
id = e[".name"],
remarks = e["remark"]
@@ -472,45 +356,32 @@ o:value("_http", translate("Custom HTTP"))
o:value("_iface", translate("Custom Interface") .. " (Only Support Xray)")
for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
o.default = "nil"
-add_v2ray_depends(o)
-add_xray_depends(o)
o = s:option(Value, option_name("outbound_node_address"), translate("Address (Support Domain Name)"))
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_socks"})
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_http"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_socks"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_http"})
+o:depends({ [option_name("outbound_node")] = "_socks"})
+o:depends({ [option_name("outbound_node")] = "_http"})
o = s:option(Value, option_name("outbound_node_port"), translate("Port"))
o.datatype = "port"
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_socks"})
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_http"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_socks"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_http"})
+o:depends({ [option_name("outbound_node")] = "_socks"})
+o:depends({ [option_name("outbound_node")] = "_http"})
o = s:option(Value, option_name("outbound_node_username"), translate("Username"))
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_socks"})
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_http"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_socks"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_http"})
+o:depends({ [option_name("outbound_node")] = "_socks"})
+o:depends({ [option_name("outbound_node")] = "_http"})
o = s:option(Value, option_name("outbound_node_password"), translate("Password"))
o.password = true
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_socks"})
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_http"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_socks"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_http"})
+o:depends({ [option_name("outbound_node")] = "_socks"})
+o:depends({ [option_name("outbound_node")] = "_http"})
o = s:option(Value, option_name("outbound_node_iface"), translate("Interface"))
o.default = "eth1"
-add_v2ray_depends(o, { [option_name("outbound_node")] = "_iface"})
-add_xray_depends(o, { [option_name("outbound_node")] = "_iface"})
+o:depends({ [option_name("outbound_node")] = "_iface"})
o = s:option(Flag, option_name("log"), translate("Log"))
o.default = "1"
o.rmempty = false
-add_v2ray_depends(o)
-add_xray_depends(o)
o = s:option(ListValue, option_name("loglevel"), translate("Log Level"))
o.default = "warning"
@@ -518,8 +389,7 @@ o:value("debug")
o:value("info")
o:value("warning")
o:value("error")
-add_v2ray_depends(o, { [option_name("log")] = true })
-add_xray_depends(o, { [option_name("log")] = true })
+o:depends({ [option_name("log")] = true })
for key, value in pairs(s.fields) do
if key:find(option_prefix) == 1 then
@@ -529,4 +399,13 @@ for key, value in pairs(s.fields) do
s.fields[key].remove = rm_prefix_remove
end
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
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua
new file mode 100644
index 000000000..766ea780b
--- /dev/null
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua
@@ -0,0 +1,377 @@
+local m, s = ...
+
+local api = require "luci.passwall2.api"
+
+if not api.is_finded("sing-box")then
+ return
+end
+
+local singbox_tags = luci.sys.exec(api.finded("sing-box") .. " version | grep 'Tags:' | awk '{print $2}'")
+
+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_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"
+}
+
+-- [[ Sing-Box ]]
+
+s.fields["type"]:value(type_name, "Sing-Box")
+
+o = s:option(ListValue, option_name("protocol"), translate("Protocol"))
+o:value("mixed", "Mixed")
+o:value("socks", "Socks")
+o:value("http", "HTTP")
+o:value("shadowsocks", "Shadowsocks")
+o:value("vmess", "Vmess")
+o:value("vless", "VLESS")
+o:value("trojan", "Trojan")
+o:value("naive", "Naive")
+if singbox_tags:find("with_quic") then
+ o:value("hysteria", "Hysteria")
+end
+if singbox_tags:find("with_quic") then
+ o:value("tuic", "TUIC")
+end
+if singbox_tags:find("with_quic") then
+ o:value("hysteria2", "Hysteria2")
+end
+o:value("direct", "Direct")
+
+o = s:option(Value, option_name("port"), translate("Listen Port"))
+o.datatype = "port"
+
+o = s:option(Flag, option_name("auth"), translate("Auth"))
+o.validate = function(self, value, t)
+ if value and value == "1" then
+ local user_v = s.fields[option_name("username")]:formvalue(t) or ""
+ local pass_v = s.fields[option_name("password")]:formvalue(t) or ""
+ if user_v == "" or pass_v == "" then
+ return nil, translate("Username and Password must be used together!")
+ end
+ end
+ return value
+end
+o:depends({ [option_name("protocol")] = "mixed" })
+o:depends({ [option_name("protocol")] = "socks" })
+o:depends({ [option_name("protocol")] = "http" })
+
+o = s:option(Value, option_name("username"), translate("Username"))
+o:depends({ [option_name("auth")] = true })
+o:depends({ [option_name("protocol")] = "naive" })
+
+o = s:option(Value, option_name("password"), translate("Password"))
+o.password = true
+o:depends({ [option_name("auth")] = true })
+o:depends({ [option_name("protocol")] = "shadowsocks" })
+o:depends({ [option_name("protocol")] = "naive" })
+o:depends({ [option_name("protocol")] = "tuic" })
+
+if singbox_tags:find("with_quic") then
+ o = s:option(Value, option_name("hysteria_up_mbps"), translate("Max upload Mbps"))
+ o.default = "100"
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_down_mbps"), translate("Max download Mbps"))
+ o.default = "100"
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_obfs"), translate("Obfs Password"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(ListValue, option_name("hysteria_auth_type"), translate("Auth Type"))
+ o:value("disable", translate("Disable"))
+ o:value("string", translate("STRING"))
+ o:value("base64", translate("BASE64"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_auth_password"), translate("Auth Password"))
+ o.password = true
+ o:depends({ [option_name("protocol")] = "hysteria", [option_name("hysteria_auth_type")] = "string"})
+ o:depends({ [option_name("protocol")] = "hysteria", [option_name("hysteria_auth_type")] = "base64"})
+
+ o = s:option(Value, option_name("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_recv_window_client"), translate("QUIC connection receive window"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Value, option_name("hysteria_max_conn_client"), translate("QUIC concurrent bidirectional streams"))
+ o.default = "1024"
+ o:depends({ [option_name("protocol")] = "hysteria" })
+
+ o = s:option(Flag, option_name("hysteria_disable_mtu_discovery"), translate("Disable MTU detection"))
+ o:depends({ [option_name("protocol")] = "hysteria" })
+end
+
+if singbox_tags:find("with_quic") then
+ o = s:option(ListValue, option_name("tuic_congestion_control"), translate("Congestion control algorithm"))
+ o.default = "cubic"
+ o:value("bbr", translate("BBR"))
+ o:value("cubic", translate("CUBIC"))
+ o:value("new_reno", translate("New Reno"))
+ o:depends({ [option_name("protocol")] = "tuic" })
+
+ o = s:option(Flag, option_name("tuic_zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake"))
+ o.default = 0
+ o:depends({ [option_name("protocol")] = "tuic" })
+
+ o = s:option(Value, option_name("tuic_heartbeat"), translate("Heartbeat interval(second)"))
+ o.datatype = "uinteger"
+ o.default = "3"
+ o:depends({ [option_name("protocol")] = "tuic" })
+end
+
+if singbox_tags:find("with_quic") then
+ o = s:option(Flag, option_name("hysteria2_ignore_client_bandwidth"), translate("Commands the client to use the BBR flow control algorithm"))
+ o.default = 0
+ o:depends({ [option_name("protocol")] = "hysteria2" })
+
+ o = s:option(Value, option_name("hysteria2_up_mbps"), translate("Max upload Mbps"))
+ o:depends({ [option_name("protocol")] = "hysteria2", [option_name("hysteria2_ignore_client_bandwidth")] = false })
+
+ o = s:option(Value, option_name("hysteria2_down_mbps"), translate("Max download Mbps"))
+ o:depends({ [option_name("protocol")] = "hysteria2", [option_name("hysteria2_ignore_client_bandwidth")] = false })
+
+ o = s:option(ListValue, option_name("hysteria2_obfs_type"), translate("Obfs Type"))
+ o:value("", translate("Disable"))
+ o:value("salamander")
+ o:depends({ [option_name("protocol")] = "hysteria2" })
+
+ o = s:option(Value, option_name("hysteria2_obfs_password"), translate("Obfs Password"))
+ o:depends({ [option_name("protocol")] = "hysteria2" })
+
+ o = s:option(Value, option_name("hysteria2_auth_password"), translate("Auth Password"))
+ o.password = true
+ o:depends({ [option_name("protocol")] = "hysteria2"})
+end
+
+o = s:option(ListValue, option_name("d_protocol"), translate("Destination protocol"))
+o:value("tcp", "TCP")
+o:value("udp", "UDP")
+o:value("tcp,udp", "TCP,UDP")
+o:depends({ [option_name("protocol")] = "direct" })
+
+o = s:option(Value, option_name("d_address"), translate("Destination address"))
+o:depends({ [option_name("protocol")] = "direct" })
+
+o = s:option(Value, option_name("d_port"), translate("Destination port"))
+o.datatype = "port"
+o:depends({ [option_name("protocol")] = "direct" })
+
+o = s:option(Value, option_name("decryption"), translate("Encrypt Method"))
+o.default = "none"
+o:depends({ [option_name("protocol")] = "vless" })
+
+o = s:option(ListValue, option_name("ss_method"), translate("Encrypt Method"))
+o.not_rewrite = true
+for a, t in ipairs(ss_method_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(DynamicList, option_name("uuid"), translate("ID") .. "/" .. translate("Password"))
+for i = 1, 3 do
+ o:value(api.gen_uuid(1))
+end
+o:depends({ [option_name("protocol")] = "vmess" })
+o:depends({ [option_name("protocol")] = "vless" })
+o:depends({ [option_name("protocol")] = "trojan" })
+o:depends({ [option_name("protocol")] = "tuic" })
+
+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" })
+
+o = s:option(Flag, option_name("tls"), translate("TLS"))
+o.default = 0
+o.validate = function(self, value, t)
+ if value then
+ if value == "1" then
+ local ca = s.fields[option_name("tls_certificateFile")]:formvalue(t) or ""
+ local key = s.fields[option_name("tls_keyFile")]:formvalue(t) or ""
+ if ca == "" or key == "" then
+ return nil, translate("Public key and Private key path can not be empty!")
+ end
+ end
+ return value
+ end
+end
+o:depends({ [option_name("protocol")] = "http" })
+o:depends({ [option_name("protocol")] = "shadowsocks" })
+o:depends({ [option_name("protocol")] = "vmess" })
+o:depends({ [option_name("protocol")] = "vless" })
+o:depends({ [option_name("protocol")] = "trojan" })
+
+-- [[ TLS部分 ]] --
+
+o = s:option(FileUpload, option_name("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
+o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
+o:depends({ [option_name("tls")] = true })
+o.validate = function(self, value, t)
+ if value and value ~= "" then
+ if not nixio.fs.access(value) then
+ return nil, translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+
+o = s:option(FileUpload, option_name("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
+o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
+o:depends({ [option_name("tls")] = true })
+o.validate = function(self, value, t)
+ if value and value ~= "" then
+ if not nixio.fs.access(value) then
+ return nil, translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+
+o = s:option(ListValue, option_name("transport"), translate("Transport"))
+o:value("tcp", "TCP")
+o:value("http", "HTTP")
+o:value("ws", "WebSocket")
+o:value("quic", "QUIC")
+o:value("grpc", "gRPC")
+o:depends({ [option_name("protocol")] = "shadowsocks" })
+o:depends({ [option_name("protocol")] = "vmess" })
+o:depends({ [option_name("protocol")] = "vless" })
+o:depends({ [option_name("protocol")] = "trojan" })
+
+-- [[ 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:depends({ [option_name("transport")] = "http" })
+
+-- [[ 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:depends({ [option_name("transport")] = "ws" })
+
+-- [[ gRPC部分 ]]--
+o = s:option(Value, option_name("grpc_serviceName"), "ServiceName")
+o:depends({ [option_name("transport")] = "grpc" })
+
+o = s:option(Flag, option_name("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed locally, It is recommended to turn on when using reverse proxies or be fallback."))
+o.default = "0"
+
+o = s:option(Flag, option_name("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
+o.default = "0"
+
+local nodes_table = {}
+for k, e in ipairs(api.get_valid_nodes()) do
+ if e.node_type == "normal" and e.type == type_name then
+ nodes_table[#nodes_table + 1] = {
+ id = e[".name"],
+ remarks = e["remark"]
+ }
+ end
+end
+
+o = s:option(ListValue, option_name("outbound_node"), translate("outbound node"))
+o:value("nil", translate("Close"))
+o:value("_socks", translate("Custom Socks"))
+o:value("_http", translate("Custom HTTP"))
+o:value("_iface", translate("Custom Interface"))
+for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
+o.default = "nil"
+
+o = s:option(Value, option_name("outbound_node_address"), translate("Address (Support Domain Name)"))
+o:depends({ [option_name("outbound_node")] = "_socks" })
+o:depends({ [option_name("outbound_node")] = "_http" })
+
+o = s:option(Value, option_name("outbound_node_port"), translate("Port"))
+o.datatype = "port"
+o:depends({ [option_name("outbound_node")] = "_socks" })
+o:depends({ [option_name("outbound_node")] = "_http" })
+
+o = s:option(Value, option_name("outbound_node_username"), translate("Username"))
+o:depends({ [option_name("outbound_node")] = "_socks" })
+o:depends({ [option_name("outbound_node")] = "_http" })
+
+o = s:option(Value, option_name("outbound_node_password"), translate("Password"))
+o.password = true
+o:depends({ [option_name("outbound_node")] = "_socks" })
+o:depends({ [option_name("outbound_node")] = "_http" })
+
+o = s:option(Value, option_name("outbound_node_iface"), translate("Interface"))
+o.default = "eth1"
+o:depends({ [option_name("outbound_node")] = "_iface" })
+
+o = s:option(Flag, option_name("log"), translate("Log"))
+o.default = "1"
+o.rmempty = false
+
+o = s:option(ListValue, option_name("loglevel"), translate("Log Level"))
+o.default = "info"
+o:value("debug")
+o:value("info")
+o:value("warn")
+o:value("error")
+o:depends({ [option_name("log")] = 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
diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua
index cba51e2a5..113e9d4be 100644
--- a/luci-app-passwall2/luasrc/passwall2/api.lua
+++ b/luci-app-passwall2/luasrc/passwall2/api.lua
@@ -282,7 +282,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 == "Xray") and e.protocol then
local protocol = e.protocol
if protocol == "vmess" then
protocol = "VMess"
@@ -314,7 +314,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 == "Xray") and n.protocol then
local protocol = n.protocol
if protocol == "vmess" then
protocol = "VMess"
@@ -381,8 +381,12 @@ function get_customed_path(e)
return uci_get_type("global_app", e .. "_file")
end
+function finded(e)
+ return luci.sys.exec('echo -n $(type -t -p "/bin/%s" -p "/usr/bin/%s" -p "%s" "%s" | head -n1)' % {e, e, get_customed_path(e), e})
+end
+
function is_finded(e)
- return luci.sys.exec('type -t -p "/bin/%s" -p "/usr/bin/%s" -p "%s" "%s"' % {e, e, get_customed_path(e), e}) ~= "" and true or false
+ return finded(e) ~= "" and true or false
end
function clone(org)
diff --git a/luci-app-passwall2/luasrc/passwall2/com.lua b/luci-app-passwall2/luasrc/passwall2/com.lua
index dfdf8321f..8da871622 100644
--- a/luci-app-passwall2/luasrc/passwall2/com.lua
+++ b/luci-app-passwall2/luasrc/passwall2/com.lua
@@ -33,13 +33,26 @@ _M.hysteria = {
}
}
-_M.v2ray = {
- name = "V2ray",
- repo = "v2fly/v2ray-core",
+_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.xray = {
+ name = "Xray",
+ repo = "XTLS/Xray-core",
get_url = gh_pre_release_url,
cmd_version = "version | awk '{print $2}' | sed -n 1P",
zipped = true,
- default_path = "/usr/bin/v2ray",
+ default_path = "/usr/bin/xray",
match_fmt_str = "linux%%-%s",
file_tree = {
x86_64 = "64",
@@ -49,15 +62,4 @@ _M.v2ray = {
}
}
-_M.xray = {
- name = "Xray",
- repo = "XTLS/Xray-core",
- get_url = gh_pre_release_url,
- cmd_version = _M.v2ray.cmd_version,
- zipped = true,
- default_path = "/usr/bin/xray",
- match_fmt_str = _M.v2ray.match_fmt_str,
- file_tree = _M.v2ray.file_tree
-}
-
return _M
diff --git a/luci-app-passwall2/luasrc/passwall2/server_app.lua b/luci-app-passwall2/luasrc/passwall2/server_app.lua
index aa3dca543..476cb1bad 100644
--- a/luci-app-passwall2/luasrc/passwall2/server_app.lua
+++ b/luci-app-passwall2/luasrc/passwall2/server_app.lua
@@ -144,12 +144,12 @@ local function start()
elseif type == "SS-Rust" then
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
- elseif type == "V2ray" then
- config = require(require_dir .. "util_xray").gen_config_server(user)
- bin = ln_run(api.get_app_path("v2ray"), "v2ray", "run -c " .. config_file, log_path)
elseif type == "Xray" then
config = require(require_dir .. "util_xray").gen_config_server(user)
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
+ elseif type == "sing-box" then
+ config = require(require_dir .. "util_sing-box").gen_config_server(user)
+ bin = ln_run(api.get_app_path("singbox"), "sing-box", "run -c " .. config_file, log_path)
elseif type == "Brook" then
local brook_protocol = user.protocol
local brook_password = user.password
diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua
new file mode 100644
index 000000000..55c04d2e2
--- /dev/null
+++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua
@@ -0,0 +1,1533 @@
+module("luci.passwall2.util_sing-box", package.seeall)
+local api = require "luci.passwall2.api"
+local uci = api.uci
+local sys = api.sys
+local jsonc = api.jsonc
+local appname = api.appname
+local fs = api.fs
+
+local new_port
+
+local function get_new_port()
+ if new_port then
+ new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port %s tcp)", appname, new_port + 1)))
+ else
+ new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port auto tcp)", appname)))
+ end
+ return new_port
+end
+
+function gen_outbound(flag, node, tag, proxy_table)
+ local result = nil
+ if node and node ~= "nil" then
+ local node_id = node[".name"]
+ if tag == nil then
+ tag = node_id
+ end
+
+ local proxy = 0
+ local proxy_tag = "nil"
+ if proxy_table ~= nil and type(proxy_table) == "table" then
+ proxy = proxy_table.proxy or 0
+ proxy_tag = proxy_table.tag or "nil"
+ end
+
+ if node.type == "sing-box" then
+ proxy = 0
+ if proxy_tag ~= "nil" then
+ node.detour = proxy_tag
+ end
+ end
+
+ if node.type ~= "sing-box" then
+ local relay_port = node.port
+ new_port = get_new_port()
+ local config_file = string.format("%s_%s_%s.json", flag, tag, new_port)
+ if tag and node_id and tag ~= node_id then
+ config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port)
+ end
+ sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
+ appname,
+ string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s",
+ new_port, --flag
+ node_id, --node
+ "127.0.0.1", --bind
+ new_port, --socks port
+ config_file, --config file
+ (proxy == 1 and relay_port) and tostring(relay_port) or "" --relay port
+ )
+ )
+ )
+ node = {
+ protocol = "socks",
+ address = "127.0.0.1",
+ port = new_port
+ }
+ end
+
+ result = {
+ _flag_tag = node_id,
+ _flag_proxy = proxy,
+ _flag_proxy_tag = proxy_tag,
+ tag = tag,
+ type = node.protocol,
+ server = node.address,
+ server_port = tonumber(node.port),
+ detour = node.detour,
+ }
+
+ local tls = nil
+ if node.tls == "1" then
+ local alpn = nil
+ if node.alpn and node.alpn ~= "default" then
+ alpn = {}
+ string.gsub(node.alpn, '[^' .. "," .. ']+', function(w)
+ table.insert(alpn, w)
+ end)
+ end
+ tls = {
+ enabled = true,
+ disable_sni = false, --不要在 ClientHello 中发送服务器名称.
+ server_name = node.tls_serverName, --用于验证返回证书上的主机名,除非设置不安全。它还包含在 ClientHello 中以支持虚拟主机,除非它是 IP 地址。
+ insecure = (node.tls_allowInsecure == "1") and true or false, --接受任何服务器证书。
+ alpn = alpn, --支持的应用层协议协商列表,按优先顺序排列。如果两个对等点都支持 ALPN,则选择的协议将是此列表中的一个,如果没有相互支持的协议则连接将失败。
+ --min_version = "1.2",
+ --max_version = "1.3",
+ utls = {
+ enabled = (node.utls == "1" or node.reality == "1") and true or false,
+ fingerprint = node.fingerprint or "chrome"
+ },
+ reality = {
+ enabled = (node.reality == "1") and true or false,
+ public_key = node.reality_publicKey,
+ short_id = node.reality_shortId
+ }
+ }
+ end
+
+ local mux = nil
+ if node.mux == "1" then
+ mux = {
+ enabled = true,
+ protocol = node.mux_type or "h2mux",
+ max_connections = tonumber(node.mux_concurrency) or 4,
+ --min_streams = 4,
+ --max_streams = 0,
+ }
+ end
+
+ local v2ray_transport = nil
+
+ if node.transport == "http" then
+ v2ray_transport = {
+ type = "http",
+ host = { node.http_host },
+ path = node.http_path or "/",
+ idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
+ ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
+ }
+ --不强制执行 TLS。如果未配置 TLS,将使用纯 HTTP 1.1。
+ end
+
+ if node.transport == "ws" then
+ v2ray_transport = {
+ type = "ws",
+ path = node.ws_path or "/",
+ headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
+ max_early_data = tonumber(node.ws_maxEarlyData) or nil,
+ early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。
+ }
+ end
+
+ if node.transport == "quic" then
+ v2ray_transport = {
+ type = "quic"
+ }
+ --没有额外的加密支持: 它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。
+ end
+
+ if node.transport == "grpc" then
+ v2ray_transport = {
+ type = "grpc",
+ serviceName = node.grpc_serviceName,
+ idle_timeout = tonumber(node.grpc_idle_timeout) or nil,
+ ping_timeout = tonumber(node.grpc_health_check_timeout) or nil,
+ permit_without_stream = (node.grpc_permit_without_stream == "1") and true or nil,
+ }
+ end
+
+ local protocol_table = nil
+
+ if node.protocol == "socks" then
+ protocol_table = {
+ version = "5",
+ username = (node.username and node.password) and node.username or nil,
+ password = (node.username and node.password) and node.password or nil,
+ udp_over_tcp = false,
+ }
+ end
+
+ if node.protocol == "http" then
+ protocol_table = {
+ username = (node.username and node.password) and node.username or nil,
+ password = (node.username and node.password) and node.password or nil,
+ path = nil,
+ headers = nil,
+ tls = tls
+ }
+ end
+
+ if node.protocol == "shadowsocks" then
+ protocol_table = {
+ method = node.method or nil,
+ password = node.password or "",
+ plugin = node.plugin and nil,
+ plugin_opts = node.plugin_opts and nil,
+ udp_over_tcp = node.uot == "1" and {
+ enabled = true,
+ version = 2
+ } or nil,
+ multiplex = mux,
+ }
+ end
+
+ if node.protocol == "shadowsocksr" then
+ protocol_table = {
+ method = node.method or nil,
+ password = node.password or "",
+ obfs = node.ssr_obfs,
+ obfs_param = node.ssr_obfs_param,
+ protocol = node.ssr_protocol,
+ protocol_param = node.ssr_protocol_param,
+ }
+ end
+
+ if node.protocol == "trojan" then
+ protocol_table = {
+ password = node.password,
+ tls = tls,
+ multiplex = mux,
+ transport = v2ray_transport
+ }
+ end
+
+ if node.protocol == "vmess" then
+ protocol_table = {
+ uuid = node.uuid,
+ security = node.security,
+ alter_id = 0,
+ global_padding = false,
+ authenticated_length = true,
+ tls = tls,
+ packet_encoding = "", --UDP 包编码。(空):禁用 packetaddr:由 v2ray 5+ 支持 xudp:由 xray 支持
+ multiplex = mux,
+ transport = v2ray_transport,
+ }
+ end
+
+ if node.protocol == "vless" then
+ protocol_table = {
+ uuid = node.uuid,
+ flow = (node.tls == '1' and node.flow) and node.flow or nil,
+ tls = tls,
+ packet_encoding = "xudp", --UDP 包编码。(空):禁用 packetaddr:由 v2ray 5+ 支持 xudp:由 xray 支持
+ transport = v2ray_transport,
+ }
+ end
+
+ if node.protocol == "wireguard" then
+ if node.wireguard_reserved then
+ local bytes = {}
+ if not node.wireguard_reserved:match("[^%d,]+") then
+ node.wireguard_reserved:gsub("%d+", function(b)
+ bytes[#bytes + 1] = tonumber(b)
+ end)
+ else
+ local result = api.bin.b64decode(node.wireguard_reserved)
+ for i = 1, #result do
+ bytes[i] = result:byte(i)
+ end
+ end
+ node.wireguard_reserved = #bytes > 0 and bytes or nil
+ end
+ protocol_table = {
+ system_interface = nil,
+ interface_name = nil,
+ local_address = node.wireguard_local_address,
+ private_key = node.wireguard_secret_key,
+ peer_public_key = node.wireguard_public_key,
+ pre_shared_key = node.wireguard_preSharedKey,
+ reserved = node.wireguard_reserved,
+ mtu = tonumber(node.wireguard_mtu),
+ }
+ end
+
+ if node.protocol == "hysteria" then
+ protocol_table = {
+ up = node.hysteria_up_mbps .. " Mbps",
+ down = node.hysteria_down_mbps .. " Mbps",
+ up_mbps = tonumber(node.hysteria_up_mbps),
+ down_mbps = tonumber(node.hysteria_down_mbps),
+ obfs = node.hysteria_obfs,
+ auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
+ auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
+ recv_window_conn = tonumber(node.hysteria_recv_window_conn),
+ recv_window = tonumber(node.hysteria_recv_window),
+ disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
+ tls = tls,
+ }
+ end
+
+ if node.protocol == "shadowtls" then
+ protocol_table = {
+ version = tonumber(node.shadowtls_version),
+ password = (node.shadowtls_version == "2" or node.shadowtls_version == "3") and node.password or nil,
+ tls = tls,
+ }
+ end
+
+ if node.protocol == "tuic" then
+ protocol_table = {
+ uuid = node.uuid,
+ password = node.password,
+ congestion_control = node.tuic_congestion_control or "cubic",
+ udp_relay_mode = node.tuic_udp_relay_mode or "native",
+ udp_over_stream = false,
+ zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
+ heartbeat = tonumber(node.tuic_heartbeat),
+ tls = tls,
+ }
+ end
+
+ if node.protocol == "hysteria2" then
+ protocol_table = {
+ up_mbps = (node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) or nil,
+ down_mbps = (node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) or nil,
+ obfs = {
+ type = node.hysteria2_obfs_type,
+ password = node.hysteria2_obfs_password
+ },
+ password = node.hysteria2_auth_password or nil,
+ tls = tls,
+ }
+ end
+
+ if protocol_table then
+ for key, value in pairs(protocol_table) do
+ result[key] = value
+ end
+ end
+ end
+ return result
+end
+
+function gen_config_server(node)
+ local outbounds = {
+ { type = "direct", tag = "direct" },
+ { type = "block", tag = "block" }
+ }
+
+ local tls = nil
+
+ if node.tls == "1" then
+ tls = {
+ enabled = true,
+ certificate_path = node.tls_certificateFile,
+ key_path = node.tls_keyFile,
+ }
+ end
+
+ local v2ray_transport = nil
+
+ if node.transport == "http" then
+ v2ray_transport = {
+ type = "http",
+ host = node.http_host,
+ path = node.http_path or "/",
+ }
+ end
+
+ if node.transport == "ws" then
+ v2ray_transport = {
+ type = "ws",
+ path = node.ws_path or "/",
+ headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
+ early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。
+ }
+ end
+
+ if node.transport == "quic" then
+ v2ray_transport = {
+ type = "quic"
+ }
+ --没有额外的加密支持: 它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。
+ end
+
+ if node.transport == "grpc" then
+ v2ray_transport = {
+ type = "grpc",
+ serviceName = node.grpc_serviceName,
+ }
+ end
+
+ local inbound = {
+ type = node.protocol,
+ tag = "inbound",
+ listen = (node.bind_local == "1") and "127.0.0.1" or "::",
+ listen_port = tonumber(node.port),
+ }
+
+ local protocol_table = nil
+
+ if node.protocol == "mixed" then
+ protocol_table = {
+ users = (node.auth == "1") and {
+ {
+ username = node.username,
+ password = node.password
+ }
+ } or nil,
+ set_system_proxy = false
+ }
+ end
+
+ if node.protocol == "socks" then
+ protocol_table = {
+ users = (node.auth == "1") and {
+ {
+ username = node.username,
+ password = node.password
+ }
+ } or nil
+ }
+ end
+
+ if node.protocol == "http" then
+ protocol_table = {
+ users = (node.auth == "1") and {
+ {
+ username = node.username,
+ password = node.password
+ }
+ } or nil,
+ tls = tls,
+ }
+ end
+
+ if node.protocol == "shadowsocks" then
+ protocol_table = {
+ method = node.method,
+ password = node.password,
+ }
+ end
+
+ if node.protocol == "vmess" then
+ if node.uuid then
+ local users = {}
+ for i = 1, #node.uuid do
+ users[i] = {
+ name = node.uuid[i],
+ uuid = node.uuid[i],
+ alterId = 0,
+ }
+ end
+ protocol_table = {
+ users = users,
+ tls = tls,
+ transport = v2ray_transport,
+ }
+ end
+ end
+
+ if node.protocol == "vless" then
+ if node.uuid then
+ local users = {}
+ for i = 1, #node.uuid do
+ users[i] = {
+ name = node.uuid[i],
+ uuid = node.uuid[i],
+ flow = node.flow,
+ }
+ end
+ protocol_table = {
+ users = users,
+ tls = tls,
+ transport = v2ray_transport,
+ }
+ end
+ end
+
+ if node.protocol == "trojan" then
+ if node.uuid then
+ local users = {}
+ for i = 1, #node.uuid do
+ users[i] = {
+ name = node.uuid[i],
+ uuid = node.uuid[i],
+ }
+ end
+ protocol_table = {
+ users = users,
+ tls = tls,
+ fallback = nil,
+ fallback_for_alpn = nil,
+ transport = v2ray_transport,
+ }
+ end
+ end
+
+ if node.protocol == "naive" then
+ protocol_table = {
+ users = {
+ {
+ username = node.username,
+ password = node.password
+ }
+ },
+ tls = tls,
+ }
+ end
+
+ if node.protocol == "hysteria" then
+ protocol_table = {
+ up = node.hysteria_up_mbps .. " Mbps",
+ down = node.hysteria_down_mbps .. " Mbps",
+ up_mbps = tonumber(node.hysteria_up_mbps),
+ down_mbps = tonumber(node.hysteria_down_mbps),
+ obfs = node.hysteria_obfs,
+ users = {
+ {
+ name = "user1",
+ auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
+ auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
+ }
+ },
+ recv_window_conn = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
+ recv_window_client = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
+ max_conn_client = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
+ disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
+ tls = tls,
+ }
+ end
+
+ if node.protocol == "tuic" then
+ protocol_table = {
+ users = {
+ {
+ name = "user1",
+ uuid = node.uuid,
+ password = node.password
+ }
+ },
+ congestion_control = node.tuic_congestion_control or "cubic",
+ zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
+ heartbeat = node.tuic_heartbeat .. "s",
+ tls = tls,
+ }
+ end
+
+ if node.protocol == "hysteria2" then
+ protocol_table = {
+ up_mbps = (node.hysteria2_ignore_client_bandwidth ~= "1" and node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) or nil,
+ down_mbps = (node.hysteria2_ignore_client_bandwidth ~= "1" and node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) or nil,
+ obfs = {
+ type = node.hysteria2_obfs_type,
+ password = node.hysteria2_obfs_password
+ },
+ users = {
+ {
+ name = "user1",
+ password = node.hysteria2_auth_password or nil,
+ }
+ },
+ ignore_client_bandwidth = (node.hysteria2_ignore_client_bandwidth == "1") and true or false,
+ tls = tls,
+ }
+ end
+
+ if node.protocol == "direct" then
+ protocol_table = {
+ network = (node.d_protocol ~= "TCP,UDP") and node.d_protocol or nil,
+ override_address = node.d_address,
+ override_port = tonumber(node.d_port)
+ }
+ end
+
+ if protocol_table then
+ for key, value in pairs(protocol_table) do
+ inbound[key] = value
+ end
+ end
+
+ local route = {
+ rules = {
+ {
+ ip_cidr = { "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" },
+ outbound = (node.accept_lan == nil or node.accept_lan == "0") and "block" or "direct"
+ }
+ }
+ }
+
+ if node.outbound_node and node.outbound_node ~= "nil" then
+ local outbound = nil
+ if node.outbound_node == "_iface" and node.outbound_node_iface then
+ outbound = {
+ type = "direct",
+ tag = "outbound",
+ bind_interface = node.outbound_node_iface,
+ routing_mark = 255,
+ }
+ sys.call("mkdir -p /tmp/etc/passwall2/iface && touch /tmp/etc/passwall2/iface/" .. node.outbound_node_iface)
+ else
+ local outbound_node_t = uci:get_all("passwall2", node.outbound_node)
+ if node.outbound_node == "_socks" or node.outbound_node == "_http" then
+ outbound_node_t = {
+ type = node.type,
+ protocol = node.outbound_node:gsub("_", ""),
+ address = node.outbound_node_address,
+ port = tonumber(node.outbound_node_port),
+ username = (node.outbound_node_username and node.outbound_node_username ~= "") and node.outbound_node_username or nil,
+ password = (node.outbound_node_password and node.outbound_node_password ~= "") and node.outbound_node_password or nil,
+ }
+ end
+ outbound = require("luci.passwall2.util_sing-box").gen_outbound(nil, outbound_node_t, "outbound")
+ end
+ if outbound then
+ route.final = "outbound"
+ table.insert(outbounds, 1, outbound)
+ end
+ end
+
+ local config = {
+ log = {
+ disabled = (not node or node.log == "0") and true or false,
+ level = node.loglevel or "info",
+ timestamp = true,
+ --output = logfile,
+ },
+ inbounds = { inbound },
+ outbounds = outbounds,
+ route = route
+ }
+
+ for index, value in ipairs(config.outbounds) do
+ for k, v in pairs(config.outbounds[index]) do
+ if k:find("_") == 1 then
+ config.outbounds[index][k] = nil
+ end
+ end
+ end
+
+ return config
+end
+
+function gen_config(var)
+ local flag = var["-flag"]
+ local log = var["-log"] or "0"
+ local loglevel = var["-loglevel"] or "warn"
+ local logfile = var["-logfile"] or "/dev/null"
+ local node_id = var["-node"]
+ local tcp_proxy_way = var["-tcp_proxy_way"]
+ local redir_port = var["-redir_port"]
+ local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
+ local local_socks_port = var["-local_socks_port"]
+ local local_socks_username = var["-local_socks_username"]
+ local local_socks_password = var["-local_socks_password"]
+ local local_http_address = var["-local_http_address"] or "0.0.0.0"
+ local local_http_port = var["-local_http_port"]
+ local local_http_username = var["-local_http_username"]
+ local local_http_password = var["-local_http_password"]
+ local dns_listen_port = var["-dns_listen_port"]
+ local direct_dns_port = var["-direct_dns_port"]
+ local direct_dns_udp_server = var["-direct_dns_udp_server"]
+ local direct_dns_query_strategy = var["-direct_dns_query_strategy"]
+ local remote_dns_port = var["-remote_dns_port"]
+ local remote_dns_udp_server = var["-remote_dns_udp_server"]
+ local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
+ local remote_dns_doh_url = var["-remote_dns_doh_url"]
+ local remote_dns_doh_host = var["-remote_dns_doh_host"]
+ local remote_dns_query_strategy = var["-remote_dns_query_strategy"]
+ local remote_dns_fake = var["-remote_dns_fake"]
+ local dns_cache = var["-dns_cache"]
+ local tags = var["-tags"]
+
+ local dns_direct_domains = {}
+ local dns_remote_domains = {}
+ local dns = nil
+ local inbounds = {}
+ local outbounds = {}
+
+ local singbox_settings = uci:get_all(appname, "@global_singbox[0]") or {}
+
+ local route = {
+ rules = {},
+ geoip = {
+ path = singbox_settings.geoip_path or "/tmp/singbox/geoip.db",
+ download_url = singbox_settings.geoip_url or nil,
+ download_detour = nil,
+ },
+ geosite = {
+ path = singbox_settings.geosite_path or "/tmp/singbox/geosite.db",
+ download_url = singbox_settings.geosite_url or nil,
+ download_detour = nil,
+ },
+ }
+
+ local experimental = nil
+
+ local nodes = {}
+ if node_id then
+ local node = uci:get_all(appname, node_id)
+ if node then
+ nodes[node_id] = node
+ end
+ end
+
+ if local_socks_port then
+ local inbound = {
+ type = "socks",
+ tag = "socks-in",
+ listen = local_socks_address,
+ listen_port = tonumber(local_socks_port),
+ sniff = true
+ }
+ if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then
+ inbound.users = {
+ {
+ username = local_socks_username,
+ password = local_socks_password
+ }
+ }
+ end
+ table.insert(inbounds, inbound)
+ end
+
+ if local_http_port then
+ local inbound = {
+ type = "http",
+ tag = "http-in",
+ listen = local_http_address,
+ listen_port = tonumber(local_http_port)
+ }
+ if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then
+ inbound.users = {
+ {
+ username = local_http_username,
+ password = local_http_password
+ }
+ }
+ end
+ table.insert(inbounds, inbound)
+ end
+
+ if redir_port then
+ local inbound_tproxy = {
+ type = "tproxy",
+ tag = "tproxy",
+ listen = "::",
+ listen_port = tonumber(redir_port),
+ sniff = true,
+ sniff_override_destination = (singbox_settings.sniff_override_destination == "1") and true or false
+ }
+ if tcp_proxy_way ~= "tproxy" then
+ local inbound = {
+ type = "redirect",
+ tag = "redirect_tcp",
+ listen = "::",
+ listen_port = tonumber(redir_port),
+ sniff = true,
+ sniff_override_destination = (singbox_settings.sniff_override_destination == "1") and true or false,
+ }
+ table.insert(inbounds, inbound)
+
+ inbound_tproxy.tag = "tproxy_udp"
+ inbound_tproxy.network = "udp"
+ end
+
+ table.insert(inbounds, inbound_tproxy)
+ end
+
+ local dns_outTag = nil
+
+ for k, v in pairs(nodes) do
+ local node = v
+ if node.protocol == "_shunt" then
+ local rules = {}
+
+ local preproxy_enabled = node.preproxy_enabled == "1"
+ local preproxy_tag = "main"
+ local preproxy_node_id = node["main_node"]
+ local preproxy_node = preproxy_enabled and preproxy_node_id and uci:get_all(appname, preproxy_node_id) or nil
+
+ if not preproxy_node and preproxy_node_id and api.parseURL(preproxy_node_id) then
+ local parsed1 = api.parseURL(preproxy_node_id)
+ local _node = {
+ type = "sing-box",
+ protocol = parsed1.protocol,
+ username = parsed1.username,
+ password = parsed1.password,
+ address = parsed1.host,
+ port = parsed1.port,
+ }
+ local preproxy_outbound = gen_outbound(flag, _node, preproxy_tag)
+ if preproxy_outbound then
+ table.insert(outbounds, preproxy_outbound)
+ else
+ preproxy_enabled = false
+ end
+ elseif preproxy_node and api.is_normal_node(preproxy_node) then
+ local preproxy_outbound = gen_outbound(flag, preproxy_node, preproxy_tag)
+ if preproxy_outbound then
+ table.insert(outbounds, preproxy_outbound)
+ else
+ preproxy_enabled = false
+ end
+ end
+
+ local function gen_shunt_node(rule_name, _node_id, as_proxy)
+ if not rule_name then return nil, nil end
+ if not _node_id then _node_id = node[rule_name] or "nil" end
+ local rule_outboundTag
+ if _node_id == "_direct" then
+ rule_outboundTag = "direct"
+ elseif _node_id == "_blackhole" then
+ rule_outboundTag = "block"
+ elseif _node_id == "_default" and rule_name ~= "default" then
+ rule_outboundTag = "default"
+ elseif api.parseURL(_node_id) then
+ local parsed1 = api.parseURL(_node_id)
+ local _node = {
+ type = "sing-box",
+ protocol = parsed1.protocol,
+ username = parsed1.username,
+ password = parsed1.password,
+ address = parsed1.host,
+ port = parsed1.port,
+ }
+ local _outbound = gen_outbound(flag, _node, rule_name)
+ if _outbound then
+ table.insert(outbounds, _outbound)
+ rule_outboundTag = rule_name
+ end
+ elseif _node_id ~= "nil" then
+ local _node = uci:get_all(appname, _node_id)
+ if not _node then return nil, nil end
+
+ if api.is_normal_node(_node) then
+ local proxy = preproxy_enabled and node[rule_name .. "_proxy_tag"] == preproxy_tag and _node_id ~= preproxy_node_id
+ local copied_outbound
+ for index, value in ipairs(outbounds) do
+ if value["_flag_tag"] == _node_id and value["_flag_proxy_tag"] == preproxy_tag then
+ copied_outbound = api.clone(value)
+ break
+ end
+ end
+ if copied_outbound then
+ copied_outbound.tag = rule_name
+ table.insert(outbounds, copied_outbound)
+ rule_outboundTag = rule_name
+ else
+ if proxy then
+ local pre_proxy = nil
+ if _node.type ~= "sing-box" then
+ pre_proxy = true
+ else
+ if _node.flow == "xtls-rprx-vision" then
+ pre_proxy = true
+ end
+ end
+ if pre_proxy then
+ new_port = get_new_port()
+ table.insert(inbounds, {
+ type = "direct",
+ tag = "proxy_" .. rule_name,
+ listen = "127.0.0.1",
+ listen_port = new_port,
+ override_address = _node.address,
+ override_port = tonumber(_node.port),
+ })
+ if _node.tls_serverName == nil then
+ _node.tls_serverName = _node.address
+ end
+ _node.address = "127.0.0.1"
+ _node.port = new_port
+ table.insert(rules, 1, {
+ inbound = {"proxy_" .. rule_name},
+ outbound = preproxy_tag,
+ })
+ end
+ end
+ local _outbound = gen_outbound(flag, _node, rule_name, { proxy = proxy and 1 or 0, tag = proxy and preproxy_tag or nil })
+ if _outbound then
+ table.insert(outbounds, _outbound)
+ if proxy then preproxy_used = true end
+ rule_outboundTag = rule_name
+ end
+ end
+ elseif _node.protocol == "_iface" then
+ if _node.iface then
+ local _outbound = {
+ type = "direct",
+ tag = rule_name,
+ bind_interface = _node.iface,
+ routing_mark = 255,
+ }
+ table.insert(outbounds, _outbound)
+ rule_outboundTag = rule_name
+ sys.call("touch /tmp/etc/passwall2/iface/" .. _node.iface)
+ end
+ end
+ end
+ return rule_outboundTag
+ end
+ --default_node
+ local default_node_id = node.default_node or "_direct"
+ local default_outboundTag = gen_shunt_node("default", default_node_id)
+ --shunt rule
+ uci:foreach(appname, "shunt_rules", function(e)
+ local outboundTag = gen_shunt_node(e[".name"])
+ if outboundTag and e.remarks then
+ if outboundTag == "default" then
+ outboundTag = default_outboundTag
+ end
+ local protocols = nil
+ if e["protocol"] and e["protocol"] ~= "" then
+ protocols = {}
+ string.gsub(e["protocol"], '[^' .. " " .. ']+', function(w)
+ table.insert(protocols, w)
+ end)
+ end
+
+ local rule = {
+ outbound = outboundTag,
+ invert = false, --匹配反选
+ protocol = protocols
+ }
+
+ if e.network then
+ local network = {}
+ string.gsub(e.network, '[^' .. "," .. ']+', function(w)
+ table.insert(network, w)
+ end)
+ rule.network = network
+ end
+
+ if e.source then
+ local source_geoip = {}
+ local source_ip_cidr = {}
+ string.gsub(e.source, '[^' .. " " .. ']+', function(w)
+ if w:find("geoip") == 1 then
+ table.insert(source_geoip, w)
+ else
+ table.insert(source_ip_cidr, w)
+ end
+ end)
+ rule.source_geoip = #source_geoip > 0 and source_geoip or nil
+ rule.source_ip_cidr = #source_ip_cidr > 0 and source_ip_cidr or nil
+ end
+
+ if e.sourcePort then
+ local source_port = {}
+ local source_port_range = {}
+ string.gsub(e.sourcePort, '[^' .. "," .. ']+', function(w)
+ if tonumber(w) and tonumber(w) >= 1 and tonumber(w) <= 65535 then
+ table.insert(source_port, tonumber(w))
+ else
+ table.insert(source_port_range, w)
+ end
+ end)
+ rule.source_port = #source_port > 0 and source_port or nil
+ rule.source_port_range = #source_port_range > 0 and source_port_range or nil
+ end
+
+ if e.port then
+ local port = {}
+ local port_range = {}
+ string.gsub(e.port, '[^' .. "," .. ']+', function(w)
+ if tonumber(w) and tonumber(w) >= 1 and tonumber(w) <= 65535 then
+ table.insert(port, tonumber(w))
+ else
+ table.insert(port_range, w)
+ end
+ end)
+ rule.port = #port > 0 and port or nil
+ rule.port_range = #port_range > 0 and port_range or nil
+ end
+
+ if e.domain_list then
+ local domain = {}
+ local domain_suffix = {}
+ local domain_keyword = {}
+ local domain_regex = {}
+ local geosite = {}
+ string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w)
+ if w:find("geosite:") == 1 then
+ table.insert(geosite, w:sub(1 + #"geosite:"))
+ elseif w:find("regexp:") == 1 then
+ table.insert(domain_regex, w:sub(1 + #"regexp:"))
+ elseif w:find("full:") == 1 then
+ table.insert(domain, w:sub(1 + #"full:"))
+ elseif w:find("domain:") == 1 then
+ table.insert(domain_keyword, w:sub(1 + #"domain:"))
+ else
+ table.insert(domain, w)
+ end
+
+ if outboundTag == "direct" then
+ table.insert(dns_direct_domains, w)
+ else
+ if outboundTag ~= "nil" then
+ table.insert(dns_remote_domains, w)
+ end
+ end
+ end)
+
+ rule.domain = #domain > 0 and domain or nil
+ rule.domain_suffix = #domain_suffix > 0 and domain_suffix or nil
+ rule.domain_keyword = #domain_keyword > 0 and domain_keyword or nil
+ rule.domain_regex = #domain_regex > 0 and domain_regex or nil
+ rule.geosite = #geosite > 0 and geosite or nil
+ end
+
+ if e.ip_list then
+ local ip_cidr = {}
+ local geoip = {}
+ string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w)
+ if w:find("geoip:") == 1 then
+ table.insert(geoip, w:sub(1 + #"geoip:"))
+ else
+ table.insert(ip_cidr, w)
+ end
+ end)
+
+ rule.ip_cidr = #ip_cidr > 0 and ip_cidr or nil
+ rule.geoip = #geoip > 0 and geoip or nil
+ end
+
+ table.insert(rules, rule)
+ end
+ end)
+
+ if default_outboundTag then
+ route.final = default_outboundTag
+ dns_outTag = default_outboundTag
+ end
+
+ for index, value in ipairs(rules) do
+ table.insert(route.rules, rules[index])
+ end
+ else
+ local outbound = nil
+ if node.protocol == "_iface" then
+ if node.iface then
+ outbound = {
+ type = "direct",
+ tag = "outbound",
+ bind_interface = node.iface,
+ routing_mark = 255,
+ }
+ sys.call("touch /tmp/etc/passwall2/iface/" .. node.iface)
+ end
+ else
+ outbound = gen_outbound(flag, node)
+ end
+ if outbound then
+ dns_outTag = outbound.tag
+ table.insert(outbounds, outbound)
+ end
+
+ route.final = node_id
+ end
+ end
+
+ if dns_listen_port then
+ dns = {
+ servers = {},
+ rules = {},
+ disable_cache = (dns_cache and dns_cache == "0") and true or false,
+ disable_expire = false, --禁用 DNS 缓存过期。
+ independent_cache = false, --使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
+ reverse_mapping = true, --在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
+ fakeip = nil,
+ }
+
+ if true then
+ local dns_tag = "remote"
+
+ local domain = {}
+ local domain_suffix = {}
+ local domain_keyword = {}
+ local domain_regex = {}
+ local geosite = {}
+ for index, value in ipairs(dns_remote_domains) do
+ if value:find("geosite:") == 1 then
+ table.insert(geosite, value:sub(1 + #"geosite:"))
+ elseif value:find("regexp:") == 1 then
+ table.insert(domain_regex, value:sub(1 + #"regexp:"))
+ elseif value:find("full:") == 1 then
+ table.insert(domain, value:sub(1 + #"full:"))
+ elseif value:find("domain:") == 1 then
+ table.insert(domain_keyword, value:sub(1 + #"domain:"))
+ else
+ table.insert(domain, value)
+ end
+ end
+ local remote_rule = {
+ server = dns_tag,
+ domain = #domain > 0 and domain or nil,
+ domain_suffix = #domain_suffix > 0 and domain_suffix or nil,
+ domain_keyword = #domain_keyword > 0 and domain_keyword or nil,
+ domain_regex = #domain_regex > 0 and domain_regex or nil,
+ geosite = #geosite > 0 and geosite or nil,
+ disable_cache = true,
+ }
+
+ local remote_strategy = "prefer_ipv6"
+ if remote_dns_query_strategy == "UseIPv4" then
+ remote_strategy = "ipv4_only"
+ elseif remote_dns_query_strategy == "UseIPv6" then
+ remote_strategy = "ipv6_only"
+ end
+
+ local server = {
+ tag = dns_tag,
+ address_strategy = "prefer_ipv4",
+ strategy = remote_strategy,
+ detour = dns_outTag,
+ }
+
+ local rule_server = dns_tag
+
+ if remote_dns_udp_server then
+ local server_port = tonumber(remote_dns_port) or 53
+ server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port
+ end
+
+ if remote_dns_tcp_server then
+ server.address = remote_dns_tcp_server
+ end
+
+ if remote_dns_doh_url and remote_dns_doh_host then
+ server.address = remote_dns_doh_url
+ end
+
+ table.insert(dns.servers, server)
+
+ if remote_dns_fake then
+ dns.fakeip = {
+ enabled = true,
+ inet4_range = "198.18.0.0/16",
+ inet6_range = "fc00::/18",
+ }
+
+ table.insert(dns.servers, {
+ tag = dns_tag .. "_fakeip",
+ address = "fakeip",
+ strategy = remote_strategy,
+ })
+
+ rule_server = dns_tag .. "_fakeip"
+
+ if tags and tags:find("with_clash_api") then
+ if not experimental then
+ experimental = {}
+ end
+ experimental.clash_api = {
+ store_fakeip = true,
+ cache_file = "/tmp/singbox/passwall2_" .. flag .. ".db"
+ }
+ end
+ end
+
+ if remote_rule.domain or remote_rule.domain_suffix or remote_rule.domain_keyword or remote_rule.domain_regex or remote_rule.geosite then
+ local rule = api.clone(remote_rule)
+ rule.server = rule_server
+ table.insert(dns.rules, rule)
+ end
+ end
+
+ if direct_dns_udp_server then
+ local nodes_domain_text = sys.exec('uci show passwall2 | grep ".address=" | cut -d "\'" -f 2 | grep "[a-zA-Z]$" | sort -u')
+ string.gsub(nodes_domain_text, '[^' .. "\r\n" .. ']+', function(w)
+ table.insert(dns_direct_domains, "full:" .. w)
+ end)
+
+ local dns_tag = "direct"
+
+ local domain = {}
+ local domain_suffix = {}
+ local domain_keyword = {}
+ local domain_regex = {}
+ local geosite = {}
+ for index, value in ipairs(dns_direct_domains) do
+ if value:find("geosite:") == 1 then
+ table.insert(geosite, value:sub(1 + #"geosite:"))
+ elseif value:find("regexp:") == 1 then
+ table.insert(domain_regex, value:sub(1 + #"regexp:"))
+ elseif value:find("full:") == 1 then
+ table.insert(domain, value:sub(1 + #"full:"))
+ elseif value:find("domain:") == 1 then
+ table.insert(domain_keyword, value:sub(1 + #"domain:"))
+ else
+ table.insert(domain, value)
+ end
+ end
+ local direct_rule = {
+ server = dns_tag,
+ domain = domain,
+ domain_suffix = #domain_suffix > 0 and domain_suffix or nil,
+ domain_keyword = #domain_keyword > 0 and domain_keyword or nil,
+ domain_regex = #domain_regex > 0 and domain_regex or nil,
+ geosite = #geosite > 0 and geosite or nil,
+ disable_cache = false,
+ }
+ table.insert(dns.rules, direct_rule)
+
+ local direct_strategy = "prefer_ipv6"
+ if direct_dns_query_strategy == "UseIPv4" then
+ direct_strategy = "ipv4_only"
+ elseif direct_dns_query_strategy == "UseIPv6" then
+ direct_strategy = "ipv6_only"
+ end
+
+ local port = tonumber(direct_dns_port) or 53
+
+ table.insert(dns.servers, {
+ tag = dns_tag,
+ address = "udp://" .. direct_dns_udp_server .. ":" .. port,
+ address_strategy = "prefer_ipv6",
+ strategy = direct_strategy,
+ detour = "direct",
+ })
+ end
+
+ table.insert(dns.servers, {
+ tag = "block",
+ address = "rcode://refused",
+ })
+
+ table.insert(inbounds, {
+ type = "direct",
+ tag = "dns-in",
+ listen = "127.0.0.1",
+ listen_port = tonumber(dns_listen_port),
+ sniff = true,
+ })
+ table.insert(outbounds, {
+ type = "dns",
+ tag = "dns-out",
+ })
+ table.insert(route.rules, 1, {
+ protocol = "dns",
+ inbound = {
+ "dns-in"
+ },
+ outbound = "dns-out"
+ })
+
+ local default_dns_flag = "remote"
+ if node_id and redir_port then
+ local node = uci:get_all(appname, node_id)
+ if node.protocol == "_shunt" then
+ if node.default_node == "_direct" then
+ default_dns_flag = "direct"
+ end
+ end
+ end
+ dns.final = default_dns_flag
+ end
+
+ if inbounds or outbounds then
+ local config = {
+ log = {
+ disabled = log == "0" and true or false,
+ level = loglevel,
+ timestamp = true,
+ output = logfile,
+ },
+ -- DNS
+ dns = dns,
+ -- 传入连接
+ inbounds = inbounds,
+ -- 传出连接
+ outbounds = outbounds,
+ -- 路由
+ route = route,
+ --实验性
+ experimental = experimental,
+ }
+ table.insert(outbounds, {
+ type = "direct",
+ tag = "direct",
+ routing_mark = 255,
+ domain_strategy = "prefer_ipv6",
+ })
+ table.insert(outbounds, {
+ type = "block",
+ tag = "block"
+ })
+ for index, value in ipairs(config.outbounds) do
+ for k, v in pairs(config.outbounds[index]) do
+ if k:find("_") == 1 then
+ config.outbounds[index][k] = nil
+ end
+ end
+ end
+ return jsonc.stringify(config, 1)
+ end
+end
+
+function gen_proto_config(var)
+ local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
+ local local_socks_port = var["-local_socks_port"]
+ local local_socks_username = var["-local_socks_username"]
+ local local_socks_password = var["-local_socks_password"]
+ local local_http_address = var["-local_http_address"] or "0.0.0.0"
+ local local_http_port = var["-local_http_port"]
+ local local_http_username = var["-local_http_username"]
+ local local_http_password = var["-local_http_password"]
+ local server_proto = var["-server_proto"]
+ local server_address = var["-server_address"]
+ local server_port = var["-server_port"]
+ local server_username = var["-server_username"]
+ local server_password = var["-server_password"]
+
+ local inbounds = {}
+ local outbounds = {}
+
+ if local_socks_address and local_socks_port then
+ local inbound = {
+ type = "socks",
+ tag = "socks-in",
+ listen = local_socks_address,
+ listen_port = tonumber(local_socks_port),
+ }
+ if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then
+ inbound.users = {
+ username = local_socks_username,
+ password = local_socks_password
+ }
+ end
+ table.insert(inbounds, inbound)
+ end
+
+ if local_http_address and local_http_port then
+ local inbound = {
+ type = "http",
+ tag = "http-in",
+ tls = nil,
+ listen = local_http_address,
+ listen_port = tonumber(local_http_port),
+ }
+ if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then
+ inbound.users = {
+ {
+ username = local_http_username,
+ password = local_http_password
+ }
+ }
+ end
+ table.insert(inbounds, inbound)
+ end
+
+ if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then
+ local outbound = {
+ type = server_proto,
+ tag = "out",
+ server = server_address,
+ server_port = tonumber(server_port),
+ username = (server_username and server_password) and server_username or nil,
+ password = (server_username and server_password) and server_password or nil,
+ }
+ if outbound then table.insert(outbounds, outbound) end
+ end
+
+ local config = {
+ log = {
+ disabled = true,
+ level = "warn",
+ timestamp = true,
+ },
+ -- 传入连接
+ inbounds = inbounds,
+ -- 传出连接
+ outbounds = outbounds,
+ }
+ return jsonc.stringify(config, 1)
+end
+
+function gen_dns_config(var)
+ local dns_listen_port = var["-dns_listen_port"]
+ local dns_query_strategy = var["-dns_query_strategy"]
+ local dns_out_tag = var["-dns_out_tag"]
+ local dns_client_ip = var["-dns_client_ip"]
+ local direct_dns_server = var["-direct_dns_server"]
+ local direct_dns_port = var["-direct_dns_port"]
+ local direct_dns_udp_server = var["-direct_dns_udp_server"]
+ local direct_dns_tcp_server = var["-direct_dns_tcp_server"]
+ local direct_dns_doh_url = var["-direct_dns_doh_url"]
+ local direct_dns_doh_host = var["-direct_dns_doh_host"]
+ local remote_dns_server = var["-remote_dns_server"]
+ local remote_dns_port = var["-remote_dns_port"]
+ local remote_dns_udp_server = var["-remote_dns_udp_server"]
+ local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
+ local remote_dns_doh_url = var["-remote_dns_doh_url"]
+ local remote_dns_doh_host = var["-remote_dns_doh_host"]
+ local remote_dns_outbound_socks_address = var["-remote_dns_outbound_socks_address"]
+ local remote_dns_outbound_socks_port = var["-remote_dns_outbound_socks_port"]
+ local remote_dns_fake = var["-remote_dns_fake"]
+ local dns_cache = var["-dns_cache"]
+ local log = var["-log"] or "0"
+ local loglevel = var["-loglevel"] or "warn"
+ local logfile = var["-logfile"] or "/dev/null"
+
+ local inbounds = {}
+ local outbounds = {}
+ local dns = nil
+ local route = nil
+
+ if dns_listen_port then
+ route = {
+ rules = {}
+ }
+
+ dns = {
+ servers = {},
+ rules = {},
+ disable_cache = (dns_cache and dns_cache == "0") and true or false,
+ disable_expire = false, --禁用 DNS 缓存过期。
+ independent_cache = false, --使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
+ reverse_mapping = true, --在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
+ fakeip = nil,
+ }
+
+ if dns_out_tag == "remote" then
+ local server = {
+ tag = dns_out_tag,
+ address_strategy = "prefer_ipv4",
+ strategy = (dns_query_strategy and dns_query_strategy ~= "UseIP") and "ipv4_only" or "prefer_ipv6",
+ detour = "remote-out",
+ }
+
+ if remote_dns_fake then
+ server.address = "fakeip"
+ dns.fakeip = {
+ enabled = true,
+ inet4_range = "198.18.0.0/16",
+ inet6_range = "fc00::/18",
+ }
+ end
+
+ if remote_dns_udp_server then
+ local server_port = tonumber(remote_dns_port) or 53
+ server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port
+ end
+
+ if remote_dns_tcp_server then
+ server.address = remote_dns_tcp_server
+ end
+
+ if remote_dns_doh_url and remote_dns_doh_host then
+ server.address = remote_dns_doh_url
+ end
+
+ table.insert(dns.servers, server)
+
+ table.insert(outbounds, 1, {
+ type = "socks",
+ tag = "remote-out",
+ server = remote_dns_outbound_socks_address,
+ server_port = tonumber(remote_dns_outbound_socks_port),
+ })
+
+ table.insert(route.rules, {
+ network = {"tcp", "udp"},
+ outbound = "remote-out"
+ })
+ elseif dns_out_tag == "direct" then
+ local server = {
+ tag = dns_out_tag,
+ address_strategy = "prefer_ipv6",
+ strategy = (dns_query_strategy and dns_query_strategy ~= "UseIP") and "ipv4_only" or "prefer_ipv6",
+ detour = "direct-out",
+ }
+
+ if direct_dns_udp_server then
+ local server_port = tonumber(direct_dns_port) or 53
+ server.address = "udp://" .. direct_dns_udp_server .. ":" .. server_port
+ end
+
+ if direct_dns_tcp_server then
+ local server_port = tonumber(direct_dns_port) or 53
+ server.address = direct_dns_tcp_server .. ":" .. server_port
+ end
+
+ if direct_dns_doh_url and direct_dns_doh_host then
+ local server_port = tonumber(direct_dns_port) or 443
+ server.address = direct_dns_doh_url
+ end
+
+ table.insert(dns.servers, server)
+
+ table.insert(outbounds, 1, {
+ type = "direct",
+ tag = "direct-out",
+ routing_mark = 255,
+ domain_strategy = (dns_query_strategy and dns_query_strategy ~= "UseIP") and "ipv4_only" or "prefer_ipv6",
+ })
+ end
+
+ table.insert(inbounds, {
+ type = "direct",
+ tag = "dns-in",
+ listen = "127.0.0.1",
+ listen_port = tonumber(dns_listen_port),
+ sniff = true,
+ })
+
+ table.insert(outbounds, {
+ type = "dns",
+ tag = "dns-out",
+ })
+
+ table.insert(route.rules, 1, {
+ protocol = "dns",
+ inbound = {
+ "dns-in"
+ },
+ outbound = "dns-out"
+ })
+ end
+
+ if inbounds or outbounds then
+ local config = {
+ log = {
+ disabled = log == "0" and true or false,
+ level = loglevel,
+ timestamp = true,
+ output = logfile,
+ },
+ -- DNS
+ dns = dns,
+ -- 传入连接
+ inbounds = inbounds,
+ -- 传出连接
+ outbounds = outbounds,
+ -- 路由
+ route = route
+ }
+ return jsonc.stringify(config, 1)
+ end
+
+end
+
+_G.gen_config = gen_config
+_G.gen_proto_config = gen_proto_config
+_G.gen_dns_config = gen_dns_config
+
+if arg[1] then
+ local func =_G[arg[1]]
+ if func then
+ print(func(api.get_function_args(arg)))
+ end
+end
diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
index dc84ad82c..6c7bd04e7 100644
--- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua
+++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
@@ -47,8 +47,8 @@ function gen_outbound(flag, node, tag, proxy_table)
proxy_tag = proxy_table.tag or "nil"
end
- if node.type == "V2ray" or node.type == "Xray" then
- if node.type == "Xray" and node.tlsflow == "xtls-rprx-vision" then
+ if node.type == "Xray" then
+ if node.flow == "xtls-rprx-vision" then
else
proxy = 0
if proxy_tag ~= "nil" then
@@ -60,7 +60,7 @@ function gen_outbound(flag, node, tag, proxy_table)
end
end
- if node.type ~= "V2ray" and node.type ~= "Xray" then
+ if node.type ~= "Xray" then
local relay_port = node.port
new_port = get_new_port()
local config_file = string.format("%s_%s_%s.json", flag, tag, new_port)
@@ -87,10 +87,10 @@ function gen_outbound(flag, node, tag, proxy_table)
node.stream_security = "none"
end
- if node.type == "V2ray" or node.type == "Xray" then
+ if node.type == "Xray" then
if node.tls and node.tls == "1" then
node.stream_security = "tls"
- if node.type == "Xray" and node.reality and node.reality == "1" then
+ if node.reality and node.reality == "1" then
node.stream_security = "reality"
end
end
@@ -133,7 +133,7 @@ function gen_outbound(flag, node, tag, proxy_table)
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false,
- fingerprint = (node.type == "Xray" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil
+ fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil
} or nil,
realitySettings = (node.stream_security == "reality") and {
serverName = node.tls_serverName,
@@ -204,7 +204,7 @@ function gen_outbound(flag, node, tag, proxy_table)
level = 0,
security = (node.protocol == "vmess") and node.security or nil,
encryption = node.encryption or "none",
- flow = (node.protocol == "vless" and node.tls == '1' and node.tlsflow) and node.tlsflow or nil
+ flow = (node.protocol == "vless" and node.tls == '1' and node.flow) and node.flow or nil
}
}
}
@@ -267,7 +267,7 @@ function gen_config_server(node)
for i = 1, #node.uuid do
clients[i] = {
id = node.uuid[i],
- flow = ("vless" == node.protocol and "1" == node.tls and node.tlsflow) and node.tlsflow or nil
+ flow = ("vless" == node.protocol and "1" == node.tls and node.flow) and node.flow or nil
}
end
settings = {
@@ -760,10 +760,10 @@ function gen_config(var)
else
if proxy then
local pre_proxy = nil
- if _node.type ~= "V2ray" and _node.type ~= "Xray" then
+ if _node.type ~= "Xray" then
pre_proxy = true
end
- if _node.type == "Xray" and _node.tlsflow == "xtls-rprx-vision" then
+ if _node.type == "Xray" and _node.flow == "xtls-rprx-vision" then
pre_proxy = true
end
if pre_proxy then
diff --git a/luci-app-passwall2/luasrc/view/passwall2/global/status.htm b/luci-app-passwall2/luasrc/view/passwall2/global/status.htm
index 45205c471..3ce29f2bb 100644
--- a/luci-app-passwall2/luasrc/view/passwall2/global/status.htm
+++ b/luci-app-passwall2/luasrc/view/passwall2/global/status.htm
@@ -91,7 +91,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md