luci-app-passwall: bump to 4-1

This commit is contained in:
CN_SZTL 2020-12-05 18:54:30 +08:00
parent c5811f6512
commit 98750d0a13
No known key found for this signature in database
GPG Key ID: 6850B6345C862176
39 changed files with 1784 additions and 374 deletions

View File

@ -6,9 +6,9 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=3.9
PKG_RELEASE:=72
PKG_DATE:=20201012
PKG_VERSION:=4
PKG_RELEASE:=1
PKG_DATE:=20201204
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
@ -33,9 +33,13 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Server
bool "Include ShadowsocksR Server"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
bool "Include Xray"
default y if i386||x86_64||arm||aarch64
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray
bool "Include V2ray"
default y if i386||x86_64||arm||aarch64
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus
bool "Include Trojan_Plus"
@ -48,7 +52,7 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_GO
config PACKAGE_$(PKG_NAME)_INCLUDE_Brook
bool "Include Brook"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
bool "Include NaiveProxy"
default n
@ -60,19 +64,19 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_kcptun
config PACKAGE_$(PKG_NAME)_INCLUDE_haproxy
bool "Include haproxy"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG
bool "Include ChinaDNS-NG"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_https_dns_proxy
bool "Include Https DNS Proxy(DoH)"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_dns2socks
bool "Include dns2socks"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_v2ray-plugin
bool "Include v2ray-plugin (Shadowsocks plugin)"
default y if i386||x86_64||arm||aarch64
@ -101,6 +105,7 @@ define Package/$(PKG_NAME)
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR:shadowsocksr-libev-alt \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR:shadowsocksr-libev-ssr-local \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Server:shadowsocksr-libev-server \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:xray \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray \
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus:trojan-plus \
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_GO:trojan-go \
@ -137,21 +142,20 @@ define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./root/etc/config/passwall $(1)/etc/config/passwall
$(INSTALL_CONF) ./root/etc/config/passwall_server $(1)/etc/config/passwall_server
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./root/etc/init.d/passwall $(1)/etc/init.d/passwall
$(INSTALL_BIN) ./root/etc/init.d/passwall_server $(1)/etc/init.d/passwall_server
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_CONF) ./root/etc/uci-defaults/* $(1)/etc/uci-defaults
$(INSTALL_DIR) $(1)/usr/share/passwall
cp -pR ./root/usr/share/passwall/* $(1)/usr/share/passwall
$(INSTALL_CONF) ./root/etc/config/passwall $(1)/usr/share/passwall/config.default
$(INSTALL_DIR) $(1)/usr/lib/lua/luci
cp -pR ./luasrc/* $(1)/usr/lib/lua/luci/
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n
po2lmo ./po/zh-cn/passwall.po $(1)/usr/lib/lua/luci/i18n/passwall.zh-cn.lmo
endef

View File

@ -4,8 +4,10 @@ local appname = "passwall"
local ucic = luci.model.uci.cursor()
local http = require "luci.http"
local util = require "luci.util"
local api = require "luci.model.cbi.passwall.api.api"
local kcptun = require "luci.model.cbi.passwall.api.kcptun"
local brook = require "luci.model.cbi.passwall.api.brook"
local xray = require "luci.model.cbi.passwall.api.xray"
local v2ray = require "luci.model.cbi.passwall.api.v2ray"
local trojan_go = require "luci.model.cbi.passwall.api.trojan_go"
@ -65,6 +67,8 @@ function index()
entry({"admin", "services", appname, "kcptun_update"}, call("kcptun_update")).leaf = true
entry({"admin", "services", appname, "brook_check"}, call("brook_check")).leaf = true
entry({"admin", "services", appname, "brook_update"}, call("brook_update")).leaf = true
entry({"admin", "services", appname, "xray_check"}, call("xray_check")).leaf = true
entry({"admin", "services", appname, "xray_update"}, call("xray_update")).leaf = true
entry({"admin", "services", appname, "v2ray_check"}, call("v2ray_check")).leaf = true
entry({"admin", "services", appname, "v2ray_update"}, call("v2ray_update")).leaf = true
entry({"admin", "services", appname, "trojan_go_check"}, call("trojan_go_check")).leaf = true
@ -239,10 +243,22 @@ function set_node()
end
function copy_node()
local e = {}
local section = luci.http.formvalue("section")
luci.http.prepare_content("application/json")
luci.http.write_json(e)
local uuid = api.gen_uuid()
ucic:section(appname, "nodes", uuid)
for k, v in pairs(ucic:get_all(appname, section)) do
local filter = k:find("%.")
if filter and filter == 1 then
else
xpcall(function()
ucic:set(appname, uuid, k, v)
end,
function(e)
end)
end
end
ucic:commit(appname)
luci.http.redirect(luci.dispatcher.build_url("admin", "services", appname, "node_config", uuid))
end
function clear_all_nodes()
@ -285,11 +301,11 @@ function check_port()
ucic:foreach(appname, "nodes", function(s)
local ret = ""
local tcp_socket
if (s.use_kcp and s.use_kcp == "1" and s.kcp_port) or
(s.v2ray_transport and s.v2ray_transport == "mkcp" and s.port) then
if (s.use_kcp and s.use_kcp == "1" and s.kcp_port) or (s.transport and s.transport == "mkcp" and s.port) then
else
local type = s.type
if type and type ~= "V2ray_balancing" and type ~= "V2ray_shunt" and
local protocol = s.protocol
if type and (protocol and protocol ~= "_balancing" and protocol ~= "_shunt") and
s.address and s.port and s.remarks then
node_name = "%s[%s] %s:%s" % {s.type, s.remarks, s.address, s.port}
tcp_socket = nixio.socket("inet", "stream")
@ -366,6 +382,25 @@ function brook_update()
http_write_json(json)
end
function xray_check()
local json = xray.to_check("")
http_write_json(json)
end
function xray_update()
local json = nil
local task = http.formvalue("task")
if task == "extract" then
json = xray.to_extract(http.formvalue("file"), http.formvalue("subfix"))
elseif task == "move" then
json = xray.to_move(http.formvalue("file"))
else
json = xray.to_download(http.formvalue("url"))
end
http_write_json(json)
end
function v2ray_check()
local json = v2ray.to_check("")
http_write_json(json)

View File

@ -19,7 +19,7 @@ function get_valid_nodes()
local nodes = {}
uci:foreach(appname, "nodes", function(e)
if e.type and e.remarks then
if e.type == "V2ray" and (e.protocol == "_balancing" or e.protocol == "_shunt") then
if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt") then
e.remarks_name = "%s[%s] " % {i18n.translatef(e.type .. e.protocol), e.remarks}
e.node_type = "special"
nodes[#nodes + 1] = e
@ -79,19 +79,55 @@ function get_customed_path(e)
end
function is_finded(e)
return luci.sys.exec('type -t -p "%s/%s" -p "/usr/bin/v2ray/%s" "%s"' % {get_customed_path(e), e, e, e}) ~= "" and true or false
return luci.sys.exec('type -t -p "%s/%s" "%s"' % {get_customed_path(e), e, e}) ~= "" and true or false
end
function get_xray_path()
local path = uci_get_type("global_app", "xray_file")
return path
end
function get_xray_version(file)
if file == nil then file = get_xray_path() end
chmod_755(file)
if fs.access(file) then
if file == get_xray_path() then
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
if fs.access("/tmp/psw_" .. md5) then
return sys.exec("cat /tmp/psw_" .. md5)
else
local version = sys.exec("echo -n $(%s -version | awk '{print $2}' | sed -n 1P)" % file)
sys.call("echo '" .. version .. "' > " .. "/tmp/psw_" .. md5)
return version
end
else
return sys.exec("echo -n $(%s -version | awk '{print $2}' | sed -n 1P)" % file)
end
end
return ""
end
function get_v2ray_path()
local path = uci_get_type("global_app", "v2ray_file")
return path .. "/v2ray"
return path
end
function get_v2ray_version(file)
if file == nil then file = get_v2ray_path() end
chmod_755(file)
if fs.access(file) then
return sys.exec("echo -n $(%s -version | awk '{print $2}' | sed -n 1P)" % file)
if file == get_v2ray_path() then
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
if fs.access("/tmp/psw_" .. md5) then
return sys.exec("cat /tmp/psw_" .. md5)
else
local version = sys.exec("echo -n $(%s -version | awk '{print $2}' | sed -n 1P)" % file)
sys.call("echo '" .. version .. "' > " .. "/tmp/psw_" .. md5)
return version
end
else
return sys.exec("echo -n $(%s -version | awk '{print $2}' | sed -n 1P)" % file)
end
end
return ""
end
@ -105,7 +141,18 @@ function get_trojan_go_version(file)
if file == nil then file = get_trojan_go_path() end
chmod_755(file)
if fs.access(file) then
return sys.exec("echo -n $(%s -version | awk '{print $2}' | sed -n 1P)" % file)
if file == get_trojan_go_path() then
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
if fs.access("/tmp/psw_" .. md5) then
return sys.exec("cat /tmp/psw_" .. md5)
else
local version = sys.exec("echo -n $(%s -version | awk '{print $2}' | sed -n 1P)" % file)
sys.call("echo '" .. version .. "' > " .. "/tmp/psw_" .. md5)
return version
end
else
return sys.exec("echo -n $(%s -version | awk '{print $2}' | sed -n 1P)" % file)
end
end
return ""
end
@ -119,7 +166,18 @@ function get_kcptun_version(file)
if file == nil then file = get_kcptun_path() end
chmod_755(file)
if fs.access(file) then
return sys.exec("echo -n $(%s -v | awk '{print $3}')" % file)
if file == get_kcptun_path() then
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
if fs.access("/tmp/psw_" .. md5) then
return sys.exec("cat /tmp/psw_" .. md5)
else
local version = sys.exec("echo -n $(%s -v | awk '{print $3}')" % file)
sys.call("echo '" .. version .. "' > " .. "/tmp/psw_" .. md5)
return version
end
else
return sys.exec("echo -n $(%s -v | awk '{print $3}')" % file)
end
end
return ""
end
@ -133,11 +191,38 @@ function get_brook_version(file)
if file == nil then file = get_brook_path() end
chmod_755(file)
if fs.access(file) then
return sys.exec("echo -n $(%s -v | awk '{print $3}')" % file)
if file == get_brook_path() then
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
if fs.access("/tmp/psw_" .. md5) then
return sys.exec("cat /tmp/psw_" .. md5)
else
local version = sys.exec("echo -n $(%s -v | awk '{print $3}')" % file)
sys.call("echo '" .. version .. "' > " .. "/tmp/psw_" .. md5)
return version
end
else
return sys.exec("echo -n $(%s -v | awk '{print $3}')" % file)
end
end
return ""
end
function get_free_space(dir)
if dir == nil then dir = "/" end
if sys.call("df -k " .. dir .. " >/dev/null") == 0 then
return tonumber(sys.exec("echo -n $(df -k " .. dir .. " | awk 'NR>1' | awk '{print $4}')"))
end
return 0
end
function get_file_space(file)
if file == nil then return 0 end
if fs.access(file) then
return tonumber(sys.exec("echo -n $(du -k " .. file .. " | awk '{print $1}')"))
end
return 0
end
function _unpack(t, i)
i = i or 1
if t[i] ~= nil then return t[i], _unpack(t, i + 1) end

View File

@ -8,6 +8,13 @@ local api = require "luci.model.cbi.passwall.api.api"
local brook_api = "https://api.github.com/repos/txthinking/brook/releases/latest"
function to_check(arch)
local app_path = api.get_brook_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Brook")
}
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
@ -15,8 +22,7 @@ function to_check(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate(
"Can't determine ARCH, or ARCH not supported.")
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
@ -66,6 +72,13 @@ function to_check(arch)
end
function to_download(url)
local app_path = api.get_brook_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Brook")
}
end
if not url or url == "" then
return {code = 1, error = i18n.translate("Download url is required.")}
end
@ -88,13 +101,20 @@ function to_download(url)
end
function to_move(file)
local app_path = api.get_brook_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Brook")
}
end
if not file or file == "" or not fs.access(file) then
sys.call("/bin/rm -rf /tmp/brook_download.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local version = api.get_brook_version(file)
if version == "" then
local new_version = api.get_brook_version(file)
if new_version == "" then
sys.call("/bin/rm -rf /tmp/brook_download.*")
return {
code = 1,
@ -102,30 +122,29 @@ function to_move(file)
}
end
local client_file = api.get_brook_path()
local client_file_bak
local app_path_bak
if fs.access(client_file) then
client_file_bak = client_file .. ".bak"
api.exec("/bin/mv", {"-f", client_file, client_file_bak})
if fs.access(app_path) then
app_path_bak = app_path .. ".bak"
api.exec("/bin/mv", {"-f", app_path, app_path_bak})
end
local result = api.exec("/bin/mv", {"-f", file, client_file}, nil, api.command_timeout) == 0
local result = api.exec("/bin/mv", {"-f", file, app_path}, nil, api.command_timeout) == 0
if not result or not fs.access(client_file) then
if not result or not fs.access(app_path) then
sys.call("/bin/rm -rf /tmp/brook_download.*")
if client_file_bak then
api.exec("/bin/mv", {"-f", client_file_bak, client_file})
if app_path_bak then
api.exec("/bin/mv", {"-f", app_path_bak, app_path})
end
return {
code = 1,
error = i18n.translatef("Can't move new file to path: %s", client_file)
error = i18n.translatef("Can't move new file to path: %s", app_path)
}
end
api.exec("/bin/chmod", {"755", client_file})
api.exec("/bin/chmod", {"755", app_path})
if client_file_bak then api.exec("/bin/rm", {"-f", client_file_bak}) end
if app_path_bak then api.exec("/bin/rm", {"-f", app_path_bak}) end
sys.call("/bin/rm -rf /tmp/brook_download.*")

View File

@ -56,10 +56,14 @@ function gen_outbound(node, tag, relay_port)
node.address = "127.0.0.1"
end
node.stream_security = "none"
end
if node.transport == "mkcp" or node.transport == "quic" then
node.stream_security = "none"
else
if node.tls and node.tls == "1" then
node.stream_security = "tls"
end
if node.transport == "mkcp" or node.transport == "quic" then
node.stream_security = "none"
end
end
result = {
@ -73,10 +77,6 @@ function gen_outbound(node, tag, relay_port)
streamSettings = (node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
network = node.transport,
security = node.stream_security,
xtlsSettings = (node.stream_security == "xtls") and {
serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false
} or nil,
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false
@ -265,7 +265,10 @@ if node then
end
end
routing = {domainStrategy = "IPOnDemand", rules = rules}
routing = {
domainStrategy = node.domainStrategy or "AsIs",
rules = rules
}
elseif node.protocol == "_balancing" then
if node.balancing_node then
@ -277,7 +280,7 @@ if node then
if outbound then table.insert(outbounds, outbound) end
end
routing = {
domainStrategy = "IPOnDemand",
domainStrategy = node.domainStrategy or "AsIs",
balancers = {{tag = "balancer", selector = nodes}},
rules = {
{type = "field", network = "tcp,udp", balancerTag = "balancer"}

View File

@ -0,0 +1,318 @@
module("luci.model.cbi.passwall.api.gen_xray", package.seeall)
local ucursor = require"luci.model.uci".cursor()
local sys = require "luci.sys"
local json = require "luci.jsonc"
local appname = "passwall"
local inbounds = {}
local outbounds = {}
local routing = nil
local node_section = arg[1] or "nil"
local proto = arg[2]
local redir_port = arg[3]
local socks_proxy_port = arg[4]
local node = ucursor:get_all(appname, node_section)
local network = proto
local new_port
local function get_new_port()
if new_port then
new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port %s tcp)", appname, new_port + 1)))
else
new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port auto tcp)", appname)))
end
return new_port
end
function gen_outbound(node, tag, relay_port)
local result = nil
if node then
local node_id = node[".name"]
if tag == nil then
tag = node_id
end
if node.type ~= "Xray" then
if node.type == "Socks" then
node.protocol = "socks"
node.transport = "tcp"
else
local node_type = (proto and proto ~= "nil") and proto or "socks"
new_port = get_new_port()
node.port = new_port
sys.call(string.format('/usr/share/%s/app.sh run_socks "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s"> /dev/null',
appname,
node_id,
"127.0.0.1",
new_port,
string.format("/var/etc/%s/v2_%s_%s.json", appname, node_type, node_id),
"0",
"nil",
"4",
relay_port and tostring(relay_port) or ""
)
)
node.protocol = "socks"
node.transport = "tcp"
node.address = "127.0.0.1"
end
node.stream_security = "none"
else
if node.tls and node.tls == "1" then
node.stream_security = "tls"
if node.xtls and node.xtls == "1" then
node.stream_security = "xtls"
end
end
if node.transport == "mkcp" or node.transport == "quic" then
node.stream_security = "none"
end
end
result = {
tag = tag,
protocol = node.protocol,
mux = (node.stream_security ~= "xtls") and {
enabled = (node.mux == "1") and true or false,
concurrency = (node.mux_concurrency) and tonumber(node.mux_concurrency) or 8
} or nil,
-- 底层传输配置
streamSettings = (node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
network = node.transport,
security = node.stream_security,
xtlsSettings = (node.stream_security == "xtls") and {
serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false
} or nil,
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false
} or nil,
tcpSettings = (node.transport == "tcp" and node.protocol ~= "socks") and {
header = {
type = node.tcp_guise,
request = (node.tcp_guise == "http") and {
path = node.tcp_guise_http_path or {"/"},
headers = {
Host = node.tcp_guise_http_host or {}
}
} or nil
}
} or nil,
kcpSettings = (node.transport == "mkcp") and {
mtu = tonumber(node.mkcp_mtu),
tti = tonumber(node.mkcp_tti),
uplinkCapacity = tonumber(node.mkcp_uplinkCapacity),
downlinkCapacity = tonumber(node.mkcp_downlinkCapacity),
congestion = (node.mkcp_congestion == "1") and true or false,
readBufferSize = tonumber(node.mkcp_readBufferSize),
writeBufferSize = tonumber(node.mkcp_writeBufferSize),
seed = (node.mkcp_seed and node.mkcp_seed ~= "") and node.mkcp_seed or nil,
header = {type = node.mkcp_guise}
} or nil,
wsSettings = (node.transport == "ws") and {
path = node.ws_path or "",
headers = (node.ws_host ~= nil) and
{Host = node.ws_host} or nil
} or nil,
httpSettings = (node.transport == "h2") and
{path = node.h2_path, host = node.h2_host} or
nil,
dsSettings = (node.transport == "ds") and
{path = node.ds_path} or nil,
quicSettings = (node.transport == "quic") and {
security = node.quic_security,
key = node.quic_key,
header = {type = node.quic_guise}
} or nil
} or nil,
settings = {
vnext = (node.protocol == "vmess" or node.protocol == "vless") and {
{
address = node.address,
port = tonumber(node.port),
users = {
{
id = node.uuid,
alterId = tonumber(node.alter_id),
level = node.level and tonumber(node.level) or 0,
security = (node.protocol == "vmess") and node.security or nil,
encryption = node.encryption or "none",
flow = node.flow or nil
}
}
}
} or nil,
servers = (node.protocol == "socks" or node.protocol == "http" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
{
address = node.address,
port = tonumber(node.port),
method = node.method or nil,
password = node.password or "",
users = (node.username and node.password) and
{{user = node.username, pass = node.password}} or nil
}
} or nil
}
}
end
return result
end
if node then
if socks_proxy_port ~= "nil" then
table.insert(inbounds, {
listen = "0.0.0.0",
port = tonumber(socks_proxy_port),
protocol = "socks",
settings = {auth = "noauth", udp = true, ip = "127.0.0.1"}
})
network = "tcp,udp"
end
if redir_port ~= "nil" then
table.insert(inbounds, {
port = tonumber(redir_port),
protocol = "dokodemo-door",
settings = {network = proto, followRedirect = true},
sniffing = {enabled = true, destOverride = {"http", "tls"}}
})
if proto == "tcp" and node.tcp_socks == "1" then
table.insert(inbounds, {
listen = "0.0.0.0",
port = tonumber(node.tcp_socks_port),
protocol = "socks",
settings = {
auth = node.tcp_socks_auth,
accounts = (node.tcp_socks_auth == "password") and {
{
user = node.tcp_socks_auth_username,
pass = node.tcp_socks_auth_password
}
} or nil,
udp = true
}
})
end
end
if node.protocol == "_shunt" then
local rules = {}
ucursor:foreach(appname, "shunt_rules", function(e)
local name = e[".name"]
local _node_id = node[name] or nil
if _node_id and _node_id ~= "nil" then
local _node = ucursor:get_all(appname, _node_id)
local is_proxy = node[name .. "_proxy"]
local relay_port
if is_proxy and is_proxy == "1" then
new_port = get_new_port()
relay_port = new_port
table.insert(inbounds, {
tag = "proxy_" .. name,
listen = "127.0.0.1",
port = new_port,
protocol = "dokodemo-door",
settings = {network = "tcp,udp", address = _node.address, port = tonumber(_node.port)}
})
if _node.tls_serverName == nil then
_node.tls_serverName = _node.address
end
_node.address = "127.0.0.1"
_node.port = new_port
end
local _outbound = gen_outbound(_node, name, relay_port)
if _outbound then
table.insert(outbounds, _outbound)
if is_proxy and is_proxy == "1" then
table.insert(rules, {
type = "field",
inboundTag = {"proxy_" .. name},
outboundTag = "default"
})
end
if e.domain_list then
local _domain = {}
string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w)
table.insert(_domain, w)
end)
table.insert(rules, {
type = "field",
outboundTag = name,
domain = _domain
})
end
if e.ip_list then
local _ip = {}
string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w)
table.insert(_ip, w)
end)
table.insert(rules, {
type = "field",
outboundTag = name,
ip = _ip
})
end
end
end
end)
local default_node_id = node.default_node or nil
if default_node_id and default_node_id ~= "nil" then
local default_node = ucursor:get_all(appname, default_node_id)
local default_outbound = gen_outbound(default_node, "default")
if default_outbound then
table.insert(outbounds, default_outbound)
local rule = {
type = "field",
outboundTag = "default",
network = network
}
table.insert(rules, rule)
end
end
routing = {
domainStrategy = node.domainStrategy or "AsIs",
rules = rules
}
elseif node.protocol == "_balancing" then
if node.balancing_node then
local nodes = node.balancing_node
local length = #nodes
for i = 1, length do
local node = ucursor:get_all(appname, nodes[i])
local outbound = gen_outbound(node)
if outbound then table.insert(outbounds, outbound) end
end
routing = {
domainStrategy = node.domainStrategy or "AsIs",
balancers = {{tag = "balancer", selector = nodes}},
rules = {
{type = "field", network = "tcp,udp", balancerTag = "balancer"}
}
}
end
else
local outbound = gen_outbound(node)
if outbound then table.insert(outbounds, outbound) end
end
-- 额外传出连接
table.insert(outbounds, {protocol = "freedom", tag = "direct", settings = {keep = ""}})
local xray = {
log = {
-- error = string.format("/var/etc/passwall/%s.log", node[".name"]),
loglevel = "warning"
},
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
-- 路由
routing = routing
}
print(json.stringify(xray, 1))
end

View File

@ -0,0 +1,80 @@
local json = require "luci.jsonc"
local inbounds = {}
local outbounds = {}
local routing = nil
local local_proto = arg[1]
local local_address = arg[2]
local local_port = arg[3]
local server_proto = arg[4]
local server_address = arg[5]
local server_port = arg[6]
local server_username = arg[7] or "nil"
local server_password = arg[8] or "nil"
function gen_outbound(proto, address, port, username, password)
local result = {
protocol = proto,
streamSettings = {
network = "tcp",
security = "none"
},
settings = {
servers = {
{
address = address,
port = tonumber(port),
users = (username ~= "nil" and password ~= "nil") and {
{
user = username,
pass = password
}
} or nil
}
}
}
}
return result
end
if local_proto ~= "nil" and local_address ~= "nil" and local_port ~= "nil" then
local inbound = {
listen = local_address,
port = tonumber(local_port),
protocol = local_proto,
settings = {
accounts = nil
}
}
if local_proto == "socks" then
inbound.settings.auth = "noauth"
inbound.settings.udp = true
elseif local_proto == "http" then
inbound.settings.allowTransparent = false
end
table.insert(inbounds, inbound)
end
if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then
local outbound = gen_outbound(server_proto, server_address, server_port, server_username, server_password)
if outbound then table.insert(outbounds, outbound) end
end
-- 额外传出连接
table.insert(outbounds, {
protocol = "freedom", tag = "direct", settings = {keep = ""}
})
local xray = {
log = {
-- error = string.format("/var/etc/passwall/%s.log", node[".name"]),
loglevel = "warning"
},
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
-- 路由
routing = routing
}
print(json.stringify(xray, 1))

View File

@ -7,30 +7,14 @@ local api = require "luci.model.cbi.passwall.api.api"
local kcptun_api = "https://api.github.com/repos/xtaci/kcptun/releases/latest"
function get_kcptun_file_path()
return api.uci_get_type("global_app", "kcptun_client_file")
end
function get_kcptun_version(file)
if file == nil then file = get_kcptun_file_path() end
if file and file ~= "" then
if not fs.access(file, "rwx", "rx", "rx") then
fs.chmod(file, 755)
end
local info = util.trim(sys.exec("%s -v 2>/dev/null" % file))
if info ~= "" then
local tb = util.split(info, "%s+", nil, true)
return tb[1] == "kcptun" and tb[3] or ""
end
end
return ""
end
function to_check(arch)
local app_path = api.get_kcptun_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Kcptun")
}
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
@ -38,8 +22,7 @@ function to_check(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate(
"Can't determine ARCH, or ARCH not supported.")
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
@ -52,12 +35,9 @@ function to_check(arch)
}
end
local now_version = api.get_kcptun_version()
local remote_version = json.tag_name:match("[^v]+")
local client_file = get_kcptun_file_path()
local needs_update = api.compare_versions(get_kcptun_version(client_file),
"<", remote_version)
local needs_update = api.compare_versions(now_version, "<", remote_version)
local html_url, download_url
if needs_update then
@ -73,24 +53,30 @@ function to_check(arch)
if needs_update and not download_url then
return {
code = 1,
now_version = get_kcptun_version(client_file),
now_version = now_version,
version = remote_version,
html_url = html_url,
error = i18n.translate(
"New version found, but failed to get new version download url.")
error = i18n.translate("New version found, but failed to get new version download url.")
}
end
return {
code = 0,
update = needs_update,
now_version = get_kcptun_version(client_file),
now_version = now_version,
version = remote_version,
url = {html = html_url, download = download_url}
}
end
function to_download(url)
local app_path = api.get_kcptun_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Kcptun")
}
end
if not url or url == "" then
return {code = 1, error = i18n.translate("Download url is required.")}
end
@ -113,6 +99,13 @@ function to_download(url)
end
function to_extract(file, subfix)
local app_path = api.get_kcptun_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Kcptun")
}
end
if not file or file == "" or not fs.access(file) then
return {code = 1, error = i18n.translate("File path required.")}
end
@ -157,47 +150,50 @@ function to_extract(file, subfix)
end
function to_move(file)
local app_path = api.get_kcptun_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Kcptun")
}
end
if not file or file == "" or not fs.access(file) then
sys.call("/bin/rm -rf /tmp/kcptun_extract.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local version = get_kcptun_version(file)
if version == "" then
local new_version = api.get_kcptun_version(file)
if new_version == "" then
sys.call("/bin/rm -rf /tmp/kcptun_extract.*")
return {
code = 1,
error = i18n.translate(
"The client file is not suitable for current device.")
error = i18n.translate("The client file is not suitable for current device.")
}
end
local client_file = get_kcptun_file_path()
local client_file_bak
local app_path_bak
if fs.access(client_file) then
client_file_bak = client_file .. ".bak"
api.exec("/bin/mv", {"-f", client_file, client_file_bak})
if fs.access(app_path) then
app_path_bak = app_path .. ".bak"
api.exec("/bin/mv", {"-f", app_path, app_path_bak})
end
local result = api.exec("/bin/mv", {"-f", file, client_file}, nil,
api.command_timeout) == 0
local result = api.exec("/bin/mv", {"-f", file, app_path}, nil, api.command_timeout) == 0
if not result or not fs.access(client_file) then
if not result or not fs.access(app_path) then
sys.call("/bin/rm -rf /tmp/kcptun_extract.*")
if client_file_bak then
api.exec("/bin/mv", {"-f", client_file_bak, client_file})
if app_path_bak then
api.exec("/bin/mv", {"-f", app_path_bak, app_path})
end
return {
code = 1,
error = i18n.translatef("Can't move new file to path: %s",
client_file)
error = i18n.translatef("Can't move new file to path: %s", app_path)
}
end
api.exec("/bin/chmod", {"755", client_file})
api.exec("/bin/chmod", {"755", app_path})
if client_file_bak then api.exec("/bin/rm", {"-f", client_file_bak}) end
if app_path_bak then api.exec("/bin/rm", {"-f", app_path_bak}) end
sys.call("/bin/rm -rf /tmp/kcptun_extract.*")

View File

@ -3,12 +3,18 @@ local fs = require "nixio.fs"
local sys = require "luci.sys"
local util = require "luci.util"
local i18n = require "luci.i18n"
local ipkg = require("luci.model.ipkg")
local api = require "luci.model.cbi.passwall.api.api"
local trojan_go_api = api.uci_get_type("global_app", "trojan_go_latest", "https://api.github.com/repos/trojan-gfw/trojan-go/releases/latest")
function to_check(arch)
local app_path = api.get_trojan_go_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Trojan-GO")
}
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
@ -16,8 +22,7 @@ function to_check(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate(
"Can't determine ARCH, or ARCH not supported.")
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
@ -59,8 +64,7 @@ function to_check(arch)
now_version = now_version,
version = remote_version,
html_url = html_url,
error = i18n.translate(
"New version found, but failed to get new version download url.") .. " [linux-" .. file_tree .. ".zip]"
error = i18n.translate("New version found, but failed to get new version download url.") .. " [linux-" .. file_tree .. ".zip]"
}
end
@ -74,6 +78,13 @@ function to_check(arch)
end
function to_download(url)
local app_path = api.get_trojan_go_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Trojan-GO")
}
end
if not url or url == "" then
return {code = 1, error = i18n.translate("Download url is required.")}
end
@ -96,10 +107,19 @@ function to_download(url)
end
function to_extract(file, subfix)
local isinstall_unzip = ipkg.installed("unzip")
if isinstall_unzip == nil then
ipkg.update()
ipkg.install("unzip")
local app_path = api.get_trojan_go_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Trojan-GO")
}
end
if sys.exec("echo -n $(opkg list-installed | grep -c unzip)") ~= "1" then
api.exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed unzip, Can't unzip!")
}
end
if not file or file == "" or not fs.access(file) then
@ -121,29 +141,35 @@ function to_extract(file, subfix)
end
function to_move(file)
local app_path = api.get_trojan_go_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Trojan-GO")
}
end
if not file or file == "" then
sys.call("/bin/rm -rf /tmp/trojan-go_extract.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local client_file = api.get_trojan_go_path()
local client_file_bak
local app_path_bak
if fs.access(client_file) then
client_file_bak = client_file .. ".bak"
api.exec("/bin/mv", {"-f", client_file, client_file_bak})
if fs.access(app_path) then
app_path_bak = app_path .. ".bak"
api.exec("/bin/mv", {"-f", app_path, app_path_bak})
end
local result = api.exec("/bin/mv", { "-f", file .. "/trojan-go", client_file }, nil, api.command_timeout) == 0
local result = api.exec("/bin/mv", { "-f", file .. "/trojan-go", app_path }, nil, api.command_timeout) == 0
sys.call("/bin/rm -rf /tmp/trojan-go_extract.*")
if not result or not fs.access(client_file) then
if not result or not fs.access(app_path) then
return {
code = 1,
error = i18n.translatef("Can't move new file to path: %s", client_file)
error = i18n.translatef("Can't move new file to path: %s", app_path)
}
end
api.exec("/bin/chmod", {"-R", "755", client_file})
api.exec("/bin/chmod", {"-R", "755", app_path})
return {code = 0}
end

View File

@ -3,26 +3,19 @@ local fs = require "nixio.fs"
local sys = require "luci.sys"
local util = require "luci.util"
local i18n = require "luci.i18n"
local ipkg = require("luci.model.ipkg")
local api = require "luci.model.cbi.passwall.api.api"
local v2ray_api = "https://api.github.com/repos/v2fly/v2ray-core/releases/latest"
local is_armv7 = false
function get_v2ray_file_path()
return api.uci_get_type("global_app", "v2ray_file")
end
function get_v2ray_version()
if get_v2ray_file_path() and get_v2ray_file_path() ~= "" then
if fs.access(get_v2ray_file_path() .. "/v2ray") then
return sys.exec("echo -n $(" .. get_v2ray_file_path() .. "/v2ray -version | awk '{print $2}' | sed -n 1P" .. ")")
end
end
return ""
end
function to_check(arch)
local app_path = api.get_v2ray_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "V2ray")
}
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
@ -31,8 +24,7 @@ function to_check(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate(
"Can't determine ARCH, or ARCH not supported.")
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
@ -48,8 +40,9 @@ function to_check(arch)
}
end
local now_version = api.get_v2ray_version()
local remote_version = json.tag_name:match("[^v]+")
local needs_update = api.compare_versions(get_v2ray_version(), "<", remote_version)
local needs_update = api.compare_versions(now_version, "<", remote_version)
local html_url, download_url
if needs_update then
@ -65,24 +58,30 @@ function to_check(arch)
if needs_update and not download_url then
return {
code = 1,
now_version = get_v2ray_version(),
now_version = now_version,
version = remote_version,
html_url = html_url,
error = i18n.translate(
"New version found, but failed to get new version download url.")
error = i18n.translate("New version found, but failed to get new version download url.")
}
end
return {
code = 0,
update = needs_update,
now_version = get_v2ray_version(),
now_version = now_version,
version = remote_version,
url = {html = html_url, download = download_url}
}
end
function to_download(url)
local app_path = api.get_v2ray_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "V2ray")
}
end
if not url or url == "" then
return {code = 1, error = i18n.translate("Download url is required.")}
end
@ -105,16 +104,26 @@ function to_download(url)
end
function to_extract(file, subfix)
local isinstall_unzip = ipkg.installed("unzip")
if isinstall_unzip == nil then
ipkg.update()
ipkg.install("unzip")
local app_path = api.get_v2ray_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "V2ray")
}
end
if not file or file == "" or not fs.access(file) then
return {code = 1, error = i18n.translate("File path required.")}
end
if sys.exec("echo -n $(opkg list-installed | grep -c unzip)") ~= "1" then
api.exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed unzip, Can't unzip!")
}
end
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
local tmp_dir = util.trim(util.exec("mktemp -d -t v2ray_extract.XXXXXX"))
@ -130,37 +139,35 @@ function to_extract(file, subfix)
end
function to_move(file)
local app_path = api.get_v2ray_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "V2ray")
}
end
if not file or file == "" then
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local client_file = get_v2ray_file_path()
sys.call("mkdir -p " .. client_file)
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
if sub_version == "7" then is_armv7 = true end
local t = ""
sys.call("/etc/init.d/passwall stop")
local result = nil
if is_armv7 and is_armv7 == true then
result = api.exec("/bin/mv", {
"-f", file .. "/v2ray_armv7", file .. "/v2ctl_armv7", client_file
}, nil, api.command_timeout) == 0
else
result = api.exec("/bin/mv", {
"-f", file .. "/v2ray", file .. "/v2ctl", client_file
}, nil, api.command_timeout) == 0
end
if sub_version and sub_version == "7" then t = "_armv7" end
result = api.exec("/bin/mv", { "-f", file .. "/v2ray" .. t, app_path }, nil, api.command_timeout) == 0
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
if not result or not fs.access(client_file) then
if not result or not fs.access(app_path) then
return {
code = 1,
error = i18n.translatef("Can't move new file to path: %s", client_file)
error = i18n.translatef("Can't move new file to path: %s", app_path)
}
end
api.exec("/bin/chmod", {"-R", "755", client_file})
api.chmod_755(app_path)
sys.call("/etc/init.d/passwall restart >/dev/null 2>&1 &")
return {code = 0}
end

View File

@ -0,0 +1,173 @@
module("luci.model.cbi.passwall.api.xray", package.seeall)
local fs = require "nixio.fs"
local sys = require "luci.sys"
local util = require "luci.util"
local i18n = require "luci.i18n"
local api = require "luci.model.cbi.passwall.api.api"
local xray_api = "https://api.github.com/repos/XTLS/Xray-core/releases/latest"
local is_armv7 = false
function to_check(arch)
local app_path = api.get_xray_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Xray")
}
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
if sub_version == "7" then is_armv7 = true end
if file_tree == "" then
return {
code = 1,
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
if file_tree == "amd64" then file_tree = "64" end
if file_tree == "386" then file_tree = "32" end
local json = api.get_api_json(xray_api)
if json.tag_name == nil then
return {
code = 1,
error = i18n.translate("Get remote version info failed.")
}
end
local now_version = api.get_xray_version()
local remote_version = json.tag_name:match("[^v]+")
local needs_update = api.compare_versions(now_version, "<", remote_version)
local html_url, download_url
if needs_update then
html_url = json.html_url
for _, v in ipairs(json.assets) do
if v.name and v.name:match("linux%-" .. file_tree) then
download_url = v.browser_download_url
break
end
end
end
if needs_update and not download_url then
return {
code = 1,
now_version = now_version,
version = remote_version,
html_url = html_url,
error = i18n.translate("New version found, but failed to get new version download url.")
}
end
return {
code = 0,
update = needs_update,
now_version = now_version,
version = remote_version,
url = {html = html_url, download = download_url}
}
end
function to_download(url)
local app_path = api.get_xray_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Xray")
}
end
if not url or url == "" then
return {code = 1, error = i18n.translate("Download url is required.")}
end
sys.call("/bin/rm -f /tmp/xray_download.*")
local tmp_file = util.trim(util.exec("mktemp -u -t xray_download.XXXXXX"))
local result = api.exec(api.curl, {api._unpack(api.curl_args), "-o", tmp_file, url}, nil, api.command_timeout) == 0
if not result then
api.exec("/bin/rm", {"-f", tmp_file})
return {
code = 1,
error = i18n.translatef("File download failed or timed out: %s", url)
}
end
return {code = 0, file = tmp_file}
end
function to_extract(file, subfix)
local app_path = api.get_xray_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Xray")
}
end
if not file or file == "" or not fs.access(file) then
return {code = 1, error = i18n.translate("File path required.")}
end
if sys.exec("echo -n $(opkg list-installed | grep -c unzip)") ~= "1" then
api.exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed unzip, Can't unzip!")
}
end
sys.call("/bin/rm -rf /tmp/xray_extract.*")
local tmp_dir = util.trim(util.exec("mktemp -d -t xray_extract.XXXXXX"))
local output = {}
api.exec("/usr/bin/unzip", {"-o", file, "-d", tmp_dir},
function(chunk) output[#output + 1] = chunk end)
local files = util.split(table.concat(output))
api.exec("/bin/rm", {"-f", file})
return {code = 0, file = tmp_dir}
end
function to_move(file)
local app_path = api.get_xray_path() or ""
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Xray")
}
end
if not file or file == "" then
sys.call("/bin/rm -rf /tmp/xray_extract.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
local t = ""
sys.call("/etc/init.d/passwall stop")
local result = nil
if sub_version and sub_version == "7" then t = "_armv7" end
result = api.exec("/bin/mv", { "-f", file .. "/xray" .. t, app_path }, nil, api.command_timeout) == 0
sys.call("/bin/rm -rf /tmp/xray_extract.*")
if not result or not fs.access(app_path) then
return {
code = 1,
error = i18n.translatef("Can't move new file to path: %s", app_path)
}
end
api.chmod_755(app_path)
sys.call("/etc/init.d/passwall restart >/dev/null 2>&1 &")
return {code = 0}
end

View File

@ -9,39 +9,39 @@ s = m:section(TypedSection, "global_app", translate("App Update"),
translate("Please confirm that your firmware supports FPU.") ..
"</font>")
s.anonymous = true
s:append(Template(appname .. "/app_update/xray_version"))
s:append(Template(appname .. "/app_update/v2ray_version"))
s:append(Template(appname .. "/app_update/trojan_go_version"))
s:append(Template(appname .. "/app_update/kcptun_version"))
s:append(Template(appname .. "/app_update/brook_version"))
---- V2ray Path
o = s:option(Value, "v2ray_file", translate("V2ray Path"), translatef("if you want to run from memory, change the path, such as %s, Then save the application and update it manually.", "/tmp/v2ray/"))
o.default = "/usr/bin/v2ray/"
o = s:option(Value, "xray_file", translatef("%s App Path", "Xray"))
o.default = "/usr/bin/xray"
o.rmempty = false
---- Trojan-Go Path
o = s:option(Value, "trojan_go_file", translate("Trojan-Go Path"), translatef("if you want to run from memory, change the path, such as %s, Then save the application and update it manually.", "/tmp/trojan-go"))
o = s:option(Value, "v2ray_file", translatef("%s App Path", "V2ray"))
o.default = "/usr/bin/v2ray"
o.rmempty = false
o = s:option(Value, "trojan_go_file", translatef("%s App Path", "Trojan-Go"))
o.default = "/usr/bin/trojan-go"
o.rmempty = false
o = s:option(Value, "trojan_go_latest", translate("Trojan-Go Version API"), translate("alternate API URL for version checking"))
o = s:option(Value, "trojan_go_latest", translatef("Trojan-Go Version API"), translate("alternate API URL for version checking"))
o.default = "https://api.github.com/repos/peter-tank/trojan-go/releases/latest"
---- Kcptun client Path
o = s:option(Value, "kcptun_client_file", translate("Kcptun Client Path"), translatef("if you want to run from memory, change the path, such as %s, Then save the application and update it manually.", "/tmp/kcptun-client"))
o = s:option(Value, "kcptun_client_file", translatef("%s Client App Path", "Kcptun"))
o.default = "/usr/bin/kcptun-client"
o.rmempty = false
--[[
o = s:option(Button, "_check_kcptun", translate("Manually update"), translatef("Make sure there is enough space to install %s", "kcptun"))
o.template = appname .. "/kcptun"
o.inputstyle = "apply"
o.btnclick = "onBtnClick_kcptun(this);"
o.id = "_kcptun-check_btn"]] --
---- Brook Path
o = s:option(Value, "brook_file", translate("Brook Path"), translatef("if you want to run from memory, change the path, such as %s, Then save the application and update it manually.", "/tmp/brook"))
o = s:option(Value, "brook_file", translatef("%s App Path", "Brook"))
o.default = "/usr/bin/brook"
o.rmempty = false
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 m

View File

@ -39,6 +39,8 @@ for i = 1, tcp_node_num, 1 do
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
end
o = s:option(Flag, "restore_switch" .. i, "TCP " .. i .. " " .. translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
end
return m

View File

@ -76,17 +76,17 @@ o.rmempty = false
---- TCP Node
local tcp_node_num = tonumber(m:get("@global_other[0]", "tcp_node_num") or 1)
for i = 1, tcp_node_num, 1 do
o = s:taboption("Main", ListValue, "tcp_node" .. i, translate("TCP Node") .. " " .. i)
if i == 1 then
o = s:taboption("Main", ListValue, "tcp_node" .. i, translate("TCP Node"))
o.description = translate("For proxy specific list.")
o.title = translate("TCP Node")
o.description = translate("For proxy specific list.") .. o.description
if tonumber(m:get("@auto_switch[0]", "enable") or 0) == 1 then
local now_node = luci.sys.exec(string.format("[ -f '/var/etc/%s/id/TCP_%s' ] && echo -n $(cat /var/etc/%s/id/TCP_%s)", appname, i, appname, i))
if now_node and now_node ~= "" then
local e = uci:get_all(appname, now_node)
local current_node = luci.sys.exec(string.format("[ -f '/var/etc/%s/id/TCP_%s' ] && echo -n $(cat /var/etc/%s/id/TCP_%s)", appname, i, appname, i))
if current_node and current_node ~= "" and current_node ~= "nil" then
local e = uci:get_all(appname, current_node)
if e then
local remarks = ""
if e.type == "V2ray" and (e.protocol == "_balancing" or e.protocol == "_shunt") then
if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt") then
remarks = "%s[%s] " % {translatef(e.type .. e.protocol), e.remarks}
else
if e.use_kcp and e.use_kcp == "1" then
@ -95,12 +95,10 @@ for i = 1, tcp_node_num, 1 do
remarks = "%s[%s] %s:%s" % {e.type, e.remarks, e.address, e.port}
end
end
o.description = o.description .. "<br />" ..translatef("Current node: %s", remarks)
o.description = translate("For proxy specific list.") .. "<br />" .. translatef("Current node: %s", '<a href="node_config/' .. current_node .. '">' .. remarks .. '</a>')
end
end
end
else
o = s:taboption("Main", ListValue, "tcp_node" .. i, translate("TCP Node") .. " " .. i)
end
o:value("nil", translate("Close"))
for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
@ -109,16 +107,14 @@ end
---- UDP Node
local udp_node_num = tonumber(m:get("@global_other[0]", "udp_node_num") or 1)
for i = 1, udp_node_num, 1 do
o = s:taboption("Main", ListValue, "udp_node" .. i, translate("UDP Node") .. " " .. i)
o:value("nil", translate("Close"))
if i == 1 then
o = s:taboption("Main", ListValue, "udp_node" .. i, translate("UDP Node"))
o.description = translate("For proxy game network, DNS hijack etc.") .. translate(" The selected server will not use Kcptun.")
o:value("nil", translate("Close"))
o.title = translate("UDP Node")
o.description = translate("For proxy game network, DNS hijack etc.") .. o.description .. "<br />" .. translate("The selected server will not use Kcptun.")
o:value("tcp_", translate("Same as the tcp node"))
--o:value("tcp", translate("Same as the tcp node"))
--o:value("tcp_", translate("Same as the tcp node") .. "" .. translate("New process") .. "")
else
o = s:taboption("Main", ListValue, "udp_node" .. i, translate("UDP Node") .. " " .. i)
o:value("nil", translate("Close"))
end
for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
end
@ -334,7 +330,7 @@ o.default = 9050
o.datatype = "port"
o.rmempty = false
if api.is_finded("v2ray") then
if api.is_finded("xray") or api.is_finded("v2ray") then
o = s:option(Value, "http_port", "HTTP" .. translate("Listen Port") .. " " .. translate("0 is not use"))
o.default = 0
o.datatype = "port"

View File

@ -47,7 +47,10 @@ o:depends("balancing_enable", 1)
-- [[ Balancing Settings ]]--
s = m:section(TypedSection, "haproxy_config", "",
"<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.").."</font>")
"<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.") ..
"\n" .. translate("Note that the node configuration parameters for load balancing must be consistent, otherwise problems can arise!") ..
"</font>")
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true

View File

@ -79,6 +79,10 @@ end
if api.is_finded("ssr-redir") then
type:value("SSR", translate("ShadowsocksR"))
end
if api.is_finded("xray") then
type:value("Xray", translate("Xray"))
type.description = translate("Xray is currently directly compatible with V2ray and used.")
end
if api.is_finded("v2ray") then
type:value("V2ray", translate("V2ray"))
end
@ -110,6 +114,7 @@ protocol:value("trojan", translate("Trojan"))
protocol:value("_balancing", translate("Balancing"))
protocol:value("_shunt", translate("Shunt"))
protocol:depends("type", "V2ray")
protocol:depends("type", "Xray")
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
@ -128,7 +133,7 @@ balancing_node:depends("protocol", "_balancing")
-- 分流
uci:foreach(appname, "shunt_rules", function(e)
o = s:option(ListValue, e[".name"], translate(e.remarks))
o = s:option(ListValue, e[".name"], '<a href="../shunt_rules/' .. e[".name"] .. '">' .. translate(e.remarks) .. "</a>")
o:value("nil", translate("Close"))
for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
o:depends("protocol", "_shunt")
@ -138,11 +143,29 @@ uci:foreach(appname, "shunt_rules", function(e)
o:depends("protocol", "_shunt")
end)
shunt_tips = s:option(DummyValue, "shunt_tips", " ")
shunt_tips.rawhtml = true
shunt_tips.cfgvalue = function(t, n)
return string.format('<a style="color: red" href="../rule">%s</a>', translate("No shunt rules? Click me to go to add."))
end
shunt_tips:depends("protocol", "_shunt")
default_node = s:option(ListValue, "default_node", translate("Default") .. " " .. translate("Node"))
default_node:value("nil", translate("Close"))
for k, v in pairs(nodes_table) do default_node:value(v.id, v.remarks) end
default_node:depends("protocol", "_shunt")
domainStrategy = s:option(ListValue, "domainStrategy", translate("Domain Strategy"))
domainStrategy:value("AsIs")
domainStrategy:value("IPIfNonMatch")
domainStrategy:value("IPOnDemand")
domainStrategy.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>"
domainStrategy:depends("protocol", "_balancing")
domainStrategy:depends("protocol", "_shunt")
-- Brook协议
brook_protocol = s:option(ListValue, "brook_protocol", translate("Protocol"))
brook_protocol:value("client", translate("Brook"))
@ -180,6 +203,12 @@ address:depends("type", "Trojan")
address:depends("type", "Trojan-Plus")
address:depends("type", "Trojan-Go")
address:depends("type", "Naiveproxy")
address:depends({ type = "Xray", protocol = "vmess" })
address:depends({ type = "Xray", protocol = "vless" })
address:depends({ type = "Xray", protocol = "http" })
address:depends({ type = "Xray", protocol = "socks" })
address:depends({ type = "Xray", protocol = "shadowsocks" })
address:depends({ type = "Xray", protocol = "trojan" })
address:depends({ type = "V2ray", protocol = "vmess" })
address:depends({ type = "V2ray", protocol = "vless" })
address:depends({ type = "V2ray", protocol = "http" })
@ -197,6 +226,12 @@ use_ipv6:depends("type", "Brook")
use_ipv6:depends("type", "Trojan")
use_ipv6:depends("type", "Trojan-Plus")
use_ipv6:depends("type", "Trojan-Go")
use_ipv6:depends({ type = "Xray", protocol = "vmess" })
use_ipv6:depends({ type = "Xray", protocol = "vless" })
use_ipv6:depends({ type = "Xray", protocol = "http" })
use_ipv6:depends({ type = "Xray", protocol = "socks" })
use_ipv6:depends({ type = "Xray", protocol = "shadowsocks" })
use_ipv6:depends({ type = "Xray", protocol = "trojan" })
use_ipv6:depends({ type = "V2ray", protocol = "vmess" })
use_ipv6:depends({ type = "V2ray", protocol = "vless" })
use_ipv6:depends({ type = "V2ray", protocol = "http" })
@ -216,6 +251,12 @@ port:depends("type", "Trojan")
port:depends("type", "Trojan-Plus")
port:depends("type", "Trojan-Go")
port:depends("type", "Naiveproxy")
port:depends({ type = "Xray", protocol = "vmess" })
port:depends({ type = "Xray", protocol = "vless" })
port:depends({ type = "Xray", protocol = "http" })
port:depends({ type = "Xray", protocol = "socks" })
port:depends({ type = "Xray", protocol = "shadowsocks" })
port:depends({ type = "Xray", protocol = "trojan" })
port:depends({ type = "V2ray", protocol = "vmess" })
port:depends({ type = "V2ray", protocol = "vless" })
port:depends({ type = "V2ray", protocol = "http" })
@ -226,6 +267,8 @@ port:depends({ type = "V2ray", protocol = "trojan" })
username = s:option(Value, "username", translate("Username"))
username:depends("type", "Socks")
username:depends("type", "Naiveproxy")
username:depends({ type = "Xray", protocol = "http" })
username:depends({ type = "Xray", protocol = "socks" })
username:depends({ type = "V2ray", protocol = "http" })
username:depends({ type = "V2ray", protocol = "socks" })
@ -239,6 +282,10 @@ password:depends("type", "Trojan")
password:depends("type", "Trojan-Plus")
password:depends("type", "Trojan-Go")
password:depends("type", "Naiveproxy")
password:depends({ type = "Xray", protocol = "http" })
password:depends({ type = "Xray", protocol = "socks" })
password:depends({ type = "Xray", protocol = "shadowsocks" })
password:depends({ type = "Xray", protocol = "trojan" })
password:depends({ type = "V2ray", protocol = "http" })
password:depends({ type = "V2ray", protocol = "socks" })
password:depends({ type = "V2ray", protocol = "shadowsocks" })
@ -266,10 +313,12 @@ end
security = s:option(ListValue, "security", translate("Encrypt Method"))
for a, t in ipairs(security_list) do security:value(t) end
security:depends({ type = "Xray", protocol = "vmess" })
security:depends({ type = "V2ray", protocol = "vmess" })
encryption = s:option(Value, "encryption", translate("Encrypt Method"))
encryption.default = "none"
encryption:depends({ type = "Xray", protocol = "vless" })
encryption:depends({ type = "V2ray", protocol = "vless" })
v_ss_encrypt_method = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method"))
@ -360,6 +409,8 @@ kcp_opts:depends("use_kcp", "1")
uuid = s:option(Value, "uuid", translate("ID"))
uuid.password = true
uuid:depends({ type = "Xray", protocol = "vmess" })
uuid:depends({ type = "Xray", protocol = "vless" })
uuid:depends({ type = "V2ray", protocol = "vmess" })
uuid:depends({ type = "V2ray", protocol = "vless" })
@ -368,64 +419,77 @@ alter_id:depends("protocol", "vmess")
level = s:option(Value, "level", translate("User Level"))
level.default = 1
level:depends({ type = "Xray", protocol = "vmess" })
level:depends({ type = "Xray", protocol = "vless" })
level:depends({ type = "Xray", protocol = "trojan" })
level:depends({ type = "V2ray", protocol = "vmess" })
level:depends({ type = "V2ray", protocol = "vless" })
level:depends({ type = "V2ray", protocol = "trojan" })
flow = s:option(Value, "flow", translate("flow"))
flow.placeholder = "xtls-rprx-origin"
--flow:value("xtls-rprx-origin")
--flow:value("xtls-rprx-origin-udp443")
--flow:value("xtls-rprx-direct")
--flow:value("xtls-rprx-direct-udp443")
flow:depends({ type = "V2ray", protocol = "vless" })
stream_security = s:option(ListValue, "stream_security", translate("Transport Layer Encryption"), translate('Whether or not transport layer encryption is enabled, "none" for unencrypted, "tls" for using TLS, "xtls" for using XTLS.'))
stream_security:value("none", "none")
stream_security:value("tls", "tls")
stream_security:value("xtls", "xtls")
stream_security.default = "tls"
stream_security:depends({ type = "V2ray", protocol = "vmess" })
stream_security:depends({ type = "V2ray", protocol = "vless" })
stream_security:depends({ type = "V2ray", protocol = "socks" })
stream_security:depends({ type = "V2ray", protocol = "shadowsocks" })
stream_security:depends({ type = "V2ray", protocol = "trojan" })
stream_security:depends("type", "Trojan")
stream_security:depends("type", "Trojan-Plus")
stream_security:depends("type", "Trojan-Go")
stream_security.validate = function(self, value)
if value == "none" and (type:formvalue(arg[1]) == "Trojan" or type:formvalue(arg[1]) == "Trojan-Plus") then
return nil, translate("'none' not supported for original Trojan, please choose 'tls'.")
tls = s:option(Flag, "tls", translate("TLS"))
tls.default = 0
tls.validate = function(self, value, t)
if value then
local type = type:formvalue(t) or ""
if value == "0" and (type == "Trojan" or type == "Trojan-Plus") then
return nil, translate("Original Trojan only supported 'tls', please choose 'tls'.")
end
return value
end
return value
end
tls:depends({ type = "Xray", protocol = "vmess" })
tls:depends({ type = "Xray", protocol = "vless" })
tls:depends({ type = "Xray", protocol = "socks" })
tls:depends({ type = "Xray", protocol = "trojan" })
tls:depends({ type = "Xray", protocol = "shadowsocks" })
tls:depends({ type = "V2ray", protocol = "vmess" })
tls:depends({ type = "V2ray", protocol = "vless" })
tls:depends({ type = "V2ray", protocol = "socks" })
tls:depends({ type = "V2ray", protocol = "shadowsocks" })
tls:depends({ type = "Xray", protocol = "trojan" })
tls:depends("type", "Trojan")
tls:depends("type", "Trojan-Plus")
tls:depends("type", "Trojan-Go")
xtls = s:option(Flag, "xtls", translate("XTLS"))
xtls.default = 0
xtls:depends({ type = "Xray", protocol = "vless", tls = "1" })
flow = s:option(Value, "flow", translate("flow"))
flow:value("xtls-rprx-origin")
flow:value("xtls-rprx-origin-udp443")
flow:value("xtls-rprx-direct")
flow:value("xtls-rprx-direct-udp443")
flow:value("xtls-rprx-splice")
flow:value("xtls-rprx-splice-udp443")
flow:depends("xtls", "1")
-- [[ TLS部分 ]] --
tls_sessionTicket = s:option(Flag, "tls_sessionTicket", translate("Session Ticket"))
tls_sessionTicket.default = "0"
tls_sessionTicket:depends({ type = "Trojan", stream_security = "tls" })
tls_sessionTicket:depends({ type = "Trojan-Plus", stream_security = "tls" })
tls_sessionTicket:depends({ type = "Trojan-Go", stream_security = "tls" })
tls_sessionTicket:depends({ type = "Trojan", tls = "1" })
tls_sessionTicket:depends({ type = "Trojan-Plus", tls = "1" })
tls_sessionTicket:depends({ type = "Trojan-Go", tls = "1" })
-- [[ Trojan TLS ]]--
trojan_force_fp = s:option(ListValue, "fingerprint", translate("Finger Print"))
for a, t in ipairs(force_fp) do trojan_force_fp:value(t) end
trojan_force_fp.default = "firefox"
trojan_force_fp:depends({ type = "Trojan-Go", stream_security = "tls" })
trojan_force_fp:depends({ type = "Trojan-Go", tls = "1" })
tls_serverName = s:option(Value, "tls_serverName", translate("Domain"))
tls_serverName:depends("stream_security", "tls")
tls_serverName:depends("stream_security", "xtls")
tls_serverName:depends("tls", "1")
tls_serverName:depends("xtls", "1")
tls_allowInsecure = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
tls_allowInsecure.default = "0"
tls_allowInsecure:depends("stream_security", "tls")
tls_allowInsecure:depends("stream_security", "xtls")
tls_allowInsecure:depends("tls", "1")
tls_allowInsecure:depends("xtls", "1")
-- [[ Trojan Cert ]]--
trojan_cert_path = s:option(Value, "trojan_cert_path", translate("Trojan Cert Path"))
trojan_cert_path.default = ""
trojan_cert_path:depends({ stream_security = "tls", tls_allowInsecure = false })
trojan_cert_path:depends({ tls = "1", tls_allowInsecure = false })
trojan_transport = s:option(ListValue, "trojan_transport", translate("Transport"))
trojan_transport:value("original", "Original")
@ -440,7 +504,7 @@ trojan_plugin:value("plaintext", "Plain Text")
trojan_plugin:value("shadowsocks", "ShadowSocks")
trojan_plugin:value("other", "Other")
trojan_plugin.default = "plaintext"
trojan_plugin:depends({ stream_security = "none", trojan_transport = "original" })
trojan_plugin:depends({ tls = "0", trojan_transport = "original" })
trojan_plugin_cmd = s:option(Value, "plugin_cmd", translate("Plugin Binary"))
trojan_plugin_cmd.placeholder = "eg: /usr/bin/v2ray-plugin"
@ -464,6 +528,11 @@ transport:value("ws", "WebSocket")
transport:value("h2", "HTTP/2")
transport:value("ds", "DomainSocket")
transport:value("quic", "QUIC")
transport:depends({ type = "Xray", protocol = "vmess" })
transport:depends({ type = "Xray", protocol = "vless" })
transport:depends({ type = "Xray", protocol = "socks" })
transport:depends({ type = "Xray", protocol = "shadowsocks" })
transport:depends({ type = "Xray", protocol = "trojan" })
transport:depends({ type = "V2ray", protocol = "vmess" })
transport:depends({ type = "V2ray", protocol = "vless" })
transport:depends({ type = "V2ray", protocol = "socks" })
@ -475,6 +544,7 @@ ss_transport = s:option(ListValue, "ss_transport", translate("Transport"))
ss_transport:value("ws", "WebSocket")
ss_transport:value("h2", "HTTP/2")
ss_transport:value("h2+ws", "HTTP/2 & WebSocket")
ss_transport:depends({ type = "Xray", protocol = "shadowsocks" })
ss_transport:depends({ type = "V2ray", protocol = "shadowsocks" })
]]--
@ -557,7 +627,7 @@ h2_path:depends("trojan_transport", "h2+ws")
h2_path:depends("trojan_transport", "h2")
-- [[ DomainSocket部分 ]]--
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running V2Ray."))
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running."))
ds_path:depends("transport", "ds")
-- [[ QUIC部分 ]]--
@ -590,6 +660,11 @@ ss_aead_pwd:depends("ss_aead", "1")
-- [[ Mux ]]--
mux = s:option(Flag, "mux", translate("Mux"))
mux:depends({ type = "Xray", protocol = "vmess" })
mux:depends({ type = "Xray", protocol = "vless", xtls = false })
mux:depends({ type = "Xray", protocol = "http" })
mux:depends({ type = "Xray", protocol = "socks" })
mux:depends({ type = "Xray", protocol = "shadowsocks" })
mux:depends({ type = "V2ray", protocol = "vmess" })
mux:depends({ type = "V2ray", protocol = "vless" })
mux:depends({ type = "V2ray", protocol = "http" })
@ -605,6 +680,7 @@ mux_concurrency:depends("mux", "1")
--[[
tcp_socks = s:option(Flag, "tcp_socks", translate("TCP Open Socks"), translate("When using this TCP node, whether to open the socks proxy at the same time"))
tcp_socks.default = 0
tcp_socks:depends("type", "Xray")
tcp_socks:depends("type", "V2ray")
tcp_socks_port = s:option(Value, "tcp_socks_port", "Socks " .. translate("Port"), translate("Do not conflict with other ports"))

View File

@ -63,7 +63,7 @@ if nodes_display:find("compact_display_nodes") then
local remarks = m:get(n, "remarks") or ""
local type = m:get(n, "type") or ""
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.type' value='%s'>", appname, n, type)
if type == "V2ray" then
if type == "Xray" or type == "V2ray" then
local protocol = m:get(n, "protocol")
if protocol == "_balancing" then
type = type .. " 负载均衡"
@ -75,7 +75,11 @@ if nodes_display:find("compact_display_nodes") then
local port = m:get(n, "port") or ""
str = str .. translate(type) .. "" .. remarks
if address ~= "" and port ~= "" then
str = str .. string.format("%s:%s", address, port)
if datatypes.ip6addr(address) then
str = str .. string.format("[%s]:%s", address, port)
else
str = str .. string.format("%s:%s", address, port)
end
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.address' value='%s'>", appname, n, address)
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.port' value='%s'>", appname, n, port)
end
@ -102,7 +106,7 @@ else
local v = Value.cfgvalue(t, n)
if v then
result = translate(v)
if v == "V2ray" then
if v == "Xray" or v == "V2ray" then
local protocol = m:get(n, "protocol")
if protocol == "_balancing" then
result = result .. " 负载均衡"

View File

@ -49,7 +49,7 @@ for e = 0, 23 do o:value(e, e .. translate("oclock")) end
o.default = 0
o:depends("auto_update", 1)
s = m:section(TypedSection, "shunt_rules", "V2ray" .. translate("Shunt") .. translate("Rule"))
s = m:section(TypedSection, "shunt_rules", "Xray/V2ray" .. translate("Shunt") .. translate("Rule"))
s.template = "cbi/tblsection"
s.anonymous = false
s.addremove = true

View File

@ -1,7 +1,7 @@
local d = require "luci.dispatcher"
local appname = "passwall"
m = Map(appname, "V2ray" .. translate("Shunt") .. translate("Rule"))
m = Map(appname, "Xray/V2ray" .. translate("Shunt") .. translate("Rule"))
m.redirect = d.build_url("admin", "services", appname)
s = m:section(NamedSection, arg[1], "shunt_rules", "")
@ -13,31 +13,61 @@ remarks.default = arg[1]
remarks.rmempty = false
domain_list = s:option(TextValue, "domain_list", translate("Domain"))
domain_list.rows = 15
domain_list.rows = 10
domain_list.wrap = "off"
domain_list.validate = function(self, value)
local hosts= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(hosts, w) end)
for index, host in ipairs(hosts) do
if not datatypes.hostname(host) then
return nil, host .. " " .. translate("Not valid domain name, please re-enter!")
local flag = 1
local tmp_host = host
if 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
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("Domains from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geosite.dat. The tag must exist in the file.")
.. "</li></ul>"
ip_list = s:option(TextValue, "ip_list", "IP")
ip_list.rows = 15
ip_list.rows = 10
ip_list.wrap = "off"
ip_list.validate = function(self, value)
local ipmasks= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if not datatypes.ipmask4(ipmask) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
if ipmask:find("geoip:") and ipmask:find("geoip:") == 1 then
elseif ipmask:find("ext:") and ipmask:find("ext:") == 1 then
else
if not datatypes.ipmask4(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("IPs from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geoip.dat. The tag must exist in the file.")
.. "</li></ul>"
return m

View File

@ -89,6 +89,9 @@ local function start()
end
type = type:lower()
bin = ln_start("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param)
elseif type == "Xray" then
config = require("luci.model.cbi.passwall.server.api.xray").gen_config(user)
bin = ln_start(_api.get_xray_path(), "xray", "-config=" .. config_file)
elseif type == "V2ray" then
config = require("luci.model.cbi.passwall.server.api.v2ray").gen_config(user)
bin = ln_start(_api.get_v2ray_path(), "v2ray", "-config=" .. config_file)

View File

@ -14,7 +14,7 @@ function gen_config(user)
for i = 1, #user.uuid do
clients[i] = {
id = user.uuid[i],
flow = (user.xtls and user.xtls == "1") and user.flow or nil,
flow = user.flow or nil,
level = tonumber(user.level),
alterId = tonumber(user.alter_id)
}
@ -109,16 +109,6 @@ function gen_config(user)
streamSettings = {
network = user.transport,
security = "none",
xtlsSettings = (user.tls and user.tls == "1" and user.xtls and user.xtls == "1") and {
--alpn = {"http/1.1"},
disableSystemRoot = false,
certificates = {
{
certificateFile = user.tls_certificateFile,
keyFile = user.tls_keyFile
}
}
} or nil,
tlsSettings = (user.tls and user.tls == "1") and {
disableSystemRoot = false,
certificates = {
@ -176,10 +166,6 @@ function gen_config(user)
if user.tls and user.tls == "1" then
config.inbounds[1].streamSettings.security = "tls"
if user.xtls and user.xtls == "1" then
config.inbounds[1].streamSettings.security = "xtls"
config.inbounds[1].streamSettings.tlsSettings = nil
end
end
if user.transport == "mkcp" or user.transport == "quic" then

View File

@ -0,0 +1,191 @@
module("luci.model.cbi.passwall.server.api.xray", package.seeall)
local ucic = require"luci.model.uci".cursor()
function gen_config(user)
local settings = nil
local routing = nil
local outbounds = {
{protocol = "freedom", tag = "direct"}, {protocol = "blackhole", tag = "blocked"}
}
if user.protocol == "vmess" or user.protocol == "vless" then
if user.uuid then
local clients = {}
for i = 1, #user.uuid do
clients[i] = {
id = user.uuid[i],
flow = (user.xtls and user.xtls == "1") and user.flow or nil,
level = tonumber(user.level),
alterId = tonumber(user.alter_id)
}
end
settings = {
clients = clients,
decryption = user.decryption or "none"
}
end
elseif user.protocol == "socks" then
settings = {
auth = (user.auth and user.auth == "1") and "password" or "noauth",
accounts = (user.auth and user.auth == "1") and {
{
user = user.username,
pass = user.password
}
}
}
elseif user.protocol == "http" then
settings = {
allowTransparent = false,
accounts = (user.auth and user.auth == "1") and {
{
user = user.username,
pass = user.password
}
}
}
user.transport = "tcp"
user.tcp_guise = "none"
elseif user.protocol == "shadowsocks" then
settings = {
method = user.method,
password = user.password,
level = tonumber(user.level) or 1,
network = user.ss_network or "TCP,UDP"
}
elseif user.protocol == "trojan" then
if user.uuid then
local clients = {}
for i = 1, #user.uuid do
clients[i] = {
password = user.uuid[i],
level = tonumber(user.level)
}
end
settings = {
clients = clients
}
end
elseif user.protocol == "mtproto" then
settings = {
users = {
{
level = tonumber(user.level) or 1,
secret = (user.password == nil) and "" or user.password
}
}
}
end
routing = {
domainStrategy = "IPOnDemand",
rules = {
{
type = "field",
ip = {"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"},
outboundTag = (user.accept_lan == nil or user.accept_lan == "0") and "blocked" or "direct"
}
}
}
if user.transit_node and user.transit_node ~= "nil" then
local gen_xray = require("luci.model.cbi.passwall.api.gen_xray")
local client = gen_xray.gen_outbound(ucic:get_all("passwall", user.transit_node), "transit")
table.insert(outbounds, 1, client)
end
local config = {
log = {
-- error = "/var/etc/passwall_server/log/" .. user[".name"] .. ".log",
loglevel = "warning"
},
-- 传入连接
inbounds = {
{
listen = (user.bind_local == "1") and "127.0.0.1" or nil,
port = tonumber(user.port),
protocol = user.protocol,
settings = settings,
streamSettings = {
network = user.transport,
security = "none",
xtlsSettings = (user.tls and user.tls == "1" and user.xtls and user.xtls == "1") and {
--alpn = {"http/1.1"},
disableSystemRoot = false,
certificates = {
{
certificateFile = user.tls_certificateFile,
keyFile = user.tls_keyFile
}
}
} or nil,
tlsSettings = (user.tls and user.tls == "1") and {
disableSystemRoot = false,
certificates = {
{
certificateFile = user.tls_certificateFile,
keyFile = user.tls_keyFile
}
}
} or nil,
tcpSettings = (user.transport == "tcp") and {
header = {
type = user.tcp_guise,
request = (user.tcp_guise == "http") and {
path = user.tcp_guise_http_path or {"/"},
headers = {
Host = user.tcp_guise_http_host or {}
}
} or nil
}
} or nil,
kcpSettings = (user.transport == "mkcp") and {
mtu = tonumber(user.mkcp_mtu),
tti = tonumber(user.mkcp_tti),
uplinkCapacity = tonumber(user.mkcp_uplinkCapacity),
downlinkCapacity = tonumber(user.mkcp_downlinkCapacity),
congestion = (user.mkcp_congestion == "1") and true or false,
readBufferSize = tonumber(user.mkcp_readBufferSize),
writeBufferSize = tonumber(user.mkcp_writeBufferSize),
seed = (user.mkcp_seed and user.mkcp_seed ~= "") and user.mkcp_seed or nil,
header = {type = user.mkcp_guise}
} or nil,
wsSettings = (user.transport == "ws") and {
acceptProxyProtocol = false,
headers = (user.ws_host) and {Host = user.ws_host} or nil,
path = user.ws_path
} or nil,
httpSettings = (user.transport == "h2") and {
path = user.h2_path, host = user.h2_host
} or nil,
dsSettings = (user.transport == "ds") and {
path = user.ds_path
} or nil,
quicSettings = (user.transport == "quic") and {
security = user.quic_security,
key = user.quic_key,
header = {type = user.quic_guise}
} or nil
}
}
},
-- 传出连接
outbounds = outbounds,
routing = routing
}
if user.tls and user.tls == "1" then
config.inbounds[1].streamSettings.security = "tls"
if user.xtls and user.xtls == "1" then
config.inbounds[1].streamSettings.security = "xtls"
config.inbounds[1].streamSettings.tlsSettings = nil
end
end
if user.transport == "mkcp" or user.transport == "quic" then
config.inbounds[1].streamSettings.security = "none"
config.inbounds[1].streamSettings.tlsSettings = nil
end
return config
end

View File

@ -47,7 +47,7 @@ e = t:option(DummyValue, "type", translate("Type"))
e.cfgvalue = function(t, n)
local v = Value.cfgvalue(t, n)
if v then
if v == "V2ray" then
if v == "Xray" or v == "V2ray" then
local protocol = m:get(n, "protocol")
return v .. " -> " .. protocol
end

View File

@ -1,5 +1,4 @@
local d = require "luci.dispatcher"
local uci = require"luci.model.uci".cursor()
local api = require "luci.model.cbi.passwall.api.api"
local ss_encrypt_method_list = {
@ -78,6 +77,9 @@ end
if api.is_finded("ssr-server") then
type:value("SSR", translate("ShadowsocksR"))
end
if api.is_finded("xray") then
type:value("Xray", translate("Xray"))
end
if api.is_finded("v2ray") then
type:value("V2ray", translate("V2ray"))
end
@ -104,6 +106,7 @@ protocol:value("socks", "Socks")
protocol:value("shadowsocks", "Shadowsocks")
protocol:value("trojan", "Trojan")
protocol:value("mtproto", "MTProto")
protocol:depends("type", "Xray")
protocol:depends("type", "V2ray")
-- Brook协议
@ -137,6 +140,8 @@ auth.validate = function(self, value, t)
return value
end
auth:depends("type", "Socks")
auth:depends({ type = "Xray", protocol = "socks" })
auth:depends({ type = "Xray", protocol = "http" })
auth:depends({ type = "V2ray", protocol = "socks" })
auth:depends({ type = "V2ray", protocol = "http" })
@ -149,9 +154,11 @@ password:depends("auth", "1")
password:depends("type", "SS")
password:depends("type", "SSR")
password:depends("type", "Brook")
password:depends({ type = "Xray", protocol = "shadowsocks" })
password:depends({ type = "V2ray", protocol = "shadowsocks" })
mtproto_password = s:option(Value, "mtproto_password", translate("Password"), translate("The MTProto protocol must be 32 characters and can only contain characters from 0 to 9 and a to f."))
mtproto_password:depends({ type = "Xray", protocol = "mtproto" })
mtproto_password:depends({ type = "V2ray", protocol = "mtproto" })
mtproto_password.default = arg[1]
function mtproto_password.cfgvalue(self, section)
@ -163,6 +170,7 @@ end
decryption = s:option(Value, "decryption", translate("Encrypt Method"))
decryption.default = "none"
decryption:depends({ type = "Xray", protocol = "vless" })
decryption:depends({ type = "V2ray", protocol = "vless" })
ss_encrypt_method = s:option(ListValue, "ss_encrypt_method", translate("Encrypt Method"))
@ -187,6 +195,7 @@ end
v_ss_encrypt_method = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(v_ss_encrypt_method_list) do v_ss_encrypt_method:value(t) end
v_ss_encrypt_method:depends({ type = "Xray", protocol = "shadowsocks" })
v_ss_encrypt_method:depends({ type = "V2ray", protocol = "shadowsocks" })
function v_ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
@ -200,6 +209,7 @@ ss_network.default = "tcp,udp"
ss_network:value("tcp", "TCP")
ss_network:value("udp", "UDP")
ss_network:value("tcp,udp", "TCP,UDP")
ss_network:depends({ type = "Xray", protocol = "shadowsocks" })
ss_network:depends({ type = "V2ray", protocol = "shadowsocks" })
ssr_protocol = s:option(ListValue, "ssr_protocol", translate("Protocol"))
@ -237,6 +247,9 @@ uuid = s:option(DynamicList, "uuid", translate("ID") .. "/" .. translate("Passwo
for i = 1, 3 do
uuid:value(api.gen_uuid(1))
end
uuid:depends({ type = "Xray", protocol = "vmess" })
uuid:depends({ type = "Xray", protocol = "vless" })
uuid:depends({ type = "Xray", protocol = "trojan" })
uuid:depends({ type = "V2ray", protocol = "vmess" })
uuid:depends({ type = "V2ray", protocol = "vless" })
uuid:depends({ type = "V2ray", protocol = "trojan" })
@ -246,10 +259,16 @@ uuid:depends("type", "Trojan-Plus")
alter_id = s:option(Value, "alter_id", translate("Alter ID"))
alter_id.default = 16
alter_id:depends({ type = "Xray", protocol = "vmess" })
alter_id:depends({ type = "V2ray", protocol = "vmess" })
level = s:option(Value, "level", translate("User Level"))
level.default = 1
level:depends({ type = "Xray", protocol = "vmess" })
level:depends({ type = "Xray", protocol = "vless" })
level:depends({ type = "Xray", protocol = "shadowsocks" })
level:depends({ type = "Xray", protocol = "trojan" })
level:depends({ type = "Xray", protocol = "mtproto" })
level:depends({ type = "V2ray", protocol = "vmess" })
level:depends({ type = "V2ray", protocol = "vless" })
level:depends({ type = "V2ray", protocol = "shadowsocks" })
@ -274,6 +293,10 @@ tls.validate = function(self, value, t)
return value
end
end
tls:depends({ type = "Xray", protocol = "vmess" })
tls:depends({ type = "Xray", protocol = "vless" })
tls:depends({ type = "Xray", protocol = "socks" })
tls:depends({ type = "Xray", protocol = "shadowsocks" })
tls:depends({ type = "V2ray", protocol = "vmess" })
tls:depends({ type = "V2ray", protocol = "vless" })
tls:depends({ type = "V2ray", protocol = "socks" })
@ -284,12 +307,16 @@ tls:depends("type", "Trojan-Go")
xtls = s:option(Flag, "xtls", translate("XTLS"))
xtls.default = 0
xtls:depends({ type = "V2ray", protocol = "vless", tls = "1" })
xtls:depends({ type = "Xray", protocol = "vless", tls = "1" })
flow = s:option(Value, "flow", translate("flow"))
flow.default = "xtls-rprx-origin"
flow:value("xtls-rprx-origin")
flow:value("xtls-rprx-origin-udp443")
flow:value("xtls-rprx-direct")
flow:value("xtls-rprx-direct-udp443")
flow:value("xtls-rprx-splice")
flow:value("xtls-rprx-splice-udp443")
flow:depends("xtls", "1")
-- [[ TLS部分 ]] --
@ -342,6 +369,11 @@ transport:value("ws", "WebSocket")
transport:value("h2", "HTTP/2")
transport:value("ds", "DomainSocket")
transport:value("quic", "QUIC")
transport:depends({ type = "Xray", protocol = "vmess" })
transport:depends({ type = "Xray", protocol = "vless" })
transport:depends({ type = "Xray", protocol = "socks" })
transport:depends({ type = "Xray", protocol = "shadowsocks" })
transport:depends({ type = "Xray", protocol = "trojan" })
transport:depends({ type = "V2ray", protocol = "vmess" })
transport:depends({ type = "V2ray", protocol = "vless" })
transport:depends({ type = "V2ray", protocol = "socks" })
@ -460,7 +492,7 @@ mkcp_seed:depends("transport", "mkcp")
-- [[ DomainSocket部分 ]]--
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running V2Ray."))
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running."))
ds_path:depends("transport", "ds")
-- [[ QUIC部分 ]]--
@ -480,6 +512,7 @@ quic_guise:depends("transport", "quic")
-- [[ VLESS Fallback部分 ]]--
--[[
fallback = s:option(Flag, "fallback", translate("Fallback"))
fallback:depends({ type = "Xray", protocol = "vless", transport = "tcp", tls = "1" })
fallback:depends({ type = "V2ray", protocol = "vless", transport = "tcp", tls = "1" })
fallback_alpn = s:option(Value, "fallback_alpn", "Fallback alpn")
@ -517,7 +550,7 @@ tcp_fast_open:depends("type", "Trojan")
tcp_fast_open:depends("type", "Trojan-Plus")
tcp_fast_open:depends("type", "Trojan-Go")
remote_enable = s:option(Flag, "remote_enable", translate("Enable Remote"), translate("You can forward to Nginx/Caddy/V2ray WebSocket and more."))
remote_enable = s:option(Flag, "remote_enable", translate("Enable Remote"), translate("You can forward to Nginx/Caddy/Xray/V2ray WebSocket and more."))
remote_enable.default = "1"
remote_enable.rmempty = false
remote_enable:depends("type", "Trojan")
@ -535,16 +568,18 @@ remote_port:depends("remote_enable", 1)
bind_local = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed locally,It is recommended to turn on when using reverse proxies."))
bind_local.default = "0"
bind_local:depends("type", "Xray")
bind_local:depends("type", "V2ray")
accept_lan = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
accept_lan.default = "0"
accept_lan.rmempty = false
accept_lan:depends("type", "Xray")
accept_lan:depends("type", "V2ray")
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" and e.type == "V2ray" then
if e.node_type == "normal" and e.type == "Xray" or e.type == "V2ray" then
nodes_table[#nodes_table + 1] = {
id = e[".name"],
remarks = e.remarks_name
@ -556,6 +591,7 @@ transit_node = s:option(ListValue, "transit_node", translate("transit node"))
transit_node:value("nil", translate("Close"))
for k, v in pairs(nodes_table) do transit_node:value(v.id, v.remarks) end
transit_node.default = "nil"
transit_node:depends("type", "Xray")
transit_node:depends("type", "V2ray")
return m

View File

@ -6,6 +6,7 @@ local brook_version = require "luci.model.cbi.passwall.api.api".get_brook_versio
//<![CDATA[
var brookInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually update%>';
var noUpdateText = '<%:It is the latest version%>';
var updateSuccessText = '<%:Update successful%>';
var clickToUpdateText = '<%:Click to update%>';
@ -48,7 +49,7 @@ local brook_version = require "luci.model.cbi.passwall.api.api".get_brook_versio
function onRequestError_brook(btn, errorMessage) {
btn.disabled = false;
btn.value = btn.placeholder;
btn.value = manuallyUpdateText;
if(errorMessage) {
alert(errorMessage);

View File

@ -6,6 +6,7 @@ local kcptun_version = require "luci.model.cbi.passwall.api.api".get_kcptun_vers
//<![CDATA[
var kcptunInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually update%>';
var noUpdateText = '<%:It is the latest version%>';
var updateSuccessText = '<%:Update successful%>';
var clickToUpdateText = '<%:Click to update%>';
@ -48,7 +49,7 @@ local kcptun_version = require "luci.model.cbi.passwall.api.api".get_kcptun_vers
function onRequestError_kcptun(btn, errorMessage) {
btn.disabled = false;
btn.value = btn.placeholder;
btn.value = manuallyUpdateText;
if(errorMessage) {
alert(errorMessage);

View File

@ -6,6 +6,7 @@ local trojan_go_version = require "luci.model.cbi.passwall.api.api".get_trojan_g
//<![CDATA[
var trojanInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually update%>';
var noUpdateText = '<%:It is the latest version%>';
var updateSuccessText = '<%:Update successful%>';
var clickToUpdateText = '<%:Click to update%>';
@ -48,7 +49,7 @@ local trojan_go_version = require "luci.model.cbi.passwall.api.api".get_trojan_g
function onRequestError_trojan(btn, errorMessage) {
btn.disabled = false;
btn.value = btn.placeholder;
btn.value = manuallyUpdateText;
if(errorMessage) {
alert(errorMessage);

View File

@ -6,6 +6,7 @@ local v2ray_version = require "luci.model.cbi.passwall.api.api".get_v2ray_versio
//<![CDATA[
var v2rayInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually update%>';
var noUpdateText = '<%:It is the latest version%>';
var updateSuccessText = '<%:Update successful%>';
var clickToUpdateText = '<%:Click to update%>';
@ -48,7 +49,7 @@ local v2ray_version = require "luci.model.cbi.passwall.api.api".get_v2ray_versio
function onRequestError_v2ray(btn, errorMessage) {
btn.disabled = false;
btn.value = btn.placeholder;
btn.value = manuallyUpdateText;
if(errorMessage) {
alert(errorMessage);

View File

@ -0,0 +1,172 @@
<%
local xray_version = require "luci.model.cbi.passwall.api.api".get_xray_version()
-%>
<script type="text/javascript">
//<![CDATA[
var xrayInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually update%>';
var noUpdateText = '<%:It is the latest version%>';
var updateSuccessText = '<%:Update successful%>';
var clickToUpdateText = '<%:Click to update%>';
var inProgressText = '<%:Updating...%>';
var unexpectedErrorText = '<%:Unexpected error%>';
var updateInProgressNotice = '<%:Updating, are you sure to close?%>';
var downloadingText = '<%:Downloading...%>';
var decompressioningText = '<%:Unpacking...%>';
var movingText = '<%:Moving...%>';
window.onload = function() {
var xrayCheckBtn = document.getElementById('_xray-check_btn');
var xrayDetailElm = document.getElementById('_xray-check_btn-detail');
};
function addPageNotice_xray() {
window.onbeforeunload = function(e) {
e.returnValue = updateInProgressNotice;
return updateInProgressNotice;
};
}
function removePageNotice_xray() {
window.onbeforeunload = undefined;
}
function onUpdateSuccess_xray(btn) {
alert(updateSuccessText);
if(btn) {
btn.value = updateSuccessText;
btn.placeholder = updateSuccessText;
btn.disabled = true;
}
window.setTimeout(function() {
window.location.reload();
}, 1000);
}
function onRequestError_xray(btn, errorMessage) {
btn.disabled = false;
btn.value = manuallyUpdateText;
if(errorMessage) {
alert(errorMessage);
}
}
function onBtnClick_xray(btn) {
if(xrayInfo === undefined) {
checkUpdate_xray(btn);
} else {
doUpdate_xray(btn);
}
}
function checkUpdate_xray(btn) {
btn.disabled = true;
btn.value = inProgressText;
addPageNotice_xray();
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
XHR.get('<%=url([[admin]], [[services]], [[passwall]], [[xray_check]])%>', {
token: tokenStr,
arch: ''
}, function(x,json) {
removePageNotice_xray();
if(json.code) {
xrayInfo = undefined;
onRequestError_xray(btn, json.error);
} else {
if(json.update) {
xrayInfo = json;
btn.disabled = false;
btn.value = clickToUpdateText;
btn.placeholder = clickToUpdateText;
if(ckeckDetailElm) {
var urlNode = '';
if(json.version) {
urlNode = '<em style="color:red;">最新版本号:' + json.version + '</em>';
if(json.url && json.url.html) {
urlNode = '<a href="' + json.url.html + '" target="_blank">' + urlNode + '</a>';
}
}
ckeckDetailElm.innerHTML = urlNode;
}
} else {
btn.disabled = true;
btn.value = noUpdateText;
}
}
},300);
}
function doUpdate_xray(btn) {
btn.disabled = true;
btn.value = downloadingText;
addPageNotice_xray();
var xrayUpdateUrl = '<%=url([[admin]], [[services]], [[passwall]], [[xray_update]])%>';
// Download file
XHR.get(xrayUpdateUrl, {
token: tokenStr,
url: xrayInfo ? xrayInfo.url.download : ''
}, function(x,json) {
if(json.code) {
removePageNotice_xray();
onRequestError_xray(btn, json.error);
} else {
btn.value = decompressioningText;
// Extract file
XHR.get(xrayUpdateUrl, {
token: tokenStr,
task: 'extract',
file: json.file,
subfix: xrayInfo ? xrayInfo.type : ''
}, function(x,json) {
if(json.code) {
removePageNotice_xray();
onRequestError_xray(btn, json.error);
} else {
btn.value = movingText;
// Move file to target dir
XHR.get(xrayUpdateUrl, {
token: tokenStr,
task: 'move',
file: json.file
}, function(x,json) {
removePageNotice_xray();
if(json.code) {
onRequestError_xray(btn, json.error);
} else {
onUpdateSuccess_xray(btn);
}
},300)
}
},300)
}
},300)
}
//]]>
</script>
<div class="cbi-value">
<label class="cbi-value-title">Xray
<%:Version%>
</label>
<div class="cbi-value-field">
<div class="cbi-value-description">
<span><%=xray_version%> 】</span>
<input class="cbi-button cbi-input-apply" type="button" id="_xray-check_btn" onclick="onBtnClick_xray(this);" value="<%:Manually update%>" />
<span id="_xray-check_btn-detail"></span>
</div>
</div>
</div>

View File

@ -1,3 +1,9 @@
<%
local api = require "luci.model.cbi.passwall.api.api"
local tcp_node_num = api.uci_get_type("global_other", "tcp_node_num", 1)
local udp_node_num = api.uci_get_type("global_other", "udp_node_num", 1)
local auto_switch = api.uci_get_type("auto_switch", "enable", 0)
-%>
<script type="text/javascript">
//<![CDATA[
var _status = document.getElementsByClassName('_status');
@ -29,5 +35,40 @@
}
);
}
var global = document.getElementById("cbi-passwall-global");
if (global) {
var node = global.getElementsByClassName("cbi-section-node")[0];
var node_id = node.getAttribute("id");
for (var i = 0; i <= 1; i++) {
var proto = "udp";
var num = <%=udp_node_num%>;
if (i == 0) {
if (<%=auto_switch%> > 0) {
continue;
}
proto = "tcp";
num = <%=tcp_node_num%>;
}
if (num >= 1) {
for (var j = 0; j < num; j++) {
var index = j + 1;
var node = document.getElementById(node_id + "-" + proto + "_node" + index);
var node_select = document.getElementById(node.id.replace("cbi-", "cbid-").replace(new RegExp("-", 'g'), "."));
var node_select_value = node_select.value;
if (node_select_value && node_select_value != "nil") {
var new_a = document.createElement("a");
new_a.setAttribute("href","node_config/" + node_select_value);
new_a.innerHTML = "<%:Edit Current Node%>";
node_select.outerHTML = node_select.outerHTML + "&nbsp&nbsp" + new_a.outerHTML;
//node_select.parentNode.insertBefore(new_a, node_select.nextSibling);
}
}
}
}
}
//]]>
</script>

View File

@ -172,14 +172,14 @@ local dsp = require "luci.dispatcher"
"&protoparam=" + b64encsafe(v_protocol_param.value) +
"&remarks=" + b64encutf8safe(v_alias.value);
url = b64encsafe(ssr_str);
} else if (v_type === "Trojan" || v_type === "Trojan-Go") {
} else if (v_type === "Trojan" || v_type === "Trojan-Plus" || v_type === "Trojan-Go") {
var v_password = opt.get(!opt.client && v_type === "Trojan-Go" ? "passwords" : "password");
var v_server = opt.get("address");
var v_port = opt.get("port");
url = encodeURIComponent(v_password.value) +
"@" + v_server.value +
":" + v_port.value + "/?";
if (opt.get("stream_security").value === "tls") {
if (opt.get("tls").checked) {
url += "tls=1";
url += opt.query("sni", "tls_serverName");
url += opt.query("allowinsecure", "tls_allowInsecure");
@ -187,7 +187,7 @@ local dsp = require "luci.dispatcher"
url += "tls=0";
}
if (v_type === "Trojan-Go") {
if (opt.get("stream_security").value === "none" && opt.get("trojan_transport").value === "original") {
if (!opt.get("tls").checked && opt.get("trojan_transport").value === "original") {
var plugin = {};
plugin.type = opt.get("plugin_type").value;
if (plugin.type !== "plaintext") {
@ -432,8 +432,8 @@ local dsp = require "luci.dispatcher"
tls = queryParam.plugin === undefined;
}
if (tls === false) { alert("TODO: plugin params for trojan-go."); }
opt.set('stream_security', tls ? "tls" : "none");
opt.get('stream_security').dispatchEvent(event);
opt.set('tls', tls);
opt.get('tls').dispatchEvent(event);
if (tls) {
opt.set('tls_serverName', queryParam.peer || queryParam.sni || '');
opt.set('tls_allowInsecure', queryParam.allowinsecure === '1');
@ -490,8 +490,8 @@ local dsp = require "luci.dispatcher"
opt.set('address', m.hostname);
opt.set('port', m.port || "443");
opt.set(opt.client ? 'password' : 'passwords', decodeURIComponent(password));
opt.set('stream_security', (queryParam.tls && queryParam.tls === '1') ? 'tls' : 'none');
opt.get('stream_security').dispatchEvent(event);
opt.set('tls', (queryParam.tls && queryParam.tls === '1'));
opt.get('tls').dispatchEvent(event);
var plugin = queryParam.plugin !== undefined;
if (plugin) {
opt.set('trojan_transport', 'original');
@ -509,8 +509,8 @@ local dsp = require "luci.dispatcher"
alert(queryParam.plugin);
}
var tls = !plugin && queryParam.tls === '1';
opt.set('stream_security', tls ? "tls" : "none");
opt.get('stream_security').dispatchEvent(event);
opt.set('tls', tls);
opt.get('tls').dispatchEvent(event);
if (tls) {
opt.set('tls_serverName', queryParam.peer || queryParam.sni || '');
opt.set('tls_allowInsecure', queryParam.allowinsecure === '1');
@ -562,13 +562,14 @@ local dsp = require "luci.dispatcher"
param = sstr.substr(ploc + 2);
}
var ssm = JSON.parse(sstr);
console.log(ssm);
opt.set('remarks', ssm.ps);
opt.set('address', ssm.add);
opt.set('port', ssm.port);
opt.set('alter_id', ssm.aid);
opt.set('uuid', ssm.id);
opt.set('stream_security', ssm.tls === "tls" ? "tls" : "none");
opt.get('stream_security').dispatchEvent(event);
opt.set('tls', ssm.tls === "tls");
opt.get('tls').dispatchEvent(event);
if (ssm.tls === "tls") {
opt.set('tls_serverName', ssm.host);
}

View File

@ -97,6 +97,10 @@ table td, .table .td {
}
}
function copy_node(cbi_id) {
window.location.href = '<%=dsp.build_url("admin/services/passwall/copy_node")%>' + "?section=" + cbi_id;
}
var section = "";
function open_set_node_div(cbi_id) {
section = cbi_id;
@ -212,7 +216,7 @@ table td, .table .td {
catch(err){}
//判断是否含有汉字
var reg = new RegExp("[\\u4E00-\\u9FFF]+","g");
if ((address != null && address != "") && (port != null && port != "") && reg.test(address) == false && (address.indexOf(".") != -1 && address.charAt(address.address - 1) != ".")) {
if ((address != null && address != "") && (port != null && port != "") && reg.test(address) == false) {
return { address: address, port: port };
} else {
return null;
@ -408,12 +412,14 @@ table td, .table .td {
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
var td = edit_btn[i].parentNode;
var new_div = "";
//添加"置顶"按钮到"修改"按钮前
//添加"置顶"按钮
new_div += '<input class="cbi-button" type="button" value="<%:To Top%>" onclick="_cbi_row_top(\'' + id + '\')"/>&nbsp;&nbsp;';
//添加"选择"按钮到"修改"按钮前
//添加"选择"按钮
new_div += '<input class="cbi-button cbi-button-add" type="button" value="<%:Select%>" id="select_' + id + '" onclick="select_node_div(this, \'' + id + '\')"/>&nbsp;&nbsp;';
//添加"应用"按钮到"修改"按钮前
//添加"应用"按钮
new_div += '<input class="cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_' + id + '" onclick="open_set_node_div(\'' + id + '\')"/>&nbsp;&nbsp;';
//添加"复制"按钮
new_div += '<input class="cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node(\'' + id + '\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
}
catch(err) {

View File

@ -112,6 +112,9 @@ msgstr "TCP节点"
msgid "UDP Node"
msgstr "UDP节点"
msgid "Edit Current Node"
msgstr "编辑当前节点"
msgid "Socks Config"
msgstr "Socks配置"
@ -391,12 +394,18 @@ msgstr "类型"
msgid "Balancing"
msgstr "负载均衡"
msgid "Xray_balancing"
msgstr "Xray 负载均衡"
msgid "V2ray_balancing"
msgstr "V2ray 负载均衡"
msgid "Shunt"
msgstr "分流"
msgid "Xray_shunt"
msgstr "Xray 分流"
msgid "V2ray_shunt"
msgstr "V2ray 分流"
@ -406,6 +415,21 @@ msgstr "前置代理"
msgid "Use the default node for the transit."
msgstr "使用默认节点代理转发。"
msgid "No shunt rules? Click me to go to add."
msgstr "没有分流规则?点我前往去添加。"
msgid "Domain Strategy"
msgstr "域名解析策略"
msgid "'AsIs': Only use domain for routing. Default value."
msgstr "AsIs只使用域名进行路由选择。默认值。"
msgid "'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again."
msgstr "IPIfNonMatch当域名没有匹配任何规则时将域名解析成 IPA 记录或 AAAA 记录)再次进行匹配。"
msgid "'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately."
msgstr "IPOnDemand当匹配时碰到任何基于 IP 的规则,将域名立即解析为 IP 进行匹配。"
msgid "Load balancing node list"
msgstr "负载均衡节点列表"
@ -505,6 +529,9 @@ msgstr "应用"
msgid "Use"
msgstr "使用"
msgid "Copy"
msgstr "复制"
msgid "Delay Settings"
msgstr "定时配置"
@ -613,6 +640,12 @@ msgstr "主节点"
msgid "List of backup nodes"
msgstr "备用节点的列表"
msgid "Restore Switch"
msgstr "恢复切换"
msgid "When detects main node is available, switch back to the main node."
msgstr "当检测到主节点可用时,切换回主节点。"
msgid "Configure this node with 127.0.0.1: this port"
msgstr "使用127.0.0.1和此端口配置节点"
@ -637,6 +670,9 @@ msgstr "负载均衡端口"
msgid "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."
msgstr "添加节点指定出口功能是为多WAN用户准备的。负载比重范围1-256。多个主服务器可以负载均衡备用只有在主服务器离线时才会启用可以设置多个组负载均衡端口相同则为一组。"
msgid "Note that the node configuration parameters for load balancing must be consistent, otherwise problems can arise!"
msgstr "注意,负载均衡的节点配置参数必须一致,否则会出问题!"
msgid "Node"
msgstr "节点"
@ -739,23 +775,20 @@ msgstr "组件更新"
msgid "Please confirm that your firmware supports FPU."
msgstr "请确认你的固件支持FPU。"
msgid "if you want to run from memory, change the path, such as %s, Then save the application and update it manually."
msgstr "如果你希望从内存中运行,请更改路径,例如%s,然后保存应用后,再手动更新。"
msgid "if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."
msgstr "如果你希望从内存中运行,请更改路径,/tmp 开头,然后保存应用后,再手动更新。"
msgid "Make sure there is enough space to install %s"
msgstr "确保有足够的空间安装%s"
msgstr "确保有足够的空间安装 %s"
msgid "V2ray Path"
msgstr "V2ray 路径"
msgid "App Path"
msgstr "程序路径"
msgid "Trojan-Go Path"
msgstr "Trojan-Go 路径"
msgid "%s App Path"
msgstr "%s 程序路径"
msgid "Kcptun Client Path"
msgstr "Kcptun 客户端路径"
msgid "Brook Path"
msgstr "Brook 路径"
msgid "%s Client App Path"
msgstr "%s 客户端程序路径"
msgid "Trojan-Go Version API"
msgstr "Trojan-Go 版本 API"
@ -838,6 +871,36 @@ msgstr "不是有效域名,请重新输入!"
msgid "Not valid IP format, please re-enter!"
msgstr "不是有效IP格式请重新输入"
msgid "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'."
msgstr "纯字符串: 当此字符串匹配目标域名中任意部分,该规则生效。比如'sina.com'可以匹配'sina.com'、'sina.com.cn'和'www.sina.com',但不匹配'sina.cn'。"
msgid "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'."
msgstr "正则表达式: 由'regexp:'开始,余下部分是一个正则表达式。当此正则表达式匹配目标域名时,该规则生效。例如'regexp:\\.goo.*\\.com$'匹配'www.google.com'、'fonts.googleapis.com',但不匹配'google.com'。"
msgid "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'."
msgstr "子域名 (推荐): 由'domain:'开始,余下部分是一个域名。当此域名是目标域名或其子域名时,该规则生效。例如'domain:v2ray.com'匹配'www.v2ray.com'、'v2ray.com',但不匹配'xv2ray.com'。"
msgid "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'."
msgstr "完整匹配: 由'full:'开始,余下部分是一个域名。当此域名完整匹配目标域名时,该规则生效。例如'full:v2ray.com'匹配'v2ray.com'但不匹配'www.v2ray.com'。"
msgid "Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
msgstr "预定义域名列表:由'geosite:'开头余下部分是一个名称如geosite:google或者geosite:cn。"
msgid "Domains from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geosite.dat. The tag must exist in the file."
msgstr "从文件中加载域名: 形如'ext:file:tag'必须以ext:小写开头后面跟文件名和标签文件存放在资源目录中文件格式与geosite.dat相同标签必须在文件中存在。"
msgid "IP: such as '127.0.0.1'."
msgstr "IP: 形如'127.0.0.1'。"
msgid "CIDR: such as '127.0.0.0/8'."
msgstr "CIDR: 形如'10.0.0.0/8'."
msgid "GeoIP: such as 'geoip:cn'. It begins with geoip: (lower case) and followed by two letter of country code."
msgstr "GeoIP: 形如'geoip:cn'必须以geoip:(小写)开头,后面跟双字符国家代码,支持几乎所有可以上网的国家。"
msgid "IPs from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geoip.dat. The tag must exist in the file."
msgstr "从文件中加载 IP: 形如'ext:file:tag'必须以ext:小写开头后面跟文件名和标签文件存放在资源目录中文件格式与geoip.dat相同标签必须在文件中存在。"
msgid "Clear logs"
msgstr "清空日志"
@ -868,6 +931,9 @@ msgstr "插件"
msgid "opts"
msgstr "插件选项"
msgid "Xray is currently directly compatible with V2ray and used."
msgstr "Xray 目前可直接兼容 V2ray 并使用。"
msgid "Protocol"
msgstr "协议名称"
@ -955,8 +1021,8 @@ msgstr "是否允许不安全连接。当勾选时,将跳过证书验证。"
msgid "<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)"
msgstr "<br />none默认值不进行伪装发送的数据是没有特征的数据包。<br />srtp伪装成 SRTP 数据包,会被识别为视频通话数据(如 FaceTime。<br />utp伪装成 uTP 数据包,会被识别为 BT 下载数据。<br />wechat-video伪装成微信视频通话的数据包。<br />dtls伪装成 DTLS 1.2 数据包。<br />wireguard伪装成 WireGuard 数据包。(并不是真正的 WireGuard 协议)"
msgid "A legal file path. This file must not exist before running V2Ray."
msgstr "一个合法的文件路径。在运行 V2Ray 之前,这个文件必须不存在。"
msgid "A legal file path. This file must not exist before running."
msgstr "一个合法的文件路径。在运行之前,这个文件必须不存在。"
msgid "TCP Open Socks"
msgstr "开启Socks"
@ -1069,6 +1135,12 @@ msgstr "日志"
msgid "UDP Forward"
msgstr "UDP转发"
msgid "You did not fill in the %s path. Please save and apply then update manually."
msgstr "您没有填写 %s 路径。请保存应用后再手动更新。"
msgid "Not installed unzip, Can't unzip!"
msgstr "未安装unzip无法解压。"
msgid "Can't determine ARCH, or ARCH not supported."
msgstr "无法确认ARCH架构或是不支持。"

View File

@ -48,6 +48,7 @@ config global_rules
option chnroute6_url 'https://ispip.clang.cn/all_cn_ipv6.txt'
config global_app
option xray_file '/usr/bin/xray/'
option v2ray_file '/usr/bin/v2ray/'
option trojan_go_file '/usr/bin/trojan-go'
option kcptun_client_file '/usr/bin/kcptun-client'

View File

@ -25,5 +25,25 @@ uci -q batch <<-EOF >/dev/null
commit uhttpd
EOF
tmp=$(uci -q get passwall.@global_app[0].xray_file)
if [ -z "$tmp" ]; then
uci set passwall.@global_app[0].xray_file="/usr/bin/xray"
else
[ -n "$(echo $tmp | grep -E "^.*/xray/$")" ] && {
uci set passwall.@global_app[0].xray_file="${tmp%?}"
}
fi
tmp=$(uci -q get passwall.@global_app[0].v2ray_file)
if [ -z "$tmp" ]; then
uci set passwall.@global_app[0].v2ray_file="/usr/bin/v2ray"
else
[ -n "$(echo $tmp | grep -E "^.*/v2ray/$")" ] && {
uci set passwall.@global_app[0].v2ray_file="${tmp%?}"
}
fi
uci commit passwall
/etc/init.d/https-dns-proxy stop >/dev/null 2>&1 &
/etc/init.d/https-dns-proxy disable >/dev/null 2>&1 &
rm -rf /tmp/luci-*cache
exit 0

