op-packages/luci-app-xclient/root/usr/share/xclient/gen_config.lua
github-actions[bot] 32600acc60 🌴 Sync 2026-03-05 23:51:42
2026-03-05 23:51:42 +08:00

627 lines
16 KiB
Lua

#!/usr/bin/lua
--local ucursor = require "luci.model.uci"
local ucursor = require "luci.model.uci".cursor()
local json = require "luci.jsonc"
local nixiofs = require "nixio.fs"
local proxy_section = ucursor:get_first("xclient", "global")
local proxy = ucursor:get_all("xclient", proxy_section)
local tcp_server_section = proxy.global_server or nil
local tcp_server = ucursor:get_all("xclient", tcp_server_section)
local udp_server_section = proxy.udp_relay_server or nil
local udp_server = ucursor:get_all("xclient", udp_server_section)
local socks5_server_section = proxy.socks5_server or nil
local socks5_server = ucursor:get_all("xclient", socks5_server_section)
local server_section = arg[1] or nil
local node = ucursor:get_all("xclient", server_section)
local geoip_existence = false
local geosite_existence = false
local xray_data_file_iterator = nixiofs.dir("/usr/share/xclient")
local proto = arg[2]
local local_port = arg[3] or "1234"
local socks_port = arg[4] or "1080"
local _protocol = arg[5] or "redir"
local _local = arg[6] or "0"
repeat
local fn = xray_data_file_iterator()
if fn == "geoip.dat" then
geoip_existence = true
end
if fn == "geosite.dat" then
geosite_existence = true
end
until fn == nil
local function direct_outbound()
return {
protocol = "freedom",
settings = {
domainStrategy = "UseIP"
},
tag = "direct"
}
end
local function block_outbound()
return {
protocol = "blackhole",
tag = "block",
settings = {
response = {
type = "http"
}
}
}
end
local function dns_outbound()
return {
protocol = "dns",
address = "1.1.1.1",
network = "tcp,udp",
port = 53,
tag = "dns_outbound"
}
end
local function stream_tcp_fake_http_request(server)
if server.tcp_guise == "http" then
return {
version = "1.1",
method = "GET",
path = server.http_path,
headers = {
Host = server.http_host,
User_Agent = {
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"
},
Accept_Encoding = {"gzip, deflate"},
Connection = {"keep-alive"},
Pragma = "no-cache"
}
}
else
return nil
end
end
local function stream_tcp_fake_http_response(server)
if server.tcp_guise == "http" then
return {
version = "1.1",
status = "200",
reason = "OK",
headers = {
Content_Type = {"application/octet-stream", "video/mpeg"},
Transfer_Encoding = {"chunked"},
Connection = {"keep-alive"},
Pragma = "no-cache"
}
}
else
return nil
end
end
local function stream_tcp(server)
if server.transport == "tcp" then
return {
header = {
type = server.tcp_guise,
request = stream_tcp_fake_http_request(server),
response = stream_tcp_fake_http_response(server)
}
}
else
return nil
end
end
local function stream_h2(server)
if (server.transport == "h2") then
return {
path = server.h2_path,
host = server.h2_host,
read_idle_timeout = tonumber(server.read_idle_timeout) or nil,
health_check_timeout = tonumber(server.health_check_timeout) or nil
}
else
return nil
end
end
local function stream_grpc(server)
if (server.transport == "grpc") then
return {
serviceName = server.serviceName or "",
multiMode = server.grpc_multi_mode == "1",
initial_windows_size = tonumber(server.initial_windows_size) or nil,
idle_timeout = tonumber(server.idle_timeout) or nil,
health_check_timeout = tonumber(server.health_check_timeout) or nil,
permit_without_stream = (server.permit_without_stream == "1") and true or nil
}
else
return nil
end
end
local function stream_kcp(server)
if server.transport == "mkcp" then
local mkcp_seed = nil
if server.seed ~= "" then
mkcp_seed = server.seed
end
return {
mtu = tonumber(server.mtu),
tti = tonumber(server.tti),
uplinkCapacity = tonumber(server.uplink_capacity),
downlinkCapacity = tonumber(server.downlink_capacity),
congestion = (server.congestion == "1") and true or false,
readBufferSize = tonumber(server.read_buffer_size),
writeBufferSize = tonumber(server.write_buffer_size),
seed = mkcp_seed,
header = {
type = server.kcp_guise
}
}
else
return nil
end
end
local function stream_quic(server)
if server.transport == "quic" then
return {
security = server.quic_security,
key = server.quic_key,
header = {type = server.quic_guise}
}
else
return nil
end
end
local function stream_ws(server)
if server.transport == "ws" then
local headers = nil
if (server.ws_host ~= nil) then
headers = {
Host = server.ws_host or server.tls_host
}
end
return {
path = server.ws_path,
headers = headers
}
else
return nil
end
end
local function tls_settings(server)
local result = {
fingerprint = server.fingerprint,
rejectUnknownSni = (server.rejectUnknownSni == "1") and true or false,
allowInsecure = (server.insecure == "1") and true or false,
serverName = server.tls_host,
}
if server.alpn ~= nil then
local alpn = {}
for _, x in ipairs(server.alpn) do
table.insert(alpn, x)
end
result["alpn"] = alpn
end
return result
end
local function reality_settings(server)
local result = {
fingerprint = server.fingerprint,
shortId = server.shortId,
publicKey = server.publicKey,
serverName = server.tls_host,
spiderX = server.spiderX,
}
return result
end
local function shadowsocks_outbound(server, tag)
return {
protocol = "shadowsocks",
tag = tag,
settings = {
servers = {
{
address = server.server,
port = tonumber(server.server_port),
password = server.password,
method = server.encrypt_method_v2ray_ss
}
}
},
streamSettings = {
network = server.transport,
security = (server.security == 'tls') and "tls" or "none",
tlsSettings = (server.security == 'tls') and "tls" and tls_settings(server) or nil,
quicSettings = stream_quic(server),
kcpSettings = stream_kcp(server),
tcpSettings = stream_tcp(server) and server.protocol ~= "shadowsocks" or nil,
wsSettings = stream_ws(server),
grpcSettings = stream_grpc(server),
httpSettings = stream_h2(server)
},
mux = (server.mux == "1" and server.security == 'tls' or server.security == 'none') and {
enabled = true,
concurrency = tonumber(server.concurrency)
} or nil
}
end
local function vmess_outbound(server, tag)
return {
protocol = "vmess",
tag = tag,
settings = {
vnext = {
{
address = server.server,
port = tonumber(server.server_port),
users = {
{
id = server.vmess_id,
alterId = 0,
security = server.vmess_encryption
}
}
}
}
},
streamSettings = {
network = server.transport,
security = (server.security == 'tls') and "tls" or "none",
tlsSettings = (server.security == 'tls') and "tls" and tls_settings(server) or nil,
tcpSettings = stream_tcp(server),
wsSettings = stream_ws(server),
grpcSettings = stream_grpc(server),
httpSettings = stream_h2(server),
quicSettings = stream_quic(server),
kcpSettings = stream_kcp(server),
}
}
end
local function vless_outbound(server, tag)
local flow = server.flow
if server.flow == "none" then
flow = nil
end
return {
protocol = "vless",
tag = tag,
settings = {
vnext = {
{
address = server.server,
port = tonumber(server.server_port),
users = {
{
id = server.vmess_id,
flow = flow,
encryption = server.vless_encryption
}
}
}
}
},
streamSettings = {
network = server.transport,
security = (server.security == 'reality') and "reality" or (server.security == 'tls') and "tls" or "none",
tlsSettings = (server.security == 'tls') and "tls" and tls_settings(server) or nil,
realitySettings = (server.security == 'reality') and "reality" and reality_settings(server) or nil,
tcpSettings = stream_tcp(server),
wsSettings = stream_ws(server),
grpcSettings = stream_grpc(server),
httpSettings = stream_h2(server),
quicSettings = stream_quic(server),
kcpSettings = stream_kcp(server),
}
}
end
local function trojan_outbound(server, tag)
local flow = server.flow
if server.flow == "none" then
flow = nil
end
return {
protocol = "trojan",
tag = tag,
settings = {
servers = {
{
address = server.server,
port = tonumber(server.server_port),
password = server.password,
flow = flow,
}
}
},
streamSettings = {
network = server.transport,
security = (server.security == 'tls') and "tls" or "none",
tlsSettings = (server.security == 'tls') and "tls" and tls_settings(server) or nil,
tcpSettings = stream_tcp(server) and server.protocol ~= "trojan" or nil,
wsSettings = stream_ws(server),
grpcSettings = stream_grpc(server),
httpSettings = stream_h2(server),
quicSettings = stream_quic(server),
kcpSettings = stream_kcp(server),
},
mux = (server.mux == "1" and server.security == 'tls' or server.security == 'none') and {
enabled = true,
concurrency = tonumber(server.concurrency)
} or nil
}
end
local function server_outbound(server, tag)
if server.protocol == "vmess" then
return vmess_outbound(server, tag)
end
if server.protocol == "vless" then
return vless_outbound(server, tag)
end
if server.protocol == "shadowsocks" then
return shadowsocks_outbound(server, tag)
end
if server.protocol == "trojan" then
return trojan_outbound(server, tag)
end
return nil
end
local function proxy_inbound()
if local_port == "0" then
return nil
end
return {
port = local_port,
protocol = "dokodemo-door",
tag = "proxy_inbound",
sniffing = {
enabled = true,
destOverride = {"http", "tls"}
} ,
settings = {
network = proto,
followRedirect = true
}
}
end
local function socks_inbound()
if socks_port == "0" then
return nil
end
return {
port = socks_port,
protocol = "socks",
tag = "socks_inbound",
settings = {
auth = "noauth",
udp = true
}
}
end
local function dns_inbounds()
local servers = {}
if proxy["dns_servers"] ~= nil then
for _, x in ipairs(proxy["dns_servers"]) do
table.insert(servers, x)
end
end
local i = 0
ucursor:foreach("xclient", "dns", function(v)
i = i + 1
table.insert(servers, {
address = v.address or nil,
port = v.port,
domains = v.domains or nil,
expectIPs = v.expectIPs or nil,
skipFallback = v.skipFallback == "1" and true or false
})
end)
return servers
end
local function dns()
local result = {}
for _, v in ipairs(dns_inbounds()) do
table.insert(result, v)
end
return {
server = result,
clientIp = proxy.clientIp or nil,
queryStrategy = proxy.queryStrategy,
disableCache = proxy.disableCache == "1" and true or false,
disableFallback = proxy.disableFallback == "1" and true or false,
disableFallbackIfMatch = proxy.disableFallbackIfMatch == "1" and true or false,
tag = "dns_inbound"
}
end
local function inbounds()
local i = {}
if proxy.global_server ~= "nil" or proxy.udp_server ~= "nil" then
i = {
proxy_inbound(),
}
if proxy.socks5_server == "same" or _local == "1" then
table.insert(i, socks_inbound())
end
table.insert(i, {
listen = "127.0.0.1",
port = 8888,
protocol = "dokodemo-door",
settings = {
address = "127.0.0.1"
},
tag = "api"
})
return i
end
end
local function api()
return {
tag = "api",
services = {
"StatsService"
}
}
end
local function routing()
local rule = {}
local i = 0
ucursor:foreach("xclient", "rule", function(v)
i = i + 1
table.insert(rule, {
type = "field",
domainMatcher = v.domainMatcher or nil,
domain = v.domain or nil,
ip = v.ip or nil,
port = v.port or nil,
sourcePort = v.sourcePort or nil,
network = v.network or nil,
source = v.source or nil,
inboundTag = v.inboundTag or nil,
protocol = v.protocol or nil,
outboundTag = v.outboundTag or nil,
})
end)
table.insert(rule, {
type = "field",
inboundTag = {
"api"
},
outboundTag = "api",
enabled = true
})
return rule
end
local function routing_rules()
local result = {}
for _, v in ipairs(routing()) do
table.insert(result, v)
end
return {
domainMatcher = proxy.domainMatcher,
domainStrategy = proxy.routing_strategy or 'AsIs',
rules = result
}
end
local function outbounds()
local result = {}
if arg[7] == "none" then
result = {
server_outbound(socks5_server, "proxy_outbound"),
dns_outbound(),
direct_outbound(),
block_outbound()
}
elseif proto == "tcp" then
result = {
server_outbound(tcp_server, "proxy_outbound"),
dns_outbound(),
direct_outbound(),
block_outbound()
}
elseif proto == "udp" then
result = {
server_outbound(udp_server, "proxy_outbound"),
dns_outbound(),
direct_outbound(),
block_outbound()
}
elseif proto == "tcp,udp" then
result = {
server_outbound(tcp_server, "proxy_outbound"),
dns_outbound(),
direct_outbound(),
block_outbound()
}
end
return result
end
local function logging()
return {
loglevel = proxy.log_level or "warning",
}
end
local function policy()
return {
system = {
statsOutboundUplink = true,
statsOutboundDownlink = true
}
}
end
local xclient = {
log = logging(),
dns = dns(),
routing = routing_rules(),
dns = dns(),
api = api(),
policy = policy(),
routing = routing_rules(),
inbounds = inbounds(),
outbounds = outbounds()
}
local shadowsocks_plugin = {
server = node.server,
server_port = tonumber(node.server_port),
local_address = "0.0.0.0",
local_port = tonumber(local_port),
mode = (proto == "tcp,udp") and "tcp_and_udp" or proto .. "_only",
password = node.password,
method = node.encrypt_method_v2ray_ss,
reuse_port = true,
protocol = _protocol,
plugin = node.plugin,
plugin_opts = node.plugin_opts or nil
}
if node.protocol == "shadowsocks-plugin" then
print(json.stringify(shadowsocks_plugin, true))
else
print(json.stringify(xclient, true))
end