🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

+349
View File
@@ -0,0 +1,349 @@
# config tree naming convention
```
- log{}
|- log_access: string
|- log_error: string
|- log_level: string
|- log_dnslog: bool
- api{}
|- tag: string
|- services[]: string array
- dns{}
|- tag: string
|- hosts{}: key:value : domain:ipaddress
|- servers[]: server object array
|- querystrategy: string: UseIP | UseIPv4 | UseIPv6
|- server_address: string
|- server_port: number: 0-65535
|- server_domains[]: string array
|- server_domain: string
|- server_expectedips[]: string array
|- server_expectedip: ipaddress
|- server_skipfallback: bool
|- server_clientip: string
|- clientip: string
|- querystrategy: string: UseIP | UseIPv4 | UseIPv6
|- disablecache: bool
|- disablefallback: bool
|- disablefallbackifmatch: bool
- routing{}
|- tag: string
|- domainstrategy: string: AsIs | IPIfNonMatch | IPOnDemand
|- domainmatcher: string: hybrid | hybrid
|- rules
|- rule_domainmatcher: string: hybrid | hybrid
|- rule_type: string: field
|- rule_domain[]: string | regexp: | domain: | full: | geosite: | ext:file:tag
|- rule_ip[]: cidr | geoip: | ext:file:tag
|- rule_port: number: 0-65535
|- rule_sourceport: number: 0-65535
|- rule_network: string: tcp | udp | tcp,udp
|- rule_source[]: cidr | geoip: | ext:file:tag
|- rule_user[]: string: email
|- rule_inboundtag[]: string
|- rule_protocol[]: string: http | tls | bittorrent
|- rule_attrs: string
|- rule_outboundtag: string
|- rule_balancertag: string
- policy{}
|- levels{0:{}, 1:{}, ...}
|- level_handshake: number: default 4
|- level_connidle: number: default 300
|- level_uplinkonly: number: default 2
|- level_downlinkonly: number default 5
|- level_statsuseruplink: bool
|- level_statsuserdownlink: bool
|- level_buffersize: number:
|- system{}
|- system_statsinbounduplink: bool
|- system_statsinbounddownlink: bool
|- system_statsoutbounduplink: bool
|- system_statsoutbounddownlink: bool
- inbounds[]
|- tag: string
|- listen: ipaddress or socket
|- port: number: 0-65535
|- protocol: string: dokodemo-door | http | shadowsocks | socks | trojan | vless | vmess
|- settings{}: protocol settings object array
|- streamsettings{}
|- stream_network: string: tcp | kcp | ws | http | domainsocket | quic | grpc
|- stream_security: string: none | tls | xtls
|- tcp_settings{}
|- tls_settings{}
|- kcp_settings{}
|- ws_settings{}
|- http_settings{}
|- quic_settings{}
|- ds_settings{}
|- grpc_settings{}
|- sockopt{}
|- sniffing{}
|- sniffing_enabled: bool
|- sniffing_destoverride[]: string array: http | tls | fakedns
|- sniffing_metadataonly: bool
|- sniffing_domainsexcluded[]: string array
|- sniffing_routeonly: bool
|- allocate{}
|- allocate_strategy: string: aways | random
|- allocate_refresh: number: >= 2
|- allocate_concurrency: number: 1 <= concurrency <= 65535/3
inbound_protocol{}
|- dokodemo-door{}
|- dokodemo_address: ipaddress or domain
|- dokodemo_port: number: 0-65535
|- dokodemo_network: tcp | udp | tcp,udp
|- dokodemo_timeout: number: default 300
|- dokodemo_followredirect: bool
|- dokodemo_level: number: default 0
|- http{}
|- http_timeout: number: default 300
|- http_auth: string: noauth | password
|- http_accounts{}
|- http_user: string
|- http_password: string
|- http_allocatransparent: bool
|- http_level: number: default 0
|- shadowsocks{}
|- ss_password: string
|- ss_method: string: aes-256-gcm | aes-128-gcm | chacha20-poly1305 | none | plain
|- ss_email: string
|- ss_network: string: tcp | udp | tcp,udp
|- ss_level: number default 0
|- socks{}
|- socks_auth: string: noauth | password
|- socks_accounts{}
|- socks_user: string
|- socks_pass: string
|- socks_udp: bool
|- socks_ip: ipaddress
|- socks_level: number: default 0
|- trojan{}
|- trojan_password: string
|- trojan_email: string
|- trojan_level: number
|- vless{}
|- vless_id: string
|- vless_email: string
|- vless_flow: string: xtls-rprx-vision | xtls-rprx-vision-udp443 | ...
|- vless_level: number
|- vless_decyption: string: none
|- vmess{}
|- vmess_id: string
|- vmess_alterid: number
|- vmess_email: string
|- vmess_level: number
|- vmess_detour: string
- outbounds[]
|- tag: string
|- address: ipaddress or domain
|- port: number: 0-65535
|- sendthrough: ipaddress
|- protocol: string: blackhole | dns | freedom | http | shadowsocks | socks | trojan | vless | vmess | wireguard
|- settings{}: protocol settings object array
|- streamsettings{}
|- stream_network: string: tcp | kcp | ws | http | domainsocket | quic | grpc
|- stream_security: string: none | tls | xtls
|- tls_settings{}
|- tcp_settings{}
|- kcp_settings{}
|- ws_settings{}
|- http_settings{}
|- quic_settings{}
|- ds_settings{}
|- grpc_settings{}
|- sockopt{}
|- proxysettings{}
|- proxy_settings: string: another outbound tag
|- mux{}
|- mux_enabled: bool
|- mux_concurrency: number: 8 <= concurrency <= 1024
- outbound_protocol{}
|- blackhole{}
|- blackhole_response: string: none | http
|- dns{}
|- dns_network: string: tcp | udp
|- dns_address: ipaddress
|- dns_port: number: 0-65535
|- freedom{}
|- freedom_domainstrategy: string: AsIs | UseIP | UseIPv4 | UseIPv6
|- freedom_redirect: ipaddress:port
|- freedom_userlevel: number
|- http{}
|- address: ipadress or domain
|- port: number: 0-65536
|- http_users[]
|- http_user: string
|- http_pass: string
|- shadowsocks{}
|- address: ipaddress or domain
|- port: number: 0-65535
|- ss_method: string: aes-256-gcm | aes-128-gcm | chacha20-poly1305 | none | plain
|- ss_password: string
|- ss_uot: bool
|- ss_email: string
|- ss_level: number
|- socks{}
|- address: ipaddress or domain
|- port: number: 65535
|- socks_users[]
|- socks_user: string
|- socks_pass: string
|- socks_level: number
|- trojan{}
|- address: ipaddress or domain
|- port: number: 65535
|- trojan_password: string
|- trojan_email: string
|- trojan_level: number
|- vless{}
|- address: ipaddress or domain
|- port: number: 65535
|- vless_users[]
|- vless_id: string
|- vless_encryption: string: none
|- vless_flow: string: xtls-rprx-vision | xtls-rprx-vision-udp443 | ...
|- vless_level: number
|- vmess{}
|- address: ipaddress or domain
|- port: number: 0-65535
|- vmess_user[]
|- vmess_id: string
|- vmess_alterid: number
|- vmess_security: string: aes-128-gcm | chacha20-poly1305 | auto | none | zero
|- vmess_level: number
|- wireguard{}
|- wg_secretkey: string
|- wg_address[ IPv4_CIDR, IPv6_CIDR, ...]
|- wg_peers[]
|- wg_endpoint: ipaddress:port or domain:port
|- wg_publickey: string
|- wg_mtu: number: default 1420
|- wg_workers: number: default 2
- transport{}
|- tcp_settings{}
|- tcp_acceptproxyprotocol: bool - inbound only
|- tcp_header{}
|- tcp_type: string: none | http
|- tcp_path: string
|- tcp_host: domain
|- kcp_settings{}
|- kcp_mtu: number: 576 <= mtu <= 1460
|- kcp_tti: number: 10 <= tti <= 100
|- kcp_uplinkcapacity: number: default 5
|- kcp_downlinkcapacity: number: default 5
|- kcp_congestion: bool
|- kcp_readbuffersize: number: default 2
|- kcp_writebuffersize: number: default 2
|- kcp_header{}
|- kcp_type: string: none | srtp | utp | wechat-video | dtls | wireguard
|- kcp_seed: string
|- ws_settings{}
|- ws_acceptproxyprotocol: bool - inbound only
|- ws_path: string: default: /
|- ws_headers{}
|- ws_host: string: domain
|- http_settings{}
|- http_host[]: string array
|- http_path: string: default /
|- http_readidletimeout: number - outbound only
|- http_healthchecktimeout: number - outbound only
|- http_method: string: PUT | GET | POST
|- quic_settigns{}
|- quic_security: string: none | aes-128-gcm | chacha20-poly1305
|- quic_key: string
|- quic_header{}
|- quic_type: string: none | srtp | utp | wechat-video | dtls | wireguard
|- ds_settings{} - inbound only
|- ds_path: string
|- ds_abstract: bool
|- ds_padding: bool
|- grpc_settings{}
|- grpc_servicename: string
|- grpc_multimode: bool - outbound only
|- grpc_idletimeout: number: >= 10 - outbound only
|- grpc_healthchecktimeout: number: default 20 - outbound only
|- grpc_permitwithoutstream: bool - outbound only
|- grpc_initialwindownsize: number: default 0 - outbound only
|- grpc_useragent: string - outbound only
|- sockopt{}
|- sockopt_mark: number - outbound only
|- sockopt_tcpfastopen: bool
|- sockopt_tproxy: string: redirect | tproxy | off - inbound only
|- sockopt_domainstrategy: string: AsIs | UseIP | UseIPv4 | UseIPv6 - outbound only
|- sockopt_dialerproxy: string: another oubound tag - outbound only
|- sockopt_acceptproxyprotocol: bool - inbound only
|- sockopt_tcpkeepaliveinterval: number - inbound only
|- sockopt_tcpcongestion: string: bbr | cubic | reno - inbound only
|- sockopt_interface: string - outbound only
|- tls_settings{}
|- tls_servername: string - outbound only
|- tls_rejectnuknownsni: bool - inbound only
|- tls_allowinsecure: bool - outbound only
|- tls_alpn[]: sting array: h2 | http/1.1
|- tls_minversion: string - inbound only
|- tls_maxversion: string - inbound only
|- tls_ciphersuites: string: cipher1:cipher2:... - inbound only
|- tls_certificates[] - inbound only
|- cert_ocspStapling: number: default 3600
|- cert_onetimeloading: bool
|- cert_usage: string
|- cert_certificatefile: string: file path
|- cert_certificatekeyfile: string: file path
|- cert_certficate: string array
|- cert_key: string array
|- tls_disablesystemroot: bool - outbound only
|- tls_enablesessionresumption: bool - outbound only
|- tls_fingerprint: string: chrome | firefox | safari | ios | android | edge | random | randomized - outbound only
|- tls_pinnedpeercertificatechainsha256[]: string - outbound only
|- reality_settings{}
|- reality_show: bool
|- reality_dest: string - inbound only
|- reality_xver: number: 0 | 1 | 2 : default 0 - inbound only
|- reality_servername[]: string array - inbound only
|- reality_privatekey: string - inbound only
|- reality_minclientver: string
|- reality_maxclientver: string
|- reality_maxtimediff: number: ms
|- reality_shortid[]: string array
|- reality_servername: string - outbound only
|- reality_fingerprint: string: chrome | firefox | safari | ios | android | edge | random | randomized - outbound only
|- reality_shortid: string
|- reality_publickey: string
|- reality_spiderx: string
|- mux_settings{} - inbound only
|- mux_enabled: bool
|- mux_concurrency: number: 1 <= concurrency <= 1024, -1
|- mux_xudpconcurrency: number: 1 <= concurrency <= 1024, -1
|- mux_xudpproxyudp443: string: reject | allow | skip
- fallback{}: require inbound to be tcp+tls
|- name: string: default ""
|- alpn: string: h2 | http/1.1
|- path: string: /xxx
|- dest: string | number
|- xver: number: default 0
- stats{}
- reverse{}
|- bridges[]
|- bridge_tag: string
|- bridge_domain: string
|- portals[]
|- portal_tag: string
|- portal_domain: string
- fakedns{}
|- ip_pool: CIDR
|- pool_size: number
```
+972
View File
@@ -0,0 +1,972 @@
#!/usr/bin/lua
local ucursor = require "luci.model.uci"
local json = require "luci.jsonc"
local nixiofs = require "nixio.fs"
local general_section = ucursor:get_first("xjay", "general")
local general = ucursor:get_all("xjay", general_section)
local inbound_section = ucursor:get_first("xjay", "inbound")
local inbound = ucursor:get_all("xjay", inbound_section)
local outbound_section = ucursor:get_first("xjay", "outbound")
local outbound = ucursor:get_all("xjay", outbound_section)
local dns_section = ucursor:get_first("xjay", "dns")
local dns = ucursor:get_all("xjay", dns_section)
local routing_section = ucursor:get_first("xjay", "routing")
local routing = ucursor:get_all("xjay", routing_section)
local misc_section = ucursor:get_first("xjay", "misc")
local misc = ucursor:get_all("xjay", misc_section)
local function tcp_settings(data, direction)
if data.stream_network == "tcp" then
local acceptproxyprotocal = data.tcp_acceptproxyprotocol == "true" and true or false
return {
acceptProxyProtocol = direction == "inbound" and acceptproxyprotocal or nil,
header = {
type = data.tcp_type,
request = data.tcp_type == "http" and {
version = "1.1",
method = "GET",
path = data.http_path,
headers = {
Host = data.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"
}
} or nil,
response = data.tcp_type == "http" and {
version = "1.1",
status = "200",
reason = "OK",
headers = {
Content_Type = {"application/octet-stream", "video/mpeg"},
Transfer_Encoding = {"chunked"},
Connection = {"keep-alive"},
Pragma = "no-cache"
}
} or nil
} or nil
}
else
return nil
end
end
local function kcp_settings(data, direction)
if data.stream_network == "kcp" then
return {
mtu = data.kcp_mtu ~= nil and tonumber(data.kcp_mtu) or nil,
tti = data.kcp_tti ~= nil and tonumber(data.kcp_tti) or nil,
uplinkCapacity = data.kcp_uplinkcapacity ~= nil and tonumber(data.kcp_uplinkcapacity) or nil,
downlinkCapacity = data.kcp_downlinkcapacity ~= nil and tonumber(data.kcp_downlinkcapacity) or nil,
congestion = data.kcp_congestion == "true" and true or false,
readBufferSize = data.kcp_readbuffersize ~= nil and tonumber(data.kcp_readbuffersize) or nil,
writeBufferSize = data.kcp_writebuffersize ~= nil and tonumber(data.kcp_writebuffersize) or nil,
seed = data.kcp_seed,
header = {
type = data.kcp_type
}
}
else
return nil
end
end
local function ws_settings(data, direction)
if data.stream_network == "ws" then
local acceptproxyprotocal = nil
if direction == "inbound" then
acceptproxyprotocal = data.ws_acceptproxyprotocol == "true" and true or false
end
return {
acceptProxyProtocol = acceptproxyprotocal,
path = data.ws_path,
headers = data.ws_host ~= nil and {
Host = data.ws_host
} or nil
}
else
return nil
end
end
local function http_settings(data, direction)
if data.stream_network == "h2" then
local readidletimeout = nil
local healthchecktimeout = nil
local http_settings = {}
http_settings.host = data.http_host
http_settings.path = data.http_path
if direction == "outbound" then
http_settings.read_idle_timeout = data.http_readidletimeout ~= nil and tonumber(data.http_readidletimeout) or nil
http_settings.health_check_timeout = data.http_healthchecktimeout ~= nil and tonumber(data.http_healthchecktimeout) or nil
end
http_settings.method = data.http_method
-- headers not implemented yet
return next(http_settings) ~= nil and http_settings or nil
else
return nil
end
end
local function quic_settings(data, direction)
if data.stream_network == "quic" then
return {
security = data.quic_security,
key = data.quic_key,
header = {
type = data.quic_type
}
}
else
return nil
end
end
local function ds_settings(data, direction)
if data.stream_network == "ds" and direction == "inbound" then
return {
path = data.ds_path,
abstract = data.ds_abstract == "true" and true or false,
padding = data.ds_padding == "true" and true or false
}
else
return nil
end
end
local function grpc_settings(data, direction)
if (data.stream_network == "grpc") then
local multimode = nil
local idletimeout = nil
local healthchecktimeout = nil
local initialwindowssize = nil
local useragent = nil
if direction == "outbound" then
multimode = data.grpc_multimode == "true" and true or false
idletimeout = data.grpc_idletimeout ~= nil and tonumber(data.grpc_idletimeout) or nil
healthchecktimeout = data.grpc_healthchecktimeout ~= nil and tonumber(data.grpc_healthchecktimeout) or nil
permitwithoutstream = data.grpc_permitwithoutstream == "true" and true or false
initialwindowssize = data.grpc_initialwindownsize ~= nil and tonumber(data.grpc_initialwindownsize) or nil
useragent = data.grpc_useragent
end
return {
serviceName = data.grpc_servicename,
multiMode = multimode,
idle_timeout = idletimeout,
health_check_timeout = healthchecktimeout,
permit_without_stream = permitwithoutstream,
initial_windows_size = initialwindowssize,
user_agent = useragent
}
else
return nil
end
end
local function tls_settings(data, direction)
local result = {}
if direction == "outbound" then
local pinnedpeercertificatechainsha256 = {}
for _, x in ipairs(data.pinnedpeercertificatechainsha256 ~= nil and data.pinnedpeercertificatechainsha256 or {}) do
table.insert(pinnedpeercertificatechainsha256, x)
end
result.serverName = data.tls_servername
result.allowInsecure = data.tls_allowinsecure == "true" and true or false
result.disableSystemRoot = data.tls_disablesystemroot == "true" and true or false
result.enableSessionResumption = data.tls_enablesessionresumption == "true" and true or false
result.fingerprint = data.tls_fingerprint or ""
result.pinnedPeerCertificateChainSha256 = next(pinnedpeercertificatechainsha256) ~= nil and pinnedpeercertificatechainsha256 or nil
end
if direction == "inbound" then
local ciphersuites = {}
for i, v in ipairs(data.tls_ciphersuites ~= nil and data.tls_ciphersuites or {}) do
if i > 1 then v = ":" .. v end
ciphersuites[#ciphersuites+1] = tostring(v)
end
result.rejectUnknownSni = data.tls_rejectnuknownsni == "true" and true or false
result.minVersion = data.tls_minversion
result.maxVersion = data.tls_maxversion
result.cipherSuites = next(ciphersuites) and table.concat(ciphersuites) or nil
result.certificates = {
{
ocspStapling = data.cert_ocspStapling ~= nil and tonumber(data.cert_ocspStapling) or nil,
oneTimeLoading = data.cert_onetimeloading == "true" and true or false,
usage = data.cert_usage,
certificateFile = data.cert_certificatefile,
keyFile = data.cert_certificatekeyfile
}
}
end
if data.tls_alpn ~= nil then
local alpn = {}
for _, x in ipairs(data.tls_alpn) do
table.insert(alpn, x)
end
result.alpn = alpn
end
return result
end
local function reality_settings(data, direction)
local result = {}
result.show = data.reality_show == "true" and true or false
if direction == "outbound" then
result.serverName = data.reality_servername
result.fingerprint = data.reality_fingerprint
result.shortId = data.reality_shortid
result.publicKey = data.reality_publickey
result.spiderX = data.reality_spiderx or ""
end
if direction == "inbound" then
local servernames = {}
local shortids = {}
for _, x in ipairs(data.reality_servername ~= nil and data.reality_servername or {}) do
table.insert(servernames, x)
end
for _, x in ipairs(data.reality_shortid ~= nil and data.reality_shortid or {}) do
table.insert(shortids, x)
end
result.dest = data.reality_dest
result.xver = data.reality_xver ~= nil and tonumber(data.reality_xver) or nil
result.serverNames = next(servernames) ~= nil and servernames or nil
result.privateKey = data.reality_privatekey
result.minClientVer = data.reality_minclientver
result.maxClientVer = data.reality_maxclientver
result.maxTimeDiff = data.reality_maxtimediff ~= nil and tonumber(data.reality_maxtimediff) or nil
result.shortIds = next(shortids) ~= nil and shortids or nil
end
return result
end
local function sockopt_settings(data, direction)
local tproxy = nil
local acceptproxyprotocal = nil
local tcpcongestion = nil
local dialerproxy = nil
local mark = nil
local tcpkeepaliveinterval = nil
local interface = nil
if direction == "inbound" then
tproxy = data.sockopt_tproxy
acceptproxyprotocal = data.sockopt_acceptproxyprotocol == "true" and true or false
tcpcongestion = data.sockopt_tcpcongestion
elseif direction == "outbound" then
dialerproxy = data.sockopt_dialerproxy
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
tcpkeepaliveinterval = data.sockopt_tcpkeepaliveinterval ~= nil and tonumber(data.sockopt_tcpkeepaliveinterval) or nil
interface = data.sockopt_interface
end
return {
mark = mark,
tcpFastOpen = data.sockopt_tcpfastopen == "true" and true or false,
tproxy = tproxy,
domainStrategy = data.sockopt_domainstrategy,
dialerProxy = dialerproxy,
acceptProxyProtocol = acceptproxyprotocal,
tcpKeepAliveInterval =tcpkeepaliveinterval,
tcpcongestion = tcpcongestion,
interface = interface
}
end
local function mux_settings(data, direction)
if data.mux_enabled == "true" and direction == 'outbound' then
return {
enabled = data.mux_enabled == "true" and true or false,
concurrency = data.mux_concurrency ~= nil and tonumber(data.mux_concurrency) or nil,
xudpConcurrency = data.mux_xudpconcurrency ~= nil and tonumber(data.mux_xudpconcurrency) or nil,
xudpProxyUDP443 = data.mux_xudpproxyudp443
}
else
return nil
end
end
local function stream_settings(data, direction)
return {
network = data.stream_network,
security = data.stream_security,
tlsSettings = data.stream_security == "tls" and tls_settings(data, direction) or nil,
xtlsSettings = data.stream_security == "xtls" and tls_settings(data, direction) or nil,
realitySettings = data.stream_security == "reality" and reality_settings(data, direction) or nil,
tcpSettings = tcp_settings(data, direction),
kcpSettings = kcp_settings(data, direction),
wsSettings = ws_settings(data, direction),
httpSettings = http_settings(data, direction),
quicSettings = quic_settings(data, direction),
dsSettings = ds_settings(data, direction),
grpcSettings = grpc_settings(data, direction),
sockopt = sockopt_settings(data, direction)
}
end
local function sniffing(data)
local destoverride = {}
local domainsexcluded = {}
for i, v in ipairs(data.sniffing_destoverride ~= nil and data.sniffing_destoverride or {}) do
table.insert(destoverride, v)
end
for i, v in ipairs(data.sniffing_domainsexcluded ~= nil and data.sniffing_domainsexcluded or {}) do
table.insert(domainsexcluded, v)
end
return data.sniffing_enabled == "true" and {
enabled = true,
destOverride = next(destoverride) and destoverride or nil,
metadataOnly = data.sniffing_metadataonly == "true" and true or false,
domainsExcluded = next(domainsexcluded) and domainsexcluded or nil,
routeOnly = data.sniffing_routeonly == "true" and true or false,
} or nil
end
local function fallback(data)
local fallbacks = {}
for i, v in ipairs(data.fallback ~= nil and data.fallback or {}) do
ucursor:foreach("xjay", "fallback", function(fb)
if fb.tag == v and fb.dest ~= nil then
local fallback = {
name = fb.name,
alpn = fb.alpn,
path = fb.path,
xver = fb.xver ~= nil and tonumber(fb.xver) or nil
}
-- if the dest is only a port number, then convert it to number
if string.match(fb.dest, "%d+") == fb.dest then
fallback.dest = tonumber(fb.dest)
else
fallback.dest = fb.dest
end
table.insert(fallbacks, fallback)
end
end)
end
return next(fallbacks) ~= nil and fallbacks or nil
end
local function dokodemo_inbound(data)
local network = {}
for i, v in ipairs(data.dokodemo_network ~= nil and data.dokodemo_network or {}) do
if i > 1 then v = "," .. v end
network[#network+1] = tostring(v)
end
return {
address = data.dokodemo_address or nil,
port = data.dokodemo_port ~= nil and tonumber(data.dokodemo_port) or nil,
network = next(network) and table.concat(network) or nil,
timeout = data.dokodemo_timeout ~= nil and tonumber(v.dokodemo_timeout) or nil,
followRedirect = data.dokodemo_followredirect == "true" and true or false,
userLevel = data.dokodemo_level ~= nil and tonumber(data.dokodemo_level) or nil,
fallbacks = fallback(data)
}
end
local function http_inbound(data)
local accounts = {}
for i, v in ipairs(data.http_user ~= nil and data.http_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
user = u.name,
pass = u.password
}
table.insert(accounts, user)
end
end)
end
return {
timeout = data.http_timeout ~= nil and tonumber(data.http_timeout) or nil,
allowTransparent = data.http_allocatransparent == "true" and true or false,
accounts = data.http_auth == 'password' and accounts or nil,
userLevel = data.http_level ~= nil and tonumber(data.http_level) or nil,
fallbacks = fallback(data)
}
end
local function shadowsocks_inbound(data)
local network = {}
local clients = {}
for i, v in ipairs(data.ss_user ~= nil and data.ss_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
password = u.password,
method = data.ss_method,
email = u.email,
level = u.level ~= nil and tonumber(u.level) or nil
}
table.insert(clients, user)
end
end)
end
for i, v in ipairs(data.ss_network == nil and {} or data.ss_network) do
if i > 1 then v = "," .. v end
network[#network+1] = tostring(v)
end
return {
clients = next(clients) and clients or nil,
network = next(network) and table.concat(network) or nil,
fallbacks = fallback(data)
}
end
local function socks_inbound(data)
local accounts = {}
for i, v in ipairs(data.socks_user ~= nil and data.socks_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
user = u.name,
pass = u.password
}
table.insert(accounts, user)
end
end)
end
return {
auth = data.socks_auth,
accounts = data.socks_auth == 'password' and accounts or nil,
udp = data.socks_udp == "true" and true or false,
ip = data.socks_ip,
userLevel = data.socks_level ~= nil and tonumber(data.socks_level) or nil,
fallbacks = fallback(data)
}
end
local function trojan_inbound(data)
local clients = {}
for i, v in ipairs(data.trojan_user ~= nil and data.trojan_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
password = u.password,
email = u.email,
level = u.level ~= nil and tonumber(u.level) or nil
}
table.insert(clients, user)
end
end)
end
return {
clients = next(clients) and clients or nil,
fallbacks = fallback(data)
}
end
local function vless_inbound(data)
local clients = {}
for i, v in ipairs(data.vless_user ~= nil and data.vless_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
id = u.password,
flow = data.vless_flow ~= "none" and data.vless_flow or "",
email = u.email,
level = u.level ~= nil and tonumber(u.level) or nil
}
table.insert(clients, user)
end
end)
end
return {
clients = next(clients) and clients or nil,
decryption = "none",
fallbacks = fallback(data)
}
end
local function vmess_inbound(data)
local clients = {}
for i, v in ipairs(data.vmess_user ~= nil and data.vmess_user or {}) do
ucursor:foreach("xjay", "user", function(u)
if u.name == v then
local user = {
id = u.password,
alterId = data.vmess_alterid ~= nil and tonumber(data.vmess_alterid) or nil,
email = u.email,
level = u.level ~= nil and tonumber(u.level) or nil
}
table.insert(clients, user)
end
end)
end
return {
clients = next(clients) and clients or nil,
default = {
alterId = 0,
level = 0
},
detour = data.vmess_detour ~= nil and {
to = data.vmess_detour
} or nil,
fallbacks = fallback(data)
}
end
local function inbound_service(data)
local inbound_settings = {}
if data.protocol == "dokodemo-door" then
inbound_settings = dokodemo_inbound(data)
elseif data.protocol == "http" then
inbound_settings = http_inbound(data)
elseif data.protocol == "shadowsocks" then
inbound_settings = shadowsocks_inbound(data)
elseif data.protocol == "socks" then
inbound_settings = socks_inbound(data)
elseif data.protocol == "trojan" then
inbound_settings = trojan_inbound(data)
elseif data.protocol == "vless" then
inbound_settings = vless_inbound(data)
elseif data.protocol == "vmess" then
inbound_settings = vmess_inbound(data)
end
return {
tag = data.tag,
listen = data.listen,
port = tonumber(data.port),
protocol = data.protocol,
settings = inbound_settings,
streamSettings = stream_settings(data, "inbound"),
sniffing = sniffing(data)
}
end
local function blackhole_outbound()
return {
tag = "blackhole",
protocol = "blackhole"
}
end
local function dns_outbound()
return {
protocol = "dns",
tag = "dns",
streamSettings = {
sockopt = {
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
}
}
}
end
local function direct_outbound()
return {
protocol = "freedom",
tag = "direct",
streamSettings = {
sockopt = {
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
}
}
}
end
local function http_outbound(data)
return {
servers = {
{
address = data.address,
port = tonumber(data.port),
users = {
{
user = data.http_user,
pass = data.http_pass
}
}
}
}
}
end
local function shadowsocks_outbound(data)
return {
servers = {
{
address = data.address,
port = tonumber(data.port),
method = data.ss_method, -- encryption algorithim
password = data.ss_password,
uot = data.ss_uot == "true" and true or false, -- udp over tcp
email = data.ss_email or nil, -- optional, for identifying a user
level = data.ss_level ~= nil and tonumber(data.ss_level) or nil
}
}
}
end
local function socks_outbound(data)
return {
servers = {
{
address = data.address,
port = tonumber(data.port),
users = {
{
user = data.socks_user,
pass = data.socks_pass,
level = data.socks_level ~= nil and tonumber(data.socks_level) or nil
}
}
}
}
}
end
local function trojan_outbound(data)
return {
servers = {
{
address = data.address,
port = tonumber(data.port),
password = data.trojan_password,
email = data.trojan_email or nil, -- optional, for identifying a user
level = data.trojan_level ~= nil and tonumber(data.trojan_level) or nil
}
}
}
end
local function vless_outbound(data)
return {
vnext = {
{
address = data.address,
port = tonumber(data.port),
users = {
{
id = data.vless_id,
flow = data.vless_flow ~= "none" and data.vless_flow or "",
encryption = data.vless_encryption, -- encryption algorithim, for vless, currently only none
level = data.vless_level ~= nil and tonumber(data.vless_level) or nil
}
}
}
}
}
end
local function vmess_outbound(data)
return {
vnext = {
{
address = data.address,
port = tonumber(data.port),
users = {
{
id = data.vmess_id,
alterId = data.vmess_alterid ~= nil and tonumber(data.vmess_alterid) or nil,
security = data.vmess_security, -- encryption algorithim
level = data.vmess_level ~= nil and tonumber(data.vmess_level) or nil
}
}
}
}
}
end
local function outbound_server(data)
local outbound_settings = {}
if data.protocol == "http" then
outbound_settings = http_outbound(data)
elseif data.protocol == "shadowsocks" then
outbound_settings = shadowsocks_outbound(data)
elseif data.protocol == "socks" then
outbound_settings = socks_outbound(data)
elseif data.protocol == "trojan" then
outbound_settings = trojan_outbound(data)
elseif data.protocol == "vless" then
outbound_settings = vless_outbound(data)
elseif data.protocol == "vmess" then
outbound_settings = vmess_outbound(data)
end
return {
tag = data.tag,
sendThrough = data.sendthrough,
protocol = data.protocol,
settings = outbound_settings,
streamSettings = stream_settings(data, "outbound"),
mux = mux_settings(data, 'outbound')
}
end
local function logging()
return {
access = misc.log_access == "true" and "" or "none",
error = misc.log_error == "true" and "" or "none",
loglevel = misc.log_level or "warning",
dnsLog = misc.log_dnslog == "true" and true or false
}
end
local function inbounds()
local inbounds = {}
ucursor:foreach("xjay", "inbound_service", function(data)
table.insert(inbounds, inbound_service(data))
end)
return inbounds
end
local function outbounds()
local outbounds = {}
local function is_outbound_created(tag)
for i, v in ipairs(outbounds) do
if tag == v.tag then
return true
end
end
return false
end
-- generating outbounds based on ruouting outbound tags
ucursor:foreach("xjay", "outbound_server", function(data)
-- insert default oubound in the first
if data.tag == outbound.default_outbound then table.insert(outbounds, 1, outbound_server(data)) end
-- append other outbounds from routing rules in the last
ucursor:foreach("xjay", "routing_rule", function(dd)
if data.tag == dd.rule_outboundtag and not is_outbound_created(dd.rule_outboundtag) then
table.insert(outbounds, outbound_server(data))
end
end)
end)
-- generating some pre-defined outbounds
if outbound.default_outbound == "direct" then
table.insert(outbounds, 1, direct_outbound())
else
table.insert(outbounds, direct_outbound())
end
if outbound.default_outbound == "blackhole" then
table.insert(outbounds, 1, blackhole_outbound())
else
table.insert(outbounds, blackhole_outbound())
end
table.insert(outbounds, dns_outbound())
return outbounds
end
local function dnss()
local hosts = {}
local servers = {}
for i, v in ipairs(dns.host == nil and {} or dns.host) do
-- break string "/dns.google/8.8.8.8" into table tab = { "dns.google", "8.8.8.8" }
local tab = {}
for w in v:gmatch("([^/]+)") do table.insert(tab, w) end
-- then add them as key value pair into hosts table
-- will be hosts = { dns.google = "8.8.8.8" }
-- not elegant but working
hosts[ tab[1] ] = tab[2]
end
ucursor:foreach("xjay", "dns_server", function(data)
local domains = {}
local expectedips = {}
local function is_domain_inserted(domain)
for i, v in ipairs(domains) do
if domain == v then
return true
end
end
return false
end
for i, v in ipairs(data.server_domain == nil and {} or data.server_domain) do
table.insert(domains, v)
end
-- get domains from routing rules domain list
ucursor:foreach("xjay", "routings", function(dd)
if data.alias == dd.dns_server then
for i, v in ipairs(dd.rule_domain == nil and {} or dd.rule_domain) do
if not is_domain_inserted(v) then table.insert(domains, v) end
end
end
end)
for i, v in ipairs(data.server_expectedip == nil and {} or data.server_expectedip) do
table.insert(expectedips, v)
end
local server = {
queryStrategy = data.querystrategy,
address = data.server_address ~= nil and data.server_address or nil,
port = data.server_port ~= nil and tonumber(data.server_port) or nil,
skipFallback = data.server_skipfallback == "true" and true or false,
domains = next(domains) and domains or nil,
expectIPs = next(expectedips) and expectedips or nil,
clientIP = data.server_clientip
}
table.insert(servers, server)
end)
for i, v in ipairs(dns.alt_dns == nil and {} or dns.alt_dns) do
table.insert(servers, v)
end
return {
tag = dns.tag,
clientIP = dns.clientip,
queryStrategy = dns.querystrategy,
disableCache = dns.disablecache == "true" and true or false,
disableFallback = dns.disablefallback == "true" and true or false,
disableFallbackIfMatch = dns.disablefallbackifmatch == "true" and true or false,
hosts = next(hosts) and hosts or nil,
servers = next(servers) and servers or nil
}
end
local function routings()
local rules = {}
ucursor:foreach("xjay", "routing_rule", function(data)
local domain = {}
local ip = {}
local port = {}
local source = {}
local sourceport = {}
local network = {}
local protocol = {}
local inboundtag = {}
for i, v in ipairs(data.rule_domain == nil and {} or data.rule_domain) do
table.insert(domain, v)
end
for i, v in ipairs(data.rule_ip == nil and {} or data.rule_ip) do
table.insert(ip, v)
end
if data.rule_port == nil then
port = nil
elseif #data.rule_port == 1 then
port = tonumber(data.rule_port[1])
elseif #data.rule_port > 1 then
for i, v in ipairs(data.rule_port) do
port[#port+1] = tostring(i == 1 and v or "," .. v)
end
port = table.concat(port)
end
for i, v in ipairs(data.rule_source == nil and {} or data.rule_source) do
table.insert(source, v)
end
if data.rule_sourceport == nil then
sourceport = nil
elseif #data.rule_sourceport == 1 then
sourceport = tonumber(data.rule_sourceport[1])
elseif #data.rule_sourceport > 1 then
for i, v in ipairs(data.rule_sourceport) do
sourceport[#sourceport+1] = tostring(i == 1 and v or "," .. v)
end
sourceport = table.concat(sourceport)
end
for i, v in ipairs(data.rule_network == nil and {} or data.rule_network) do
if i > 1 then v = "," .. v end
network[#network+1] = tostring(v)
end
for i, v in ipairs(data.rule_protocol == nil and {} or data.rule_protocol) do
table.insert(protocol, v)
end
for i, v in ipairs(data.rule_inboundtag == nil and {} or data.rule_inboundtag) do
table.insert(inboundtag, v)
end
local rule = {
domainMatcher = data.domain_matcher == nil and nil or data.domain_matcher,
type = "field",
domain = next(domain) and domain or nil,
ip = next(ip) and ip or nil,
port = port,
source = next(source) and source or nil,
sourcePort = sourceport,
network = next(network) and table.concat(network) or nil,
protocol = next(protocol) and protocol or nil,
attrs = data.rule_attrs,
inboundTag = next(inboundtag) and inboundtag or nil,
outboundTag = data.rule_outboundtag
}
table.insert(rules, rule)
end)
return {
domainStrategy = routing.domainstrategy,
domainMatcher = routing.domainmatcher,
rules = rules
}
end
local xray = {
log = logging(),
inbounds = inbounds(),
outbounds = outbounds(),
dns = dnss(),
routing = routings()
}
print(json.stringify(xray, true))