luci: add v2ray server luci
This commit is contained in:
parent
facb9642b3
commit
015810919d
18
package/lean/luci-app-v2ray-server/Makefile
Normal file
18
package/lean/luci-app-v2ray-server/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2018-2019 Lienol
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI support for V2ray Server
|
||||
LUCI_DEPENDS:=+libsodium +luci-lib-jsonc +v2ray
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
module("luci.controller.v2ray_server",package.seeall)
|
||||
local http = require "luci.http"
|
||||
local v2ray = require "luci.model.cbi.v2ray_server.api.v2ray"
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/v2ray_server")then
|
||||
return
|
||||
end
|
||||
entry({"admin","vpn"}, firstchild(), "VPN", 45).dependent = false
|
||||
entry({"admin","vpn","v2ray_server"},cbi("v2ray_server/index"),_("V2ray Server"),3).dependent=true
|
||||
entry({"admin","vpn","v2ray_server","config"},cbi("v2ray_server/config")).leaf=true
|
||||
|
||||
entry({"admin","vpn","v2ray_server","users_status"},call("v2ray_users_status")).leaf=true
|
||||
entry({"admin", "vpn", "v2ray_server", "check"}, call("v2ray_check")).leaf = true
|
||||
entry({"admin", "vpn", "v2ray_server", "update"}, call("v2ray_update")).leaf = true
|
||||
end
|
||||
|
||||
local function http_write_json(content)
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(content or { code = 1 })
|
||||
end
|
||||
|
||||
function v2ray_users_status()
|
||||
local e={}
|
||||
e.index=luci.http.formvalue("index")
|
||||
e.status=luci.sys.call("ps -w| grep -v grep | grep '/var/etc/v2ray_server/" .. luci.http.formvalue("id") .. "' >/dev/null")==0
|
||||
http_write_json(e)
|
||||
end
|
||||
|
||||
function v2ray_check()
|
||||
local json = v2ray.to_check("")
|
||||
http_write_json(json)
|
||||
end
|
||||
|
||||
function v2ray_update()
|
||||
local json = nil
|
||||
local task = http.formvalue("task")
|
||||
if task == "extract" then
|
||||
json = v2ray.to_extract(http.formvalue("file"), http.formvalue("subfix"))
|
||||
elseif task == "move" then
|
||||
json = v2ray.to_move(http.formvalue("file"))
|
||||
else
|
||||
json = v2ray.to_download(http.formvalue("url"))
|
||||
end
|
||||
|
||||
http_write_json(json)
|
||||
end
|
||||
@ -0,0 +1,65 @@
|
||||
local ucursor = require "luci.model.uci".cursor()
|
||||
local json = require "luci.jsonc"
|
||||
local server_section = arg[1]
|
||||
local server = ucursor:get_all("v2ray_server", server_section)
|
||||
|
||||
local v2ray = {
|
||||
log = {
|
||||
--error = "/var/log/v2ray.log",
|
||||
loglevel = "warning"
|
||||
},
|
||||
-- 传入连接
|
||||
inbound = {
|
||||
port = tonumber(server.port),
|
||||
protocol = server.protocol,
|
||||
settings = {
|
||||
clients = {
|
||||
{
|
||||
id = server.VMess_id,
|
||||
alterId = tonumber(server.VMess_alterId),
|
||||
level = tonumber(server.VMess_level)
|
||||
}
|
||||
}
|
||||
},
|
||||
-- 底层传输配置
|
||||
streamSettings = {
|
||||
network = server.transport,
|
||||
security = (server.tls == '1') and "tls" or "none",
|
||||
kcpSettings = (server.transport == "mkcp") and {
|
||||
mtu = tonumber(server.mkcp_mtu),
|
||||
tti = tonumber(server.mkcp_tti),
|
||||
uplinkCapacity = tonumber(server.mkcp_uplinkCapacity),
|
||||
downlinkCapacity = tonumber(server.mkcp_downlinkCapacity),
|
||||
congestion = (server.mkcp_congestion == "1") and true or false,
|
||||
readBufferSize = tonumber(server.mkcp_readBufferSize),
|
||||
writeBufferSize = tonumber(server.mkcp_writeBufferSize),
|
||||
header = {
|
||||
type = server.mkcp_guise
|
||||
}
|
||||
} or nil,
|
||||
httpSettings = (server.transport == "h2") and {
|
||||
path = server.h2_path,
|
||||
host = server.h2_host,
|
||||
} or nil,
|
||||
quicSettings = (server.transport == "quic") and {
|
||||
security = server.quic_security,
|
||||
key = server.quic_key,
|
||||
header = {
|
||||
type = server.quic_guise
|
||||
}
|
||||
} or nil
|
||||
}
|
||||
},
|
||||
-- 传出连接
|
||||
outbound = {
|
||||
protocol = "freedom"
|
||||
},
|
||||
-- 额外传出连接
|
||||
outboundDetour = {
|
||||
{
|
||||
protocol = "blackhole",
|
||||
tag = "blocked"
|
||||
}
|
||||
}
|
||||
}
|
||||
print(json.stringify(v2ray,1))
|
||||
@ -0,0 +1,321 @@
|
||||
-- Copyright 2018 Lienol <lienol@qq.com>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local fs = require "nixio.fs"
|
||||
local sys = require "luci.sys"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local util = require "luci.util"
|
||||
local i18n = require "luci.i18n"
|
||||
|
||||
module("luci.model.cbi.v2ray_server.api.v2ray", package.seeall)
|
||||
|
||||
local v2ray_api = "https://api.github.com/repos/v2ray/v2ray-core/releases/latest"
|
||||
|
||||
local wget = "/usr/bin/wget"
|
||||
local wget_args = { "--no-check-certificate", "--quiet", "--timeout=10", "--tries=2" }
|
||||
local curl = "/usr/bin/curl"
|
||||
local command_timeout = 40
|
||||
|
||||
local function _unpack(t, i)
|
||||
i = i or 1
|
||||
if t[i] ~= nil then
|
||||
return t[i], _unpack(t, i + 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function exec(cmd, args, writer, timeout)
|
||||
local os = require "os"
|
||||
local nixio = require "nixio"
|
||||
|
||||
local fdi, fdo = nixio.pipe()
|
||||
local pid = nixio.fork()
|
||||
|
||||
if pid > 0 then
|
||||
fdo:close()
|
||||
|
||||
if writer or timeout then
|
||||
local starttime = os.time()
|
||||
while true do
|
||||
if timeout and os.difftime(os.time(), starttime) >= timeout then
|
||||
nixio.kill(pid, nixio.const.SIGTERM)
|
||||
return 1
|
||||
end
|
||||
|
||||
if writer then
|
||||
local buffer = fdi:read(2048)
|
||||
if buffer and #buffer > 0 then
|
||||
writer(buffer)
|
||||
end
|
||||
end
|
||||
|
||||
local wpid, stat, code = nixio.waitpid(pid, "nohang")
|
||||
|
||||
if wpid and stat == "exited" then
|
||||
return code
|
||||
end
|
||||
|
||||
if not writer and timeout then
|
||||
nixio.nanosleep(1)
|
||||
end
|
||||
end
|
||||
else
|
||||
local wpid, stat, code = nixio.waitpid(pid)
|
||||
return wpid and stat == "exited" and code
|
||||
end
|
||||
elseif pid == 0 then
|
||||
nixio.dup(fdo, nixio.stdout)
|
||||
fdi:close()
|
||||
fdo:close()
|
||||
nixio.exece(cmd, args, nil)
|
||||
nixio.stdout:close()
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
local function compare_versions(ver1, comp, ver2)
|
||||
local table = table
|
||||
|
||||
local av1 = util.split(ver1, "[%.%-]", nil, true)
|
||||
local av2 = util.split(ver2, "[%.%-]", nil, true)
|
||||
|
||||
local max = table.getn(av1)
|
||||
local n2 = table.getn(av2)
|
||||
if (max < n2) then
|
||||
max = n2
|
||||
end
|
||||
|
||||
for i = 1, max, 1 do
|
||||
local s1 = av1[i] or ""
|
||||
local s2 = av2[i] or ""
|
||||
|
||||
if comp == "~=" and (s1 ~= s2) then return true end
|
||||
if (comp == "<" or comp == "<=") and (s1 < s2) then return true end
|
||||
if (comp == ">" or comp == ">=") and (s1 > s2) then return true end
|
||||
if (s1 ~= s2) then return false end
|
||||
end
|
||||
|
||||
return not (comp == "<" or comp == ">")
|
||||
end
|
||||
|
||||
local function auto_get_arch()
|
||||
local arch = nixio.uname().machine or ""
|
||||
|
||||
if arch == "mips" then
|
||||
if fs.access("/usr/lib/os-release") then
|
||||
arch = sys.exec("grep 'LEDE_BOARD' /usr/lib/os-release | grep -oE 'ramips|ar71xx'")
|
||||
elseif fs.access("/etc/openwrt_release") then
|
||||
arch = sys.exec("grep 'DISTRIB_TARGET' /etc/openwrt_release | grep -oE 'ramips|ar71xx'")
|
||||
end
|
||||
end
|
||||
|
||||
return util.trim(arch)
|
||||
end
|
||||
|
||||
local function get_file_info(arch)
|
||||
local file_tree = ""
|
||||
local sub_version = ""
|
||||
|
||||
if arch == "x86_64" then
|
||||
file_tree = "64"
|
||||
elseif arch == "ramips" then
|
||||
file_tree = "mipsle"
|
||||
elseif arch == "ar71xx" then
|
||||
file_tree = "mips"
|
||||
elseif arch:match("^i[%d]86$") then
|
||||
file_tree = "32"
|
||||
elseif arch:match("^armv[5-8]") then
|
||||
file_tree = "arm"
|
||||
sub_version = arch:match("[5-8]")
|
||||
end
|
||||
|
||||
return file_tree, sub_version
|
||||
end
|
||||
|
||||
local function get_api_json(url)
|
||||
local jsonc = require "luci.jsonc"
|
||||
|
||||
local output = { }
|
||||
--exec(wget, { "-O-", url, _unpack(wget_args) },
|
||||
-- function(chunk) output[#output + 1] = chunk end)
|
||||
--local json_content = util.trim(table.concat(output))
|
||||
|
||||
local json_content = luci.sys.exec(curl.." -sL "..url)
|
||||
|
||||
if json_content == "" then
|
||||
return { }
|
||||
end
|
||||
|
||||
return jsonc.parse(json_content) or { }
|
||||
end
|
||||
|
||||
function get_config_option(option, default)
|
||||
return uci:get("v2ray", "general", option) or default
|
||||
end
|
||||
|
||||
function get_current_log_file(type)
|
||||
local log_folder = get_config_option("log_folder", "/var/log/v2ray")
|
||||
return "%s/%s.%s.log" % { log_folder, type, "general" }
|
||||
end
|
||||
|
||||
function is_running(client)
|
||||
if client and client ~= "" then
|
||||
local file_name = client:match(".*/([^/]+)$") or ""
|
||||
if file_name ~= "" then
|
||||
return sys.call("pidof %s >/dev/null" % file_name) == 0
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function get_v2ray_version()
|
||||
return luci.sys.exec("/usr/bin/v2ray/v2ray -version | awk '{print $2}' | sed -n 1P")
|
||||
end
|
||||
|
||||
function to_check(arch)
|
||||
if not arch or arch == "" then
|
||||
arch = auto_get_arch()
|
||||
end
|
||||
|
||||
local file_tree, sub_version = get_file_info(arch)
|
||||
|
||||
if file_tree == "" then
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translate("Can't determine ARCH, or ARCH not supported. Please select manually.")
|
||||
}
|
||||
end
|
||||
|
||||
local json = get_api_json(v2ray_api)
|
||||
|
||||
if json.tag_name == nil then
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translate("Get remote version info failed.")
|
||||
}
|
||||
end
|
||||
|
||||
local remote_version = json.tag_name:match("[^v]+")
|
||||
local needs_update = compare_versions(get_v2ray_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,
|
||||
version = remote_version,
|
||||
html_url = html_url,
|
||||
type = file_tree .. sub_version,
|
||||
error = i18n.translate("New version found, but failed to get new version download url.")
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
code = 0,
|
||||
update = needs_update,
|
||||
version = remote_version,
|
||||
url = {
|
||||
html = html_url,
|
||||
download = download_url
|
||||
},
|
||||
type = file_tree .. sub_version
|
||||
}
|
||||
end
|
||||
|
||||
function to_download(url)
|
||||
if not url or url == "" then
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translate("Download url is required.")
|
||||
}
|
||||
end
|
||||
|
||||
sys.call("/bin/rm -f /tmp/v2ray_download.*")
|
||||
|
||||
local tmp_file = util.trim(util.exec("mktemp -u -t v2ray_download.XXXXXX"))
|
||||
|
||||
local result = exec(wget, {
|
||||
"-O", tmp_file, url, _unpack(wget_args) }, nil, command_timeout) == 0
|
||||
|
||||
if not result then
|
||||
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 isinstall_unzip=sys.call("opkg list-installed | grep unzip > /dev/null")==0
|
||||
if not isinstall_unzip then
|
||||
sys.call("opkg update && opkg install unzip > /dev/null")
|
||||
end
|
||||
|
||||
if not file or file == "" or not fs.access(file) then
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translate("File path required.")
|
||||
}
|
||||
end
|
||||
|
||||
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
|
||||
local tmp_dir = util.trim(util.exec("mktemp -d -t v2ray_extract.XXXXXX"))
|
||||
|
||||
local output = { }
|
||||
exec("/usr/bin/unzip", { "-o", file , "-d", tmp_dir },
|
||||
function(chunk) output[#output + 1] = chunk end)
|
||||
|
||||
local files = util.split(table.concat(output))
|
||||
|
||||
exec("/bin/rm", { "-f", file })
|
||||
|
||||
return {
|
||||
code = 0,
|
||||
file = tmp_dir
|
||||
}
|
||||
end
|
||||
|
||||
function to_move(file)
|
||||
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 = "/usr/bin/v2ray"
|
||||
|
||||
sys.call("mkdir -p "..client_file)
|
||||
|
||||
local result = exec("/bin/mv", { "-f", file.."/v2ray", file.."/v2ctl", client_file }, nil, command_timeout) == 0
|
||||
|
||||
if not result or not fs.access(client_file) then
|
||||
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translatef("Can't move new file to path: %s", client_file)
|
||||
}
|
||||
end
|
||||
|
||||
exec("/bin/chmod", { "-R", "755", client_file })
|
||||
|
||||
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
|
||||
|
||||
return { code = 0 }
|
||||
end
|
||||
@ -0,0 +1,110 @@
|
||||
local i = "v2ray_server"
|
||||
local d = require "luci.dispatcher"
|
||||
local a,t,e
|
||||
|
||||
local header_type={
|
||||
"none",
|
||||
"srtp",
|
||||
"utp",
|
||||
"wechat-video",
|
||||
"dtls",
|
||||
"wireguard",
|
||||
}
|
||||
|
||||
a=Map(i,"V2ray "..translate("Server Config"))
|
||||
a.redirect=d.build_url("admin","vpn","v2ray_server")
|
||||
|
||||
t=a:section(NamedSection,arg[1],"user","")
|
||||
t.addremove=false
|
||||
t.dynamic=false
|
||||
|
||||
e=t:option(Flag, "enable", translate("Enable"))
|
||||
e.default = "1"
|
||||
e.rmempty = false
|
||||
|
||||
e=t:option(Value,"remarks",translate("Remarks"))
|
||||
e.default=translate("Remarks")
|
||||
e.rmempty=false
|
||||
|
||||
e=t:option(Value,"port",translate("Port"))
|
||||
e.datatype="port"
|
||||
e.rmempty=false
|
||||
|
||||
e=t:option(ListValue,"protocol",translate("Protocol"))
|
||||
e:value("vmess",translate("Vmess"))
|
||||
|
||||
e=t:option(Value,"VMess_id",translate("ID"))
|
||||
e.default = luci.sys.exec("cat /proc/sys/kernel/random/uuid")
|
||||
e.rmempty=false
|
||||
e:depends("protocol","vmess")
|
||||
|
||||
e=t:option(Value,"VMess_alterId",translate("Alter ID"))
|
||||
e.default=16
|
||||
e.rmempty=false
|
||||
e:depends("protocol","vmess")
|
||||
|
||||
e=t:option(Value,"VMess_level",translate("User Level"))
|
||||
e.default=1
|
||||
|
||||
e=t:option(ListValue,"transport",translate("Transport"))
|
||||
e:value("tcp","TCP")
|
||||
e:value("mkcp", "mKCP")
|
||||
e:value("quic", "QUIC")
|
||||
|
||||
-- [[ TCP部分 ]]--
|
||||
|
||||
-- TCP伪装
|
||||
e = t:option(ListValue, "tcp_guise", translate("Camouflage Type"))
|
||||
e:depends("transport", "tcp")
|
||||
e:value("none", "none")
|
||||
e:value("http", "http")
|
||||
|
||||
-- HTTP域名
|
||||
e = t:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
|
||||
e:depends("tcp_guise", "http")
|
||||
|
||||
-- HTTP路径
|
||||
e = t:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
|
||||
e:depends("tcp_guise", "http")
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
e=t:option(ListValue,"mkcp_guise",translate("Camouflage Type"))
|
||||
for a,t in ipairs(header_type)do e:value(t)end
|
||||
e:depends("transport","mkcp")
|
||||
|
||||
e=t:option(Value,"mkcp_mtu",translate("KCP MTU"))
|
||||
e:depends("transport","mkcp")
|
||||
|
||||
e=t:option(Value,"mkcp_tti",translate("KCP TTI"))
|
||||
e:depends("transport","mkcp")
|
||||
|
||||
e=t:option(Value,"mkcp_uplinkCapacity",translate("KCP uplinkCapacity"))
|
||||
e:depends("transport","mkcp")
|
||||
|
||||
e=t:option(Value,"mkcp_downlinkCapacity",translate("KCP downlinkCapacity"))
|
||||
e:depends("transport","mkcp")
|
||||
|
||||
e=t:option(Flag,"mkcp_congestion",translate("KCP Congestion"))
|
||||
e:depends("transport","mkcp")
|
||||
|
||||
e=t:option(Value,"mkcp_readBufferSize",translate("KCP readBufferSize"))
|
||||
e:depends("transport","mkcp")
|
||||
|
||||
e=t:option(Value,"mkcp_writeBufferSize",translate("KCP writeBufferSize"))
|
||||
e:depends("transport","mkcp")
|
||||
|
||||
-- [[ QUIC部分 ]]--
|
||||
e=t:option(ListValue,"quic_security",translate("Encrypt Method"))
|
||||
e:value("none")
|
||||
e:value("aes-128-gcm")
|
||||
e:value("chacha20-poly1305")
|
||||
e:depends("transport","quic")
|
||||
|
||||
e=t:option(Value,"quic_key",translate("Encrypt Method")..translate("Key"))
|
||||
e:depends("transport","quic")
|
||||
|
||||
e=t:option(ListValue,"quic_guise",translate("Camouflage Type"))
|
||||
for a,t in ipairs(header_type)do e:value(t)end
|
||||
e:depends("transport","quic")
|
||||
|
||||
return a
|
||||
@ -0,0 +1,57 @@
|
||||
local o = require "luci.dispatcher"
|
||||
local fs = require "nixio.fs"
|
||||
local sys = require "luci.sys"
|
||||
local cursor = luci.model.uci.cursor()
|
||||
local appname = "v2ray_server"
|
||||
local a,t,e
|
||||
|
||||
a=Map(appname, translate("V2ray Server"))
|
||||
|
||||
t=a:section(TypedSection,"global",translate("Global Settings"))
|
||||
t.anonymous=true
|
||||
t.addremove=false
|
||||
|
||||
e=t:option(Flag,"enable",translate("Enable"))
|
||||
e.rmempty=false
|
||||
|
||||
t:append(Template("v2ray_server/v2ray"))
|
||||
|
||||
t=a:section(TypedSection,"user",translate("Users Manager"))
|
||||
t.anonymous=true
|
||||
t.addremove=true
|
||||
t.template="cbi/tblsection"
|
||||
t.extedit=o.build_url("admin","vpn",appname,"config","%s")
|
||||
function t.create(e,t)
|
||||
local e=TypedSection.create(e,t)
|
||||
luci.http.redirect(o.build_url("admin","vpn",appname,"config",e))
|
||||
end
|
||||
|
||||
function t.remove(t,a)
|
||||
t.map.proceed=true
|
||||
t.map:del(a)
|
||||
luci.http.redirect(o.build_url("admin","vpn",appname))
|
||||
end
|
||||
|
||||
e=t:option(Flag, "enable", translate("Enable"))
|
||||
e.width="5%"
|
||||
e.rmempty = false
|
||||
|
||||
e=t:option(DummyValue,"status",translate("Status"))
|
||||
e.template="v2ray_server/users_status"
|
||||
e.value=translate("Collecting data...")
|
||||
|
||||
e=t:option(DummyValue,"remarks",translate("Remarks"))
|
||||
e.width="15%"
|
||||
|
||||
e=t:option(DummyValue,"port",translate("Port"))
|
||||
e.width="10%"
|
||||
|
||||
e=t:option(DummyValue,"protocol",translate("Protocol"))
|
||||
e.width="15%"
|
||||
|
||||
e=t:option(DummyValue,"VMess_id",translate("ID"))
|
||||
e.width="35%"
|
||||
|
||||
a:append(Template("v2ray_server/users_list_status"))
|
||||
|
||||
return a
|
||||
@ -0,0 +1,28 @@
|
||||
<%#
|
||||
Copyright 2018 Lienol
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%
|
||||
local dsp = require "luci.dispatcher"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var v2ray_users_status = document.getElementsByClassName('v2ray_users_status');
|
||||
for(var i = 0; i < v2ray_users_status.length; i++) {
|
||||
var id = v2ray_users_status[i].parentElement.parentElement.parentElement.id;
|
||||
id = id.substr(id.lastIndexOf("-") + 1);
|
||||
XHR.poll(1,'<%=dsp.build_url("admin/vpn/v2ray_server/users_status")%>', {
|
||||
index: i,
|
||||
id: id
|
||||
},
|
||||
function(x, result) {
|
||||
v2ray_users_status[result.index].setAttribute("style","font-weight:bold;");
|
||||
v2ray_users_status[result.index].setAttribute("color",result.status ? "green":"red");
|
||||
v2ray_users_status[result.index].innerHTML = (result.status ? '✓' : 'X');
|
||||
}
|
||||
);
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
@ -0,0 +1,3 @@
|
||||
<%+cbi/valueheader%>
|
||||
<font class="v2ray_users_status" hint="<%=self:cfgvalue(section)%>">--</font>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,188 @@
|
||||
<%#
|
||||
Copyright 2018 <lienol@qq.com>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%
|
||||
local v2ray_version=luci.sys.exec("/usr/bin/v2ray/v2ray -version | awk '{print $2}' | sed -n 1P")
|
||||
local dsp = require "luci.dispatcher"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var v2rayInfo;
|
||||
var tokenStr = '<%=token%>';
|
||||
var noUpdateText = '<%:已是最新版本%>';
|
||||
var updateSuccessText = '<%:更新成功.%>';
|
||||
var clickToUpdateText = '<%:点击更新%>';
|
||||
var inProgressText = '<%:正在更新...%>';
|
||||
var unexpectedErrorText = '<%:意外错误.%>';
|
||||
var updateInProgressNotice = '<%:正在更新,你确认要关闭吗?%>';
|
||||
|
||||
window.onload = function() {
|
||||
var v2rayCheckBtn = document.getElementById('_v2ray-check_btn');
|
||||
var v2rayDetailElm = document.getElementById('_v2ray-check_btn-detail');
|
||||
};
|
||||
|
||||
function addPageNotice_v2ray() {
|
||||
window.onbeforeunload = function(e) {
|
||||
e.returnValue = updateInProgressNotice;
|
||||
return updateInProgressNotice;
|
||||
};
|
||||
}
|
||||
|
||||
function removePageNotice_v2ray() {
|
||||
window.onbeforeunload = undefined;
|
||||
}
|
||||
|
||||
function onUpdateSuccess_v2ray(btn) {
|
||||
alert(updateSuccessText);
|
||||
|
||||
if(btn) {
|
||||
btn.value = updateSuccessText;
|
||||
btn.placeholder = updateSuccessText;
|
||||
btn.disabled = true;
|
||||
}
|
||||
|
||||
window.setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function onRequestError_v2ray(btn, errorMessage) {
|
||||
btn.disabled = false;
|
||||
btn.value = btn.placeholder;
|
||||
|
||||
if(errorMessage) {
|
||||
alert(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function doAjaxGet(url, data, onResult) {
|
||||
new XHR().get(url, data, function(_, json) {
|
||||
var resultJson = json || {
|
||||
'code': 1,
|
||||
'error': unexpectedErrorText
|
||||
};
|
||||
|
||||
if(typeof onResult === 'function') {
|
||||
onResult(resultJson);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onBtnClick_v2ray(btn) {
|
||||
if(v2rayInfo === undefined) {
|
||||
checkUpdate_v2ray(btn);
|
||||
} else {
|
||||
doUpdate_v2ray(btn);
|
||||
}
|
||||
}
|
||||
|
||||
function checkUpdate_v2ray(btn) {
|
||||
btn.disabled = true;
|
||||
btn.value = inProgressText;
|
||||
|
||||
addPageNotice_v2ray();
|
||||
|
||||
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
|
||||
|
||||
doAjaxGet('<%=dsp.build_url("admin/vpn/v2ray_server/check")%>', {
|
||||
token: tokenStr,
|
||||
arch: ''
|
||||
}, function(json) {
|
||||
removePageNotice_v2ray();
|
||||
|
||||
if(json.code) {
|
||||
v2rayInfo = undefined;
|
||||
onRequestError_v2ray(btn, json.error);
|
||||
} else {
|
||||
if(json.update) {
|
||||
v2rayInfo = 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doUpdate_v2ray(btn) {
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:下载中...%>';
|
||||
|
||||
addPageNotice_v2ray();
|
||||
|
||||
var v2rayUpdateUrl = '<%=dsp.build_url("admin/vpn/v2ray_server/update")%>';
|
||||
// Download file
|
||||
doAjaxGet(v2rayUpdateUrl, {
|
||||
token: tokenStr,
|
||||
url: v2rayInfo ? v2rayInfo.url.download : ''
|
||||
}, function(json) {
|
||||
if(json.code) {
|
||||
removePageNotice_v2ray();
|
||||
onRequestError_v2ray(btn, json.error);
|
||||
} else {
|
||||
btn.value = '<%:解压中...%>';
|
||||
|
||||
// Extract file
|
||||
doAjaxGet(v2rayUpdateUrl, {
|
||||
token: tokenStr,
|
||||
task: 'extract',
|
||||
file: json.file,
|
||||
subfix: v2rayInfo ? v2rayInfo.type : ''
|
||||
}, function(json) {
|
||||
if(json.code) {
|
||||
removePageNotice_v2ray();
|
||||
onRequestError_v2ray(btn, json.error);
|
||||
} else {
|
||||
btn.value = '<%:移动中...%>';
|
||||
|
||||
// Move file to target dir
|
||||
doAjaxGet(v2rayUpdateUrl, {
|
||||
token: tokenStr,
|
||||
task: 'move',
|
||||
file: json.file
|
||||
}, function(json) {
|
||||
removePageNotice_v2ray();
|
||||
if(json.code) {
|
||||
onRequestError_v2ray(btn, json.error);
|
||||
} else {
|
||||
onUpdateSuccess_v2ray(btn);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title">V2ray
|
||||
<%:Version%>
|
||||
</label>
|
||||
<div class="cbi-value-field">
|
||||
<div class="cbi-value-description">
|
||||
<img src="/luci-static/resources/cbi/help.gif">
|
||||
<span>【 <%=v2ray_version%>】</span>
|
||||
<input class="cbi-button cbi-input-apply" type="submit" id="_v2ray-check_btn" onclick="onBtnClick_v2ray(this);" value="<%:Manually update%>">
|
||||
<span id="_v2ray-check_btn-detail"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
53
package/lean/luci-app-v2ray-server/po/zh-cn/v2ray_server.po
Normal file
53
package/lean/luci-app-v2ray-server/po/zh-cn/v2ray_server.po
Normal file
@ -0,0 +1,53 @@
|
||||
msgid "V2ray Server"
|
||||
msgstr "V2ray 服务器"
|
||||
|
||||
msgid "Global Settings"
|
||||
msgstr "全局设置"
|
||||
|
||||
msgid "Server Config"
|
||||
msgstr "服务器配置"
|
||||
|
||||
msgid "Users Manager"
|
||||
msgstr "用户管理"
|
||||
|
||||
msgid "Remarks"
|
||||
msgstr "备注"
|
||||
|
||||
msgid "Port"
|
||||
msgstr "端口"
|
||||
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
msgid "Protocol"
|
||||
msgstr "协议"
|
||||
|
||||
msgid "Null"
|
||||
msgstr "无"
|
||||
|
||||
msgid "Alter ID"
|
||||
msgstr "额外ID(AlterID)"
|
||||
|
||||
msgid "User Level"
|
||||
msgstr "用户等级(Level)"
|
||||
|
||||
msgid "Transport"
|
||||
msgstr "传输方式"
|
||||
|
||||
msgid "Camouflage Type"
|
||||
msgstr "伪装类型"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
msgid "Current Condition"
|
||||
msgstr "当前状态"
|
||||
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "RUNNING"
|
||||
msgstr "运行中"
|
||||
@ -0,0 +1,15 @@
|
||||
|
||||
config global
|
||||
option enable '0'
|
||||
|
||||
config user
|
||||
option enable '1'
|
||||
option remarks '备注222'
|
||||
option protocol 'vmess'
|
||||
option VMess_id 'fd00927a-b0c2-4629-aef7-d9ff15a9d722'
|
||||
option VMess_alterId '16'
|
||||
option VMess_level '1'
|
||||
option transport 'tcp'
|
||||
option tcp_guise 'none'
|
||||
option port '12366'
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2018-2019 Lienol <lawlienol@gmail.com>
|
||||
|
||||
START=99
|
||||
|
||||
CONFIG=v2ray_server
|
||||
CONFIG_PATH=/var/etc/$CONFIG
|
||||
|
||||
gen_v2ray_config_file() {
|
||||
config_get enable $1 enable
|
||||
[ "$enable" = "0" ] && return 0
|
||||
config_get remarks $1 remarks
|
||||
config_get port $1 port
|
||||
lua /usr/lib/lua/luci/model/cbi/v2ray_server/api/genv2rayconfig.lua $1 > $CONFIG_PATH/$1.json
|
||||
/usr/bin/v2ray/v2ray -config $CONFIG_PATH/$1.json >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
start_v2ray_server() {
|
||||
config_foreach gen_v2ray_config_file "user"
|
||||
fw3 reload
|
||||
}
|
||||
|
||||
stop_v2ray_server() {
|
||||
fw3 reload
|
||||
ps -w | grep "$CONFIG_PATH/" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
start() {
|
||||
config_load $CONFIG
|
||||
enable=$(uci get $CONFIG.@global[0].enable)
|
||||
if [ "$enable" = "0" ];then
|
||||
stop_v2ray_server
|
||||
else
|
||||
mkdir -p $CONFIG_PATH
|
||||
start_v2ray_server
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
stop_v2ray_server
|
||||
rm -rf $CONFIG_PATH
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete firewall.v2ray_server
|
||||
set firewall.v2ray_server=include
|
||||
set firewall.v2ray_server.type=script
|
||||
set firewall.v2ray_server.path=/usr/share/v2ray_server/firewall.include
|
||||
set firewall.v2ray_server.reload=1
|
||||
EOF
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@v2ray_server[-1]
|
||||
add ucitrack v2ray_server
|
||||
set ucitrack.@v2ray_server[-1].init=v2ray_server
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
chmod a+x /usr/share/v2ray_server/* >/dev/null 2>&1
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
. $IPKG_INSTROOT/lib/functions.sh
|
||||
. $IPKG_INSTROOT/lib/functions/service.sh
|
||||
|
||||
gen_user_iptables() {
|
||||
config_get enable $1 enable
|
||||
[ "$enable" = "0" ] && return 0
|
||||
config_get remarks $1 remarks
|
||||
config_get port $1 port
|
||||
iptables -A V2RAY-SERVER -p tcp --dport $port -m comment --comment "$remarks" -j ACCEPT
|
||||
iptables -A V2RAY-SERVER -p udp --dport $port -m comment --comment "$remarks" -j ACCEPT
|
||||
}
|
||||
|
||||
iptables -F V2RAY-SERVER 2>/dev/null
|
||||
iptables -D INPUT -j V2RAY-SERVER 2>/dev/null
|
||||
iptables -X V2RAY-SERVER 2>/dev/null
|
||||
|
||||
enable=$(uci get v2ray_server.@global[0].enable)
|
||||
if [ $enable -eq 1 ]; then
|
||||
iptables -N V2RAY-SERVER
|
||||
iptables -I INPUT -j V2RAY-SERVER
|
||||
config_load v2ray_server
|
||||
config_foreach gen_user_iptables "user"
|
||||
fi
|
||||
Loading…
Reference in New Issue
Block a user