From 4a38dae8fffd7dd35b0e0ebb42c40a1caafac9b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Nov 2026 21:15:30 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=9E=20Sync=202026-11-10=2021:15:30?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-passwall/Makefile | 2 +- .../luasrc/passwall/util_sing-box.lua | 18 +- .../luasrc/passwall/util_xray.lua | 45 +-- luci-app-passwall2/Makefile | 2 +- .../cbi/passwall2/client/type/1_sing-box.lua | 10 - .../cbi/passwall2/server/type/1_sing-box.lua | 4 + .../luasrc/passwall2/util_sing-box.lua | 23 +- .../luasrc/passwall2/util_xray.lua | 45 +-- .../passwall2/node_config/link_share_man.htm | 88 ------ luci-app-passwall2/po/fa/passwall2.po | 6 - luci-app-passwall2/po/ru/passwall2.po | 6 - luci-app-passwall2/po/zh-cn/passwall2.po | 6 - luci-app-passwall2/po/zh-tw/passwall2.po | 6 - .../root/usr/share/passwall2/subscribe.lua | 60 ---- luci-app-v2raya/LICENSE | 202 +++++++++++++ luci-app-v2raya/Makefile | 22 ++ .../resources/view/v2raya/config.js | 168 +++++++++++ .../luci-static/resources/view/v2raya/log.js | 76 +++++ .../resources/view/v2raya/overview.js | 280 ++++++++++++++++++ luci-app-v2raya/po/templates/v2raya.pot | 163 ++++++++++ .../share/luci/menu.d/luci-app-v2raya.json | 37 +++ .../usr/share/rpcd/acl.d/luci-app-v2raya.json | 25 ++ openwrt-natmapt/Makefile | 4 +- v2raya-core/Makefile | 74 +++++ v2raya-core/test.sh | 13 + v2raya-geoip/Makefile | 65 ++++ v2raya-geosite/Makefile | 66 +++++ 27 files changed, 1281 insertions(+), 235 deletions(-) create mode 100644 luci-app-v2raya/LICENSE create mode 100644 luci-app-v2raya/Makefile create mode 100644 luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/config.js create mode 100644 luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/log.js create mode 100644 luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/overview.js create mode 100644 luci-app-v2raya/po/templates/v2raya.pot create mode 100644 luci-app-v2raya/root/usr/share/luci/menu.d/luci-app-v2raya.json create mode 100644 luci-app-v2raya/root/usr/share/rpcd/acl.d/luci-app-v2raya.json create mode 100644 v2raya-core/Makefile create mode 100644 v2raya-core/test.sh create mode 100644 v2raya-geoip/Makefile create mode 100644 v2raya-geosite/Makefile diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index 75eb0addd..f0d82808f 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.11.3 -PKG_RELEASE:=373 +PKG_RELEASE:=374 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua index dc45f1b63..90a6df4fd 100644 --- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -362,16 +362,22 @@ function gen_outbound(flag, node, tag, proxy_table) ["User-Agent"] = node.user_agent } or nil, } - local path = api.UrlDecode(node.ws_path) - local path_dat = api.split(path, "?") - local params = {} - for _, v in pairs(api.split(path_dat[2], '&')) do - local t = api.split(v, '=') + local path, query = (node.ws_path or "/"):match("^(.-)%?(.*)$") + local params, query_params = {}, {} + for v in (query or ""):gmatch("[^&]+") do + local t = api.split(api.UrlDecode(v), '=') params[t[1]] = t[2] + if t[1] ~= "ed" and t[1] ~= "eh" then + table.insert(query_params, v) + end end local ed = tonumber(params.ed) if ed then - v2ray_transport.path = path_dat[1] + -- Consume early-data options while preserving the remaining query. + v2ray_transport.path = path + if #query_params > 0 then + v2ray_transport.path = path .. "?" .. table.concat(query_params, "&") + end v2ray_transport.max_early_data = ed end v2ray_transport.early_data_header_name = params.eh or "Sec-WebSocket-Protocol" diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua index 50af41781..dd81a1574 100644 --- a/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -12,7 +12,7 @@ local GLOBAL = { local xray_version = api.get_app_version("xray") -local xray_min_version = "26.7.11" +local xray_min_version = "26.9.9" local function get_domain_excluded() local path = string.format("/etc/%s/rules/domains_excluded", api.c_config) @@ -355,6 +355,30 @@ function gen_outbound(flag, node, tag, proxy_table) } } udp[#udp+1] = r + elseif node.hysteria2_hop and node.hysteria2_hop ~= "" then + udp[#udp+1] = { + type = "udphop", + settings = { + mode = "intervallocal,intervalremote", + remotePorts = string.gsub(node.hysteria2_hop, ":", "-"), + interval = (function(v) + if not v then return 30 end + if v:find("-", 1, true) then + local min, max = v:match("^(%d+)%-(%d+)$") + min = tonumber(min) + max = tonumber(max) + if min and max then + min = (min >= 5) and min or 5 + max = (max >= min) and max or min + return min .. "-" .. max + end + return 30 + end + v = tonumber((v or "30"):match("^%d+")) + return (v and v >= 5) and v or 30 + end)(node.hysteria2_hop_interval) + } + } end finalmask.udp = udp local up = tonumber(node.hysteria2_up_mbps) or 0 @@ -363,25 +387,6 @@ function gen_outbound(flag, node, tag, proxy_table) congestion = (up <= 0 and down <= 0) and "bbr" or "brutal", brutalUp = up > 0 and (up .. "mbps") or nil, brutalDown = down > 0 and (down .. "mbps") or nil, - udpHop = (node.hysteria2_hop) and { - ports = string.gsub(node.hysteria2_hop, ":", "-"), - interval = (function(v) - if not v then return 30 end - if v:find("-", 1, true) then - local min, max = v:match("^(%d+)%-(%d+)$") - min = tonumber(min) - max = tonumber(max) - if min and max then - min = (min >= 5) and min or 5 - max = (max >= min) and max or min - return min .. "-" .. max - end - return 30 - end - v = tonumber((v or "30"):match("^%d+")) - return (v and v >= 5) and v or 30 - end)(node.hysteria2_hop_interval) - } or nil, maxIdleTimeout = (function(t) t = tonumber(tostring(t or "30"):match("^%d+")) return (t and t >= 4 and t <= 120) and t or 30 diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 767571cd7..9c5ab289a 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 PKG_VERSION:=26.11.3 -PKG_RELEASE:=174 +PKG_RELEASE:=175 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua index 5a9587fb2..d8eb80a77 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua @@ -810,16 +810,6 @@ o = s:option(Value, "ws_path", translate("WebSocket Path")) o.placeholder = "/" o:depends({ transport = "ws" }) -o = s:option(Flag, "ws_enableEarlyData", translate("Enable early data")) -o:depends({ transport = "ws" }) - -o = s:option(Value, "ws_maxEarlyData", translate("Early data length")) -o.default = "1024" -o:depends({ ws_enableEarlyData = true }) - -o = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol")) -o:depends({ ws_enableEarlyData = true }) - -- [[ HTTPUpgrade ]]-- o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host")) o:depends({ transport = "httpupgrade" }) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua index 2f6c94818..8815f57dd 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua @@ -459,6 +459,10 @@ o:depends({ transport = "ws" }) o = s:option(Value, "ws_path", translate("WebSocket Path")) o:depends({ transport = "ws" }) +o = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol")) +o.placeholder = "Sec-WebSocket-Protocol" +o:depends({ transport = "ws" }) + -- [[ HTTPUpgrade ]]-- o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host")) diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua index a73d89494..56b33aa94 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua @@ -366,9 +366,26 @@ function gen_outbound(flag, node, tag, proxy_table) Host = node.ws_host, ["User-Agent"] = node.user_agent } or nil, - max_early_data = tonumber(node.ws_maxEarlyData) or nil, - early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil -- For compatibility with Xray-core, set it to Sec-WebSocket-Protocol. It needs to be consistent with the server. } + local path, query = (node.ws_path or "/"):match("^(.-)%?(.*)$") + local params, query_params = {}, {} + for v in (query or ""):gmatch("[^&]+") do + local t = api.split(api.UrlDecode(v), '=') + params[t[1]] = t[2] + if t[1] ~= "ed" and t[1] ~= "eh" then + table.insert(query_params, v) + end + end + local ed = tonumber(params.ed) + if ed then + -- Consume early-data options while preserving the remaining query. + v2ray_transport.path = path + if #query_params > 0 then + v2ray_transport.path = path .. "?" .. table.concat(query_params, "&") + end + v2ray_transport.max_early_data = ed + end + v2ray_transport.early_data_header_name = params.eh or "Sec-WebSocket-Protocol" end if node.transport == "httpupgrade" then @@ -822,7 +839,7 @@ function gen_config_server(node) type = "ws", path = node.ws_path or "/", headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil, - early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil -- For compatibility with Xray-core, set it to Sec-WebSocket-Protocol. It needs to be consistent with the server. + early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or "Sec-WebSocket-Protocol" } end diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 1a03a2164..825e86fd9 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -12,7 +12,7 @@ local GLOBAL = { local xray_version = api.get_app_version("xray") -local xray_min_version = "26.7.11" +local xray_min_version = "26.9.9" local function get_domain_excluded() local path = "/usr/share/passwall2/domains_excluded" @@ -364,6 +364,30 @@ function gen_outbound(flag, node, tag, proxy_table) } } udp[#udp+1] = r + elseif node.hysteria2_hop and node.hysteria2_hop ~= "" then + udp[#udp+1] = { + type = "udphop", + settings = { + mode = "intervallocal,intervalremote", + remotePorts = string.gsub(node.hysteria2_hop, ":", "-"), + interval = (function(v) + if not v then return 30 end + if v:find("-", 1, true) then + local min, max = v:match("^(%d+)%-(%d+)$") + min = tonumber(min) + max = tonumber(max) + if min and max then + min = (min >= 5) and min or 5 + max = (max >= min) and max or min + return min .. "-" .. max + end + return 30 + end + v = tonumber((v or "30"):match("^%d+")) + return (v and v >= 5) and v or 30 + end)(node.hysteria2_hop_interval) + } + } end finalmask.udp = udp local up = tonumber(node.hysteria2_up_mbps) or 0 @@ -372,25 +396,6 @@ function gen_outbound(flag, node, tag, proxy_table) congestion = (up <= 0 and down <= 0) and "bbr" or "brutal", brutalUp = up > 0 and (up .. "mbps") or nil, brutalDown = down > 0 and (down .. "mbps") or nil, - udpHop = (node.hysteria2_hop) and { - ports = string.gsub(node.hysteria2_hop, ":", "-"), - interval = (function(v) - if not v then return 30 end - if v:find("-", 1, true) then - local min, max = v:match("^(%d+)%-(%d+)$") - min = tonumber(min) - max = tonumber(max) - if min and max then - min = (min >= 5) and min or 5 - max = (max >= min) and max or min - return min .. "-" .. max - end - return 30 - end - v = tonumber((v or "30"):match("^%d+")) - return (v and v >= 5) and v or 30 - end)(node.hysteria2_hop_interval) - } or nil, maxIdleTimeout = (function(t) t = tonumber(tostring(t or "30"):match("^%d+")) return (t and t >= 4 and t <= 120) and t or 30 diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm index 7741cca56..562f1ab0c 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm @@ -254,10 +254,6 @@ local current_node = map:get(section) if (v_transport === "ws") { params += opt.query("host", dom_prefix + "ws_host"); params += opt.query("path", dom_prefix + "ws_path"); - if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) { - var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value; - params += "?ed=" + ws_maxEarlyData; - } } else if (v_transport === "http") { params += opt.query("host", dom_prefix + "http_host"); params += opt.query("path", dom_prefix + "http_path"); @@ -376,10 +372,6 @@ local current_node = map:get(section) if (v_transport === "ws") { info.host = opt.get(dom_prefix + "ws_host").value; info.path = opt.get(dom_prefix + "ws_path").value; - if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) { - var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value; - info.path = info.path + "?ed=" + ws_maxEarlyData; - } } else if (v_transport === "http") { info.host = opt.get(dom_prefix + "http_host").value; info.path = opt.get(dom_prefix + "http_path").value; @@ -436,10 +428,6 @@ local current_node = map:get(section) if (v_transport === "ws") { params += opt.query("host", dom_prefix + "ws_host"); params += opt.query("path", dom_prefix + "ws_path"); - if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) { - var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value; - params += encodeURIComponent("?ed=" + ws_maxEarlyData); - } } else if (v_transport === "http") { params += opt.query("host", dom_prefix + "http_host"); params += opt.query("path", dom_prefix + "http_path"); @@ -528,10 +516,6 @@ local current_node = map:get(section) if (v_transport === "ws") { params += opt.query("host", dom_prefix + "ws_host"); params += opt.query("path", dom_prefix + "ws_path"); - if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) { - var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value; - params += "?ed=" + ws_maxEarlyData; - } } else if (v_transport === "http") { params += opt.query("host", dom_prefix + "http_host"); params += opt.query("path", dom_prefix + "http_path"); @@ -1111,24 +1095,6 @@ local current_node = map:get(section) } else if (queryParam.type === "ws") { opt.set(dom_prefix + 'ws_host', queryParam.host || ""); opt.set(dom_prefix + 'ws_path', queryParam.path || ""); - if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) { - var ws_path_params = {}; - var ws_path_dat = queryParam.path.split('?'); - var ws_path = ws_path_dat[0]; - var ws_path_params = {}; - var ws_path_params_array = (ws_path_dat[1] || '').split('&'); - for (i = 0; i < ws_path_params_array.length; i++) { - var kv = ws_path_params_array[i].split('='); - ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); - } - - if (ws_path_params.ed) { - opt.set(dom_prefix + 'ws_path', ws_path); - opt.set(dom_prefix + 'ws_enableEarlyData', true); - opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); - opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); - } - } } else if (queryParam.type === "http") { if (dom_prefix == "xray_") { opt.set(dom_prefix + 'xhttp_mode', "stream-one"); @@ -1259,24 +1225,6 @@ local current_node = map:get(section) } else if (queryParam.type === "ws") { opt.set(dom_prefix + 'ws_host', queryParam.host || ""); opt.set(dom_prefix + 'ws_path', queryParam.path || ""); - if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) { - var ws_path_params = {}; - var ws_path_dat = queryParam.path.split('?'); - var ws_path = ws_path_dat[0]; - var ws_path_params = {}; - var ws_path_params_array = (ws_path_dat[1] || '').split('&'); - for (i = 0; i < ws_path_params_array.length; i++) { - var kv = ws_path_params_array[i].split('='); - ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); - } - - if (ws_path_params.ed) { - opt.set(dom_prefix + 'ws_path', ws_path); - opt.set(dom_prefix + 'ws_enableEarlyData', true); - opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); - opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); - } - } } else if (queryParam.type === "h2" || queryParam.type === "http") { if (dom_prefix == "xray_") { opt.set(dom_prefix + 'xhttp_mode', "stream-one"); @@ -1446,24 +1394,6 @@ local current_node = map:get(section) } else if (ssm.net === "ws") { opt.set(dom_prefix + 'ws_host', ssm.host); opt.set(dom_prefix + 'ws_path', ssm.path); - if (dom_prefix == "singbox_" && ssm.path && ssm.path.length > 1) { - var ws_path_params = {}; - var ws_path_dat = ssm.path.split('?'); - var ws_path = ws_path_dat[0]; - var ws_path_params = {}; - var ws_path_params_array = (ws_path_dat[1] || '').split('&'); - for (i = 0; i < ws_path_params_array.length; i++) { - var kv = ws_path_params_array[i].split('='); - ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); - } - - if (ws_path_params.ed) { - opt.set(dom_prefix + 'ws_path', ws_path); - opt.set(dom_prefix + 'ws_enableEarlyData', true); - opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); - opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); - } - } } else if (ssm.net === "http") { if (dom_prefix == "xray_") { opt.set(dom_prefix + 'xhttp_mode', "stream-one"); @@ -1589,24 +1519,6 @@ local current_node = map:get(section) } else if (queryParam.type === "ws") { opt.set(dom_prefix + 'ws_host', queryParam.host || ""); opt.set(dom_prefix + 'ws_path', queryParam.path || ""); - if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) { - var ws_path_params = {}; - var ws_path_dat = queryParam.path.split('?'); - var ws_path = ws_path_dat[0]; - var ws_path_params = {}; - var ws_path_params_array = (ws_path_dat[1] || '').split('&'); - for (i = 0; i < ws_path_params_array.length; i++) { - var kv = ws_path_params_array[i].split('='); - ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); - } - - if (ws_path_params.ed) { - opt.set(dom_prefix + 'ws_path', ws_path); - opt.set(dom_prefix + 'ws_enableEarlyData', true); - opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); - opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); - } - } } else if (queryParam.type === "h2" || queryParam.type === "http") { if (dom_prefix == "xray_") { opt.set(dom_prefix + 'xhttp_mode', "stream-one"); diff --git a/luci-app-passwall2/po/fa/passwall2.po b/luci-app-passwall2/po/fa/passwall2.po index 2f2644743..cd5dd963a 100644 --- a/luci-app-passwall2/po/fa/passwall2.po +++ b/luci-app-passwall2/po/fa/passwall2.po @@ -1585,12 +1585,6 @@ msgstr "همزمانی XUDP Mux" msgid "Padding" msgstr "پدینگ (Padding)" -msgid "Enable early data" -msgstr "فعال‌سازی داده‌های اولیه (Early Data)" - -msgid "Early data length" -msgstr "طول داده‌های اولیه" - msgid "Early data header name" msgstr "نام هدر داده‌های اولیه" diff --git a/luci-app-passwall2/po/ru/passwall2.po b/luci-app-passwall2/po/ru/passwall2.po index 700bedd5e..11b6f90f5 100644 --- a/luci-app-passwall2/po/ru/passwall2.po +++ b/luci-app-passwall2/po/ru/passwall2.po @@ -1586,12 +1586,6 @@ msgstr "Макс. параллельных соединений XUDP" msgid "Padding" msgstr "Заполнение" -msgid "Enable early data" -msgstr "Включить 0-RTT данные" - -msgid "Early data length" -msgstr "Макс. длина 0-RTT данных" - msgid "Early data header name" msgstr "Имя HTTP-заголовка для 0-RTT" diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po index 0017efa44..f0f67cde2 100644 --- a/luci-app-passwall2/po/zh-cn/passwall2.po +++ b/luci-app-passwall2/po/zh-cn/passwall2.po @@ -1577,12 +1577,6 @@ msgstr "XUDP 最大并发连接数" msgid "Padding" msgstr "填充" -msgid "Enable early data" -msgstr "启用前置数据" - -msgid "Early data length" -msgstr "前置数据最大长度" - msgid "Early data header name" msgstr "前置数据 HTTP 头名" diff --git a/luci-app-passwall2/po/zh-tw/passwall2.po b/luci-app-passwall2/po/zh-tw/passwall2.po index 1d993c711..0d60d4425 100644 --- a/luci-app-passwall2/po/zh-tw/passwall2.po +++ b/luci-app-passwall2/po/zh-tw/passwall2.po @@ -1577,12 +1577,6 @@ msgstr "XUDP 最大並發連接數" msgid "Padding" msgstr "填充" -msgid "Enable early data" -msgstr "啟用前置數据" - -msgid "Early data length" -msgstr "前置數据最大长度" - msgid "Early data header name" msgstr "前置數据 HTTP 头名" diff --git a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua index 69ec808a1..3cc13a78f 100755 --- a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua +++ b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua @@ -679,21 +679,6 @@ local function processData(szType, content, add_mode, group, sub_cfg) if info.net == 'ws' then result.ws_host = info.host result.ws_path = info.path - if result.type == "sing-box" and info.path then - local ws_path_dat = split(info.path, "?") - local ws_path = ws_path_dat[1] - local ws_path_params = {} - for _, v in pairs(split(ws_path_dat[2], '&')) do - local t = split(v, '=') - ws_path_params[t[1]] = t[2] - end - if ws_path_params.ed and tonumber(ws_path_params.ed) then - result.ws_path = ws_path - result.ws_enableEarlyData = "1" - result.ws_maxEarlyData = tonumber(ws_path_params.ed) - result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol" - end - end end if info.net == "http" then if result.type == "Xray" then @@ -936,21 +921,6 @@ local function processData(szType, content, add_mode, group, sub_cfg) if params.type == 'ws' then result.ws_host = params.host result.ws_path = params.path - if result.type == "sing-box" and params.path then - local ws_path_dat = split(params.path, "%?") - local ws_path = ws_path_dat[1] - local ws_path_params = {} - for _, v in pairs(split(ws_path_dat[2], '&')) do - local t = split(v, '=') - ws_path_params[t[1]] = t[2] - end - if ws_path_params.ed and tonumber(ws_path_params.ed) then - result.ws_path = ws_path - result.ws_enableEarlyData = "1" - result.ws_maxEarlyData = tonumber(ws_path_params.ed) - result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol" - end - end end if params.type == "http" then if result.type == "sing-box" then @@ -1152,21 +1122,6 @@ local function processData(szType, content, add_mode, group, sub_cfg) if params.type == 'ws' then result.ws_host = params.host result.ws_path = params.path - if result.type == "sing-box" and params.path then - local ws_path_dat = split(params.path, "%?") - local ws_path = ws_path_dat[1] - local ws_path_params = {} - for _, v in pairs(split(ws_path_dat[2], '&')) do - local t = split(v, '=') - ws_path_params[t[1]] = t[2] - end - if ws_path_params.ed and tonumber(ws_path_params.ed) then - result.ws_path = ws_path - result.ws_enableEarlyData = "1" - result.ws_maxEarlyData = tonumber(ws_path_params.ed) - result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol" - end - end end if params.type == "http" then if result.type == "sing-box" then @@ -1281,21 +1236,6 @@ local function processData(szType, content, add_mode, group, sub_cfg) if params.type == 'ws' then result.ws_host = params.host result.ws_path = params.path - if result.type == "sing-box" and params.path then - local ws_path_dat = split(params.path, "%?") - local ws_path = ws_path_dat[1] - local ws_path_params = {} - for _, v in pairs(split(ws_path_dat[2], '&')) do - local t = split(v, '=') - ws_path_params[t[1]] = t[2] - end - if ws_path_params.ed and tonumber(ws_path_params.ed) then - result.ws_path = ws_path - result.ws_enableEarlyData = "1" - result.ws_maxEarlyData = tonumber(ws_path_params.ed) - result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol" - end - end end if params.type == "http" then if result.type == "sing-box" then diff --git a/luci-app-v2raya/LICENSE b/luci-app-v2raya/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/luci-app-v2raya/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/luci-app-v2raya/Makefile b/luci-app-v2raya/Makefile new file mode 100644 index 000000000..4c1fa9e51 --- /dev/null +++ b/luci-app-v2raya/Makefile @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2026 Amr Fateem +# +# LuCI application for v2rayA. + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI support for v2rayA +LUCI_DEPENDS:=+luci-base +v2raya +LUCI_PKGARCH:=all +LUCI_MAINTAINER:=Amr Fateem + +PKG_VERSION:=2.5.13 +PKG_RELEASE:=1 + +PKG_LICENSE:=Apache-2.0 +CONFLICTS:=luci-app-v2raya-nextgen + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/config.js b/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/config.js new file mode 100644 index 000000000..df8e3943b --- /dev/null +++ b/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/config.js @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: Apache-2.0 +'use strict'; +'require form'; +'require uci'; +'require ui'; +'require view'; + +return view.extend({ + load: function () { + return uci.load('v2raya'); + }, + + render: function () { + var m, s, o; + + m = new form.Map('v2raya', _('v2rayA'), + _('Settings for the v2rayA service. Save & Apply restarts the service so the new values take effect. v2rayA 2.5.x uses only its bundled v2raya_core; Xray-core and sing-box are not supported backends.')); + + s = m.section(form.NamedSection, 'config', 'v2raya'); + s.tab('general', _('General')); + s.tab('proxy', _('Transparent proxy'), + _('The transparent proxy mode, routing rules and nodes are configured in the v2rayA web interface. These options control core start-up and hooks.')); + s.tab('logging', _('Logging')); + s.tab('advanced', _('Advanced')); + + o = s.taboption('general', form.Flag, 'enabled', _('Enable service')); + o.default = '0'; + o.rmempty = false; + o.description = _('Start v2rayA at boot. You can also start or stop it from the overview page.'); + + o = s.taboption('general', form.Value, 'address', _('Listening address')); + o.placeholder = '0.0.0.0:2017'; + o.rmempty = false; + o.validate = function (section_id, value) { + if (!value) + return _('An address is required.'); + + if (!/^(\[[0-9a-fA-F:]+\]|[A-Za-z0-9._-]+):[0-9]{1,5}$/.test(value)) + return _('Enter an address and port, for example 0.0.0.0:2017 or [::]:2017.'); + + return true; + }; + o.description = _('The web interface listens on this address. Use 127.0.0.1:2017 to keep it off the LAN.'); + + o = s.taboption('general', form.Value, 'baseurl', _('Base URL')); + o.placeholder = '/'; + o.description = _('URL prefix when v2rayA runs behind a reverse proxy.'); + o.validate = function (section_id, value) { + if (value && value.charAt(0) !== '/') + return _('The base URL must start with /.'); + return true; + }; + + o = s.taboption('general', form.ListValue, 'ipv6_support', _('IPv6 support')); + o.value('auto', _('Auto')); + o.value('on', _('On')); + o.value('off', _('Off')); + o.default = 'auto'; + o.description = _('Make sure IPv6 connectivity works before forcing it on.'); + + o = s.taboption('general', form.ListValue, 'nftables_support', _('nftables support')); + o.value('auto', _('Auto')); + o.value('on', _('On')); + o.value('off', _('Off')); + o.default = 'auto'; + o.description = _('nftables is the default firewall backend on OpenWrt 22.03 and later.'); + + o = s.taboption('proxy', form.Value, 'core_startup_timeout', _('Core startup timeout')); + o.datatype = 'uinteger'; + o.placeholder = '15'; + o.default = '15'; + o.description = _('Seconds to wait for v2raya_core to start. Raise this on slow MIPS routers.'); + + o = s.taboption('proxy', form.Flag, 'redirect_respect_bound_device', _('Respect bound devices in redirect mode')); + o.default = '0'; + o.description = _('Let sockets bound to a device bypass the proxy. Requires Linux 5.14 or later and cgroup v2.'); + + o = s.taboption('proxy', form.Value, 'transparent_hook', _('Transparent proxy hook')); + o.placeholder = '/usr/bin/example-hook'; + o.description = _('Executable called around transparent proxy setup and teardown.'); + + o = s.taboption('proxy', form.Value, 'core_hook', _('Core life-cycle hook')); + o.placeholder = '/usr/bin/example-hook'; + o.description = _('Executable called around v2raya_core start and stop.'); + + o = s.taboption('proxy', form.Value, 'plugin_manager', _('Plugin manager')); + o.placeholder = '/usr/bin/example-plugin-manager'; + o.description = _('Executable used to parse protocols that v2rayA does not know natively.'); + + o = s.taboption('logging', form.ListValue, 'log_level', _('Log level')); + o.value('trace', _('Trace')); + o.value('debug', _('Debug')); + o.value('info', _('Info')); + o.value('warn', _('Warning')); + o.value('error', _('Error')); + o.default = 'warn'; + o.description = _('Warning keeps flash writes down. Use Info while debugging.'); + + o = s.taboption('logging', form.Value, 'log_max_days', _('Keep logs for')); + o.datatype = 'uinteger'; + o.placeholder = '1'; + o.default = '1'; + o.description = _('Number of days to keep rotated log files.'); + + o = s.taboption('logging', form.Flag, 'log_disable_color', _('Disable colours in the log')); + o.default = '1'; + o.description = _('The log file is plain text, so colours are not useful there.'); + + o = s.taboption('logging', form.Flag, 'log_disable_timestamp', _('Disable timestamps in the log')); + o.default = '0'; + o.description = _('Only relevant when v2rayA writes to syslog or journald.'); + + o = s.taboption('advanced', form.Value, 'v2ray_assetsdir', _('Rule data directory')); + o.placeholder = '/usr/share/v2raya'; + o.default = '/usr/share/v2raya'; + o.description = _('Directory containing geoip.dat and geosite.dat. Leave empty for auto-detection.'); + + o = s.taboption('advanced', form.Value, 'v2ray_bin', _('Core binary (v2raya_core only)')); + o.placeholder = _('Auto-detect'); + o.description = _('Path to v2raya_core. Leave empty to use the binary next to v2raya or in PATH. Only the matching v2raya_core is supported; Xray-core and sing-box use incompatible configuration formats.'); + + o = s.taboption('advanced', form.Value, 'v2ray_confdir', _('Extra configuration directory')); + o.placeholder = _('None'); + o.description = _('Additional v2ray configuration files merged into the generated configuration.'); + + o = s.taboption('advanced', form.Value, 'gomemlimit', _('Go memory limit')); + o.placeholder = '64MiB'; + o.default = '64MiB'; + o.description = _('Soft memory limit for both v2raya and v2raya_core (GOMEMLIMIT). Use 0 to disable, or 96MiB on a larger router.'); + + o = s.taboption('advanced', form.Value, 'gomaxprocs', _('Go scheduler threads')); + o.datatype = 'uinteger'; + o.placeholder = '2'; + o.default = '2'; + o.description = _('GOMAXPROCS. 0 uses every CPU core; 2 is enough for most routers.'); + + o = s.taboption('advanced', form.Value, 'gogc', _('Go GC percent')); + o.datatype = 'uinteger'; + o.placeholder = '0'; + o.default = '0'; + o.description = _('GOGC. 0 keeps the Go default; lower values use less memory and more CPU.'); + + o = s.taboption('advanced', form.Value, 'nofile', _('Open file limit')); + o.datatype = 'uinteger'; + o.placeholder = '16384'; + o.default = '16384'; + o.description = _('Maximum open file descriptors. 16384 is plenty for a home router and bounds a connection flood.'); + + return m.render(); + }, + + handleSaveApply: function (ev, mode) { + /* + * Applying the UCI delta emits a config.change event; the init + * script's procd reload trigger restarts the service with the new + * values. Restarting here as well would stop and start it twice. + */ + return this.handleSave(ev).then(function () { + return uci.apply(); + }).then(function () { + ui.addNotification(null, E('p', {}, + _('Settings applied. The service is restarting.')), 'info'); + }).catch(function (err) { + ui.addNotification(null, E('p', {}, + _('The configuration was not applied: %s').format(err)), 'danger'); + }); + } +}); diff --git a/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/log.js b/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/log.js new file mode 100644 index 000000000..f0ac469f6 --- /dev/null +++ b/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/log.js @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +'use strict'; +'require dom'; +'require fs'; +'require poll'; +'require ui'; +'require view'; + +var LOG_PATH = '/var/log/v2raya/v2raya.log'; +var MAX_LINES = 500; + +return view.extend({ + render: function () { + var logEl = E('pre', { + id: 'v2raya-log', + style: 'white-space:pre-wrap;word-break:break-all;max-height:65vh;overflow:auto;padding:8px;margin:0;background:rgba(0,0,0,0.04);border-radius:4px' + }, _('Loading…')); + + var refresh = function () { + return L.resolveDefault(fs.read_direct(LOG_PATH, 'text'), null).then(function (text) { + if (text === null) { + dom.content(logEl, _('The log file does not exist yet. Start the service and try again.')); + return; + } + + var lines = text.replace(/\s+$/, '').split('\n'); + if (lines.length > MAX_LINES) + lines = lines.slice(-MAX_LINES); + + var atBottom = (logEl.scrollTop + logEl.clientHeight + 20) >= logEl.scrollHeight; + dom.content(logEl, lines.join('\n') || _('The log is empty.')); + + if (atBottom) + logEl.scrollTop = logEl.scrollHeight; + }).catch(function (err) { + dom.content(logEl, _('Unable to read the log: %s').format(err)); + }); + }; + + var refreshBtn = E('button', { class: 'btn cbi-button cbi-button-action' }, _('Refresh')); + var clearBtn = E('button', { class: 'btn cbi-button cbi-button-reset' }, _('Clear log')); + + refreshBtn.addEventListener('click', function () { refresh(); }); + clearBtn.addEventListener('click', function () { + if (!confirm(_('Clear the v2rayA log file?'))) + return; + + L.resolveDefault(fs.write(LOG_PATH, ''), null).then(function (res) { + if (res === null) + ui.addNotification(null, E('p', {}, _('Unable to clear the log file.')), 'danger'); + else + ui.addNotification(null, E('p', {}, _('Log file cleared.')), 'info'); + + refresh(); + }); + }); + + refresh(); + poll.add(refresh, 5); + + return E('div', { class: 'cbi-map' }, [ + E('h2', {}, _('v2rayA log')), + E('div', { class: 'cbi-map-descr' }, + _('Showing the last %d lines of %s. Refreshes every %d seconds.') + .format(MAX_LINES, LOG_PATH, L.env.pollinterval || 5)), + E('div', { class: 'cbi-section' }, [ + logEl, + E('div', { style: 'margin-top:8px' }, [ refreshBtn, ' ', clearBtn ]) + ]) + ]); + }, + + handleSave: null, + handleSaveApply: null, + handleReset: null +}); diff --git a/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/overview.js b/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/overview.js new file mode 100644 index 000000000..f956a5eff --- /dev/null +++ b/luci-app-v2raya/htdocs/luci-static/resources/view/v2raya/overview.js @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: Apache-2.0 +'use strict'; +'require view'; +'require fs'; +'require poll'; +'require rpc'; +'require uci'; +'require ui'; + +var callServiceList = rpc.declare({ + object: 'service', + method: 'list', + params: [ 'name' ], + expect: { '': {} } +}); + +function serviceRunning() { + return L.resolveDefault(callServiceList('v2raya'), {}).then(function (res) { + var inst = res && res.v2raya && res.v2raya.instances && res.v2raya.instances.v2raya; + return !!(inst && inst.running); + }); +} + +function commandVersion(command, args) { + return L.resolveDefault(fs.exec(command, args || []), null).then(function (res) { + if (!res || res.code !== 0 || !res.stdout) + return null; + + var line = res.stdout.trim().split('\n')[0].trim(); + return line || null; + }); +} + +function detectBackends() { + return L.resolveDefault(fs.exec('/usr/libexec/v2raya-detect', []), null).then(function (res) { + var info = {}; + + if (!res || res.code !== 0) + return info; + + (res.stdout || '').split('\n').forEach(function (line) { + var pos = line.indexOf('='); + if (pos > 0) + info[line.substring(0, pos)] = line.substring(pos + 1); + }); + + return info; + }); +} + +function backendRow(label, path, version, packageFlag, note) { + var field = []; + + if (path) { + field.push(E('strong', {}, _('Detected'))); + field.push(' — ', E('code', {}, path)); + if (version) + field.push(E('div', { class: 'cbi-section-descr' }, version)); + } + else { + field.push(E('span', { style: 'color:#777' }, _('Not installed'))); + } + + if (packageFlag === '1') + field.push(E('div', { class: 'cbi-section-descr' }, _('Package installed'))); + + if (note) + field.push(E('div', { class: 'cbi-section-descr' }, note)); + + return E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, label), + E('div', { class: 'cbi-value-field' }, field) + ]); +} + +function parseListenAddress(address) { + var match = String(address || '').match(/^\[?([^\]]*)\]?:(\d+)$/); + var host = match ? match[1] : ''; + var port = match ? match[2] : '2017'; + + if (!host || host === '0.0.0.0' || host === '::') + host = window.location.hostname; + + if (host.indexOf(':') !== -1) + host = '[' + host + ']'; + + return { host: host, port: port }; +} + +function renderStatus(running) { + var el = document.getElementById('v2raya-status'); + if (!el) + return; + + el.textContent = running ? _('Running') : _('Stopped'); + el.style.color = running ? '#2e7d32' : '#c62828'; +} + +function setBusy(button, busy) { + if (!button) + return; + button.disabled = busy; +} + +function runAction(action, button) { + setBusy(button, true); + + return L.resolveDefault(fs.exec('/etc/init.d/v2raya', [ action ]), null).then(function (res) { + setBusy(button, false); + + if (!res || res.code !== 0) { + ui.addNotification(null, E('p', {}, + (res && (res.stderr || res.stdout)) || _('The service command failed.')), 'danger'); + return; + } + + ui.addNotification(null, E('p', {}, _('Service action "%s" completed.').format(action)), 'info'); + return serviceRunning().then(renderStatus); + }).catch(function (err) { + setBusy(button, false); + ui.addNotification(null, E('p', {}, _('Unable to run the service command: %s').format(err)), 'danger'); + }); +} + +function startService(button) { + uci.set('v2raya', 'config', 'enabled', '1'); + + return L.resolveDefault(uci.save(), null).then(function () { + return runAction('start', button); + }).then(function () { + /* + * Persist enabled=1 so the service also starts after a reboot. + * The resulting config.change event may cause one more restart; + * that is expected after an apply and the page polls the state. + */ + return L.resolveDefault(uci.apply(), null); + }); +} + +return view.extend({ + load: function () { + return Promise.all([ + uci.load('v2raya'), + commandVersion('/usr/bin/v2raya', [ '--version' ]), + commandVersion('/usr/bin/v2raya_core', [ 'version' ]), + detectBackends() + ]); + }, + + render: function (data) { + var address = uci.get('v2raya', 'config', 'address') || '0.0.0.0:2017'; + var baseurl = uci.get('v2raya', 'config', 'baseurl') || '/'; + if (baseurl === '') + baseurl = '/'; + var listen = parseListenAddress(address); + var weburl = 'http://' + listen.host + ':' + listen.port + + (baseurl === '/' ? '/' : '/' + baseurl.replace(/^\/+|\/+$/g, '') + '/'); + + var serviceVersion = data[1] || _('not detected'); + var coreVersion = data[2] || _('not detected'); + var backends = data[3] || {}; + var detectAvailable = Object.keys(backends).length > 0; + var enabledServices = (backends.services_enabled || '').split(',').filter(function (name) { + return name && name !== 'v2raya'; + }); + + var backendChildren = [ + E('h3', {}, _('Detected cores and proxy services')), + E('p', { class: 'cbi-section-descr' }, + _('v2rayA 2.5.x runs only its bundled v2raya_core. Existing Xray-core, v2ray and sing-box installations are detected here so they are not duplicated or replaced; they are never used as the v2rayA backend.')) + ]; + + if (detectAvailable) { + backendChildren.push( + backendRow(_('v2raya_core (required)'), backends.core_path, backends.core_version, null, + _('This is the only core v2rayA 2.5.x supports.')), + backendRow(_('Xray-core'), backends.xray_path, backends.xray_version, backends.xray_package, + _('Left untouched. Do not point the core binary setting at this.')), + backendRow(_('v2ray-core'), backends.v2ray_path, backends.v2ray_version, backends.v2ray_package, + _('Left untouched. Do not point the core binary setting at this.')), + backendRow(_('sing-box'), backends.singbox_path, backends.singbox_version, backends.singbox_package, + _('Left untouched. sing-box uses a different configuration format and cannot run v2rayA configs.')), + E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, _('Rule data')), + E('div', { class: 'cbi-value-field' }, + (backends.geoip_present === '1' && backends.geosite_present === '1') + ? _('geoip.dat and geosite.dat are installed') + : _('Missing. v2rayA downloads the data on first start and needs internet access; install v2raya-geoip and v2raya-geosite for offline use.')) + ]), + E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, _('Other proxy services')), + E('div', { class: 'cbi-value-field' }, + backends.services_present ? backends.services_present.split(',').join(', ') : _('None detected')) + ]) + ); + } + else { + backendChildren.push(E('p', { class: 'cbi-section-descr' }, + _('Core and service detection is unavailable. The installed v2raya package does not provide the detection helper; version 2.5.x and later install it.'))); + } + + if (detectAvailable && !backends.core_path) + backendChildren.push(E('div', { class: 'alert-message warning' }, + _('v2raya_core is missing. Install the v2raya-core package.'))); + + if (detectAvailable && backends.old_v2raya_package === '1') + backendChildren.push(E('div', { class: 'alert-message warning' }, + _('An older v2raya package is still installed. Remove it to avoid duplicate services.'))); + + if (detectAvailable && enabledServices.length) + backendChildren.push(E('div', { class: 'alert-message warning' }, + _('These proxy services are enabled at boot: %s. Run only one transparent-proxy stack, otherwise duplicate firewall rules will conflict.').format(enabledServices.join(', ')))); + + var status = E('span', { id: 'v2raya-status', style: 'font-weight:bold' }, _('Checking…')); + + var startBtn = E('button', { class: 'btn cbi-button cbi-button-apply' }, _('Start')); + var stopBtn = E('button', { class: 'btn cbi-button cbi-button-reset' }, _('Stop')); + var restartBtn = E('button', { class: 'btn cbi-button cbi-button-action' }, _('Restart')); + + startBtn.addEventListener('click', function () { startService(startBtn); }); + stopBtn.addEventListener('click', function () { runAction('stop', stopBtn); }); + restartBtn.addEventListener('click', function () { runAction('restart', restartBtn); }); + + var refresh = function () { + return serviceRunning().then(renderStatus); + }; + + refresh(); + poll.add(refresh, 5); + + return E('div', { class: 'cbi-map' }, [ + E('h2', {}, _('v2rayA')), + E('div', { class: 'cbi-map-descr' }, [ + _('A web client for its own Xray-based core, with transparent proxy support.'), + ' ', + E('a', { href: weburl, target: '_blank', rel: 'noreferrer noopener' }, weburl) + ]), + E('div', { class: 'cbi-section' }, [ + E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, _('Service')), + E('div', { class: 'cbi-value-field' }, status) + ]), + E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, _('v2rayA version')), + E('div', { class: 'cbi-value-field' }, serviceVersion) + ]), + E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, _('v2raya_core version')), + E('div', { class: 'cbi-value-field' }, coreVersion) + ]), + E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, _('Listening address')), + E('div', { class: 'cbi-value-field' }, address) + ]), + E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, _('Web interface')), + E('div', { class: 'cbi-value-field' }, + E('a', { href: weburl, target: '_blank', rel: 'noreferrer noopener' }, _('Open v2rayA'))) + ]) + ]), + E('div', { class: 'cbi-section' }, backendChildren), + E('div', { class: 'cbi-section' }, [ + E('div', { class: 'cbi-value' }, [ + E('label', { class: 'cbi-value-title' }, _('Actions')), + E('div', { class: 'cbi-value-field' }, [ startBtn, ' ', stopBtn, ' ', restartBtn ]) + ]), + E('p', { class: 'cbi-section-descr' }, + _('These buttons control the background service. Connecting the proxy is done in the v2rayA web UI with the Connect button.')) + ]), + E('div', { class: 'cbi-section' }, [ + E('p', { class: 'cbi-section-descr' }, + _('This independent, community-run packaging project is not affiliated with the upstream v2rayA authors. It is AI-assisted and human-maintained.')) + ]) + ]); + }, + + handleSave: null, + handleSaveApply: null, + handleReset: null +}); diff --git a/luci-app-v2raya/po/templates/v2raya.pot b/luci-app-v2raya/po/templates/v2raya.pot new file mode 100644 index 000000000..c51fc27c6 --- /dev/null +++ b/luci-app-v2raya/po/templates/v2raya.pot @@ -0,0 +1,163 @@ +msgid "" +msgstr "" +"Project-Id-Version: luci-app-v2raya\n" +"Report-Msgid-Bugs-To: https://github.com/AmrFateem/luci-V2raya-NextGen/issues\n" +"POT-Creation-Date: 2026-11-07 00:00+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "v2rayA" +msgstr "" + +msgid "Overview" +msgstr "" + +msgid "Settings" +msgstr "" + +msgid "Log" +msgstr "" + +msgid "Running" +msgstr "" + +msgid "Stopped" +msgstr "" + +msgid "Checking…" +msgstr "" + +msgid "Start" +msgstr "" + +msgid "Stop" +msgstr "" + +msgid "Restart" +msgstr "" + +msgid "Service" +msgstr "" + +msgid "v2rayA version" +msgstr "" + +msgid "v2raya_core version" +msgstr "" + +msgid "Listening address" +msgstr "" + +msgid "Web interface" +msgstr "" + +msgid "Open v2rayA" +msgstr "" + +msgid "Actions" +msgstr "" + +msgid "Enable service" +msgstr "" + +msgid "General" +msgstr "" + +msgid "Transparent proxy" +msgstr "" + +msgid "Logging" +msgstr "" + +msgid "Advanced" +msgstr "" + +msgid "IPv6 support" +msgstr "" + +msgid "nftables support" +msgstr "" + +msgid "Auto" +msgstr "" + +msgid "On" +msgstr "" + +msgid "Off" +msgstr "" + +msgid "Log level" +msgstr "" + +msgid "Trace" +msgstr "" + +msgid "Debug" +msgstr "" + +msgid "Info" +msgstr "" + +msgid "Warning" +msgstr "" + +msgid "Error" +msgstr "" + +msgid "Keep logs for" +msgstr "" + +msgid "Clear log" +msgstr "" + +msgid "Refresh" +msgstr "" + +msgid "The log is empty." +msgstr "" + +msgid "The log file does not exist yet. Start the service and try again." +msgstr "" + +msgid "v2rayA log" +msgstr "" + +msgid "Settings for the v2rayA service. Save & Apply restarts the service so the new values take effect. v2rayA 2.5.x uses only its bundled v2raya_core; Xray-core and sing-box are not supported backends." +msgstr "" + +msgid "This independent, community-run packaging project is not affiliated with the upstream v2rayA authors. It is AI-assisted and human-maintained." +msgstr "" + +msgid "Warning keeps flash writes down. Use Info while debugging." +msgstr "" + +msgid "Go memory limit" +msgstr "" + +msgid "Soft memory limit for both v2raya and v2raya_core (GOMEMLIMIT). Use 0 to disable, or 96MiB on a larger router." +msgstr "" + +msgid "Go scheduler threads" +msgstr "" + +msgid "GOMAXPROCS. 0 uses every CPU core; 2 is enough for most routers." +msgstr "" + +msgid "Go GC percent" +msgstr "" + +msgid "GOGC. 0 keeps the Go default; lower values use less memory and more CPU." +msgstr "" + +msgid "Open file limit" +msgstr "" + +msgid "Maximum open file descriptors. 16384 is plenty for a home router and bounds a connection flood." +msgstr "" diff --git a/luci-app-v2raya/root/usr/share/luci/menu.d/luci-app-v2raya.json b/luci-app-v2raya/root/usr/share/luci/menu.d/luci-app-v2raya.json new file mode 100644 index 000000000..1240aabdc --- /dev/null +++ b/luci-app-v2raya/root/usr/share/luci/menu.d/luci-app-v2raya.json @@ -0,0 +1,37 @@ +{ + "admin/services/v2raya": { + "title": "v2rayA", + "order": 60, + "action": { + "type": "firstchild" + }, + "depends": { + "acl": [ "luci-app-v2raya" ], + "uci": { "v2raya": true } + } + }, + "admin/services/v2raya/overview": { + "title": "Overview", + "order": 10, + "action": { + "type": "view", + "path": "v2raya/overview" + } + }, + "admin/services/v2raya/config": { + "title": "Settings", + "order": 20, + "action": { + "type": "view", + "path": "v2raya/config" + } + }, + "admin/services/v2raya/log": { + "title": "Log", + "order": 30, + "action": { + "type": "view", + "path": "v2raya/log" + } + } +} diff --git a/luci-app-v2raya/root/usr/share/rpcd/acl.d/luci-app-v2raya.json b/luci-app-v2raya/root/usr/share/rpcd/acl.d/luci-app-v2raya.json new file mode 100644 index 000000000..63029ff78 --- /dev/null +++ b/luci-app-v2raya/root/usr/share/rpcd/acl.d/luci-app-v2raya.json @@ -0,0 +1,25 @@ +{ + "luci-app-v2raya": { + "description": "Grant access to the v2rayA service and configuration", + "read": { + "file": { + "/etc/init.d/v2raya": [ "exec" ], + "/usr/bin/v2raya": [ "exec" ], + "/usr/bin/v2raya_core": [ "exec" ], + "/usr/libexec/v2raya-detect": [ "exec" ], + "/var/log/v2raya/v2raya.log": [ "read" ] + }, + "ubus": { + "service": [ "list" ] + }, + "uci": [ "v2raya" ] + }, + "write": { + "file": { + "/etc/init.d/v2raya": [ "exec" ], + "/var/log/v2raya/v2raya.log": [ "write" ] + }, + "uci": [ "v2raya" ] + } + } +} diff --git a/openwrt-natmapt/Makefile b/openwrt-natmapt/Makefile index 78fa38dde..cc387d785 100644 --- a/openwrt-natmapt/Makefile +++ b/openwrt-natmapt/Makefile @@ -6,10 +6,10 @@ include $(TOPDIR)/rules.mk PKG_UPSTREAM_NAME:=natmap PKG_NAME:=natmapt -PKG_UPSTREAM_VERSION:=20260214 +PKG_UPSTREAM_VERSION:=20261109 PKG_UPSTREAM_GITHASH:= PKG_VERSION:=$(PKG_UPSTREAM_VERSION)$(if $(PKG_UPSTREAM_GITHASH),~$(call version_abbrev,$(PKG_UPSTREAM_GITHASH))) -PKG_RELEASE:=19 +PKG_RELEASE:=20 SCRIPTS_VERSION:=0.2026.01.24 PKG_SOURCE_SUBDIR:=$(PKG_UPSTREAM_NAME)-$(PKG_UPSTREAM_VERSION) diff --git a/v2raya-core/Makefile b/v2raya-core/Makefile new file mode 100644 index 000000000..3710b2b2a --- /dev/null +++ b/v2raya-core/Makefile @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# Copyright (C) 2021 ImmortalWrt.org +# Copyright (C) 2026 Amr Fateem +# +# v2raya_core for OpenWrt. This is the merged xray-core based binary from +# the v2rayA 2.5.x tree. It is built from the same source tarball as the +# service and must always carry the same version. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=v2raya-core +PKG_VERSION:=2.5.14 +PKG_RELEASE:=1 + +PKG_SOURCE:=v2rayA-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)? +PKG_HASH:=skip + +PKG_BUILD_DIR:=$(BUILD_DIR)/v2rayA-core-$(PKG_VERSION)/core + +PKG_LICENSE:=MPL-2.0 +PKG_LICENSE_FILES:=../LICENSE +PKG_MAINTAINER:=Amr Fateem + +PKG_BUILD_DEPENDS:=golang/host +PKG_BUILD_PARALLEL:=1 +PKG_BUILD_FLAGS:=no-mips16 + +# Keep this in sync with v2raya/Makefile. +V2RAYA_GO_TOOLCHAIN?=go1.27.3+auto + +GO_PKG:=github.com/v2rayA/v2raya-core +GO_PKG_BUILD_PKG:=github.com/v2rayA/v2raya-core/main +GO_PKG_LDFLAGS_X:=main.Version=$(PKG_VERSION) + +include $(INCLUDE_DIR)/package.mk +include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk + +# The core is pure Go. CGO stays disabled on every target, including mips: +# this is the same configuration upstream uses for its release binaries and +# it keeps the binary self-contained. +GO_PKG_TARGET_VARS:=$(filter-out CGO_ENABLED=%,$(GO_PKG_TARGET_VARS)) CGO_ENABLED=0 +GO_PKG_DEFAULT_LDFLAGS:=$(filter-out -linkmode external,$(GO_PKG_DEFAULT_LDFLAGS)) +GO_PKG_BUILD_VARS:=$(filter-out GOTOOLCHAIN=%,$(GO_PKG_BUILD_VARS)) GOTOOLCHAIN=$(V2RAYA_GO_TOOLCHAIN) + +TAR_OPTIONS+= --strip-components 1 +TAR_CMD=$(HOST_TAR) -C $(1)/.. $(TAR_OPTIONS) + +define Package/v2raya-core + TITLE:=v2raya_core - the merged xray-core used by v2rayA + SECTION:=net + CATEGORY:=Network + SUBMENU:=Web Servers/Proxies + DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle + CONFLICTS:=v2raya-core-nextgen + URL:=https://github.com/v2rayA/v2rayA +endef + +define Package/v2raya-core/description + v2raya_core combines xray-core with the MultiObservatory support and the + extra protocols required by v2rayA 2.5.x. The service and the core must + always be installed at the same version. +endef + +define Package/v2raya-core/install + $(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR)) + + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/main $(1)/usr/bin/v2raya_core +endef + +$(eval $(call GoBinPackage,v2raya-core)) +$(eval $(call BuildPackage,v2raya-core)) diff --git a/v2raya-core/test.sh b/v2raya-core/test.sh new file mode 100644 index 000000000..81243588d --- /dev/null +++ b/v2raya-core/test.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-only +# +# Copyright (C) 2026 Amr Fateem + +case "$1" in +v2raya-core) + /usr/bin/v2raya_core version | grep -F "$2" + ;; +*) + exit 1 + ;; +esac diff --git a/v2raya-geoip/Makefile b/v2raya-geoip/Makefile new file mode 100644 index 000000000..e4ca71ebd --- /dev/null +++ b/v2raya-geoip/Makefile @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# Copyright (C) 2021 ImmortalWrt.org +# Copyright (C) 2026 Amr Fateem +# +# GeoIP database used by v2rayA. The package is architecture independent and +# is updated independently of the v2rayA release by scripts/update-version.sh. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=v2raya-geoip +PKG_VERSION:=202611050539 +PKG_RELEASE:=1 + +PKG_LICENSE:=CC-BY-SA-4.0 +PKG_MAINTAINER:=Amr Fateem + +include $(INCLUDE_DIR)/package.mk + +GEOIP_FILE:=geoip.dat.$(PKG_VERSION) +GEOIP_HASH:=eedb24b3b9fcba27e06b92c0428dab90d9496083091ae4cf39e11387fd99d0e2 + +define Download/geoip + URL:=https://github.com/v2fly/geoip/releases/download/$(PKG_VERSION)/ + URL_FILE:=geoip.dat + FILE:=$(GEOIP_FILE) + HASH:=skip +endef + +define Package/v2raya-geoip + TITLE:=GeoIP database for v2rayA + SECTION:=net + CATEGORY:=Network + PKGARCH:=all + URL:=https://github.com/v2fly/geoip +endef + +define Package/v2raya-geoip/description + GeoIP database (geoip.dat) used by v2rayA and v2raya_core for routing. +endef + +define Package/v2raya-geoip/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + if [ -x /etc/init.d/v2raya ] && /etc/init.d/v2raya enabled; then + /etc/init.d/v2raya restart + fi +} +endef + +define Build/Prepare + $(call Build/Prepare/Default) + $(call Download,geoip) +endef + +define Build/Compile +endef + +define Package/v2raya-geoip/install + $(INSTALL_DIR) $(1)/usr/share/v2raya + $(INSTALL_DATA) $(DL_DIR)/$(GEOIP_FILE) $(1)/usr/share/v2raya/geoip.dat +endef + +$(eval $(call Download,geoip)) +$(eval $(call BuildPackage,v2raya-geoip)) diff --git a/v2raya-geosite/Makefile b/v2raya-geosite/Makefile new file mode 100644 index 000000000..4749b0c65 --- /dev/null +++ b/v2raya-geosite/Makefile @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# Copyright (C) 2021 ImmortalWrt.org +# Copyright (C) 2026 Amr Fateem +# +# GeoSite database used by v2rayA. The package is architecture independent +# and is updated independently of the v2rayA release by +# scripts/update-version.sh. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=v2raya-geosite +PKG_VERSION:=20261107053621 +PKG_RELEASE:=1 + +PKG_LICENSE:=MIT +PKG_MAINTAINER:=Amr Fateem + +include $(INCLUDE_DIR)/package.mk + +GEOSITE_FILE:=dlc.dat.$(PKG_VERSION) +GEOSITE_HASH:=f110a7b96b3e3ed9c19cf18ae0ac5f8c46de54fa778863609c0a3b8f1738b58c + +define Download/geosite + URL:=https://github.com/v2fly/domain-list-community/releases/download/$(PKG_VERSION)/ + URL_FILE:=dlc.dat + FILE:=$(GEOSITE_FILE) + HASH:=skip +endef + +define Package/v2raya-geosite + TITLE:=GeoSite database for v2rayA + SECTION:=net + CATEGORY:=Network + PKGARCH:=all + URL:=https://github.com/v2fly/domain-list-community +endef + +define Package/v2raya-geosite/description + GeoSite database (geosite.dat) used by v2rayA and v2raya_core for routing. +endef + +define Package/v2raya-geosite/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + if [ -x /etc/init.d/v2raya ] && /etc/init.d/v2raya enabled; then + /etc/init.d/v2raya restart + fi +} +endef + +define Build/Prepare + $(call Build/Prepare/Default) + $(call Download,geosite) +endef + +define Build/Compile +endef + +define Package/v2raya-geosite/install + $(INSTALL_DIR) $(1)/usr/share/v2raya + $(INSTALL_DATA) $(DL_DIR)/$(GEOSITE_FILE) $(1)/usr/share/v2raya/geosite.dat +endef + +$(eval $(call Download,geosite)) +$(eval $(call BuildPackage,v2raya-geosite))