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

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

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
# Copyright (C) 2020-2026 Hyy2001X <https://github.com/Hyy2001X>
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-iperf3-server
PKG_VERSION:=3.0.0
PKG_RELEASE:=1
PKG_LICENSE:=GPL-3.0-or-later
LUCI_TITLE:=LuCI support for iPerf3 Server
LUCI_DESCRIPTION:=Configure and manage multiple iperf3 server instances with LuCI status/actions.
LUCI_DEPENDS:=+iperf3
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
+3
View File
@@ -0,0 +1,3 @@
# luci-app-iperf3-server
一个简单的 iPerf3 服务端
+185
View File
@@ -0,0 +1,185 @@
module("luci.controller.iperf3-server", package.seeall)
function index()
local fs = require "nixio.fs"
if not fs.access("/etc/config/iperf3-server") then
return
end
local page
page = entry({"admin", "services", "iperf3-server"}, cbi("iperf3-server"), _("iPerf3 Server"), 99)
page.acl_depends = { "luci-app-iperf3-server" }
page = entry({"admin", "services", "iperf3-server", "status"}, call("act_status"))
page.leaf = true
page.acl_depends = { "luci-app-iperf3-server" }
page = entry({"admin", "services", "iperf3-server", "start"}, call("act_start"))
page.leaf = true
page.acl_depends = { "luci-app-iperf3-server" }
page = entry({"admin", "services", "iperf3-server", "stop"}, call("act_stop"))
page.leaf = true
page.acl_depends = { "luci-app-iperf3-server" }
page = entry({"admin", "services", "iperf3-server", "restart"}, call("act_restart"))
page.leaf = true
page.acl_depends = { "luci-app-iperf3-server" }
end
local function json_ok(extra)
local e = extra or {}
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function act_status()
local uci = require("luci.model.uci").cursor()
local sys = require("luci.sys")
local result = {
running = false, -- 是否任意一个 server 正常运行
servers = {} -- 每个端口的详细状态
}
-- 解析 /proc/net/tcp & /proc/net/tcp6,判断端口是否 LISTEN
local function is_listening_in_proc(path, port)
local f = io.open(path, "r")
if not f then return false end
local hex = string.format("%04X", port) -- 端口 16 进制,大写,固定 4 位
-- /proc/net/tcp 列格式: sl local_address rem_address st ...
-- local_address: AAAAAAAA:PPPP
-- st = 0A 表示 LISTEN
for line in f:lines() do
-- 跳过表头
if not line:match("^%s*sl%s+") then
local st = line:match("^%s*%d+:%s+%x+:" .. hex .. "%s+%x+:%x+%s+(%x+)")
if st == "0A" then
f:close()
return true
end
end
end
f:close()
return false
end
local function is_listening(port)
-- IPv4 LISTEN
if is_listening_in_proc("/proc/net/tcp", port) then
return true
end
-- IPv6 LISTEN(有的系统会只在 tcp6 里出现)
if is_listening_in_proc("/proc/net/tcp6", port) then
return true
end
return false
end
-- 用 ps 判断:是否存在 iperf3 server / delay 阶段 sleep+iperf3
local ps = sys.exec("ps w 2>/dev/null") or ""
local function has_iperf3_server_proc(port)
local p = tostring(port)
for line in ps:gmatch("[^\r\n]+") do
-- 只要这行同时包含:
-- 1) iperf3
-- 2) -s(server)
-- 3) -p 端口(支持 "-p 5201" 或 "-p5201")
if line:find("iperf3", 1, true) then
local hasS = line:match("%-s") ~= nil
local hasP = (line:match("%-p%s*" .. p .. "%f[^%d]") ~= nil) or (line:match("%-p" .. p .. "%f[^%d]") ~= nil)
if hasS and hasP then
return true
end
end
end
return false
end
local function has_delay_pending_proc(port)
local p = tostring(port)
for line in ps:gmatch("[^\r\n]+") do
-- 匹配 sh -c "sleep N; exec iperf3 -s -p <port> ..."
if (line:find("/bin/sh", 1, true) or line:find("sh -c", 1, true))
and line:find("sleep", 1, true)
and line:find("iperf3", 1, true)
and (line:match("%-p%s*" .. p .. "%f[^%d]") or line:match("%-p" .. p .. "%f[^%d]"))
then
return true
end
end
return false
end
uci:foreach("iperf3-server", "servers", function(s)
local port = tonumber(s.port)
local enabled = (s.enable_server == "1")
if not port then
return
end
local listen = is_listening(port)
local iperf3_proc = has_iperf3_server_proc(port)
local delay_pending = has_delay_pending_proc(port)
local state, detail
if not enabled then
state = "disabled"
detail = "detail_disabled"
else
if delay_pending and not listen then
state = "delay"
detail = "detail_delay"
elseif listen and iperf3_proc then
state = "running"
detail = "detail_running"
result.running = true
elseif listen and not iperf3_proc then
state = "conflict"
detail = "detail_conflict"
else
state = "stopped"
detail = "detail_stopped"
end
end
result.servers[#result.servers + 1] = {
port = port,
enable = enabled,
listen = listen,
state = state,
detail = detail
}
end)
luci.http.prepare_content("application/json")
luci.http.write_json(result)
end
function act_start()
local rc = luci.sys.call("/etc/init.d/iperf3-server start >/dev/null 2>&1")
json_ok({ ok = (rc == 0) })
end
function act_stop()
local rc = luci.sys.call("/etc/init.d/iperf3-server stop >/dev/null 2>&1")
json_ok({ ok = (rc == 0) })
end
function act_restart()
local rc = luci.sys.call("/etc/init.d/iperf3-server restart >/dev/null 2>&1")
json_ok({ ok = (rc == 0) })
end
+95
View File
@@ -0,0 +1,95 @@
m = Map("iperf3-server",
translate("iPerf3 Server"),
translate("iPerf3 - The ultimate speed test tool for TCP, UDP and SCTP")
)
m:section(SimpleSection).template = "iperf3-server/iperf3-server_status"
-- 主配置段
local g = m:section(TypedSection, "iperf3-server", "")
g.addremove = false
g.anonymous = true
local main_enable = g:option(Flag, "main_enable",
translate("Enable"),
translate("Enable iPerf3 Servers")
)
main_enable.default = "0"
main_enable.rmempty = false
-- servers 表格
local s = m:section(TypedSection, "servers",
translate("Server Settings"),
translate("Set up Multi-iPerf3 Servers")
)
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
local enable_server = s:option(Flag, "enable_server", translate("Enable"))
enable_server.default = "1"
enable_server.rmempty = false
local port = s:option(Value, "port", translate("Port"))
port.datatype = "port"
port.default = "5201"
port.rmempty = true -- 关键:允许新增行先空着,避免“点添加无反应”
function port.validate(self, value, section)
-- 新增行刚创建时可能是空值:先放行,让它能显示出来
if value == nil or value == "" then
return value
end
local v = tonumber(value)
if not v or v < 1 or v > 65535 then
return nil, translate("Invalid port.")
end
-- 端口去重:仅在有值时检查
local dup = false
m.uci:foreach("iperf3-server", "servers", function(s2)
if s2[".name"] ~= section and s2.port and tonumber(s2.port) == v then
dup = true
end
end)
if dup then
return nil, translate("Port must be unique.")
end
return tostring(v)
end
local delay = s:option(Value, "delay", translate("Start delay (Seconds)"))
delay.default = "0"
delay.datatype = "uinteger"
delay.rmempty = true -- 同理:新增行先允许空
function delay.validate(self, value, section)
if value == nil or value == "" then
return value
end
local v = tonumber(value)
if v == nil or v < 0 or v > 3600 then
return nil, translate("Delay must be between 0 and 3600 seconds.")
end
return tostring(v)
end
local extra_options = s:option(Value, "extra_options", translate("Extra Options"))
extra_options.rmempty = true
extra_options.password = false
function extra_options.validate(self, value, section)
if not value or value == "" then
return value
end
-- 简单拦截 shell 元字符(安全兜底)
if value:match("[;&|`$()<>\"']") then
return nil, translate("Invalid characters in Extra Options.")
end
return value
end
return m
@@ -0,0 +1,181 @@
<fieldset class="cbi-section">
<legend><%:Status%></legend>
<div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
<div id="iperf3_status">-</div>
<button class="btn cbi-button cbi-button-apply" id="btn_start" type="button"><%:Start%></button>
<button class="btn cbi-button cbi-button-reset" id="btn_stop" type="button"><%:Stop%></button>
<button class="btn cbi-button cbi-button-action" id="btn_restart" type="button"><%:Restart%></button>
<span id="iperf3_busy" style="display:none; color:#999;">...</span>
</div>
<div id="iperf3_list" style="margin-top:8px;"></div>
</fieldset>
<script type="text/javascript">
//<![CDATA[
(function () {
var token = '<%=token%>';
function esc(s) {
if (s === null || s === undefined) return '';
return String(s).replace(/[&<>"']/g, function (c) {
return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]);
});
}
function setBusy(b) {
document.getElementById('iperf3_busy').style.display = b ? '' : 'none';
document.getElementById('btn_start').disabled = b;
document.getElementById('btn_stop').disabled = b;
document.getElementById('btn_restart').disabled = b;
}
function badge(text, color) {
return '<span style="color:' + color + '">' + text + '</span>';
}
function stateBadge(it) {
// 新字段优先:it.state;旧字段兼容:it.running
var st = it.state;
if (!st) {
if (it.enable === false || it.enable === "0") st = "disabled";
else st = it.running ? "running" : "stopped";
}
if (st === "running") return badge('<%:Running%>', 'green');
if (st === "delay") return badge('<%:Delay%>', '#d67f00');
if (st === "conflict") return badge('<%:Conflict%>', 'red');
if (st === "disabled") return badge('<%:Disabled%>', '#999');
return badge('<%:Stopped%>', '#999');
}
function overallBadge(data) {
// controller 返回 data.running=true 表示至少一个 running
// 如果没有 running,但存在 delay/conflict,也给更准确的总体状态
if (data && data.running) {
return badge('<%:Running%>', 'green');
}
var hasDelay = false, hasConflict = false;
if (data && data.servers && data.servers.length) {
for (var i = 0; i < data.servers.length; i++) {
var st = data.servers[i].state;
if (st === "delay") hasDelay = true;
else if (st === "conflict") hasConflict = true;
}
}
if (hasConflict) return badge('<%:Conflict%>', 'red');
if (hasDelay) return badge('<%:Delay%>', '#d67f00');
return badge('<%:Not running%>', 'red');
}
function translateDetail(detail) {
// detail 来自 controller 的英文字符串,这里做映射为可翻译字符串
// 未命中映射时,直接原样显示
var map = {
"disabled in config": "<%:disabled in config%>",
"delay pending (sleep)": "<%:delay pending (sleep)%>",
"listening and iperf3 process found": "<%:listening and iperf3 process found%>",
"port is listening but not iperf3 (occupied)": "<%:port is listening but not iperf3 (occupied)%>",
"not listening": "<%:not listening%>"
};
return map[detail] || detail || "";
}
function renderStatus(data) {
var s = document.getElementById('iperf3_status');
s.innerHTML = overallBadge(data);
var list = document.getElementById('iperf3_list');
var html = '';
if (data && data.servers && data.servers.length) {
html += '<ul>';
for (var i = 0; i < data.servers.length; i++) {
var it = data.servers[i];
var port = esc(it.port);
var en = (it.enable !== false) && (it.enable !== "0");
// 旧字段 pid 兼容
var pidInfo = '';
if (it.pid) {
pidInfo = ' <span style="color:#999"><%:PID%> ' + esc(it.pid) + '</span>';
}
// 监听信息(新字段 listen)
var listenInfo = '';
if (typeof it.listen === "boolean") {
listenInfo = it.listen
? ' <span style="color:#999">[<%:LISTEN%>]</span>'
: ' <span style="color:#999">[<%:NO LISTEN%>]</span>';
}
// detail(来自 controller)
var dt = translateDetail(it.detail);
var detail = dt ? (' <span style="color:#999">(' + esc(dt) + ')</span>') : '';
html += '<li>'
+ '<%:Port%> ' + port + ' : '
+ stateBadge(it)
+ pidInfo
+ listenInfo
+ detail
+ (en ? '' : ' <span style="color:#999">(<%:disabled%>)</span>')
+ '</li>';
}
html += '</ul>';
// 状态说明(简短)
html += '<div style="margin-top:6px; color:#999;">'
+ '<span><%:Delay%></span>: <%:starting after configured delay%> &nbsp; '
+ '<span><%:Conflict%></span>: <%:port is occupied%>'
+ '</div>';
}
list.innerHTML = html;
}
function refresh() {
XHR.get('<%=luci.dispatcher.build_url("admin/services/iperf3-server/status")%>', null,
function (x, data) { renderStatus(data || {}); }
);
}
function doAction(url, confirmText) {
if (confirmText && !window.confirm(confirmText)) return;
setBusy(true);
XHR.post(url, { token: token }, function (x, data) {
setBusy(false);
refresh();
});
}
document.getElementById('btn_start').addEventListener('click', function () {
doAction('<%=luci.dispatcher.build_url("admin/services/iperf3-server/start")%>', '<%:Start iPerf3 servers?%>');
});
document.getElementById('btn_stop').addEventListener('click', function () {
doAction('<%=luci.dispatcher.build_url("admin/services/iperf3-server/stop")%>', '<%:Stop iPerf3 servers?%>');
});
document.getElementById('btn_restart').addEventListener('click', function () {
doAction('<%=luci.dispatcher.build_url("admin/services/iperf3-server/restart")%>', '<%:Restart iPerf3 servers?%>');
});
// 轮询刷新
XHR.poll(2, '<%=luci.dispatcher.build_url("admin/services/iperf3-server/status")%>', null,
function (x, data) { renderStatus(data || {}); }
);
// 首次刷新
refresh();
})();
//]]>
</script>
+123
View File
@@ -0,0 +1,123 @@
#
# App / Menu
#
msgid "iPerf3 Server"
msgstr "iPerf3 服务器"
msgid "iPerf3 - The ultimate speed test tool for TCP, UDP and SCTP"
msgstr "iPerf3 是一款集 TCP、UDP 和 SCTP 于一身的终极速度测试工具"
#
# CBI (model)
#
msgid "Enable"
msgstr "启用"
msgid "Enable iPerf3 Servers"
msgstr "启用 iPerf3 服务器"
msgid "Server Settings"
msgstr "服务端设置"
msgid "Set up Multi-iPerf3 Servers"
msgstr "设置多个 iPerf3 服务端"
msgid "Port"
msgstr "端口"
msgid "Start delay (Seconds)"
msgstr "启动延迟(秒)"
msgid "Extra Options"
msgstr "额外参数"
msgid "Invalid port."
msgstr "端口无效。"
msgid "Port must be unique."
msgstr "端口必须唯一,不能重复。"
msgid "Delay must be between 0 and 3600 seconds."
msgstr "启动延迟必须在 0 到 3600 秒之间。"
msgid "Invalid characters in Extra Options."
msgstr "额外参数包含非法字符。"
#
# Status page (.htm)
#
msgid "Status"
msgstr "状态"
msgid "Running"
msgstr "运行中"
msgid "Not running"
msgstr "未运行"
msgid "Stopped"
msgstr "已停止"
msgid "Start"
msgstr "启动"
msgid "Stop"
msgstr "停止"
msgid "Restart"
msgstr "重启"
msgid "Delay"
msgstr "延迟中"
msgid "Conflict"
msgstr "端口冲突"
msgid "Disabled"
msgstr "已禁用"
msgid "disabled"
msgstr "已禁用"
msgid "PID"
msgstr "进程号"
msgid "LISTEN"
msgstr "监听中"
msgid "NO LISTEN"
msgstr "未监听"
msgid "starting after configured delay"
msgstr "将在设定的延迟时间后启动"
msgid "port is occupied"
msgstr "端口已被其他服务占用"
msgid "Start iPerf3 servers?"
msgstr "确认启动 iPerf3 服务?"
msgid "Stop iPerf3 servers?"
msgstr "确认停止 iPerf3 服务?"
msgid "Restart iPerf3 servers?"
msgstr "确认重启 iPerf3 服务?"
#
# Controller detail keys (act_status -> detail)
# 你现在返回的是 detail_* key,前端应做 key->文本映射或直接 <%:detail_xxx%>
#
msgid "detail_disabled"
msgstr "已在配置中禁用"
msgid "detail_delay"
msgstr "延迟启动中"
msgid "detail_running"
msgstr "正在监听,且 iPerf3 进程已启动"
msgid "detail_conflict"
msgstr "端口正在监听,但不是 iPerf3(被占用)"
msgid "detail_stopped"
msgstr "未监听端口"
+123
View File
@@ -0,0 +1,123 @@
#
# App / Menu
#
msgid "iPerf3 Server"
msgstr "iPerf3 服务器"
msgid "iPerf3 - The ultimate speed test tool for TCP, UDP and SCTP"
msgstr "iPerf3 是一款集 TCP、UDP 和 SCTP 于一身的终极速度测试工具"
#
# CBI (model)
#
msgid "Enable"
msgstr "启用"
msgid "Enable iPerf3 Servers"
msgstr "启用 iPerf3 服务器"
msgid "Server Settings"
msgstr "服务端设置"
msgid "Set up Multi-iPerf3 Servers"
msgstr "设置多个 iPerf3 服务端"
msgid "Port"
msgstr "端口"
msgid "Start delay (Seconds)"
msgstr "启动延迟(秒)"
msgid "Extra Options"
msgstr "额外参数"
msgid "Invalid port."
msgstr "端口无效。"
msgid "Port must be unique."
msgstr "端口必须唯一,不能重复。"
msgid "Delay must be between 0 and 3600 seconds."
msgstr "启动延迟必须在 0 到 3600 秒之间。"
msgid "Invalid characters in Extra Options."
msgstr "额外参数包含非法字符。"
#
# Status page (.htm)
#
msgid "Status"
msgstr "状态"
msgid "Running"
msgstr "运行中"
msgid "Not running"
msgstr "未运行"
msgid "Stopped"
msgstr "已停止"
msgid "Start"
msgstr "启动"
msgid "Stop"
msgstr "停止"
msgid "Restart"
msgstr "重启"
msgid "Delay"
msgstr "延迟中"
msgid "Conflict"
msgstr "端口冲突"
msgid "Disabled"
msgstr "已禁用"
msgid "disabled"
msgstr "已禁用"
msgid "PID"
msgstr "进程号"
msgid "LISTEN"
msgstr "监听中"
msgid "NO LISTEN"
msgstr "未监听"
msgid "starting after configured delay"
msgstr "将在设定的延迟时间后启动"
msgid "port is occupied"
msgstr "端口已被其他服务占用"
msgid "Start iPerf3 servers?"
msgstr "确认启动 iPerf3 服务?"
msgid "Stop iPerf3 servers?"
msgstr "确认停止 iPerf3 服务?"
msgid "Restart iPerf3 servers?"
msgstr "确认重启 iPerf3 服务?"
#
# Controller detail keys (act_status -> detail)
# 你现在返回的是 detail_* key,前端应做 key->文本映射或直接 <%:detail_xxx%>
#
msgid "detail_disabled"
msgstr "已在配置中禁用"
msgid "detail_delay"
msgstr "延迟启动中"
msgid "detail_running"
msgstr "正在监听,且 iPerf3 进程已启动"
msgid "detail_conflict"
msgstr "端口正在监听,但不是 iPerf3(被占用)"
msgid "detail_stopped"
msgstr "未监听端口"
+3
View File
@@ -0,0 +1,3 @@
config iperf3-server 'main'
option main_enable '0'
+184
View File
@@ -0,0 +1,184 @@
#!/bin/sh /etc/rc.common
# iPerf3 Server init (OpenWrt 23.05) - procd multi-instance (UCI-only fetch)
# UCI: /etc/config/iperf3-server
#
# config iperf3-server
# option main_enable '1'
#
# config servers
# option enable_server '1'
# option port '5201'
# option delay '0'
# option extra_options ''
USE_PROCD=1
START=99
PROG="/usr/bin/iperf3"
CONFIG="iperf3-server"
LOGGER="logger -t iPerf3-Server"
# 与 CBI 的 extra_options 校验保持一致:拦截明显 shell 元字符
is_safe_extra_opts() {
[ -z "$1" ] && return 0
case "$1" in
*";"*|*"&"*|*"|"*|*"\`"*|*"\$"*|*"<"*|*">"*|*"\""*|*"'"* )
return 1
;;
esac
return 0
}
# 停止某端口:既杀 iperf3 server,也杀 delay 阶段的 sh/sleep
kill_by_port() {
local port="$1"
local pids
# 1) 已经起来的 iperf3 server
pids="$(ps w | grep '[i]perf3' | grep ' -s' | grep " -p $port" | awk '{print $1}')"
[ -n "$pids" ] && kill $pids 2>/dev/null
# 2) delay 阶段:/bin/sh -c "sleep N; exec iperf3 -s -p PORT ..."
pids="$(ps w | grep '[s]leep' | grep '[i]perf3' | grep " -p $port" | awk '{print $1}')"
[ -n "$pids" ] && kill $pids 2>/dev/null
sleep 1
# hard kill 兜底
pids="$(ps w | grep '[i]perf3' | grep ' -s' | grep " -p $port" | awk '{print $1}')"
[ -n "$pids" ] && kill -9 $pids 2>/dev/null
pids="$(ps w | grep '[s]leep' | grep '[i]perf3' | grep " -p $port" | awk '{print $1}')"
[ -n "$pids" ] && kill -9 $pids 2>/dev/null
}
# 获取 servers 索引列表(0 1 2 ...)
get_server_indexes() {
# 从 uci show 里提取 @servers[NUM]
uci -q show "$CONFIG" 2>/dev/null \
| sed -n "s/^$CONFIG\.@servers\[\([0-9]\+\)\]=servers$/\1/p"
}
start_one_idx() {
local idx="$1"
local enable_server port delay extra_options
enable_server="$(uci -q get $CONFIG.@servers[$idx].enable_server)"
port="$(uci -q get $CONFIG.@servers[$idx].port)"
delay="$(uci -q get $CONFIG.@servers[$idx].delay)"
extra_options="$(uci -q get $CONFIG.@servers[$idx].extra_options)"
# 默认值
[ -n "$enable_server" ] || enable_server="1"
[ -n "$port" ] || port="5201"
[ -n "$delay" ] || delay="0"
[ "$enable_server" = "1" ] || return 0
# 端口合法性兜底
case "$port" in
*[!0-9]*|'') $LOGGER "Skip invalid port in @servers[$idx]"; return 0 ;;
esac
[ "$port" -ge 1 ] 2>/dev/null && [ "$port" -le 65535 ] 2>/dev/null || {
$LOGGER "Skip invalid port [$port] in @servers[$idx]"
return 0
}
# delay 合法性兜底
case "$delay" in
*[!0-9]*|'') delay=0 ;;
esac
# extra_options 安全兜底
if [ -n "$extra_options" ] && ! is_safe_extra_opts "$extra_options"; then
$LOGGER "Rejected unsafe extra_options on port [$port] (@servers[$idx])"
extra_options=""
fi
$LOGGER "Starting @servers[$idx] port=[$port] delay=[$delay] ..."
# procd 实例名按端口
procd_open_instance "iperf3_${port}"
# 不用 -D;delay 用 sh -c 不阻塞
if [ "$delay" -gt 0 ] 2>/dev/null; then
if [ -n "$extra_options" ]; then
procd_set_param command /bin/sh -c "sleep $delay; exec $PROG -s -p $port $extra_options"
else
procd_set_param command /bin/sh -c "sleep $delay; exec $PROG -s -p $port"
fi
else
if [ -n "$extra_options" ]; then
procd_set_param command "$PROG" -s -p "$port" $extra_options
else
procd_set_param command "$PROG" -s -p "$port"
fi
fi
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
start_service() {
[ -x "$PROG" ] || { $LOGGER "iperf3 not found: $PROG"; return 1; }
# 主开关:直接用 uci 读取匿名段
local main_enable
main_enable="$(uci -q get $CONFIG.@iperf3-server[0].main_enable)"
[ -n "$main_enable" ] || main_enable="0"
[ "$main_enable" = "1" ] || {
$LOGGER "iPerf3 Server is disabled ..."
return 0
}
# 遍历 servers 索引
local idx
for idx in $(get_server_indexes); do
start_one_idx "$idx"
done
return 0
}
stop_one_idx() {
local idx="$1"
local enable_server port
enable_server="$(uci -q get $CONFIG.@servers[$idx].enable_server)"
[ -n "$enable_server" ] || enable_server="1"
[ "$enable_server" = "1" ] || return 0
port="$(uci -q get $CONFIG.@servers[$idx].port)"
[ -n "$port" ] || port="5201"
case "$port" in
*[!0-9]*|'') return 0 ;;
esac
kill_by_port "$port"
}
stop_service() {
$LOGGER "Stopping iPerf3 Server ..."
# 按 servers 列表逐个端口清理(不误杀其它 iperf3)
local idx
for idx in $(get_server_indexes); do
stop_one_idx "$idx"
done
return 0
}
reload_service() {
# /etc/init.d/iperf3-server reload
stop
start
}
service_triggers() {
procd_add_reload_trigger "$CONFIG"
}
@@ -0,0 +1,22 @@
#!/bin/sh
[ ! -f "/usr/share/ucitrack/luci-app-iperf3-server.json" ] && {
cat > /usr/share/ucitrack/luci-app-iperf3-server.json << EEOF
{
"config": "iperf3-server",
"init": "iperf3-server"
}
EEOF
}
# 已存在就不重复写
if uci -q show ucitrack | grep -q "=iperf3-server"; then
exit 0
fi
uci -q batch <<'EOF' >/dev/null
add ucitrack iperf3-server
set ucitrack.@iperf3-server[-1].init='iperf3-server'
commit ucitrack
EOF
exit 0
@@ -0,0 +1,14 @@
{
"luci-app-iperf3-server": {
"description": "Grant access to LuCI app iPerf3 Server",
"read": {
"uci": [ "iperf3-server" ]
},
"write": {
"uci": [ "iperf3-server" ],
"file": {
"/etc/init.d/iperf3-server": [ "exec" ]
}
}
}
}