update 2026-10-27 07:31:06

This commit is contained in:
action committed 2026-10-27 07:31:06 +08:00
1 parent 197c60d843
commit a4d6470641
4213 files changed
-1575003

No files matched your search

@@ -1,73 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map()
s = m:section(NamedSection, "@global[0]", "global", translate("ACLs"), "<font color='red'>" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "</font>")
o = s:option(Flag, "acl_enable", translate("Main switch"))
o.rmempty = false
o.default = false
-- [[ ACLs Settings ]]--
s = m:section(TypedSection, "acl_rule")
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
s.addremove = true
s.extedit = api.url("acl_config", "%s")
function s.create(e, t)
local uid = "acl_" .. api.gen_random_char(5)
TypedSection.create(e, uid)
luci.http.redirect(e.extedit:format(uid))
end
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
---- Remarks
o = s:option(Value, "remarks", translate("Remarks"))
o.rmempty = true
local mac_t = {}
api.sys.net.mac_hints(function(e, t)
mac_t[e] = {
ip = t,
mac = e
}
end)
i = s:option(DummyValue, "interface", translate("Source Interface"))
i.cfgvalue = function(t, n)
local v = Value.cfgvalue(t, n) or ''
if v == "" then
return translate("All")
end
return v
end
o = s:option(DummyValue, "sources", translate("Source"))
o.rawhtml = true
o.cfgvalue = function(self, section)
local v = self.map:get(section, self.option) or {}
if type(v) == "table" then
return table.concat(v, "<br/>")
end
end
i = s:option(DummyValue, "mode", translate("Mode"))
i.cfgvalue = function(t, n)
local v = Value.cfgvalue(t, n) or '0'
if v == "1" then
return translate("Proxy")
elseif v == "2" then
return translate("Proxy") .. " " .. translate("Use global config")
end
return translate("No Proxy")
end
m:appendTemplate("/cbi/sortable", {sectiontype = s.sectiontype})
return api.return_map(m)
@@ -1,399 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map()
m.redirect = api.url("acl")
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
m:appendTemplate("/cbi/nodes_listvalue_com")
local port_validate = function(self, value, t)
return value:gsub("-", ":")
end
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
local doh_validate = function(self, value, t)
if value ~= "" then
local flag = 0
local util = require "luci.util"
local val = util.split(value, ",")
local url = val[1]
val[1] = nil
for i = 1, #val do
local v = val[i]
if v then
if not api.datatypes.ipmask4(v) then
flag = 1
end
end
end
if flag == 0 then
return value
end
end
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
end
-- [[ ACLs Settings ]]--
s = m:section(NamedSection, arg[1], translate("ACLs"), translate("ACLs"))
s:tab("Main", translate("Main"))
s:tab("Proxy", translate("Proxy"))
s:tab("DNS", translate("DNS"))
s:tab("Log", translate("Log"))
o = s:taboption("Main", Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
o = s:taboption("Main", Value, "remarks", translate("Remarks"))
o.default = arg[1]
o.rmempty = false
o = s:taboption("Main", Value, "interface", translate("Source Interface"))
o:value("", translate("All"))
local iface = api.get_network_devices()
for _, d in ipairs(iface) do
o:value(d.name, d.label)
end
o.validate = function(self, value, section)
if value == "" or value:match("^[a-zA-Z0-9][a-zA-Z0-9%.%_%-]*$") then
return value
end
return nil, translate("Invalid interface name")
end
local mac_t = {}
api.sys.net.mac_hints(function(e, t)
mac_t[#mac_t + 1] = {
ip = t,
mac = e
}
end)
table.sort(mac_t, function(a,b)
if #a.ip < #b.ip then
return true
elseif #a.ip == #b.ip then
if a.ip < b.ip then
return true
else
return #a.ip < #b.ip
end
end
return false
end)
---- Source
sources = s:taboption("Main", DynamicList, "sources", translate("Source"))
sources.description = "<ul><li>" .. translate("Example:")
.. "</li><li>" .. translate("MAC") .. ": 00:00:00:FF:FF:FF"
.. "</li><li>" .. translate("IP") .. ": 192.168.1.100"
.. "</li><li>" .. translate("IP CIDR") .. ": 192.168.1.0/24"
.. "</li><li>" .. translate("IP range") .. ": 192.168.1.100-192.168.1.200"
.. "</li><li>" .. translate("IPSet") .. ": ipset:lanlist"
.. "</li></ul>"
for _, key in pairs(mac_t) do
sources:value(key.mac, "%s (%s)" % {key.mac, key.ip})
end
sources.validate = function(self, value, t)
local err = {}
for _, v in ipairs(value) do
local flag = false
if v:find("ipset:") and v:find("ipset:") == 1 then
local ipset = v:gsub("ipset:", "")
if ipset and ipset ~= "" then
flag = true
end
end
if flag == false and api.datatypes.macaddr(v) then
flag = true
end
if flag == false and api.datatypes.ip4addr(v) then
flag = true
end
if flag == false and api.iprange(v) then
flag = true
end
if flag == false then
err[#err + 1] = v
end
end
if #err > 0 then
self:add_error(t, "invalid", translate("Not true format, please re-enter!"))
for _, v in ipairs(err) do
self:add_error(t, "invalid", v)
end
end
return value
end
local NODE = m:get("@global[0]", "node")
o = s:taboption("Main", ListValue, "mode", translate("Mode"))
o:value("0", translate("No Proxy"))
o:value("1", translate("Proxy"))
o:value("2", translate("Proxy") .. " " .. translate("Use global config") .. "(" .. api.get_node_name(NODE) .. ")")
o = s:taboption("Proxy", Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
o:value("", translate("No patterns are used"))
o:value("1:65535", translate("All"))
o:depends("mode", "1")
o:depends("mode", "2")
o.validate = port_validate
o = s:taboption("Proxy", Value, "udp_no_redir_ports", translate("UDP No Redir Ports"))
o:value("", translate("No patterns are used"))
o:value("1:65535", translate("All"))
o:depends("mode", "1")
o:depends("mode", "2")
o.validate = port_validate
o = s:taboption("Main", HideValue, "_show_tcp_redir")
o:depends({ tcp_no_redir_ports = "1:65535", ['!reverse'] = true })
o = s:taboption("Main", HideValue, "_show_udp_redir")
o:depends({ udp_no_redir_ports = "1:65535", ['!reverse'] = true })
o = s:taboption("Proxy", Value, "tcp_redir_ports", translate("TCP Redir Ports"))
o:value("1:65535", translate("All"))
o:value("22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418", translate("Common Use"))
o:value("80,443", "80,443")
o.default = o.keylist[1]
o.validate = port_validate
o:depends({ _node = "1", _show_tcp_redir = "1" })
o:depends({ mode = "2", _show_tcp_redir = "1" })
o = s:taboption("Proxy", Value, "udp_redir_ports", translate("UDP Redir Ports"))
o:value("1:65535", translate("All"))
o.default = o.keylist[1]
o.validate = port_validate
o:depends({ _node = "1", _show_udp_redir = "1" })
o:depends({ mode = "2", _show_udp_redir = "1" })
o = s:taboption("Proxy", DummyValue, "tips", " ")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<font color="red">%s</font>',
translate("The port settings support single ports and ranges.<br>Separate multiple ports with commas (,).<br>Example: 21,80,443,1000:2000."))
end
o:depends("mode", "1")
o:depends("mode", "2")
o = s:taboption("Main", HideValue, "_no_proxy")
o:depends("mode", "0")
o:depends({ tcp_no_redir_ports = "1:65535", udp_no_redir_ports = "1:65535" })
o = s:taboption("Main", HideValue, "_show_node_option")
o:depends({ mode = "1", _no_proxy = "" })
o = s:taboption("Main", ListValue, "node", "<a style='color: red'>" .. translate("Node") .. "</a>")
o.group = {}
o:depends("_show_node_option", "1")
o.template = m:template_path("/cbi/nodes_listvalue")
current_node_id = m:get(arg[1], "node")
local node_value = s.fields["node"]:formvalue(s.section)
if node_value then
current_node_id = node_value
end
current_node = current_node_id and m:get(current_node_id) or {}
o = s:taboption("Main", HideValue, "node_save_before")
o.value = current_node[".name"]
o.cbid = function(self, section) return "node_save_before" end
o = s:taboption("Main", HideValue, "_node")
o:depends({ node = "", ['!reverse'] = true })
o = s:taboption("Main", HideValue, "_is_singbox")
o:depends("_hide", "1")
o = s:taboption("Main", HideValue, "_is_xray")
o:depends("_hide", "1")
o = s:taboption("Log", Flag, "log", translate("Enable Node Log"))
o:depends("_node", "1")
o = s:taboption("Log", ListValue, "loglevel", translate("Log Level"))
o.default = "warn"
o:value("debug", "Debug")
o:value("info", "Info")
o:value("warn", "Warning")
o:value("error", "Error")
o:depends("log", "1")
o = s:taboption("Log", DummyValue, "_acl_log", translate("Log File"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local log_file = api.TMP_PATH .. "/acl/" .. arg[1] .. ".log"
local log_url = api.url("get_redir_log") .. "?id=" .. arg[1]
local s = "<code>%s</code>&nbsp;&nbsp;" % log_file
if api.fs.access(log_file) then
local btn = string.format(
'<input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
translate("View Log"),
log_url
)
s = s .. btn
end
return s
end
o:depends("log", "1")
o = s:taboption("DNS", HideValue, "_show_dns_option")
o:depends({ mode = "1", _node = "1" })
o = s:taboption("DNS", ListValue, "direct_dns_query_strategy", translate("Direct Query Strategy"))
o.default = "UseIP"
o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
o:depends("_show_dns_option", "1")
o = s:taboption("DNS", ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:value("udp", "UDP")
if m.is_js_luci then
if current_node.type == "sing-box" then
o:value("tls", "TLS(DoT)")
o:value("quic", "QUIC(DoQ)")
o:value("http3", "HTTP3(DoH3)")
end
else
o:value("tls", "TLS(DoT)", { _is_singbox = "1" })
o:value("quic", "QUIC(DoQ)", { _is_singbox = "1" })
o:value("http3", "HTTP3(DoH3)", { _is_singbox = "1" })
end
o:depends("_show_dns_option", "1")
---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ)
o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
o.datatype = "or(ipaddr,ipaddrport(1))"
o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
o:value("1.1.1.2", "1.1.1.2 (CloudFlare-Security)")
o:value("8.8.4.4", "8.8.4.4 (Google)")
o:value("8.8.8.8", "8.8.8.8 (Google)")
o:value("9.9.9.9", "9.9.9.9 (Quad9-Recommended)")
o:value("149.112.112.112", "149.112.112.112 (Quad9-Recommended)")
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
o:depends("remote_dns_protocol", "tcp")
o:depends("remote_dns_protocol", "udp")
o:depends("remote_dns_protocol", "quic")
o:depends("remote_dns_protocol", "tls")
---- DNS over HTTP (DoH) or DNS over HTTP3(DoH3)
o = s:taboption("DNS", Value, "remote_dns_doh", translate("Remote DNS DoH"))
o:value("https://1.1.1.1/dns-query", "CloudFlare")
o:value("https://1.1.1.2/dns-query", "CloudFlare-Security")
o:value("https://8.8.4.4/dns-query", "Google 8844")
o:value("https://8.8.8.8/dns-query", "Google 8888")
o:value("https://9.9.9.9/dns-query", "Quad9-Recommended 9.9.9.9")
o:value("https://149.112.112.112/dns-query", "Quad9-Recommended 149.112.112.112")
o:value("https://208.67.222.222/dns-query", "OpenDNS")
o:value("https://dns.adguard.com/dns-query,94.140.14.14", "AdGuard")
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS")
o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)")
o.default = "https://1.1.1.1/dns-query"
o.validate = doh_validate
o:depends("remote_dns_protocol", "doh")
o:depends("remote_dns_protocol", "http3")
o = s:taboption("DNS", Value, "remote_dns_client_ip", translate("Remote DNS EDNS Client Subnet"))
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr"
o:depends("_show_dns_option", "1")
o = s:taboption("DNS", ListValue, "remote_dns_detour", translate("Remote DNS Outbound"))
o.default = "remote"
o:value("remote", translate("Remote"))
o:value("direct", translate("Direct"))
o:depends("_show_dns_option", "1")
o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the domain that proxy."))
o.default = "0"
o.rmempty = false
o:depends("_hide", "1")
o = s:taboption("DNS", ListValue, "remote_dns_query_strategy", translate("Remote Query Strategy"))
o.default = "UseIPv4"
o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
o:depends("_show_dns_option", "1")
o = s:taboption("DNS", Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL")
o.datatype = "min(1)"
o.default = "30"
o:depends({ _show_dns_option = "1", _is_singbox = "1" })
o = s:taboption("DNS", ListValue, "dns_hosts_mode", translate("Domain Override"))
o:value("default", translate("Use global config"))
o:value("disable", translate("No patterns are used"))
o:value("custom", translate("-- custom --"))
o:depends("_show_dns_option", "1")
o = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
o.rows = 5
o.wrap = "off"
o:depends("dns_hosts_mode", "custom")
o.remove = function(self, section)
local node_value = s.fields["node"]:formvalue(arg[1])
if node_value then
local node_t = m:get(node_value) or {}
if node_t.type == "Xray" or node_t.type == "sing-box" then
AbstractValue.remove(self, section)
end
end
end
local o_node = s.fields["node"]
local shunt_list = {}
for k, v in pairs(nodes_table) do
o_node:value(v.id, v["remark"])
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
if v.type == "Xray" then
--s.fields["_is_xray"]:depends({ node = v.id })
end
if v.type == "sing-box" then
s.fields["_is_singbox"]:depends({ node = v.id })
end
if v.node_type == "normal" or v.protocol == "_balancing" or v.protocol == "_urltest" then
--Shunt node has its own separate options.
s.fields["remote_fakedns"]:depends({ node = v.id, _show_dns_option = "1" })
end
if v.protocol and v.protocol == "_shunt" then
shunt_list[#shunt_list + 1] = v
end
end
if current_node.protocol == "_shunt" then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, {
node = current_node,
verify_option = s.fields["node"],
tab = "Shunt",
tab_desc = translate("Shunt Rule"),
})
end
m:appendTemplate("/include/node_change", { verify_option = s.fields["node"], shunt_list = api.jsonc.stringify(shunt_list) })
return api.return_map(m)
@@ -1,37 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
local com = require "luci.passwall2.com"
m = Map()
-- [[ App Settings ]]--
s = m:section(NamedSection, "@global_app[0]", "global_app", translate("App Update"))
s:appendTemplate("/app_update/app_version", {com = com})
o = s:option(ListValue, "gh_proxy_url", translate("GitHub Proxy"), translate("Use GitHub instead of proxy nodes for component updates."))
o.default = ""
o:value("", translate("Close(Not use)"))
o:value("https://gh-proxy.org/", "gh-proxy.org")
o:value("https://ghproxy.cc/", "ghproxy.cc")
o:value("https://mirror.ghproxy.com/", "ghproxy.com")
o:value("https://ghfast.top/", "ghfast.top")
for _, k in ipairs(com.order) do
local v = com[k]
o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name))
o.default = v.default_path or ("/usr/bin/" .. k)
o.placeholder = o.default
function o:remove(section)
self:write(section, self.default)
end
end
o = s:option(DummyValue, "tips", " ")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<font color="red">%s</font>', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
end
return api.return_map(m)
@@ -1,17 +0,0 @@
local api = require "luci.passwall2.api"
local fs = api.fs
local geo_dir = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
local geosite_path = geo_dir .. "/geosite.dat"
local geoip_path = geo_dir .. "/geoip.dat"
if fs.access(geosite_path) and fs.access(geoip_path) then
f = SimpleForm(api.c_config)
f.reset = false
f.submit = false
local obj = Template(api.appname .. "/rule/geoview")
obj.api = api
f:append(obj)
end
return f
@@ -1,423 +0,0 @@
api = require "luci.passwall2.api"
datatypes = api.datatypes
has_singbox = api.finded_com("sing-box")
has_xray = api.finded_com("xray")
api.set_default_cbi()
m = Map()
m:appendTemplate("/cbi/nodes_listvalue_com")
nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
local normal_list = {}
local balancing_list = {}
local urltest_list = {}
local shunt_list = {}
local iface_list = {}
for k, v in pairs(nodes_table) do
if v.node_type == "normal" then
normal_list[#normal_list + 1] = v
end
if v.protocol and v.protocol == "_balancing" then
balancing_list[#balancing_list + 1] = v
end
if v.protocol and v.protocol == "_urltest" then
urltest_list[#urltest_list + 1] = v
end
if v.protocol and v.protocol == "_shunt" then
shunt_list[#shunt_list + 1] = v
end
if v.protocol and v.protocol == "_iface" then
iface_list[#iface_list + 1] = v
end
end
local socks_list = {}
m:foreach("socks", function(s)
if s.enabled == "1" and s.node then
socks_list[#socks_list + 1] = {
id = s[".name"],
remark = translate("Socks Config") .. " [" .. s.port .. translate("Port") .. "]",
group = "Socks"
}
end
end)
local doh_validate = function(self, value, t)
if value ~= "" then
local flag = 0
local util = require "luci.util"
local val = util.split(value, ",")
local url = val[1]
val[1] = nil
for i = 1, #val do
local v = val[i]
if v then
if not datatypes.ipmask4(v) and not datatypes.ipmask6(v) then
flag = 1
end
end
end
if flag == 0 then
return value
end
end
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
end
m:appendTemplate("/global/status")
s = m:section(NamedSection, "@global[0]", "global")
s:tab("Main", translate("Main"))
-- [[ Global Settings ]]--
o = s:taboption("Main", Flag, "enabled", translate("Main switch"))
o.rmempty = false
---- Node
o = s:taboption("Main", ListValue, "node", "<a style='color: red'>" .. translate("Node") .. "</a>")
o.template = m:template_path("/cbi/nodes_listvalue")
o:value("", translate("Close"))
o.group = {""}
o = s:taboption("Main", HideValue, "_node")
o:depends({ node = "", ['!reverse'] = true })
o = s:taboption("Main", HideValue, "_is_singbox")
o:depends("_hide", "1")
current_node_id = m:get(s.section, "node")
local node_value = s.fields["node"]:formvalue(s.section)
if node_value then
current_node_id = node_value
end
current_node = current_node_id and m:get(current_node_id) or {}
o = s:taboption("Main", HideValue, "node_save_before")
o.value = current_node[".name"]
o.cbid = function(self, section) return "node_save_before" end
-- Shunt Start
if (has_singbox or has_xray) and #nodes_table > 0 then
if #normal_list > 0 or #iface_list > 0 then
if current_node.protocol == "_shunt" then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, {
node = current_node,
verify_option = s.fields["node"],
tab = "Shunt",
tab_desc = translate("Shunt Rule"),
})
end
else
local tips = s:taboption("Main", DummyValue, "tips", " ")
tips.rawhtml = true
tips.cfgvalue = function(t, n)
return string.format('<a style="color: red">%s</a>', translate("There are no available nodes, please add or subscribe nodes first."))
end
tips:depends("_node", "1")
for k, v in pairs(shunt_list) do
tips:depends("node", v.id)
end
for k, v in pairs(balancing_list) do
tips:depends("node", v.id)
end
end
end
---- Check the transparent proxy component
local handle = io.popen("lsmod")
local mods = ""
if handle then
mods = handle:read("*a") or ""
handle:close()
end
if (mods:find("REDIRECT") and mods:find("TPROXY")) or (mods:find("nft_redir") and mods:find("nft_tproxy")) then
o = s:taboption("Main", Flag, "localhost_proxy", translate("Localhost Proxy"), translate("When selected, localhost can transparent proxy."))
o.default = "1"
o.rmempty = false
o = s:taboption("Main", Flag, "client_proxy", translate("Client Proxy"), translate("When selected, devices in LAN can transparent proxy. Otherwise, it will not be proxy. But you can still use access control to allow the designated device to proxy."))
o.default = "1"
o.rmempty = false
else
local html = string.format([[<div class="cbi-checkbox"><input class="cbi-input-checkbox" type="checkbox" disabled></div><div class="cbi-value-description"><font color="red">%s</font></div>]], translate("Missing components, transparent proxy is unavailable."))
o = s:taboption("Main", DummyValue, "localhost_proxy", translate("Localhost Proxy"))
o.rawhtml = true
function o.cfgvalue(self, section)
return html
end
o = s:taboption("Main", DummyValue, "client_proxy", translate("Client Proxy"))
o.rawhtml = true
function o.cfgvalue(self, section)
return html
end
end
node_socks_port = s:taboption("Main", Value, "node_socks_port", translate("Node") .. " Socks " .. translate("Listen Port"))
node_socks_port.default = 1070
node_socks_port.datatype = "port"
node_socks_port:depends("_node", "1")
node_socks_bind_local = s:taboption("Main", Flag, "node_socks_bind_local", translate("Node") .. " Socks " .. translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
node_socks_bind_local.default = "1"
node_socks_bind_local:depends("_node", "1")
s:tab("DNS", translate("DNS"))
o = s:taboption("DNS", ListValue, "direct_dns_protocol", translate("Direct DNS Protocol"))
o:value("", translate("Auto"))
--o:value("tcp", "TCP")
o:value("udp", "UDP")
o = s:taboption("DNS", Value, "direct_dns", translate("Direct DNS"))
o.datatype = "or(ipaddr,ipaddrport(1))"
o.default = "223.5.5.5"
o:value("223.5.5.5")
o:value("223.6.6.6")
o:value("114.114.114.114")
o:value("119.29.29.29")
o:value("180.76.76.76")
o:depends("direct_dns_protocol", "tcp")
o:depends("direct_dns_protocol", "udp")
o = s:taboption("DNS", ListValue, "direct_dns_query_strategy", translate("Direct Query Strategy"))
o.default = "UseIP"
o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
o = s:taboption("DNS", ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:value("udp", "UDP")
if m.is_js_luci then
if current_node.type == "sing-box" then
o:value("tls", "TLS(DoT)")
o:value("quic", "QUIC(DoQ)")
o:value("http3", "HTTP3(DoH3)")
end
else
o:value("tls", "TLS(DoT)", { _is_singbox = "1" })
o:value("quic", "QUIC(DoQ)", { _is_singbox = "1" })
o:value("http3", "HTTP3(DoH3)", { _is_singbox = "1" })
end
---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ)
o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
o.datatype = "or(ipaddr,ipaddrport(1))"
o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
o:value("1.1.1.2", "1.1.1.2 (CloudFlare-Security)")
o:value("8.8.4.4", "8.8.4.4 (Google)")
o:value("8.8.8.8", "8.8.8.8 (Google)")
o:value("9.9.9.9", "9.9.9.9 (Quad9-Recommended)")
o:value("149.112.112.112", "149.112.112.112 (Quad9-Recommended)")
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
o:depends("remote_dns_protocol", "tcp")
o:depends("remote_dns_protocol", "udp")
o:depends("remote_dns_protocol", "quic")
o:depends("remote_dns_protocol", "tls")
---- DNS over HTTP (DoH) or DNS over HTTP3(DoH3)
o = s:taboption("DNS", Value, "remote_dns_doh", translate("Remote DNS DoH"))
o.default = "https://1.1.1.1/dns-query"
o:value("https://1.1.1.1/dns-query", "CloudFlare")
o:value("https://1.1.1.2/dns-query", "CloudFlare-Security")
o:value("https://8.8.4.4/dns-query", "Google 8844")
o:value("https://8.8.8.8/dns-query", "Google 8888")
o:value("https://9.9.9.9/dns-query", "Quad9-Recommended 9.9.9.9")
o:value("https://149.112.112.112/dns-query", "Quad9-Recommended 149.112.112.112")
o:value("https://208.67.222.222/dns-query", "OpenDNS")
o:value("https://dns.adguard.com/dns-query,94.140.14.14", "AdGuard")
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS")
o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)")
o.validate = doh_validate
o:depends("remote_dns_protocol", "doh")
o:depends("remote_dns_protocol", "http3")
o = s:taboption("DNS", Value, "remote_dns_client_ip", translate("Remote DNS EDNS Client Subnet"))
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr"
o = s:taboption("DNS", ListValue, "remote_dns_detour", translate("Remote DNS Outbound"))
o.default = "remote"
o:value("remote", translate("Remote"))
o:value("direct", translate("Direct"))
o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the domain that proxy."))
o.default = "0"
o.rmempty = false
o = s:taboption("DNS", ListValue, "remote_dns_query_strategy", translate("Remote Query Strategy"))
o.default = "UseIPv4"
o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
o = s:taboption("DNS", Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL")
o.datatype = "min(1)"
o.default = "30"
o:depends("_is_singbox", "1")
o = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
o.rows = 5
o.wrap = "off"
o.remove = function(self, section)
local node_value = s.fields["node"]:formvalue(section)
if node_value then
local node_t = m:get(node_value) or {}
if node_t.type == "Xray" or node_t.type == "sing-box" then
AbstractValue.remove(self, section)
end
end
end
o = s:taboption("DNS", Flag, "dns_redirect", translate("DNS Redirect"), translate("Force special DNS server to need proxy devices."))
o.default = "1"
o.rmempty = false
local prefer_nft = m:get("@global_forwarding[0]", "prefer_nft") == "1"
local set_title = api.i18n.translate(prefer_nft and "Clear NFTSET" or "Clear IPSET")
o = s:taboption("DNS", DummyValue, "clear_ipset", set_title, translate("Try this feature if the rule modification does not take effect."))
o.rawhtml = true
function o.cfgvalue(self, section)
return string.format(
[[<button type="button" class="cbi-button cbi-button-remove" onclick="location.href='%s'">%s</button>]],
api.url("flush_set") .. "?redirect=1&reload=1", set_title)
end
s:tab("log", translate("Log"))
o = s:taboption("log", Flag, "log_node", translate("Enable Node Log"))
o.default = "1"
o.rmempty = false
loglevel = s:taboption("log", ListValue, "loglevel", translate("Log Level"))
loglevel.default = "warn"
loglevel:value("debug")
loglevel:value("info")
loglevel:value("warn")
loglevel:value("error")
o = s:taboption("log", DummyValue, "_log", translate("Log File"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local log_path = api.TMP_PATH .. "/acl/acl_default.log"
local log_url = api.url("get_redir_log") .. "?id=acl_default"
return string.format(
'<code>%s</code>&nbsp;&nbsp;<input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
log_path,
translate("View Log"),
log_url
)
end
o:depends("log_node", "1")
s:tab("faq", "FAQ")
o = s:taboption("faq", DummyValue, "")
o.template = m:template_path("/global/faq")
s:tab("maintain", translate("Maintain"))
o = s:taboption("maintain", DummyValue, "")
o.template = m:template_path("/global/backup")
m:appendTemplate("/include/node_change", { verify_option = s.fields["node"], shunt_list = api.jsonc.stringify(shunt_list) })
-- [[ Socks Server ]]--
o = s:taboption("Main", Flag, "socks_enabled", "Socks " .. translate("Main switch"))
o.rmempty = false
s2 = m:section(TypedSection, "socks", translate("Socks Config"))
s2.template = "cbi/tblsection"
s2.sortable = true
s2.anonymous = true
s2.addremove = true
s2.extedit = api.url("socks_config", "%s")
function s2.create(e, t)
local uid = "socks_" .. api.gen_random_char(5)
TypedSection.create(e, uid)
luci.http.redirect(e.extedit:format(uid))
end
o = s2:option(DummyValue, "status", translate("Status"))
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<div class="_status" socks_id="%s"></div>', n)
end
---- Enable
o = s2:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
o = s2:option(ListValue, "node", translate("Socks Node"))
o.template = m:template_path("/cbi/nodes_listvalue")
o.group = {}
o = s2:option(DummyValue, "now_node", translate("Current Node"))
o.rawhtml = true
o.cfgvalue = function(_, n)
local current_node = api.get_cache_var(n)
if current_node then
local node = m:get(current_node)
if node then
return (api.get_node_remarks(node) or ""):gsub("(:)%[", "%1<br>[")
end
end
end
local n = 1
m:foreach("socks", function(s)
if s[".name"] == section then
return false
end
n = n + 1
end)
o = s2:option(Value, "port", "Socks " .. translate("Listen Port"))
o.default = n + 1080
o.datatype = "port"
o.rmempty = false
--[[
if has_singbox or has_xray then
o = s2:option(Value, "http_port", "HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
o.default = 0
o.datatype = "port"
end
]]--
local o_node = s.fields["node"]
local o_socks = s2.fields["node"]
for k, v in pairs(nodes_table) do
if #normal_list == 0 and #iface_list == 0 then
break
end
if v.type == "sing-box" then
s.fields["_is_singbox"]:depends({ node = v.id })
end
o_node:value(v.id, v["remark"])
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
o_socks:value(v.id, v["remark"])
o_socks.group[#o_socks.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
if v.node_type == "normal" or v.protocol == "_balancing" or v.protocol == "_urltest" then
--Shunt node has its own separate options.
s.fields["remote_fakedns"]:depends({ node = v.id })
end
end
m:appendTemplate("/global/footer")
m:appendTemplate("/cbi/sortable", {sectiontype = s2.sectiontype})
return api.return_map(m)
@@ -1,181 +0,0 @@
local api = require "luci.passwall2.api"
local datatypes = api.datatypes
api.set_default_cbi()
local nodes_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"],
obj = e,
remarks = e["remark"],
group = e["group"]
}
end
end
m = Map()
m:appendTemplate("/cbi/nodes_value_com")
-- [[ Haproxy Settings ]]--
s = m:section(NamedSection, "@global_haproxy[0]", "global_haproxy", translate("Basic Settings"))
s:appendTemplate("/haproxy/status")
---- Balancing Enable
o = s:option(Flag, "balancing_enable", translate("Enable Load Balancing"))
o.rmempty = false
o.default = false
---- Console Login Auth
o = s:option(Flag, "console_auth", translate("Console Login Auth"))
o.default = false
o:depends("balancing_enable", true)
---- Console Username
o = s:option(Value, "console_user", translate("Console Username"))
o.default = ""
o:depends("console_auth", true)
---- Console Password
o = s:option(Value, "console_password", translate("Console Password"))
o.password = true
o.default = ""
o:depends("console_auth", true)
---- Console Port
o = s:option(Value, "console_port", translate("Console Port"), translate("In the browser input routing IP plus port access, such as:192.168.1.1:1188"))
o.datatype = "port"
o.default = 1188
o:depends("balancing_enable", true)
o = s:option(Flag, "bind_local", translate("Haproxy Port") .. " " .. translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
o.default = "0"
o:depends("balancing_enable", true)
o = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy"))
o:value("roundrobin", "roundRobin")
o:value("random", "random")
o:value("leastconn", "leastConn")
o:value("source", "source")
o.default = "roundrobin"
o:depends("balancing_enable", true)
---- Health Check Type
o = s:option(ListValue, "health_check_type", translate("Health Check Type"))
o.default = "script_logic"
o:value("tcp", "TCP")
o:value("script_logic", translate("URL Test") .. string.format("(passwall2 %s)", translate("Inner implement")))
o:depends("balancing_enable", true)
---- Passwall Inner implement Probe URL
o = s:option(Value, "health_probe_url", translate("Probe URL"))
o:value("https://cp.cloudflare.com/", "Cloudflare")
o:value("https://www.gstatic.com/generate_204", "Gstatic")
o:value("https://www.google.com/generate_204", "Google")
o:value("https://www.youtube.com/generate_204", "YouTube")
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3]
o.description = translate("The URL used to detect the connection status.")
o:depends("health_check_type", "script_logic")
---- Health Check Inter
o = s:option(Value, "health_check_inter", translate("Health Check Inter"))
o.datatype = "min(20)"
o.default = 60
o:depends("balancing_enable", true)
o.description = translate("Units:seconds") .. " ( ≥ 20s )"
o = s:option(DummyValue, "health_check_tips", " ")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<span style="color: red">%s</span>', translate("When the URL test is used, the load balancing node will be converted into a Socks node. when node list set customizing, must be a Socks node, otherwise the health check will be invalid."))
end
o:depends("health_check_type", "script_logic")
-- [[ Balancing Settings ]]--
s = m:section(TypedSection, "haproxy_config", translate("Node List"))
s.description = "<font color='red'>" ..
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") .. "<br>" ..
translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") .. "</font>"
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
s.addremove = true
s.create = function(e, t)
local uid = "haproxy_" .. api.gen_random_char(5)
TypedSection.create(e, uid)
end
s.remove = function(self, section)
for k, v in pairs(self.children) do
v.rmempty = true
v.validate = nil
end
TypedSection.remove(self, section)
end
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
---- Node Address
o = s:option(Value, "lbss", translate("Node Address"))
o.template = m:template_path("/cbi/nodes_value")
o.group = {}
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
o.rmempty = false
o.validate = function(self, value)
if not value then return nil, translate("Node address cannot be empty.") end
local t = m:get(value) or nil
if t and t[".type"] == "nodes" then
return value
end
if datatypes.hostport(value) or datatypes.ip4addrport(value) then
return value
end
if api.is_ipv6addrport(value) then
return value
end
return nil, translate("Not valid IP format, please re-enter!") .. " (IP:Port)"
end
---- Haproxy Port
o = s:option(Value, "haproxy_port", translate("Haproxy Port"))
o.datatype = "port"
o.default = 1181
o.rmempty = false
---- Node Weight
o = s:option(Value, "lbweight", translate("Node Weight"))
o.datatype = "min(1)"
o.default = 5
o.rmempty = false
---- Export
o = s:option(ListValue, "export", translate("Export Of Multi WAN"))
o:value(0, translate("Auto"))
local wa = require "luci.tools.webadmin"
wa.cbi_add_networks(o)
o.default = 0
o.rmempty = false
---- Mode
o = s:option(ListValue, "backup", translate("Mode"))
o:value(0, translate("Primary"))
o:value(1, translate("Standby"))
o.rmempty = false
m:appendTemplate("/cbi/sortable", {sectiontype = s.sectiontype})
m:appendTemplate("/haproxy/js")
return api.return_map(m)
@@ -1,258 +0,0 @@
local m, s, data = ...
if not data.node or not data.node[".name"] then
return
end
local api = m.api
local section_id = s.section
local current_node = data.node
local current_node_id = current_node[".name"]
local verify_option = data.verify_option
local node_list = data.node_list or api.get_node_list()
local shunt_section_type = "shunt_option_list"
if data.tab then
if #s.tab_names > 0 then
table.insert(s.tab_names, 2, data.tab)
s.tabs[data.tab] = {
title = data.tab_desc,
childs = { }
}
else
s:tab(data.tab, data.tab_desc)
end
end
local groups = {}
m:foreach("shunt_rules", function(s)
if s.group and s.group ~= "" then
groups[s.group] = true
end
end)
local node_save_before = luci.http.formvalue("node_save_before")
local load_shunt = luci.http.formvalue("load_shunt")
local function get_cfgvalue()
return function(self, section)
return m:get(current_node_id, self.option)
end
end
local function get_write()
return function(self, section, value)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
if verify_option then
if verify_option:formvalue(section) == current_node_id then
m:set(current_node_id, self.option, value)
end
else
m:set(current_node_id, self.option, value)
end
end
end
local function get_remove()
return function(self, section)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
if verify_option then
if verify_option:formvalue(section) == current_node_id then
m:del(current_node_id, self.option)
end
else
m:del(current_node_id, self.option)
end
end
end
local function add_option(class, option_name, option_title, option_desc)
local a
if data.tab then
a = s:taboption(data.tab, class, option_name, option_title)
else
a = s:option(class, option_name, option_title)
end
if a then
if option_desc then
a.description = option_desc
end
a.cfgvalue = get_cfgvalue()
a.write = get_write()
a.remove = get_remove()
end
if verify_option then
a:depends(verify_option.option, current_node_id)
end
return a
end
local function add_depends(o, deps)
if #o.deps > 0 then
for index, value in ipairs(o.deps) do
for k, v in pairs(deps) do
o.deps[index][k] = v
end
end
else
o:depends(deps)
end
end
if current_node.type == "Xray" then
o = add_option(ListValue, "domainStrategy", translate("Domain Strategy"))
o:value("AsIs")
o:value("IPIfNonMatch")
o:value("IPOnDemand")
o.default = "IPOnDemand"
o.description = "<br /><ul><li>" .. translate("'AsIs': Only use domain for routing. Default value.")
.. "</li><li>" .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.")
.. "</li><li>" .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.")
.. "</li></ul>"
o = add_option(ListValue, "domainMatcher", translate("Domain matcher"))
o:value("hybrid")
o:value("linear")
end
o = add_option(Flag, "fakedns", '<a style="color:#FF8C00">FakeDNS</a>' .. " " .. translate("Main switch"), translate("Use FakeDNS work in the domain that proxy.") .. "<br>" ..
translate("Suitable scenarios for let the node servers get the target domain names.") .. "<br>" ..
translate("Such as: DNS unlocking of streaming media, reducing DNS query latency, etc."))
o = add_option(Flag, "write_ipset_direct", translate("Direct DNS result write to IPSet"), translate("Perform the matching direct domain name rules into IP to IPSet/NFTSet, and then connect directly (not entering the core).") .. " " .. translate("Maybe conflict with some special circumstances."))
o.default = 1
o.rmempty = false
if api.is_finded("geoview") then
o = add_option(Flag, "enable_geoview_ip", translate("Enable GeoIP Data Parsing"))
o.default = 1
o.rmempty = false
o.description = "<ul>"
.. "<li>" .. translate("Analyzes and preloads GeoIP data to enhance the shunt performance.") .. "</li>"
.. "<li>" .. translate("Note: Increases resource usage.") .. " " .. translate("Maybe conflict with some special circumstances.") .. "</li>"
.. "</ul>"
end
shunt_group = add_option(ListValue, "shunt_group", translate("Shunt Rule Group"))
shunt_group:value("", translate("default"))
for k, v in pairs(groups) do
shunt_group:value(k)
end
local shunt_group_val = m:get(current_node_id, "shunt_group") or ""
shunt_group_val = shunt_group_val:lower()
local shunt_rules = {}
m:foreach("shunt_rules", function(e)
local group = e.group or ""
group = group:lower()
if group == shunt_group_val then
e.id = e[".name"]
e.remarks = e.remarks or e[".name"]
e["_node_option"] = e[".name"]
e["_node_default"] = ""
e["_fakedns_option"] = e[".name"] .. "_fakedns"
e["_proxy_tag_option"] = e[".name"] .. "_proxy_tag"
table.insert(shunt_rules, e)
end
end)
table.insert(shunt_rules, {
id = ".default",
remarks = translate("Default"),
_node_option = "default_node",
_node_default = "_direct",
_fakedns_option = "default_fakedns",
_proxy_tag_option = "default_proxy_tag",
})
s2 = m:section(Table, shunt_rules, " ")
s2.config = m.config
s2.sectiontype = shunt_section_type
o = s2:option(DummyValue, "remarks", translate("Rule"))
o.rawhtml = true
o.cfgvalue = function(self, section)
if shunt_rules[section].id == ".default" then
return string.format('<font style="color: red">%s</font>', shunt_rules[section].remarks)
else
return string.format('<a href="%s" target="_blank">%s</a>', api.url("shunt_rules", shunt_rules[section].id), shunt_rules[section].remarks)
end
end
_node = s2:option(Value, "_node", translate("Node"))
_node.template = m:template_path("/cbi/nodes_listvalue")
_node.group = {"","","",""}
_node:value("", translate("Close (Not use)"))
_node:value("_default", translate("Use default node"))
_node:value("_direct", translate("Direct Connection"))
_node:value("_blackhole", translate("Blackhole (Block)"))
_node.cfgvalue = function(self, section)
return m:get(current_node_id, shunt_rules[section]["_node_option"]) or shunt_rules[section]["_node_default"]
end
_node.write = function(self, section, value)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:set(current_node_id, shunt_rules[section]["_node_option"], value)
end
_node.remove = function(self, section)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:del(current_node_id, shunt_rules[section]["_node_option"])
end
o = s2:option(Flag, "_fakedns", string.format('<a style="color:#FF8C00" title="%s">FakeDNS</a>', translate("Use FakeDNS work in the domain that proxy.") .. "\n" ..
translate("Suitable scenarios for let the node servers get the target domain names.") .. "\n" ..
translate("Such as: DNS unlocking of streaming media, reducing DNS query latency, etc.")))
o.cfgvalue = function(self, section)
return m:get(current_node_id, shunt_rules[section]["_fakedns_option"])
end
o.write = function(self, section, value)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:set(current_node_id, shunt_rules[section]["_fakedns_option"], value)
end
o.remove = function(self, section)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:del(current_node_id, shunt_rules[section]["_fakedns_option"])
end
proxy_tag_node = s2:option(ListValue, "_proxy_tag", string.format('<a style="color:red" title="%s">%s</a>',
translate("Set the node to be used as a pre-proxy.") .. "\n" .. translate("Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."),
translate("Preproxy")))
proxy_tag_node.template = m:template_path("/cbi/nodes_listvalue")
proxy_tag_node.group = {""}
proxy_tag_node:value("", translate("Close (Not use)"))
proxy_tag_node.cfgvalue = function(self, section)
return m:get(current_node_id, shunt_rules[section]["_proxy_tag_option"])
end
proxy_tag_node.write = function(self, section, value)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:set(current_node_id, shunt_rules[section]["_proxy_tag_option"], value)
end
proxy_tag_node.remove = function(self, section)
if node_save_before and node_save_before ~= current_node_id then return end
if load_shunt == "1" then return end
return m:del(current_node_id, shunt_rules[section]["_proxy_tag_option"])
end
for k1, v1 in pairs(node_list) do
if k1 ~= "shunt_list" then
for i, v in ipairs(v1) do
_node:value(v.id, v.remark)
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
proxy_tag_node:value(v.id, v.remark)
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
end
m:appendTemplate("/include/shunt_options", {
node_id = current_node_id,
section_id = section_id or current_node_id,
section_type = shunt_section_type,
tab_name = data.tab,
normal_list = api.jsonc.stringify(node_list.normal_list),
shunt_list = api.jsonc.stringify(node_list.shunt_list)
})
@@ -1,87 +0,0 @@
api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map()
m.redirect = api.url("node_list")
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
formvalue_key = "cbid." .. m.config .. "." .. arg[1] .. "."
m:appendTemplate("/node_config/header", {section = arg[1]})
m:appendTemplate("/cbi/nodes_multivalue_com")
m:appendTemplate("/cbi/nodes_listvalue_com")
groups = {}
m:foreach("nodes", function(s)
if s[".name"] ~= arg[1] then
if s.group and s.group ~= "" then
groups[s.group] = true
end
end
end)
local s = m:section(NamedSection, arg[1], "nodes", translate("Node Config"))
s.addremove = false
s.dynamic = false
o = s:option(DummyValue, "passwall2", " ")
o.rawhtml = true
o.template = m:template_path("/node_config/link_share_man")
o.value = arg[1]
o = s:option(Value, "remarks", translate("Node Remarks"))
o.default = translate("Remarks")
o.rmempty = false
o = s:option(Value, "group", translate("Group Name"))
o.default = ""
o:value("", translate("default"))
for k, v in pairs(groups) do
o:value(k)
end
o.write = function(self, section, value)
value = api.trim(value)
local lower = value:lower()
if lower == "" or lower == "default" then
return m:del(section, self.option)
end
for _, v in ipairs(self.keylist or {}) do
if v:lower() == lower then
return m:set(section, self.option, v)
end
end
m:set(section, self.option, value)
end
local types_dir = "/usr/lib/lua/luci/model/cbi/" .. api.appname .. "/client/type/"
s.val = {}
s.val["type"] = m:get(arg[1], "type")
if luci.http.formvalue("cbi.submit") == "1" then
local formvalue_type = luci.http.formvalue(formvalue_key .. "type")
if formvalue_type then
s.val["type"] = formvalue_type
end
end
o = s:option(ListValue, "type", translate("Type"))
local type_table = {}
for filename in api.fs.dir(types_dir) do
table.insert(type_table, filename)
end
table.sort(type_table)
for index, value in ipairs(type_table) do
local p_func = loadfile(types_dir .. value)
setfenv(p_func, getfenv(1))(m, s)
end
m:appendTemplate("/node_config/footer", {section = arg[1]})
return api.return_map(m)
@@ -1,49 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map()
-- [[ Other Settings ]]--
s = m:section(NamedSection, "@global_other[0]", "global_other")
o = s:option(ListValue, "auto_detection_time", translate("Automatic detection delay"))
o:value("0", translate("Close"))
o:value("icmp", "Ping")
o:value("tcping", "TCP Ping")
o = s:option(Flag, "show_node_info", translate("Show server address and port"))
o.default = "0"
o = s:option(Value, "url_test_url", translate("URL Test Address"))
o:value("https://cp.cloudflare.com/", "Cloudflare")
o:value("https://www.gstatic.com/generate_204", "Gstatic")
o:value("https://www.google.com/generate_204", "Google")
o:value("https://www.youtube.com/generate_204", "YouTube")
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3]
if true then
m:appendTemplate("/node_list/node_list")
if luci.http.formvalue("cbi.submit") == "1" then
local group_order = {}
group_order = luci.http.formvaluetable("group.order")
if group_order then
for k, v in pairs(group_order) do
if v and v~= "" then
local new_order = {}
string.gsub(v, "[^" .. " " .. "]+", function(w)
new_order[#new_order + 1] = w
end)
for idx, name in ipairs(new_order) do
m.uci:reorder(m.config, name, idx - 1)
end
end
end
end
end
end
return api.return_map(m)
@@ -1,222 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
local ss_type = {}
local trojan_type = {}
local vmess_type = {}
local vless_type = {}
local hysteria2_type = {}
local xray_version = api.get_app_version("xray")
if has_ss_rust then
local s = "shadowsocks-rust"
table.insert(ss_type, s)
end
if has_singbox then
local s = "sing-box"
table.insert(trojan_type, s)
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
table.insert(hysteria2_type, s)
end
if has_xray then
local s = "xray"
table.insert(trojan_type, s)
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
if api.compare_versions(xray_version, ">=", "26.1.13") then
table.insert(hysteria2_type, s)
end
end
m = Map()
function m.on_before_save(self)
self:foreach("subscribe_list", function(e)
self:del(e[".name"], "md5")
end)
end
-- [[ Subscribe Settings ]]--
s = m:section(NamedSection, "@global_subscribe[0]", "global_subscribe")
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
o:value("2", translate("Keep List"))
o:value("3", translate("Discard List,But Keep List First"))
o:value("4", translate("Keep List,But Discard List First"))
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
if #ss_type > 0 then
o = s:option(ListValue, "ss_type", translatef("%s Node Use Type", "Shadowsocks"))
o:value("", translate("Auto"))
for key, value in pairs(ss_type) do
o:value(value)
end
end
if #trojan_type > 0 then
o = s:option(ListValue, "trojan_type", translatef("%s Node Use Type", "Trojan"))
o:value("", translate("Auto"))
for key, value in pairs(trojan_type) do
o:value(value)
end
end
if #vmess_type > 0 then
o = s:option(ListValue, "vmess_type", translatef("%s Node Use Type", "VMess"))
o:value("", translate("Auto"))
for key, value in pairs(vmess_type) do
o:value(value)
end
end
if #vless_type > 0 then
o = s:option(ListValue, "vless_type", translatef("%s Node Use Type", "VLESS"))
o:value("", translate("Auto"))
for key, value in pairs(vless_type) do
o:value(value)
end
end
if #hysteria2_type > 0 then
o = s:option(ListValue, "hysteria2_type", translatef("%s Node Use Type", "Hysteria2"))
o:value("", translate("Auto"))
for key, value in pairs(hysteria2_type) do
o:value(value)
end
end
if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 then
o.description = string.format("<font color='red'>%s</font>",
translate("The configured type also applies to the core specified when manually importing nodes."))
end
---- Subscribe Delete All
o = s:option(DummyValue, "_stop", translate("Delete All Subscribe Node"))
o.rawhtml = true
function o.cfgvalue(self, section)
return string.format(
[[<input type="button" class="btn cbi-button cbi-button-remove" onclick="return confirmDeleteAll()" value="%s" />]],
translate("Delete All Subscribe Node"))
end
o = s:option(DummyValue, "_update", translate("Manual subscription All"))
o.rawhtml = true
o.cfgvalue = function(self, section)
return string.format([[
<input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribeAll()" value="%s" />]],
translate("Manual subscription All"))
end
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
s.addremove = true
s.anonymous = true
s.sortable = true
s.template = "cbi/tblsection"
s.extedit = api.url("node_subscribe_config", "%s")
function s.create(e, t)
local uid = "sub_" .. api.gen_random_char(5)
TypedSection.create(e, uid)
m:set(uid, "hysteria_up_mbps", "100")
m:set(uid, "hysteria_down_mbps", "100")
luci.http.redirect(e.extedit:format(uid))
end
o = s:option(Value, "remark", translate("Remarks"))
o.width = "auto"
o.rmempty = false
o.validate = function(self, value, section)
value = api.trim(value)
if value == "" then
return nil, translate("Remark cannot be empty.")
end
local duplicate = false
m:foreach(s.sectiontype, function(e)
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
duplicate = true
return false
end
end)
if duplicate or value:lower() == "default" then
return nil, translate("This remark already exists, please change a new remark.")
end
return value
end
o.write = function(self, section, value)
local old = m:get(section, self.option) or ""
if old ~= value then
m:foreach("nodes", function(e)
if e["group"] and e["group"]:lower() == old:lower() then
m:set(e[".name"], "group", value)
end
if e["protocol"] and (e["protocol"] == "_balancing" or e["protocol"] == "_urltest") and e["node_group"] then
local gs = ""
for g in e["node_group"]:gmatch("%S+") do
if api.UrlEncode(old) == g then
gs = gs .. " " .. api.UrlEncode(value)
else
gs = gs .. " " .. g
end
end
gs = api.trim(gs)
m:set(e[".name"], "node_group", gs)
end
end)
end
return Value.write(self, section, value)
end
o = s:option(DummyValue, "_node_count", translate("Subscribe Info"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local remark = m:get(n, "remark") or ""
local str = m:get(n, "rem_traffic") or ""
local expired_date = m:get(n, "expired_date") or ""
if expired_date ~= "" then
str = str .. (str ~= "" and "/" or "") .. expired_date
end
str = str ~= "" and "<br>" .. str or ""
local num = 0
m:foreach("nodes", function(s)
if s["group"] and s["group"]:lower() == remark:lower() then
num = num + 1
end
end)
return string.format("%s%s", translate("Node num") .. ": " .. num, str)
end
o = s:option(Value, "url", translate("Subscribe URL"))
o.width = "auto"
o.rmempty = false
o = s:option(DummyValue, "_remove", translate("Delete the subscribed node"))
o.rawhtml = true
function o.cfgvalue(self, section)
local remark = m:get(section, "remark") or ""
return string.format(
[[<input type="button" class="btn cbi-button cbi-button-remove" onclick="return confirmDeleteNode('%s')" value="%s" />]],
remark, translate("Delete the subscribed node"))
end
o = s:option(DummyValue, "_update", translate("Manual subscription"))
o.rawhtml = true
o.cfgvalue = function(self, section)
return string.format([[
<input type="button" class="btn cbi-button cbi-button-apply" onclick="ManualSubscribe('%s')" value="%s" />]],
section, translate("Manual subscription"))
end
m:appendTemplate("/cbi/sortable", {sectiontype = s.sectiontype})
m:appendTemplate("/node_subscribe/js")
return api.return_map(m)
@@ -1,326 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map()
m.redirect = api.url("node_subscribe")
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
function m.on_before_save(self)
self:del(arg[1], "md5")
end
m:appendTemplate("/cbi/nodes_listvalue_com")
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
local ss_type = {}
local trojan_type = {}
local vmess_type = {}
local vless_type = {}
local hysteria2_type = {}
local xray_version = api.get_app_version("xray")
if has_ss_rust then
local s = "shadowsocks-rust"
table.insert(ss_type, s)
end
if has_singbox then
local s = "sing-box"
table.insert(trojan_type, s)
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
table.insert(hysteria2_type, s)
end
if has_xray then
local s = "xray"
table.insert(trojan_type, s)
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
if api.compare_versions(xray_version, ">=", "26.1.13") then
table.insert(hysteria2_type, s)
end
end
local nodes_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"],
remark = e["remark"],
type = e["type"],
add_mode = e["add_mode"],
chain_proxy = e["chain_proxy"],
group = e["group"]
}
end
end
s = m:section(NamedSection, arg[1])
s.addremove = false
s.dynamic = false
o = s:option(Value, "remark", translate("Subscribe Remark"))
o.rmempty = false
o.validate = function(self, value, section)
value = api.trim(value)
if value == "" then
return nil, translate("Remark cannot be empty.")
end
local duplicate = false
m:foreach("subscribe_list", function(e)
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
duplicate = true
return false
end
end)
if duplicate or value:lower() == "default" then
return nil, translate("This remark already exists, please change a new remark.")
end
return value
end
o.write = function(self, section, value)
local old = m:get(section, self.option) or ""
if old ~= value then
m:foreach("nodes", function(e)
if e["group"] and e["group"]:lower() == old:lower() then
self.map:set(e[".name"], "group", value)
end
if e["protocol"] and (e["protocol"] == "_balancing" or e["protocol"] == "_urltest") and e["node_group"] then
local gs = ""
for g in e["node_group"]:gmatch("%S+") do
if api.UrlEncode(old) == g then
gs = gs .. " " .. api.UrlEncode(value)
else
gs = gs .. " " .. g
end
end
gs = api.trim(gs)
self.map:set(e[".name"], "node_group", gs)
end
end)
end
return Value.write(self, section, value)
end
o = s:option(TextValue, "url", translate("Subscribe URL"))
o.rows = 5
o.rmempty = false
o.validate = function(self, value)
if not value or value == "" then
return nil, translate("URL cannot be empty.")
end
return value:gsub("%s+", ""):gsub("%z", "")
end
o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"))
o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "<br>" ..
translate("Supports only Xray or Sing-box node types.")
o:value("", translate("Auto"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("https", "DoH")
o = s:option(Value, "domain_resolver_dns", "DNS")
o.datatype = "or(ipaddr,ipaddrport)"
o:value("114.114.114.114")
o:value("223.5.5.5:53")
o.default = o.keylist[1]
o:depends({ domain_resolver = "tcp" })
o:depends({ domain_resolver = "udp" })
o = s:option(Value, "domain_resolver_dns_https", "DNS")
o:value("https://120.53.53.53/dns-query", "DNSPod")
o:value("https://223.5.5.5/dns-query", "AliDNS")
o.default = o.keylist[1]
o:depends({ domain_resolver = "https" })
o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
o.default = ""
o:value("", translate("Auto"))
o:value("UseIPv4", translate("IPv4 Only"))
o:value("UseIPv6", translate("IPv6 Only"))
o = s:option(Flag, "allowInsecure", translate("allowInsecure"))
o.default = "0"
o.rmempty = false
o.description = translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.") .. "<br>" ..
translate("Used when the node link does not include this parameter.")
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o.default = "5"
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
o:value("2", translate("Keep List"))
o:value("3", translate("Discard List,But Keep List First"))
o:value("4", translate("Keep List,But Discard List First"))
o:value("5", translate("Use global config"))
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
o:depends("filter_keyword_mode", "1")
o:depends("filter_keyword_mode", "3")
o:depends("filter_keyword_mode", "4")
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
o:depends("filter_keyword_mode", "2")
o:depends("filter_keyword_mode", "3")
o:depends("filter_keyword_mode", "4")
if #ss_type > 0 then
o = s:option(ListValue, "ss_type", translatef("%s Node Use Type", "Shadowsocks"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(ss_type) do
o:value(value)
end
end
if #trojan_type > 0 then
o = s:option(ListValue, "trojan_type", translatef("%s Node Use Type", "Trojan"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(trojan_type) do
o:value(value)
end
end
if #vmess_type > 0 then
o = s:option(ListValue, "vmess_type", translatef("%s Node Use Type", "VMess"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(vmess_type) do
o:value(value)
end
end
if #vless_type > 0 then
o = s:option(ListValue, "vless_type", translatef("%s Node Use Type", "VLESS"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(vless_type) do
o:value(value)
end
end
if #hysteria2_type > 0 then
o = s:option(ListValue, "hysteria2_type", translatef("%s Node Use Type", "Hysteria2"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(hysteria2_type) do
o:value(value)
end
o = s:option(Value, "hysteria_up_mbps", "Hy/Hy2 " .. translate("Max upload Mbps"))
o.datatype = "uinteger"
o = s:option(Value, "hysteria_down_mbps", "Hy/Hy2 " .. translate("Max download Mbps"))
o.datatype = "uinteger"
end
o = s:option(Flag, "boot_update", translate("Update Once on Boot"), translate("Updates the subscription the first time runs automatically after each system boot."))
o.default = 0
o = s:option(ListValue, "update_week_mode", translate("Auto Update Mode"))
o:value("", translate("Disable"))
o:value(8, translate("Loop Mode"))
o:value(7, translate("Every day"))
o:value(1, translate("Every Monday"))
o:value(2, translate("Every Tuesday"))
o:value(3, translate("Every Wednesday"))
o:value(4, translate("Every Thursday"))
o:value(5, translate("Every Friday"))
o:value(6, translate("Every Saturday"))
o:value(0, translate("Every Sunday"))
o = s:option(Value, "update_time_mode", translate("Update Time"))
o:value("0:00")
for t = 0, 23 do
if t == 12 then
o:value(t .. ":30")
elseif t == 23 then
o:value(t .. ":59")
else
o:value(t .. ":00")
end
end
o.default = "0:00"
o.datatype = "timehhmm"
o:depends("update_week_mode", "0")
o:depends("update_week_mode", "1")
o:depends("update_week_mode", "2")
o:depends("update_week_mode", "3")
o:depends("update_week_mode", "4")
o:depends("update_week_mode", "5")
o:depends("update_week_mode", "6")
o:depends("update_week_mode", "7")
o = s:option(ListValue, "update_interval_mode", translate("Update Interval(hour)"))
for t = 1, 24 do o:value(t, t .. " " .. translate("hour")) end
o.default = 2
o:depends("update_week_mode", "8")
o.rmempty = true
o = s:option(ListValue, "access_mode", translate("Subscribe URL Access Method"))
o.default = ""
o:value("", translate("Auto"))
o:value("direct", translate("Direct Connection"))
o:value("proxy", translate("Proxy"))
o = s:option(Value, "user_agent", translate("User-Agent"))
o.default = "v2rayN/9.99"
o:value("passwall2", "PassWall2")
o:value("v2rayN/9.99", "v2rayN")
o:value("clash.meta/v9.99.99", "Clash.Meta")
o:value("Shadowrocket/9999 CFNetwork/3896.100.1.2.1 Darwin/27.0.0 iPhone19,7", "Shadowrocket")
o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux")
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows")
o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
o:value("", translate("Close(Not use)"))
o:value("1", translate("Preproxy Node"))
o:value("2", translate("Landing Node"))
o:value("3", translate("Outbound Interface"))
local descrStr = "Chained proxy works only with Xray or Sing-box nodes.<br>"
descrStr = descrStr .. "You can only use manual or imported nodes as chained nodes."
descrStr = translate(descrStr) .. "<br>" .. translate("Only support a layer of proxy.")
o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"))
o1:depends({ ["chain_proxy"] = "1" })
o1.description = descrStr
o1.template = m:template_path("/cbi/nodes_listvalue")
o1.group = {}
o2 = s:option(ListValue, "to_node", translate("Landing Node"))
o2:depends({ ["chain_proxy"] = "2" })
o2.description = descrStr
o2.template = m:template_path("/cbi/nodes_listvalue")
o2.group = {}
o3 = s:option(Value, "outbound_iface", translate("Outbound Interface"))
o3:depends({ ["chain_proxy"] = "3" })
o3:value("", translate("All"))
local iface = api.get_network_devices()
for _, d in ipairs(iface) do
o3:value(d.name, d.label)
end
for k, v in pairs(nodes_table) do
if (v.type == "Xray" or v.type == "sing-box") and (not v.chain_proxy or v.chain_proxy == "") and v.add_mode ~= "2" then
o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
o2:value(v.id, v.remark)
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
o = s:option(Flag, "hwid", translate("Send HWID"))
o.default = 0
o.description = translate("Some subscription providers may require a hardware identifier (HWID) for authentication.") .. "<br>" ..
translate("Enabling this option will send a hashed HWID when updating subscriptions.") .. "<br>" ..
translate("This may reveal limited hardware information. Enable only if you trust your provider.")
return api.return_map(m)
@@ -1,303 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
local fs = api.fs
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
local port_validate = function(self, value, t)
return value:gsub("-", ":")
end
m = Map()
-- [[ Delay Settings ]]--
s = m:section(NamedSection, "@global_delay[0]", "global_delay", translate("Delay Settings"))
---- Open and close Daemon
o = s:option(Flag, "start_daemon", translate("Open and close Daemon"))
o.default = 1
o.rmempty = false
---- Delay Start
o = s:option(Value, "start_delay", translate("Delay Start"), translate("Units:seconds"))
o.default = "1"
o.rmempty = true
for index, value in ipairs({"stop", "start", "restart"}) do
o = s:option(ListValue, value .. "_week_mode", translate(value .. " automatically mode"))
o:value("", translate("Disable"))
if value == "restart" then
o:value(8, translate("Loop Mode"))
end
o:value(7, translate("Every day"))
o:value(1, translate("Every Monday"))
o:value(2, translate("Every Tuesday"))
o:value(3, translate("Every Wednesday"))
o:value(4, translate("Every Thursday"))
o:value(5, translate("Every Friday"))
o:value(6, translate("Every Saturday"))
o:value(0, translate("Every Sunday"))
o = s:option(Value, value .. "_time_mode", translate(value .. " Time"))
o:value("0:00")
for t = 0, 23 do
if t == 12 then
o:value(t .. ":30")
elseif t == 23 then
o:value(t .. ":59")
else
o:value(t .. ":00")
end
end
o.default = "0:00"
o.datatype = "timehhmm"
o:depends(value .. "_week_mode", "0")
o:depends(value .. "_week_mode", "1")
o:depends(value .. "_week_mode", "2")
o:depends(value .. "_week_mode", "3")
o:depends(value .. "_week_mode", "4")
o:depends(value .. "_week_mode", "5")
o:depends(value .. "_week_mode", "6")
o:depends(value .. "_week_mode", "7")
o = s:option(ListValue, value .. "_interval_mode", translate(value .. " Interval(Hour)"))
for t = 1, 24 do o:value(t, t .. " " .. translate("Hour")) end
o.default = 2
o:depends(value .. "_week_mode", "8")
end
-- [[ Forwarding Settings ]]--
s = m:section(NamedSection, "@global_forwarding[0]", "global_forwarding", translate("Forwarding Settings"))
---- TCP No Redir Ports
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
o:value("", translate("No patterns are used"))
o:value("1:65535", translate("All"))
o.validate = port_validate
---- UDP No Redir Ports
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"),
"<font color='red'>" ..
translate("Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") ..
"</font>")
o:value("", translate("No patterns are used"))
o:value("1:65535", translate("All"))
o.validate = port_validate
---- TCP Redir Ports
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
o:value("1:65535", translate("All"))
o:value("22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418", translate("Common Use"))
o:value("80,443", translate("Only Web"))
o.default = o.keylist[1]
o.validate = port_validate
---- UDP Redir Ports
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
o:value("1:65535", translate("All"))
o.default = o.keylist[1]
o.validate = port_validate
o = s:option(DummyValue, "tips", " ")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<font color="red">%s</font>',
translate("The port settings support single ports and ranges.<br>Separate multiple ports with commas (,).<br>Example: 21,80,443,1000:2000."))
end
---- Use nftables
o = s:option(ListValue, "prefer_nft", translate("Prefer firewall tools"))
o.default = "1"
o:value("0", "Iptables")
o:value("1", "Nftables")
---- Check the transparent proxy component
local handle = io.popen("lsmod")
local mods = ""
if handle then
mods = handle:read("*a") or ""
handle:close()
end
if (mods:find("REDIRECT") and mods:find("TPROXY")) or (mods:find("nft_redir") and mods:find("nft_tproxy")) then
o = s:option(ListValue, "tcp_proxy_way", translate("TCP Proxy Way"))
o.default = "redirect"
o:value("redirect", "REDIRECT")
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", false)
o.remove = function(self, section)
-- Do not delete while hidden
end
o = s:option(ListValue, "_tcp_proxy_way", translate("TCP Proxy Way"))
o.default = "tproxy"
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", true)
o.write = function(self, section, value)
self.map:set(section, "tcp_proxy_way", value)
end
if mods:find("ip6table_mangle") or mods:find("nft_tproxy") then
---- IPv6 TProxy
o = s:option(Flag, "ipv6_tproxy", translate("IPv6 TProxy"),
"<font color='red'>" ..
translate("Experimental feature. Make sure that your node supports IPv6.") ..
"</font>")
o.default = 0
o.rmempty = false
end
end
o = s:option(Flag, "accept_icmp", translate("Hijacking ICMP (PING)"))
o.default = 0
o = s:option(Flag, "accept_icmpv6", translate("Hijacking ICMPv6 (IPv6 PING)"))
o:depends("ipv6_tproxy", true)
o.default = 0
function clean_text(text)
local nbsp = string.char(0xC2, 0xA0)
local fullwidth_space = string.char(0xE3, 0x80, 0x80)
return text
:gsub("\t", " ")
:gsub(nbsp, " ")
:gsub(fullwidth_space, " ")
:gsub("^%s+", "")
:gsub("%s+$", "\n")
:gsub("\r\n", "\n")
:gsub("[ \t]*\n[ \t]*", "\n")
end
local direct_ip_file = "/usr/share/passwall2/direct_ip"
o = s:option(TextValue, "direct_ip", translate("Direct IP List"), "<font color='red'>" .. translate("These had been joined ip addresses will connect directly (not entering the core).") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(direct_ip_file) or ""
end
o.write = function(self, section, value)
fs.writefile(direct_ip_file, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(direct_ip_file, "")
end
o.validate = function(self, value)
local ipmasks= {}
value = clean_text(value)
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, api.trim(w)) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask ~= "" and not ipmask:find("^#") and not ipmask:find("^geoip:") then
if not ( datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask) ) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
end
return value
end
if has_xray then
s_xray = m:section(NamedSection, "@global_xray[0]", "global_xray", "Xray " .. translate("Settings"))
o = s_xray:option(Flag, "fragment", translate("Fragment"), translate("TCP fragments, which can deceive the censorship system in some cases, such as bypassing SNI blacklists."))
o.default = 0
o = s_xray:option(ListValue, "fragment_packets", translate("Fragment Packets"), translate(" \"1-3\" is for segmentation at TCP layer, applying to the beginning 1 to 3 data writes by the client. \"tlshello\" is for TLS client hello packet fragmentation."))
o.default = "tlshello"
o:value("tlshello", "tlshello")
o:value("1-1", "1-1")
o:value("1-2", "1-2")
o:value("1-3", "1-3")
o:value("1-5", "1-5")
o:depends("fragment", true)
o = s_xray:option(Value, "fragment_lengths", translate("Fragment Length"), translate("Fragmented packet length (byte)"))
o.default = "3-5,6-8,10-20"
o:depends("fragment", true)
o = s_xray:option(Value, "fragment_delays", translate("Fragment Delay"), translate("Fragmentation interval (ms)"))
o.default = "10-20"
o:depends("fragment", true)
o = s_xray:option(Value, "fragment_maxSplit", translate("Max Split"), translate("Limit the maximum number of splits."))
o.datatype = "or(uinteger,portrange)"
o.default = "3-6"
o:depends("fragment", true)
o = s_xray:option(Flag, "noise", translate("Noise"), translate("UDP noise, Under some circumstances it can bypass some UDP based protocol restrictions."))
o.default = 0
o = s_xray:option(Flag, "sniffing_override_dest", translate("Override the connection destination address"))
o.default = 0
o.description = translate("Override the connection destination address with the sniffed domain.<br />Otherwise use sniffed domain for routing only.<br />If using shunt nodes, configure the domain shunt rules correctly.")
o = s_xray:option(Flag, "route_only", translate("Sniffing Route Only"))
o.default = 0
o:depends("sniffing", true)
local domains_excluded = string.format("/usr/share/%s/domains_excluded", m.config)
o = s_xray:option(TextValue, "excluded_domains", translate("Excluded Domains"), translate("If the traffic sniffing result is in this list, the destination address will not be overridden."))
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section) return fs.readfile(domains_excluded) or "" end
o.write = function(self, section, value) fs.writefile(domains_excluded, value:gsub("\r\n", "\n")) end
o:depends({sniffing_override_dest = true})
o = s_xray:option(Value, "buffer_size", translate("Buffer Size"), translate("Buffer size for every connection (kB)"))
o.datatype = "uinteger"
s_xray_noise = m:section(TypedSection, "xray_noise_packets", translate("Xray Noise Packets"),"<font color='red'>" .. translate("To send noise packets, select \"Noise\" in Xray Settings.") .. "</font>")
s_xray_noise.template = "cbi/tblsection"
s_xray_noise.sortable = true
s_xray_noise.anonymous = true
s_xray_noise.addremove = true
s_xray_noise.create = function(e, t)
local uid = "xray_noise_" .. api.gen_random_char(5)
TypedSection.create(e, uid)
end
s_xray_noise.remove = function(self, section)
local o = m:get(section) or {}
if o[".type"] == self.sectiontype then
for k, v in pairs(self.children) do
v.rmempty = true
v.validate = nil
end
TypedSection.remove(self, section)
end
end
o = s_xray_noise:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
o = s_xray_noise:option(ListValue, "type", translate("Type"))
o:value("rand", "rand")
o:value("array", "array")
o:value("str", "str")
o:value("hex", "hex")
o:value("base64", "base64")
o = s_xray_noise:option(Value, "packet", translate("Packet | Rand Length"))
o.datatype = "minlength(1)"
o.rmempty = false
o = s_xray_noise:option(Value, "delay", translate("Delay (ms)"))
o.datatype = "or(uinteger,portrange)"
o.rmempty = false
end
if has_singbox then
s = m:section(NamedSection, "@global_singbox[0]", "global_singbox", "Sing-Box " .. translate("Settings"))
o = s:option(Flag, "record_fragment", "TLS Record " .. translate("Fragment"),
translate("Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first."))
o.default = 0
o = s:option(Flag, "fragment", "TLS TCP " .. translate("Fragment"),
translate("Split handshake into multiple TCP segments. Enhances obfuscation. May increase delay. Use only if needed."))
o.default = 0
end
return api.return_map(m)
@@ -1,108 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map()
-- [[ Rule Settings ]]--
s = m:section(NamedSection, "@global_rules[0]", "global_rules", translate("Rule status"))
o = s:option(Value, "geoip_url", translate("GeoIP Update URL"))
o:value("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", translate("Loyalsoldier/geoip"))
o:value("https://gh-proxy.org/https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", translate("Loyalsoldier/geoip (gh-proxy)"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat", translate("Loyalsoldier/geoip (CDN)"))
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.dat", translate("MetaCubeX/geoip"))
o:value("https://gh-proxy.org/https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.dat", translate("MetaCubeX/geoip (gh-proxy)"))
o:value("https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", translate("MetaCubeX/geoip (CDN)"))
o:value("https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/geoip.dat", translate("Chocolate4U/geoip (IR)"))
o:value("https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", translate("runetfreedom/geoip (RU)"))
o.default = o.keylist[1]
o = s:option(Value, "geosite_url", translate("Geosite Update URL"))
o:value("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", translate("Loyalsoldier/geosite"))
o:value("https://gh-proxy.org/https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", translate("Loyalsoldier/geosite (gh-proxy)"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat", translate("Loyalsoldier/geosite (CDN)"))
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geosite.dat", translate("MetaCubeX/geosite"))
o:value("https://gh-proxy.org/https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geosite.dat", translate("MetaCubeX/geosite (gh-proxy)"))
o:value("https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat", translate("MetaCubeX/geosite (CDN)"))
o:value("https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/geosite.dat", translate("Chocolate4U/geosite (IR)"))
o:value("https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", translate("runetfreedom/geosite (RU)"))
o.default = o.keylist[1]
o = s:option(Value, "v2ray_location_asset", translate("Location of Geo rule files"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))
o.default = "/usr/share/v2ray/"
o.placeholder = o.default
o.rmempty = false
o = s:option(ListValue, "update_week_mode", translate("Auto Update Mode"))
o:value("", translate("Disable"))
o:value(8, translate("Loop Mode"))
o:value(7, translate("Every day"))
o:value(1, translate("Every Monday"))
o:value(2, translate("Every Tuesday"))
o:value(3, translate("Every Wednesday"))
o:value(4, translate("Every Thursday"))
o:value(5, translate("Every Friday"))
o:value(6, translate("Every Saturday"))
o:value(0, translate("Every Sunday"))
o = s:option(Value, "update_time_mode", translate("Update Time"))
o:value("0:00")
for t = 0, 23 do
if t == 12 then
o:value(t .. ":30")
elseif t == 23 then
o:value(t .. ":59")
else
o:value(t .. ":00")
end
end
o.default = "0:00"
o.datatype = "timehhmm"
o:depends("update_week_mode", "0")
o:depends("update_week_mode", "1")
o:depends("update_week_mode", "2")
o:depends("update_week_mode", "3")
o:depends("update_week_mode", "4")
o:depends("update_week_mode", "5")
o:depends("update_week_mode", "6")
o:depends("update_week_mode", "7")
o = s:option(ListValue, "update_interval_mode", translate("Update Interval(hour)"))
for t = 1, 24 do o:value(t, t .. " " .. translate("hour")) end
o.default = 2
o:depends("update_week_mode", "8")
o.rmempty = true
--- The update option is always hidden by JavaScript.
local flags = {
"geoip_update", "geosite_update"
}
for _, f in ipairs(flags) do
o = s:option(Flag, f)
o.rmempty = false
end
s:appendTemplate("/rule/rule_version")
if true then
m:appendTemplate("/rule/shunt_rule_list")
if luci.http.formvalue("cbi.submit") == "1" then
local group_order = luci.http.formvaluetable("group.order")
if group_order then
for k, v in pairs(group_order) do
if v and v~= "" then
local new_order = {}
string.gsub(v, "[^" .. " " .. "]+", function(w)
new_order[#new_order + 1] = w
end)
for idx, name in ipairs(new_order) do
m.uci:reorder(m.config, name, idx - 1)
end
end
end
end
end
end
return api.return_map(m)
@@ -1,267 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
local datatypes = api.datatypes
m = Map()
m.redirect = api.url("rule")
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
function m.on_before_save(self)
self:set("@global[0]", "flush_set", "1")
end
-- Add inline CSS to map description
m.description = (m.description or "") .. [[
<style>
/* Scope to passwall2 CBI fields so it won't leak elsewhere */
div[id^="cbid.passwall2."] .cbi-value-field {
display: flex;
flex-wrap: wrap;
gap: 1em;
}
div[id^="cbid.passwall2."] .cbi-checkbox {
display: inline-flex;
align-items: center;
}
</style>
]]
function clean_text(text)
local nbsp = string.char(0xC2, 0xA0) -- Non-breaking space (U+00A0)
local fullwidth_space = string.char(0xE3, 0x80, 0x80) -- Full-width space (U+3000)
return text
:gsub("\t", " ")
:gsub(nbsp, " ")
:gsub(fullwidth_space, " ")
:gsub("^%s+", "")
:gsub("%s+$", "\n")
:gsub("\r\n", "\n")
:gsub("[ \t]*\n[ \t]*", "\n")
end
local remarks_lookup = {}
local groups = {}
m:foreach("shunt_rules", function(s)
if s[".name"] ~= arg[1] then
if s.remarks then
remarks_lookup[s.remarks] = s[".name"]
end
if s.group and s.group ~= "" then
groups[s.group] = true
end
end
end)
s = m:section(NamedSection, arg[1], "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"))
s.addremove = false
s.dynamic = false
remarks = s:option(Value, "remarks", translate("Remarks"))
remarks.default = arg[1]
remarks.rmempty = false
remarks.validate = function(self, value, section)
value = api.trim(value)
if value == "" then
return nil, translate("Remark cannot be empty.")
end
if remarks_lookup[value] then
return nil, translate("This remark already exists, please change a new remark.")
end
return value
end
o = s:option(Value, "group", translate("Shunt Rule Group"))
o.default = ""
o:value("", translate("default"))
for k, v in pairs(groups) do
o:value(k)
end
o.write = function(self, section, value)
value = api.trim(value)
local lower = value:lower()
if lower == "" or lower == "default" then
return m:del(section, self.option)
end
for _, v in ipairs(self.keylist or {}) do
if v:lower() == lower then
return m:set(section, self.option, v)
end
end
m:set(section, self.option, value)
end
protocol = s:option(MultiValue, "protocol", translate("Protocol"))
protocol:value("http")
protocol:value("tls")
protocol:value("quic")
protocol:value("bittorrent")
protocol.widget = "checkbox"
protocol.default = nil
protocol.optional = false
o = s:option(MultiValue, "inbound", translate("Inbound Tag"))
o:value("tproxy", translate("Transparent proxy"))
o:value("socks", "Socks")
o.widget = "checkbox"
o.default = nil
o.optional = false
network = s:option(ListValue, "network", translate("Network"))
network:value("tcp,udp", "TCP UDP")
network:value("tcp", "TCP")
network:value("udp", "UDP")
source = s:option(DynamicList, "source", translate("Source"))
source.description = "<ul><li>" .. translate("Example:")
.. "</li><li>" .. translate("IP") .. ": 192.168.1.100"
.. "</li><li>" .. translate("IP CIDR") .. ": 192.168.1.0/24"
.. "</li><li>" .. translate("GeoIP") .. ": geoip:private"
.. "</li></ul>"
source.cast = "string"
source.cfgvalue = function(self, section)
local value
if self.tag_error[section] then
value = self:formvalue(section)
else
value = self.map:get(section, self.option)
if type(value) == "string" then
local value2 = {}
string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end)
value = value2
end
end
return value
end
source.validate = function(self, value, t)
local err = {}
for _, v in ipairs(value) do
local flag = false
if datatypes.ip4addr(v) then
flag = true
end
if flag == false and v:find("geoip:") and v:find("geoip:") == 1 then
flag = true
end
if flag == false then
err[#err + 1] = v
end
end
if #err > 0 then
self:add_error(t, "invalid", translate("Not true format, please re-enter!"))
for _, v in ipairs(err) do
self:add_error(t, "invalid", v)
end
end
return value
end
--[[
-- Too low usage rate, hidden
sourcePort = s:option(Value, "sourcePort", translate("Source port"))
]]--
port = s:option(Value, "port", translate("Port"))
domain_list = s:option(TextValue, "domain_list", translate("Domain"))
domain_list.rows = 10
domain_list.wrap = "off"
domain_list.validate = function(self, value)
local hosts= {}
value = clean_text(value)
string.gsub(value, "[^\r\n]+", function(w) table.insert(hosts, w) end)
for index, host in ipairs(hosts) do
local flag = 1
local tmp_host = host
if not host:find("#") and host:find("%s") then
elseif host:find("regexp:") and host:find("regexp:") == 1 then
flag = 0
elseif host:find("domain:.") and host:find("domain:.") == 1 then
tmp_host = host:gsub("domain:", "")
elseif host:find("full:.") and host:find("full:.") == 1 then
tmp_host = host:gsub("full:", "")
elseif host:find("geosite:") and host:find("geosite:") == 1 then
flag = 0
-- elseif host:find("ext:") and host:find("ext:") == 1 then
-- flag = 0
elseif host:find("rule-set:", 1, true) == 1 or host:find("rs:") == 1 then
local w = host:sub(host:find(":") + 1, #host)
if w:find("local:") == 1 or w:find("remote:") == 1 then
flag = 0
end
elseif host:find("#") and host:find("#") == 1 then
flag = 0
end
if flag == 1 then
if not datatypes.hostname(tmp_host) then
return nil, tmp_host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
end
return value
end
domain_list.description = "<br /><ul>"
.. "<li>" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.") .. "</li>"
.. "<li>" .. translate("Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.") .. "</li>"
.. "<li>" .. translate("Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.") .. "</li>"
.. "<li>" .. translate("Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.") .. "</li>"
.. "<li>" .. translate("Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.") .. "</li>"
.. "<li>"
.. translate("Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'")
.. "<ul>"
.. "<li>" .. translate("Such as:") .. "'rule-set:remote:https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs'" .. "</li>"
.. "<li>" .. translate("Such as:") .. "'rule-set:local:/usr/share/sing-box/geosite-cn.srs'" .. "</li>"
.. "</ul>"
.. "</li>"
.. "<li>" .. translate("Annotation: Begining with #") .. "</li>"
.. "</ul>"
ip_list = s:option(TextValue, "ip_list", "IP")
ip_list.rows = 10
ip_list.wrap = "off"
ip_list.validate = function(self, value)
local ipmasks= {}
value = clean_text(value)
string.gsub(value, "[^\r\n]+", function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask:find("geoip:") and ipmask:find("geoip:") == 1 and not ipmask:find("%s") then
-- elseif ipmask:find("ext:") and ipmask:find("ext:") == 1 and not ipmask:find("%s") then
elseif ipmask:find("rule-set:", 1, true) == 1 or ipmask:find("rs:") == 1 then
local w = ipmask:sub(ipmask:find(":") + 1, #ipmask)
if w:find("local:") == 1 or w:find("remote:") == 1 then
flag = 0
end
elseif ipmask:find("#") and ipmask:find("#") == 1 then
else
if not (datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask)) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
end
return value
end
ip_list.description = "<br /><ul>"
.. "<li>" .. translate("IP: such as '127.0.0.1'.") .. "</li>"
.. "<li>" .. translate("CIDR: such as '127.0.0.0/8'.") .. "</li>"
.. "<li>" .. translate("GeoIP: such as 'geoip:cn'. It begins with geoip: (lower case) and followed by two letter of country code.") .. "</li>"
.. "<li>"
.. translate("Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'")
.. "<ul>"
.. "<li>" .. translate("Such as:") .. "'rule-set:remote:https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs'" .. "</li>"
.. "<li>" .. translate("Such as:") .. "'rule-set:local:/usr/share/sing-box/geoip-cn.srs'" .. "</li>"
.. "</ul>"
.. "</li>"
.. "<li>" .. translate("Annotation: Begining with #") .. "</li>"
.. "</ul>"
o = s:option(Flag, "invert", "invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box."))
return api.return_map(m)
@@ -1,182 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map()
m.redirect = api.url()
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
m:appendTemplate("/cbi/nodes_dynamiclist_com")
m:appendTemplate("/cbi/nodes_listvalue_com")
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
s = m:section(NamedSection, arg[1], translate("Socks Config"), translate("Socks Config"))
s.addremove = false
s.dynamic = false
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
local auto_switch_tip
local current_node = api.get_cache_var(arg[1])
if current_node then
local n = m:get(current_node)
if n then
if tonumber(m:get(arg[1], "enable_autoswitch") or 0) == 1 then
if n then
local remarks = api.get_node_remarks(n)
local url = api.url("node_config", n[".name"])
auto_switch_tip = translatef("Current node: %s", string.format('<a href="%s">%s</a>', url, remarks)) .. "<br />"
end
end
end
end
socks_node = s:option(ListValue, "node", translate("Node"))
if auto_switch_tip then
socks_node.description = auto_switch_tip
end
socks_node.template = m:template_path("/cbi/nodes_listvalue")
socks_node.group = {}
o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
o.default = "0"
local n = 1
m:foreach("socks", function(s)
if s[".name"] == section then
return false
end
n = n + 1
end)
o = s:option(Value, "port", "Socks " .. translate("Listen Port"))
o.default = n + 1080
o.datatype = "port"
o.rmempty = false
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"
end
o = s:option(Flag, "log", translate("Enable") .. " " .. translate("Log"))
o.default = 1
o.rmempty = false
o = s:option(DummyValue, "_log", translate("Log File"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local log_path = api.TMP_PATH .. "/" .. arg[1] .. ".log"
local log_url = api.url("get_socks_log") .. "?name=" .. arg[1]
return string.format(
'<code>%s</code>&nbsp;&nbsp;<input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
log_path,
translate("View Log"),
log_url
)
end
o:depends("log", true)
o = s:option(Flag, "enable_autoswitch", translate("Auto Switch"))
o.default = 0
o.rmempty = false
o = s:option(ListValue, "backup_node_add_mode", translate("Backup Node Addition Method"))
o:depends("enable_autoswitch", true)
o.default = "manual"
o:value("manual", translate("Manual"))
o:value("batch", translate("Batch"))
o = s:option(DynamicList, "autoswitch_backup_node", translate("List of backup nodes"))
o:depends("backup_node_add_mode", "manual")
o.template = m:template_path("/cbi/nodes_dynamiclist")
o.group = {}
o.write = function(self, section, value)
local n = s.fields["node"]:formvalue(section)
local v = type(value) == "table" and value or { value }
local t = {}
for _, x in ipairs(v) do
if x and x ~= n then t[#t+1] = x end
end
return DynamicList.write(self, section, t)
end
for i, v in pairs(nodes_table) do
if v.protocol ~= "_shunt" then
o:value(v.id, v.remark)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
s.fields["enable_autoswitch"]:depends({ node = v.id })
end
socks_node:value(v.id, v["remark"])
socks_node.group[#socks_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
o = s:option(MultiValue, "backup_node_group", translate("Select Group"))
o:depends("backup_node_add_mode", "batch")
o.widget = "checkbox"
o:value("default", translate("default"))
local groups = {}
m:foreach("nodes", function(s)
if s.group and s.group ~= "" then
groups[s.group] = true
end
end)
for k, v in pairs(groups) do
o:value(api.UrlEncode(k), k)
end
o = s:option(Value, "backup_node_match_rule", translate("Node Matching Rules"))
o:depends("backup_node_add_mode", "batch")
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
descrStr = descrStr .. "Conditions are joined by <code>&&</code> (AND), and their order does not affect the result.<br>"
descrStr = descrStr .. "Multiple groups can be separated by <code>||</code> (OR), matching succeeds if any group matches.<br>"
descrStr = descrStr .. "Example: <code>A && B || C && D</code> means (A AND B) OR (C AND D)."
o.description = translate(descrStr)
o = s:option(Value, "autoswitch_testing_time", translate("How often to test"), translate("Units:seconds"))
o.datatype = "min(10)"
o.default = 30
o:depends("enable_autoswitch", true)
o = s:option(Value, "autoswitch_connect_timeout", translate("Timeout seconds"), translate("Units:seconds"))
o.datatype = "min(1)"
o.default = 3
o:depends("enable_autoswitch", true)
o = s:option(Value, "autoswitch_retry_num", translate("Timeout retry num"))
o.datatype = "min(1)"
o.default = 1
o:depends("enable_autoswitch", true)
o = s:option(Flag, "autoswitch_restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
o:depends("enable_autoswitch", true)
o = s:option(Value, "autoswitch_probe_url", translate("Probe URL"), translate("The URL used to detect the connection status."))
o:value("https://cp.cloudflare.com/", "Cloudflare")
o:value("https://www.gstatic.com/generate_204", "Gstatic")
o:value("https://www.google.com/generate_204", "Google")
o:value("https://www.youtube.com/generate_204", "YouTube")
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3]
o:depends("enable_autoswitch", true)
o = s:option(DummyValue, "btn")
o.template = m:template_path("/socks_auto_switch/btn")
o:depends("backup_node_add_mode", "manual")
return api.return_map(m)
File diff suppressed because it is too large. Load diff
@@ -1,938 +0,0 @@
if not api.finded_com("xray") then
return
end
-- [[ Xray ]]
local m, s1 = ...
local type_name = "Xray"
s1.fields["type"]:value(type_name, "Xray")
if not s1.fields["type"].default then
s1.fields["type"].default = type_name
end
if s1.val["type"] ~= type_name then
return
end
local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name
s.option_prefix = "xray_"
api.set_type_cbi(s)
local ss_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
local security_list = { "auto", "aes-128-gcm", "chacha20-poly1305" }
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
}
o = s:option(ListValue, "protocol", translate("Protocol"))
o:value("vmess", translate("Vmess"))
o:value("vless", translate("VLESS"))
o:value("http", translate("HTTP"))
o:value("socks", translate("Socks"))
o:value("shadowsocks", translate("Shadowsocks"))
o:value("trojan", translate("Trojan"))
o:value("wireguard", translate("WireGuard"))
o:value("hysteria2", translate("Hysteria2"))
o:value("masque", "MASQUE")
o:value("_balancing", translate("Balancing"))
o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface"))
local protocol_val = m:get(s.section, "protocol")
local formvalue_proto = s.fields["protocol"]:formvalue(s.section)
if formvalue_proto then
protocol_val = formvalue_proto
end
local load_balancing_options = protocol_val == "_balancing"
local load_shunt_options = protocol_val == "_shunt"
local load_iface_options = protocol_val == "_iface"
local load_normal_options = true
if load_balancing_options or load_shunt_options or load_iface_options then
load_normal_options = nil
end
local netdev_list = api.get_network_devices()
local node_list = api.get_node_list()
local fallback_list = {}
local is_balancer = nil
for k, e in ipairs(node_list.balancing_list or {}) do
if e.id ~= s.section then
fallback_list[#fallback_list + 1] = {
id = e["id"],
remark = e["remark"],
group = e["group"],
fallback = e.o["fallback_node"],
}
else
is_balancer = true
end
end
if load_balancing_options then -- [[ Load balancing Start ]]
o = s:option(ListValue, "node_add_mode", translate("Node Addition Method"))
o:depends({ protocol = "_balancing" })
o.default = "manual"
o:value("manual", translate("Manual"))
o:value("batch", translate("Batch"))
o = s:option(MultiValue, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
o:depends({ node_add_mode = "manual" })
o.widget = "checkbox"
o.cast = "table"
o.template = m:template_path("/cbi/nodes_multivalue")
o.group = {}
for k1, v1 in pairs(node_list) do
if k1 == "socks_list" or k1 == "normal_list" then
for i, v in ipairs(v1) do
o:value(v.id, v.remark)
o.group[#o.group+1] = v.group or ""
end
end
end
o = s:option(MultiValue, "node_group", translate("Select Group"))
o:depends({ node_add_mode = "batch" })
o.widget = "checkbox"
o:value("default", translate("default"))
for k, v in pairs(groups) do
o:value(api.UrlEncode(k), k)
end
o = s:option(Value, "node_match_rule", translate("Node Matching Rules"))
o:depends({ node_add_mode = "batch" })
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
descrStr = descrStr .. "Conditions are joined by <code>&&</code> (AND), and their order does not affect the result.<br>"
descrStr = descrStr .. "Multiple groups can be separated by <code>||</code> (OR), matching succeeds if any group matches.<br>"
descrStr = descrStr .. "Example: <code>A && B || C && D</code> means (A AND B) OR (C AND D)."
o.description = translate(descrStr)
o = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy"))
o:depends({ protocol = "_balancing" })
o:value("random")
o:value("roundRobin")
o:value("leastPing")
o:value("leastLoad")
o.default = "random"
-- Fallback Node
o = s:option(ListValue, "fallback_node", translate("Fallback Node"))
o.group = {"",""}
o:value("", translate("Close(Not use)"))
o:value("_direct", translate("Direct Connection"))
o:depends({ protocol = "_balancing" })
o.template = m:template_path("/cbi/nodes_listvalue")
-- Maximum number of fallback nesting layers
local MAX_FALLBACK_DEPTH = 3
-- Check if a loop will form.
local function will_loop(start_id, target_id, depth)
depth = depth or 0
-- Recursion stops after the maximum depth is exceeded.
if depth >= MAX_FALLBACK_DEPTH then
return false
end
for _, v in ipairs(fallback_list) do
if v.id == target_id then
local fb = v.fallback
-- No fallback
if not fb or fb == "" or fb == "_direct" then
return false
end
-- Loopback detected
if fb == start_id then
return true
end
-- Continue recursive checking
return will_loop(start_id, fb, depth + 1)
end
end
return false
end
-- Get fallback chain depth
local function get_fallback_depth(id, depth)
depth = depth or 0
if depth >= MAX_FALLBACK_DEPTH then
return depth
end
for _, v in ipairs(fallback_list) do
if v.id == id then
local fb = v.fallback
if not fb or fb == "" or fb == "_direct" then
return depth
end
return get_fallback_depth(fb, depth + 1)
end
end
return depth
end
for _, v in ipairs(fallback_list) do
local depth = get_fallback_depth(v.id)
-- Once the maximum number of nested doll layers is exceeded, further selection of the balancer is not allowed.
if depth < MAX_FALLBACK_DEPTH
and not will_loop(s.section, v.id)
then
o:value(v.id, v.remark)
o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
for k1, v1 in pairs(node_list) do
if k1 == "socks_list" or k1 == "normal_list" or k1 == "urltest_list" then
for i, v in ipairs(v1) do
o:value(v.id, v.remark)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
end
o = s:option(Flag, "useCustomProbeUrl", translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
o:depends({ protocol = "_balancing" })
o = s:option(Value, "probeUrl", translate("Probe URL"))
o:depends({ useCustomProbeUrl = true })
o:value("https://cp.cloudflare.com/", "Cloudflare")
o:value("https://www.gstatic.com/generate_204", "Gstatic")
o:value("https://www.google.com/generate_204", "Google")
o:value("https://www.youtube.com/generate_204", "YouTube")
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3]
o.description = translate("The URL used to detect the connection status.")
o = s:option(Value, "probeInterval", translate("Probe Interval"))
o:depends({ protocol = "_balancing" })
o.default = "1m"
o.placeholder = "1m"
o.description = translate("The interval between initiating probes.") .. "<br>" ..
translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>s</code>, <code>m</code>, <code>h</code>, which correspond to seconds, minutes, and hours, respectively.") .. "<br>" ..
translate("When the unit is not filled in, it defaults to seconds.")
o = s:option(Value, "expected", translate("Preferred Node Count"))
o:depends({ balancingStrategy = "leastLoad" })
o.datatype = "uinteger"
o.default = "2"
o.placeholder = "2"
o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.")
o = s:option(Value, "tolerance", translate("Failure Tolerance (%)"))
o:depends({ balancingStrategy = "leastLoad" })
o.datatype = "uinteger"
o.default = "10"
o.placeholder = "10"
o.description = translate("The maximum acceptable speed test failure rate. For example, 1 means allowing a 1% failure rate.")
end -- [[ Load balancing End ]]
if load_iface_options then -- [[ Custom Interface Start ]]
o = s:option(Value, "iface", translate("Interface"))
o:depends({ protocol = "_iface" })
for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
end -- [[ Custom Interface End ]]
-- [[ Normal single node Start ]]
if load_normal_options then
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
o = s:option(Value, "port", translate("Port"))
o.datatype = "port"
o = s:option(Value, "uuid", translate("ID"))
o.password = true
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o = s:option(Value, "username", translate("Username"))
o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "masque" })
o = s:option(Value, "password", translate("Password"))
o.password = true
o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" })
o:depends({ protocol = "masque" })
-- [[ MASQUE ]] --
o = s:option(Flag, "masque_warp", "WARP")
o.default = "0"
o:depends({ protocol = "masque" })
for _, field in ipairs({
{ "masque_warp_private_key", translate("WARP Device Private Key") },
{ "masque_warp_public_key", translate("WARP Endpoint Public Key") }
}) do
o = s:option(TextValue, field[1], field[2])
o.rows = 3
o.wrap = "off"
o:depends({ protocol = "masque", masque_warp = "1" })
o.cfgvalue = function(self, section)
return (m:get(section, self.config_option) or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
return api.trim(value):gsub("\r\n?", "\n"):gsub("\n", "\\n")
end
end
o = s:option(DynamicList, "masque_warp_address", "WARP " .. translate("Local Address"))
o:depends({ protocol = "masque", masque_warp = "1" })
o = s:option(Value, "masque_path", translate("MASQUE Path"))
o:depends({ protocol = "masque" })
o = s:option(TextValue, "masque_headers", translate("Custom HTTP Headers"))
o.rows = 5
o.wrap = "off"
o:depends({ protocol = "masque" })
o.cfgvalue = function(self, section)
return (m:get(section, self.config_option) or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n")
local headers = {}
for line in value:gmatch("[^\n]+") do
if api.trim(line) ~= "" then
local key = line:match("^%s*([^:]+):")
key = key and api.trim(key):lower()
if not key or not key:match("^[!#$%%&'*+%.%^_`|~%w%-]+$") or headers[key] or key == "host" or key == "capsule-protocol" then
return nil, translate("Not true format, please re-enter!") .. " (" .. line .. ")"
end
headers[key] = true
end
end
return value:gsub("\n", "\\n")
end
o = s:option(ListValue, "security", translate("Encrypt Method"))
for a, t in ipairs(security_list) do o:value(t) end
o:depends({ protocol = "vmess" })
o = s:option(Value, "encryption", translate("Encrypt Method") .. " (encryption)")
o.default = "none"
o.placeholder = "none"
o:depends({ protocol = "vless" })
o.validate = function(self, value)
value = api.trim(value)
return (value == "" and "none" or value)
end
o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
for a, t in ipairs(ss_method_list) do o:value(t) end
o:depends({ protocol = "shadowsocks" })
o = s:option(ListValue, "flow", translate("flow"))
o.default = ""
o:value("", translate("Disable"))
o:value("xtls-rprx-vision")
o:value("xtls-rprx-vision-udp443")
o:depends({ protocol = "vless" })
---- [[hysteria2]]
o = s:option(Value, "hysteria2_hop", translate("Port hopping range"))
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
o = s:option(Value, "hysteria2_hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
o.datatype = "or(uinteger,portrange)"
o.placeholder = "30"
o.default = "30"
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
o = s:option(Flag, "hysteria2_realms", translate("Realms"))
o.default = "0"
o:depends({ protocol = "hysteria2"})
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o:depends({ hysteria2_realms = "1" })
o.validate = function(self, value)
value = api.trim(value)
local realm = api.parse_realm_uri(value)
if realm then return value end
return nil, translate("Invalid Realm URL.")
end
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ hysteria2_realms = "1" })
o = s:option(Flag, "hysteria2_realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
o.default = "0"
o:depends({ hysteria2_realms = "1" })
o = s:option(Value, "hysteria2_auth_password", translate("Auth Password"))
o.password = true
o:depends({ protocol = "hysteria2"})
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
o:value("gecko")
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
o:depends({ hysteria2_obfs_type = "salamander" })
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
o.datatype = "uinteger"
o.placeholder = "512"
o.default = "512"
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
o.datatype = "uinteger"
o.placeholder = "1200"
o.default = "1200"
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_stream_recv_win", translate("QUIC stream receive window") .. " (MB)")
o.datatype = "range(1,1000)"
o.placeholder = "8"
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_conn_recv_win", translate("QUIC connection receive window") .. " (MB)")
o.datatype = "range(1,1000)"
o.placeholder = "20"
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
o.datatype = "range(4,120)"
o:depends({ protocol = "hysteria2"})
o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
o.datatype = "range(2,60)"
o:depends({ protocol = "hysteria2"})
o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection"))
o.default = "0"
o:depends({ protocol = "hysteria2"})
---- [[hysteria2 end]]
o = s:option(Flag, "tls", translate("TLS"))
o.default = 0
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "trojan" })
o:depends({ protocol = "shadowsocks" })
o = s:option(Flag, "reality", translate("REALITY"))
o.default = 0
o:depends({ tls = true, transport = "raw" })
o:depends({ tls = true, transport = "ws" })
o:depends({ tls = true, transport = "grpc" })
o:depends({ tls = true, transport = "httpupgrade" })
o:depends({ tls = true, transport = "xhttp" })
o = s:option(ListValue, "alpn", translate("alpn"))
o.default = "default"
o:value("default", translate("Default"))
o:value("h3")
o:value("h2")
o:value("h3,h2")
o:value("http/1.1")
o:value("h2,http/1.1")
o:value("h3,h2,http/1.1")
o:depends({ tls = true, reality = false })
o:depends({ protocol = "masque" })
-- o = s:option(Value, "minversion", translate("minversion"))
-- o.default = "1.3"
-- o:value("1.3")
-- o:depends({ tls = true })
o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain"))
o:depends({ tls = true })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(Value, "tls_pinSHA256", translate("TLS Chain Fingerprint (SHA256)"))
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
o = s:option(Value, "tls_CertByName", translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)"))
o.default = "0"
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format."))
o.default = ""
o.rows = 5
o.wrap = "off"
o:depends({ tls_certificate = true })
o.cfgvalue = function(self, section)
return (m:get(section, "tls_certificate_pem") or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
return value:gsub("\n", "\\n")
end
o = s:option(Flag, "ech", translate("ECH"))
o.default = "0"
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "ech_config", translate("ECH Config"))
o.default = ""
o.rows = 5
o.wrap = "soft"
o:depends({ ech = true })
o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
-- [[ REALITY ]] --
o = s:option(Value, "reality_publicKey", translate("Public Key"))
o:depends({ tls = true, reality = true })
o = s:option(Value, "reality_shortId", translate("Short Id"))
o:depends({ tls = true, reality = true })
o = s:option(Value, "reality_spiderX", translate("Spider X"))
o.placeholder = "/"
o:depends({ tls = true, reality = true })
o = s:option(Flag, "utls", translate("uTLS"))
o.default = "0"
o:depends({ tls = true, reality = false })
o = s:option(ListValue, "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:value("unsafe")
o.default = "chrome"
o:depends({ tls = true, utls = true })
o:depends({ tls = true, reality = true })
o = s:option(Flag, "use_mldsa65Verify", translate("ML-DSA-65"))
o.default = "0"
o:depends({ tls = true, reality = true })
o = s:option(DynamicList, "cipherSuites", translate("Cipher Suites"), '<a href="https://go.dev/src/crypto/tls/cipher_suites.go#L44" target="_blank">***</a>' .. " " .. translate("Configures the list of supported cipher suites."))
o:value("TLS_AES_128_GCM_SHA256")
o:value("TLS_AES_256_GCM_SHA384")
o:value("TLS_CHACHA20_POLY1305_SHA256")
o:value("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA")
o:value("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA")
o:value("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA")
o:value("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA")
o:value("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
o:value("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
o:value("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
o:value("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
o:value("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
o:value("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
o:depends({ tls = true, reality = false })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "reality_mldsa65Verify", "ML-DSA-65 " .. translate("Public key"))
o.default = ""
o.rows = 5
o.wrap = "soft"
o:depends({ use_mldsa65Verify = true })
o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
o = s:option(ListValue, "transport", translate("Transport"))
o:value("raw", "RAW (TCP)")
o:value("mkcp", "mKCP")
o:value("ws", "WebSocket")
o:value("grpc", "gRPC")
o:value("httpupgrade", "HttpUpgrade")
o:value("xhttp", "XHTTP")
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" })
o = s:option(Value, "wireguard_public_key", translate("Public Key"))
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_secret_key", translate("Private Key"))
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key"))
o:depends({ protocol = "wireguard" })
o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_mtu", translate("MTU"))
o.default = "1420"
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
o.default = "0"
o:depends({ protocol = "wireguard" })
-- [[ RAW ]]--
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
o:value("none", "none")
o:value("http", "http")
o:depends({ transport = "raw" })
o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
o:depends({ tcp_guise = "http" })
o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
o.placeholder = "/"
o:depends({ tcp_guise = "http" })
-- [[ mKCP ]]--
o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
for a, t in ipairs(header_type_list) do o:value(t) end
o:depends({ transport = "mkcp" })
o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
o:depends({ mkcp_guise = "dns" })
o = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
o.datatype = "uinteger"
o.default = 1350
o:depends({ transport = "mkcp" })
o = s:option(Value, "mkcp_seed", translate("KCP Seed"))
o:depends({ transport = "mkcp" })
-- [[ WebSocket ]]--
o = s:option(Value, "ws_host", translate("WebSocket Host"))
o:depends({ transport = "ws" })
o = s:option(Value, "ws_path", translate("WebSocket Path"))
o.placeholder = "/"
o:depends({ transport = "ws" })
o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)"))
o.datatype = "integer"
o:depends({ transport = "ws" })
-- [[ gRPC ]]--
o = s:option(Value, "grpc_serviceName", "ServiceName")
o:depends({ transport = "grpc" })
o = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode"))
o:value("gun")
o:value("multi")
o:depends({ transport = "grpc" })
o = s:option(Flag, "grpc_health_check", translate("Health check"))
o:depends({ transport = "grpc" })
o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
o.default = "10"
o:depends({ grpc_health_check = true })
o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout"))
o.default = "20"
o:depends({ grpc_health_check = true })
o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream"))
o.default = "0"
o:depends({ grpc_health_check = true })
o = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size"))
o.default = "0"
o:depends({ transport = "grpc" })
-- [[ HttpUpgrade ]]--
o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host"))
o:depends({ transport = "httpupgrade" })
o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path"))
o.placeholder = "/"
o:depends({ transport = "httpupgrade" })
-- [[ XHTTP ]]--
o = s:option(ListValue, "xhttp_mode", "XHTTP " .. translate("Mode"))
o:depends({ transport = "xhttp" })
o.default = "auto"
o:value("auto")
o:value("packet-up")
o:value("stream-up")
o:value("stream-one")
o = s:option(Value, "xhttp_host", translate("XHTTP Host"))
o:depends({ transport = "xhttp" })
o = s:option(Value, "xhttp_path", translate("XHTTP Path"))
o.placeholder = "/"
o:depends({ transport = "xhttp" })
o = s:option(Flag, "use_xhttp_extra", translate("XHTTP Extra"))
o.default = "0"
o:depends({ transport = "xhttp" })
o = s:option(TextValue, "xhttp_extra", " ", translate("An XHttpObject in JSON format, used for sharing."))
o:depends({ use_xhttp_extra = true })
o.rows = 10
o.wrap = "off"
o.datatype = "json"
local o_validate = o.validate
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
local v = o_validate(self, value)
if v then return v end
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section, value)
local raw = m:get(section, "xhttp_extra")
if raw then
return api.base64Decode(raw)
end
end
o.write = function(self, section, value)
m:set(section, "xhttp_extra", api.base64Encode(value) or "")
local success, data = pcall(api.jsonc.parse, value)
if success and data then
local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
or (data.downloadSettings and data.downloadSettings.address)
if address and address ~= "" then
address = address:gsub("^%[", ""):gsub("%]$", "")
m:set(section, "download_address", address)
else
m:del(section, "download_address")
end
else
m:del(section, "download_address")
end
end
o.remove = function(self, section, value)
m:del(section, "xhttp_extra")
m:del(section, "download_address")
end
-- [[ User-Agent ]]--
o = s:option(Value, "user_agent", translate("User-Agent"))
o.default = ""
o:value("", translate("default"))
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
o:value("Go-http-client/1.1", "golang")
o:value("curl/7.68.0", "curl")
o:depends({ tcp_guise = "http" })
o:depends({ transport = "ws" })
o:depends({ transport = "httpupgrade" })
o:depends({ transport = "xhttp" })
o:depends({ transport = "grpc" })
o:depends({ protocol = "masque" })
-- [[ Mux.Cool ]]--
o = s:option(Flag, "mux", "Mux", translate("Enable Mux.Cool"))
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless", transport = "raw" })
o:depends({ protocol = "vless", transport = "ws" })
o:depends({ protocol = "vless", transport = "grpc" })
o:depends({ protocol = "vless", transport = "httpupgrade" })
o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" })
o = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
o.default = -1
o:depends({ mux = true })
o = s:option(Value, "xudp_concurrency", translate("XUDP Mux concurrency"))
o.default = 8
o:depends({ mux = true })
--[[FinalMask]]
o = s:option(Flag, "use_finalmask", "FinalMask")
o.default = "0"
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "trojan" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "wireguard" })
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "finalmask", " ")
o:depends({ use_finalmask = true })
o.rows = 10
o.wrap = "off"
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
o.datatype = "json"
local o_validate = o.validate
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
local v = o_validate(self, value)
if v then return v end
return nil, "FinalMask " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section, value)
local raw = m:get(section, "finalmask")
if raw then
return api.base64Decode(raw)
end
end
o.write = function(self, section, value)
m:set(section, "finalmask", api.base64Encode(value) or "")
end
--[[Fast Open]]
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
--[[tcpMptcp]]
o = s:option(Flag, "tcpMptcp", "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
o.default = 0
o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"), translate("If the node address is a domain name, this DNS will be used for resolution."))
o:value("", translate("Auto"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("https", "HTTPS")
o = s:option(Value, "domain_resolver_dns", "DNS")
o.datatype = "or(ipaddr,ipaddrport)"
o:value("114.114.114.114")
o:value("223.5.5.5:53")
o.default = o.keylist[1]
o:depends({ domain_resolver = "tcp" })
o:depends({ domain_resolver = "udp" })
o = s:option(Value, "domain_resolver_dns_https", "DNS")
o:value("https://120.53.53.53/dns-query", "DNSPod")
o:value("https://223.5.5.5/dns-query", "AliDNS")
o.default = o.keylist[1]
o:depends({ domain_resolver = "https" })
o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
o.default = ""
o:value("", translate("Auto"))
o:value("UseIPv4", translate("IPv4 Only"))
o:value("UseIPv6", translate("IPv6 Only"))
o = s:option(Flag, "happy_eyeballs", translate("Enable Happy Eyeballs"), translate("Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."))
o.default = 0
local protocols = s.fields["protocol"].keylist
if #protocols > 0 then
for i, v in ipairs(protocols) do
if not v:find("^_") then
local depends_condition = { protocol = v }
if v == "hysteria2" then
depends_condition["hysteria2_realms"] = false
end
s.fields["address"]:depends(depends_condition)
s.fields["port"]:depends(depends_condition)
s.fields["domain_resolver"]:depends(depends_condition)
s.fields["happy_eyeballs"]:depends(depends_condition)
local strategy_depends = api.clone(depends_condition)
strategy_depends["happy_eyeballs"] = false
s.fields["domain_strategy"]:depends(strategy_depends)
if v ~= "hysteria2" then
s.fields["tcp_fast_open"]:depends({ protocol = v })
s.fields["tcpMptcp"]:depends({ protocol = v })
end
end
end
end
end
-- [[ Normal single node End ]]
if not load_shunt_options then
o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
o:value("", translate("Close(Not use)"))
if not (load_iface_options or load_balancing_options) then
-- Special node cannot be use pre-proxy.
o:value("1", translate("Preproxy Node"))
o:value("3", translate("Outbound Interface"))
end
o:value("2", translate("Landing Node"))
o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy."))
o1:depends({ chain_proxy = "1", hysteria2_realms = false })
o1.template = m:template_path("/cbi/nodes_listvalue")
o1.group = {}
o3 = s:option(Value, "outbound_iface", translate("Outbound Interface"))
o3:depends({ chain_proxy = "3" })
o3:value("", translate("All"))
for _, d in ipairs(netdev_list) do
o3:value(d.name, d.label)
end
o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy."))
o2:depends({ chain_proxy = "2", hysteria2_realms = false })
o2.template = m:template_path("/cbi/nodes_listvalue")
o2.group = {}
for k1, v1 in pairs(node_list) do
if k1 ~= "shunt_list" and k1 ~= "iface_list" then
for i, v in ipairs(v1) do
if v.id ~= s.section then
o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
if k1 == "normal_list" then
-- Landing Node not support use special node.
o2:value(v.id, v.remark)
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
end
end
end
end
api.type_cbi_section(s1, s)
if load_shunt_options then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s1, {
node = {
[".name"] = s.section,
type = type_name,
},
node_list = node_list,
})
end
@@ -1,70 +0,0 @@
if not api.is_finded("sslocal") then
return
end
-- [[ Shadowsocks Rust ]]
local m, s1 = ...
local type_name = "SS-Rust"
s1.fields["type"]:value(type_name, "Shadowsocks Rust")
if s1.val["type"] ~= type_name then
return
end
local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name
s.option_prefix = "ssrust_"
api.set_type_cbi(s)
local ssrust_encrypt_method_list = {
"none", "plain",
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
}
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
o = s:option(Value, "port", translate("Port"))
o.datatype = "port"
o = s:option(Value, "password", translate("Password"))
o.password = true
o = s:option(Value, "method", translate("Encrypt Method"))
for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
o = s:option(Value, "timeout", translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
o = s:option(Flag, "plugin_enabled", translate("plugin"))
o.default = 0
o = s:option(Value, "plugin", "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed."))
o.default = "none"
o:value("none", translate("none"))
if api.is_finded("xray-plugin") then o:value("xray-plugin") end
if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end
if api.is_finded("obfs-local") then o:value("obfs-local") end
if api.is_finded("shadow-tls") then o:value("shadow-tls") end
o:depends({ plugin_enabled = true })
o.validate = function(self, value, t)
if value and value ~= "" and value ~= "none" then
if not api.is_finded(value) then
return nil, value .. ": " .. translate("Can't find this file!")
else
return value
end
end
return nil
end
o = s:option(Value, "plugin_opts", translate("opts"))
o:depends({ plugin_enabled = true })
api.type_cbi_section(s1, s)
@@ -1,68 +0,0 @@
if not api.is_finded("ssr-local") then
return
end
-- [[ ShadowsocksR Libev ]]
local m, s1 = ...
local type_name = "SSR"
s1.fields["type"]:value(type_name, "ShadowsocksR Libev")
if s1.val["type"] ~= type_name then
return
end
local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name
s.option_prefix = "ssr_"
api.set_type_cbi(s)
local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
"chacha20-ietf"
}
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"
}
local ssr_obfs_list = {
"plain", "http_simple", "http_post", "random_head", "tls_simple",
"tls1.0_session_auth", "tls1.2_ticket_auth"
}
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
o = s:option(Value, "port", translate("Port"))
o.datatype = "port"
o = s:option(Value, "password", translate("Password"))
o.password = true
o = s:option(ListValue, "method", translate("Encrypt Method"))
for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
o = s:option(ListValue, "protocol", translate("Protocol"))
for a, t in ipairs(ssr_protocol_list) do o:value(t) end
o = s:option(Value, "protocol_param", translate("Protocol_param"))
o = s:option(ListValue, "obfs", translate("Obfs"))
for a, t in ipairs(ssr_obfs_list) do o:value(t) end
o = s:option(Value, "obfs_param", translate("Obfs_param"))
o = s:option(Value, "timeout", translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
api.type_cbi_section(s1, s)
@@ -1,141 +0,0 @@
local api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map(api.s_config)
m.redirect = api.url("server")
t = m:section(NamedSection, "global", "global", translate("Server-Side"))
t.anonymous = true
t.addremove = false
e = t:option(Flag, "enable", translate("Enable"))
e.rmempty = false
s_server = m:section(TypedSection, "server", translate("Servers Manager"))
s_server.anonymous = true
s_server.addremove = true
s_server.sortable = true
s_server.template = "cbi/tblsection"
s_server.extedit = api.url("server_config", "%s")
function s_server.create(self, section)
local uid = api.gen_random_char()
TypedSection.create(self, uid)
luci.http.redirect(self.extedit:format(uid))
end
function s_server.remove(self, section)
local o = m:get(section) or {}
if o[".type"] == self.sectiontype then
m:del(section)
end
end
e = s_server:option(Flag, "enable", translate("Enable"))
e.width = "5%"
e.rmempty = false
e = s_server:option(DummyValue, "status", translate("Status"))
e.rawhtml = true
e.cfgvalue = function(t, n)
return string.format('<font class="_servers_status">%s</font>', translate("Collecting data..."))
end
e = s_server:option(DummyValue, "remarks", translate("Remarks"))
e.width = "15%"
e = s_server:option(DummyValue, "type", translate("Type"))
e.width = "20%"
e.rawhtml = true
e.cfgvalue = function(t, n)
local str = ""
local type = m:get(n, "type") or ""
if type == "sing-box" or type == "Xray" then
local protocol = m:get(n, "protocol") or ""
if protocol == "vmess" then
protocol = "VMess"
elseif protocol == "vless" then
protocol = "VLESS"
elseif protocol == "shadowsocks" then
protocol = "SS"
elseif protocol == "shadowsocksr" then
protocol = "SSR"
elseif protocol == "wireguard" then
protocol = "WG"
elseif protocol == "hysteria" then
protocol = "HY"
elseif protocol == "hysteria2" then
protocol = "HY2"
elseif protocol == "anytls" then
protocol = "AnyTLS"
else
protocol = protocol:gsub("^%l",string.upper)
local custom = m:get(n, "custom") or "0"
if custom == "1" then
protocol = translate("Custom Config")
end
end
if type == "sing-box" then type = "Sing-Box" end
type = type .. " " .. protocol
end
str = str .. translate(type)
return str
end
e = s_server:option(DummyValue, "port", translate("Port"))
e = s_server:option(Flag, "log", translate("Log"))
e.default = "1"
e.rmempty = false
m:appendTemplate("/cbi/sortable", {sectiontype = s_server.sectiontype})
m:appendTemplate("/server/server_list_status", {sectiontype = s_server.sectiontype})
s_user = m:section(TypedSection, "user", translate("Users Manager"))
s_user.anonymous = true
s_user.addremove = true
s_user.sortable = true
s_user.template = "cbi/tblsection"
s_user.extedit = api.url("server_user_config", "%s")
s_user.create = function(e, section)
local uid = api.gen_random_char()
TypedSection.create(e, uid)
luci.http.redirect(e.extedit:format(uid))
end
s_user.remove = function(self, section)
local o = m:get(section) or {}
if o[".type"] == self.sectiontype then
m:foreach("server", function(o)
if o.user and section == o.user then
m:set(o[".name"], "user", "")
end
local changed = false
local users = o.users or {}
for i = #users, 1, -1 do
if section == users[i] then
table.remove(users, i)
changed = true
end
end
if changed then
m:set(o[".name"], "users", users)
end
end)
m:del(section)
luci.http.redirect(api.url("server"))
end
end
e = s_user:option(DummyValue, "username", translate("Username"))
e.width = "25%"
e = s_user:option(DummyValue, "password", translate("Password"))
e.width = "25%"
e = s_user:option(DummyValue, "uuid", "UUID")
e.width = "25%"
m:appendTemplate("/cbi/sortable", {sectiontype = s_user.sectiontype})
m:appendTemplate("/server/log")
return api.return_map(m)
@@ -1,52 +0,0 @@
api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map(api.s_config)
m.redirect = api.url("server")
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
m:appendTemplate("/server/config_header", {section = arg[1]})
m:appendTemplate("/cbi/nodes_listvalue_com")
user_list = {}
m:foreach("user", function(s)
user_list[#user_list + 1] = s
end)
local s = m:section(NamedSection, arg[1], "server", translate("Server Config"))
s.addremove = false
s.dynamic = false
local types_dir = "/usr/lib/lua/luci/model/cbi/" .. api.appname .. "/server/type/"
s.val = m:get(arg[1]) or {}
o = s:option(Flag, "enable", translate("Enable"))
o.default = "1"
o.rmempty = false
o = s:option(Value, "remarks", translate("Remarks"))
o.default = translate("Remarks")
o.rmempty = false
o = s:option(ListValue, "type", translate("Type"))
local type_table = {}
for filename in api.fs.dir(types_dir) do
table.insert(type_table, filename)
end
table.sort(type_table, function(a, b)
return a < b
end)
for index, value in ipairs(type_table) do
local p_func = loadfile(types_dir .. value)
setfenv(p_func, getfenv(1))(m, s)
end
m:appendTemplate("/server/config_footer", {section = arg[1]})
return api.return_map(m)
@@ -1,627 +0,0 @@
local singbox_bin = api.finded_com("sing-box")
if not singbox_bin then
return
end
-- [[ Sing-Box ]]
local m, s1 = ...
local type_name = "sing-box"
s1.fields["type"]:value(type_name, "Sing-Box")
if not s1.fields["type"].default then
s1.fields["type"].default = type_name
end
if not s1.val["type"] then
s1.val["type"] = type_name
end
if s1.val["type"] and s1.val["type"] ~= type_name then
return
end
local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name
s.option_prefix = "singbox_"
api.set_type_cbi(s)
local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
local local_version = api.get_app_version("sing-box"):match("[^v]+")
local version_ge_1_14_0 = api.compare_versions(local_version, ">=", "1.14.0")
local version_ge_1_15_0 = api.compare_versions(local_version, ">=", "1.15.0")
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"
}
o = s:option(Flag, "custom", translate("Use Custom Config"))
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
o:depends({ custom = true })
o.datatype = "json"
local o_validate = o.validate
o.validate = function(self, value)
local v = o_validate(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
end
end
o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "")
end
o = s:option(ListValue, "protocol", translate("Protocol"))
o:value("direct", "Direct")
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")
o:value("anytls", "AnyTLS")
o:depends({ custom = false })
o = s:option(Value, "port", translate("Listen Port"))
o.datatype = "port"
o:depends({ custom = false })
o = s:option(MultiValue, "users", translate("User"))
o.cast = "table"
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ protocol = "mixed" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "http" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "trojan" })
o:depends({ protocol = "naive" })
o:depends({ protocol = "anytls" })
if singbox_tags:find("with_quic") then
-- hysteria
s.fields["protocol"]:value("hysteria", "Hysteria")
s.fields["users"]:depends({ protocol = "hysteria" })
o = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
o:depends({ protocol = "hysteria" })
o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
o.default = "100"
o:depends({ protocol = "hysteria" })
o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
o.default = "100"
o:depends({ protocol = "hysteria" })
o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
o:depends({ protocol = "hysteria" })
o = s:option(Value, "hysteria_recv_window_client", translate("QUIC connection receive window"))
o:depends({ protocol = "hysteria" })
o = s:option(Value, "hysteria_max_conn_client", translate("QUIC concurrent bidirectional streams"))
o.default = "1024"
o:depends({ protocol = "hysteria" })
o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
o:depends({ protocol = "hysteria" })
end
if singbox_tags:find("with_quic") then
-- tuic
s.fields["protocol"]:value("tuic", "TUIC")
s.fields["users"]:depends({ protocol = "tuic" })
o = s:option(ListValue, "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({ protocol = "tuic" })
o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake"))
o.default = 0
o:depends({ protocol = "tuic" })
o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)"))
o.datatype = "uinteger"
o.default = "3"
o:depends({ protocol = "tuic" })
o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN"))
o.default = "default"
o:value("default", translate("Default"))
o:value("h3")
o:value("h2")
o:value("h3,h2")
o:value("http/1.1")
o:value("h2,http/1.1")
o:value("h3,h2,http/1.1")
o:value("spdy/3.1")
o:value("h3,spdy/3.1")
o:depends({ protocol = "tuic" })
end
if singbox_tags:find("with_quic") then
-- hysteria2
s.fields["protocol"]:value("hysteria2", "Hysteria2")
s.fields["users"]:depends({ protocol = "hysteria2" })
o = s:option(Flag, "hysteria2_realms", translate("Realms"))
o.default = "0"
o:depends({ protocol = "hysteria2"})
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o:depends({ hysteria2_realms = "1" })
o.validate = function(self, value)
value = api.trim(value)
local realm = api.parse_realm_uri(value)
if realm then return value end
return nil, translate("Invalid Realm URL.")
end
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ hysteria2_realms = "1" })
o = s:option(Flag, "hysteria2_realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
o.default = "0"
o:depends({ hysteria2_realms = "1" })
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
o:value("gecko")
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
o:depends({ hysteria2_obfs_type = "salamander" })
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
o.datatype = "uinteger"
o.placeholder = "512"
o.default = "512"
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
o.datatype = "uinteger"
o.placeholder = "1200"
o.default = "1200"
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control"), translate("Commands the client to use the BBR flow control algorithm"))
o.default = 0
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
end
o = s:option(ListValue, "d_protocol", translate("Destination protocol"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("tcp,udp", "TCP,UDP")
o:depends({ protocol = "direct" })
o = s:option(Value, "d_address", translate("Destination address"))
o:depends({ protocol = "direct" })
o = s:option(Value, "d_port", translate("Destination port"))
o.datatype = "port"
o:depends({ protocol = "direct" })
o = s:option(Value, "decryption", translate("Encrypt Method"))
o.default = "none"
o:depends({ protocol = "vless" })
o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
for a, t in ipairs(ss_method_list) do o:value(t) end
o:depends({ protocol = "shadowsocks" })
o = s:option(Value, "ss_password", translate("Password"))
o:depends({ protocol = "shadowsocks" })
o = s:option(ListValue, "flow", translate("flow"))
o.default = ""
o:value("", translate("Disable"))
o:value("xtls-rprx-vision")
o:depends({ protocol = "vless" , tls = true })
-- [[ MASQUE ]] --
if version_ge_1_15_0 and singbox_tags:find("with_quic") then
s.fields["protocol"]:value("masque", "MASQUE")
s.fields["users"]:depends({ protocol = "masque" })
o = s:option(Value, "masque_path", translate("MASQUE Path"))
o:depends({ protocol = "masque" })
o = s:option(DynamicList, "masque_address", translate("MASQUE Address"))
o.rmempty = false
o:depends({ protocol = "masque" })
o = s:option(Value, "masque_mtu", "MTU")
o.datatype = "uinteger"
o.default = "1280"
o:depends({ protocol = "masque" })
end
o = s:option(Flag, "tls", translate("TLS"))
o.default = 0
o.validate = function(self, value, t)
if value then
local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil
local use_pem = s.fields["tls_use_pem"] and s.fields["tls_use_pem"]:formvalue(t) or nil
if reality and reality == "1" then return value end
if value == "1" and use_pem ~= "1" then
local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or ""
local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or ""
if ca == "" or key == "" then
return nil, translate("Certificate and Private key path can not be empty!")
end
elseif value == "1" and use_pem == "1" then
local ca = s.fields["tls_certificate"] and s.fields["tls_certificate"]:formvalue(t) or ""
local key = s.fields["tls_key"] and s.fields["tls_key"]:formvalue(t) or ""
if ca == "" or key == "" then
return nil, translate("Certificate and Private key PEM can not be empty!")
end
end
return value
end
end
o:depends({ protocol = "http" })
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "trojan" })
o:depends({ protocol = "anytls" })
-- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab
if singbox_tags:find("with_utls") then
-- [[ REALITY ]] --
o = s:option(Flag, "reality", translate("REALITY"))
o.default = 0
o:depends({ protocol = "http", tls = true })
o:depends({ protocol = "vmess", tls = true })
o:depends({ protocol = "vless", tls = true })
o:depends({ protocol = "trojan", tls = true })
o:depends({ protocol = "anytls", tls = true })
o = s:option(Value, "reality_private_key", translate("Private Key"))
o:depends({ reality = true })
o = s:option(Value, "reality_shortId", translate("Short Id"))
o:depends({ reality = true })
o = s:option(Value, "reality_handshake_server", translate("Handshake Server"))
o.default = "google.com"
o:depends({ reality = true })
o = s:option(Value, "reality_handshake_server_port", translate("Handshake Server Port"))
o.datatype = "port"
o.default = "443"
o:depends({ reality = true })
end
o = s:option(ListValue, "alpn", translate("ALPN"))
o.default = "default"
o:value("default", translate("Default"))
o:value("h3")
o:value("h2")
o:value("h3,h2")
o:value("http/1.1")
o:value("h2,http/1.1")
o:value("h3,h2,http/1.1")
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria" })
-- [[ TLS ]] --
o = s:option(Flag, "tls_use_pem", translate("Use PEM"), translate("Use certificate and private key PEM content."))
o:depends({ tls = true, reality = false })
o:depends({ protocol = "naive" })
o:depends({ protocol = "hysteria" })
o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(FileUpload, "tls_certificateFile", translate("Path to the certificate file"), translate("as:") .. "/etc/ssl/fullchain.crt")
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. s.section .. ".crt"
if o and o:formvalue(s.section) then o.default = o:formvalue(s.section) end
o:depends({ tls = true, reality = false, tls_use_pem = false })
o:depends({ protocol = "naive", tls_use_pem = false })
o:depends({ protocol = "hysteria", tls_use_pem = false })
o:depends({ protocol = "tuic", tls_use_pem = false })
o:depends({ protocol = "hysteria2", tls_use_pem = false })
o:depends({ protocol = "masque", tls_use_pem = false })
o.validate = function(self, value, t)
if value and value ~= "" then
if not api.fs.access(value) then
return nil, translate("Can't find this file!")
else
return value
end
end
return nil
end
o = s:option(FileUpload, "tls_keyFile", translate("Path to the private key file"), translate("as:") .. "/etc/ssl/private.key")
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. s.section .. ".key"
if o and o:formvalue(s.section) then o.default = o:formvalue(s.section) end
o:depends({ tls = true, reality = false, tls_use_pem = false })
o:depends({ protocol = "naive", tls_use_pem = false })
o:depends({ protocol = "hysteria", tls_use_pem = false })
o:depends({ protocol = "tuic", tls_use_pem = false })
o:depends({ protocol = "hysteria2", tls_use_pem = false })
o:depends({ protocol = "masque", tls_use_pem = false })
o.validate = function(self, value, t)
if value and value ~= "" then
if not api.fs.access(value) then
return nil, translate("Can't find this file!")
else
return value
end
end
return nil
end
o = s:option(TextValue, "tls_certificate", translate("TLS Certificate (PEM)"), translate("Full certificate (chain), PEM format."))
o.default = ""
o.rows = 5
o.wrap = "off"
o:depends({ tls_use_pem = true })
o.cfgvalue = function(self, section)
return (m:get(section, "tls_certificate") or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
return value:gsub("\n", "\\n")
end
o = s:option(TextValue, "tls_key", translate("TLS Private Key (PEM)"), translate("Private key in PEM format."))
o.default = ""
o.rows = 5
o.wrap = "off"
o:depends({ tls_use_pem = true })
o.cfgvalue = function(self, section)
return (m:get(section, "tls_key") or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
return value:gsub("\n", "\\n")
end
o = s:option(Flag, "ech", translate("ECH"))
o.default = "0"
o:depends({ tls = true, flow = "", reality = false })
o:depends({ protocol = "naive" })
o:depends({ protocol = "hysteria" })
o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "ech_key", translate("ECH Key"))
o.default = ""
o.rows = 5
o.wrap = "off"
o:depends({ ech = true })
o.cfgvalue = function(self, section)
return (m:get(section, "ech_key") or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
return value:gsub("\n", "\\n")
end
o = s:option(ListValue, "transport", translate("Transport"))
o:value("tcp", "TCP")
o:value("http", "HTTP")
o:value("ws", "WebSocket")
o:value("httpupgrade", "HTTPUpgrade")
o:value("quic", "QUIC")
o:value("grpc", "gRPC")
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "trojan" })
-- [[ HTTP ]]--
o = s:option(DynamicList, "http_host", translate("HTTP Host"))
o:depends({ transport = "http" })
o = s:option(Value, "http_path", translate("HTTP Path"))
o:depends({ transport = "http" })
-- [[ WebSocket ]]--
o = s:option(Value, "ws_host", translate("WebSocket Host"))
o:depends({ transport = "ws" })
o = s:option(Value, "ws_path", translate("WebSocket Path"))
o:depends({ transport = "ws" })
-- [[ HTTPUpgrade ]]--
o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host"))
o:depends({ transport = "httpupgrade" })
o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path"))
o:depends({ transport = "httpupgrade" })
-- [[ gRPC ]]--
o = s:option(Value, "grpc_serviceName", "ServiceName")
o:depends({ transport = "grpc" })
-- [[ Mux ]]--
o = s:option(Flag, "mux", translate("Mux"))
o.rmempty = false
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless", flow = "" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" })
-- [[ TCP Brutal ]]--
o = s:option(Flag, "tcpbrutal", translate("TCP Brutal"))
o.default = 0
o:depends({ mux = true })
o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps"))
o.default = "10"
o:depends({ tcpbrutal = true })
o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps"))
o.default = "50"
o:depends({ tcpbrutal = true })
if singbox_tags:find("with_wireguard") then
-- wireguard
s.fields["protocol"]:value("wireguard", "WireGuard")
s.fields["users"]:depends({ protocol = "wireguard" })
o = s:option(Flag, "wireguard_system_interface", translate("System interface"))
o.default = 0
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_mtu", "MTU")
o.default = "1408"
o:depends({ protocol = "wireguard" })
o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_private_key", translate("Private Key"))
o.datatype = "base64"
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_public_key", translate("Public Key"))
o.datatype = "base64"
o:depends({ protocol = "wireguard" })
o = s:option(DummyValue, "gen_wireguard_key")
o.template = m:template_path("/server/gen_wireguard_key")
o:depends({ protocol = "wireguard" })
end
if version_ge_1_14_0 then
-- snell
s.fields["protocol"]:value("snell", "Snell")
s.fields["users"]:depends({ protocol = "snell" })
o = s:option(ListValue, "snell_version", translate("Version"))
o:value("5")
o:value("6")
o:depends({ protocol = "snell" })
o = s:option(Value, "snell_psk", translate("Pre shared key"))
o.rmempty = false
o:depends({ protocol = "snell" })
o = s:option(ListValue, "snell_obfs_mode", translate("Obfs"))
o:value("none")
o:value("http")
o:depends({ protocol = "snell", snell_version = "5" })
o = s:option(ListValue, "snell_mode", translate("Mode"))
o:value("default")
o:value("unshaped")
o:value("unsafe-raw")
o:depends({ protocol = "snell", snell_version = "6" })
end
o = s:option(Flag, "firewall_allow", translate("Firewall Allow"))
o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
o.template = "cbi/firewall_zonelist"
o:depends({ custom = false, firewall_allow = true })
o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
o.default = "0"
o:depends({ custom = false })
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"],
group = e["group"]
}
end
end
o = s:option(ListValue, "outbound_node", translate("outbound node"))
o:value("", translate("Close"))
o:value("_socks", translate("Custom Socks"))
o:value("_http", translate("Custom HTTP"))
o:value("_iface", translate("Custom Interface"))
o.template = m:template_path("/cbi/nodes_listvalue")
o.group = {"","","",""}
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
o:depends({ custom = false })
o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)"))
o:depends({ outbound_node = "_socks" })
o:depends({ outbound_node = "_http" })
o = s:option(Value, "outbound_node_port", translate("Port"))
o.datatype = "port"
o:depends({ outbound_node = "_socks" })
o:depends({ outbound_node = "_http" })
o = s:option(Value, "outbound_node_username", translate("Username"))
o:depends({ outbound_node = "_socks" })
o:depends({ outbound_node = "_http" })
o = s:option(Value, "outbound_node_password", translate("Password"))
o.password = true
o:depends({ outbound_node = "_socks" })
o:depends({ outbound_node = "_http" })
o = s:option(Value, "outbound_node_iface", translate("Interface"))
o:depends({ outbound_node = "_iface" })
local netdev_list = api.get_network_devices()
for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
o = s:option(Flag, "log", translate("Log"))
o.default = "1"
o.rmempty = false
o = s:option(ListValue, "loglevel", translate("Log Level"))
o.default = "info"
o:value("debug")
o:value("info")
o:value("warn")
o:value("error")
o:depends({ log = true })
api.type_cbi_section(s1, s)
@@ -1,621 +0,0 @@
if not api.finded_com("xray") then
return
end
-- [[ Xray ]]
local m, s1 = ...
local type_name = "Xray"
s1.fields["type"]:value(type_name, "Xray")
if not s1.fields["type"].default then
s1.fields["type"].default = type_name
end
if not s1.val["type"] then
s1.val["type"] = type_name
end
if s1.val["type"] and s1.val["type"] ~= type_name then
return
end
local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name
s.option_prefix = "xray_"
api.set_type_cbi(s)
local 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"
}
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
}
o = s:option(Flag, "custom", translate("Use Custom Config"))
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
o:depends({ custom = true })
o.datatype = "json"
local o_validate = o.validate
o.validate = function(self, value)
local v = o_validate(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
end
end
o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "")
end
o = s:option(ListValue, "protocol", translate("Protocol"))
o:value("vmess", "Vmess")
o:value("vless", "VLESS")
o:value("http", "HTTP")
o:value("socks", "Socks")
o:value("shadowsocks", "Shadowsocks")
o:value("trojan", "Trojan")
o:value("hysteria2", "Hysteria2")
o:value("wireguard", "WireGuard")
o:value("masque", "MASQUE")
o:value("tunnel", "Tunnel")
o:depends({ custom = false })
o = s:option(Value, "port", translate("Listen Port"))
o.datatype = "port"
o:depends({ custom = false })
o = s:option(MultiValue, "users", translate("User"))
o.cast = "table"
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "trojan" })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "wireguard" })
o:depends({ protocol = "masque" })
o = s:option(ListValue, "d_protocol", translate("Destination protocol"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("tcp,udp", "TCP,UDP")
o:depends({ protocol = "tunnel" })
o = s:option(Value, "d_address", translate("Destination address"))
o:depends({ protocol = "tunnel" })
o = s:option(Value, "d_port", translate("Destination port"))
o.datatype = "port"
o:depends({ protocol = "tunnel" })
o = s:option(Value, "decryption", translate("Encrypt Method") .. " (decryption)")
o.default = "none"
o.placeholder = "none"
o:depends({ protocol = "vless" })
o.validate = function(self, value)
value = api.trim(value)
return (value == "" and "none" or value)
end
o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
for a, t in ipairs(ss_method_list) do o:value(t) end
o:depends({ protocol = "shadowsocks" })
o = s:option(Value, "ss_password", translate("Password"))
o:depends({ protocol = "shadowsocks" })
o = s:option(ListValue, "ss_network", translate("Transport"))
o.default = "tcp,udp"
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:value("tcp,udp", "TCP,UDP")
o:depends({ protocol = "shadowsocks" })
o = s:option(Flag, "udp_forward", translate("UDP Forward"))
o.default = "1"
o.rmempty = false
o:depends({ protocol = "socks" })
o = s:option(ListValue, "flow", translate("flow"))
o.default = ""
o:value("", translate("Disable"))
o:value("xtls-rprx-vision")
o:depends({ protocol = "vless" })
---- [[ hysteria2 ]]
o = s:option(Flag, "hysteria2_realms", translate("Realms"))
o.default = "0"
o:depends({ protocol = "hysteria2"})
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
o:depends({ hysteria2_realms = "1" })
o.validate = function(self, value)
value = api.trim(value)
local realm = api.parse_realm_uri(value)
if realm then return value end
return nil, translate("Invalid Realm URL.")
end
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
o:depends({ hysteria2_realms = "1" })
o = s:option(Flag, "hysteria2_realm_upnp", translate("Enable") .. " UPnP/NAT-PMP", translate("Enable UPnP/NAT-PMP port mapping on your gateway to improve hole punching success."))
o.default = "0"
o:depends({ hysteria2_realms = "1" })
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
o:value("gecko")
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
o:depends({ hysteria2_obfs_type = "salamander" })
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
o.datatype = "uinteger"
o.placeholder = "512"
o.default = "512"
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
o.datatype = "uinteger"
o.placeholder = "1200"
o.default = "1200"
o:depends({ hysteria2_obfs_type = "gecko" })
o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control"))
o.default = 0
o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
-- [[ MASQUE ]] --
o = s:option(Value, "masque_path", translate("MASQUE Path"))
o:depends({ protocol = "masque" })
o = s:option(DynamicList, "masque_address", translate("MASQUE Address"))
o.rmempty = false
o:depends({ protocol = "masque" })
o = s:option(Value, "masque_mtu", "MTU")
o.datatype = "uinteger"
o.default = "1280"
o:depends({ protocol = "masque" })
---- [[ TLS ]]
o = s:option(Flag, "tls", "TLS")
o.default = 0
o.validate = function(self, value, t)
if value then
local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil
local use_pem = s.fields["tls_use_pem"] and s.fields["tls_use_pem"]:formvalue(t) or nil
if reality and reality == "1" then return value end
if value == "1" and use_pem ~= "1" then
local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or ""
local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or ""
if ca == "" or key == "" then
return nil, translate("Certificate and Private key path can not be empty!")
end
elseif value == "1" and use_pem == "1" then
local ca = s.fields["tls_certificate"] and s.fields["tls_certificate"]:formvalue(t) or ""
local key = s.fields["tls_key"] and s.fields["tls_key"]:formvalue(t) or ""
if ca == "" or key == "" then
return nil, translate("Certificate and Private key PEM can not be empty!")
end
end
return value
end
end
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "http" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" })
-- [[ REALITY ]] --
o = s:option(Flag, "reality", translate("REALITY"))
o.default = 0
o:depends({ tls = true, transport = "raw" })
o:depends({ tls = true, transport = "ws" })
o:depends({ tls = true, transport = "grpc" })
o:depends({ tls = true, transport = "httpupgrade" })
o:depends({ tls = true, transport = "xhttp" })
o = s:option(Value, "reality_private_key", translate("Private Key"))
o:depends({ reality = true })
o = s:option(DynamicList, "reality_shortId", translate("Short Id"))
o:depends({ reality = true })
o = s:option(Value, "reality_dest", translate("Dest"))
o.default = "google.com:443"
o:depends({ reality = true })
o = s:option(DynamicList, "reality_serverNames", translate("serverNames"))
o:depends({ reality = true })
o = s:option(ListValue, "alpn", translate("alpn"))
o.default = "default"
o:value("default", translate("Default"))
o:value("h3")
o:value("h2")
o:value("h3,h2")
o:value("http/1.1")
o:value("h2,http/1.1")
o:value("h3,h2,http/1.1")
o:depends({ tls = true, reality = false })
o = s:option(Flag, "use_mldsa65Seed", translate("ML-DSA-65"))
o.default = "0"
o:depends({ reality = true })
o = s:option(TextValue, "reality_mldsa65Seed", "ML-DSA-65 " .. translate("Private Key"))
o.default = ""
o.rows = 5
o.wrap = "soft"
o:depends({ use_mldsa65Seed = true })
o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
-- o = s:option(Value, "minversion", translate("minversion"))
-- o.default = "1.3"
-- o:value("1.3")
--o:depends({ tls = true })
-- [[ TLS ]] --
o = s:option(Flag, "tls_use_pem", translate("Use PEM"), translate("Use certificate and private key PEM content."))
o:depends({ tls = true, reality = false })
o:depends({ protocol = "hysteria2" })
o:depends({ protocol = "masque" })
o = s:option(FileUpload, "tls_certificateFile", translate("Path to the certificate file"), translate("as:") .. "/etc/ssl/fullchain.crt")
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. s.section .. ".crt"
if o and o:formvalue(s.section) then o.default = o:formvalue(s.section) end
o:depends({ tls = true, reality = false, tls_use_pem = false })
o:depends({ protocol = "hysteria2", tls_use_pem = false })
o:depends({ protocol = "masque", tls_use_pem = false })
o.validate = function(self, value, t)
if value and value ~= "" then
if not api.fs.access(value) then
return nil, translate("Can't find this file!")
else
return value
end
end
return nil
end
o = s:option(FileUpload, "tls_keyFile", translate("Path to the private key file"), translate("as:") .. "/etc/ssl/private.key")
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. s.section .. ".key"
if o and o:formvalue(s.section) then o.default = o:formvalue(s.section) end
o:depends({ tls = true, reality = false, tls_use_pem = false })
o:depends({ protocol = "hysteria2", tls_use_pem = false })
o:depends({ protocol = "masque", tls_use_pem = false })
o.validate = function(self, value, t)
if value and value ~= "" then
if not api.fs.access(value) then
return nil, translate("Can't find this file!")
else
return value
end
end
return nil
end
o = s:option(TextValue, "tls_certificate", translate("TLS Certificate (PEM)"), translate("Full certificate (chain), PEM format."))
o.default = ""
o.rows = 5
o.wrap = "off"
o:depends({ tls_use_pem = true })
o.cfgvalue = function(self, section)
return (m:get(section, "tls_certificate") or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
return value:gsub("\n", "\\n")
end
o = s:option(TextValue, "tls_key", translate("TLS Private Key (PEM)"), translate("Private key in PEM format."))
o.default = ""
o.rows = 5
o.wrap = "off"
o:depends({ tls_use_pem = true })
o.cfgvalue = function(self, section)
return (m:get(section, "tls_key") or ""):gsub("\\n", "\n")
end
o.validate = function(self, value)
value = api.trim(value):gsub("\r\n", "\n"):gsub("\r", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
return value:gsub("\n", "\\n")
end
o = s:option(Flag, "ech", translate("ECH"))
o.default = "0"
o:depends({ tls = true, reality = false })
o:depends({ protocol = "masque" })
o = s:option(TextValue, "ech_key", translate("ECH Key"))
o.default = ""
o.rows = 5
o.wrap = "soft"
o:depends({ ech = true })
o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
o = s:option(ListValue, "transport", translate("Transport"))
o:value("raw", "RAW")
o:value("mkcp", "mKCP")
o:value("ws", "WebSocket")
o:value("grpc", "gRPC")
o:value("httpupgrade", "HttpUpgrade")
o:value("xhttp", "XHTTP")
o:depends({ protocol = "vmess" })
o:depends({ protocol = "vless" })
o:depends({ protocol = "socks" })
o:depends({ protocol = "shadowsocks" })
o:depends({ protocol = "trojan" })
-- [[ WebSocket ]]--
o = s:option(Value, "ws_host", translate("WebSocket Host"))
o:depends({ transport = "ws" })
o = s:option(Value, "ws_path", translate("WebSocket Path"))
o:depends({ transport = "ws" })
-- [[ HttpUpgrade ]]--
o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host"))
o:depends({ transport = "httpupgrade" })
o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path"))
o.placeholder = "/"
o:depends({ transport = "httpupgrade" })
-- [[ XHTTP ]]--
o = s:option(Value, "xhttp_host", translate("XHTTP Host"))
o:depends({ transport = "xhttp" })
o = s:option(Value, "xhttp_path", translate("XHTTP Path"))
o.placeholder = "/"
o:depends({ transport = "xhttp" })
o = s:option(Value, "xhttp_maxuploadsize", translate("maxUploadSize"))
o.default = "1000000"
o:depends({ transport = "xhttp" })
o = s:option(Value, "xhttp_maxconcurrentuploads", translate("maxConcurrentUploads"))
o.default = "10"
o:depends({ transport = "xhttp" })
o = s:option(Value, "splithttp_maxuploadsize", translate("maxUploadSize"))
o.default = "1000000"
o:depends({ transport = "splithttp" })
o = s:option(Value, "splithttp_maxconcurrentuploads", translate("maxConcurrentUploads"))
o.default = "10"
o:depends({ transport = "splithttp" })
-- [[ TCP ]]--
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
o:value("none", "none")
o:value("http", "http")
o:depends({ transport = "raw" })
o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
o:depends({ tcp_guise = "http" })
o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
o:depends({ tcp_guise = "http" })
-- [[ mKCP ]]--
o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
for a, t in ipairs(header_type_list) do o:value(t) end
o:depends({ transport = "mkcp" })
o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
o:depends({ mkcp_guise = "dns" })
o = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
o.datatype = "uinteger"
o.default = 1350
o:depends({ transport = "mkcp" })
o = s:option(Value, "mkcp_seed", translate("KCP Seed"))
o:depends({ transport = "mkcp" })
-- [[ gRPC ]]--
o = s:option(Value, "grpc_serviceName", "ServiceName")
o:depends({ transport = "grpc" })
--[[FinalMask]]
o = s:option(Flag, "use_finalmask", "FinalMask")
o.default = "0"
o:depends({ custom = false, protocol = "vmess" })
o:depends({ custom = false, protocol = "vless" })
o:depends({ custom = false, protocol = "trojan" })
o:depends({ custom = false, protocol = "shadowsocks" })
o:depends({ custom = false, protocol = "hysteria2", hysteria2_realms = false })
o = s:option(TextValue, "finalmask", "FinalMask JSON")
o:depends({ use_finalmask = true })
o.rows = 10
o.wrap = "off"
o.datatype = "json"
o.cfgvalue = function(self, section)
local raw = m:get(section, "finalmask")
if raw then
return api.base64Decode(raw)
end
end
o.write = function(self, section, value)
m:set(section, "finalmask", api.base64Encode(value) or "")
end
--[[acceptProxyProtocol]]
o = s:option(Flag, "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."))
o.default = "0"
o:depends({ transport = "raw" })
o:depends({ transport = "ws" })
--[[Fast Open]]
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
o.default = "0"
o:depends({ protocol = "vmess", custom = false })
o:depends({ protocol = "vless", custom = false })
o:depends({ protocol = "shadowsocks", custom = false })
o:depends({ protocol = "trojan", custom = false })
-- [[ Fallback ]]--
o = s:option(Flag, "fallback", translate("Fallback"))
o:depends({ protocol = "vless", transport = "raw" })
o:depends({ protocol = "trojan", transport = "raw" })
--[[
o = s:option(Value, "fallback_alpn", "Fallback alpn")
o:depends({ fallback = true })
o = s:option(Value, "fallback_path", "Fallback path")
o:depends({ fallback = true })
o = s:option(Value, "fallback_dest", "Fallback dest")
o:depends({ fallback = true })
o = s:option(Value, "fallback_xver", "Fallback xver")
o.default = 0
o:depends({ fallback = true })
]]--
o = s:option(DynamicList, "fallback_list", "Fallback", translate("format: dest,path,xver"))
o:depends({ fallback = true })
-- Not supported at present
--[[
o = s:option(Flag, "wireguard_system_interface", translate("System interface"))
o.default = 0
o:depends({ protocol = "wireguard" })
]]--
o = s:option(Value, "wireguard_mtu", "MTU")
o.default = "1420"
o:depends({ protocol = "wireguard" })
-- Not supported at present
--[[
o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
o:depends({ protocol = "wireguard" })
]]--
o = s:option(Value, "wireguard_private_key", translate("Private Key"))
o.datatype = "base64"
o:depends({ protocol = "wireguard" })
o = s:option(Value, "wireguard_public_key", translate("Public Key"))
o.datatype = "base64"
o:depends({ protocol = "wireguard" })
o = s:option(DummyValue, "gen_wireguard_key")
o.template = m:template_path("/server/gen_wireguard_key")
o:depends({ protocol = "wireguard" })
o = s:option(Flag, "firewall_allow", translate("Firewall Allow"))
o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
o.template = "cbi/firewall_zonelist"
o:depends({ custom = false, firewall_allow = true })
o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
o.default = "0"
o:depends({ custom = false })
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"],
group = e["group"]
}
end
end
o = s:option(ListValue, "outbound_node", translate("outbound node"))
o:value("", translate("Close"))
o:value("_socks", translate("Custom Socks"))
o:value("_http", translate("Custom HTTP"))
o:value("_iface", translate("Custom Interface"))
o.template = m:template_path("/cbi/nodes_listvalue")
o.group = {"","","",""}
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
o:depends({ custom = false })
o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)"))
o:depends({ outbound_node = "_socks"})
o:depends({ outbound_node = "_http"})
o = s:option(Value, "outbound_node_port", translate("Port"))
o.datatype = "port"
o:depends({ outbound_node = "_socks"})
o:depends({ outbound_node = "_http"})
o = s:option(Value, "outbound_node_username", translate("Username"))
o:depends({ outbound_node = "_socks"})
o:depends({ outbound_node = "_http"})
o = s:option(Value, "outbound_node_password", translate("Password"))
o.password = true
o:depends({ outbound_node = "_socks"})
o:depends({ outbound_node = "_http"})
o = s:option(Value, "outbound_node_iface", translate("Interface"))
o:depends({ outbound_node = "_iface"})
local netdev_list = api.get_network_devices()
for _, d in ipairs(netdev_list) do
o:value(d.name, d.label)
end
o = s:option(Flag, "log", translate("Log"))
o.default = "1"
o.rmempty = false
o = s:option(ListValue, "loglevel", translate("Log Level"))
o.default = "warning"
o:value("debug")
o:value("info")
o:value("warning")
o:value("error")
o:depends({ log = true })
api.type_cbi_section(s1, s)
@@ -1,93 +0,0 @@
if not api.is_finded("ssserver") then
return
end
-- [[ Shadowsocks Rust ]]
local m, s1 = ...
local type_name = "SS-Rust"
s1.fields["type"]:value(type_name, translate("Shadowsocks Rust"))
if not s1.val["type"] then
s1.val["type"] = type_name
end
if s1.val["type"] and s1.val["type"] ~= type_name then
return
end
local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name
s.option_prefix = "ssrust_"
api.set_type_cbi(s)
local ssrust_encrypt_method_list = {
"plain", "none",
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
}
o = s:option(Flag, "custom", translate("Use Custom Config"))
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
o:depends({ custom = true })
o.datatype = "json"
local o_validate = o.validate
o.validate = function(self, value)
local v = o_validate(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
end
end
o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "")
end
o = s:option(Value, "port", translate("Listen Port"))
o.datatype = "port"
o:depends({ custom = false })
o = s:option(ListValue, "user", translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ custom = false })
o = s:option(ListValue, "method", translate("Encrypt Method"))
for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
o:depends({ custom = false })
o = s:option(Value, "timeout", translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o:depends({ custom = false })
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
o.default = "0"
o:depends({ custom = false })
o = s:option(Flag, "firewall_allow", translate("Firewall Allow"))
o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
o.template = "cbi/firewall_zonelist"
o:depends({ custom = false, firewall_allow = true })
o = s:option(Flag, "log", translate("Log"))
o.default = "1"
o.rmempty = false
api.type_cbi_section(s1, s)
@@ -1,124 +0,0 @@
if not api.is_finded("ssr-server") then
return
end
-- [[ ShadowsocksR ]]
local m, s1 = ...
local type_name = "SSR"
s1.fields["type"]:value(type_name, translate("ShadowsocksR"))
if not s1.val["type"] then
s1.val["type"] = type_name
end
if s1.val["type"] and s1.val["type"] ~= type_name then
return
end
local s = NamedSection(m, arg[1], "tmp_" .. s1.sectiontype)
s.parent = s1
s.type_name = type_name
s.option_prefix = "ssr_"
api.set_type_cbi(s)
local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
"chacha20-ietf"
}
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"
}
local ssr_obfs_list = {
"plain", "http_simple", "http_post", "random_head", "tls_simple",
"tls1.0_session_auth", "tls1.2_ticket_auth"
}
o = s:option(Flag, "custom", translate("Use Custom Config"))
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
o:depends({ custom = true })
o.datatype = "json"
local o_validate = o.validate
o.validate = function(self, value)
local v = o_validate(self, value)
if v then return v end
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
end
o.cfgvalue = function(self, section)
local config_str = m:get(section, "config_str")
if config_str then
return api.base64Decode(config_str)
end
end
o.write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "")
end
o = s:option(Value, "port", translate("Listen Port"))
o.datatype = "port"
o:depends({ custom = false })
o = s:option(ListValue, "user", translate("User"))
for i, v in ipairs(user_list) do
o:value(v[".name"], v.username)
end
o:depends({ custom = false })
o = s:option(ListValue, "method", translate("Encrypt Method"))
for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
o:depends({ custom = false })
o = s:option(ListValue, "protocol", translate("Protocol"))
for a, t in ipairs(ssr_protocol_list) do o:value(t) end
o:depends({ custom = false })
o = s:option(Value, "protocol_param", translate("Protocol_param"))
o:depends({ custom = false })
o = s:option(ListValue, "obfs", translate("Obfs"))
for a, t in ipairs(ssr_obfs_list) do o:value(t) end
o:depends({ custom = false })
o = s:option(Value, "obfs_param", translate("Obfs_param"))
o:depends({ custom = false })
o = s:option(Value, "timeout", translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o:depends({ custom = false })
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
o.default = "0"
o:depends({ custom = false })
o = s:option(Flag, "udp_forward", translate("UDP Forward"))
o.default = "1"
o.rmempty = false
o = s:option(Flag, "firewall_allow", translate("Firewall Allow"))
o.default = "0"
o:depends({ custom = false })
o = s:option(Value, "firewall_allow_src", translate("Source zone"))
o.rmempty = not m.is_js_luci
o.nocreate = true
o.allowany = true
o.default = "wan"
o.template = "cbi/firewall_zonelist"
o:depends({ custom = false, firewall_allow = true })
o = s:option(Flag, "log", translate("Log"))
o.default = "1"
o.rmempty = false
api.type_cbi_section(s1, s)
@@ -1,54 +0,0 @@
api = require "luci.passwall2.api"
api.set_default_cbi()
m = Map(api.s_config)
m.redirect = api.url("server")
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
s = m:section(NamedSection, arg[1], "user", translate("User Config"))
s.addremove = false
s.dynamic = false
o = s:option(Value, "username", translate("Username"))
o.datatype = "and(uciname,maxlength(24))"
o.rmempty = false
function o.validate(self, value, section)
local exists = false
m:foreach("user", function(s)
if s[".name"] ~= section and s.username == value then
exists = true
return false
end
end)
if exists then
return nil, translate("This username already exists.")
end
return value
end
o = s:option(Value, "password", translate("Password"))
o.rmempty = false
o = s:option(Value, "uuid", "UUID")
o.datatype = "uuid"
o.default = api.gen_uuid()
o.rmempty = false
o = s:option(Value, "wireguard_public_key", "Wireguard " .. translate("Public Key"))
o.datatype = "base64"
o = s:option(Value, "wireguard_private_key", "Wireguard " .. translate("Private Key"))
o.datatype = "base64"
o = s:option(DummyValue, "gen_wireguard_key")
o.template = m:template_path("/server/gen_wireguard_key")
o = s:option(Value, "wireguard_pre_shared_key", "Wireguard " .. translate("Pre shared key"))
o.datatype = "base64"
o = s:option(DynamicList, "allowed_ips", "Wireguard " .. translate("Allowed IPs"))
return api.return_map(m)