update 2026-10-14 17:13:24

This commit is contained in:
action committed 2026-10-14 17:13:24 +08:00
1 parent 79897976d6
commit e75bfefc80
345 files changed
+1076 -91524

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
PKG_NAME:=easytier-noweb
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.4)
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.7.0)
ifeq ($(ARCH),mipsel)
APP_ARCH:=mipsel
+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
PKG_NAME:=easytier
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.4)
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.7.0)
ifeq ($(ARCH),mipsel)
APP_ARCH:=mipsel
+3 -1
View File
@@ -7,9 +7,11 @@
include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.4)
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.7.0)
PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION)
LUCI_TITLE:=LuCI support for EasyTier
LUCI_DEPENDS:=+kmod-tun +luci-compat
LUCI_PKGARCH:=all
@@ -163,7 +163,7 @@ function get_upload_config()
easytierbin = uci:get_first("easytier", "easytier", "easytierbin") or "/usr/bin/easytier-core",
webbin = uci:get_first("easytier", "easytier", "webbin") or "/usr/bin/easytier-web",
github_proxys = {},
fallback_version = uci:get_first("easytier", "easytier", "fallback_version") or "v2.6.4"
fallback_version = uci:get_first("easytier", "easytier", "fallback_version") or "v2.7.0"
}
-- 读取代理列表(list 类型)
@@ -200,7 +200,7 @@ function save_upload_config()
-- 保存配置
uci:set("easytier", "@easytier[0]", "easytierbin", data.easytierbin or "/usr/bin/easytier-core")
uci:set("easytier", "@easytier[0]", "webbin", data.webbin or "/usr/bin/easytier-web")
uci:set("easytier", "@easytier[0]", "fallback_version", data.fallback_version or "v2.6.4")
uci:set("easytier", "@easytier[0]", "fallback_version", data.fallback_version or "v2.7.0")
-- 删除旧的代理列表
uci:delete("easytier", "@easytier[0]", "github_proxys")
@@ -595,69 +595,130 @@ function clear_version_cache()
luci.http.write("OK")
end
-- 连接信息各分区与 easytier-cli 子命令的对应关系。
-- 实测每调用一次 easytier-cli 都要新起一个进程,单次进程启动开销约 200ms(路由器上更慢),
-- RPC 往返本身只要几毫秒,因此耗时几乎完全取决于进程个数,改为按分区按需加载。
local CONNINFO_CLI_PARTS = {
node = "node",
peer = "peer",
route = "route",
connector = "connector",
stun = "stun",
peer_center = "peer-center",
vpn_portal = "vpn-portal",
proxy = "proxy",
acl = "acl stats",
mapped_listener = "mapped-listener",
stats = "stats",
}
-- cmdline 只读本地 /proc,不需要 easytier-cli
local CONNINFO_LOCAL_PARTS = { cmdline = true }
-- 分区结果的短时缓存,避免轮询与手动刷新、来回切换分区时重复起进程
local CONNINFO_CACHE_DIR = "/tmp/easytier_conninfo"
local CONNINFO_CACHE_TTL = 3
local function read_conninfo_cache(name)
local handle = io.open(CONNINFO_CACHE_DIR .. "/" .. name, "r")
if not handle then
return nil
end
local content = handle:read("*all") or ""
handle:close()
local ts, data = content:match("^(%d+)\n([%s%S]*)$")
if not ts or (os.time() - tonumber(ts)) > CONNINFO_CACHE_TTL then
return nil
end
return data
end
local function write_conninfo_cache(name, data)
luci.sys.call("mkdir -p " .. CONNINFO_CACHE_DIR)
local handle = io.open(CONNINFO_CACHE_DIR .. "/" .. name, "w")
if handle then
handle:write(tostring(os.time()) .. "\n" .. data)
handle:close()
end
end
function act_conninfo()
local e = {}
local uci = require "luci.model.uci".cursor()
local easytierbin = uci:get_first("easytier", "easytier", "easytierbin") or "/usr/bin/easytier-core"
local clibin = easytierbin:gsub("easytier%-core$", "easytier-cli")
local process_status = luci.sys.exec("pgrep easytier-core")
if process_status ~= "" then
-- 获取各类CLI信息
local function get_cli_output(cmd)
local handle = io.popen(clibin .. " " .. cmd .. " 2>&1")
if handle then
local result = handle:read("*all")
handle:close()
return result or ""
-- 解析要加载的分区,前端只请求当前显示的分区;
-- 不传 parts 时加载除 stun 以外的所有分区(stun 会实时访问 STUN 服务器,单次实测约 3 秒)
local parts = luci.http.formvalue("parts")
local want_cli, want_local = {}, {}
if parts and parts ~= "" then
for name in parts:gmatch("[%w_]+") do
if CONNINFO_CLI_PARTS[name] then
want_cli[name] = true
elseif CONNINFO_LOCAL_PARTS[name] then
want_local[name] = true
end
return ""
end
e.node = get_cli_output("node")
e.peer = get_cli_output("peer")
e.connector = get_cli_output("connector")
e.stun = get_cli_output("stun")
e.route = get_cli_output("route")
e.peer_center = get_cli_output("peer-center")
e.vpn_portal = get_cli_output("vpn-portal")
e.proxy = get_cli_output("proxy")
e.acl = get_cli_output("acl stats")
e.mapped_listener = get_cli_output("mapped-listener")
e.stats = get_cli_output("stats")
-- 获取启动参数
local cmdhandle = io.popen("cat /proc/$(pidof easytier-core)/cmdline 2>/dev/null | tr '\\0' ' '")
if cmdhandle then
e.cmdline = cmdhandle:read("*all") or ""
cmdhandle:close()
else
e.cmdline = ""
else
for name in pairs(CONNINFO_CLI_PARTS) do
if name ~= "stun" then
want_cli[name] = true
end
end
-- 检查是否使用配置文件启动
if e.cmdline:match("%-%-config%-file") or e.cmdline:match("%-c%s+/") then
e.config_file = safe_read_file("/etc/easytier/config.toml") or ""
else
e.config_file = ""
for name in pairs(CONNINFO_LOCAL_PARTS) do
want_local[name] = true
end
end
local process_status = luci.sys.exec("pgrep easytier-core")
if process_status ~= "" then
-- 只运行被请求的分区,命中缓存的分区直接复用结果
for name in pairs(want_cli) do
local cached = read_conninfo_cache(name)
if cached then
e[name] = cached
else
local handle = io.popen(clibin .. " " .. CONNINFO_CLI_PARTS[name] .. " 2>&1")
local result = ""
if handle then
result = handle:read("*all") or ""
handle:close()
end
e[name] = result
write_conninfo_cache(name, result)
end
end
if want_local.cmdline then
-- 获取启动参数
local cmdhandle = io.popen("cat /proc/$(pidof easytier-core)/cmdline 2>/dev/null | tr '\\0' ' '")
if cmdhandle then
e.cmdline = cmdhandle:read("*all") or ""
cmdhandle:close()
else
e.cmdline = ""
end
-- 检查是否使用配置文件启动,配置内容随本次响应一起返回,前端无需再请求一次
if e.cmdline:match("%-%-config%-file") or e.cmdline:match("%-c%s+/") then
e.config_file = safe_read_file("/etc/easytier/config.toml") or ""
else
e.config_file = ""
end
end
else
local errMsg = i18n.translate("Error: Program not running! Please start the program and refresh")
e.node = errMsg
e.peer = errMsg
e.connector = errMsg
e.stun = errMsg
e.route = errMsg
e.peer_center = errMsg
e.vpn_portal = errMsg
e.proxy = errMsg
e.acl = errMsg
e.mapped_listener = errMsg
e.stats = errMsg
e.cmdline = errMsg
for name in pairs(want_cli) do
e[name] = errMsg
end
if want_local.cmdline then
e.cmdline = errMsg
end
-- 未运行时清掉缓存,避免下次启动后读到旧的错误输出
luci.sys.call("rm -rf " .. CONNINFO_CACHE_DIR)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
@@ -682,6 +743,11 @@ function get_web_config()
web_geoip_db = uci:get_first("easytier", "easytier", "web_geoip_db") or "",
web_disable_registration = uci:get_first("easytier", "easytier", "web_disable_registration") or "0",
web_allow_auto_create_user = uci:get_first("easytier", "easytier", "web_allow_auto_create_user") or "0",
-- EasyTier 2.7.0 新增的 web 参数
web_heartbeat_min_response_ms = uci:get_first("easytier", "easytier", "web_heartbeat_min_response_ms") or "",
web_heartbeat_timeout_ms = uci:get_first("easytier", "easytier", "web_heartbeat_timeout_ms") or "",
web_gateway_peer_url = uci:get_first("easytier", "easytier", "web_gateway_peer_url") or "",
web_gateway_relay_data = uci:get_first("easytier", "easytier", "web_gateway_relay_data") or "0",
web_weblog = uci:get_first("easytier", "easytier", "web_weblog") or "off",
-- OIDC配置
web_oidc_enabled = uci:get_first("easytier", "easytier", "web_oidc_enabled") or "0",
@@ -723,6 +789,11 @@ function save_web_config()
local web_geoip_db = http.formvalue("web_geoip_db") or ""
local web_disable_registration = http.formvalue("web_disable_registration") or "0"
local web_allow_auto_create_user = http.formvalue("web_allow_auto_create_user") or "0"
-- EasyTier 2.7.0 新增的 web 参数
local web_heartbeat_min_response_ms = http.formvalue("web_heartbeat_min_response_ms") or ""
local web_heartbeat_timeout_ms = http.formvalue("web_heartbeat_timeout_ms") or ""
local web_gateway_peer_url = http.formvalue("web_gateway_peer_url") or ""
local web_gateway_relay_data = http.formvalue("web_gateway_relay_data") or "0"
local web_weblog = http.formvalue("web_weblog") or "off"
-- OIDC
local web_oidc_enabled = http.formvalue("web_oidc_enabled") or "0"
@@ -756,6 +827,11 @@ function save_web_config()
uci:set("easytier", "@easytier[0]", "web_geoip_db", web_geoip_db)
uci:set("easytier", "@easytier[0]", "web_disable_registration", web_disable_registration)
uci:set("easytier", "@easytier[0]", "web_allow_auto_create_user", web_allow_auto_create_user)
-- EasyTier 2.7.0 新增的 web 参数
uci:set("easytier", "@easytier[0]", "web_heartbeat_min_response_ms", web_heartbeat_min_response_ms)
uci:set("easytier", "@easytier[0]", "web_heartbeat_timeout_ms", web_heartbeat_timeout_ms)
uci:set("easytier", "@easytier[0]", "web_gateway_peer_url", web_gateway_peer_url)
uci:set("easytier", "@easytier[0]", "web_gateway_relay_data", web_gateway_relay_data)
uci:set("easytier", "@easytier[0]", "web_weblog", web_weblog)
-- OIDC
uci:set("easytier", "@easytier[0]", "web_oidc_enabled", web_oidc_enabled)
@@ -45,10 +45,11 @@ end
web_config = s:taboption("general", Value, "web_config", translate("Web Server Address"),
translate("Web configuration server address. (-w parameter)<br>"
.. "For a self-hosted Web server, use the format: udp://server_address:22020/username<br>"
.. "For the official Web server, use the format: username<br>"
.. "Since EasyTier 2.7.0 a full URL is required, the token-only shorthand is no longer supported<br>"
.. "Self-hosted Web server, use the format: udp://server_address:22020/username<br>"
.. "Official Web server, use the format: udp://config-server.easytier.cn:22020/username<br>"
.. "Official Web Console: <a href='https://easytier.cn/web'>easytier.cn/web</a>"))
web_config.placeholder = "admin"
web_config.placeholder = "udp://config-server.easytier.cn:22020/my-token"
web_config:depends("etcmd", "web")
network_name = s:taboption("general", Value, "network_name", translate("Network Name"),
@@ -199,13 +200,53 @@ instance_name = s:taboption("privacy", Value, "instance_name", translate("Instan
instance_name.placeholder = "default"
instance_name:depends("etcmd", "etcmd")
vpn_portal = s:taboption("privacy", Value, "vpn_portal", translate("VPN Portal URL"),
translate("Defines the URL of the VPN portal, allowing other VPN clients to connect.<br>"
.. "Example: wg://0.0.0.0:11011/10.14.14.0/24 means the VPN portal is a WireGuard server listening on vpn."
.. "example.com:11010, and the VPN clients are in the 10.14.14.0/24 network (--vpn-portal parameter)"))
vpn_portal.placeholder = "wg://0.0.0.0:11011/10.14.14.0/24"
vpn_portal = s:taboption("privacy", Value, "vpn_portal", translate("VPN Portal Listen Address"),
translate("WireGuard VPN portal listen address, allowing other VPN clients to connect.<br>"
.. "Since EasyTier 2.7.0 only wg://HOST:PORT is accepted; the client network can no longer be "
.. "appended to the URL, please add each client in \"VPN Portal Clients\" below "
.. "(--vpn-portal parameter)"))
vpn_portal.placeholder = "wg://0.0.0.0:11011"
vpn_portal.validate = function(self, value, section)
value = value and tostring(value):match("^%s*(.-)%s*$") or ""
if value == "" then return value end
-- EasyTier 2.7.0 起不再允许把客户端网段写在 URL 里,这里提前拦截,避免核心启动失败
if not (value:match("^wg://%d+%.%d+%.%d+%.%d+:%d+$") or value:match("^wg://%[[%x:]+%]:%d+$")) then
return nil, translate("Only wg://HOST:PORT is accepted since EasyTier 2.7.0, please fill the client network into \"VPN Portal Clients\"")
end
return value
end
vpn_portal:depends("etcmd", "etcmd")
vpn_portal_clients = s:taboption("privacy", DynamicList, "vpn_portal_clients", translate("VPN Portal Clients"),
translate("One WireGuard client per entry in the form NAME=CIDR, for example client1=10.14.14.1/24.<br>"
.. "NAME may contain letters, digits and dashes only; the address must be a usable host address "
.. "inside the client network. (--vpn-portal-client parameter)"))
vpn_portal_clients.placeholder = "client1=10.14.14.1/24"
vpn_portal_clients.validate = function(self, value, section)
if type(value) == "table" then
local i
for i = 1, #value do
local item = value[i] and tostring(value[i]):match("^%s*(.-)%s*$") or ""
if item ~= "" then
-- 与 easytier-core 的校验保持一致:名称只能是字母数字与短横线,地址必须是 主机/前缀
if not item:match("^[%w][%w%-]*=[%d%.]+/%d+$") then
return nil, translate("Invalid client, expected NAME=CIDR, for example client1=10.14.14.1/24")
end
end
end
end
return value
end
vpn_portal_clients:depends("etcmd", "etcmd")
vpn_portal_private_key = s:taboption("privacy", Value, "vpn_portal_private_key", translate("VPN Portal Private Key"),
translate("Base64 encoded 32-byte WireGuard private key of the portal.<br>"
.. "Required since EasyTier 2.7.0, the portal will not start without it. "
.. "It is handed to easytier-core through an environment variable so that it never shows up in the process list. "
.. "(--vpn-portal-private-key parameter)"))
vpn_portal_private_key.password = true
vpn_portal_private_key:depends("etcmd", "etcmd")
mtu = s:taboption("privacy", Value, "mtu", translate("MTU"),
translate("MTU for the TUN device, default is 1380 when unencrypted, and 1360 when encrypted"))
mtu.datatype = "range(1,1500)"
@@ -314,6 +355,23 @@ socks_port.datatype = "range(1,65535)"
socks_port.placeholder = "1080"
socks_port:depends("etcmd", "etcmd")
disable_upnp = s:taboption("privacy", Flag, "disable_upnp", translate("Disable UPnP"),
translate("EasyTier 2.7.0 tries to map listener ports through UPnP/NAT-PMP by default. "
.. "Enable this to turn the automatic port mapping off. (--disable-upnp parameter)"))
disable_upnp:depends("etcmd", "etcmd")
stun_servers = s:taboption("privacy", DynamicList, "stun_servers", translate("STUN Servers"),
translate("Custom UDP STUN servers used for NAT type detection and hole punching. "
.. "Leave empty to use the built-in defaults. (--stun-servers parameter)"))
stun_servers.placeholder = "stun.miwifi.com:3478"
stun_servers:depends("etcmd", "etcmd")
tcp_stun_servers = s:taboption("privacy", DynamicList, "tcp_stun_servers", translate("STUN Servers (TCP)"),
translate("Custom TCP STUN servers used for NAT type detection and hole punching. "
.. "Leave empty to use the built-in defaults. (--tcp-stun-servers parameter)"))
tcp_stun_servers.placeholder = "stun.miwifi.com:3478"
tcp_stun_servers:depends("etcmd", "etcmd")
disable_p2p = s:taboption("privacy", Flag, "disable_p2p", translate("Disable P2P"),
translate("Disable P2P communication; only use nodes specified by -p to forward packets (--disable-p2p parameter)"))
disable_p2p:depends("etcmd", "etcmd")
@@ -322,6 +380,15 @@ p2p_only = s:taboption("privacy", Flag, "p2p_only", translate("P2P only"),
translate("only communicate with peers that already establish p2p connection. (--p2p-only parameter)"))
p2p_only:depends("etcmd", "etcmd")
lazy_p2p = s:taboption("privacy", Flag, "lazy_p2p", translate("Lazy P2P"),
translate("Only try to establish P2P connections when traffic actually needs to be forwarded. "
.. "(--lazy-p2p parameter)"))
lazy_p2p:depends("etcmd", "etcmd")
need_p2p = s:taboption("privacy", Flag, "need_p2p", translate("Need P2P"),
translate("Require a P2P connection before forwarding traffic through relays. (--need-p2p parameter)"))
need_p2p:depends("etcmd", "etcmd")
disable_udp = s:taboption("privacy", Flag, "disable_udp", translate("Disable UDP"),
translate("Disable UDP hole punching (--disable-udp-hole-punching parameter)"))
disable_udp:depends("etcmd", "etcmd")
@@ -373,6 +440,14 @@ relay_kcp = s:taboption("privacy", Flag, "relay_kcp", translate("Relay foreign n
translate("If true, allow relay kcp packets from foreign network. default is false (not forward foreign network kcp packets). (--enable-relay-foreign-network-kcp parameter)"))
relay_kcp:depends("etcmd", "etcmd")
disable_relay_quic = s:taboption("privacy", Flag, "disable_relay_quic", translate("Disable relay quic"),
translate("If true, disable relaying quic packets, avoiding consuming too much bandwidth. (--disable-relay-quic parameter)"))
disable_relay_quic:depends("etcmd", "etcmd")
relay_quic = s:taboption("privacy", Flag, "relay_quic", translate("Relay foreign network quic"),
translate("If true, allow relaying quic packets from foreign networks. (--enable-relay-foreign-network-quic parameter)"))
relay_quic:depends("etcmd", "etcmd")
quic_proxy = s:taboption("privacy", Flag, "quic_proxy", translate("Enable QUIC Proxy"),
translate("Proxy tcp streams with QUIC, improving the latency and throughput on the network with udp packet loss.<br>"
.. "All nodes in the virtual network must be using EasyTier version v2.3.2 or higher for this feature. "
@@ -396,6 +471,12 @@ accept_dns = s:taboption("privacy", Flag, "accept_dns", translate("Enable Magic
.. "Magic DNS will modify your system DNS settings, please enable with caution. (--accept-dns parameter)"))
accept_dns:depends("etcmd", "etcmd")
tld_dns_zone = s:taboption("privacy", Value, "tld_dns_zone", translate("Magic DNS Zone"),
translate("Top level DNS zone used by Magic DNS, default is et.net. Only takes effect when Magic DNS is enabled. "
.. "(--tld-dns-zone parameter)"))
tld_dns_zone.placeholder = "et.net."
tld_dns_zone:depends("accept_dns", "1")
private_mode = s:taboption("privacy", Flag, "private_mode", translate("Enable Private Mode"),
translate("When enabled, nodes with a different network name and password are not allowed to handshake or "
.. "relay via this node. (--private-mode parameter)"))
@@ -406,6 +487,17 @@ foreign_relay_bps_limit = s:taboption("privacy", Value, "foreign_relay_bps_limit
.. "(--foreign-relay-bps-limit parameter)"))
foreign_relay_bps_limit:depends("etcmd", "etcmd")
instance_recv_bps_limit = s:taboption("privacy", Value, "instance_recv_bps_limit", translate("Instance Receive Rate Limit"),
translate("the maximum inbound bps limit for this network instance, default is no limit. "
.. "unit: BPS (bytes per second). (--instance-recv-bps-limit parameter)"))
instance_recv_bps_limit:depends("etcmd", "etcmd")
socket_mark = s:taboption("privacy", Value, "socket_mark", translate("Socket Mark"),
translate("Set SO_MARK on all underlay sockets, Linux only and requires CAP_NET_ADMIN. "
.. "Usually used together with policy routing. (--socket-mark parameter)"))
socket_mark.datatype = "range(0,4294967295)"
socket_mark:depends("etcmd", "etcmd")
extra_args = s:taboption("privacy", DynamicList, "extra_args", translate("Extra Parameters"),
translate("Additional command-line arguments passed to the backend process"))
extra_args.placeholder = "--tcp-whitelist=80"
@@ -900,18 +900,133 @@ var translations = {
// Stats指标名称翻译
"traffic_control_bytes_rx": "<%:Traffic Control Bytes RX%>",
"traffic_control_bytes_tx": "<%:Traffic Control Bytes TX%>",
"traffic_control_bytes_forwarded": "<%:Traffic Control Bytes Forwarded%>",
"traffic_control_bytes_rx_by_instance": "<%:Traffic Control Bytes RX By Instance%>",
"traffic_control_bytes_tx_by_instance": "<%:Traffic Control Bytes TX By Instance%>",
"traffic_control_packets_rx": "<%:Traffic Control Packets RX%>",
"traffic_control_packets_tx": "<%:Traffic Control Packets TX%>",
"traffic_control_packets_forwarded": "<%:Traffic Control Packets Forwarded%>",
"traffic_control_packets_rx_by_instance": "<%:Traffic Control Packets RX By Instance%>",
"traffic_control_packets_tx_by_instance": "<%:Traffic Control Packets TX By Instance%>",
"traffic_bytes_rx_by_instance": "<%:Traffic Bytes RX By Instance%>",
"traffic_bytes_tx_by_instance": "<%:Traffic Bytes TX By Instance%>",
"traffic_packets_rx_by_instance": "<%:Traffic Packets RX By Instance%>",
"traffic_packets_tx_by_instance": "<%:Traffic Packets TX By Instance%>",
"traffic_bytes_foreign_forward_rx": "<%:Traffic Bytes Foreign Forward RX%>",
"traffic_bytes_foreign_forward_tx": "<%:Traffic Bytes Foreign Forward TX%>",
"traffic_bytes_foreign_forward_forwarded": "<%:Traffic Bytes Foreign Forward Forwarded%>",
"traffic_packets_foreign_forward_rx": "<%:Traffic Packets Foreign Forward RX%>",
"traffic_packets_foreign_forward_tx": "<%:Traffic Packets Foreign Forward TX%>",
"traffic_packets_foreign_forward_forwarded": "<%:Traffic Packets Foreign Forward Forwarded%>",
"peer_rpc_errors": "<%:Peer RPC Errors%>",
"tcp_proxy_connect": "<%:TCP Proxy Connect%>",
"udp_broadcast_relay_packets_captured": "<%:UDP Broadcast Relay Packets Captured%>",
"udp_broadcast_relay_packets_forwarded": "<%:UDP Broadcast Relay Packets Forwarded%>",
"udp_broadcast_relay_packets_forward_failed": "<%:UDP Broadcast Relay Packets Forward Failed%>",
"udp_broadcast_relay_packets_ignored": "<%:UDP Broadcast Relay Packets Ignored%>",
"vpn_portal_client": "<%:VPN Portal Client%>",
"vpn_portal_client_bytes_rx": "<%:VPN Portal Client Bytes RX%>",
"vpn_portal_client_bytes_tx": "<%:VPN Portal Client Bytes TX%>",
"vpn_portal_client_packets_rx": "<%:VPN Portal Client Packets RX%>",
"vpn_portal_client_packets_tx": "<%:VPN Portal Client Packets TX%>",
// Stats 标签(Labels 列)字段翻译
"compression_algo": "<%:Compression Algorithm%>",
"direction": "<%:Direction%>",
"dst_ip": "<%:Destination IP%>",
"mapped_dst_ip": "<%:Mapped Destination IP%>",
"error_type": "<%:Error Type%>",
"protocol": "<%:Protocol%>",
"from_instance_id": "<%:From Instance ID%>",
"to_instance_id": "<%:To Instance ID%>"
};
var currentTab = 'node';
var isRefreshing = false;
// 连接信息分区表:id 为 TAB 元素后缀,key 为后端 api_conninfo?parts= 使用的分区名
var connInfoSections = [
{id: 'node', key: 'node'},
{id: 'peer', key: 'peer'},
{id: 'route', key: 'route'},
{id: 'connector', key: 'connector'},
{id: 'stun', key: 'stun'},
{id: 'peercenter', key: 'peer_center'},
{id: 'vpnportal', key: 'vpn_portal'},
{id: 'proxy', key: 'proxy'},
{id: 'acl', key: 'acl'},
{id: 'mappedlistener', key: 'mapped_listener'},
{id: 'stats', key: 'stats'},
{id: 'cmdline', key: 'cmdline'}
];
// 已经成功加载过的分区,切换 TAB 时命中就不再请求
var connInfoLoaded = {};
// 待请求的分区队列:同一时刻只允许一个请求在跑,避免一次性起一堆 easytier-cli 进程
var connInfoQueue = [];
var connInfoBusy = false;
// 最近一次响应中的配置文件内容,供"命令行"分区直接使用
var lastConfigFile = null;
// ACL 统计是 core 直接输出的英文结构体文本(core 侧没有多语言),
// 字段名单独放在这里精确匹配,避免 name / bytes 这类通用词影响其它卡片
var aclFieldTranslations = {
"name": "<%:Name%>",
"prio": "<%:Priority%>",
"action": "<%:Action%>",
"enabled": "<%:Enabled%>",
"proto": "<%:Protocol%>",
"ports": "<%:Ports%>",
"src_ports": "<%:Source Ports%>",
"src_ips": "<%:Source IPs%>",
"dst_ips": "<%:Destination IPs%>",
"stateful": "<%:Stateful%>",
"rate": "<%:Rate Limit%>",
"burst": "<%:Burst Limit%>",
"pkts": "<%:Packets%>",
"bytes": "<%:Bytes%>",
"src": "<%:Source Address%>",
"dst": "<%:Destination Address%>",
"state": "<%:State%>",
"created": "<%:Created%>",
"last_seen": "<%:Last Seen%>"
};
// ACL 规则里的枚举取值
var aclValueTranslations = {
"Allow": "<%:Allow%>",
"Drop": "<%:Drop (no response)%>",
"Noop": "<%:Noop%>",
"Any": "<%:Any%>",
"Unspecified": "<%:Unspecified%>",
"New": "<%:New%>",
"Established": "<%:Established%>",
"Related": "<%:Related%>",
"Invalid": "<%:Invalid%>",
"<default/none>": "<%:Default / None%>"
};
// 翻译 ACL 统计文本:先翻译枚举取值,再翻译字段名
function translateAclStats(content) {
var text = content;
text = text.replace(/\baction: (Noop|Allow|Drop)\b/g, function(m, v) {
return 'action: ' + (aclValueTranslations[v] || v);
});
text = text.replace(/\bproto: (Unspecified|Icmpv6|Tcp|Udp|Icmp|Any)\b/g, function(m, v) {
return 'proto: ' + (aclValueTranslations[v] || v);
});
text = text.replace(/\bstate: (New|Established|Related|Invalid)\b/g, function(m, v) {
return 'state: ' + (aclValueTranslations[v] || v);
});
text = text.replace(/\b(name|prio|action|enabled|proto|ports|src_ports|src_ips|dst_ips|stateful|rate|burst|pkts|bytes|src|dst|state|created|last_seen): /g,
function(m, field) {
return (aclFieldTranslations[field] || field) + ': ';
});
text = text.replace(/<default\/none>/g, aclValueTranslations['<default/none>'] || '<default/none>');
return text;
}
function tr(text) {
return translations[text] || text;
@@ -996,37 +1111,16 @@ function parseTable(content, tabName) {
var html = renderCopyablePre(content);
// 检查是否使用配置文件启动
if (content.indexOf('--config-file') !== -1 || content.match(/-c\s+\//)) {
XHR.get('<%=url("admin/vpn/easytier/api_conninfo")%>', null, function(x, data) {
if (data && data.config_file) {
var configDiv = document.createElement('div');
configDiv.className = 'et-pre';
configDiv.style.marginTop = '16px';
configDiv.style.borderTop = '2px solid var(--border-color)';
configDiv.style.paddingTop = '16px';
var title = document.createElement('div');
title.style.fontWeight = '600';
title.style.marginBottom = '8px';
title.style.color = 'var(--primary-color)';
title.textContent = tr('Configuration File Content') + ':';
var contentDiv = document.createElement('pre');
contentDiv.style.margin = '0';
contentDiv.style.whiteSpace = 'pre-wrap';
contentDiv.style.overflowWrap = 'anywhere';
contentDiv.style.wordBreak = 'break-word';
contentDiv.textContent = data.config_file;
configDiv.appendChild(title);
configDiv.appendChild(contentDiv);
var container = document.getElementById('tab_cmdline');
if (container) {
container.appendChild(configDiv);
}
}
});
// 配置文件内容随 cmdline 分区一起返回,这里不再单独请求一次 api_conninfo
if ((content.indexOf('--config-file') !== -1 || content.match(/-c\s+\//)) && lastConfigFile) {
html += '<div class="et-pre" style="margin-top:16px;border-top:2px solid var(--border-color);padding-top:16px;">' +
'<div style="font-weight:600;margin-bottom:8px;color:var(--primary-color);">' +
escapeHtml(tr('Configuration File Content')) + ':' +
'</div>' +
'<pre style="margin:0;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;">' +
escapeHtml(lastConfigFile) +
'</pre>' +
'</div>';
}
return html;
@@ -1091,7 +1185,12 @@ function parseTable(content, tabName) {
translatedContent = translatedContent.replace(/###############\s*([^#]+)\s*###############/g, function(match, p1) {
return '############### ' + tr(p1.trim()) + ' ###############';
});
// ACL 统计的结构体字段名与枚举取值按精确规则翻译
if (tabName === 'acl') {
translatedContent = translateAclStats(translatedContent);
}
return '<div class="et-pre">' + (translatedContent.indexOf('<svg') !== -1 || translatedContent.indexOf('<span') !== -1 ? translatedContent : escapeHtml(translatedContent)) + '</div>';
}
@@ -1179,96 +1278,157 @@ function parseTable(content, tabName) {
}
function switchTab(tabName) {
if (isRefreshing) return;
var tabs = document.querySelectorAll('.et-tab');
var contents = document.querySelectorAll('.et-tab-content');
tabs.forEach(function(tab) {
tab.classList.remove('active');
});
contents.forEach(function(content) {
content.classList.remove('active');
});
var activeTab = document.querySelector('.et-tab[data-tab="' + tabName + '"]');
var activeContent = document.getElementById('tab_' + tabName);
if (activeTab && activeContent) {
activeTab.classList.add('active');
activeContent.classList.add('active');
currentTab = tabName;
// 按需加载:只请求当前显示的分区,没有加载过才发起请求
queueConnInfo([tabName], false);
}
}
// 把 TAB 名换算成后端分区名
function connInfoKeyOfTab(tabName) {
for (var i = 0; i < connInfoSections.length; i++) {
if (connInfoSections[i].id === tabName) {
return connInfoSections[i].key;
}
}
return null;
}
// 分区加载失败(超时/网络错误)时给出提示,避免加载动画一直转下去
function reportConnInfoError(keys) {
for (var i = 0; i < keys.length; i++) {
var sec = null;
for (var j = 0; j < connInfoSections.length; j++) {
if (connInfoSections[j].key === keys[i]) sec = connInfoSections[j];
}
if (!sec) continue;
var el = document.getElementById('tab_' + sec.id);
if (el) {
el.innerHTML = '<div class="et-error"><%:Loading timed out. Please click Refresh to try again.%></div>';
el.setAttribute('data-loaded', '1');
}
}
}
// 把分区加入请求队列
function queueConnInfo(tabNames, force) {
var changed = false;
for (var i = 0; i < tabNames.length; i++) {
var key = connInfoKeyOfTab(tabNames[i]);
if (!key) continue;
if (!force && connInfoLoaded[key]) continue;
if (connInfoQueue.indexOf(key) !== -1) continue;
connInfoQueue.push(key);
changed = true;
}
if (changed) {
pumpConnInfoQueue();
}
}
// 依次请求队列里的分区,同一时刻只允许一个请求在跑,
// 避免一次起多个 easytier-cli 进程把路由器拖慢
function pumpConnInfoQueue() {
if (connInfoBusy || connInfoQueue.length === 0) return;
var keys = connInfoQueue;
connInfoQueue = [];
connInfoBusy = true;
setRefreshBtnBusy(true);
var xhr = new XMLHttpRequest();
var finished = false;
var onDone = function(data) {
if (finished) return;
finished = true;
connInfoBusy = false;
setRefreshBtnBusy(false);
if (data) {
if (typeof data.config_file === 'string') {
lastConfigFile = data.config_file;
}
for (var i = 0; i < keys.length; i++) {
connInfoLoaded[keys[i]] = true;
}
updateConnInfo(data);
} else {
reportConnInfoError(keys);
}
// 继续处理下一个分区
pumpConnInfoQueue();
};
xhr.open('GET', '<%=url("admin", "vpn", "easytier", "api_conninfo")%>?parts=' + encodeURIComponent(keys.join(',')), true);
xhr.timeout = 20000;
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
var data = null;
if (xhr.status === 200) {
try {
data = JSON.parse(xhr.responseText);
} catch (e) {
data = null;
}
}
onDone(data);
};
xhr.ontimeout = function() { onDone(null); };
xhr.onerror = function() { onDone(null); };
xhr.onabort = function() { onDone(null); };
xhr.send();
}
function setRefreshBtnBusy(busy) {
var btn = document.getElementById('et_conninfo_refresh');
if (!btn) return;
var btnText = btn.querySelector('span');
btn.disabled = busy;
if (busy) {
btn.classList.add('loading');
if (btnText) btnText.textContent = '<%:Refreshing...%>';
} else {
btn.classList.remove('loading');
if (btnText) btnText.textContent = '<%:Refresh%>';
}
}
function refreshConnInfo() {
if (isRefreshing) return;
isRefreshing = true;
var btn = document.getElementById('et_conninfo_refresh');
var btnText = btn.querySelector('span');
var originalText = btnText.textContent;
btn.disabled = true;
btn.classList.add('loading');
btnText.textContent = '<%:Refreshing...%>';
var xhr = new XMLHttpRequest();
xhr.open('GET', '<%=url("admin", "vpn", "easytier", "api_conninfo")%>', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
isRefreshing = false;
btn.disabled = false;
btn.classList.remove('loading');
btnText.textContent = originalText;
if (xhr.status === 200) {
try {
var data = JSON.parse(xhr.responseText);
updateConnInfo(data);
} catch (e) {
console.error('Failed to parse response:', e);
}
}
// 手动刷新:立即刷新当前显示的分区,其余分区标记失效,切过去时再重新加载,
// 这样点一次刷新不会同时起十几个 easytier-cli 进程
var activeKey = connInfoKeyOfTab(currentTab);
for (var i = 0; i < connInfoSections.length; i++) {
if (connInfoSections[i].key !== activeKey) {
delete connInfoLoaded[connInfoSections[i].key];
}
};
xhr.onerror = function() {
isRefreshing = false;
btn.disabled = false;
btn.classList.remove('loading');
btnText.textContent = originalText;
};
xhr.onabort = function() {
isRefreshing = false;
btn.disabled = false;
btn.classList.remove('loading');
btnText.textContent = originalText;
};
xhr.send();
}
queueConnInfo([currentTab], true);
}
function updateConnInfo(data) {
if (!data) return;
var sections = [
{id: 'node', key: 'node'},
{id: 'peer', key: 'peer'},
{id: 'route', key: 'route'},
{id: 'connector', key: 'connector'},
{id: 'stun', key: 'stun'},
{id: 'peercenter', key: 'peer_center'},
{id: 'vpnportal', key: 'vpn_portal'},
{id: 'proxy', key: 'proxy'},
{id: 'acl', key: 'acl'},
{id: 'mappedlistener', key: 'mapped_listener'},
{id: 'stats', key: 'stats'},
{id: 'cmdline', key: 'cmdline'}
];
sections.forEach(function(sec) {
connInfoSections.forEach(function(sec) {
var el = document.getElementById('tab_' + sec.id);
if (el && data[sec.key] !== undefined) {
el.innerHTML = parseTable(data[sec.key], sec.id);
el.setAttribute('data-loaded', '1');
}
});
}
@@ -1290,9 +1450,16 @@ document.addEventListener('DOMContentLoaded', function() {
// 加载贡献者
loadContributors();
// 初始加载数据
refreshConnInfo();
// 初始只加载当前显示的分区,其余分区等切过去时再按需请求
queueConnInfo([currentTab], false);
// 定时刷新:只刷新当前显示的分区,不再每次把十几个 easytier-cli 全部跑一遍。
// 间隔取 10 秒,低性能设备上也能跑得过来(单次只起一个进程)
window.setInterval(function() {
if (document.hidden) return;
queueConnInfo([currentTab], true);
}, 10000);
});
// 贡献者轮播管理 (ES5)
@@ -1506,11 +1673,6 @@ function loadContributors() {
contributorsCarousel.init();
}
// 自动刷新
XHR.poll(5, '<%=url("admin", "vpn", "easytier", "api_conninfo")%>', null, function(x, data) {
if (!isRefreshing && data) {
updateConnInfo(data);
}
});
// 自动刷新已改为只刷新当前显示的分区,见上方 DOMContentLoaded 中的定时器
//]]>
</script>
@@ -383,7 +383,7 @@ local translate = i18n.translate
<select id="fallback_version_select" class="config-input" style="display:none;width:100%;">
<option value=""><%=translate("Loading versions...")%></option>
</select>
<input type="text" id="fallback_version" class="config-input" placeholder="v2.6.4" style="display:none;width:100%;">
<input type="text" id="fallback_version" class="config-input" placeholder="v2.7.0" style="display:none;width:100%;">
</div>
<button type="button" class="upload-btn" onclick="showDownloadModal()" style="padding:8px 16px;min-width:auto;flex-shrink:0;">
<%=translate("Online Download")%>
@@ -758,14 +758,14 @@ function loadConfig() {
proxyList = Array.isArray(proxys) ? proxys : [];
renderProxyList();
versionManager.setVersion(config.fallback_version || 'v2.6.4');
versionManager.setVersion(config.fallback_version || 'v2.7.0');
// 保存原始配置
originalConfig = {
easytierbin: config.easytierbin || '/usr/bin/easytier-core',
webbin: config.webbin || '/usr/bin/easytier-web',
github_proxys: JSON.stringify(proxyList),
fallback_version: config.fallback_version || 'v2.6.4'
fallback_version: config.fallback_version || 'v2.7.0'
};
checkDiskSpace('easytierbin');
@@ -843,7 +843,7 @@ function getCurrentConfig() {
easytierbin: document.getElementById('easytierbin').value || '/usr/bin/easytier-core',
webbin: document.getElementById('webbin').value || '/usr/bin/easytier-web',
github_proxys: JSON.stringify(proxyList),
fallback_version: versionManager.getCurrentVersion() || 'v2.6.4'
fallback_version: versionManager.getCurrentVersion() || 'v2.7.0'
};
}
@@ -868,7 +868,7 @@ function saveConfig() {
easytierbin: document.getElementById('easytierbin').value || '/usr/bin/easytier-core',
webbin: document.getElementById('webbin').value || '/usr/bin/easytier-web',
github_proxys: proxyList,
fallback_version: versionManager.getCurrentVersion() || 'v2.6.4'
fallback_version: versionManager.getCurrentVersion() || 'v2.7.0'
};
var xhr = new XMLHttpRequest();
@@ -677,6 +677,35 @@ input:disabled + .slider {
<span class="description"><%:Automatically create local user when easytier-core connects with an unknown username%>. (--allow-auto-create-user <%:parameter%>)</span>
</div>
<div class="form-group">
<label for="web_heartbeat_min_response_ms"><%:Heartbeat Minimum Response Time (ms)%></label>
<input type="number" class="form-control" id="web_heartbeat_min_response_ms" name="web_heartbeat_min_response_ms" placeholder="3500">
<span class="description"><%:Minimum heartbeat response delay used by the config server when measuring client latency%>. (--heartbeat-min-response-ms <%:parameter%>)</span>
</div>
<div class="form-group">
<label for="web_heartbeat_timeout_ms"><%:Heartbeat Timeout (ms)%></label>
<input type="number" class="form-control" id="web_heartbeat_timeout_ms" name="web_heartbeat_timeout_ms" placeholder="15000">
<span class="description"><%:How long a client may stay silent before the config server marks it offline%>. (--heartbeat-timeout-ms <%:parameter%>)</span>
</div>
<div class="form-group">
<label for="web_gateway_peer_url"><%:Gateway Peer URL%></label>
<input type="text" class="form-control" id="web_gateway_peer_url" name="web_gateway_peer_url" placeholder="tcp://public.easytier.top:11010">
<span class="description"><%:Peer URL used by this web instance when it works as a gateway%>. (--gateway-peer-url <%:parameter%>)</span>
</div>
<div class="form-group">
<div class="checkbox-wrapper">
<label for="web_gateway_relay_data"><%:Gateway Relay Data%></label>
<label class="switch">
<input type="checkbox" id="web_gateway_relay_data" name="web_gateway_relay_data">
<span class="slider"></span>
</label>
</div>
<span class="description"><%:Allow this web gateway instance to relay data traffic%>. (--gateway-relay-data <%:parameter%>)</span>
</div>
<div class="form-group">
<label for="web_weblog"><%:Log Level%></label>
<select class="form-control" id="web_weblog" name="web_weblog">
@@ -905,6 +934,11 @@ function loadConfig() {
document.getElementById('web_geoip_db').value = data.web_geoip_db || '';
document.getElementById('web_disable_registration').checked = data.web_disable_registration === '1';
document.getElementById('web_allow_auto_create_user').checked = data.web_allow_auto_create_user === '1';
// EasyTier 2.7.0 新增的 web 参数
document.getElementById('web_heartbeat_min_response_ms').value = data.web_heartbeat_min_response_ms || '';
document.getElementById('web_heartbeat_timeout_ms').value = data.web_heartbeat_timeout_ms || '';
document.getElementById('web_gateway_peer_url').value = data.web_gateway_peer_url || '';
document.getElementById('web_gateway_relay_data').checked = data.web_gateway_relay_data === '1';
document.getElementById('web_weblog').value = data.web_weblog || 'off';
// OIDC配置
@@ -953,6 +987,11 @@ function getCurrentConfig() {
web_geoip_db: document.getElementById('web_geoip_db').value,
web_disable_registration: document.getElementById('web_disable_registration').checked ? '1' : '0',
web_allow_auto_create_user: document.getElementById('web_allow_auto_create_user').checked ? '1' : '0',
// EasyTier 2.7.0 新增的 web 参数
web_heartbeat_min_response_ms: document.getElementById('web_heartbeat_min_response_ms').value,
web_heartbeat_timeout_ms: document.getElementById('web_heartbeat_timeout_ms').value,
web_gateway_peer_url: document.getElementById('web_gateway_peer_url').value,
web_gateway_relay_data: document.getElementById('web_gateway_relay_data').checked ? '1' : '0',
web_weblog: document.getElementById('web_weblog').value,
// OIDC
web_oidc_enabled: document.getElementById('web_oidc_enabled').checked ? '1' : '0',
@@ -906,10 +906,12 @@ msgstr ""
#: applications/luci-app-easytier/luasrc/model/cbi/easytier.lua:65
msgid ""
"Web configuration server address. (-w parameter)<br>For a self-hosted Web "
"server, use the format: udp://server_address:22020/username<br>For the "
"official Web server, use the format: username<br>Official Web Console: <a "
"href='https://easytier.cn/web'>easytier.cn/web</a>"
"Web configuration server address. (-w parameter)<br>Since EasyTier 2.7.0 a "
"full URL is required, the token-only shorthand is no longer supported<br>Self-"
"hosted Web server, use the format: udp://server_address:22020/username<br>"
"Official Web server, use the format: udp://config-server.easytier.cn:22020/"
"username<br>Official Web Console: <a href='https://easytier.cn/web'>easytier."
"cn/web</a>"
msgstr ""
#: applications/luci-app-easytier/luasrc/model/cbi/easytier.lua:365
@@ -1412,3 +1414,111 @@ msgstr ""
msgid "Duplicate extra parameter"
msgstr ""
msgid "VPN Portal Listen Address"
msgstr ""
msgid "WireGuard VPN portal listen address, allowing other VPN clients to connect.<br>Since EasyTier 2.7.0 only wg://HOST:PORT is accepted; the client network can no longer be appended to the URL, please add each client in \"VPN Portal Clients\" below (--vpn-portal parameter)"
msgstr ""
msgid "Only wg://HOST:PORT is accepted since EasyTier 2.7.0, please fill the client network into \"VPN Portal Clients\""
msgstr ""
msgid "VPN Portal Clients"
msgstr ""
msgid "One WireGuard client per entry in the form NAME=CIDR, for example client1=10.14.14.1/24.<br>NAME may contain letters, digits and dashes only; the address must be a usable host address inside the client network. (--vpn-portal-client parameter)"
msgstr ""
msgid "Invalid client, expected NAME=CIDR, for example client1=10.14.14.1/24"
msgstr ""
msgid "VPN Portal Private Key"
msgstr ""
msgid "Base64 encoded 32-byte WireGuard private key of the portal.<br>Required since EasyTier 2.7.0, the portal will not start without it. It is handed to easytier-core through an environment variable so that it never shows up in the process list. (--vpn-portal-private-key parameter)"
msgstr ""
msgid "Disable relay quic"
msgstr ""
msgid "If true, disable relaying quic packets, avoiding consuming too much bandwidth. (--disable-relay-quic parameter)"
msgstr ""
msgid "Relay foreign network quic"
msgstr ""
msgid "If true, allow relaying quic packets from foreign networks. (--enable-relay-foreign-network-quic parameter)"
msgstr ""
msgid "Magic DNS Zone"
msgstr ""
msgid "Top level DNS zone used by Magic DNS, default is et.net. Only takes effect when Magic DNS is enabled. (--tld-dns-zone parameter)"
msgstr ""
msgid "Instance Receive Rate Limit"
msgstr ""
msgid "the maximum inbound bps limit for this network instance, default is no limit. unit: BPS (bytes per second). (--instance-recv-bps-limit parameter)"
msgstr ""
msgid "Socket Mark"
msgstr ""
msgid "Set SO_MARK on all underlay sockets, Linux only and requires CAP_NET_ADMIN. Usually used together with policy routing. (--socket-mark parameter)"
msgstr ""
msgid "Lazy P2P"
msgstr ""
msgid "Only try to establish P2P connections when traffic actually needs to be forwarded. (--lazy-p2p parameter)"
msgstr ""
msgid "Need P2P"
msgstr ""
msgid "Require a P2P connection before forwarding traffic through relays. (--need-p2p parameter)"
msgstr ""
msgid "Disable UPnP"
msgstr ""
msgid "EasyTier 2.7.0 tries to map listener ports through UPnP/NAT-PMP by default. Enable this to turn the automatic port mapping off. (--disable-upnp parameter)"
msgstr ""
msgid "STUN Servers"
msgstr ""
msgid "Custom UDP STUN servers used for NAT type detection and hole punching. Leave empty to use the built-in defaults. (--stun-servers parameter)"
msgstr ""
msgid "STUN Servers (TCP)"
msgstr ""
msgid "Custom TCP STUN servers used for NAT type detection and hole punching. Leave empty to use the built-in defaults. (--tcp-stun-servers parameter)"
msgstr ""
msgid "Heartbeat Minimum Response Time (ms)"
msgstr ""
msgid "Minimum heartbeat response delay used by the config server when measuring client latency"
msgstr ""
msgid "Heartbeat Timeout (ms)"
msgstr ""
msgid "How long a client may stay silent before the config server marks it offline"
msgstr ""
msgid "Gateway Peer URL"
msgstr ""
msgid "Peer URL used by this web instance when it works as a gateway"
msgstr ""
msgid "Gateway Relay Data"
msgstr ""
msgid "Allow this web gateway instance to relay data traffic"
msgstr ""
@@ -818,12 +818,15 @@ msgstr "Web服务器地址"
#: applications/luci-app-easytier/luasrc/model/cbi/easytier.lua:65
msgid ""
"Web configuration server address. (-w parameter)<br>For a self-hosted Web "
"server, use the format: udp://server_address:22020/username<br>For the "
"official Web server, use the format: username<br>Official Web Console: <a "
"href='https://easytier.cn/web'>easytier.cn/web</a>"
"Web configuration server address. (-w parameter)<br>Since EasyTier 2.7.0 a "
"full URL is required, the token-only shorthand is no longer supported<br>Self-"
"hosted Web server, use the format: udp://server_address:22020/username<br>"
"Official Web server, use the format: udp://config-server.easytier.cn:22020/"
"username<br>Official Web Console: <a href='https://easytier.cn/web'>easytier."
"cn/web</a>"
msgstr ""
"Web配置服务器地址。(-w 参数)<br>自建Web服务器 输入格式:udp://服务器地址:22020/账户名<br>官方Web服务器 输入格式:账户名 <br>"
"Web配置服务器地址。(-w 参数)<br>EasyTier 2.7.0 起必须填写完整 URL,不再支持仅填 token 的简写方式<br>"
"自建Web服务器 输入格式:udp://服务器地址:22020/账户名<br>官方Web服务器 输入格式:udp://config-server.easytier.cn:22020/账户名<br>"
"官方Web控制台:<a href='https://easytier.cn/web' target='_blank'>https://easytier.cn/web</a>"
#: applications/luci-app-easytier/luasrc/model/cbi/easytier.lua:365
@@ -1663,6 +1666,9 @@ msgstr "节点中心服务不可用,此功能要求网络中包含多个节点
msgid "Refreshing..."
msgstr "正在刷新..."
msgid "Loading timed out. Please click Refresh to try again."
msgstr "加载超时,请点击刷新重试。"
msgid "Failed to parse response:"
msgstr "解析响应失败:"
@@ -1802,9 +1808,174 @@ msgstr "按实例统计接收数据包"
msgid "Traffic Control Packets TX By Instance"
msgstr "按实例统计发送数据包"
msgid "Traffic Control Bytes Forwarded"
msgstr "流量控制转发字节数"
msgid "Traffic Control Packets Forwarded"
msgstr "流量控制转发数据包"
msgid "Traffic Bytes RX By Instance"
msgstr "按实例统计接收字节数(总计)"
msgid "Traffic Bytes TX By Instance"
msgstr "按实例统计发送字节数(总计)"
msgid "Traffic Packets RX By Instance"
msgstr "按实例统计接收数据包(总计)"
msgid "Traffic Packets TX By Instance"
msgstr "按实例统计发送数据包(总计)"
msgid "Traffic Bytes Foreign Forward RX"
msgstr "跨网中转接收字节数"
msgid "Traffic Bytes Foreign Forward TX"
msgstr "跨网中转发送字节数"
msgid "Traffic Bytes Foreign Forward Forwarded"
msgstr "跨网中转转发字节数"
msgid "Traffic Packets Foreign Forward RX"
msgstr "跨网中转接收数据包"
msgid "Traffic Packets Foreign Forward TX"
msgstr "跨网中转发送数据包"
msgid "Traffic Packets Foreign Forward Forwarded"
msgstr "跨网中转转发数据包"
msgid "Peer RPC Errors"
msgstr "节点 RPC 错误数"
msgid "TCP Proxy Connect"
msgstr "TCP 代理连接数"
msgid "UDP Broadcast Relay Packets Captured"
msgstr "UDP 广播中继捕获数据包"
msgid "UDP Broadcast Relay Packets Forwarded"
msgstr "UDP 广播中继转发数据包"
msgid "UDP Broadcast Relay Packets Forward Failed"
msgstr "UDP 广播中继转发失败数据包"
msgid "UDP Broadcast Relay Packets Ignored"
msgstr "UDP 广播中继忽略数据包"
msgid "VPN Portal Client"
msgstr "VPN 门户客户端"
msgid "VPN Portal Client Bytes RX"
msgstr "VPN 门户客户端接收字节数"
msgid "VPN Portal Client Bytes TX"
msgstr "VPN 门户客户端发送字节数"
msgid "VPN Portal Client Packets RX"
msgstr "VPN 门户客户端接收数据包"
msgid "VPN Portal Client Packets TX"
msgstr "VPN 门户客户端发送数据包"
msgid "From Instance ID"
msgstr "来源实例ID"
msgid "Direction"
msgstr "方向"
msgid "Destination IP"
msgstr "目标 IP"
msgid "Mapped Destination IP"
msgstr "映射后目标 IP"
msgid "Error Type"
msgstr "错误类型"
msgid "Protocol"
msgstr "协议"
msgid "Name"
msgstr "名称"
msgid "Priority"
msgstr "优先级"
msgid "Action"
msgstr "动作"
msgid "Enabled"
msgstr "已启用"
msgid "Ports"
msgstr "端口"
msgid "Source Ports"
msgstr "源端口"
msgid "Source IPs"
msgstr "源 IP"
msgid "Destination IPs"
msgstr "目标 IP"
msgid "Stateful"
msgstr "有状态"
msgid "Rate Limit"
msgstr "速率限制"
msgid "Burst Limit"
msgstr "突发限制"
msgid "Packets"
msgstr "数据包数"
msgid "Bytes"
msgstr "字节数"
msgid "Source Address"
msgstr "源地址"
msgid "Destination Address"
msgstr "目标地址"
msgid "Created"
msgstr "创建时间"
msgid "Last Seen"
msgstr "最近活跃"
msgid "Allow"
msgstr "允许"
msgid "Drop (no response)"
msgstr "丢弃(不回应)"
msgid "Noop"
msgstr "不处理"
msgid "Any"
msgstr "任意"
msgid "Unspecified"
msgstr "未指定"
msgid "New"
msgstr "新建"
msgid "Established"
msgstr "已建立"
msgid "Related"
msgstr "关联"
msgid "Invalid"
msgstr "无效"
msgid "Default / None"
msgstr "默认 / 无"
msgid "To Instance ID"
msgstr "目标实例ID"
@@ -2107,3 +2278,111 @@ msgstr "数据库重置成功"
msgid "Restart service to apply changes?"
msgstr "重启服务以生效?"
msgid "VPN Portal Listen Address"
msgstr "VPN 门户监听地址"
msgid "WireGuard VPN portal listen address, allowing other VPN clients to connect.<br>Since EasyTier 2.7.0 only wg://HOST:PORT is accepted; the client network can no longer be appended to the URL, please add each client in \"VPN Portal Clients\" below (--vpn-portal parameter)"
msgstr "WireGuard VPN 门户监听地址,允许其他 VPN 客户端接入。<br>EasyTier 2.7.0 起只接受 wg://主机:端口,客户端网段不能再写在地址里,请在下方“VPN 门户客户端”中逐个添加(--vpn-portal 参数)"
msgid "Only wg://HOST:PORT is accepted since EasyTier 2.7.0, please fill the client network into \"VPN Portal Clients\""
msgstr "EasyTier 2.7.0 起只接受 wg://主机:端口,请把客户端网段填写到“VPN 门户客户端”中"
msgid "VPN Portal Clients"
msgstr "VPN 门户客户端"
msgid "One WireGuard client per entry in the form NAME=CIDR, for example client1=10.14.14.1/24.<br>NAME may contain letters, digits and dashes only; the address must be a usable host address inside the client network. (--vpn-portal-client parameter)"
msgstr "每条一个 WireGuard 客户端,格式为 名称=CIDR,例如 client1=10.14.14.1/24。<br>名称只能包含字母、数字和短横线;地址必须是该网段内可用的主机地址(--vpn-portal-client 参数)"
msgid "Invalid client, expected NAME=CIDR, for example client1=10.14.14.1/24"
msgstr "客户端格式错误,应为 名称=CIDR,例如 client1=10.14.14.1/24"
msgid "VPN Portal Private Key"
msgstr "VPN 门户私钥"
msgid "Base64 encoded 32-byte WireGuard private key of the portal.<br>Required since EasyTier 2.7.0, the portal will not start without it. It is handed to easytier-core through an environment variable so that it never shows up in the process list. (--vpn-portal-private-key parameter)"
msgstr "门户的 WireGuard 私钥(base64 编码的 32 字节)。<br>EasyTier 2.7.0 起必填,未填写时门户不会启动。私钥通过环境变量传给 easytier-core,不会出现在进程列表中(--vpn-portal-private-key 参数)"
msgid "Disable relay quic"
msgstr "禁用转发 QUIC"
msgid "If true, disable relaying quic packets, avoiding consuming too much bandwidth. (--disable-relay-quic parameter)"
msgstr "开启后禁止转发 QUIC 报文,避免占用过多带宽(--disable-relay-quic 参数)"
msgid "Relay foreign network quic"
msgstr "转发外部网络 QUIC"
msgid "If true, allow relaying quic packets from foreign networks. (--enable-relay-foreign-network-quic parameter)"
msgstr "开启后允许转发来自其他网络的 QUIC 报文(--enable-relay-foreign-network-quic 参数)"
msgid "Magic DNS Zone"
msgstr "魔法 DNS 区域"
msgid "Top level DNS zone used by Magic DNS, default is et.net. Only takes effect when Magic DNS is enabled. (--tld-dns-zone parameter)"
msgstr "魔法 DNS 使用的顶级域名区域,默认 et.net.,仅在启用魔法 DNS 时生效(--tld-dns-zone 参数)"
msgid "Instance Receive Rate Limit"
msgstr "实例接收限速"
msgid "the maximum inbound bps limit for this network instance, default is no limit. unit: BPS (bytes per second). (--instance-recv-bps-limit parameter)"
msgstr "本网络实例的整体入站限速,默认不限速,单位字节每秒(--instance-recv-bps-limit 参数)"
msgid "Socket Mark"
msgstr "Socket Mark"
msgid "Set SO_MARK on all underlay sockets, Linux only and requires CAP_NET_ADMIN. Usually used together with policy routing. (--socket-mark parameter)"
msgstr "为所有底层套接字设置 SO_MARK,仅 Linux 支持且需要 CAP_NET_ADMIN 权限,通常配合策略路由使用(--socket-mark 参数)"
msgid "Lazy P2P"
msgstr "延迟 P2P"
msgid "Only try to establish P2P connections when traffic actually needs to be forwarded. (--lazy-p2p parameter)"
msgstr "仅在有实际数据需要转发时才尝试建立 P2P 连接(--lazy-p2p 参数)"
msgid "Need P2P"
msgstr "强制 P2P"
msgid "Require a P2P connection before forwarding traffic through relays. (--need-p2p parameter)"
msgstr "必须建立 P2P 连接后才允许通过中继转发数据(--need-p2p 参数)"
msgid "Disable UPnP"
msgstr "禁用 UPnP"
msgid "EasyTier 2.7.0 tries to map listener ports through UPnP/NAT-PMP by default. Enable this to turn the automatic port mapping off. (--disable-upnp parameter)"
msgstr "EasyTier 2.7.0 默认会尝试通过 UPnP/NAT-PMP 自动映射监听端口,开启本项可关闭自动端口映射(--disable-upnp 参数)"
msgid "STUN Servers"
msgstr "STUN 服务器"
msgid "Custom UDP STUN servers used for NAT type detection and hole punching. Leave empty to use the built-in defaults. (--stun-servers parameter)"
msgstr "用于 NAT 类型探测与打洞的自定义 UDP STUN 服务器,留空则使用内置默认值(--stun-servers 参数)"
msgid "STUN Servers (TCP)"
msgstr "STUN 服务器(TCP)"
msgid "Custom TCP STUN servers used for NAT type detection and hole punching. Leave empty to use the built-in defaults. (--tcp-stun-servers parameter)"
msgstr "用于 NAT 类型探测与打洞的自定义 TCP STUN 服务器,留空则使用内置默认值(--tcp-stun-servers 参数)"
msgid "Heartbeat Minimum Response Time (ms)"
msgstr "心跳最小响应时间(毫秒)"
msgid "Minimum heartbeat response delay used by the config server when measuring client latency"
msgstr "配置服务器测量客户端延迟时使用的心跳最小响应延迟"
msgid "Heartbeat Timeout (ms)"
msgstr "心跳超时时间(毫秒)"
msgid "How long a client may stay silent before the config server marks it offline"
msgstr "客户端多久无心跳后会被配置服务器标记为离线"
msgid "Gateway Peer URL"
msgstr "网关对等节点地址"
msgid "Peer URL used by this web instance when it works as a gateway"
msgstr "本 Web 实例作为网关时使用的对等节点地址"
msgid "Gateway Relay Data"
msgstr "网关转发数据"
msgid "Allow this web gateway instance to relay data traffic"
msgstr "允许本 Web 网关实例转发数据流量"
@@ -3,6 +3,8 @@ instance_id = "f9f05421-7c6b-47d6-a67d-4d58d49f51d8"
dhcp = true
listeners = []
exit_nodes = []
# 注意:顶层配置结构里没有 rpc_portal 字段,该行会被 easytier-core 静默忽略。
# RPC 端口只能通过命令行 -r 指定(本插件的“RPC 端口”选项),保持默认即可。
rpc_portal = "0.0.0.0:15888"
[network_identity]
@@ -12,4 +14,18 @@ network_secret = "mysecret"
[[peer]]
uri = "tcp://public.easytier.top:11010"
# EasyTier 2.7.0 起 WireGuard 门户写法(默认关闭,需要时取消注释)
# 旧版把客户端网段写在 wireguard_listen 之外的 client_cidr 键已不再支持,会导致配置文件解析失败。
# 门户私钥为 base64 编码的 32 字节,2.7.0 起必须填写,否则门户监听不会启动。
# [vpn_portal_config]
# enabled = true
# wireguard_listen = "0.0.0.0:51820"
# wireguard_private_key = ""
#
# [[vpn_portal_config.clients]]
# name = "client1"
# virtual_ip = "10.14.14.1/24"
# groups = []
@@ -107,7 +107,7 @@ check_bin() {
# 如果获取失败,从 UCI 配置或使用默认版本
if [ -z "$tag" ]; then
tag=$(uci -q get easytier.@easytier[0].fallback_version)
[ -z "$tag" ] && tag="v2.6.4"
[ -z "$tag" ] && tag="v2.7.0"
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始在线下载${tag}版本,${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip下载较慢耐心等候" >>/tmp/easytier.log
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始在线下载${tag}版本,${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip下载较慢耐心等候" >>/tmp/easytierweb.log
@@ -167,7 +167,10 @@ check_config() {
wireguard_port=$(cat $config_file | grep "wireguard_listen = " | awk -F ":" '{print $2}'| tr -d '/", ')
log="$(uci -q get easytier.@easytier[0].log || echo off)"
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 检测配置文件可用性 ${easytierbin} --check-config --config-file ${config_file}" >>/tmp/easytier.log
${easytierbin} --check-config --config-file ${config_file} >> /tmp/easytier.log 2>&1 &
${easytierbin} --check-config --config-file ${config_file} >> /tmp/easytier.log 2>&1
if [ "$?" != "0" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 配置文件 ${config_file} 校验失败,请查看上方 easytier-core 的报错。EasyTier 2.7.0 起配置校验更严格:旧的 [vpn_portal_config] client_cidr 写法与 [flags] 中的未知键都会导致解析失败" >>/tmp/easytier.log
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始运行 ${easytierbin} -c ${config_file}" >>/tmp/easytier.log
procd_open_instance
@@ -218,6 +221,8 @@ get_etconfig() {
desvice_name="$(uci -q get easytier.@easytier[0].desvice_name)"
instance_name="$(uci -q get easytier.@easytier[0].instance_name)"
vpn_portal="$(uci -q get easytier.@easytier[0].vpn_portal)"
vpn_portal_clients="$(uci -q get easytier.@easytier[0].vpn_portal_clients)"
vpn_portal_private_key="$(uci -q get easytier.@easytier[0].vpn_portal_private_key)"
mtu="$(uci -q get easytier.@easytier[0].mtu)"
default_protocol="$(uci -q get easytier.@easytier[0].default_protocol)"
disable_encryption="$(uci -q get easytier.@easytier[0].disable_encryption || echo 0)"
@@ -263,7 +268,18 @@ get_etconfig() {
auto_config_interface="$(uci -q get easytier.@easytier[0].auto_config_interface || echo 1)"
interface_netmask="$(uci -q get easytier.@easytier[0].interface_netmask)"
auto_config_firewall="$(uci -q get easytier.@easytier[0].auto_config_firewall || echo 1)"
# EasyTier 2.7.0 新增参数
tld_dns_zone="$(uci -q get easytier.@easytier[0].tld_dns_zone)"
instance_recv_bps_limit="$(uci -q get easytier.@easytier[0].instance_recv_bps_limit)"
socket_mark="$(uci -q get easytier.@easytier[0].socket_mark)"
disable_upnp="$(uci -q get easytier.@easytier[0].disable_upnp || echo 0)"
lazy_p2p="$(uci -q get easytier.@easytier[0].lazy_p2p || echo 0)"
need_p2p="$(uci -q get easytier.@easytier[0].need_p2p || echo 0)"
disable_relay_quic="$(uci -q get easytier.@easytier[0].disable_relay_quic || echo 0)"
relay_quic="$(uci -q get easytier.@easytier[0].relay_quic || echo 0)"
stun_servers="$(uci -q get easytier.@easytier[0].stun_servers)"
tcp_stun_servers="$(uci -q get easytier.@easytier[0].tcp_stun_servers)"
}
set_firewall() {
@@ -449,7 +465,8 @@ start_et() {
procd_open_instance "easytier_core"
get_tz
[ -z "$SET_TZ" ] || procd_set_param env TZ="$SET_TZ"
core_cmd=""
# 门户私钥通过环境变量下发给 easytier-core,避免出现在进程命令行(状态页会展示命令行)
[ -z "$vpn_portal_private_key" ] || procd_set_param env ET_VPN_PORTAL_PRIVATE_KEY="$vpn_portal_private_key"
if [ "$etcmd" = "etcmd" ] ; then
#默认方式启动
[ -z "$network_name" ] || core_cmd="${core_cmd} --network-name $(shell_quote "$network_name")"
@@ -536,12 +553,46 @@ start_et() {
[ "$listenermode" = "ON" ] || core_cmd="${core_cmd} --no-listener"
[ -z "$desvice_name" ] || core_cmd="${core_cmd} --hostname $(shell_quote "$desvice_name")"
[ -z "$instance_name" ] || core_cmd="${core_cmd} -m $instance_name"
[ -z "$vpn_portal" ] || core_cmd="${core_cmd} --vpn-portal $vpn_portal"
# EasyTier 2.7.0 起 --vpn-portal 只接受 wg://主机:端口,客户端必须逐个具名枚举
if [ -n "$vpn_portal" ]; then
case "$vpn_portal" in
wg://*/*)
# 旧版把客户端网段写在 URL path 里,2.7.0 会直接拒绝并让进程退出,这里跳过门户并提示用户修改
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : VPN 门户地址 ${vpn_portal} 是 2.6.4 旧写法,EasyTier 2.7.0 起只接受 wg://主机:端口,已跳过门户启动;请在界面把客户端网段填入 VPN 门户客户端列表" >>/tmp/easytier.log
;;
wg://*)
if [ -z "$vpn_portal_private_key" ]; then
# 2.7.0 起门户必须有私钥,否则门户监听会启动失败并连带整个实例退出
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 已配置 VPN 门户但未填写门户私钥,EasyTier 2.7.0 起门户必须有私钥,已跳过门户启动;请在界面填写 VPN 门户私钥" >>/tmp/easytier.log
else
core_cmd="${core_cmd} --vpn-portal $vpn_portal"
fi
;;
*)
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : VPN 门户地址 ${vpn_portal} 格式不正确,应为 wg://主机:端口,已跳过门户启动" >>/tmp/easytier.log
;;
esac
fi
# 门户具名客户端,格式 名称=地址/前缀,可配置多条
if [ -n "$vpn_portal_clients" ]; then
if [[ "$(grep "list vpn_portal_clients" /etc/config/easytier | awk '{print $3}' | wc -l)" -eq 1 ]]; then
core_cmd="${core_cmd} --vpn-portal-client $vpn_portal_clients"
else
for vpn_portal_client in $(cat /etc/config/easytier | grep 'list vpn_portal_clients' | awk -F 'list vpn_portal_clients' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} --vpn-portal-client $vpn_portal_client"
done
fi
fi
[ -z "$mtu" ] || core_cmd="${core_cmd} --mtu $mtu"
[ "$default_protocol" = "-" ] || core_cmd="${core_cmd} --default-protocol $default_protocol"
[ -z "$tunname" ] || core_cmd="${core_cmd} --dev-name $tunname"
[ "$disable_encryption" = "0" ] || core_cmd="${core_cmd} -u"
[ "$multi_thread" = "0" ] || core_cmd="${core_cmd} --multi-thread"
# 2.7.0 起 multi_thread 的默认值由 false 改为 true,这里始终显式传值,保证升级后行为与界面勾选一致
if [ "$multi_thread" = "0" ]; then
core_cmd="${core_cmd} --multi-thread false"
else
core_cmd="${core_cmd} --multi-thread true"
fi
[ -z "$multi_thread_count" ] || core_cmd="${core_cmd} --multi-thread-count $multi_thread_count"
[ "$no_tun" = "0" ] || core_cmd="${core_cmd} --no-tun"
[ "$smoltcp" = "0" ] || core_cmd="${core_cmd} --use-smoltcp"
@@ -571,8 +622,28 @@ start_et() {
[ "$disable_sym" = "0" ] || core_cmd="${core_cmd} --disable-sym-hole-punching"
[ "$disable_relay_kcp" = "0" ] || core_cmd="${core_cmd} --disable-relay-kcp true"
[ "$relay_kcp" = "0" ] || core_cmd="${core_cmd} --enable-relay-foreign-network-kcp true"
[ "$disable_relay_quic" = "0" ] || core_cmd="${core_cmd} --disable-relay-quic true"
[ "$relay_quic" = "0" ] || core_cmd="${core_cmd} --enable-relay-foreign-network-quic true"
[ -z "$udp_white_port" ] || core_cmd="${core_cmd} --udp-whitelist $udp_white_port"
[ -z "$tcp_white_port" ] || core_cmd="${core_cmd} --tcp-whitelist $tcp_white_port"
# EasyTier 2.7.0 新增的常用参数
[ -z "$tld_dns_zone" ] || core_cmd="${core_cmd} --tld-dns-zone $tld_dns_zone"
[ -z "$instance_recv_bps_limit" ] || core_cmd="${core_cmd} --instance-recv-bps-limit $instance_recv_bps_limit"
[ -z "$socket_mark" ] || core_cmd="${core_cmd} --socket-mark $socket_mark"
[ "$disable_upnp" = "0" ] || core_cmd="${core_cmd} --disable-upnp"
[ "$lazy_p2p" = "0" ] || core_cmd="${core_cmd} --lazy-p2p"
[ "$need_p2p" = "0" ] || core_cmd="${core_cmd} --need-p2p"
# 自定义 STUN 服务器,可配置多条
if [ -n "$stun_servers" ] ; then
for stun_server in $(cat /etc/config/easytier | grep 'list stun_servers' | awk -F 'list stun_servers' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} --stun-servers $stun_server"
done
fi
if [ -n "$tcp_stun_servers" ] ; then
for tcp_stun_server in $(cat /etc/config/easytier | grep 'list tcp_stun_servers' | awk -F 'list tcp_stun_servers' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} --tcp-stun-servers $tcp_stun_server"
done
fi
if [ -n "$extra_args" ] ; then
while IFS= read -r extra_arg; do
[ -n "$extra_arg" ] && core_cmd="${core_cmd} $extra_arg"
@@ -595,7 +666,18 @@ EOF
fi
et_uuid="$(cat /etc/easytier/et_machine_id | tr -d ' \n')"
[ -z "$et_uuid" ] || core_cmd="${core_cmd} --machine-id $et_uuid"
[ -z "$web_config" ] || core_cmd="${core_cmd} -w $web_config"
# EasyTier 2.7.0 起 -w 必须填写完整 URL(例如 udp://config-server.easytier.cn:22020/账户名),
# 旧版只填账户名的写法会解析失败并让整个进程退出,这里跳过 Web 客户端并提示用户修改
case "$web_config" in
"")
;;
*://*)
core_cmd="${core_cmd} -w $web_config"
;;
*)
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : Web 服务器地址 ${web_config} 不是完整 URL,EasyTier 2.7.0 起必须填写完整地址(例如 udp://config-server.easytier.cn:22020/账户名),已跳过 Web 客户端启动;请在界面把 Web 服务器地址改为完整地址" >>/tmp/easytier.log
;;
esac
[ -z "$desvice_name" ] || core_cmd="${core_cmd} --hostname $(shell_quote "$desvice_name")"
fi
procd_set_param command /bin/sh -c "
@@ -623,7 +705,10 @@ EOF
procd_set_param limits nofile="1000000 1000000"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn
# procd 进程守护参数:threshold=3600 秒、timeout=5 秒、retry=20 次
# 存活满 1 小时后崩溃计数归零;1 小时内崩溃超过 20 次(即第 21 次)才判定崩溃循环并放弃拉起,
# 避免参数配置错误这类必然失败的场景无限重启刷日志、空烧 CPU
procd_set_param respawn 3600 5 20
procd_close_instance
# 不再自动放行 SOCKS5 端口到 WAN —— 无认证 SOCKS5 代理暴露给公网有严重安全风险
@@ -691,6 +776,11 @@ start_web() {
html_addr="$(uci -q get easytier.@easytier[0].web_html_addr)"
disable_registration="$(uci -q get easytier.@easytier[0].web_disable_registration || echo 0)"
allow_auto_create_user="$(uci -q get easytier.@easytier[0].web_allow_auto_create_user || echo 0)"
# EasyTier 2.7.0 新增的 web 参数
heartbeat_min_response_ms="$(uci -q get easytier.@easytier[0].web_heartbeat_min_response_ms)"
heartbeat_timeout_ms="$(uci -q get easytier.@easytier[0].web_heartbeat_timeout_ms)"
gateway_peer_url="$(uci -q get easytier.@easytier[0].web_gateway_peer_url)"
gateway_relay_data="$(uci -q get easytier.@easytier[0].web_gateway_relay_data || echo 0)"
# OIDC配置
oidc_enabled="$(uci -q get easytier.@easytier[0].web_oidc_enabled || echo 0)"
oidc_issuer_url="$(uci -q get easytier.@easytier[0].web_oidc_issuer_url)"
@@ -729,6 +819,11 @@ start_web() {
[ -n "$html_addr" ] && [ "$html_addr" != "0.0.0.0" ] && web_cmd="${web_cmd} --web-server-addr $html_addr"
[ "$disable_registration" = "1" ] && web_cmd="${web_cmd} --disable-registration"
[ "$allow_auto_create_user" = "1" ] && web_cmd="${web_cmd} --allow-auto-create-user"
# EasyTier 2.7.0 新增的 web 参数
[ -z "$heartbeat_min_response_ms" ] || web_cmd="${web_cmd} --heartbeat-min-response-ms $heartbeat_min_response_ms"
[ -z "$heartbeat_timeout_ms" ] || web_cmd="${web_cmd} --heartbeat-timeout-ms $heartbeat_timeout_ms"
[ -z "$gateway_peer_url" ] || web_cmd="${web_cmd} --gateway-peer-url $gateway_peer_url"
[ "$gateway_relay_data" = "1" ] && web_cmd="${web_cmd} --gateway-relay-data true"
# OIDC配置参数
if [ "$oidc_enabled" = "1" ] ; then
[ -z "$oidc_issuer_url" ] || web_cmd="${web_cmd} --oidc-issuer-url $oidc_issuer_url"
@@ -773,7 +868,9 @@ start_web() {
"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn
# procd 进程守护参数:threshold=3600 秒、timeout=5 秒、retry=20 次
# 存活满 1 小时后崩溃计数归零;1 小时内崩溃超过 20 次(即第 21 次)才判定崩溃循环并放弃拉起
procd_set_param respawn 3600 5 20
procd_close_instance
if [ ! -z "$web_port" ] && [ "$fw_web" = "1" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_web 放行服务端口 ${web_port} " >>/tmp/easytierweb.log
@@ -42,7 +42,7 @@ get_latest_version() {
# 如果获取失败,从 UCI 配置或使用默认版本
if [ -z "$tag" ]; then
tag=$(uci -q get easytier.@easytier[0].fallback_version)
[ -z "$tag" ] && tag="v2.6.2"
[ -z "$tag" ] && tag="v2.7.0"
fi
echo "$tag"
+1 -1
View File
@@ -2,4 +2,4 @@
# This file is the single source of truth for the EasyTier version
# Used by: easytier/Makefile, luci-app-easytier/Makefile, init.d/easytier
EASYTIER_VERSION=2.6.4
EASYTIER_VERSION=2.7.0