mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-05 09:54:11 +08:00
update 2026-10-27 07:31:06
This commit is contained in:
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> " % 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> <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> <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)
|
||||
Reference in new issue
Block a user