🦄 Sync 2026-09-21 17:33:30

This commit is contained in:
github-actions[bot] committed 2026-09-21 17:33:31 +08:00
1 parent 1e082ed4ab
commit 4294063e8f
23 files changed
+831 -202

No files matched your search

+2 -2
View File
@@ -8,8 +8,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=apfree-wifidog
PKG_VERSION:=9.09.2938
PKG_RELEASE:=1
PKG_VERSION:=9.09.2947
PKG_RELEASE:=2
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/liudf0716/$(PKG_NAME)/tar.gz/$(PKG_VERSION)?
+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=dae
PKG_VERSION:=2026.09.18
PKG_RELEASE:=45
PKG_VERSION:=2026.09.20
PKG_RELEASE:=46
PKG_SOURCE:=dae-src-2026.09.18-dc29e60bab23.tar.gz
PKG_SOURCE:=dae-src-2026.09.20-2a846ee5a7e4.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip
+12 -7
View File
@@ -5,15 +5,20 @@
KPROBE_SKB_AT(3)
KPROBE_SKB_AT(4)
-KPROBE_SKB_AT(5)
SEC("kprobe/skb_lifetime_termination")
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
--- a/trace/trace.go
+++ b/trace/trace.go
@@ -191,7 +191,6 @@ func attachBpfToTargets(objs *bpfObjects, targets map[string]int) (links []link.Link, err error) {
1: objs.KprobeSkb1,
2: objs.KprobeSkb2,
3: objs.KprobeSkb3,
4: objs.KprobeSkb4,
@@ -191,11 +191,10 @@
progByPos := map[int]*ebpf.Program{
1: objs.KprobeSkb1,
2: objs.KprobeSkb2,
3: objs.KprobeSkb3,
4: objs.KprobeSkb4,
- 5: objs.KprobeSkb5,
}
}
symbolsByPos := make(map[int][]string, len(progByPos))
for fn, pos := range targets {
if _, ok := progByPos[pos]; !ok {
continue
+3 -3
View File
@@ -5,10 +5,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=daed
PKG_VERSION:=2026.09.18
PKG_RELEASE:=56
PKG_VERSION:=2026.09.20
PKG_RELEASE:=57
PKG_SOURCE:=daed-src-2026.09.18-0d4c9d0c95b9.tar.gz
PKG_SOURCE:=daed-src-2026.09.20-d00211e69e94.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_HASH:=skip
+12 -7
View File
@@ -5,15 +5,20 @@
KPROBE_SKB_AT(3)
KPROBE_SKB_AT(4)
-KPROBE_SKB_AT(5)
SEC("kprobe/skb_lifetime_termination")
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
--- a/dae-core/trace/trace.go
+++ b/dae-core/trace/trace.go
@@ -191,7 +191,6 @@ func attachBpfToTargets(objs *bpfObjects, targets map[string]int) (links []link.Link, err error) {
1: objs.KprobeSkb1,
2: objs.KprobeSkb2,
3: objs.KprobeSkb3,
4: objs.KprobeSkb4,
@@ -191,11 +191,10 @@
progByPos := map[int]*ebpf.Program{
1: objs.KprobeSkb1,
2: objs.KprobeSkb2,
3: objs.KprobeSkb3,
4: objs.KprobeSkb4,
- 5: objs.KprobeSkb5,
}
}
symbolsByPos := make(map[int][]string, len(progByPos))
for fn, pos := range targets {
if _, ok := progByPos[pos]; !ok {
continue
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=AgentFlow
PKG_VERSION:=1.0.1
PKG_RELEASE:=6
PKG_RELEASE:=7
LUCI_DEPENDS:=+agentflow +luci-compat +luci-lib-taskd
LUCI_MINIFY_CSS:=0
LUCI_MINIFY_JS:=0
@@ -5,6 +5,13 @@ module("luci.controller.agentflow", package.seeall)
local APPS_PROXY_PREFIX = "/apps=http://127.0.0.1:19290"
local DEFAULT_BASE_PATH = "/apps/agentflow/"
local DEFAULT_PORT = 9000
local AGENTS = {
{ id = "codexcli", package = "@openai/codex" },
{ id = "claude-code", package = "@anthropic-ai/claude-code" },
{ id = "opencode", package = "opencode-ai" },
{ id = "kimi", package = "@moonshot-ai/kimi-code" },
{ id = "reasonix", package = "reasonix" }
}
function index()
entry({"admin", "services", "agentflow_status"}, call("agentflow_status"))
@@ -133,10 +140,54 @@ local function agentflow_entry_url()
return "http://" .. url_authority(request_or_lan_host(), port) .. base_path
end
local function node_modules_root()
local sys = require "luci.sys"
local util = require "luci.util"
local script = table.concat({
'. /lib/functions/mise.sh 2>/dev/null || exit 1',
'istore_runtime_env >/dev/null 2>&1 || exit 1',
'mise_bin="$(command -v mise-istore || command -v mise)"',
'[ -n "$mise_bin" ] || exit 1',
'node_dir="$("$mise_bin" where node@lts 2>/dev/null)"',
'[ -n "$node_dir" ] || exit 1',
'printf "%s/lib/node_modules" "$node_dir"'
}, "; ")
local root = sys.exec("/bin/sh -c " .. util.shellquote(script)) or ""
root = root:match("^%s*(.-)%s*$")
if root == "" or root:sub(1, 1) ~= "/" then
return nil
end
return root
end
local function agent_statuses()
local fs = require "nixio.fs"
local jsonc = require "luci.jsonc"
local root = node_modules_root()
local statuses = {}
for _, agent in ipairs(AGENTS) do
local status = { id = agent.id, installed = false }
if root then
local package_file = root .. "/" .. agent.package .. "/package.json"
local package_data = fs.readfile(package_file)
local package_json = package_data and jsonc.parse(package_data) or nil
if type(package_json) == "table" then
status.installed = true
status.version = package_json.version
end
end
statuses[#statuses + 1] = status
end
return statuses, root ~= nil
end
function agentflow_status()
local sys = require "luci.sys"
local port, base_path = agentflow_config()
local entry_url = agentflow_entry_url()
local agents, agents_available = agent_statuses()
local status = {
running = (sys.call("pidof agentflow >/dev/null") == 0),
@@ -145,7 +196,9 @@ function agentflow_status()
entry_url = entry_url,
proxy_prefix_supported = uhttpd_supports_proxy_prefix(),
proxy_prefix_enabled = uhttpd_apps_proxy_available(),
linkeasefull_running = linkeasefull_running()
linkeasefull_running = linkeasefull_running(),
agents_available = agents_available,
agents = agents
}
write_json(status)
end
@@ -158,20 +211,17 @@ function agentflow_agent_install()
local task_id = "agentflow-agent-install"
local task_script = "/tmp/agentflow-agent-install.sh"
local installer_url = "https://fw.koolcenter.com/binary/geili/agentflow/releases/installapp/installapp-mise.sh"
local agents = {
codexcli = true,
["claude-code"] = true,
opencode = true,
kimi = true,
reasonix = true
}
local supported_agents = {}
for _, supported_agent in ipairs(AGENTS) do
supported_agents[supported_agent.id] = true
end
if not require_post_csrf() then
return
end
local agent = http.formvalue("agent") or ""
if not agents[agent] then
if not supported_agents[agent] then
write_json({ ok = false, error = "unsupported agent" })
return
end
@@ -1,58 +1,378 @@
<%+tasks/embed%>
<style type="text/css">
.agentflow-actions { display:flex; gap:10px; flex-wrap:wrap; margin:12px; }
.agentflow-actions .btn, .agentflow-agent-dialog-actions .btn { min-height:40px; }
.agentflow-agent-modal { position:fixed; inset:0; z-index:10000; display:none; align-items:center; justify-content:center; padding:20px; box-sizing:border-box; background:rgba(15,23,42,.45); }
.agentflow-shell {
--af-panel:#fff;
--af-panel-soft:#f6f8fc;
--af-border:#dfe5ef;
--af-text:#17213d;
--af-muted:#667397;
--af-primary:#356cf6;
--af-primary-hover:#2559d8;
--af-success:#159447;
--af-success-soft:#e9f8ef;
--af-shadow:0 8px 24px rgba(46, 67, 112, .08);
color:var(--af-text);
margin:12px 0 20px;
}
.agentflow-panel {
background:var(--af-panel);
border:1px solid var(--af-border);
border-radius:10px;
box-shadow:var(--af-shadow);
box-sizing:border-box;
}
.agentflow-overview {
display:grid;
grid-template-columns:minmax(260px, .9fr) minmax(320px, 1.1fr);
gap:28px;
padding:24px;
}
.agentflow-service {
display:flex;
align-items:flex-start;
gap:16px;
}
.agentflow-service-icon {
display:flex;
align-items:center;
justify-content:center;
width:54px;
height:54px;
flex:0 0 54px;
border-radius:50%;
background:#eef1f6;
color:var(--af-muted);
font-size:28px;
font-weight:700;
}
.agentflow-service-icon.running {
background:var(--af-success-soft);
color:var(--af-success);
}
.agentflow-service-copy { min-width:0; }
.agentflow-service-copy h3 {
margin:2px 0 6px;
color:var(--af-text);
font-size:20px;
line-height:1.3;
}
.agentflow-service-copy p {
margin:0 0 16px;
color:var(--af-muted);
line-height:1.6;
}
.agentflow-access {
padding-left:28px;
border-left:1px solid var(--af-border);
}
.agentflow-access label {
display:block;
margin:0 0 9px;
font-weight:700;
}
.agentflow-address-row {
display:flex;
gap:8px;
}
.agentflow-address-row input[type="text"] {
min-width:0;
flex:1 1 auto;
height:40px;
box-sizing:border-box;
border:1px solid var(--af-border);
border-radius:7px;
background:var(--af-panel-soft);
color:var(--af-text);
font-variant-numeric:tabular-nums;
}
.agentflow-access-note {
min-height:20px;
margin:10px 0 0;
color:var(--af-muted);
font-size:13px;
}
.agentflow-button {
min-height:40px;
box-sizing:border-box;
border-radius:7px !important;
white-space:nowrap;
}
.agentflow-primary {
border-color:var(--af-primary) !important;
background:var(--af-primary) !important;
color:#fff !important;
}
.agentflow-primary:hover { background:var(--af-primary-hover) !important; }
.agentflow-button:disabled { cursor:not-allowed; opacity:.55; }
.agentflow-agents { margin-top:16px; padding:22px 20px 20px; }
.agentflow-section-heading {
display:flex;
align-items:flex-start;
gap:12px;
margin-bottom:18px;
}
.agentflow-section-mark {
display:flex;
align-items:center;
justify-content:center;
width:38px;
height:38px;
flex:0 0 38px;
border-radius:9px;
background:#edf3ff;
color:var(--af-primary);
font-size:17px;
font-weight:800;
}
.agentflow-section-heading h3 { margin:0 0 4px; color:var(--af-text); font-size:20px; }
.agentflow-section-heading p { margin:0; color:var(--af-muted); line-height:1.55; }
.agentflow-agent-grid {
display:grid;
grid-template-columns:repeat(3, minmax(0, 1fr));
gap:14px;
}
.agentflow-agent-card {
display:flex;
min-width:0;
min-height:190px;
padding:16px;
box-sizing:border-box;
flex-direction:column;
border:1px solid var(--af-border);
border-radius:9px;
background:var(--af-panel);
}
.agentflow-agent-card[data-state="installed"] { border-color:#b8dfc6; }
.agentflow-agent-top { display:flex; align-items:flex-start; gap:12px; }
.agentflow-agent-logo {
display:flex;
align-items:center;
justify-content:center;
width:48px;
height:48px;
flex:0 0 48px;
border-radius:9px;
background:var(--agent-color, var(--af-primary));
color:#fff;
font-size:21px;
font-weight:800;
}
.agentflow-agent-copy { min-width:0; }
.agentflow-agent-title { margin:2px 0 4px; color:var(--af-text); font-size:17px; font-weight:700; line-height:1.4; }
.agentflow-agent-copy p { margin:0; color:var(--af-muted); font-size:13px; line-height:1.45; }
.agentflow-agent-meta {
display:flex;
align-items:center;
gap:10px;
margin:17px 0 12px;
color:var(--af-muted);
font-size:13px;
}
.agentflow-agent-state { display:inline-flex; align-items:center; gap:7px; font-weight:700; }
.agentflow-agent-dot { width:8px; height:8px; border-radius:50%; background:#8a95af; }
.agentflow-agent-card[data-state="installed"] .agentflow-agent-state { color:var(--af-success); }
.agentflow-agent-card[data-state="installed"] .agentflow-agent-dot { background:var(--af-success); box-shadow:0 0 0 4px var(--af-success-soft); }
.agentflow-agent-divider { color:#b4bdd0; }
.agentflow-install-button { width:100%; margin-top:auto; }
.agentflow-agent-card[data-state="installed"] .agentflow-install-button {
border-color:var(--af-success) !important;
background:var(--af-success-soft) !important;
color:var(--af-success) !important;
}
.agentflow-agent-modal {
position:fixed;
inset:0;
z-index:10000;
display:none;
align-items:center;
justify-content:center;
padding:20px;
box-sizing:border-box;
background:rgba(15, 23, 42, .48);
}
.agentflow-agent-modal.open { display:flex; }
.agentflow-agent-dialog, body[theme="light"] .agentflow-agent-dialog { width:min(520px, 100%); padding:20px; box-sizing:border-box; border:1px solid #d0d7de; border-radius:12px; background:#fff; color:inherit; box-shadow:0 18px 48px rgba(15,23,42,.24); }
.agentflow-agent-dialog h3 { margin:0 0 8px; text-wrap:balance; }
.agentflow-agent-dialog > p, body[theme="light"] .agentflow-agent-dialog > p { margin:0 0 16px; color:#666; text-wrap:pretty; }
.agentflow-agent-field label { display:block; margin-bottom:6px; font-weight:600; }
.agentflow-agent-field select { width:100%; min-height:40px; box-sizing:border-box; }
.agentflow-agent-dialog-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:18px; }
body[theme="dark"] .agentflow-agent-dialog { border-color:#465166; background:#1f2937; color:#e5e7eb; }
body[theme="dark"] .agentflow-agent-dialog > p { color:#aeb8c7; }
.agentflow-agent-dialog {
width:100%;
max-width:460px;
padding:22px;
box-sizing:border-box;
border:1px solid var(--af-border);
border-radius:10px;
background:var(--af-panel);
color:var(--af-text);
box-shadow:0 18px 48px rgba(15, 23, 42, .24);
}
.agentflow-agent-dialog h3 { margin:0 0 8px; color:var(--af-text); }
.agentflow-agent-dialog p { margin:0; color:var(--af-muted); line-height:1.6; }
.agentflow-agent-dialog strong { color:var(--af-text); }
.agentflow-dialog-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:20px; }
body[theme="dark"] .agentflow-shell {
--af-panel:#202733;
--af-panel-soft:#171d27;
--af-border:#3b4658;
--af-text:#edf2fb;
--af-muted:#adb8cb;
--af-primary:#5d88ff;
--af-primary-hover:#7197ff;
--af-success:#50c878;
--af-success-soft:rgba(80, 200, 120, .14);
--af-shadow:0 8px 24px rgba(0, 0, 0, .18);
}
body[theme="dark"] .agentflow-section-mark { background:rgba(93, 136, 255, .14); }
@media (prefers-color-scheme: dark) {
body:not([theme]) .agentflow-agent-dialog { border-color:#465166; background:#1f2937; color:#e5e7eb; }
body:not([theme]) .agentflow-agent-dialog > p { color:#aeb8c7; }
body:not([theme]) .agentflow-shell {
--af-panel:#202733;
--af-panel-soft:#171d27;
--af-border:#3b4658;
--af-text:#edf2fb;
--af-muted:#adb8cb;
--af-primary:#5d88ff;
--af-primary-hover:#7197ff;
--af-success:#50c878;
--af-success-soft:rgba(80, 200, 120, .14);
--af-shadow:0 8px 24px rgba(0, 0, 0, .18);
}
body:not([theme]) .agentflow-section-mark { background:rgba(93, 136, 255, .14); }
}
@media (max-width:1000px) {
.agentflow-agent-grid { grid-template-columns:repeat(2, minmax(0, 1fr)); }
}
@media (max-width:760px) {
.agentflow-overview { grid-template-columns:1fr; gap:20px; padding:18px; }
.agentflow-access { padding:18px 0 0; border-top:1px solid var(--af-border); border-left:0; }
.agentflow-agent-grid { grid-template-columns:1fr; }
.agentflow-agents { padding:18px 14px 14px; }
.agentflow-address-row { flex-wrap:wrap; }
.agentflow-address-row input[type="text"] { flex-basis:100%; }
.agentflow-address-row .agentflow-button { flex:1 1 auto; }
}
</style>
<script type="text/javascript">//<![CDATA[
var agentflowOpenUrl = '<%=url("admin/services/agentflow/open")%>';
var agentflowStatusUrl = '<%=url("admin/services/agentflow_status")%>';
var agentflowAgentInstallUrl = '<%=url("admin/services/agentflow/agent_install")%>';
var agentflowCsrfToken = (window.taskd && window.taskd.csrfToken) || '';
var agentflowSelectedAgent = '';
var agentflowAgentNames = {
codexcli: 'Codex',
'claude-code': 'Claude Code',
opencode: 'OpenCode',
kimi: 'Kimi',
reasonix: 'Reasonix'
};
var agentflowLabels = {
installed: '<%:Installed%>',
notInstalled: '<%:Not installed%>',
unknown: '<%:Status unavailable%>',
install: '<%:Install%>',
starting: '<%:Starting...%>',
copied: '<%:Copied%>'
};
function agentflowAgentDialog(open) {
function agentflowAbsoluteUrl(value) {
try { return new URL(value, window.location.href).href; }
catch (e) { return value || ''; }
}
function agentflowRenderStatus(st) {
var icon = document.getElementById('agentflow-service-icon');
var title = document.getElementById('agentflow-service-title');
var description = document.getElementById('agentflow-service-description');
var openButton = document.getElementById('agentflow-open');
var address = document.getElementById('agentflow-address');
var statuses = {};
var agents = st && st.agents || [];
var i;
if (!st) return;
if (icon) {
icon.className = 'agentflow-service-icon' + (st.running ? ' running' : '');
icon.textContent = st.running ? '\u2713' : '!';
}
if (title) title.textContent = st.running ? '<%:AgentFlow service is running%>' : '<%:AgentFlow service is not running%>';
if (description) description.textContent = st.running
? '<%:The service is ready. You can open AgentFlow or install coding agents.%>'
: '<%:Start the service to use AgentFlow. Agent installation remains available.%>';
if (openButton) openButton.disabled = !st.running;
if (address) address.value = agentflowAbsoluteUrl(st.entry_url || '');
for (i = 0; i < agents.length; i++) statuses[agents[i].id] = agents[i];
for (i in agentflowAgentNames) {
if (!agentflowAgentNames.hasOwnProperty(i)) continue;
var card = document.getElementById('agentflow-agent-' + i);
var state = document.getElementById('agentflow-agent-state-' + i);
var version = document.getElementById('agentflow-agent-version-' + i);
var installButton = document.getElementById('agentflow-agent-install-' + i);
var item = statuses[i];
var available = st.agents_available !== false && !!item;
var installed = available && item.installed === true;
if (card) card.setAttribute('data-state', installed ? 'installed' : (available ? 'available' : 'unknown'));
if (state) state.textContent = installed ? agentflowLabels.installed : (available ? agentflowLabels.notInstalled : agentflowLabels.unknown);
if (version) version.textContent = installed && item.version ? item.version : '-';
if (installButton) {
installButton.disabled = !available || installed;
installButton.value = installed ? agentflowLabels.installed : agentflowLabels.install;
}
}
}
function agentflowCopyAddress() {
var address = document.getElementById('agentflow-address');
var feedback = document.getElementById('agentflow-copy-feedback');
if (!address || !address.value) return;
function done() {
if (!feedback) return;
feedback.textContent = agentflowLabels.copied;
window.setTimeout(function() { feedback.textContent = ''; }, 1800);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(address.value).then(done, function() {
address.select();
document.execCommand('copy');
done();
});
} else {
address.select();
document.execCommand('copy');
done();
}
}
function agentflowAgentDialog(open, agent) {
var modal = document.getElementById('agentflow-agent-modal');
var select = document.getElementById('agentflow-agent-select');
var trigger = document.getElementById('agentflow-agent-open');
var name = document.getElementById('agentflow-selected-agent-name');
var confirmButton = document.getElementById('agentflow-agent-install-now');
if (!modal) return;
if (open) {
agentflowSelectedAgent = agent || '';
if (name) name.textContent = agentflowAgentNames[agentflowSelectedAgent] || agentflowSelectedAgent;
modal.classList.add('open');
if (select) select.focus();
if (confirmButton) confirmButton.focus();
} else {
modal.classList.remove('open');
var trigger = document.getElementById('agentflow-agent-install-' + agentflowSelectedAgent);
if (trigger) trigger.focus();
}
}
function agentflowInstallAgent() {
var selected = document.getElementById('agentflow-agent-select');
var button = document.getElementById('agentflow-agent-install-now');
if (!selected || !button) return;
if (!agentflowSelectedAgent || !button) return;
if (!window.taskd || !window.taskd.show_log) {
alert('<%:Task log window is not available.%>');
return;
}
button.disabled = true;
button.value = agentflowLabels.starting;
(new XHR()).post(agentflowAgentInstallUrl, {
agent: selected.value,
agent: agentflowSelectedAgent,
token: agentflowCsrfToken
}, function(x, data) {
button.disabled = false;
button.value = '<%:Install now%>';
if (!data) {
try { data = JSON.parse(x.responseText || '{}'); }
catch (e) { data = {}; }
@@ -66,25 +386,8 @@ function agentflowInstallAgent() {
});
}
XHR.poll(5, '<%=url("admin/services/agentflow_status")%>', null, function(x, st) {
var el = document.getElementById('agentflow_status');
var openButton = document.getElementById('agentflow-open');
if (st && el) {
if (!st.running) {
if (openButton) openButton.style.display = 'none';
el.innerHTML = '<br/><em style="color:red"><%:The AgentFlow service is not running.%></em>';
} else {
if (openButton) openButton.style.display = '';
var accessMode = '';
if (st.proxy_prefix_enabled && st.linkeasefull_running) {
accessMode = '<br/><em><%:Access mode: LinkEase Desktop /apps proxy.%></em>';
} else {
accessMode = '<br/><em><%:Access mode: external port.%></em>';
}
el.innerHTML = '<br/><em style="color:green"><%:The AgentFlow service is running.%></em>'
+ accessMode;
}
}
XHR.poll(5, agentflowStatusUrl, null, function(x, st) {
agentflowRenderStatus(st);
});
document.addEventListener('keydown', function(ev) {
@@ -92,34 +395,71 @@ document.addEventListener('keydown', function(ev) {
});
//]]></script>
<fieldset class="cbi-section">
<legend><%:Status%></legend>
<p id="agentflow_status">
<em><%:Collecting data...%></em>
</p>
<div class="agentflow-actions">
<input id="agentflow-open" class="btn cbi-button cbi-button-apply" type="button" value="<%:Click to open AgentFlow%>" onclick="window.open(agentflowOpenUrl)" style="display:none">
<input id="agentflow-agent-open" class="btn cbi-button cbi-button-apply" type="button" value="<%:Install agent%>" onclick="agentflowAgentDialog(true)">
</div>
</fieldset>
<div id="agentflow-agent-modal" class="agentflow-agent-modal" role="dialog" aria-modal="true" aria-labelledby="agentflow-agent-title" onclick="if (event.target === this) agentflowAgentDialog(false)">
<div class="agentflow-agent-dialog">
<h3 id="agentflow-agent-title"><%:Install agent%></h3>
<p><%:Select a coding agent to install into the shared AgentFlow runtime.%></p>
<div class="agentflow-agent-field">
<label for="agentflow-agent-select"><%:Agent%></label>
<select id="agentflow-agent-select" class="cbi-input-select">
<option value="codexcli">Codex</option>
<option value="claude-code">Claude Code</option>
<option value="opencode">OpenCode</option>
<option value="kimi">Kimi</option>
<option value="reasonix">Reasonix</option>
</select>
<div class="agentflow-shell">
<section class="agentflow-panel agentflow-overview" aria-labelledby="agentflow-service-title">
<div class="agentflow-service">
<div id="agentflow-service-icon" class="agentflow-service-icon" aria-hidden="true">...</div>
<div class="agentflow-service-copy">
<h3 id="agentflow-service-title"><%:Checking AgentFlow service%></h3>
<p id="agentflow-service-description"><%:Collecting data...%></p>
<input id="agentflow-open" class="btn cbi-button agentflow-button agentflow-primary" type="button" value="<%:Open AgentFlow%>" onclick="window.open(agentflowOpenUrl)" disabled>
</div>
</div>
<div class="agentflow-agent-dialog-actions">
<input class="btn cbi-button" type="button" value="<%:Cancel%>" onclick="agentflowAgentDialog(false)">
<input id="agentflow-agent-install-now" class="btn cbi-button cbi-button-apply" type="button" value="<%:Install now%>" onclick="agentflowInstallAgent()">
<div class="agentflow-access">
<label for="agentflow-address"><%:Local network access%></label>
<div class="agentflow-address-row">
<input id="agentflow-address" type="text" readonly value="">
<input class="btn cbi-button agentflow-button" type="button" value="<%:Copy%>" onclick="agentflowCopyAddress()">
</div>
<p class="agentflow-access-note"><%:Open this address in a browser to access the AgentFlow Web interface.%> <span id="agentflow-copy-feedback" role="status" aria-live="polite"></span></p>
</div>
</section>
<section class="agentflow-panel agentflow-agents" aria-labelledby="agentflow-agents-title">
<div class="agentflow-section-heading">
<div class="agentflow-section-mark" aria-hidden="true">A</div>
<div>
<h3 id="agentflow-agents-title"><%:Agent installation%></h3>
<p><%:Install coding agents into the shared AgentFlow runtime. Installed agents show their current version.%></p>
</div>
</div>
<div class="agentflow-agent-grid">
<div id="agentflow-agent-codexcli" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#356cf6">
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">C</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">Codex</div><p><%:OpenAI coding agent for software development tasks.%></p></div></div>
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-codexcli"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-codexcli">-</span></span></div>
<input id="agentflow-agent-install-codexcli" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'codexcli')" disabled>
</div>
<div id="agentflow-agent-claude-code" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#dc633d">
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">C</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">Claude Code</div><p><%:Anthropic coding agent for code analysis and development.%></p></div></div>
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-claude-code"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-claude-code">-</span></span></div>
<input id="agentflow-agent-install-claude-code" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'claude-code')" disabled>
</div>
<div id="agentflow-agent-opencode" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#6a3ee8">
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">O</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">OpenCode</div><p><%:Open source coding agent with a flexible terminal workflow.%></p></div></div>
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-opencode"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-opencode">-</span></span></div>
<input id="agentflow-agent-install-opencode" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'opencode')" disabled>
</div>
<div id="agentflow-agent-kimi" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#1677d2">
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">K</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">Kimi</div><p><%:Moonshot AI coding agent for terminal-based development.%></p></div></div>
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-kimi"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-kimi">-</span></span></div>
<input id="agentflow-agent-install-kimi" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'kimi')" disabled>
</div>
<div id="agentflow-agent-reasonix" class="agentflow-agent-card" data-state="unknown" style="--agent-color:#3f526f">
<div class="agentflow-agent-top"><div class="agentflow-agent-logo" aria-hidden="true">R</div><div class="agentflow-agent-copy"><div class="agentflow-agent-title">Reasonix</div><p><%:AI coding agent for software engineering workflows.%></p></div></div>
<div class="agentflow-agent-meta"><span class="agentflow-agent-state"><span class="agentflow-agent-dot"></span><span id="agentflow-agent-state-reasonix"><%:Checking...%></span></span><span class="agentflow-agent-divider">|</span><span><%:Version%>: <span id="agentflow-agent-version-reasonix">-</span></span></div>
<input id="agentflow-agent-install-reasonix" class="btn cbi-button agentflow-button agentflow-primary agentflow-install-button" type="button" value="<%:Install%>" onclick="agentflowAgentDialog(true, 'reasonix')" disabled>
</div>
</div>
</section>
<div id="agentflow-agent-modal" class="agentflow-agent-modal" role="dialog" aria-modal="true" aria-labelledby="agentflow-agent-dialog-title" onclick="if (event.target === this) agentflowAgentDialog(false)">
<div class="agentflow-agent-dialog">
<h3 id="agentflow-agent-dialog-title"><%:Confirm agent installation%></h3>
<p><%:The installer will download and install%> <strong id="agentflow-selected-agent-name"></strong> <%:into the shared AgentFlow runtime.%></p>
<div class="agentflow-dialog-actions">
<input class="btn cbi-button agentflow-button" type="button" value="<%:Cancel%>" onclick="agentflowAgentDialog(false)">
<input id="agentflow-agent-install-now" class="btn cbi-button agentflow-button agentflow-primary" type="button" value="<%:Install now%>" onclick="agentflowInstallAgent()">
</div>
</div>
</div>
</div>
+81
View File
@@ -72,3 +72,84 @@ msgstr "任务日志窗口不可用。"
msgid "Failed to start agent installation."
msgstr "启动智能体安装失败。"
msgid "Installed"
msgstr "已安装"
msgid "Not installed"
msgstr "未安装"
msgid "Status unavailable"
msgstr "状态不可用"
msgid "Install"
msgstr "安装"
msgid "Starting..."
msgstr "正在启动……"
msgid "Copied"
msgstr "已复制"
msgid "Copy"
msgstr "复制"
msgid "AgentFlow service is running"
msgstr "AgentFlow 服务运行中"
msgid "AgentFlow service is not running"
msgstr "AgentFlow 服务未运行"
msgid "The service is ready. You can open AgentFlow or install coding agents."
msgstr "服务已就绪,可以打开 AgentFlow 或安装编码智能体。"
msgid "Start the service to use AgentFlow. Agent installation remains available."
msgstr "请启动服务后使用 AgentFlow;智能体安装仍然可用。"
msgid "Checking AgentFlow service"
msgstr "正在检查 AgentFlow 服务"
msgid "Open AgentFlow"
msgstr "打开 AgentFlow"
msgid "Local network access"
msgstr "局域网访问"
msgid "Open this address in a browser to access the AgentFlow Web interface."
msgstr "在浏览器中打开此地址即可访问 AgentFlow Web 界面。"
msgid "Agent installation"
msgstr "智能体安装"
msgid "Install coding agents into the shared AgentFlow runtime. Installed agents show their current version."
msgstr "将编码智能体安装到 AgentFlow 共享运行环境中;已安装的智能体会显示当前版本。"
msgid "OpenAI coding agent for software development tasks."
msgstr "OpenAI 提供的代码智能体,适用于软件开发任务。"
msgid "Anthropic coding agent for code analysis and development."
msgstr "Anthropic 提供的代码智能体,适用于代码分析与开发。"
msgid "Open source coding agent with a flexible terminal workflow."
msgstr "开源代码智能体,提供灵活的终端开发工作流。"
msgid "Moonshot AI coding agent for terminal-based development."
msgstr "Moonshot AI 提供的代码智能体,适用于终端开发。"
msgid "AI coding agent for software engineering workflows."
msgstr "面向软件工程工作流的 AI 代码智能体。"
msgid "Checking..."
msgstr "正在检查……"
msgid "Version"
msgstr "版本"
msgid "Confirm agent installation"
msgstr "确认安装智能体"
msgid "The installer will download and install"
msgstr "安装程序将下载并安装"
msgid "into the shared AgentFlow runtime."
msgstr "到 AgentFlow 共享运行环境。"
@@ -16,37 +16,57 @@ class AgentInstallContractTest(unittest.TestCase):
self.assertIn("+luci-lib-taskd", makefile)
self.assertIn('<%+tasks/embed%>', status)
self.assertIn('<select id="agentflow-agent-select"', status)
for agent in ("codexcli", "claude-code", "opencode", "kimi", "reasonix"):
self.assertIn(f'<option value="{agent}">', status)
self.assertNotIn('type="radio"', status)
self.assertIn(f'id="agentflow-agent-{agent}"', status)
self.assertIn(f"agentflowAgentDialog(true, '{agent}')", status)
self.assertNotIn('<select id="agentflow-agent-select"', status)
self.assertNotIn("<h4>", status)
self.assertEqual(status.count('class="agentflow-agent-title"'), 5)
self.assertNotIn("Update", status)
self.assertNotIn("Uninstall", status)
self.assertIn('id="agentflow-open"', status)
self.assertIn('id="agentflow-agent-open"', status)
self.assertGreater(status.index('id="agentflow-agent-open"'), status.index('id="agentflow_status"'))
self.assertIn('margin:12px;', status)
self.assertIn("var selected = document.getElementById('agentflow-agent-select')", status)
self.assertIn('id="agentflow-agent-install-now"', status)
self.assertIn("agentflowSelectedAgent", status)
self.assertIn("window.taskd.show_log(data.task_id", status)
self.assertNotIn('local token =', status)
self.assertIn("window.taskd.csrfToken", status)
self.assertIn("token: agentflowCsrfToken", status)
self.assertIn('http.formvalue("agent")', controller)
self.assertIn('codexcli = true', controller)
self.assertIn('["claude-code"] = true', controller)
self.assertIn('opencode = true', controller)
self.assertIn('kimi = true', controller)
self.assertIn('reasonix = true', controller)
for agent in ("codexcli", "claude-code", "opencode", "kimi", "reasonix"):
self.assertIn(f'id = "{agent}"', controller)
self.assertIn('supported_agents[supported_agent.id] = true', controller)
self.assertIn('context.authtoken or context.token', controller)
self.assertIn('http.formvalue("token") ~= expected', controller)
self.assertIn('/etc/init.d/tasks task_add ', controller)
self.assertIn('task_id = "agentflow-agent-install"', controller)
def test_status_reports_installed_agents_from_shared_mise_runtime(self):
controller = self.read("luasrc/controller/agentflow.lua")
status = self.read("luasrc/view/agentflow/status.htm")
self.assertIn("istore_runtime_env", controller)
self.assertIn('where node@lts', controller)
for package in (
"@openai/codex",
"@anthropic-ai/claude-code",
"opencode-ai",
"@moonshot-ai/kimi-code",
"reasonix",
):
self.assertIn(f'package = "{package}"', controller)
self.assertIn('status.version = package_json.version', controller)
self.assertIn('agents_available = agents_available', controller)
self.assertIn('st.agents_available', status)
self.assertIn('item.installed === true', status)
self.assertIn('installButton.disabled = !available || installed', status)
self.assertIn('installed ? agentflowLabels.installed : agentflowLabels.install', status)
def test_modal_theme_prefers_body_attribute_then_system_theme(self):
status = self.read("luasrc/view/agentflow/status.htm")
self.assertIn('body[theme="light"] .agentflow-agent-dialog', status)
self.assertIn('body[theme="dark"] .agentflow-agent-dialog', status)
self.assertIn('body[theme="dark"] .agentflow-shell', status)
self.assertIn('@media (prefers-color-scheme: dark)', status)
self.assertIn('body:not([theme]) .agentflow-agent-dialog', status)
self.assertIn('body:not([theme]) .agentflow-shell', status)
def test_controller_runs_remote_installer_inside_taskd(self):
controller = self.read("luasrc/controller/agentflow.lua")
+29 -6
View File
@@ -11,8 +11,8 @@ LUCI_DESCRIPTION:=Live firewall log table (firewall4/nft; iptables-tagged lines
LUCI_DEPENDS:=+luci-base +logd +jsonfilter
LUCI_PKGARCH:=all
PKG_VERSION:=0.1.44
PKG_RELEASE:=28
PKG_VERSION:=0.1.45
PKG_RELEASE:=29
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
ifdef SOURCE_DATE_EPOCH
@@ -28,11 +28,34 @@ case "$${IPKG_INSTROOT}" in
''|/) ;;
*) exit 0 ;;
esac
# The opkg wrapper invokes this body as <wrapper> <action>.
# OpenWrt APK post-upgrade/pre-deinstall wiring is in package-pack.mk;
# post-upgrade exports PKG_UPGRADE=1. This guard is belt-and-braces with
# the upgrade action arg.
# A version-shaped $$1 with empty $$2 is APK uninstall (pre-deinstall).
# Same-version APK reinstall uses post-upgrade and does not run this hook.
# A version-changing APK upgrade is indistinguishable from uninstall at
# this hook (both pass the old version as $$1). Do not claim that path
# was lab-proven.
[ "$${PKG_UPGRADE:-}" = 1 ] && exit 0
case "$${2-}" in
remove) ;;
upgrade) exit 0 ;;
'')
case "$${1-}" in
remove) ;;
upgrade|''|[!0-9]*) exit 0 ;;
# Character-class '.' so the version arm is a literal dot (1a2 must not restore).
[0-9]*[.][0-9]*) ;;
*) exit 0 ;;
esac
;;
*) exit 0 ;;
esac
. /usr/libexec/fwlive-logging.sh
# Best-effort restore: always exit 0 so a transient lock/pending-staging
# failure cannot strand the package half-removed. Baseline is kept on
# failure (restore_wan_log_baseline) for admin recovery; helpers are still
# present while this prerm runs.
# Best-effort restore still exits 0 so a transient lock/pending-staging failure
# cannot strand the package half-removed. The baseline is kept on failure for
# admin recovery; helpers are still present while this prerm runs.
restore_wan_log_baseline || logger -t fwlive "WAN log baseline restore failed during uninstall"
exit 0
endef
@@ -9,7 +9,7 @@
*/
return baseclass.extend({
/* Keep in sync with openwrt-feed/luci-app-fwlive/Makefile PKG_VERSION. */
APP_VERSION: '0.1.44',
APP_VERSION: '0.1.45',
ROW_LIMIT_OPTIONS: [25, 50, 100, 250, 500, 1000, 2000],
DEFAULT_ROW_LIMIT: 100,
FETCH_MODE_OPTIONS: ['auto', 'manual'],
@@ -184,7 +184,7 @@ return view.extend({
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_user', title: _('PPPoE Username'), depends: { wan_proto: 'pppoe' } },
{ tab: 'netsetup', type: form.Value, id: 'wan_pppoe_pass', title: _('PPPoE Password'), depends: { wan_proto: 'pppoe' }, password: true },
{ tab: 'netsetup', type: form.Value, id: 'lan_ipaddr', title: _('LAN IPv4 Address'), datatype: 'ip4addr', placeholder: '10.0.0.1' },
{ tab: 'netsetup', type: form.DynamicList, id: 'lan_dns', title: _('Custom DNS Server(s)'), datatype: 'ipaddr', placeholder: '223.5.5.5' },
{ tab: 'netsetup', type: form.DynamicList, id: 'lan_dns', title: _('Custom DNS Server(s)'), datatype: 'ipaddr', placeholder: '223.5.5.5', desc: _('Leave empty for ISP DNS.') },
{ tab: 'netsetup', type: form.Value, id: 'lan_gateway', title: _('Gateway Address'), datatype: 'ip4addr', placeholder: '', depends: { wan_proto: 'siderouter' }, desc: _('Primary router IP address used when operating in side-router mode.') },
{ tab: 'netsetup', type: form.Flag, id: 'dhcp', title: _('Enable DHCP Server'), desc: _('Enable or disable LAN DHCP server (usually disabled in side-router mode to avoid IP address conflicts).') },
{ tab: 'netsetup', type: form.Flag, id: 'ipv6', title: _('IPv6 Support'), desc: _('Enable or disable IPv6 router advertisements and DHCPv6.') },
+3
View File
@@ -72,6 +72,9 @@ msgstr "启用 DHCP 服务器"
msgid "Enable or disable LAN DHCP server (usually disabled in side-router mode to avoid IP address conflicts)."
msgstr "开启或关闭局域网 DHCP 服务(旁路由模式下通常建议关闭,避免局域网 IP 分配冲突)。"
msgid "Leave empty for ISP DNS."
msgstr "留空则使用 ISP 运营商DNS"
msgid "IPv6 Support"
msgstr "IPv6 支持"
+3 -3
View File
@@ -17,12 +17,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-wwand
PKG_VERSION:=1.6.7_p11
PKG_RELEASE:=24
PKG_VERSION:=1.6.7_p13
PKG_RELEASE:=25
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
PKG_SOURCE_VERSION:=2ca9a94506f972d89a398d560d5667a809e89d9f
PKG_SOURCE_VERSION:=e97ffb8db7ca59049195d9a37ae86b69c1e1264d
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL-2.0-only
@@ -205,6 +205,13 @@ return view.extend({
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
o = s.taboption('general', form.Flag, 'enabled', _('Enable Client Daemon'),
_('Enable unified xkcptun client daemon.'));
o.enabled = '1';
o.disabled = '0';
o.default = '0';
o.rmempty = false;
o = s.taboption('general', form.Value, 'remote_addr', _('Default Server Address'),
_('Remote xkcptun server IP address or domain name.'));
o.datatype = 'host';
@@ -244,10 +251,10 @@ return view.extend({
s.tab('advanced', _('Advanced Overrides'));
// Overview Columns in table
o = s.option(form.Flag, 'disabled', _('Enabled'));
o.enabled = '0';
o.disabled = '1';
o.default = '0';
o = s.option(form.Flag, 'enabled', _('Enabled'));
o.enabled = '1';
o.disabled = '0';
o.default = '1';
o.rmempty = false;
o.editable = true;
@@ -274,10 +281,10 @@ return view.extend({
o.placeholder = '127.0.0.1';
// Modal / Tab: General Settings
o = s.taboption('general', form.Flag, 'disabled', _('Enable Tunnel'));
o.enabled = '0';
o.disabled = '1';
o.default = '0';
o = s.taboption('general', form.Flag, 'enabled', _('Enable Tunnel'));
o.enabled = '1';
o.disabled = '0';
o.default = '1';
o.rmempty = false;
o.modalonly = true;
@@ -399,10 +406,10 @@ return view.extend({
s.tab('advanced', _('Advanced Settings'));
// Overview Columns in table
o = s.option(form.Flag, 'disabled', _('Enabled'));
o.enabled = '0';
o.disabled = '1';
o.default = '0';
o = s.option(form.Flag, 'enabled', _('Enabled'));
o.enabled = '1';
o.disabled = '0';
o.default = '1';
o.rmempty = false;
o.editable = true;
@@ -434,14 +441,14 @@ return view.extend({
o = s.option(form.Flag, 'fec', _('FEC'));
o.modalonly = false;
o.default = '0';
o.default = '1';
o.editable = true;
// Modal / Tab: General Settings
o = s.taboption('general', form.Flag, 'disabled', _('Enable Tunnel'));
o.enabled = '0';
o.disabled = '1';
o.default = '0';
o = s.taboption('general', form.Flag, 'enabled', _('Enable Tunnel'));
o.enabled = '1';
o.disabled = '0';
o.default = '1';
o.rmempty = false;
o.modalonly = true;
+1 -1
View File
@@ -10,7 +10,7 @@ include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=my-default-settings
PKG_VERSION:=2
PKG_RELEASE:=16
PKG_RELEASE:=17
include $(INCLUDE_DIR)/package.mk
+124 -38
View File
@@ -16,25 +16,33 @@ show_help() {
选项:
-s, --ssid <名称> 指定基础WiFi名称
-p, --password <密码> 指定WiFi密码
-f, --force 强制重新执行(忽略 wifi=1 已配置检查)
-h, --help 查看帮助信息
并发与等待机制:
- 无参调用时:若已有 mywifi 在执行,直接终止退出;
- 带 -f 或传参调用时:若已有 mywifi 在执行,自动排队等待其执行完成后再继续执行。
WiFi命名规范:
2.4 GHz -> <WiFi名>_2.4G
5 GHz -> <WiFi名>_5G
6 GHz -> <WiFi名>_6G
使用示例:
mywifi # 默认使用 hostname,无密码
mywifi "HomeWiFi" # HomeWiFi_2.4G / HomeWiFi_5G,无密码
mywifi "HomeWiFi" "12345678" # HomeWiFi_2.4G / HomeWiFi_5G,密码 12345678
mywifi -s "HomeWiFi" -p "12345678" # 选项传参
mywifi # 默认执行(若已配置过或正在执行则自动终止)
mywifi -f # 强制重新按 hostname 恢复默认配置(若有执行中则排队等待)
mywifi "HomeWiFi" "12345678" # 设置指定 WiFi 名及密码(若有执行中则排队等待)
mywifi -f "HomeWiFi" "12345678" # 强制覆盖设置
EOF_HELP
}
# 1. 解析命令行参数(同时兼顾位置参数与选项参数)
# -------------------------------------------------------------
# 1. 优先解析命令行参数(判断是否带有 -f 或自定义参数)
# -------------------------------------------------------------
CUSTOM_SSID=""
CUSTOM_PASSWD=""
HAS_PASSWD=0
FORCE=0
while [ $# -gt 0 ]; do
case "$1" in
@@ -42,6 +50,9 @@ while [ $# -gt 0 ]; do
show_help
exit 0
;;
-f|--force)
FORCE=1
;;
-s|--ssid)
shift
CUSTOM_SSID="$1"
@@ -63,7 +74,94 @@ while [ $# -gt 0 ]; do
shift
done
# 密码合法性校验
# 判断是否需要等待正在运行的实例完成
# 只要指定了 -f 或传入了自定义 SSID / 密码,就启用等待锁机制
WAIT_LOCK=0
if [ $FORCE -eq 1 ] || [ -n "$CUSTOM_SSID" ] || [ $HAS_PASSWD -eq 1 ]; then
WAIT_LOCK=1
fi
# -------------------------------------------------------------
# 2. 检查 base_config 状态标记(无参且已配置过 wifi=1 时直接退出)
# -------------------------------------------------------------
if [ $FORCE -eq 0 ]; then
if [ "$(uci -q get base_config.@status[0].wifi 2>/dev/null)" = "1" ]; then
if [ -z "$CUSTOM_SSID" ] && [ $HAS_PASSWD -eq 0 ]; then
echo "[INFO] WiFi 已配置完成 (base_config.@status[0].wifi=1),无需重复执行,直接终止脚本。"
exit 0
fi
fi
fi
# -------------------------------------------------------------
# 3. 并发锁处理:带参/-f 模式等待完成,无参模式直接终止
# -------------------------------------------------------------
LOCK_FILE="/var/run/mywifi.lock"
acquire_lock() {
local cur_pid=$$
local cur_ppid=$PPID
# A. 优先采用 flock 文件描述符锁(内核自动回收,安全可靠)
if command -v flock >/dev/null 2>&1; then
exec 9>"$LOCK_FILE" 2>/dev/null || true
if [ $WAIT_LOCK -eq 1 ]; then
if ! flock -n 9 2>/dev/null; then
echo "[INFO] 检测到 /sbin/mywifi 已在执行中,等待其执行完成后再继续..."
flock -w 60 9 2>/dev/null || {
echo "[ERROR] 等待其他 /sbin/mywifi 执行超时(60秒)!" >&2
exit 1
}
fi
else
if ! flock -n 9 2>/dev/null; then
echo "[INFO] 检测到 /sbin/mywifi 已在执行中,直接终止脚本。"
exit 0
fi
fi
else
# B. 备用进程轮询检测机制(兼容无 flock 极简环境)
local wait_limit=60
local waited=0
while true; do
local other_running=0
for p in $(pgrep -f "mywifi" 2>/dev/null); do
if [ "$p" != "$cur_pid" ] && [ "$p" != "$cur_ppid" ]; then
local cmd="$(cat /proc/$p/cmdline 2>/dev/null | tr '\0' ' ')"
case "$cmd" in
*mywifi*)
other_running=1
break
;;
esac
fi
done
if [ $other_running -eq 0 ]; then
break
fi
if [ $WAIT_LOCK -eq 1 ]; then
[ $waited -eq 0 ] && echo "[INFO] 检测到 /sbin/mywifi 已在执行中,等待其执行完成后再继续..."
sleep 1
waited=$((waited + 1))
if [ $waited -ge $wait_limit ]; then
echo "[ERROR] 等待其他 /sbin/mywifi 执行超时!" >&2
exit 1
fi
else
echo "[INFO] 检测到 /sbin/mywifi 已在执行中,直接终止脚本。"
exit 0
fi
done
fi
}
acquire_lock
# -------------------------------------------------------------
# 4. 密码长度合法性校验(若指定了密码)
# -------------------------------------------------------------
if [ $HAS_PASSWD -eq 1 ] && [ -n "$CUSTOM_PASSWD" ]; then
pwd_len=${#CUSTOM_PASSWD}
if [ "$pwd_len" -lt 8 ] || [ "$pwd_len" -gt 63 ]; then
@@ -75,7 +173,9 @@ else
SSID_PASSWD=""
fi
# 2. 深度就绪检测:确保内核PHY驱动、多频硬件、系统初始化状态和UCI配置全部真正就绪
# -------------------------------------------------------------
# 5. 深度就绪检测(确保多频硬件驱动加载与 UCI 配置完整就绪)
# -------------------------------------------------------------
wait_wifi_ready() {
local max_wait=15
local waited=0
@@ -84,34 +184,30 @@ wait_wifi_ready() {
local phy_count=0
local uci_count=0
# B. 检查内核物理无线设备 (PHY)
# 检查内核物理无线设备 (PHY)
local phys="$(ls -d /sys/class/ieee80211/phy* 2>/dev/null)"
[ -n "$phys" ] && phy_count=$(echo "$phys" | wc -w)
# C. 统计 UCI wireless 中实际已解析出的 wifi-device 节点数量(非文件大小)
# 检查 UCI 中真实生成的 wifi-device 数量
if [ -s /etc/config/wireless ]; then
local uci_radios="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
[ -n "$uci_radios" ] && uci_count=$(echo "$uci_radios" | wc -w)
fi
# D. 自动探测与配置补全:
# - 物理网卡已加载,但 UCI 配置中的 radio 数量少于物理网卡数量(多频网卡尚未完全生成配置)
# - 或者配置文件缺失/没有 radio 配置
# 自动补全配置(物理硬件数量 > UCI 配置数量)
if [ $phy_count -gt 0 ] && [ $uci_count -lt $phy_count ]; then
[ -x /sbin/wifi ] && /sbin/wifi config >/dev/null 2>&1
elif [ $phy_count -eq 0 ] && [ $uci_count -eq 0 ] && [ ! -s /etc/config/wireless ]; then
[ -x /sbin/wifi ] && /sbin/wifi config >/dev/null 2>&1
fi
# 重新同步一次 UCI radio 数量
# 重新同步 UCI radio 数量
if [ -s /etc/config/wireless ]; then
local uci_radios_check="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
[ -n "$uci_radios_check" ] && uci_count=$(echo "$uci_radios_check" | wc -w)
fi
# E. 判定就绪标准:
# 1) UCI 中有效 wifi-device 数量 > 0
# 2) 若检测到物理 PHY,UCI radio 数量必须完全覆盖检测到的 PHY 数量
# 就绪标准判定
if [ $uci_count -gt 0 ]; then
if [ $phy_count -eq 0 ] || [ $uci_count -ge $phy_count ]; then
return 0
@@ -122,20 +218,18 @@ wait_wifi_ready() {
waited=$((waited + 1))
done
# 超时保护:只要至少有一个 radio 节点,允许降级继续
if [ $uci_count -gt 0 ]; then
return 0
fi
[ $uci_count -gt 0 ] && return 0
return 1
}
# 执行深度就绪等待
if ! wait_wifi_ready; then
echo "[ERROR] 未检测到可用无线硬件或无线配置生成超时!" >&2
exit 1
fi
# 3. 识别射频频段函数 (2g, 5g, 6g)
# -------------------------------------------------------------
# 6. 频段识别函数 (2g, 5g, 6g)
# -------------------------------------------------------------
detect_band() {
local radio="$1"
local band="$(uci -q get wireless.${radio}.band 2>/dev/null | tr 'A-Z' 'a-z')"
@@ -146,7 +240,6 @@ detect_band() {
*6g*) echo "6g"; return ;;
esac
# 信道 (channel) 推断
local channel="$(uci -q get wireless.${radio}.channel 2>/dev/null)"
if [ -n "$channel" ] && [ "$channel" -eq "$channel" 2>/dev/null ]; then
if [ "$channel" -ge 1 ] && [ "$channel" -le 14 ]; then
@@ -158,14 +251,12 @@ detect_band() {
fi
fi
# hwmode 推断
local hwmode="$(uci -q get wireless.${radio}.hwmode 2>/dev/null | tr 'A-Z' 'a-z')"
case "$hwmode" in
*a*) echo "5g"; return ;;
*b*|*g*) echo "2g"; return ;;
esac
# 双频常规配对推断
if [ "$radio" = "radio0" ]; then
local r1_band="$(uci -q get wireless.radio1.band 2>/dev/null | tr 'A-Z' 'a-z')"
[ "$r1_band" = "5g" ] && echo "2g" && return
@@ -177,33 +268,31 @@ detect_band() {
echo "2g"
}
# 4. 加载系统发行版信息(若存在)
# -------------------------------------------------------------
# 7. 配置 SSID 与各频段接口
# -------------------------------------------------------------
[ -f /etc/openwrt_release ] && . /etc/openwrt_release
# 5. 确定基础 WiFi 名称
if [ -n "$CUSTOM_SSID" ]; then
SSID="$CUSTOM_SSID"
else
SSID="$(uci -q get system.@system[0].hostname 2>/dev/null)"
[ -z "$SSID" ] && [ -r /proc/sys/kernel/hostname ] && SSID="$(cat /proc/sys/kernel/hostname)"
[ -z "$SSID" ] && SSID="Kwrt"
[ -z "$SSID" ] && SSID="$(uci -q get wizard.default.wifi_ssid 2>/dev/null)"
[ -z "$SSID" ] && SSID="OpenWrt"
fi
# 移除禁用标记
sed -i '/option disabled/d' /etc/config/wireless 2>/dev/null
# 修正双频标记
if [ "$(uci -q get wireless.radio1.band 2>/dev/null)" = "5g" ]; then
[ -z "$(uci -q get wireless.radio0.band 2>/dev/null)" ] && uci -q set wireless.radio0.band="2g"
fi
# 重命名匿名接口 section 为 default_radioX
widx=0
while uci rename wireless.@wifi-iface[$widx]=default_radio$widx >/dev/null 2>&1; do
widx=$((widx + 1))
done
# 6. 配置各 Radio 参数
wifi_setup_radio() {
local radio="$1"
@@ -223,7 +312,6 @@ wifi_setup_radio() {
local full_ssid="${SSID}${ssid_suffix}"
# 特定硬件架构适配(如高通 ipq60xx)
if [ "$band" = "5g" ]; then
case "$DISTRIB_TARGET" in
*ipq60xx*)
@@ -240,10 +328,8 @@ wifi_setup_radio() {
uci -q set wireless.${iface}.network='lan'
uci -q set wireless.${iface}.ssid="${full_ssid}"
# 密码与加密策略配置
if [ -n "$SSID_PASSWD" ]; then
if [ "$band" = "6g" ]; then
# 6GHz 频段按标准强制要求 WPA3-SAE
uci -q set wireless.${iface}.encryption='sae'
else
uci -q set wireless.${iface}.encryption='psk2'
@@ -257,7 +343,7 @@ wifi_setup_radio() {
fi
}
# 7. 动态遍历所有射频设备并应用设置
# 动态遍历所有射频设备并配置
radios="$(uci -q show wireless 2>/dev/null | sed -n 's/^wireless\.\([^.]*\)=wifi-device/\1/p')"
[ -z "$radios" ] && radios="radio0 radio1 radio2 radio3"
@@ -265,10 +351,10 @@ for radio in $radios; do
wifi_setup_radio "$radio"
done
# 提交配置
# 提交无线配置
uci commit wireless 2>/dev/null
# 若存在 base_config(kiddin9 固件状态记录),更新状态
# 标记 WiFi 基础配置已完成
if uci -q get base_config.@status[0] >/dev/null 2>&1; then
uci -q set base_config.@status[0].wifi=1
uci commit base_config 2>/dev/null
+2 -2
View File
@@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk
PKG_NPM_NAME:=pnpm
PKG_NAME:=node-$(PKG_NPM_NAME)
PKG_VERSION:=11.15.1
PKG_RELEASE:=1
PKG_VERSION:=11.22.0
PKG_RELEASE:=2
PKG_SOURCE:=$(PKG_NPM_NAME)-$(PKG_VERSION).tgz
PKG_SOURCE_URL:=https://mirrors.tencent.com/npm/$(PKG_NPM_NAME)/-/ \
+3 -3
View File
@@ -50,12 +50,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=wwand
PKG_VERSION:=1.6.7_p58
PKG_RELEASE:=50
PKG_VERSION:=1.6.7_p60
PKG_RELEASE:=51
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
PKG_SOURCE_VERSION:=1b575d41dd35684e035541afe1f129da072275dd
PKG_SOURCE_VERSION:=f5cba40880e0a11c00cf6b9040a6eb74e3d222da
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL-2.0-only
+3 -2
View File
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=xkcptun
PKG_VERSION:=1.09.561
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/liudf0716/$(PKG_NAME)/tar.gz/$(PKG_VERSION)?
@@ -34,7 +34,8 @@ endef
define Package/xkcptun/description
xkcptun is a lightweight and high-performance C language implementation of kcptun.
This package contains both client and server binaries.
This package contains the client and server binaries as well as the
xkcp_spy monitoring tool.
endef
define Package/xkcptun/conffiles
+4 -3
View File
@@ -1,5 +1,6 @@
config global 'client'
option enabled '0'
option remote_addr '1.2.3.4'
option remote_port '9089'
option mode 'fast3'
@@ -19,10 +20,10 @@ config global 'client'
option sockbuf '16777216'
option keepalive '10'
option conntimeout '60'
option key '7f7ac683d720e1437019692eadb0b811'
option key 'change_me'
config client 'client1'
option disabled '1'
option enabled '0'
option name 'ssh_tunnel'
option local_interface 'br-lan'
option local_port '9088'
@@ -50,4 +51,4 @@ config global 'server'
option sockbuf '16777216'
option keepalive '10'
option conntimeout '60'
option key '7f7ac683d720e1437019692eadb0b811'
option key 'change_me'
+19 -12
View File
@@ -53,7 +53,7 @@ append_toml_tunnel() {
eval "$("validate_${cfgtype}_section" "$cfg" validate_mklocal)"
"validate_${cfgtype}_section" "$cfg" || return 1
[ "$disabled" = 0 ] || return 1
[ "${enabled:-1}" -eq 1 ] || return 1
[ -n "$local_port" ] || return 1
cat << EOF >> "$outfile"
@@ -128,15 +128,19 @@ EOF
config_foreach append_section "$cfgtype"
if [ "$cfgtype" = "client" ] && [ "$count" -eq 0 ]; then
rm -f "$conftoml"
return
if [ "$cfgtype" = "client" ]; then
local cli_enabled
config_get_bool cli_enabled client enabled 0
if [ "$cli_enabled" -ne 1 ] || [ "$count" -eq 0 ]; then
rm -f "$conftoml"
return
fi
fi
if [ "$cfgtype" = "server" ]; then
local srv_enabled
config_get_bool srv_enabled server enabled 0
if [ "$srv_enabled" -ne 1 ] && [ "$count" -eq 0 ]; then
if [ "$srv_enabled" -ne 1 ]; then
rm -f "$conftoml"
return
fi
@@ -151,6 +155,9 @@ EOF
start_service() {
mkdir -p "$confdir"
# generated TOML files contain the pre-shared key: keep them private
chmod 0700 "$confdir"
umask 077
config_load xkcptun
start_daemon server
start_daemon client
@@ -189,10 +196,11 @@ validate_common_options() {
local cfgtype="$1"; shift
local cfg="$1"; shift
local func="$1"; shift
local enabled_def="${1:-1}"; shift
local mode_profiles='"fast3", "fast2", "fast", "normal", "manual"'
"${func:-validate}" "$cfgtype" "$cfg" "$@" \
'disabled:bool:0' \
"enabled:bool:$enabled_def" \
'name:string' \
"mode:or($mode_profiles)" \
'mtu:uinteger' \
@@ -217,8 +225,7 @@ validate_common_options() {
}
validate_global_options() {
validate_common_options global "$1" "$2" \
'enabled:bool:0' \
validate_common_options global "$1" "$2" 0 \
'remote_addr:host' \
'remote_port:port' \
'local_interface:string' \
@@ -226,15 +233,15 @@ validate_global_options() {
}
validate_server_options() {
validate_common_options server "$1" "$2" \
validate_common_options server "$1" "$2" 1 \
'local_interface:string:eth0' \
'local_port:port:9089' \
'remote_addr:host:127.0.0.1' \
'remote_port:port:443'
'target_addr:host' \
'target_port:port'
}
validate_client_options() {
validate_common_options client "$1" "$2" \
validate_common_options client "$1" "$2" 1 \
'local_interface:string:br-lan' \
'local_port:port:9088' \
'proto:or("tcp", "udp")' \