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

525 lines
16 KiB
Lua

module("luci.controller.xclient", package.seeall)
require "nixio.fs"
require "luci.http"
require "luci.sys"
require "luci.model.uci"
local json = require "luci.jsonc"
function index()
if not nixio.fs.access("/etc/config/xclient") then
return
end
local page = entry({"admin", "services", "xclient"},alias("admin", "services", "xclient", "overview"), "XClient", 1)
page.dependent = true
page.acl_depends = {"luci-app-xclient"}
entry({"admin", "services", "xclient", "overview"},cbi("xclient/status"), "Overview", 5).leaf = true
entry({"admin", "services", "xclient", "servers" },cbi("xclient/servers"), "Servers", 10).leaf = true
entry({"admin", "services", "xclient", "server"},cbi("xclient/server"), nil).leaf = true
entry({"admin", "services", "xclient", "dns"},cbi("xclient/dns"),"DNS", 20).leaf = true
entry({"admin", "services", "xclient", "add-dns"},cbi("xclient/add-dns"), nil).leaf = true
entry({"admin", "services", "xclient", "routing"},cbi("xclient/rules"),"Routing", 30).leaf = true
entry({"admin", "services", "xclient", "rule"},cbi("xclient/rule"), nil).leaf = true
entry({"admin", "services", "xclient", "update"},cbi("xclient/update"),"Update", 40).leaf = true
entry({"admin", "services", "xclient", "log"},form("xclient/log"),"Log", 50).leaf = true
entry({"admin", "services", "xclient", "ping"}, call("act_ping")).leaf=true
entry({"admin", "services", "xclient", "run"},call("action_run")).leaf=true
entry({"admin", "services", "xclient", "subscribe"}, call("subscribe"))
entry({"admin", "services", "xclient", "update_geosite"}, call("update_geosite")).leaf = true
entry({"admin", "services", "xclient", "update_geoip"}, call("update_geoip")).leaf = true
entry({"admin", "services", "xclient", "logstatus"},call("logstatus_check")).leaf=true
entry({"admin", "services", "xclient", "readlog"},call("action_read")).leaf=true
entry({'admin', 'services', "xclient", 'web'}, call('web_check')).leaf=true
entry({"admin", "services", "xclient", "delete"}, call("act_delete"))
entry({"admin", "services", "xclient", "info"}, call("act_info"))
entry({"admin", "services", "xclient", "logout"}, call("act_logout"))
entry({"admin", "services", "xclient", "login"}, call("act_login"))
entry({"admin", "services", "xclient", "login_check"}, call("act_login_check"))
entry({"admin", "services", "xclient", "login_info"}, call("act_login_info"))
--entry({"admin", "services", "xclient", "xray_check"}, call("xray_check")).leaf = true
--entry({"admin", "services", "xclient", "xray_update"}, call("xrayx_update")).leaf = true
entry({"admin", "services", "xclient", "geolocation"}, call("geoloc")).leaf = true
entry({"admin", "services", "xclient", "geoip"}, call("geoip_check")).leaf=true
entry({"admin", "services", "xclient", "geoipupdate"}, call("geoip_update")).leaf=true
entry({"admin", "services", "xclient", "check_geoip"}, call("check_geoip_log")).leaf=true
entry({"admin", "services", "xclient", "geosite"}, call("geosite_check")).leaf=true
entry({"admin", "services", "xclient", "geositeupdate"}, call("geosite_update")).leaf=true
entry({"admin", "services", "xclient", "check_geosite"}, call("check_geosite_log")).leaf=true
entry({"admin", "services", "xclient", "version"}, call("check_version")).leaf=true
entry({"admin", "services", "xclient", "check_latest"}, call("check_latest")).leaf=true
entry({"admin", "services", "xclient", "traffic"}, call("statistics")).leaf=true
end
local uci = luci.model.uci.cursor()
local Process_list = luci.sys.exec("busybox ps -w")
local function dns_status()
if Process_list:find("xclient/bin/pdnsd") or (Process_list:find("xclient.dns") and Process_list:find("dns2socks.127.0.0.1.*127.0.0.1.5335")) then
return 1
else
return 0
end
end
local function tcp_status()
if Process_list:find("local.udp.xclient.retcp") or Process_list:find("tcp.udp.xclient.retcp") or Process_list:find("local.xclient.retcp") or Process_list:find("tcp.only.xclient.retcp") then
return 1
else
return 0
end
end
local function udp_status()
if Process_list:find("local.udp.xclient.retcp") or Process_list:find("tcp.udp.xclient.retcp") or Process_list:find("local.xclient.retcp") or Process_list:find("udp.only.xclient.reudp") then
return 1
else
return 0
end
end
local function socks_status()
if Process_list:find("local.udp.xclient.retcp") or Process_list:find("tcp.udp.xclient.local") or Process_list:find("local.xclient.retcp") then
return 1
else
return 0
end
end
local function running_status()
return luci.sys.call("busybox ps -w | grep xclient-retcp | grep -v grep >/dev/null") == 0
end
local function http_write_json(content)
luci.http.prepare_content("application/json")
luci.http.write_json(content or {code = 1})
end
function subscribe()
luci.http.prepare_content("application/json")
local token, details, data, suburl
token = uci:get("xclient", "config", "token")
local site = uci:get("xclient", "config", "site")
details = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -d '{\"token\":\"%s\"}' -X POST %s/api/v1/details", token, site))
if details then
data = json.parse(details)
if data and data.ret == 1 then
suburl = data.url.xclient
if suburl then
uci:set("xclient", "config", "subscribe_url", suburl)
uci:commit("xclient")
luci.sys.call("/usr/bin/lua /usr/share/xclient/subscribe.lua >>/var/log/xclient.log")
luci.http.write_json({ret = 1})
end
else
luci.http.write_json({ret = 0})
end
else
luci.http.write_json({ret = 0})
end
end
function act_login_check()
luci.http.prepare_content("application/json")
local token = uci:get("xclient", "config", "token")
if token then
luci.http.write_json({login = 1})
else
luci.http.write_json({login = 0})
end
end
function act_login()
luci.http.prepare_content("application/json")
local info, token
local email = uci:get("xclient", "config", "email")
local passwd = uci:get("xclient", "config", "passwd")
local site = uci:get("xclient", "config", "site")
if email and passwd then
info = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -d '{\"email\":\"%s\", \"passwd\":\"%s\"}' -X POST %s/api/v1/login", email, passwd, site))
if info then
info = json.parse(info)
else
luci.http.write_json({login = 0})
end
if info and info.ret == 1 then
token = info.token
uci:set("xclient", "config", "token", token)
uci:set("xclient", "config", "auto_update_servers", "6")
uci:commit("xclient")
luci.http.write_json({login = 1})
else
luci.http.write_json({login = 0})
end
else
luci.http.write_json({login = 0})
end
end
function login_info()
uci:set("xclient", "config", "email", luci.http.formvalue("email"))
uci:set("xclient", "config", "passwd", luci.http.formvalue("passwd"))
uci:set("xclient", "config", "site", luci.http.formvalue("weburl"))
uci:commit("xclient")
return 1
end
function act_logout()
luci.http.prepare_content("application/json")
local token, details, data
token = uci:get("xclient", "config", "token")
local site = uci:get("xclient", "config", "site")
details = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -d '{\"token\":\"%s\"}' -X POST %s/api/v1/logout", token, site))
if details then
data = json.parse(details)
if data.ret == 1 then
uci:delete("xclient", "config", "token")
uci:delete("xclient", "config", "subscribe_url")
uci:delete("xclient", "config", "auto_update_servers")
uci:delete_all("xclient", "servers", function(s)
if s.hashkey or s.isSubscribe then
return true
else
return false
end
end)
uci:commit("xclient")
luci.sys.call("/etc/init.d/xclient boot &")
luci.http.write_json({logout = 1;})
else
luci.http.write_json({logout = 0;})
end
else
luci.http.write_json({logout = 0})
end
end
function act_login_info()
luci.http.prepare_content("application/json")
luci.http.write_json({
login_info = login_info();
})
end
function action_run()
luci.http.prepare_content("application/json")
luci.http.write_json({
dns_status = dns_status(),
tcp_status = tcp_status(),
udp_status = udp_status(),
socks_status = socks_status(),
})
end
function act_ping()
local e = {}
local domain = luci.http.formvalue("domain")
local port = luci.http.formvalue("port")
local transport = luci.http.formvalue("transport")
local wsPath = luci.http.formvalue("wsPath")
local tls = luci.http.formvalue("tls")
e.index = luci.http.formvalue("index")
local iret = luci.sys.call("ipset add xclient_spec_wan_ac " .. domain .. " 2>/dev/null")
if transport == "ws" then
local prefix = tls=='1' and "https://" or "http://"
local address = prefix..domain..':'..port..wsPath
local result = luci.sys.exec("curl --http1.1 -m 2 -ksN -o /dev/null -w 'time_connect=%{time_connect}\nhttp_code=%{http_code}' -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==' -H 'Sec-WebSocket-Version: 13' "..address)
e.socket = string.match(result,"http_code=(%d+)")=="101"
e.ping = tonumber(string.match(result, "time_connect=(%d+.%d%d%d)"))*1000
else
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
e.socket = socket:connect(domain, port)
socket:close()
-- e.ping = luci.sys.exec("ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*.[0-9]' | awk -F '=' '{print$2}'" % domain)
-- if (e.ping == "") then
e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, domain))
end
if (iret == 0) then
luci.sys.call(" ipset del xclient_spec_wan_ac " .. domain)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function logstatus_check()
luci.http.prepare_content("text/plain; charset=utf-8")
local fdp=tonumber(nixio.fs.readfile("/usr/share/xclient/logstatus_check")) or 0
local f=io.open("/usr/share/xclient/xclient.txt", "r+")
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
nixio.fs.writefile("/usr/share/xclient/logstatus_check",tostring(fdp))
f:close()
if nixio.fs.access("/usr/share/xclient/logstatus_check") then
luci.http.write(a)
else
luci.http.write(a.."\0")
end
end
local function readlog()
return luci.sys.exec("sed -n '$p' /usr/share/xclient/readlog.txt 2>/dev/null")
end
function action_read()
luci.http.prepare_content("application/json")
luci.http.write_json({
readlog = readlog();
})
end
function check(host, port)
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
local ret = socket:connect(host, port)
socket:close()
return ret
end
function web_check()
local e = {}
local port = 80
e.baidu = check('www.baidu.com', port)
e.taobao = check('www.taobao.com', port)
e.google = check('www.google.com', port)
e.youtube = check('www.youtube.com', port)
luci.http.prepare_content('application/json')
luci.http.write_json(e)
end
function update_geoip()
luci.sys.call("lua /usr/share/xclient/geoip.lua log > /dev/null 2>&1 &")
http_write_json()
end
function update_geosite()
luci.sys.call("lua /usr/share/xclient/geosite.lua log > /dev/null 2>&1 &")
http_write_json()
end
function act_delete()
luci.sys.call("/etc/init.d/xclient reload &")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "xclient", "servers"))
end
function geoloc()
local geo, data
geo = luci.sys.exec(string.format("curl -sL -H 'User-Agent: luci-app-xclient' 'Content-Type: application/json' -X GET https://query-geolocation.herokuapp.com/?lang=en"))
if geo then
data = json.parse(geo)
end
luci.http.prepare_content("application/json")
luci.http.write_json({
code = data.code,
location = data.location
})
end
local function geoipcheck()
if nixio.fs.access("/var/run/geo_ip_update_error") then
return "0"
elseif nixio.fs.access("/var/run/geo_ip_update") then
return "1"
elseif nixio.fs.access("/var/run/geo_ip_down_complete") then
return "2"
end
end
local function geositecheck()
if nixio.fs.access("/var/run/geo_site_update_error") then
return "0"
elseif nixio.fs.access("/var/run/geo_site_update") then
return "1"
elseif nixio.fs.access("/var/run/geo_site_down_complete") then
return "2"
end
end
function geoip_check()
luci.http.prepare_content("application/json")
luci.http.write_json({
geoipcheck = geoipcheck();
})
end
function geosite_check()
luci.http.prepare_content("application/json")
luci.http.write_json({
geositecheck = geositecheck();
})
end
function geoip_update()
nixio.fs.writefile("/var/run/geoiplog","0")
luci.sys.exec("(rm /var/run/geo_ip_update_error ; touch /var/run/geo_ip_update ; sh /usr/share/xclient/geoip.sh >/tmp/geo_ip_update.txt 2>&1 || touch /var/run/geo_ip_update_error ;rm /var/run/geo_ip_update) &")
end
function geosite_update()
nixio.fs.writefile("/var/run/geositelog","0")
luci.sys.exec("(rm /var/run/geo_site_update_error ; touch /var/run/geo_site_update ; sh /usr/share/xclient/geosite.sh >/tmp/geo_site_update.txt 2>&1 || touch /var/run/geo_site_update_error ;rm /var/run/geo_site_update) &")
end
function check_geoip_log()
luci.http.prepare_content("text/plain; charset=utf-8")
local fdp=tonumber(nixio.fs.readfile("/var/run/geoiplog")) or 0
local f=io.open("/tmp/geo_ip_update.txt", "r+")
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
nixio.fs.writefile("/var/run/geoiplog",tostring(fdp))
f:close()
if nixio.fs.access("/var/run/geo_ip_update") then
luci.http.write(a)
else
luci.http.write(a.."\0")
end
end
function check_geosite_log()
luci.http.prepare_content("text/plain; charset=utf-8")
local fdp=tonumber(nixio.fs.readfile("/var/run/geositelog")) or 0
local f=io.open("/tmp/geo_site_update.txt", "r+")
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
nixio.fs.writefile("/var/run/geositelog",tostring(fdp))
f:close()
if nixio.fs.access("/var/run/geo_site_update") then
luci.http.write(a)
else
luci.http.write(a.."\0")
end
end
local function core_version()
if nixio.fs.access("/usr/bin/xray") then
if nixio.fs.access("/usr/share/xclient/core_version") then
return luci.sys.exec("sed -n 1p /usr/share/xclient/core_version")
else
return luci.sys.exec("/usr/bin/xray -version | awk '{print $2}' | sed -n 1P")
end
else
return "--"
end
end
local function latest_core()
if nixio.fs.access("/usr/share/xclient/new_core") then
return luci.sys.exec("sed -n 1p /usr/share/xclient/new_core")
else
return "--"
end
end
local function geosite_version()
if nixio.fs.access("/usr/bin/geosite.dat") then
if nixio.fs.access("/usr/share/xclient/geosite_version") then
return luci.sys.exec("sed -n 1p /usr/share/xclient/geosite_version")
else
return "--"
end
else
return "--"
end
end
local function geoip_version()
if nixio.fs.access("/usr/bin/geoip.dat") then
if nixio.fs.access("/usr/share/xclient/geoip_version") then
return luci.sys.exec("sed -n 1p /usr/share/xclient/geoip_version")
else
return "--"
end
else
return "--"
end
end
function check_latest()
return luci.sys.exec("sh /usr/share/xclient/core_version.sh")
end
function check_version()
luci.http.prepare_content("application/json")
luci.http.write_json({
core_version = core_version(),
geoip_version = geoip_version(),
geosite_version = geosite_version(),
latest_core = latest_core()
})
end
function statistics()
luci.http.prepare_content("application/json")
local up, check_uplink
check_uplink = luci.sys.exec(string.format("xray api stats --server=127.0.0.1:8888 -name \"outbound>>>proxy_outbound>>>traffic>>>uplink\""))
if check_uplink then
up = json.parse(check_uplink)
if up ~= nil then
if up.stat.value ~= nil then
local uplink = up.stat.value
else
local uplink = 0
end
else
local uplink = 0
end
else
local uplink = 0
end
local down, check_downlink
check_downlink = luci.sys.exec(string.format("xray api stats --server=127.0.0.1:8888 -name \"outbound>>>proxy_outbound>>>traffic>>>downlink\""))
if check_downlink then
down = json.parse(check_downlink)
if down ~= nil then
if down.stat.value ~= nil then
local downlink = down.stat.value
else
local downlink = 0
end
else
local downlink = 0
end
else
local downlink = 0
end
luci.http.write_json({
downlink = downlink,
uplink = uplink
})
end