View File

@ -26,6 +26,8 @@ use_tcp_node_resolve_dns=0
use_udp_node_resolve_dns=0
LUA_API_PATH=/usr/lib/lua/luci/model/cbi/$CONFIG/api
API_GEN_SS=$LUA_API_PATH/gen_shadowsocks.lua
API_GEN_XRAY=$LUA_API_PATH/gen_xray.lua
API_GEN_XRAY_PROTO=$LUA_API_PATH/gen_xray_proto.lua
API_GEN_V2RAY=$LUA_API_PATH/gen_v2ray.lua
API_GEN_V2RAY_PROTO=$LUA_API_PATH/gen_v2ray_proto.lua
API_GEN_TROJAN=$LUA_API_PATH/gen_trojan.lua
@ -209,7 +211,7 @@ get_new_port() {
first_type() {
local path_name=${1}
type -t -p "/bin/${path_name}" -p "${TMP_BIN_PATH}/${path_name}" -p "${path_name}" -p "/usr/bin/v2ray/{path_name}" "$@" | head -n1
type -t -p "/bin/${path_name}" -p "${TMP_BIN_PATH}/${path_name}" -p "${path_name}" "$@" | head -n1
}
ln_start_bin() {
@ -333,7 +335,7 @@ run_socks() {
msg="某种原因,此 Socks 服务的相关配置已失联,启动中止!"
fi
if [ "$type" == "v2ray" ] && ([ -n "$(config_n_get $node balancing_node)" ] || [ "$(config_n_get $node default_node)" != "nil" ]); then
if [ "$type" == "xray" -o "$type" == "v2ray" ] && ([ -n "$(config_n_get $node balancing_node)" ] || [ "$(config_n_get $node default_node)" != "nil" ]); then
unset msg
fi
@ -350,6 +352,10 @@ run_socks() {
[ -n "$_username" ] && [ -n "$_password" ] && local _auth="--uname $_username --passwd $_password"
ln_start_bin "$(first_type ssocks)" ssocks_SOCKS_$id --listen $socks_port --socks $server_host:$port $_auth
;;
xray)
lua $API_GEN_XRAY $node nil nil $socks_port > $config_file
ln_start_bin "$(first_type $(config_t_get global_app xray_file notset)/xray xray)" xray -config="$config_file"
;;
v2ray)
lua $API_GEN_V2RAY $node nil nil $socks_port > $config_file
ln_start_bin "$(first_type $(config_t_get global_app v2ray_file notset)/v2ray v2ray)" v2ray -config="$config_file"
@ -423,6 +429,10 @@ run_redir() {
eval port=\$UDP_REDIR_PORT$6
ln_start_bin "$(first_type ipt2socks)" "ipt2socks_udp_$6" -U -l "$port" -b 0.0.0.0 -s "$node_address" -p "$node_port" -R
;;
xray)
lua $API_GEN_XRAY $node udp $local_port nil > $config_file
ln_start_bin "$(first_type $(config_t_get global_app xray_file notset)/xray xray)" xray -config="$config_file"
;;
v2ray)
lua $API_GEN_V2RAY $node udp $local_port nil > $config_file
ln_start_bin "$(first_type $(config_t_get global_app v2ray_file notset)/v2ray v2ray)" v2ray -config="$config_file"
@ -480,6 +490,12 @@ run_redir() {
_socks_username=$(config_n_get $node username)
_socks_password=$(config_n_get $node password)
;;
xray)
local extra_param="tcp"
[ "$6" == 1 ] && [ "$UDP_NODE1" == "tcp" ] && extra_param="tcp,udp"
lua $API_GEN_XRAY $node $extra_param $local_port nil > $config_file
ln_start_bin "$(first_type $(config_t_get global_app xray_file notset)/xray xray)" xray -config="$config_file"
;;
v2ray)
local extra_param="tcp"
[ "$6" == 1 ] && [ "$UDP_NODE1" == "tcp" ] && extra_param="tcp,udp"
@ -603,15 +619,22 @@ start_socks() {
clean_log() {
logsnum=$(cat $LOG_FILE 2>/dev/null | wc -l)
[ "$logsnum" -gt 300 ] && {
[ "$logsnum" -gt 1000 ] && {
echo "" > $LOG_FILE
echolog "日志文件过长,清空处理!"
}
}
start_crontab() {
clean_crontab() {
touch /etc/crontabs/root
sed -i "/$CONFIG/d" /etc/crontabs/root >/dev/null 2>&1 &
#sed -i "/${CONFIG}/d" /etc/crontabs/root >/dev/null 2>&1 &
sed -i "/$(echo "/etc/init.d/${CONFIG}" | sed 's#\/#\\\/#g')/d" /etc/crontabs/root >/dev/null 2>&1 &
sed -i "/$(echo "lua ${APP_PATH}/rule_update.lua log" | sed 's#\/#\\\/#g')/d" /etc/crontabs/root >/dev/null 2>&1 &
sed -i "/$(echo "lua ${APP_PATH}/subscribe.lua start log" | sed 's#\/#\\\/#g')/d" /etc/crontabs/root >/dev/null 2>&1 &
}
start_crontab() {
clean_crontab
auto_on=$(config_t_get global_delay auto_on 0)
if [ "$auto_on" = "1" ]; then
time_off=$(config_t_get global_delay time_off)
@ -666,8 +689,7 @@ start_crontab() {
}
stop_crontab() {
touch /etc/crontabs/root
sed -i "/$CONFIG/d" /etc/crontabs/root >/dev/null 2>&1 &
clean_crontab
ps | grep "$APP_PATH/test.sh" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
/etc/init.d/cron restart
#echolog "清除定时执行命令。"
@ -1118,7 +1140,7 @@ start_haproxy() {
unset msg
failcount=0
while [ "$failcount" -lt "3" ]; do
ubus list network.interface.${export} >/dev/null 2>&1
ip route show dev ${export} >/dev/null 2>&1
if [ $? -ne 0 ]; then
let "failcount++"
echolog " - 找不到出口接口:$export1分钟后再重试(${failcount}/3)${bip}"

View File

@ -8,7 +8,7 @@ require 'luci.model.uci'
require 'luci.util'
require 'luci.jsonc'
require 'luci.sys'
local _api = require "luci.model.cbi.passwall.api.api"
local api = require "luci.model.cbi.passwall.api.api"
-- these global functions are accessed all the time by the event handler
-- so caching them is worth the effort
@ -135,7 +135,7 @@ do
end
ucic2:foreach(application, uciType, function(node)
if node.type == 'V2ray' and node.protocol == '_shunt' then
if node.protocol and node.protocol == '_shunt' then
local node_id = node[".name"]
ucic2:foreach(application, "shunt_rules", function(e)
local _node_id = node[e[".name"]] or nil
@ -146,7 +146,7 @@ do
CONFIG[#CONFIG + 1] = {
log = false,
currentNode = _node,
remarks = "V2ray分流" .. e.remarks .. "节点",
remarks = "分流" .. e.remarks .. "节点",
set = function(server)
ucic2:set(application, node_id, e[".name"], server)
end
@ -161,12 +161,12 @@ do
CONFIG[#CONFIG + 1] = {
log = false,
currentNode = default_node,
remarks = "V2ray分流默认节点",
remarks = "分流默认节点",
set = function(server)
ucic2:set(application, node_id, "default_node", server)
end
}
elseif node.type == 'V2ray' and node.protocol == '_balancing' then
elseif node.protocol and node.protocol == '_balancing' then
local node_id = node[".name"]
local nodes = {}
local new_nodes = {}
@ -184,7 +184,7 @@ do
remarks = node,
set = function(server)
for kk, vv in pairs(CONFIG) do
if (vv.remarks == "V2ray负载均衡节点列表" .. node_id) then
if (vv.remarks == "负载均衡节点列表" .. node_id) then
table.insert(vv.new_nodes, server)
end
end
@ -193,13 +193,13 @@ do
end
end
CONFIG[#CONFIG + 1] = {
remarks = "V2ray负载均衡节点列表" .. node_id,
remarks = "负载均衡节点列表" .. node_id,
nodes = nodes,
new_nodes = new_nodes,
set = function()
for kk, vv in pairs(CONFIG) do
if (vv.remarks == "V2ray负载均衡节点列表" .. node_id) then
log("刷新V2ray负载均衡节点列表")
if (vv.remarks == "负载均衡节点列表" .. node_id) then
log("刷新负载均衡节点列表")
ucic2:foreach(application, uciType, function(node2)
if node2[".name"] == node[".name"] then
local index = node2[".index"]
@ -315,7 +315,7 @@ local function base64Decode(text)
end
-- 处理数据
local function processData(szType, content, add_mode)
log(content, add_mode)
--log(content, add_mode)
local result = {
timeout = 60,
add_mode = add_mode,
@ -344,6 +344,9 @@ local function processData(szType, content, add_mode)
elseif szType == 'vmess' then
local info = jsonParse(content)
result.type = 'V2ray'
if api.is_finded("xray") then
result.type = 'Xray'
end
result.address = info.add
result.port = info.port
result.protocol = 'vmess'
@ -385,11 +388,11 @@ local function processData(szType, content, add_mode)
end
if not info.security then result.security = "auto" end
if info.tls == "tls" or info.tls == "1" then
result.stream_security = "tls"
result.tls = "1"
result.tls_serverName = info.host
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
else
result.stream_security = "none"
result.tls = "0"
end
elseif szType == "ss" then
local idx_sp = 0
@ -506,7 +509,7 @@ local function processData(szType, content, add_mode)
result.type = "Trojan-Go"
result.fingerprint = "firefox"
end
result.stream_security = 'tls'
result.tls = '1'
result.tls_serverName = peer and peer or sni
result.tls_allowInsecure = allowInsecure and "1" or "0"
end
@ -567,7 +570,7 @@ local function processData(szType, content, add_mode)
end
result.port = port
result.fingerprint = "firefox"
result.stream_security = 'tls'
result.tls = '1'
result.tls_serverName = peer and peer or sni
result.tls_allowInsecure = allowInsecure and "1" or "0"
end
@ -647,21 +650,21 @@ end
local function select_node(nodes, config)
local server
if config.currentNode then
-- 特别优先级 V2ray分流 + 备注
if config.currentNode.type == 'V2ray' and config.currentNode.protocol == '_shunt' then
-- 特别优先级 分流 + 备注
if config.currentNode.protocol and config.currentNode.protocol == '_shunt' then
for id, node in pairs(nodes) do
if node.remarks == config.currentNode.remarks then
log('选择【' .. config.remarks .. 'V2ray分流匹配节点:' .. node.remarks)
log('选择【' .. config.remarks .. '分流匹配节点:' .. node.remarks)
server = id
break
end
end
end
-- 特别优先级 V2ray负载均衡 + 备注
if config.currentNode.type == 'V2ray' and config.currentNode.protocol == '_balancing' then
-- 特别优先级 负载均衡 + 备注
if config.currentNode.protocol and config.currentNode.protocol == '_balancing' then
for id, node in pairs(nodes) do
if node.remarks == config.currentNode.remarks then
log('选择【' .. config.remarks .. 'V2ray负载均衡匹配节点:' .. node.remarks)
log('选择【' .. config.remarks .. '负载均衡匹配节点:' .. node.remarks)
server = id
break
end
@ -679,11 +682,11 @@ local function select_node(nodes, config)
end
end
end
-- 第二优先级 IP + 端口
-- 第二优先级 类型 + IP + 端口
if not server then
for id, node in pairs(nodes) do
if node.address and node.port then
if node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port then
if node.type and node.address and node.port then
if node.type == config.currentNode.type and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then
if config.log == nil or config.log == true then
log('选择【' .. config.remarks .. '】第二匹配节点:' .. node.remarks)
end
@ -693,11 +696,11 @@ local function select_node(nodes, config)
end
end
end
-- 第三优先级 IP
-- 第三优先级 IP + 端口
if not server then
for id, node in pairs(nodes) do
if node.address then
if node.address == config.currentNode.address then
if node.address and node.port then
if node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port then
if config.log == nil or config.log == true then
log('选择【' .. config.remarks .. '】第三匹配节点:' .. node.remarks)
end
@ -707,13 +710,27 @@ local function select_node(nodes, config)
end
end
end
-- 第四优先级备注
-- 第四优先级 IP
if not server then
for id, node in pairs(nodes) do
if node.address then
if node.address == config.currentNode.address then
if config.log == nil or config.log == true then
log('选择【' .. config.remarks .. '】第四匹配节点:' .. node.remarks)
end
server = id
break
end
end
end
end
-- 第五优先级备注
if not server then
for id, node in pairs(nodes) do
if node.remarks then
if node.remarks == config.currentNode.remarks then
if config.log == nil or config.log == true then
log('选择【' .. config.remarks .. '】第四匹配节点:' .. node.remarks)
log('选择【' .. config.remarks .. '】第匹配节点:' .. node.remarks)
end
server = id
break
@ -751,7 +768,7 @@ local function update_node(manual)
end)
for _, v in ipairs(nodeResult) do
for _, vv in ipairs(v) do
local uuid = _api.gen_uuid()
local uuid = api.gen_uuid()
local cfgid = ucic2:section(application, uciType, uuid)
cfgid = uuid
for kkk, vvv in pairs(vv) do
@ -858,7 +875,7 @@ local function parse_link(raw, remark, manual)
not result.address:find("%.") or -- 虽然没有.也算域,不过应该没有人会这样干吧
result.address:sub(#result.address) == "." -- 结尾是.
then
log('丢弃无效节点: ' .. result.type .. ' 节点, ' .. result.remarks)
log('丢弃过滤节点: ' .. result.type .. ' 节点, ' .. result.remarks)
else
tinsert(all_nodes, result)
end

View File

@ -67,40 +67,15 @@ test_auto_switch() {
fi
status=$(test_proxy)
if [ "$status" == 0 ]; then
echolog "自动切换检测:${type}_${index}节点$(config_n_get $now_node type) $(config_n_get $now_node address) $(config_n_get $now_node port)正常。"
#检测主节点是否能使用
local main_node=$(config_t_get auto_switch tcp_main1)
if [ "$now_node" != "$main_node" ]; then
local node_type=$(echo $(config_n_get $main_node type) | tr 'A-Z' 'a-z')
if [ "$node_type" == "socks" ]; then
local node_address=$(config_n_get $main_node address)
local node_port=$(config_n_get $main_node port)
[ -n "$node_address" ] && [ -n "$node_port" ] && local curlx="socks5h://$node_address:$node_port"
else
local tmp_port=$(/usr/share/passwall/app.sh get_new_port 61080 tcp)
/usr/share/passwall/app.sh run_socks "$main_node" "127.0.0.1" "$tmp_port" "/var/etc/passwall/auto_switch_$index.json" "10"
local curlx="socks5h://127.0.0.1:$tmp_port"
fi
sleep 10s
proxy_status=$(test_url "https://www.google.com/generate_204" 3 3 "-x $curlx")
ps -w | grep -v "grep" | grep "/var/etc/passwall/auto_switch_$index.json" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
if [ "$proxy_status" -eq 200 ]; then
#主节点正常,切换到主节点
echolog "自动切换检测:${type}_${index}主节点正常,切换到主节点!"
/usr/share/passwall/app.sh node_switch $type $2 $index $main_node
return 0
fi
fi
return 0
elif [ "$status" == 2 ]; then
if [ "$status" == 2 ]; then
echolog "自动切换检测:无法连接到网络,请检查网络是否正常!"
return 2
elif [ "$status" == 1 ]; then
echolog "自动切换检测:${type}_${index}节点异常,开始切换节点!"
fi
local restore_switch=$(config_t_get auto_switch restore_switch${index} 0)
if [ "$restore_switch" == "1" ]; then
#检测主节点是否能使用
local main_node=$(config_t_get auto_switch tcp_main1)
local main_node=$(config_t_get auto_switch tcp_main${index})
if [ "$now_node" != "$main_node" ]; then
local node_type=$(echo $(config_n_get $main_node type) | tr 'A-Z' 'a-z')
if [ "$node_type" == "socks" ]; then
@ -122,7 +97,13 @@ test_auto_switch() {
return 0
fi
fi
fi
if [ "$status" == 0 ]; then
echolog "自动切换检测:${type}_${index}节点$(config_n_get $now_node type) $(config_n_get $now_node address) $(config_n_get $now_node port)正常。"
return 0
elif [ "$status" == 1 ]; then
echolog "自动切换检测:${type}_${index}节点异常,开始切换节点!"
local new_node
in_backup_nodes=$(echo $b_tcp_nodes | grep $now_node)
# 判断当前节点是否存在于备用节点列表里