🌴 Sync 2026-07-08 20:36:07

This commit is contained in:
github-actions[bot] committed 2026-07-08 20:36:07 +08:00
1 parent bd8d184870
commit 7aa399d98e
28 files changed
+839 -198

No files matched your search

+43
View File
@@ -4,6 +4,49 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
## [2.0.10] - 2026-07-08
### 修复微信渠道配对
- 启动 OpenClaw 时自动自愈微信 npm 插件注册,补齐 `plugins.installs.openclaw-weixin`、`plugins.allow` 和 `channels.openclaw-weixin.enabled`。
- 微信安装、升级和登录流程增加 `https://ilinkai.weixin.qq.com` 连通性检查,日志直接显示 HTTP、TLS 或 timeout 结果。
- 兼容没有 `su` / `runuser` 的 OpenWrt 固件,微信安装、升级、登录和下线改用 `start-stop-daemon` 兜底以 `openclaw` 用户执行。
- 微信安装网络探测优先使用 `curl`,避免低内存/精简 musl 固件上 Node `fetch` 触发 undici Wasm OOM。
- 微信官方 CLI 安装触发 undici Wasm OOM 时,自动改用 npm 直装到 OpenClaw `npm/projects` 并继续注册插件配置。
- 微信登录前补充 Node、python3、插件目录、账号状态目录和配置写权限检查,并清理残留登录进程与旧二维码状态。
- 登录失败时在 LuCI 页面展示真实日志详情,不再只显示“登录失败”。
- 二维码链接提取更稳,页面明确提示“点击链接后用微信扫码”。
- README 补充微信插件正确配对流程和常见失败原因。
### 修复权限混乱
- 新增统一权限修复工具 `openclaw-permissions.sh`。
- 避免把整个 `OC_DATA` 或 `OC_STATE_DIR` 递归改成 `openclaw`。
- `npm/projects` 保持 `openclaw` 可写,插件源码目录保持 `root:root 755`,避免 `EACCES` 和 `suspicious ownership` 互相打架。
- retained npm generation 目录保持可清理,修复 Gateway 清理旧 generation 的权限报错。
- legacy `extensions` / `archived-extensions` 保持 root-owned,降低 OpenClaw 插件安全检查误报风险。
### 修复检测升级
- “快捷操作 → 检测升级”改为语义版本比较,只在远端版本真正高于当前版本时提示升级。
- 本地版本高于 GitHub latest 时不再误判为可升级。
- 已实测旧版 `2.0.8` 可通过 LuCI 检测并一键升级到 `2.0.9`,升级日志返回成功且服务保持正常。
### 修复干净安装启动失败
- 修复首次安装后 `doctor --fix` 可能移除 `gateway.auth.token`,导致 Gateway 绑定 LAN 时因缺少认证直接退出,页面显示“启动失败,退出码 78”。
- `doctor --fix` 执行环境补齐 `NODE_ICU_DATA` 和 Node/OpenClaw PATH,避免部分 musl 固件上配置迁移阶段触发 ICU/Unicode 正则错误。
- Gateway 启动时增加 `OPENCLAW_GATEWAY_TOKEN` 环境变量兜底,确保 JSON 配置被迁移工具改写后仍能使用 UCI token 启动。
- `.run` 安装器补齐运行依赖安装,避免精简固件缺少 GNU tar 时 Node.js `.tar.xz` 解压失败。
- `openclaw-env setup` 改为先完整解压验证 Node.js,再替换正式目录;安装失败时保留已有运行目录,避免重装失败清空 `/opt/openclaw`。
### 验证
- 通过 shell / Node / Lua 语法检查。
- 通过契约测试 `tests/test_openclaw_contracts.sh`。
---
## [2.0.9] - 2026-07-04
### 修复微信 npm 插件注册
+2 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-openclaw
PKG_VERSION:=$(strip $(shell cat $(CURDIR)/VERSION 2>/dev/null || echo "1.0.0"))
PKG_RELEASE:=19
PKG_RELEASE:=20
PKG_MAINTAINER:=10000ge10000 <10000ge10000@users.noreply.github.com>
PKG_LICENSE:=GPL-3.0
@@ -59,6 +59,7 @@ define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/libexec
$(INSTALL_BIN) ./root/usr/libexec/openclaw-paths.sh $(1)/usr/libexec/openclaw-paths.sh
$(INSTALL_BIN) ./root/usr/libexec/openclaw-node.sh $(1)/usr/libexec/openclaw-node.sh
$(INSTALL_BIN) ./root/usr/libexec/openclaw-permissions.sh $(1)/usr/libexec/openclaw-permissions.sh
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller
$(INSTALL_DATA) ./luasrc/controller/openclaw.lua $(1)/usr/lib/lua/luci/controller/openclaw.lua
$(INSTALL_DIR) $(1)/usr/lib/lua/openclaw
+18
View File
@@ -113,6 +113,8 @@ openclaw-env setup
- `openclaw` 系统用户是否存在,不存在时自动创建
- `python3` 是否已安装;缺失时会自动执行 `opkg update && opkg install python3-light`
- npm cache、tmp、OpenClaw 数据目录是否可由 `openclaw` 用户写入
- 路由器到 `https://ilinkai.weixin.qq.com` 的 TLS/超时状态
- npm 插件是否已写入 `plugins.installs.openclaw-weixin`、`plugins.allow` 和 `channels.openclaw-weixin.enabled`
- 旧渠道名 `weixin` 会迁移为 `openclaw-weixin`
如自动安装失败,可手动安装:
@@ -122,6 +124,22 @@ opkg update
opkg install python3-light
```
微信配对推荐流程:
1. 在 LuCI「微信配置」页点击「安装/重新安装插件」。
2. 插件安装成功后点击「登录账号」。
3. 页面出现链接后,先点击打开链接,再用微信「扫一扫」扫码。
4. 等待页面提示登录成功,网关会重新加载微信账号。
5. 用微信给网关发一条测试消息,确认能触发 AI 回复。
常见失败原因:
- 二维码过期:重新点击「登录账号」生成新链接。
- 微信账号触发安全风控:更换常用设备/常用网络后再扫码,或换账号测试。
- 路由器网络异常:检查日志中的 `微信接口连通性检查`,重点看 TLS、timeout、DNS。
- 插件注册缺失:重新安装插件,启动时也会自动补齐 `openclaw-weixin` 配置。
- 目录权限错误:按页面日志提示修复 OpenClaw 数据目录权限。
## 已知说明
- OpenClaw 的 diagnostic heartbeat 可能在日志中出现类似周期性探测记录。它不是一次真实用户对话请求;如需降低噪音,优先在 OpenClaw 配置或日志采集侧降低诊断日志级别,不建议直接修改模型调用逻辑。
+1 -1
View File
@@ -1 +1 @@
2.0.9
2.0.10
+215 -41
View File
@@ -52,6 +52,34 @@ local function is_safe_openclaw_root(value)
return value == "/opt/openclaw" or value:match("^/mnt/[^/]+/openclaw$") ~= nil or value:match("^/media/[^/]+/openclaw$") ~= nil
end
local function compare_versions(a, b)
local function parts(v)
local out = {}
for n in tostring(v or ""):gsub("^v", ""):gmatch("(%d+)") do
out[#out + 1] = tonumber(n) or 0
end
return out
end
local aa, bb = parts(a), parts(b)
local len = math.max(#aa, #bb)
for i = 1, len do
local av, bv = aa[i] or 0, bb[i] or 0
if av > bv then return 1 end
if av < bv then return -1 end
end
return 0
end
local function is_newer_version(latest, current)
return latest ~= nil and current ~= nil and latest ~= "" and current ~= "" and compare_versions(latest, current) > 0
end
local function fix_openclaw_state_permissions(oc_data)
local sys = require "luci.sys"
local state_dir = tostring(oc_data or "") .. "/.openclaw"
sys.exec("if [ -x /usr/libexec/openclaw-permissions.sh ]; then /usr/libexec/openclaw-permissions.sh fix-state " .. shellquote(state_dir) .. " >/dev/null 2>&1; fi")
end
local function ensure_openclaw_user(oc_data)
local sys = require "luci.sys"
local uid = sys.exec("id -u openclaw 2>/dev/null"):gsub("%s+", "")
@@ -86,7 +114,8 @@ local function find_wechat_plugin_dir(install_path)
return nil
end
local function wechat_register_plugin_cmd(install_path, node_bin, log_file)
local function wechat_register_plugin_cmd(install_path, node_bin, log_file, exit_file)
exit_file = exit_file or "/tmp/openclaw-wechat-install.exit"
local oc_data = install_path .. "/data"
local config_file = oc_data .. "/.openclaw/openclaw.json"
local npm_projects = oc_data .. "/.openclaw/npm/projects"
@@ -163,11 +192,102 @@ fs.writeFileSync(configPath, JSON.stringify(d, null, 2) + '\n');
"if [ $REG_RC -eq 0 ]; then " ..
"chown openclaw:openclaw " .. shellquote(config_file) .. " 2>/dev/null; " ..
"echo 'Registered openclaw-weixin npm plugin in OpenClaw config.' >> " .. shellquote(log_file) .. "; " ..
"else RC=$REG_RC; echo $RC > /tmp/openclaw-wechat-install.exit; echo 'Failed to register openclaw-weixin npm plugin in OpenClaw config.' >> " .. shellquote(log_file) .. "; fi; " ..
"else RC=127; echo $RC > /tmp/openclaw-wechat-install.exit; echo 'Node.js not found, cannot register openclaw-weixin plugin.' >> " .. shellquote(log_file) .. "; fi; " ..
"else RC=$REG_RC; echo $RC > " .. shellquote(exit_file) .. "; echo 'Failed to register openclaw-weixin npm plugin in OpenClaw config.' >> " .. shellquote(log_file) .. "; fi; " ..
"else RC=127; echo $RC > " .. shellquote(exit_file) .. "; echo 'Node.js not found, cannot register openclaw-weixin plugin.' >> " .. shellquote(log_file) .. "; fi; " ..
"fi; "
end
local function wechat_network_probe_cmd(node_bin, log_file)
local target = "https://ilinkai.weixin.qq.com/ilink/bot/getupdates"
local probe_js = [[
const target = 'https://ilinkai.weixin.qq.com/ilink/bot/getupdates';
const started = Date.now();
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(new Error('timeout')), 10000);
(async () => {
try {
const res = await fetch(target, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: '{}',
signal: controller.signal
});
const text = await res.text();
const sample = text.replace(/\s+/g, ' ').slice(0, 180);
console.log(`微信接口连通性检查: HTTP ${res.status} ${res.statusText || ''} ${Date.now() - started}ms ${sample}`);
} catch (e) {
const code = e && (e.code || (e.cause && e.cause.code) || e.name) || 'ERR';
const msg = e && e.message ? e.message : String(e);
console.log(`微信接口连通性检查失败: ${code} ${msg}`);
} finally {
clearTimeout(timer);
}
})();
]]
return "echo '微信接口连通性检查: https://ilinkai.weixin.qq.com' >> " .. shellquote(log_file) .. "; " ..
"if command -v curl >/dev/null 2>&1; then " ..
"_oc_probe_body=/tmp/openclaw-wechat-probe-$$.txt; " ..
"_oc_probe_code=$(curl -sS -o \"$_oc_probe_body\" -w '%%{http_code}' --connect-timeout 8 --max-time 15 -X POST -H 'content-type: application/json' --data '{}' " .. shellquote(target) .. " 2>> " .. shellquote(log_file) .. "); " ..
"_oc_probe_rc=$?; " ..
"if [ $_oc_probe_rc -eq 0 ]; then _oc_probe_sample=$(tr '\\n\\r\\t' ' ' < \"$_oc_probe_body\" 2>/dev/null | cut -c1-180); echo \"微信接口连通性检查: HTTP $_oc_probe_code $_oc_probe_sample\" >> " .. shellquote(log_file) .. "; else echo \"微信接口连通性检查失败: curl exit $_oc_probe_rc\" >> " .. shellquote(log_file) .. "; fi; " ..
"rm -f \"$_oc_probe_body\"; " ..
"elif [ -x " .. shellquote(node_bin) .. " ]; then " ..
"NODE_ICU_DATA=\"${NODE_ICU_DATA:-/opt/openclaw/node/share/icu}\" " .. shellquote(node_bin) .. " -e " .. shellquote(probe_js) .. " >> " .. shellquote(log_file) .. " 2>&1 || true; " ..
"else echo '⚠️ curl/Node.js 不存在,跳过微信接口连通性检查' >> " .. shellquote(log_file) .. "; fi; "
end
local function openclaw_user_runner_cmd()
return "_oc_as_openclaw() { " ..
"if command -v su >/dev/null 2>&1; then su -s /bin/sh openclaw -c \"$1\"; " ..
"elif command -v runuser >/dev/null 2>&1; then runuser -u openclaw -- sh -c \"$1\"; " ..
"elif command -v start-stop-daemon >/dev/null 2>&1; then _oc_pid=/tmp/openclaw-user-$$.pid; _oc_cwd=$(pwd); rm -f \"$_oc_pid\"; start-stop-daemon -S -m -p \"$_oc_pid\" -c openclaw:openclaw -d \"$_oc_cwd\" -x /bin/sh -- -c \"$1\"; _oc_rc=$?; rm -f \"$_oc_pid\"; return $_oc_rc; " ..
"else echo '❌ 缺少 su/runuser/start-stop-daemon,无法以 openclaw 用户运行命令' >&2; return 127; fi; " ..
"}; "
end
local function wechat_npm_fallback_install_cmd(install_path, log_file, exit_file)
local package_json = "{\n \"private\": true,\n \"dependencies\": {\n \"@tencent-weixin/openclaw-weixin\": \"latest\"\n }\n}\n"
return "if [ $RC -ne 0 ]; then " ..
"echo '⚠️ 官方微信 CLI 安装失败,尝试 npm 直装兜底...' >> " .. shellquote(log_file) .. "; " ..
"OC_WECHAT_NPM_PROJECT=\"$OC_WECHAT_DATA/.openclaw/npm/projects/tencent-weixin-openclaw-weixin-7783ac86ba\"; " ..
"mkdir -p \"$OC_WECHAT_NPM_PROJECT\" \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/npm/projects\"; " ..
"chown -R openclaw:openclaw \"$OC_WECHAT_NPM_PROJECT\" \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/npm/projects\" 2>/dev/null; " ..
"cat > \"$OC_WECHAT_NPM_PROJECT/package.json\" <<'OC_WECHAT_PACKAGE_JSON'\n" ..
package_json ..
"OC_WECHAT_PACKAGE_JSON\n" ..
"chown openclaw:openclaw \"$OC_WECHAT_NPM_PROJECT/package.json\" 2>/dev/null; " ..
"cd \"$OC_WECHAT_NPM_PROJECT\" && " ..
"_oc_as_openclaw 'HOME=$OC_WECHAT_DATA OPENCLAW_HOME=$OC_WECHAT_DATA OPENCLAW_STATE_DIR=$OC_WECHAT_DATA/.openclaw OPENCLAW_CONFIG_PATH=$OC_WECHAT_DATA/.openclaw/openclaw.json " ..
"NPM_CONFIG_CACHE=$OC_WECHAT_DATA/.npm npm_config_cache=$OC_WECHAT_DATA/.npm TMPDIR=$OC_WECHAT_DATA/.tmp " ..
"PATH=" .. install_path .. "/node/bin:" .. install_path .. "/global/bin:$PATH " ..
"npm install --omit=dev --omit=peer --legacy-peer-deps --no-audit --no-fund --loglevel=error' >> " .. shellquote(log_file) .. " 2>&1; " ..
"RC=$?; echo $RC > " .. shellquote(exit_file) .. "; " ..
"if [ $RC -eq 0 ]; then echo '✅ npm 直装兜底完成' >> " .. shellquote(log_file) .. "; else echo '❌ npm 直装兜底失败 (exit: '$RC')' >> " .. shellquote(log_file) .. "; fi; " ..
"fi; "
end
local function wechat_tail_detail(text, max_lines)
if not text or text == "" then
return ""
end
local lines = {}
for line in (text .. "\n"):gmatch("([^\r\n]*)\r?\n") do
if line and line ~= "" then
line = line:gsub("\27%[[0-9;]*m", "")
if not line:match("^%s*$") then
table.insert(lines, line)
end
end
end
local start = math.max(1, #lines - (max_lines or 30) + 1)
local out = {}
for i = start, #lines do
table.insert(out, lines[i])
end
return table.concat(out, "\n")
end
local function write_wechat_log_and_exit(log_file, exit_file, content, exit_code)
local f = io.open(log_file, "w")
if f then
@@ -669,7 +789,7 @@ function action_check_update()
end
end
if plugin_current ~= "" and plugin_latest ~= "" and plugin_current ~= plugin_latest then
if is_newer_version(plugin_latest, plugin_current) then
plugin_has_update = true
end
@@ -1104,7 +1224,7 @@ function action_backup()
local extract_out = sys.exec(extract_cmd)
-- 6) 修复权限
sys.exec("chown -R openclaw:openclaw " .. oc_data_dir .. " 2>/dev/null")
fix_openclaw_state_permissions(oc_data_dir)
-- 7) 重启服务
sys.exec("/etc/init.d/openclaw start >/dev/null 2>&1 &")
@@ -1437,39 +1557,41 @@ function action_wechat_install()
local extensions_dir = install_path .. "/data/.openclaw/extensions"
local install_cmd = string.format(
"( " ..
openclaw_user_runner_cmd() ..
"echo '开始安装微信插件...' > /tmp/openclaw-wechat-install.log; " ..
"echo '安装路径: %s' >> /tmp/openclaw-wechat-install.log; " ..
"echo 'npx 路径: %s' >> /tmp/openclaw-wechat-install.log; " ..
"echo 'Node 版本:' $(%s -v 2>/dev/null || echo 未检测到) >> /tmp/openclaw-wechat-install.log; " ..
wechat_network_probe_cmd(node_bin, "/tmp/openclaw-wechat-install.log") ..
wechat_python3_bootstrap_cmd("/tmp/openclaw-wechat-install.log") ..
"mkdir -p %s/.npm %s/.tmp %s/.openclaw/extensions; " ..
"if [ ! -w %s/.openclaw/extensions ]; then chown -R openclaw:openclaw %s/.openclaw/extensions 2>/dev/null; fi; " ..
"chown -R openclaw:openclaw %s/.npm %s/.tmp %s/.openclaw 2>/dev/null; " ..
"OC_WECHAT_DATA=%s; export OC_WECHAT_DATA; " ..
"if [ -x /usr/libexec/openclaw-permissions.sh ]; then /usr/libexec/openclaw-permissions.sh prepare-workdirs \"$OC_WECHAT_DATA\" >/dev/null 2>&1; " ..
"else mkdir -p \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/extensions\"; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" 2>/dev/null; chown openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw\" 2>/dev/null; fi; " ..
"if [ ! -w %s/.npm ] || [ ! -w %s/.tmp ]; then echo '❌ npm cache/tmp 目录不可写' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; fi; " ..
"su -s /bin/sh openclaw -c 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw' || { echo '❌ openclaw 用户无法写入 npm cache/tmp/data 目录' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; }; " ..
"_oc_as_openclaw 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw' || { echo '❌ openclaw 用户无法写入 npm cache/tmp/data 目录' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; }; " ..
"cd %s && " ..
"su -s /bin/sh openclaw -c 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw " ..
"_oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw " ..
"OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json NPM_CONFIG_CACHE=%s/.npm npm_config_cache=%s/.npm TMPDIR=%s/.tmp " ..
"PATH=%s/node/bin:%s/global/bin:$PATH " ..
"%s -y @tencent-weixin/openclaw-weixin-cli install' >> /tmp/openclaw-wechat-install.log 2>&1; " ..
"RC=$?; echo $RC > /tmp/openclaw-wechat-install.exit; " ..
wechat_npm_fallback_install_cmd(install_path, "/tmp/openclaw-wechat-install.log", "/tmp/openclaw-wechat-install.exit") ..
wechat_register_plugin_cmd(install_path, node_bin, "/tmp/openclaw-wechat-install.log") ..
-- 关键修复: 安装完成后强制修复插件目录权限 (确保 Gateway 可读取插件)
-- 原因: npx/npm 以 root 身份创建目录,默认权限 700 导致其他用户无法读取
-- 注意: 保持 root:root 属主 (OpenClaw v2026.4.9+ 安全要求),仅修复权限模式
"chown -R root:root %s 2>/dev/null; chmod -R 755 %s 2>/dev/null; " ..
"[ -x /usr/libexec/openclaw-permissions.sh ] && /usr/libexec/openclaw-permissions.sh fix-state %s/.openclaw >/dev/null 2>&1; " ..
"if [ $RC -eq 0 ]; then echo '✅ 微信插件安装成功!' >> /tmp/openclaw-wechat-install.log; " ..
"else echo '❌ 安装失败 (exit: '$RC')' >> /tmp/openclaw-wechat-install.log; fi " ..
") & echo $! > /tmp/openclaw-wechat-install.pid",
install_path, npx_bin,
oc_data, oc_data, oc_data,
oc_data, oc_data,
oc_data, oc_data, oc_data,
install_path, npx_bin, node_bin,
shellquote(oc_data),
oc_data, oc_data,
oc_data, oc_data, oc_data,
install_path, oc_data, oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data,
install_path, install_path, npx_bin,
extensions_dir, extensions_dir
oc_data
)
sys.exec(install_cmd)
@@ -1573,28 +1695,53 @@ function action_wechat_login()
-- 清理旧状态和可能的残留进程
sys.exec("kill -9 $(cat /tmp/openclaw-wechat-login.pid 2>/dev/null) 2>/dev/null")
sys.exec("pkill -f 'channels login --channel openclaw-weixin' 2>/dev/null")
sys.exec("rm -f /tmp/openclaw-wechat-qrcode.txt /tmp/openclaw-wechat-login.pid /tmp/openclaw-wechat-login.exit /tmp/openclaw-wechat-restarted") -- 后台启动登录流程,将二维码输出到文件
sys.exec("rm -f /tmp/openclaw-wechat-qrcode.txt /tmp/openclaw-wechat-login.pid /tmp/openclaw-wechat-login.exit /tmp/openclaw-wechat-restarted")
local wechat_plugin_dir = find_wechat_plugin_dir(install_path)
if not wechat_plugin_dir then
write_wechat_log_and_exit(
"/tmp/openclaw-wechat-qrcode.txt",
"/tmp/openclaw-wechat-login.exit",
"微信插件未安装...\n安装路径: " .. install_path .. "\n未找到包含 openclaw.plugin.json 的微信插件目录。\n请先安装插件或重新安装插件。\n",
1
)
http.prepare_content("application/json")
http.write_json({ status = "error", message = "微信插件未安装或未找到,请先安装/重新安装插件" })
return
end
-- 后台启动登录流程,将二维码输出到文件
local login_cmd = string.format(
"( " ..
openclaw_user_runner_cmd() ..
"echo '正在启动微信登录...' > /tmp/openclaw-wechat-qrcode.txt; " ..
"echo '安装路径: %s' >> /tmp/openclaw-wechat-qrcode.txt; " ..
"echo 'OpenClaw 入口: %s' >> /tmp/openclaw-wechat-qrcode.txt; " ..
"echo '微信插件目录: %s' >> /tmp/openclaw-wechat-qrcode.txt; " ..
"echo 'Node 版本:' $(%s -v 2>/dev/null || echo 未检测到) >> /tmp/openclaw-wechat-qrcode.txt; " ..
"if command -v python3 >/dev/null 2>&1; then echo 'python3: 已安装' >> /tmp/openclaw-wechat-qrcode.txt; else echo '⚠️ python3: 未安装,微信插件可能无法完成配对' >> /tmp/openclaw-wechat-qrcode.txt; fi; " ..
wechat_network_probe_cmd(node_bin, "/tmp/openclaw-wechat-qrcode.txt") ..
"mkdir -p %s/.npm %s/.tmp %s/.openclaw/openclaw-weixin; " ..
"touch %s/.openclaw/openclaw.json 2>/dev/null || true; " ..
"chown -R openclaw:openclaw %s/.npm %s/.tmp %s/.openclaw/openclaw-weixin 2>/dev/null; " ..
"chown openclaw:openclaw %s/.openclaw %s/.openclaw/openclaw.json 2>/dev/null; " ..
"su -s /bin/sh openclaw -c 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw && test -w %s/.openclaw/openclaw-weixin && test -w %s/.openclaw/openclaw.json' || { echo '❌ openclaw 用户无法写入微信登录目录,请检查数据目录权限' >> /tmp/openclaw-wechat-qrcode.txt; echo 1 > /tmp/openclaw-wechat-login.exit; exit 0; }; " ..
"_oc_as_openclaw 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw && test -w %s/.openclaw/openclaw-weixin && test -w %s/.openclaw/openclaw.json' || { echo '❌ openclaw 用户无法写入微信登录目录,请检查数据目录权限' >> /tmp/openclaw-wechat-qrcode.txt; echo 1 > /tmp/openclaw-wechat-login.exit; exit 0; }; " ..
"RC=0; " ..
wechat_register_plugin_cmd(install_path, node_bin, "/tmp/openclaw-wechat-qrcode.txt", "/tmp/openclaw-wechat-login.exit") ..
"if [ $RC -ne 0 ]; then echo '❌ 微信插件注册失败,无法登录' >> /tmp/openclaw-wechat-qrcode.txt; exit 0; fi; " ..
"cd %s && " ..
"su -s /bin/sh openclaw -c 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json " ..
"_oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json " ..
"NPM_CONFIG_CACHE=%s/.npm npm_config_cache=%s/.npm TMPDIR=%s/.tmp PATH=%s/node/bin:%s/global/bin:$PATH " ..
"%s %s channels login --channel openclaw-weixin' >> /tmp/openclaw-wechat-qrcode.txt 2>&1; " ..
"echo $? > /tmp/openclaw-wechat-login.exit; " ..
") >/dev/null 2>&1 & echo $! > /tmp/openclaw-wechat-login.pid",
install_path,
install_path, oc_entry, wechat_plugin_dir, node_bin,
oc_data, oc_data, oc_data, oc_data, oc_data, oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data, oc_data, oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data, oc_data, oc_data, oc_data, oc_data,
install_path, install_path, node_bin, oc_entry
) sys.exec(login_cmd)
)
sys.exec(login_cmd)
http.prepare_content("application/json")
http.write_json({ status = "ok", message = "微信登录流程已启动" })
@@ -1637,11 +1784,21 @@ function action_wechat_login_status()
end
end
-- 提取最后生成的二维码 URL
-- 提取最后生成的二维码 URL;排除前置网络探测中的接口 URL,避免页面误导用户扫码错误链接
local qrcode_url = ""
for url in qrcode:gmatch("https?://[^\n\r]+") do
qrcode_url = url
end -- 检查是否登录成功
for url in qrcode:gmatch("https?://[^%s%]%)}\"'<>]+") do
if url:match("liteapp%.weixin%.qq%.com/q/") or url:match("weixin%.qq%.com/q/") then
qrcode_url = url
end
end
if qrcode_url == "" then
for url in qrcode:gmatch("https?://[^%s%]%)}\"'<>]+") do
if not url:match("ilinkai%.weixin%.qq%.com") then
qrcode_url = url
end
end
end
-- 检查是否登录成功
local logged_in = qrcode:find("登录成功") ~= nil or qrcode:find("成功登录") ~= nil or qrcode:find("Login success") ~= nil or qrcode:find("Logged in") ~= nil
local state = "idle"
@@ -1657,11 +1814,24 @@ function action_wechat_login_status()
state = "failed"
end
-- 如果刚登录成功,触发一次重启,确保主进程加载微信
local error_detail = ""
local message = ""
if state == "failed" then
error_detail = wechat_tail_detail(qrcode, 35)
if error_detail ~= "" then
message = "登录失败,下面是最近日志,请按提示处理"
else
message = "登录失败,请查看 /tmp/openclaw-wechat-qrcode.txt"
end
end
-- 如果刚登录成功,触发一次重启,确保主进程加载微信账号
if state == "success" and not nixio.fs.stat("/tmp/openclaw-wechat-restarted", "type") then
sys.exec("touch /tmp/openclaw-wechat-restarted")
sys.exec("/etc/init.d/openclaw restart &")
end http.prepare_content("application/json")
message = "微信登录成功,正在重新加载微信账号"
end
http.prepare_content("application/json")
http.write_json({
status = "ok",
state = state,
@@ -1669,7 +1839,9 @@ function action_wechat_login_status()
qrcode_url = qrcode_url,
running = running,
exit_code = exit_code,
logged_in = logged_in
logged_in = logged_in,
message = message,
error_detail = error_detail
})
end
@@ -1778,7 +1950,7 @@ function action_wechat_check_upgrade()
latest_version = sys.exec(check_cmd):gsub("%s+", "")
local has_upgrade = false
if current_version ~= "" and latest_version ~= "" and current_version ~= latest_version then
if is_newer_version(latest_version, current_version) then
has_upgrade = true
end
@@ -1849,38 +2021,40 @@ function action_wechat_upgrade_plugin()
local extensions_dir = install_path .. "/data/.openclaw/extensions"
local upgrade_cmd = string.format(
"( " ..
openclaw_user_runner_cmd() ..
"echo '正在升级微信插件...' > /tmp/openclaw-wechat-install.log; " ..
"echo '安装路径: %s' >> /tmp/openclaw-wechat-install.log; " ..
"echo 'npx 路径: %s' >> /tmp/openclaw-wechat-install.log; " ..
"echo 'Node 版本:' $(%s -v 2>/dev/null || echo 未检测到) >> /tmp/openclaw-wechat-install.log; " ..
wechat_network_probe_cmd(node_bin, "/tmp/openclaw-wechat-install.log") ..
wechat_python3_bootstrap_cmd("/tmp/openclaw-wechat-install.log") ..
"mkdir -p %s/.npm %s/.tmp %s/.openclaw/extensions; " ..
"if [ ! -w %s/.openclaw/extensions ]; then chown -R openclaw:openclaw %s/.openclaw/extensions 2>/dev/null; fi; " ..
"chown -R openclaw:openclaw %s/.npm %s/.tmp %s/.openclaw 2>/dev/null; " ..
"OC_WECHAT_DATA=%s; export OC_WECHAT_DATA; " ..
"if [ -x /usr/libexec/openclaw-permissions.sh ]; then /usr/libexec/openclaw-permissions.sh prepare-workdirs \"$OC_WECHAT_DATA\" >/dev/null 2>&1; " ..
"else mkdir -p \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" \"$OC_WECHAT_DATA/.openclaw/extensions\"; chown -R openclaw:openclaw \"$OC_WECHAT_DATA/.npm\" \"$OC_WECHAT_DATA/.tmp\" 2>/dev/null; chown openclaw:openclaw \"$OC_WECHAT_DATA/.openclaw\" 2>/dev/null; fi; " ..
"if [ ! -w %s/.npm ] || [ ! -w %s/.tmp ]; then echo '❌ npm cache/tmp 目录不可写' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; fi; " ..
"su -s /bin/sh openclaw -c 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw' || { echo '❌ openclaw 用户无法写入 npm cache/tmp/data 目录' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; }; " ..
"_oc_as_openclaw 'test -w %s/.npm && test -w %s/.tmp && test -w %s/.openclaw' || { echo '❌ openclaw 用户无法写入 npm cache/tmp/data 目录' >> /tmp/openclaw-wechat-install.log; echo 1 > /tmp/openclaw-wechat-install.exit; exit 0; }; " ..
"cd %s && " ..
"su -s /bin/sh openclaw -c 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw " ..
"_oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw " ..
"OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json NPM_CONFIG_CACHE=%s/.npm npm_config_cache=%s/.npm TMPDIR=%s/.tmp " ..
"PATH=%s/node/bin:%s/global/bin:$PATH " ..
"%s -y @tencent-weixin/openclaw-weixin-cli install' >> /tmp/openclaw-wechat-install.log 2>&1; " ..
"RC=$?; echo $RC > /tmp/openclaw-wechat-install.exit; " ..
wechat_npm_fallback_install_cmd(install_path, "/tmp/openclaw-wechat-install.log", "/tmp/openclaw-wechat-install.exit") ..
wechat_register_plugin_cmd(install_path, node_bin, "/tmp/openclaw-wechat-install.log") ..
-- 关键修复: 升级完成后强制修复插件目录权限 (确保 Gateway 可读取插件)
-- 注意: 保持 root:root 属主 (OpenClaw v2026.4.9+ 安全要求),仅修复权限模式
"chown -R root:root %s 2>/dev/null; chmod -R 755 %s 2>/dev/null; " ..
"[ -x /usr/libexec/openclaw-permissions.sh ] && /usr/libexec/openclaw-permissions.sh fix-state %s/.openclaw >/dev/null 2>&1; " ..
"if [ $RC -eq 0 ]; then echo '✅ 微信插件升级成功!' >> /tmp/openclaw-wechat-install.log; " ..
"else echo '❌ 升级失败 (exit: '$RC')' >> /tmp/openclaw-wechat-install.log; fi " ..
") & echo $! > /tmp/openclaw-wechat-install.pid",
install_path, npx_bin,
oc_data, oc_data, oc_data,
oc_data, oc_data,
oc_data, oc_data, oc_data,
install_path, npx_bin, node_bin,
shellquote(oc_data),
oc_data, oc_data,
oc_data, oc_data, oc_data,
install_path, oc_data, oc_data, oc_data,
oc_data, oc_data, oc_data, oc_data,
install_path, install_path, npx_bin,
extensions_dir, extensions_dir
oc_data
)
sys.exec(upgrade_cmd)
@@ -1940,7 +2114,7 @@ function action_wechat_logout()
-- 在后台执行 logout
local logout_cmd = string.format(
"cd %s && su -s /bin/sh openclaw -c 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json " ..
openclaw_user_runner_cmd() .. "cd %s && _oc_as_openclaw 'HOME=%s OPENCLAW_HOME=%s OPENCLAW_STATE_DIR=%s/.openclaw OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json " ..
"PATH=%s/node/bin:%s/global/bin:$PATH " ..
"%s %s channels logout --channel openclaw-weixin --account \"%s\"'",
oc_data, oc_data, oc_data, oc_data, oc_data, install_path, install_path, node_bin, oc_entry, account_id
@@ -309,11 +309,12 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
<div class="oc-prereq-box">
<h3>📋 前置条件</h3>
<ol>
<li><strong>微信客户端版本 >= 8.0.70</strong> — 旧版本可能不支持插件功能</li>
<li><strong>启用 ClawBot 插件</strong> — 安装插件后→ 打开微信客户端「扫一扫」扫描屏幕中的二维码</li>
<li><strong>先安装/重新安装微信插件</strong> — 页面会自动检查 Node、python3、目录权限和微信接口连通性</li>
<li><strong>点击“登录账号”</strong> — 出现链接后先点击打开,再使用微信客户端「扫一扫」扫码</li>
<li><strong>等待成功提示</strong> — 成功后网关会重新加载微信账号,再用微信发一条消息验证</li>
</ol>
<div class="warning">
⚠️ 注意:微信登录有可能会验证账号安全环境,为了防止微信风控封号,尽量使用常用账号且确保账号状态正常。通过上方扫码即可直接连接,无需在手机端手动启用额外功能。
⚠️ 常见失败原因:二维码过期、账号风控、路由器到 ilinkai.weixin.qq.com TLS/timeout、插件注册缺失、数据目录权限错误。失败时页面会显示最近日志。
</div>
</div>
@@ -553,13 +554,12 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
// 2. 清理指定的特定前缀
cleanLog = cleanLog.replace(/\[openclaw-weixin\] /g, '');
// 3. 提取其中的二维码链接
var linkMatch = cleanLog.match(/(https:\/\/liteapp\.weixin\.qq\.com\/q\/[^\s]+)/);
if (linkMatch && linkMatch[1]) {
var linkUrl = linkMatch[1];
// 3. 提取其中的微信登录链接
var linkUrl = extractWechatLoginUrl(cleanLog);
if (linkUrl) {
var linkContainer = document.getElementById('oc-install-qrcode-link');
linkContainer.style.display = 'block';
linkContainer.innerHTML = '<a href="' + escapeHtml(linkUrl) + '" target="_blank" rel="noopener noreferrer" class="oc-qrcode-link-btn" style="padding: 10px 20px; font-size: 14px; margin: 0;">🔗 点击此处前往浏览器扫码登录</a>';
linkContainer.innerHTML = '<a href="' + escapeHtml(linkUrl) + '" target="_blank" rel="noopener noreferrer" class="oc-qrcode-link-btn" style="padding: 10px 20px; font-size: 14px; margin: 0;">🔗 点击打开链接,然后用微信扫码</a>';
}
logEl.textContent = cleanLog;
@@ -608,7 +608,7 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
document.getElementById('oc-install-progress').style.display = 'none';
var resultEl = document.getElementById('oc-install-result');
resultEl.style.display = 'block';
resultEl.innerHTML = '<div class="oc-result-error">❌ ' + message + '</div>';
resultEl.innerHTML = '<div class="oc-result-error">❌ ' + escapeHtml(message) + '</div>';
var btn = document.getElementById('btn-install-plugin');
btn.disabled = false;
@@ -669,6 +669,22 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
return div.innerHTML;
}
function extractWechatLoginUrl(text) {
if (!text) return '';
var matches = text.match(/https?:\/\/[^\s\]})"'<>]+/g) || [];
var fallback = '';
for (var i = 0; i < matches.length; i++) {
var url = matches[i];
if (/liteapp\.weixin\.qq\.com\/q\//.test(url) || /weixin\.qq\.com\/q\//.test(url)) {
fallback = url;
} else if (!/ilinkai\.weixin\.qq\.com/.test(url)) {
fallback = url;
}
}
return fallback;
}
// 复制到剪贴板
window.ocCopyToClipboard = function(text, btn) {
if (navigator.clipboard && navigator.clipboard.writeText) {
@@ -720,34 +736,33 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
// 是否出现二维码过期文本提示
var isExpired = d.qrcode && d.qrcode.indexOf('已被扫描') > -1 || d.qrcode.indexOf('二维码已过期') > -1;
var statusMessage = isExpired ? '<p style="color:#cf222e;font-size:13px;margin:8px 0;text-align:center;">⌛ 二维码已刷新,请点击最新链接扫码登录</p>'
: '<p style="font-size:14px;color:#333;margin-bottom:8px;text-align:center;">📱 请点击下方按钮打开微信扫码登录</p>';
: '<p style="font-size:14px;color:#333;margin-bottom:8px;text-align:center;">📱 请点击下方链接打开二维码,再用微信扫码</p>';
if (d.state === 'success' || d.logged_in) {
showLoginSuccess();
} else if (d.state === 'failed') {
showLoginError('登录失败');
showLoginError(d.message || '登录失败', d.error_detail || d.qrcode || '');
} else if (d.state === 'qrcode' && d.qrcode_url) {
document.getElementById('oc-login-progress').style.display = 'none';
qrcodeEl.style.display = 'block';
var safeUrl = escapeHtml(d.qrcode_url);
qrcodeEl.innerHTML = '<div class="oc-qrcode-box">' +
statusMessage +
'<a href="' + safeUrl + '" target="_blank" rel="noopener noreferrer" class="oc-qrcode-link-btn" style="font-size:16px;">🔗 点击获取微信登录二维码</a>' +
'<a href="' + safeUrl + '" target="_blank" rel="noopener noreferrer" class="oc-qrcode-link-btn" style="font-size:16px;">🔗 点击打开链接,然后用微信扫码</a>' +
'<p class="oc-qrcode-copy-link">或复制链接: <code>' + safeUrl + '</code>' +
'<button class="oc-copy-btn" onclick="ocCopyToClipboard(\'' + safeUrl.replace(/'/g, "\\'") + '\', this)">复制</button></p>' +
'</div>';
loginPollTimer = setTimeout(pollLoginStatus, 2000);
} else if (d.running) {
if (d.qrcode && d.qrcode.length > 50) {
var urlMatches = d.qrcode.match(/https?:\/\/[^\s\n]+/g);
if (urlMatches && urlMatches.length > 0) {
var extractedUrl = extractWechatLoginUrl(d.qrcode);
if (extractedUrl) {
document.getElementById('oc-login-progress').style.display = 'none';
qrcodeEl.style.display = 'block';
var extractedUrl = urlMatches[urlMatches.length - 1]; // 取最后一个
var safeUrl2 = escapeHtml(extractedUrl);
qrcodeEl.innerHTML = '<div class="oc-qrcode-box">' +
statusMessage +
'<a href="' + safeUrl2 + '" target="_blank" rel="noopener noreferrer" class="oc-qrcode-link-btn" style="font-size:16px;">🔗 点击获取微信登录二维码</a>' +
'<a href="' + safeUrl2 + '" target="_blank" rel="noopener noreferrer" class="oc-qrcode-link-btn" style="font-size:16px;">🔗 点击打开链接,然后用微信扫码</a>' +
'<p class="oc-qrcode-copy-link">或复制链接: <code>' + safeUrl2 + '</code>' +
'<button class="oc-copy-btn" onclick="ocCopyToClipboard(\'' + safeUrl2.replace(/'/g, "\\'") + '\', this)">复制</button></p>' +
'</div>';
@@ -770,17 +785,22 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
document.getElementById('oc-login-qrcode').style.display = 'none';
var resultEl = document.getElementById('oc-login-result');
resultEl.style.display = 'block';
resultEl.innerHTML = '<div class="oc-result-success">✅ 微信登录成功!<br><br>现在可以开始与 AI 助手对话了。</div>';
resultEl.innerHTML = '<div class="oc-result-success">✅ 微信登录成功!<br><br>网关正在重新加载微信账号,请等待 10-20 秒后,用微信给网关发一条测试消息验证。</div>';
ocRefreshWechatStatus();
}
// 显示登录失败
function showLoginError(message) {
function showLoginError(message, detail) {
document.getElementById('oc-login-progress').style.display = 'none';
var resultEl = document.getElementById('oc-login-result');
resultEl.style.display = 'block';
resultEl.innerHTML = '<div class="oc-result-error">❌ ' + message + '</div>';
var html = '<div class="oc-result-error">❌ ' + escapeHtml(message || '登录失败');
if (detail) {
html += '<pre class="oc-error-detail">' + escapeHtml(detail) + '</pre>';
}
html += '</div>';
resultEl.innerHTML = html;
}
// 关闭登录对话框
@@ -867,7 +887,7 @@ local pty_port = uci:get("openclaw", "main", "pty_port") or "18793"
var linkContainer = document.getElementById('oc-upgrade-qrcode-link');
if (linkContainer) {
linkContainer.style.display = 'block';
linkContainer.innerHTML = '<a href="' + escapeHtml(linkUrl) + '" target="_blank" rel="noopener noreferrer" class="oc-qrcode-link-btn" style="padding: 10px 20px; font-size: 14px; margin: 0;">🔗 点击此处前往浏览器扫码登录</a>';
linkContainer.innerHTML = '<a href="' + escapeHtml(linkUrl) + '" target="_blank" rel="noopener noreferrer" class="oc-qrcode-link-btn" style="padding: 10px 20px; font-size: 14px; margin: 0;">🔗 点击打开链接,然后用微信扫码</a>';
}
}
logEl.textContent = cleanLog;
+87 -46
View File
@@ -16,6 +16,10 @@ EXTRA_HELP=" setup 下载 Node.js 并安装 OpenClaw
[ -r /usr/libexec/openclaw-paths.sh ] && . /usr/libexec/openclaw-paths.sh
[ -r /usr/libexec/openclaw-node.sh ] && . /usr/libexec/openclaw-node.sh
OC_NODE_MIN_VERSION="${OC_NODE_MIN_VERSION:-22.19.0}"
fix_openclaw_state_permissions() {
[ -x /usr/libexec/openclaw-permissions.sh ] && /usr/libexec/openclaw-permissions.sh fix-state "${OC_DATA}/.openclaw"
}
OC_CONFIGURED_PATH="$(uci -q get openclaw.main.install_path 2>/dev/null || echo '/opt')"
if command -v oc_load_paths >/dev/null 2>&1; then
if ! oc_load_paths "$OC_CONFIGURED_PATH"; then
@@ -311,6 +315,59 @@ fi
# 同步 UCI 到 JSON
sync_uci_to_json
# 微信 npm 插件自愈注册:
# 有些环境里 npm projects 已安装 @tencent-weixin/openclaw-weixin,
# 但 openclaw.json 缺少 plugins.installs / plugins.allow / channels 配置,
# 会导致页面显示“已安装”而 Gateway 或登录流程无法稳定加载插件。
self_heal_wechat_npm_plugin_config() {
[ -x "$NODE_BIN" ] || return 0
[ -f "$CONFIG_FILE" ] || return 0
local npm_projects_dir="${OC_DATA}/.openclaw/npm/projects"
[ -d "$npm_projects_dir" ] || return 0
local wechat_plugin_json wechat_plugin_dir heal_out
wechat_plugin_json=$(find "$npm_projects_dir" -path "*/node_modules/@tencent-weixin/openclaw-weixin/openclaw.plugin.json" -type f 2>/dev/null | head -n 1)
[ -n "$wechat_plugin_json" ] || return 0
wechat_plugin_dir="${wechat_plugin_json%/openclaw.plugin.json}"
heal_out=$(OC_CONFIG="$CONFIG_FILE" OC_WECHAT_PLUGIN_DIR="$wechat_plugin_dir" "$NODE_BIN" -e "
const fs=require('fs');
const p=process.env.OC_CONFIG;
const pluginDir=process.env.OC_WECHAT_PLUGIN_DIR;
let d={};
try{d=JSON.parse(fs.readFileSync(p,'utf8'));}catch(e){}
if(!d.plugins||typeof d.plugins!=='object')d.plugins={};
if(!d.plugins.installs||typeof d.plugins.installs!=='object')d.plugins.installs={};
if(!Array.isArray(d.plugins.allow))d.plugins.allow=[];
if(!d.channels||typeof d.channels!=='object')d.channels={};
let changed=false;
const want={kind:'npm',installPath:pluginDir,packageName:'@tencent-weixin/openclaw-weixin'};
const cur=d.plugins.installs['openclaw-weixin'];
if(!cur||cur.kind!==want.kind||cur.installPath!==want.installPath||cur.packageName!==want.packageName){
d.plugins.installs['openclaw-weixin']=want; changed=true;
}
if(!d.plugins.allow.includes('openclaw-weixin')){
d.plugins.allow.push('openclaw-weixin'); changed=true;
}
if(!d.channels['openclaw-weixin']||typeof d.channels['openclaw-weixin']!=='object'){
d.channels['openclaw-weixin']={}; changed=true;
}
if(d.channels['openclaw-weixin'].enabled!==true){
d.channels['openclaw-weixin'].enabled=true; changed=true;
}
if(d.plugins.entries&&d.plugins.entries['openclaw-weixin']){
delete d.plugins.entries['openclaw-weixin']; changed=true;
}
if(changed){
fs.writeFileSync(p,JSON.stringify(d,null,2)+'\n');
console.log('FIXED');
}
" 2>/dev/null)
if echo "$heal_out" | grep -q "FIXED"; then
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
logger -t openclaw "已自愈注册 openclaw-weixin npm plugin: $wechat_plugin_dir"
fi
}
self_heal_wechat_npm_plugin_config
# v2026.3.13: 修复插件配置中的插件名称不匹配问题
# OpenClaw 加强了配置验证,plugins.allow 中的名称必须与实际插件名完全匹配
# 问题: 旧版本写入的是 "openclaw-qqbot",但实际插件名是 "@tencent-connect/openclaw-qqbot"
@@ -361,7 +418,7 @@ fi
fix_plugin_config
# 修复数据目录权限 (防止 root 用户操作后留下无法读取的文件)
chown -R openclaw:openclaw "$OC_DATA" 2>/dev/null || true
fix_openclaw_state_permissions 2>/dev/null || true
# v2026.4.9: 修复插件目录权限 (OpenClaw 要求插件目录属主为 root)
# 详见: https://github.com/nicepkg/openclaw/releases/tag/v2026.4.9
@@ -389,17 +446,8 @@ fi
mv "$stale_dir" "${archive_root}/${stale_name}" 2>/dev/null || true
done
fi
if [ -d "$ext_dir" ]; then
chown -R root:root "$ext_dir" 2>/dev/null || true
chmod -R 755 "$ext_dir" 2>/dev/null || true
fi
if [ -d "$npm_projects_dir" ]; then
chown -R root:root "$npm_projects_dir" 2>/dev/null || true
chmod -R 755 "$npm_projects_dir" 2>/dev/null || true
fi
fix_openclaw_state_permissions 2>/dev/null || true
# v2026.4.x: 清理 jiti 缓存目录 (修复微信插件加载权限问题)
# jiti 编译 TypeScript 时会在 /tmp/jiti 创建缓存,如果由 root 创建则 openclaw 用户无法写入
rm -rf /tmp/jiti 2>/dev/null || true
# v2026.4.10: 优化配置迁移 - 只在版本变更时运行 doctor --fix
@@ -425,6 +473,7 @@ fi
if [ "$current_ver" != "$last_ver" ]; then
logger -t openclaw "检测到版本变更 ($last_ver -> $current_ver),执行配置迁移 (doctor --fix)..."
OPENCLAW_HOME="$OC_DATA" OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \
NODE_ICU_DATA="${NODE_BASE}/share/icu" PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH" \
"$NODE_BIN" "$oc_entry" doctor --fix 2>/dev/null && \
logger -t openclaw "配置迁移完成" || \
logger -t openclaw "配置迁移失败,请手动检查"
@@ -439,14 +488,16 @@ fi
# doctor --fix 后再次修复权限 (防止 root 创建的文件导致 EACCES)
# 注意: extensions 目录保持 root 权限 (OpenClaw 安全要求)
find "$OC_DATA" -user root ! -path "${OC_DATA}/.openclaw/extensions*" -exec chown openclaw:openclaw {} \; 2>/dev/null || true
fix_openclaw_state_permissions 2>/dev/null || true
# v2026.4.10: doctor --fix 后确保关键配置字段存在
# doctor --fix 可能会删除或遗漏某些必要字段,需要补充
_ensure_critical_config() {
[ ! -f "$CONFIG_FILE" ] && return
[ ! -x "$NODE_BIN" ] && return
"$NODE_BIN" -e "
local critical_token
critical_token=$(uci -q get openclaw.main.token || echo "")
OC_SYNC_TOKEN="$critical_token" "$NODE_BIN" -e "
const fs = require('fs');
const f = '${CONFIG_FILE}';
let d = {};
@@ -469,6 +520,9 @@ if (d.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback !== true) d.gat
// 确保认证配置
if (!d.gateway.auth) d.gateway.auth = {};
if (!d.gateway.auth.mode) d.gateway.auth.mode = 'token';
if (!d.gateway.auth.token && process.env.OC_SYNC_TOKEN) {
d.gateway.auth.token = process.env.OC_SYNC_TOKEN;
}
// 禁用 ACP dispatch (路由器内存有限)
if (!d.acp) d.acp = {};
@@ -507,43 +561,26 @@ if(d.gateway&&d.gateway.auth&&d.gateway.auth.token)process.stdout.write(d.gatewa
uci set openclaw.main.token="$json_token"
uci commit openclaw 2>/dev/null
logger -t openclaw "Token 同步 (doctor 后): JSON -> UCI"
elif [ -z "$json_token" ] && [ -n "$uci_token" ]; then
OC_SYNC_TOKEN="$uci_token" OC_SYNC_FILE="$CONFIG_FILE" "$NODE_BIN" -e "
const fs=require('fs');
const f=process.env.OC_SYNC_FILE;
let d={};
try{d=JSON.parse(fs.readFileSync(f,'utf8'));}catch(e){}
if(!d.gateway)d.gateway={};
if(!d.gateway.auth)d.gateway.auth={};
d.gateway.auth.mode=d.gateway.auth.mode||'token';
d.gateway.auth.token=process.env.OC_SYNC_TOKEN;
fs.writeFileSync(f, JSON.stringify(d, null, 2));
" 2>/dev/null
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
logger -t openclaw "Token 同步 (doctor 后): UCI -> JSON"
fi
}
_sync_token_after_doctor
# v2026.4.10: 最终权限修复 - 确保 Gateway 启动前所有数据目录权限正确
# doctor --fix 或 _ensure_critical_config 可能创建新的目录/文件
# 关键: agents 目录下的 agent 子目录必须可被 openclaw 用户写入
find "${OC_DATA}/.openclaw" -user root \
! -path "${OC_DATA}/.openclaw/extensions*" \
! -path "${OC_DATA}/.openclaw/archived-extensions*" \
! -path "${OC_DATA}/.openclaw/npm/projects*" \
-type d -exec chown openclaw:openclaw {} \; 2>/dev/null || true
find "${OC_DATA}/.openclaw" -user root \
! -path "${OC_DATA}/.openclaw/extensions*" \
! -path "${OC_DATA}/.openclaw/archived-extensions*" \
! -path "${OC_DATA}/.openclaw/npm/projects*" \
-type f -exec chown openclaw:openclaw {} \; 2>/dev/null || true
# 特别确保 agents/main/agent 目录权限 (模型配置写入目录)
local agent_dir="${OC_DATA}/.openclaw/agents/main/agent"
if [ -d "$agent_dir" ]; then
chown openclaw:openclaw "$agent_dir" 2>/dev/null || true
chmod 755 "$agent_dir" 2>/dev/null || true
fi
# extensions 目录除外 - 必须保持 root 权限 (OpenClaw 安全要求)
# 同时修复权限模式为 755,确保 Gateway 可读取插件
if [ -d "${OC_DATA}/.openclaw/extensions" ]; then
chown -R root:root "${OC_DATA}/.openclaw/extensions" 2>/dev/null || true
chmod -R 755 "${OC_DATA}/.openclaw/extensions" 2>/dev/null || true
fi
if [ -d "${OC_DATA}/.openclaw/archived-extensions" ]; then
chown -R root:root "${OC_DATA}/.openclaw/archived-extensions" 2>/dev/null || true
chmod -R 700 "${OC_DATA}/.openclaw/archived-extensions" 2>/dev/null || true
fi
if [ -d "${OC_DATA}/.openclaw/npm/projects" ]; then
chown -R root:root "${OC_DATA}/.openclaw/npm/projects" 2>/dev/null || true
chmod -R 755 "${OC_DATA}/.openclaw/npm/projects" 2>/dev/null || true
fi
# v2026.4.10: final permission fix before Gateway start
fix_openclaw_state_permissions 2>/dev/null || true
# Patch iframe 安全头,允许 LuCI 嵌入
patch_iframe_headers
@@ -557,6 +594,9 @@ all) gw_bind="custom" ;; # custom = 0.0.0.0
*) gw_bind="$bind" ;;
esac
local gw_token
gw_token=$(uci -q get openclaw.main.token || echo "")
# 确保网关端口未被残留进程占用 (防止 restart 时 crash loop)
# v2026.3.14 优化: 快速轮询 + 批量 kill
_ensure_port_free() {
@@ -619,6 +659,7 @@ _ensure_port_free "$port"
OPENCLAW_HOME="$OC_DATA" \
OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \
OPENCLAW_GATEWAY_TOKEN="$gw_token" \
NODE_ICU_DATA="${NODE_BASE}/share/icu" \
NODE_BASE="$NODE_BASE" \
OC_GLOBAL="$OC_GLOBAL" \
+33 -25
View File
@@ -146,9 +146,13 @@ probe_install_root() {
cleanup_partial_install() {
# 只在确认目标是规范化后的 openclaw 运行根目录时清理,避免误删用户目录。
if command -v oc_safe_openclaw_root >/dev/null 2>&1 && oc_safe_openclaw_root "$OC_INSTALL_PATH"; then
log_warn "安装失败,清理未完成的运行目录: $OC_INSTALL_PATH"
rm -rf "$OC_INSTALL_PATH" 2>/dev/null || true
[ -d /overlay/upper ] && rm -rf "/overlay/upper${OC_INSTALL_PATH}" 2>/dev/null || true
if [ "${OC_SETUP_FRESH_ROOT:-0}" = "1" ]; then
log_warn "安装失败,清理本次新建的运行目录: $OC_INSTALL_PATH"
rm -rf "$OC_INSTALL_PATH" 2>/dev/null || true
[ -d /overlay/upper ] && rm -rf "/overlay/upper${OC_INSTALL_PATH}" 2>/dev/null || true
else
log_warn "安装失败,检测到已有运行目录,已保留: $OC_INSTALL_PATH"
fi
fi
}
@@ -332,34 +336,33 @@ download_node() {
# 解压
echo " 正在解压到 ${NODE_BASE}..."
# 先解压到临时目录并确认完整,再替换 NODE_BASE。
# 这样即使 tar/xz 缺失或压缩包损坏,也不会删除已有 Node.js。
local tmp_extract="/tmp/node-extract-$$"
rm -rf "$tmp_extract" 2>/dev/null
ensure_mkdir "$tmp_extract"
if ! tar xf "$tmp_file" -C "$tmp_extract" 2>/dev/null; then
log_error "Node.js 解压失败,请确认已安装 GNU tar(opkg install tar)且下载文件完整"
rm -rf "$tmp_extract"
exit 1
fi
# 找顶层目录 (node-vX.X.X-linux-xxx)
local top_dir
top_dir=$(ls "$tmp_extract" 2>/dev/null | head -1)
if [ -z "$top_dir" ] || [ ! -d "$tmp_extract/$top_dir" ]; then
log_error "解压后未找到顶层目录,安装失败"
rm -rf "$tmp_extract"
exit 1
fi
# OverlayFS 兼容: rm -rf 后可能因 whiteout 导致 mkdir 失败
# 先尝试常规方式,失败则通过 overlay upper 层操作
# 只有在临时目录已解压成功后才替换正式 Node 目录。
rm -rf "$NODE_BASE" 2>/dev/null
if [ -d /overlay/upper ]; then
rm -rf "/overlay/upper${NODE_BASE}" 2>/dev/null
fi
ensure_mkdir "$NODE_BASE"
# 兼容 BusyBox tar (不支持 --strip-components) 和 GNU tar
# 方法: 先解压到临时目录,再移动顶层子目录内容到目标目录
if tar --strip-components=1 -xf "$tmp_file" -C "$NODE_BASE" 2>/dev/null; then
: # GNU tar 成功
else
# BusyBox tar 回退: 解压到临时目录后手动移动
local tmp_extract="/tmp/node-extract-$$"
ensure_mkdir "$tmp_extract"
tar xf "$tmp_file" -C "$tmp_extract"
# 找顶层目录 (node-vX.X.X-linux-xxx)
local top_dir
top_dir=$(ls "$tmp_extract" 2>/dev/null | head -1)
if [ -n "$top_dir" ] && [ -d "$tmp_extract/$top_dir" ]; then
cp -a "$tmp_extract/$top_dir/." "$NODE_BASE/"
else
log_error "解压后未找到顶层目录,安装失败"
rm -rf "$tmp_extract"
exit 1
fi
rm -rf "$tmp_extract"
fi
cp -a "$tmp_extract/$top_dir/." "$NODE_BASE/"
rm -rf "$tmp_extract"
rm -f "$tmp_file"
# 验证
@@ -542,6 +545,11 @@ do_setup() {
ensure_openclaw_user
probe_install_root
if [ -e "$OC_INSTALL_PATH" ]; then
OC_SETUP_FRESH_ROOT=0
else
OC_SETUP_FRESH_ROOT=1
fi
OC_SETUP_CLEANUP=1
trap 'rc=$?; if [ $rc -ne 0 ] && [ "${OC_SETUP_CLEANUP:-0}" = "1" ]; then cleanup_partial_install; fi; exit $rc' EXIT
+135
View File
@@ -0,0 +1,135 @@
#!/bin/sh
# OpenClaw state permission helper.
# Keep runtime state writable by the openclaw user, while keeping plugin source
# directories root-owned for OpenClaw's official ownership checks.
oc_perm_state_dir() {
if [ -n "${1:-}" ]; then
printf '%s\n' "$1"
elif [ -n "${OPENCLAW_STATE_DIR:-}" ]; then
printf '%s\n' "$OPENCLAW_STATE_DIR"
elif [ -n "${OC_DATA:-}" ]; then
printf '%s/.openclaw\n' "$OC_DATA"
else
printf '/opt/openclaw/data/.openclaw\n'
fi
}
oc_perm_data_dir_from_state() {
local state_dir="$1"
case "$state_dir" in
*/.openclaw) printf '%s\n' "${state_dir%/.openclaw}" ;;
*) printf '%s\n' "${OC_DATA:-/opt/openclaw/data}" ;;
esac
}
oc_fix_npm_projects_permissions() {
local state_dir npm_projects plugin_json plugin_dir
state_dir="$(oc_perm_state_dir "${1:-}")"
npm_projects="${state_dir}/npm/projects"
[ -d "$npm_projects" ] || return 0
# Gateway must be able to clean npm project generations.
chown -R openclaw:openclaw "$npm_projects" 2>/dev/null || true
chmod u+rwx,go+rx "$npm_projects" 2>/dev/null || true
# Actual OpenClaw plugin roots must stay root-owned, otherwise OpenClaw refuses
# to load them with "suspicious ownership".
find "$npm_projects" -name openclaw.plugin.json -type f 2>/dev/null | while IFS= read -r plugin_json; do
plugin_dir="${plugin_json%/openclaw.plugin.json}"
case "$plugin_dir" in
*__openclaw-generation__*)
# Retained generation dirs are temporary and must remain removable by openclaw.
;;
"$npm_projects"/*/node_modules/*)
chown -R root:root "$plugin_dir" 2>/dev/null || true
chmod -R 755 "$plugin_dir" 2>/dev/null || true
;;
esac
done
# Compatibility for the WeChat plugin path even if its manifest scan fails.
find "$npm_projects" -path '*/node_modules/@tencent-weixin/openclaw-weixin' -type d -prune \
-exec chown -R root:root {} \; -exec chmod -R 755 {} \; 2>/dev/null || true
}
oc_fix_state_permissions() {
local state_dir data_dir ext_dir archive_dir config_file agent_dir
state_dir="$(oc_perm_state_dir "${1:-}")"
data_dir="$(oc_perm_data_dir_from_state "$state_dir")"
ext_dir="${state_dir}/extensions"
archive_dir="${state_dir}/archived-extensions"
config_file="${state_dir}/openclaw.json"
[ -d "$data_dir" ] && chown openclaw:openclaw "$data_dir" 2>/dev/null || true
[ -d "$state_dir" ] || mkdir -p "$state_dir" 2>/dev/null || true
[ -d "$state_dir" ] && chown openclaw:openclaw "$state_dir" 2>/dev/null || true
[ -d "$state_dir" ] && chmod 755 "$state_dir" 2>/dev/null || true
if [ -d "$state_dir" ]; then
find "$state_dir" -user root \
! -path "${ext_dir}*" \
! -path "${archive_dir}*" \
! -path "${state_dir}/npm/projects*" \
-exec chown openclaw:openclaw {} \; 2>/dev/null || true
fi
[ -f "$config_file" ] && chown openclaw:openclaw "$config_file" 2>/dev/null || true
[ -f "${config_file}.bak" ] && chown openclaw:openclaw "${config_file}.bak" 2>/dev/null || true
agent_dir="${state_dir}/agents/main/agent"
if [ -d "$agent_dir" ]; then
chown openclaw:openclaw "$agent_dir" 2>/dev/null || true
chmod 755 "$agent_dir" 2>/dev/null || true
fi
if [ -d "$ext_dir" ]; then
chown -R root:root "$ext_dir" 2>/dev/null || true
chmod -R 755 "$ext_dir" 2>/dev/null || true
fi
if [ -d "$archive_dir" ]; then
chown -R root:root "$archive_dir" 2>/dev/null || true
chmod -R 700 "$archive_dir" 2>/dev/null || true
fi
oc_fix_npm_projects_permissions "$state_dir"
}
oc_prepare_openclaw_workdirs() {
local data_dir state_dir npm_dir
data_dir="${1:-${OC_DATA:-/opt/openclaw/data}}"
state_dir="${data_dir}/.openclaw"
npm_dir="${state_dir}/npm/projects"
mkdir -p "${data_dir}/.npm" "${data_dir}/.tmp" "$npm_dir" "${state_dir}/extensions" 2>/dev/null || true
chown -R openclaw:openclaw "${data_dir}/.npm" "${data_dir}/.tmp" 2>/dev/null || true
chown openclaw:openclaw "$data_dir" "$state_dir" "${state_dir}/npm" "$npm_dir" 2>/dev/null || true
chmod u+rwx,go+rx "${data_dir}/.npm" "${data_dir}/.tmp" "$state_dir" "${state_dir}/npm" "$npm_dir" 2>/dev/null || true
# During install/upgrade, allow openclaw to update npm generations. Call
# fix-state after the install to restore plugin roots to root:root.
[ -d "$npm_dir" ] && chown -R openclaw:openclaw "$npm_dir" 2>/dev/null || true
}
if [ "${OPENCLAW_PERMISSIONS_SOURCED:-0}" = "1" ]; then
return 0
fi
case "${1:-fix-state}" in
fix-state)
shift || true
oc_fix_state_permissions "${1:-}"
;;
fix-npm-projects)
shift || true
oc_fix_npm_projects_permissions "${1:-}"
;;
prepare-workdirs)
shift || true
oc_prepare_openclaw_workdirs "${1:-}"
;;
*)
echo "Usage: $0 {fix-state [state_dir]|fix-npm-projects [state_dir]|prepare-workdirs [data_dir]}" >&2
exit 2
;;
esac
@@ -15,7 +15,7 @@
const path = require('path');
const fs = require('fs');
const { spawn, execSync } = require('child_process');
const { spawn, execSync, execFileSync } = require('child_process');
const menu = require('./oc-menu-engine');
const { C, select, input, confirm, spinner, resetRenderCount } = menu;
@@ -31,6 +31,7 @@ const OC_DATA = process.env.OC_DATA || `${OC_INSTALL_PATH}/data`;
const OC_STATE_DIR = process.env.OPENCLAW_STATE_DIR || `${OC_DATA}/.openclaw`;
const CONFIG_FILE = process.env.OPENCLAW_CONFIG_PATH || `${OC_STATE_DIR}/openclaw.json`;
const NODE_BIN = `${NODE_BASE}/bin/node`;
const PERMISSIONS_HELPER = '/usr/libexec/openclaw-permissions.sh';
// ═══════════════════════════════════════════════════════════════════════════
// 辅助函数 (与 oc-config.sh 逻辑对应)
@@ -73,6 +74,16 @@ function runCommand(cmd, args = [], options = {}) {
/**
* 读取 JSON 配置文件
*/
function fixStatePermissions() {
try {
execFileSync(PERMISSIONS_HELPER, ['fix-state', OC_STATE_DIR], { stdio: 'ignore', timeout: 10000 });
} catch {
try {
execSync(`find "${OC_STATE_DIR}" -user root ! -path "*/extensions*" ! -path "*/archived-extensions*" ! -path "*/npm/projects*" -exec chown openclaw:openclaw {} + 2>/dev/null || true`, { stdio: 'ignore' });
} catch {}
}
}
function readConfig() {
try {
if (fs.existsSync(CONFIG_FILE)) {
@@ -95,7 +106,7 @@ function writeConfig(config) {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
try {
execSync(`chown openclaw:openclaw "${CONFIG_FILE}"`, { stdio: 'ignore' });
execSync(`find "${OC_STATE_DIR}" -user root ! -path "*/extensions*" -exec chown openclaw:openclaw {} + 2>/dev/null || true`, { stdio: 'ignore' });
fixStatePermissions();
} catch {}
}
@@ -222,7 +233,7 @@ function authSetApikey(provider, apiKey, profileId) {
fs.writeFileSync(authFile, JSON.stringify(authData, null, 2));
try {
execSync(`chown openclaw:openclaw "${authFile}"`, { stdio: 'ignore' });
execSync(`find "${OC_STATE_DIR}" -user root ! -path "*/extensions*" -exec chown openclaw:openclaw {} + 2>/dev/null || true`, { stdio: 'ignore' });
fixStatePermissions();
} catch {}
}
@@ -444,6 +455,7 @@ async function showModelMenu() {
{ key: 'l', label: 'Ollama', desc: '本地模型,无需 API Key', value: 'ollama' },
{ key: 'm', label: '自定义 OpenAI 兼容 API', desc: '', value: 'custom' },
{ key: 'n', label: '自定义 Anthropic 兼容 API', desc: '', value: 'custom-anthropic' },
{ key: 'o', label: '一万AI分享 粉丝专享 API', desc: '', value: 'yiwanai-fan' },
{ label: '', disabled: true },
{ key: '0', label: '返回', desc: '', value: 'back' },
@@ -1242,6 +1254,33 @@ async function configureCustomAnthropic() {
return true;
}
async function configureYiwanAIFanAPI() {
resetRenderCount();
const providerName = 'yiwanai';
const baseUrl = 'https://api.910501.xyz/v1';
const modelName = 'gpt-5.5';
console.log(`\n${C.bold}一万AI分享 粉丝专享 API 配置${C.reset}`);
console.log(`${C.yellow}OpenAI 兼容模式;Base URL 和模型已内置,只需要填写 API Key。${C.reset}`);
console.log(`${C.dim}Base URL: ${baseUrl}${C.reset}`);
console.log(`${C.dim}Model: ${modelName}${C.reset}\n`);
const apiKey = await input({ prompt: 'API Key', placeholder: 'sk-...' });
if (!apiKey) { console.log(`${C.yellow}已取消${C.reset}`); return false; }
authSetApikey(providerName, apiKey, `${providerName}:fan`);
registerCustomProvider(providerName, baseUrl, apiKey, modelName, modelName);
const config = readConfig();
const provider = config.models?.providers?.[providerName];
if (provider?.models?.[0]) {
provider.models[0].reasoning = true;
}
writeConfig(config);
registerAndSetModel(`${providerName}/${modelName}`);
console.log(`\n${C.green}✅ 一万AI分享粉丝专享 API 已配置,活跃模型: ${providerName}/${modelName}${C.reset}\n`);
return true;
}
async function launchWizard() {
resetRenderCount();
console.log(`\n${C.cyan}启动官方完整模型配置向导...${C.reset}\n`);
@@ -1281,6 +1320,7 @@ async function handleModelConfig() {
case 'ollama': configured = await configureOllama(); break;
case 'custom': configured = await configureCustomAPI(); break;
case 'custom-anthropic': configured = await configureCustomAnthropic(); break;
case 'yiwanai-fan': configured = await configureYiwanAIFanAPI(); break;
}
if (configured) await askRestart();
@@ -61,6 +61,14 @@ export OPENCLAW_CONFIG_PATH="$CONFIG_FILE"
export NODE_ICU_DATA="${NODE_BASE}/share/icu"
export PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
fix_openclaw_state_permissions() {
[ -x /usr/libexec/openclaw-permissions.sh ] && /usr/libexec/openclaw-permissions.sh fix-state "$OC_STATE_DIR"
}
prepare_openclaw_workdirs() {
[ -x /usr/libexec/openclaw-permissions.sh ] && /usr/libexec/openclaw-permissions.sh prepare-workdirs "$OC_DATA"
}
# ── 查找 openclaw 入口 ──
OC_PKG_DIR=""
for d in "${OC_GLOBAL}/lib/node_modules/openclaw" "${OC_GLOBAL}/node_modules/openclaw" "${NODE_BASE}/lib/node_modules/openclaw"; do
@@ -84,7 +92,7 @@ oc_cmd() {
"$NODE_BIN" "$OC_ENTRY" "$@" 2>&1
local rc=$?
# 修复权限: oc_cmd 以 root 运行但配置文件应属于 openclaw 用户
find "$OC_STATE_DIR" -user root ! -path "*/extensions*" -exec chown openclaw:openclaw {} \; 2>/dev/null || true
fix_openclaw_state_permissions 2>/dev/null || true
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
chown openclaw:openclaw "${CONFIG_FILE}.bak" 2>/dev/null || true
return $rc
@@ -130,7 +138,7 @@ json_set() {
fi
# 尝试修复所有权
chown -R openclaw:openclaw "$OC_STATE_DIR" 2>/dev/null || true
fix_openclaw_state_permissions 2>/dev/null || true
# 创建空配置文件
if ! echo '{}' > "$CONFIG_FILE" 2>/dev/null; then
@@ -548,7 +556,7 @@ restart_gateway() {
echo -e " ${YELLOW}正在重启 Gateway...${NC}"
# 修复数据目录权限 (root 用户操作可能改变了文件属主)
chown -R openclaw:openclaw "$OC_DATA" 2>/dev/null || true
fix_openclaw_state_permissions 2>/dev/null || true
local port
port=$(json_get gateway.port)
@@ -694,6 +702,8 @@ configure_model() {
echo -e " ${BOLD}🏠 ── 本地模型 / 自定义 API ──${NC}"
echo -e " ${CYAN}l)${NC} Ollama (本地模型,无需 API Key)"
echo -e " ${CYAN}m)${NC} 自定义 OpenAI 兼容 API"
echo -e " ${CYAN}n)${NC} 自定义 Anthropic 兼容 API"
echo -e " ${CYAN}o)${NC} 一万AI分享 粉丝专享 API"
echo ""
echo -e " ${CYAN}q)${NC} 返回"
echo ""
@@ -1380,6 +1390,70 @@ configure_model() {
echo -e " ${GREEN}✅ 自定义模型已配置,活跃模型: openai-compatible/${model_name}${NC}"
fi
;;
n)
echo ""
echo -e " ${BOLD}自定义 Anthropic 兼容 API${NC}"
echo -e " ${YELLOW}支持任何兼容 Anthropic Messages API 格式的服务商${NC}"
echo ""
prompt_with_default "API Base URL (如 https://api.anthropic.com)" "" base_url
prompt_with_default "API Key" "" api_key
prompt_with_default "模型名称" "claude-sonnet-4-20250514" model_name
if [ -n "$base_url" ] && [ -n "$api_key" ] && [ -n "$model_name" ]; then
_ACP_URL="${base_url%/}" _ACP_KEY="$api_key" _ACP_MID="$model_name" "$NODE_BIN" -e "
const fs=require('fs');
let d={};
try{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}
if(!d.models)d.models={};
if(!d.models.providers)d.models.providers={};
d.models.mode='merge';
d.models.providers['anthropic-compatible']={
baseUrl:process.env._ACP_URL,
apiKey:process.env._ACP_KEY,
api:'anthropic-messages',
models:[{
id:process.env._ACP_MID,
name:process.env._ACP_MID,
reasoning:false,
input:['text','image'],
cost:{input:0,output:0,cacheRead:0,cacheWrite:0},
contextWindow:200000,
maxTokens:16000
}]
};
fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
" 2>/dev/null
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
auth_set_apikey anthropic-compatible "$api_key" "anthropic-compatible:manual"
register_and_set_model "anthropic-compatible/${model_name}"
echo -e " ${GREEN}✅ 自定义 Anthropic API 已配置,活跃模型: anthropic-compatible/${model_name}${NC}"
fi
;;
o)
echo ""
echo -e " ${BOLD}一万AI分享 粉丝专享 API${NC}"
echo -e " ${YELLOW}OpenAI 兼容模式;Base URL 和模型已内置,只需要填写 API Key。${NC}"
echo -e " ${DIM}Base URL: https://api.910501.xyz/v1${NC}"
echo -e " ${DIM}Model: gpt-5.5${NC}"
echo ""
prompt_with_default "API Key" "" api_key
if [ -n "$api_key" ]; then
auth_set_apikey yiwanai "$api_key" "yiwanai:fan"
register_custom_provider yiwanai "https://api.910501.xyz/v1" "$api_key" "gpt-5.5" "gpt-5.5"
_YW_PROV="yiwanai" "$NODE_BIN" -e "
const fs=require('fs');
let d={};
try{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}
const p=d.models&&d.models.providers&&d.models.providers[process.env._YW_PROV];
if(p&&p.models&&p.models[0]){
p.models[0].reasoning=true;
fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
}
" 2>/dev/null
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
register_and_set_model "yiwanai/gpt-5.5"
echo -e " ${GREEN}✅ 一万AI分享粉丝专享 API 已配置,活跃模型: yiwanai/gpt-5.5${NC}"
fi
;;
q) return ;;
esac
@@ -2470,7 +2544,7 @@ backup_restore_menu() {
# 提取 payload 到根目录 (还原到原始绝对路径)
tar -xzf "$latest" --strip-components=3 -C / "${backup_name}/payload/posix/" 2>&1
# 修复权限
chown -R openclaw:openclaw "$OC_STATE_DIR" 2>/dev/null
fix_openclaw_state_permissions 2>/dev/null || true
echo -e " ${GREEN}✅ 配置和数据已完整恢复!原配置已保存为 openclaw.json.pre-restore${NC}"
echo ""
prompt_with_default "是否重启服务使配置生效? (Y/n)" "Y" do_restart
@@ -2514,7 +2588,7 @@ launch_interactive_menu() {
"$NODE_BIN" "$OC_INTERACTIVE" 2>&1
local rc=$?
find "$OC_STATE_DIR" -user root ! -path "*/extensions*" -exec chown openclaw:openclaw {} \; 2>/dev/null || true
fix_openclaw_state_permissions 2>/dev/null || true
# 返回后刷新配置权限
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
return $rc
@@ -2532,7 +2606,7 @@ launch_interactive_model_config() {
"$NODE_BIN" "$OC_INTERACTIVE" model 2>&1
local rc=$?
find "$OC_STATE_DIR" -user root ! -path "*/extensions*" -exec chown openclaw:openclaw {} \; 2>/dev/null || true
fix_openclaw_state_permissions 2>/dev/null || true
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
return $rc
}
@@ -9,7 +9,7 @@
const http = require('http');
const https = require('https');
const crypto = require('crypto');
const { spawn } = require('child_process');
const { spawn, execFileSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
@@ -36,12 +36,21 @@ try {
const NODE_BASE = process.env.NODE_BASE || installPath + '/node';
const OC_GLOBAL = process.env.OC_GLOBAL || installPath + '/global';
const OC_DATA = process.env.OC_DATA || installPath + '/data';
const OC_STATE_DIR = `${OC_DATA}/.openclaw`;
const PERMISSIONS_HELPER = '/usr/libexec/openclaw-permissions.sh';
const SCRIPT_PATH = process.env.OC_CONFIG_SCRIPT || '/usr/share/openclaw/oc-config.sh';
const SSL_CERT = '/etc/uhttpd.crt';
const SSL_KEY = '/etc/uhttpd.key';
const MAX_SESSIONS = parseInt(process.env.OC_MAX_SESSIONS || '5', 10);
// ── 认证令牌 (从 UCI 或环境变量读取) ──
function fixStatePermissions() {
try {
execFileSync(PERMISSIONS_HELPER, ['fix-state', OC_STATE_DIR], { stdio: 'pipe', timeout: 10000 });
} catch {}
}
function loadAuthToken() {
try {
const { execSync } = require('child_process');
@@ -220,7 +229,7 @@ class PtySession {
if (!this.alive) return;
// PTY 以 root 运行,子脚本可能创建了 root-owned 的目录
// 修复权限,防止以 openclaw 用户运行的 Gateway 遇到 EACCES
try { require('child_process').execFileSync('chown', ['-R', 'openclaw:openclaw', OC_DATA], { stdio: 'pipe', timeout: 5000 }); } catch(e) {}
try { fixStatePermissions(); } catch(e) {}
this._spawnFailCount++;
if (this._spawnFailCount > this._MAX_SPAWN_RETRIES) {
console.log(`[oc-config] Script failed ${this._spawnFailCount} times, stopping retries`);
+3 -1
View File
@@ -116,7 +116,9 @@ case "$ARCH" in
esac
# 检查依赖
for dep in luci-compat luci-base; do
# .run 不经过 opkg 依赖解析,需要主动补齐运行时依赖。
# 特别是精简固件常见 /bin/tar 为 BusyBox 版本,无法解压 Node.js 的 .tar.xz。
for dep in luci-compat luci-base curl openssl-util script-utils tar libstdcpp6; do
if ! opkg list-installed 2>/dev/null | grep -q "^${dep} "; then
echo "警告: 缺少依赖 $dep,尝试安装..."
opkg update >/dev/null 2>&1 || true
@@ -17,6 +17,7 @@ fi
grep -q "wechat.htm" Makefile || fail "Makefile must install wechat.htm"
grep -q "luci-app-openclaw.json" Makefile || fail "Makefile must install rpcd ACL"
grep -q "openclaw-permissions.sh" Makefile || fail "Makefile must install permission helper"
if grep -q "openclaw.zh-cn.lmo" Makefile; then
fail "main package must not install openclaw.zh-cn.lmo"
fi
@@ -42,7 +43,7 @@ grep -q "dropChannel(d.plugins.installs)" luasrc/controller/openclaw.lua || fail
grep -q "openclaw-weixin" root/etc/init.d/openclaw || fail "weixin channel migration missing"
grep -q "delete d.plugins.entries\\['openclaw-weixin'\\]" root/etc/init.d/openclaw || fail "weixin duplicate entries cleanup missing"
grep -q "npm/projects" root/etc/init.d/openclaw || fail "npm plugin project ownership fix missing"
grep -q "! -path.*npm/projects" root/etc/init.d/openclaw || fail "npm plugin projects should be excluded from openclaw ownership reset"
grep -q "! -path.*npm/projects" root/usr/libexec/openclaw-permissions.sh || fail "npm plugin projects should be excluded from generic ownership reset"
grep -q "archived-extensions" root/etc/init.d/openclaw || fail "legacy weixin extension archive missing"
grep -q "find_wechat_plugin_dir" luasrc/controller/openclaw.lua || fail "wechat npm plugin detection missing"
grep -q "wechat_register_plugin_cmd" luasrc/controller/openclaw.lua || fail "wechat install must register npm plugin config"
@@ -50,7 +51,70 @@ grep -q "plugins.installs\\['openclaw-weixin'\\]" luasrc/controller/openclaw.lua
grep -q "packageName: '@tencent-weixin/openclaw-weixin'" luasrc/controller/openclaw.lua || fail "wechat plugin packageName registration missing"
grep -q "channels\\['openclaw-weixin'\\].enabled = true" luasrc/controller/openclaw.lua || fail "wechat channel enable registration missing"
grep -q "Registered openclaw-weixin npm plugin" luasrc/controller/openclaw.lua || fail "wechat install log must confirm config registration"
grep -q "wechat_network_probe_cmd" luasrc/controller/openclaw.lua || fail "wechat network probe helper missing"
grep -q "openclaw_user_runner_cmd" luasrc/controller/openclaw.lua || fail "wechat commands need user runner compatibility helper"
grep -q "start-stop-daemon -S -m -p" luasrc/controller/openclaw.lua || fail "wechat commands must support OpenWrt without su"
grep -q "command -v curl" luasrc/controller/openclaw.lua || fail "wechat network probe should prefer curl"
grep -q "%%{http_code}" luasrc/controller/openclaw.lua || fail "wechat curl probe percent must be escaped for string.format"
grep -q "wechat_npm_fallback_install_cmd" luasrc/controller/openclaw.lua || fail "wechat install needs npm fallback for OpenWrt Node undici OOM"
grep -q "npm install --omit=dev --omit=peer" luasrc/controller/openclaw.lua || fail "wechat npm fallback install command missing"
grep -q "tencent-weixin-openclaw-weixin-7783ac86ba" luasrc/controller/openclaw.lua || fail "wechat npm fallback project dir missing"
grep -q "export OC_WECHAT_DATA" luasrc/controller/openclaw.lua || fail "wechat npm fallback must export data dir into openclaw user shell"
grep -q "compare_versions" luasrc/controller/openclaw.lua || fail "semantic version compare helper missing"
grep -q "is_newer_version(plugin_latest, plugin_current)" luasrc/controller/openclaw.lua || fail "plugin update check must not treat newer local versions as upgradeable"
grep -q "is_newer_version(latest_version, current_version)" luasrc/controller/openclaw.lua || fail "wechat update check must use semantic version compare"
grep -q "ilinkai.weixin.qq.com" luasrc/controller/openclaw.lua || fail "wechat network probe endpoint missing"
grep -q "微信接口连通性检查" luasrc/controller/openclaw.lua || fail "wechat network probe log missing"
grep -q "error_detail" luasrc/controller/openclaw.lua || fail "wechat login failure detail response missing"
grep -q "self_heal_wechat_npm_plugin_config" root/etc/init.d/openclaw || fail "init.d wechat npm plugin self-heal missing"
grep -q "已自愈注册 openclaw-weixin npm plugin" root/etc/init.d/openclaw || fail "init.d wechat self-heal log missing"
grep -q "OC_WECHAT_PLUGIN_DIR" root/etc/init.d/openclaw || fail "init.d wechat self-heal plugin dir env missing"
grep -q "openclaw-permissions.sh" root/etc/init.d/openclaw || fail "init.d must use permission helper"
grep -q "fix_openclaw_state_permissions" root/etc/init.d/openclaw || fail "init.d final permission helper call missing"
grep -q 'NODE_ICU_DATA="${NODE_BASE}/share/icu" PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:$PATH"' root/etc/init.d/openclaw || fail "doctor migration must run with Node ICU data"
grep -q "Token 同步 (doctor 后): UCI -> JSON" root/etc/init.d/openclaw || fail "doctor must restore missing JSON token from UCI"
grep -q 'OPENCLAW_GATEWAY_TOKEN="$gw_token"' root/etc/init.d/openclaw || fail "gateway must receive token env fallback"
grep -q "oc_fix_npm_projects_permissions" root/usr/libexec/openclaw-permissions.sh || fail "npm projects permission helper missing"
grep -q "openclaw.plugin.json" root/usr/libexec/openclaw-permissions.sh || fail "npm plugin root detection must use plugin manifest"
grep -q "node_modules/@tencent-weixin/openclaw-weixin" root/usr/libexec/openclaw-permissions.sh || fail "wechat npm plugin compatibility permission fix missing"
grep -q "__openclaw-generation__" root/usr/libexec/openclaw-permissions.sh || fail "retained npm generations must remain removable by openclaw"
grep -q 'chown -R root:root "$plugin_dir"' root/usr/libexec/openclaw-permissions.sh || fail "npm plugin roots must keep root ownership"
grep -q 'chown -R openclaw:openclaw "$npm_projects"' root/usr/libexec/openclaw-permissions.sh || fail "npm projects generation must be writable by openclaw"
if grep -q 'chown -R openclaw:openclaw "$OC_DATA"' root/etc/init.d/openclaw root/usr/share/openclaw/oc-config.sh root/usr/share/openclaw/web-pty.js; then
fail "must not recursively chown the whole OC_DATA to openclaw"
fi
if grep -q 'chown -R openclaw:openclaw "$OC_STATE_DIR"' root/usr/share/openclaw/oc-config.sh; then
fail "must not recursively chown the whole OC_STATE_DIR to openclaw"
fi
if grep -q 'find "$OC_STATE_DIR" -user root ! -path "*/extensions*"' root/usr/share/openclaw/oc-config.sh root/usr/share/openclaw/oc-config-interactive.js; then
fail "state permission reset must also exclude npm/projects and archived-extensions"
fi
grep -q "extractWechatLoginUrl" luasrc/view/openclaw/wechat.htm || fail "wechat page must filter login URLs"
grep -q "oc-error-detail" luasrc/view/openclaw/wechat.htm || fail "wechat page must show real login failure detail"
grep -q "点击打开链接,然后用微信扫码" luasrc/view/openclaw/wechat.htm || fail "wechat page must tell users to open link and scan with WeChat"
grep -q "@tencent-weixin/openclaw-weixin/openclaw.plugin.json" root/usr/share/openclaw/oc-config.sh || fail "oc-config wechat npm detection missing"
grep -q "fix_openclaw_state_permissions" root/usr/share/openclaw/oc-config.sh || fail "oc-config must use permission helper"
grep -q "fixStatePermissions" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive config must use permission helper"
grep -q "fixStatePermissions" root/usr/share/openclaw/web-pty.js || fail "web pty must use permission helper"
grep -q "一万AI分享 粉丝专享 API" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive menu missing Yiwan AI fan API"
grep -q "configureYiwanAIFanAPI" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive Yiwan AI configure handler missing"
grep -q "yiwanai-fan" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive Yiwan AI menu value missing"
grep -q "一万AI分享 粉丝专享 API" root/usr/share/openclaw/oc-config.sh || fail "shell menu missing Yiwan AI fan API"
grep -q "https://api.910501.xyz/v1" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive Yiwan AI base URL missing"
grep -q "https://api.910501.xyz/v1" root/usr/share/openclaw/oc-config.sh || fail "shell Yiwan AI base URL missing"
grep -q "gpt-5.5" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive Yiwan AI model missing"
grep -q "gpt-5.5" root/usr/share/openclaw/oc-config.sh || fail "shell Yiwan AI model missing"
grep -q "authSetApikey(providerName, apiKey,.*fan" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive Yiwan AI auth profile missing"
grep -q 'auth_set_apikey yiwanai "$api_key" "yiwanai:fan"' root/usr/share/openclaw/oc-config.sh || fail "shell Yiwan AI auth profile missing"
grep -q "registerCustomProvider(providerName, baseUrl, apiKey, modelName, modelName)" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive Yiwan AI custom provider registration missing"
grep -q 'register_custom_provider yiwanai "https://api.910501.xyz/v1" "$api_key" "gpt-5.5" "gpt-5.5"' root/usr/share/openclaw/oc-config.sh || fail "shell Yiwan AI custom provider registration missing"
grep -q 'registerAndSetModel(`${providerName}/${modelName}`)' root/usr/share/openclaw/oc-config-interactive.js || fail "interactive Yiwan AI active model missing"
grep -q "provider.models\[0\].reasoning = true" root/usr/share/openclaw/oc-config-interactive.js || fail "interactive Yiwan AI reasoning flag missing"
grep -q "p.models\[0\].reasoning=true" root/usr/share/openclaw/oc-config.sh || fail "shell Yiwan AI reasoning flag missing"
grep -q "yiwanai/gpt-5.5" root/usr/share/openclaw/oc-config.sh || fail "shell Yiwan AI active model missing"
grep -q "anthropic-compatible" root/usr/share/openclaw/oc-config.sh || fail "shell custom Anthropic provider missing"
grep -q "anthropic-messages" root/usr/share/openclaw/oc-config.sh || fail "shell custom Anthropic provider mode missing"
grep -q "var url = 'http://'" luasrc/view/openclaw/console.htm || fail "console must force HTTP gateway URL"
grep -q "新窗口打开" luasrc/view/openclaw/console.htm || fail "console must expose a new-window entry"
@@ -60,5 +124,8 @@ fi
grep -q "root/usr/libexec" scripts/build_ipk.sh || fail "ipk script must package shell helpers"
grep -q "root/usr/libexec" scripts/build_run.sh || fail "run script must package shell helpers"
grep -q "for dep in luci-compat luci-base curl openssl-util script-utils tar libstdcpp6" scripts/build_run.sh || fail ".run installer must install runtime dependencies"
grep -q "先解压到临时目录并确认完整,再替换 NODE_BASE" root/usr/bin/openclaw-env || fail "Node install must not delete existing runtime before extraction succeeds"
grep -q "OC_SETUP_FRESH_ROOT" root/usr/bin/openclaw-env || fail "setup cleanup must preserve existing runtime roots"
echo "ok"
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.7.1
PKG_RELEASE:=174
PKG_RELEASE:=175
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -652,12 +652,12 @@ o = s:option(Flag, _n("http_h2_health_check"), translate("Health check"))
o:depends({ [_n("tls")] = true, [_n("transport")] = "http" })
o = s:option(Value, _n("http_h2_read_idle_timeout"), translate("Idle timeout"))
o.default = "10"
o:depends({ [_n("tls")] = true, [_n("transport")] = "http", [_n("http_h2_health_check")] = true })
o.default = "15"
o:depends({ [_n("http_h2_health_check")] = true })
o = s:option(Value, _n("http_h2_health_check_timeout"), translate("Health check timeout"))
o.default = "15"
o:depends({ [_n("tls")] = true, [_n("transport")] = "http", [_n("http_h2_health_check")] = true })
o:depends({ [_n("http_h2_health_check")] = true })
-- [[ WebSocket部分 ]]--
o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
@@ -693,11 +693,11 @@ o = s:option(Flag, _n("grpc_health_check"), translate("Health check"))
o:depends({ [_n("transport")] = "grpc" })
o = s:option(Value, _n("grpc_idle_timeout"), translate("Idle timeout"))
o.default = "10"
o.default = "15"
o:depends({ [_n("grpc_health_check")] = true })
o = s:option(Value, _n("grpc_health_check_timeout"), translate("Health check timeout"))
o.default = "20"
o.default = "15"
o:depends({ [_n("grpc_health_check")] = true })
o = s:option(Flag, _n("grpc_permit_without_stream"), translate("Permit without stream"))
+2 -2
View File
@@ -1447,7 +1447,7 @@ function get_std_domain(domain)
return domain
end
function format_go_time(input)
function format_go_time(input, default)
input = input and trim(input)
local N = 0
if input and input:match("^%d+$") then
@@ -1465,7 +1465,7 @@ function format_go_time(input)
end
end
if N <= 0 then
return "0s"
return default or "0s"
end
local result = ""
local h = math.floor(N / 3600)
@@ -304,8 +304,6 @@ function gen_outbound(flag, node, tag, proxy_table)
headers = node.user_agent and {
["User-Agent"] = node.user_agent
} or nil,
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
}
--不强制执行 TLS。如果未配置 TLS,将使用纯 HTTP 1.1。
end
@@ -318,8 +316,8 @@ function gen_outbound(flag, node, tag, proxy_table)
headers = node.user_agent and {
["User-Agent"] = node.user_agent
} or nil,
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
idle_timeout = (node.http_h2_health_check == "1") and api.format_go_time(node.http_h2_read_idle_timeout, "15s") or nil,
ping_timeout = (node.http_h2_health_check == "1") and api.format_go_time(node.http_h2_health_check_timeout, "15s") or nil,
}
--不强制执行 TLS。如果未配置 TLS,将使用纯 HTTP 1.1。
end
@@ -359,8 +357,8 @@ function gen_outbound(flag, node, tag, proxy_table)
v2ray_transport = {
type = "grpc",
service_name = node.grpc_serviceName,
idle_timeout = tonumber(node.grpc_idle_timeout) or nil,
ping_timeout = tonumber(node.grpc_health_check_timeout) or nil,
idle_timeout = (node.grpc_health_check == "1") and api.format_go_time(node.grpc_idle_timeout, "15s") or nil,
ping_timeout = (node.grpc_health_check == "1") and api.format_go_time(node.grpc_health_check_timeout, "15s") or nil,
permit_without_stream = (node.grpc_permit_without_stream == "1") and true or nil,
}
end
@@ -583,12 +581,12 @@ function gen_outbound(flag, node, tag, proxy_table)
idle_timeout = (function(t)
if not version_ge_1_14_0 then return nil end
t = tonumber(tostring(t or "30"):match("^%d+"))
return (t and t >= 4 and t <= 120) and t or 30
return (t and t >= 4 and t <= 120) and t .. "s" or "30s"
end)(node.hysteria2_idle_timeout),
keep_alive_period = (function(t)
if not version_ge_1_14_0 then return nil end
t = tonumber(tostring(t or "0"):match("^%d+"))
return (t and t >= 2 and t <= 60) and t or nil
return (t and t >= 2 and t <= 60) and t .. "s" or nil
end)(node.hysteria2_keep_alive_period),
disable_path_mtu_discovery = version_ge_1_14_0 and (tonumber(node.hysteria2_disable_mtu_discovery) == 1) or nil,
tls = tls,
+1 -1
View File
@@ -9,7 +9,7 @@ LUCI_TITLE:=LuCI support for quickstart
LUCI_DEPENDS:=+quickstart +luci-app-ttyd +luci-i18n-ttyd-zh-cn
LUCI_PKGARCH:=all
PKG_VERSION:=0.12.7-r1
PKG_VERSION:=0.12.8-r1
# PKG_RELEASE MUST be empty for luci.mk
PKG_RELEASE:=
@@ -1,3 +1,3 @@
var go=Object.defineProperty,vo=Object.defineProperties;var bo=Object.getOwnPropertyDescriptors;var xa=Object.getOwnPropertySymbols;var ho=Object.prototype.hasOwnProperty,_o=Object.prototype.propertyIsEnumerable;var We=(o,n,a)=>n in o?go(o,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):o[n]=a,st=(o,n)=>{for(var a in n||(n={}))ho.call(n,a)&&We(o,a,n[a]);if(xa)for(var a of xa(n))_o.call(n,a)&&We(o,a,n[a]);return o},vt=(o,n)=>vo(o,bo(n));var xo=(o,n)=>()=>(n||o((n={exports:{}}).exports,n),n.exports);var ve=(o,n,a)=>(We(o,typeof n!="symbol"?n+"":n,a),a);var L=(o,n,a)=>new Promise((l,u)=>{var d=g=>{try{k(a.next(g))}catch(p){u(p)}},c=g=>{try{k(a.throw(g))}catch(p){u(p)}},k=g=>g.done?l(g.value):Promise.resolve(g.value).then(d,c);k((a=a.apply(o,n)).next())});import{a as wo,c as ko,d as we,b as I,e as Q,u as e,o as r,f as s,g as t,t as i,n as ht,h as D,i as Y,r as _t,j as lt,k as Yt,w as O,v as Rt,l as Ia,m as Z,p as V,q as E,s as $t,x as Pt,y as pt,F as U,z as tt,A as at,B as jt,C as ut,D as ct,E as ke,G as ee,T as St,H as Nt,I as ft,J as xt,K as dt,L as gt,M as Bt,N as Xe,O as Ma,P as La,Q as Oa,R as ta,S as ea,U as Na,V as aa,W as Pe,X as yo,Y as oa,Z as ye,_ as Fo,$ as Eo,a0 as na,a1 as $o,a2 as Te,a3 as Co,a4 as Ie,a5 as Do,a6 as Bo,a7 as Yo,a8 as Ao,a9 as So,aa as zo,ab as Po,ac as To}from"./vendor.js?v=4c8355cf";var sT=xo(ae=>{const Io=function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))l(u);new MutationObserver(u=>{for(const d of u)if(d.type==="childList")for(const c of d.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&l(c)}).observe(document,{childList:!0,subtree:!0});function a(u){const d={};return u.integrity&&(d.integrity=u.integrity),u.referrerpolicy&&(d.referrerPolicy=u.referrerpolicy),u.crossorigin==="use-credentials"?d.credentials="include":u.crossorigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function l(u){if(u.ep)return;u.ep=!0;const d=a(u);fetch(u.href,d)}};Io();const Gt={language:void 0,numberFormat:new Intl.NumberFormat("en",{notation:"compact"})},Mo=o=>L(ae,null,function*(){const n=window.vue_lang;new Date().getTime();let a={};try{const d=yield wo({url:window.vue_lang_data,method:"GET"});d.data&&(a=d.data)}catch(d){console.log(d)}const l=ko({defaultLanguage:n,mutedLanguages:["zh-cn"],translations:a,setGlobalProperties:!1,provideDirective:!1,provideComponent:!1});o.use(l),Gt.language=l;const{$gettext:u}=l;window.$i18n=u;try{Gt.numberFormat=new Intl.NumberFormat(n,{notation:"compact"})}catch(d){console.error("Intl.NumberFormat unsupported lang",n,d)}}),J=()=>{if(Gt.language)return Gt.language;throw new Error("I18N Uninitialized!")},Me=()=>({$gettext:(o,n,a)=>{if(Gt.language)return Gt.language.$gettext(o,n,a);throw new Error("I18N Uninitialized!")},$ngettext:(o,n,a,l,u)=>{if(Gt.language)return Gt.language.$ngettext(o,n,a,l,u);throw new Error("I18N Uninitialized!")}}),De=o=>typeof o=="number"?Gt.numberFormat.format(o):"?",{$gettext:Lo,$ngettext:uT}=Me(),Va=(o,n)=>L(ae,null,function*(){return new Promise((a,l)=>L(ae,null,function*(){try{const u=yield fetch(o,n);if(Math.floor(u.status/100)!=2)throw u.status+" "+u.statusText;const d=st({},u);d.data=yield u.json(),a(d)}catch(u){const d=u;l(Lo("\u7F51\u7EDC\u5F02\u5E38\uFF1A")+((d==null?void 0:d.message)||u))}}))});class ia{constructor(n){ve(this,"config",{baseURL:"",headers:{}});ve(this,"useRequest",n=>n);ve(this,"useResponse",n=>n);ve(this,"useError",n=>n);n.baseURL&&(this.config.baseURL=n.baseURL),n.headers&&(this.config.headers=n.headers)}static create(n){return new ia(n)}Do(n,a){return L(this,null,function*(){return new Promise((l,u)=>L(this,null,function*(){try{const d=this.useRequest({baseURL:this.config.baseURL,headers:this.config.headers});n=`${d.baseURL||""}${n}`,a.headers==null&&(a.headers={}),d.headers&&(a.headers=st({},d.headers));const k=yield fetch(n,a),g=st({},k);g.data=yield k.json(),l(this.useResponse(g))}catch(d){this.useError(d),u(d)}}))})}TEXT(n,a){return L(this,null,function*(){return new Promise((l,u)=>L(this,null,function*(){try{const d=this.useRequest({baseURL:this.config.baseURL,headers:this.config.headers});n=`${d.baseURL||""}${n}`,a.headers==null&&(a.headers={}),d.headers&&(a.headers=st({},d.headers));const k=yield fetch(n,a),g=st({},k);g.data=yield k.text(),l(g)}catch(d){this.useError(d),u(d)}}))})}interceptors(){const n=this;return{requset:{use(a){n.useRequest=a}},response:{use(a,l){n.useResponse=a,l&&(n.useError=l)}}}}}const Ga=ia.create({});Ga.interceptors().requset.use(o=>o);Ga.interceptors().response.use(o=>(o.data&&o.data.success==null&&o.data.success==0,o));const{$gettext:ja,$ngettext:cT}=Me(),Oo="/cgi-bin/luci/istore",No="/cgi-bin/luci/admin";let Ae=!1;const W=(o,n)=>(o.indexOf("//")==-1&&(o=`${Oo}${o}`),Va(o,n).then(a=>(a!=null&&a.data&&a.data.success==-1001&&a.data.error=="Forbidden"&&(Ae||(Ae=!0,alert(ja("\u767B\u5F55\u8FC7\u671F\uFF0C\u8BF7\u91CLine truncated
var go=Object.defineProperty,vo=Object.defineProperties;var bo=Object.getOwnPropertyDescriptors;var xa=Object.getOwnPropertySymbols;var ho=Object.prototype.hasOwnProperty,_o=Object.prototype.propertyIsEnumerable;var We=(o,n,a)=>n in o?go(o,n,{enumerable:!0,configurable:!0,writable:!0,value:a}):o[n]=a,st=(o,n)=>{for(var a in n||(n={}))ho.call(n,a)&&We(o,a,n[a]);if(xa)for(var a of xa(n))_o.call(n,a)&&We(o,a,n[a]);return o},vt=(o,n)=>vo(o,bo(n));var xo=(o,n)=>()=>(n||o((n={exports:{}}).exports,n),n.exports);var ve=(o,n,a)=>(We(o,typeof n!="symbol"?n+"":n,a),a);var L=(o,n,a)=>new Promise((l,u)=>{var d=g=>{try{k(a.next(g))}catch(p){u(p)}},c=g=>{try{k(a.throw(g))}catch(p){u(p)}},k=g=>g.done?l(g.value):Promise.resolve(g.value).then(d,c);k((a=a.apply(o,n)).next())});import{a as wo,c as ko,d as we,b as I,e as Q,u as e,o as r,f as s,g as t,t as i,n as ht,h as D,i as Y,r as _t,j as Yt,w as O,v as Rt,k as Ia,l as Z,m as V,p as E,q as $t,s as Pt,x as pt,F as U,y as tt,z as at,A as jt,B as ut,C as ct,D as ke,E as ee,T as St,G as Nt,H as lt,I as ft,J as xt,K as dt,L as gt,M as Bt,N as Xe,O as Ma,P as La,Q as Oa,R as ta,S as ea,U as Na,V as aa,W as Pe,X as yo,Y as oa,Z as ye,_ as Fo,$ as Eo,a0 as na,a1 as $o,a2 as Te,a3 as Co,a4 as Ie,a5 as Do,a6 as Bo,a7 as Yo,a8 as Ao,a9 as So,aa as zo,ab as Po,ac as To}from"./vendor.js?v=91daea62";var sT=xo(ae=>{const Io=function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))l(u);new MutationObserver(u=>{for(const d of u)if(d.type==="childList")for(const c of d.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&l(c)}).observe(document,{childList:!0,subtree:!0});function a(u){const d={};return u.integrity&&(d.integrity=u.integrity),u.referrerpolicy&&(d.referrerPolicy=u.referrerpolicy),u.crossorigin==="use-credentials"?d.credentials="include":u.crossorigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function l(u){if(u.ep)return;u.ep=!0;const d=a(u);fetch(u.href,d)}};Io();const Gt={language:void 0,numberFormat:new Intl.NumberFormat("en",{notation:"compact"})},Mo=o=>L(ae,null,function*(){const n=window.vue_lang;new Date().getTime();let a={};try{const d=yield wo({url:window.vue_lang_data,method:"GET"});d.data&&(a=d.data)}catch(d){console.log(d)}const l=ko({defaultLanguage:n,mutedLanguages:["zh-cn"],translations:a,setGlobalProperties:!1,provideDirective:!1,provideComponent:!1});o.use(l),Gt.language=l;const{$gettext:u}=l;window.$i18n=u;try{Gt.numberFormat=new Intl.NumberFormat(n,{notation:"compact"})}catch(d){console.error("Intl.NumberFormat unsupported lang",n,d)}}),J=()=>{if(Gt.language)return Gt.language;throw new Error("I18N Uninitialized!")},Me=()=>({$gettext:(o,n,a)=>{if(Gt.language)return Gt.language.$gettext(o,n,a);throw new Error("I18N Uninitialized!")},$ngettext:(o,n,a,l,u)=>{if(Gt.language)return Gt.language.$ngettext(o,n,a,l,u);throw new Error("I18N Uninitialized!")}}),De=o=>typeof o=="number"?Gt.numberFormat.format(o):"?",{$gettext:Lo,$ngettext:uT}=Me(),Va=(o,n)=>L(ae,null,function*(){return new Promise((a,l)=>L(ae,null,function*(){try{const u=yield fetch(o,n);if(Math.floor(u.status/100)!=2)throw u.status+" "+u.statusText;const d=st({},u);d.data=yield u.json(),a(d)}catch(u){const d=u;l(Lo("\u7F51\u7EDC\u5F02\u5E38\uFF1A")+((d==null?void 0:d.message)||u))}}))});class ia{constructor(n){ve(this,"config",{baseURL:"",headers:{}});ve(this,"useRequest",n=>n);ve(this,"useResponse",n=>n);ve(this,"useError",n=>n);n.baseURL&&(this.config.baseURL=n.baseURL),n.headers&&(this.config.headers=n.headers)}static create(n){return new ia(n)}Do(n,a){return L(this,null,function*(){return new Promise((l,u)=>L(this,null,function*(){try{const d=this.useRequest({baseURL:this.config.baseURL,headers:this.config.headers});n=`${d.baseURL||""}${n}`,a.headers==null&&(a.headers={}),d.headers&&(a.headers=st({},d.headers));const k=yield fetch(n,a),g=st({},k);g.data=yield k.json(),l(this.useResponse(g))}catch(d){this.useError(d),u(d)}}))})}TEXT(n,a){return L(this,null,function*(){return new Promise((l,u)=>L(this,null,function*(){try{const d=this.useRequest({baseURL:this.config.baseURL,headers:this.config.headers});n=`${d.baseURL||""}${n}`,a.headers==null&&(a.headers={}),d.headers&&(a.headers=st({},d.headers));const k=yield fetch(n,a),g=st({},k);g.data=yield k.text(),l(g)}catch(d){this.useError(d),u(d)}}))})}interceptors(){const n=this;return{requset:{use(a){n.useRequest=a}},response:{use(a,l){n.useResponse=a,l&&(n.useError=l)}}}}}const Ga=ia.create({});Ga.interceptors().requset.use(o=>o);Ga.interceptors().response.use(o=>(o.data&&o.data.success==null&&o.data.success==0,o));const{$gettext:ja,$ngettext:cT}=Me(),Oo="/cgi-bin/luci/istore",No="/cgi-bin/luci/admin";let Ae=!1;const W=(o,n)=>(o.indexOf("//")==-1&&(o=`${Oo}${o}`),Va(o,n).then(a=>(a!=null&&a.data&&a.data.success==-1001&&a.data.error=="Forbidden"&&(Ae||(Ae=!0,alert(ja("\u767B\u5F55\u8FC7\u671F\uFF0C\u8BF7\u91CLine truncated
`+k.value+`
`+d.value,disabled:""},null,8,RF)]),t("div",WF,[t("div",{class:"close",onClick:m,disabled:u.value},i(e(a)("\u5173\u95ED")),9,HF),u.value?D("",!0):(r(),s("div",{key:0,class:"next",onClick:v,disabled:u.value},i(e(a)("\u8FD0\u884C")),9,JF))])])]),_:1}))}});var KF=N(ZF,[["__scopeId","data-v-70c3aae0"]]);const QF={class:"actioner-container"},XF={class:"actioner-container_header"},tE={class:"tabs"},eE={class:"actioner-container_body"},aE={key:0,class:"table"},oE={class:"tr"},nE={class:"td left"},iE={class:"td left"},rE={class:"tr"},sE={class:"td left"},dE={class:"td left"},lE={class:"tr"},uE={class:"td left"},cE={class:"td left"},pE=["value"],fE=["value"],mE=["value"],gE={class:"actioner-container_footer"},vE=["disabled"],bE=I({props:{close:{type:Function,required:!0},disk:{type:Object,required:!0}},setup(o){const n=o,{$gettext:a,$ngettext:l}=J(),u=E(!1),d=E("info"),c=b=>{switch(d.value=b,b){case"info":break;case"attribute":m();break;case"log":p();break;case"extend":v();break}},k=()=>{u.value=!0,n.close()},g=gt({log:"",attribute:"",extend:""}),p=()=>L(this,null,function*(){try{const b=yield G.Smart.Test.Result.POST({type:"selftest",devicePath:n.disk.path||""});if(b.data){const{result:y,error:f}=b.data;y&&y.result&&(g.log=y.result),f&&(g.log=f)}}catch(b){g.log=b}}),m=()=>L(this,null,function*(){try{const b=yield G.Smart.Attribute.Result.POST({devicePath:n.disk.path||""});if(b.data){const{result:y,error:f}=b.data;y&&y.result&&(g.attribute=y.result),f&&(g.attribute=f)}}catch(b){g.attribute=b}}),v=()=>L(this,null,function*(){try{const b=yield G.Smart.Extend.Result.POST({devicePath:n.disk.path||""});if(b.data){const{result:y,error:f}=b.data;y&&y.result&&(g.extend=y.result),f&&(g.extend=f)}}catch(b){g.extend=b}});return(b,y)=>(r(),Z(je,null,{default:V(()=>[t("div",QF,[t("div",XF,[t("ul",tE,[t("li",{class:lt({"active cbi-tab":d.value=="info"}),onClick:y[0]||(y[0]=f=>c("info"))},[t("a",null,i(e(a)("\u8BBE\u5907\u4FE1\u606F")),1)],2),t("li",{class:lt({"active cbi-tab":d.value=="attribute"}),onClick:y[1]||(y[1]=f=>c("attribute"))},[t("a",null,i(e(a)("\u5C5E\u6027")),1)],2),t("li",{class:lt({"active cbi-tab":d.value=="log"}),onClick:y[2]||(y[2]=f=>c("log"))},[t("a",null,i(e(a)("\u81EA\u68C0\u65E5\u5FD7")),1)],2),t("li",{class:lt({"active cbi-tab":d.value=="extend"}),onClick:y[3]||(y[3]=f=>c("extend"))},[t("a",null,i(e(a)("\u6269\u5C55\u4FE1\u606F")),1)],2)])]),t("div",eE,[d.value=="info"?(r(),s("table",aE,[t("tr",oE,[t("td",nE,i(e(a)("\u8BBE\u5907")),1),t("td",iE,i(o.disk.path),1)]),t("tr",rE,[t("td",sE,i(e(a)("\u578B\u53F7")),1),t("td",dE,i(o.disk.model),1)]),t("tr",lE,[t("td",uE,i(e(a)("\u5E8F\u53F7")),1),t("td",cE,i(o.disk.serial),1)])])):d.value=="attribute"?(r(),s("textarea",{key:1,disabled:"",value:e(g).attribute},null,8,pE)):d.value=="log"?(r(),s("textarea",{key:2,disabled:"",value:e(g).log},null,8,fE)):d.value=="extend"?(r(),s("textarea",{key:3,disabled:"",value:e(g).extend},null,8,mE)):D("",!0)]),t("div",gE,[t("div",{class:"close",onClick:k,disabled:u.value},i(e(a)("\u5173\u95ED")),9,vE)])])]),_:1}))}});var hE=N(bE,[["__scopeId","data-v-313197ee"]]);const _E=o=>{const n=document.createElement("div");document.body.appendChild(n);const a=Y(tF,vt(st({},o),{close:()=>{l()}})),l=()=>{n.remove()};Ie(a,n)},xE=o=>{const n=document.createElement("div");document.body.appendChild(n);const a=Y(GF,vt(st({},o),{close:()=>{l()}})),l=()=>{n.remove()};Ie(a,n)},wE=o=>{const n=document.createElement("div");document.body.appendChild(n);const a=Y(KF,vt(st({},o),{close:()=>{l()}})),l=()=>{n.remove()};Ie(a,n)},kE=o=>{const n=document.createElement("div");document.body.appendChild(n);const a=Y(hE,vt(st({},o),{close:()=>{l()}})),l=()=>{n.remove()};Ie(a,n)},yE={class:"cbi-section"},FE={class:"cbi-value"},EE={class:"cbi-value-title"},$E={class:"cbi-value-field"},CE={class:"cbi-checkbox"},DE=["value"],BE={class:"cbi-value"},YE={class:"cbi-value-title"},AE={class:"cbi-value-field"},SE={class:"cbi-checkbox"},zE={value:"never"},PE={value:"sleep"},TE={value:"standby"},IE={value:"idle"},ME={class:"cbi-value-description"},LE=t("br",null,null,-1),OE=t("br",null,null,-1),NE=t("br",null,null,-1),VE=t("br",null,null,-1),GE={class:"cbi-value"},jE={class:"cbi-value-title"},UE={class:"cbi-value-field"},qE={class:"cbi-checkbox"},RE={value:0},WE=["value"],HE={class:"cbi-value-description"},JE={class:"cbi-value"},ZE={class:"cbi-value-title"},KE={class:"cbi-value-field"},QE={class:"cbi-checkbox"},XE={value:0},t$=["value"],e$={class:"cbi-value-description"},a$={class:"cbi-section cbi-tblsection",id:"cbi-nfs-mount"},o$={class:"table cbi-section-table"},n$={class:"tr cbi-section-table-titles anonymous"},i$={class:"th cbi-section-table-cell","data-widget":"value"},r$={class:"th cbi-section-table-cell","data-widget":"value"},s$={class:"th cbi-section-table-cell","data-widget":"value"},d$={class:"th cbi-section-table-cell","data-widget":"value"},l$={class:"th cbi-section-table-cell","data-widget":"value"},u$={class:"th cbi-section-table-cell","data-widget":"value" Line truncated
@@ -1,2 +1,2 @@
@keyframes bganimation-0698ba82{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter-0698ba82{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns-0698ba82{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active[data-v-0698ba82]{animation:rotateEnter-0698ba82 .7s;position:relative}.rotate-leave-active[data-v-0698ba82]{opacity:0;display:none;position:relative;z-index:-999}.app-container[data-v-0698ba82]{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}.disk-item-tooltip[data-v-0698ba82]{position:fixed;background:rgba(0,0,0,.7);z-index:10111;color:#fff;padding:.5rem 1rem;font-size:1em;min-width:200px;line-height:24px}.disk-item-tooltip[data-v-0698ba82]:after{content:"";position:absolute;bottom:-6px;border-color:#4c4c4c rgba(0,0,0,0) rgba(0,0,0,0);left:0;right:0;text-align:center;width:0;margin:0 auto;border-width:6px 8px 0;border-style:solid}#main .app-btn,#actioner .app-btn{min-height:36px}@keyframes bganimation-9bc295c2{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter-9bc295c2{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns-9bc295c2{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active[data-v-9bc295c2]{animation:rotateEnter-9bc295c2 .7s;position:relative}.rotate-leave-active[data-v-9bc295c2]{opacity:0;display:none;position:relative;z-index:-999}.app-container[data-v-9bc295c2]{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}#main[data-v-9bc295c2]{width:100%}#main[data-v-9bc295c2],#main[data-v-9bc295c2] *{-webkit-box-sizing:border-box;-webkit-tap-highlight-color:transparent;box-sizing:border-box;word-wrap:break-word;outline:none}@keyframes bganimation{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active{animation:rotateEnter .7s;position:relative}.rotate-leave-active{opacity:0;display:none;position:relative;z-index:-999}.app-container{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}:root{--flow-bg-color: #fff;--flow-span-color: rgba(0, 0, 0, .6);--card-bg-color: #fff;--card-box-shadow: 0 0 10px 1px #bfbfbf24;--app-container_title-color: #1e1e1e;--app-container_status-label_block: black;--item-label_key-span-color: #333;--item-label_value-span-color: #333;--app-container_status-label_bg: #f3f3f3;--item_btn-border: 1px solid #553afb;--item_btn-color: #553afb;--tit-color: #1e1e1e;--popup-bg-color: #fff;--tag-bg-color: #f5f5f5;--gradient-bg-color:linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.8) 62%, rgba(255,255,255,1) 100%);--breadcrumbs-tit-color:#d6dbf8;--breadcrumbs-tit-color1:#f1f2f9;--border-color: #e5e5e5;--SystemUpdateBanner-bg-color: #eff6ff;--SystemUpdateBanner-border-color: #bedbff;--btn-border-color: #e5e5e5;--card-txt-color: #32325d;--SystemInfo-bg-color:#f9fafd}@media (prefers-color-scheme: light){:root{--flow-bg-color: #fff;--flow-span-color: rgba(0, 0, 0, .6);--card-bg-color: #fff;--card-box-shadow: 0 0 10px 1px #bfbfbf24;--app-container_title-color: #1e1e1e;--app-container_status-label_block: black;--item-label_key-span-color: #333;--item-label_value-span-color: #333;--app-container_status-label_bg: #f3f3f3;--item_btn-border: 1px solid #553afb;--item_btn-color: #553afb;--tit-color: #1e1e1e;--popup-bg-color: #fff;--tag-Line truncated
@keyframes bganimation-0698ba82{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter-0698ba82{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns-0698ba82{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active[data-v-0698ba82]{animation:rotateEnter-0698ba82 .7s;position:relative}.rotate-leave-active[data-v-0698ba82]{opacity:0;display:none;position:relative;z-index:-999}.app-container[data-v-0698ba82]{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}.disk-item-tooltip[data-v-0698ba82]{position:fixed;background:rgba(0,0,0,.7);z-index:10111;color:#fff;padding:.5rem 1rem;font-size:1em;min-width:200px;line-height:24px}.disk-item-tooltip[data-v-0698ba82]:after{content:"";position:absolute;bottom:-6px;border-color:#4c4c4c rgba(0,0,0,0) rgba(0,0,0,0);left:0;right:0;text-align:center;width:0;margin:0 auto;border-width:6px 8px 0;border-style:solid}#main .app-btn,#actioner .app-btn{min-height:36px}@keyframes bganimation-9bc295c2{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter-9bc295c2{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns-9bc295c2{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active[data-v-9bc295c2]{animation:rotateEnter-9bc295c2 .7s;position:relative}.rotate-leave-active[data-v-9bc295c2]{opacity:0;display:none;position:relative;z-index:-999}.app-container[data-v-9bc295c2]{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}#main[data-v-9bc295c2]{width:100%}#main[data-v-9bc295c2],#main[data-v-9bc295c2] *{-webkit-box-sizing:border-box;-webkit-tap-highlight-color:transparent;box-sizing:border-box;word-wrap:break-word;outline:none}@keyframes bganimation{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes rotateEnter{0%{transform:rotateY(180deg)}10%{transform:rotateY(198deg)}20%{transform:rotateY(216deg)}30%{transform:rotateY(234deg)}40%{transform:rotateY(252deg)}50%{transform:rotateY(270deg)}60%{transform:rotateY(288deg)}70%{transform:rotateY(306deg)}80%{transform:rotateY(324deg)}90%{transform:rotateY(342deg)}to{transform:rotateY(360deg)}}@keyframes turns{0%{-webkit-transform:rotate(0deg)}25%{-webkit-transform:rotate(90deg)}50%{-webkit-transform:rotate(180deg)}75%{-webkit-transform:rotate(270deg)}to{-webkit-transform:rotate(360deg)}}.rotate-enter-active{animation:rotateEnter .7s;position:relative}.rotate-leave-active{opacity:0;display:none;position:relative;z-index:-999}.app-container{width:100%;background-color:var(--card-bg-color);box-shadow:var(--card-box-shadow);padding:10px 30px;border-radius:6px;position:relative}:root{--flow-bg-color: #fff;--flow-span-color: rgba(0, 0, 0, .6);--card-bg-color: #fff;--card-box-shadow: 0 0 10px 1px #bfbfbf24;--app-container_title-color: #1e1e1e;--app-container_status-label_block: black;--item-label_key-span-color: #333;--item-label_value-span-color: #333;--app-container_status-label_bg: #f3f3f3;--item_btn-border: 1px solid #553afb;--item_btn-color: #553afb;--tit-color: #1e1e1e;--popup-bg-color: #fff;--tag-bg-color: #f5f5f5;--gradient-bg-color:linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.8) 62%, rgba(255,255,255,1) 100%);--breadcrumbs-tit-color:#d6dbf8;--breadcrumbs-tit-color1:#f1f2f9;--border-color: #e5e5e5;--SystemUpdateBanner-bg-color: #eff6ff;--SystemUpdateBanner-border-color: #bedbff;--btn-border-color: #e5e5e5;--card-txt-color: #32325d;--SystemInfo-bg-color:#f9fafd}@media (prefers-color-scheme: light){:root{--flow-bg-color: #fff;--flow-span-color: rgba(0, 0, 0, .6);--card-bg-color: #fff;--card-box-shadow: 0 0 10px 1px #bfbfbf24;--app-container_title-color: #1e1e1e;--app-container_status-label_block: black;--item-label_key-span-color: #333;--item-label_value-span-color: #333;--app-container_status-label_bg: #f3f3f3;--item_btn-border: 1px solid #553afb;--item_btn-color: #553afb;--tit-color: #1e1e1e;--popup-bg-color: #fff;--tag-Line truncated
.page-container>div>div>a:first-child{display:none}
@@ -45,4 +45,4 @@ PERFORMANCE OF THIS SOFTWARE.
`:"<br/>",m=c.join(g);this._showOrMove(s,function(){this._updateContentNotChangedOnAxis(r,u)?this._updatePosition(s,h,o[0],o[1],this._tooltipContent,u):this._showTooltipContent(s,m,u,Math.random()+"",o[0],o[1],h,null,v)})},t.prototype._showSeriesItemTooltip=function(r,n,i){var a=this._ecModel,o=Nt(n),s=o.seriesIndex,l=a.getSeriesByIndex(s),u=o.dataModel||l,f=o.dataIndex,c=o.dataType,v=u.getData(c),h=this._renderMode,d=r.positionDefault,p=lo([v.getItemModel(f),u,l&&(l.coordinateSystem||{}).model],this._tooltipModel,d?{position:d}:null),g=p.get("trigger");if(!(g!=null&&g!=="item")){var m=u.getDataParams(f,c),y=new mc;m.marker=y.makeTooltipMarker("item",Ti(m.color),h);var _=vy(u.formatTooltip(f,!1,c)),w=p.get("order"),S=p.get("valueFormatter"),b=_.frag,x=b?yy(S?Y({valueFormatter:S},b):b,y,h,w,a.get("useUTC"),p.get("textStyle")):_.text,T="item_"+u.name+"_"+f;this._showOrMove(p,function(){this._showTooltipContent(p,x,m,T,r.offsetX,r.offsetY,r.position,r.target,y)}),i({type:"showTip",dataIndexInside:f,dataIndex:v.getRawIndex(f),seriesIndex:s,from:this.uid})}},t.prototype._showComponentItemTooltip=function(r,n,i){var a=Nt(n),o=a.tooltipConfig,s=o.option||{};if(it(s)){var l=s;s={content:l,formatter:l}}var u=[s],f=this._ecModel.getComponent(a.componentMainType,a.componentIndex);f&&u.push(f),u.push({formatter:s.content});var c=r.positionDefault,v=lo(u,this._tooltipModel,c?{position:c}:null),h=v.get("content"),d=Math.random()+"",p=new mc;this._showOrMove(v,function(){var g=xt(v.get("formatterParams")||{});this._showTooltipContent(v,h,g,d,r.offsetX,r.offsetY,r.position,n,p)}),i({type:"showTip",from:this.uid})},t.prototype._showTooltipContent=function(r,n,i,a,o,s,l,u,f){if(this._ticket="",!(!r.get("showContent")||!r.get("show"))){var c=this._tooltipContent;c.setEnterable(r.get("enterable"));var v=r.get("formatter");l=l||r.get("position");var h=n,d=this._getNearestPoint([o,s],i,r.get("trigger"),r.get("borderColor")),p=d.color;if(v)if(it(v)){var g=r.ecModel.get("useUTC"),m=et(i)?i[0]:i,y=m&&m.axisType&&m.axisType.indexOf("time")>=0;h=v,y&&(h=Zu(m.axisValue,h,g)),h=Ib(h,i,!0)}else if(mt(v)){var _=Ft(function(w,S){w===this._ticket&&(c.setContent(S,f,r,p,l),this._updatePosition(r,l,o,s,c,i,u))},this);this._ticket=a,h=v(i,a,_)}else h=v;c.setContent(h,f,r,p,l),c.show(r,p),this._updatePosition(r,l,o,s,c,i,u)}},t.prototype._getNearestPoint=function(r,n,i,a){if(i==="axis"||et(n))return{color:a||(this._renderMode==="html"?"#fff":"none")};if(!et(n))return{color:a||n.color||n.borderColor}},t.prototype._updatePosition=function(r,n,i,a,o,s,l){var u=this._api.getWidth(),f=this._api.getHeight();n=n||r.get("position");var c=o.getSize(),v=r.get("align"),h=r.get("verticalAlign"),d=l&&l.getBoundingRect().clone();if(l&&d.applyTransform(l.transform),mt(n)&&(n=n([i,a],s,o.el,d,{viewSize:[u,f],contentSize:c.slice()})),et(n))i=Vt(n[0],u),a=Vt(n[1],f);else if(st(n)){var p=n;p.width=c[0],p.height=c[1];var g=Ma(p,{width:u,height:f});i=g.x,a=g.y,v=null,h=null}else if(it(n)&&l){var m=p5(n,d,c,r.get("borderWidth"));i=m[0],a=m[1]}else{var m=v5(i,a,o,u,f,v?null:20,h?null:20);i=m[0],a=m[1]}if(v&&(i-=B0(v)?c[0]/2:v==="right"?c[0]:0),h&&(a-=B0(h)?c[1]/2:h==="bottom"?c[1]:0),xx(r)){var m=d5(i,a,o,u,f);i=m[0],a=m[1]}o.moveTo(i,a)},t.prototype._updateContentNotChangedOnAxis=function(r,n){var i=this._lastDataByCoordSys,a=this._cbParamsList,o=!!i&&i.length===r.length;return o&&I(i,function(s,l){var u=s.dataByAxis||[],f=r[l]||{},c=f.dataByAxis||[];o=o&&u.length===c.length,o&&I(u,function(v,h){var d=c[h]||{},p=v.seriesDataIndices||[],g=d.seriesDataIndices||[];o=o&&v.value===d.value&&v.axisType===d.axisType&&v.axisId===d.axisId&&p.length===g.length,o&&I(p,function(m,y){var _=g[y];o=o&&m.seriesIndex===_.seriesIndex&&m.dataIndex===_.dataIndex}),a&&I(v.seriesDataIndices,function(m){var y=m.seriesIndex,_=n[y],w=a[y];_&&w&&w.data!==_.data&&(o=!1)})})}),this._lastDataByCoordSys=r,this._cbParamsList=n,!!o},t.prototype._hide=function(r){this._lastDataByCoordSys=null,r({type:"hideTip",from:this.uid})},t.prototype.dispose=function(r,n){wt.node||!n.getDom()||(Jh(this,"_updatePosition"),this._tooltipContent.dispose(),dv("itemTooltip",n))},t.type="tooltip",t}(qr);function lo(e,t,r){var n=t.ecModel,i;r?(i=new ce(r,n,n),i=new ce(t.option,i,n)):i=t;for(var a=e.length-1;a>=0;a--){var o=e[a];o&&(o instanceof ce&&(o=o.get("tooltip",!0)),it(o)&&(o={formatter:o}),o&&(i=new ce(o,i,n)))}return i}function N0(e,t){return e.dispatchAction||Ft(t.dispatchAction,t)}function v5(e,t,r,n,i,a,o){var s=r.getSize(),l=s[0],u=s[1];return a!=null&&(e+l+a+2>n?e-=l+a:e+=a),o!=null&&(t+u+o>i?t-=u+o:t+=o),[e,t]}function d5(e,t,r,n,i){var a=r.getSize(),o=a[0],s=a[1];return e=Math.min(e+o,n)-o,t=Math.min(t+s,i)-s,e=Math.max(e,0),t=Math.max(t,0),[e,t]}function p5(e,t,r,n){var i=r[0],a=r[1],o=Math.ceil(Math.SQRT2*n)+8,s=0,l=0,u=t.width,f=t.height;switch(e){case"inside":s=t.x+u/2-i/2,l=t.y+f/2-a/2;break;case"top":s=t.x+u/2-i/2,l=t.y-a-o;break;case"bottom":s=t.x+u/2-i/2,l=t.y+f+o;break;case"left":s=t.x-i-o,l=t Line truncated
`)){var g=new vl;g.newline=!0,u.add(g);return}var m=i.getSeriesByName(p)[0];if(!f.get(p))if(m){var y=m.getData(),_=y.getVisual("legendLineStyle")||{},w=y.getVisual("legendIcon"),S=y.getVisual("style"),b=this._createItem(m,p,d,h,n,r,_,S,w,c);b.on("click",Qi(H0,p,null,a,v)).on("mouseover",Qi(mv,m.name,null,a,v)).on("mouseout",Qi(yv,m.name,null,a,v)),f.set(p,!0)}else i.eachRawSeries(function(x){if(!f.get(p)&&x.legendVisualProvider){var T=x.legendVisualProvider;if(!T.containName(p))return;var C=T.indexOfName(p),D=T.getItemVisual(C,"style"),P=T.getItemVisual(C,"legendIcon"),A=$r(D.fill);A&&A[3]===0&&(A[3]=.2,D=Y(Y({},D),{fill:zu(A,"rgba")}));var L=this._createItem(x,p,d,h,n,r,{},D,P,c);L.on("click",Qi(H0,null,p,a,v)).on("mouseover",Qi(mv,null,p,a,v)).on("mouseout",Qi(yv,null,p,a,v)),f.set(p,!0)}},this)},this),o&&this._createSelector(o,n,a,s,l)},t.prototype._createSelector=function(r,n,i,a,o){var s=this.getSelectorGroup();gv(r,function(u){var f=u.type,c=new he({style:{x:0,y:0,align:"center",verticalAlign:"middle"},onclick:function(){i.dispatchAction({type:f==="all"?"legendAllSelect":"legendInverseSelect"})}});s.add(c);var v=n.getModel("selectorLabel"),h=n.getModel(["emphasis","selectorLabel"]);Xu(c,{normal:v,emphasis:h},{defaultText:u.title}),$h(c)})},t.prototype._createItem=function(r,n,i,a,o,s,l,u,f,c){var v=r.visualDrawType,h=o.get("itemWidth"),d=o.get("itemHeight"),p=o.isSelected(n),g=a.get("symbolRotate"),m=a.get("symbolKeepAspect"),y=a.get("icon");f=y||f||"roundRect";var _=P5(f,a,l,u,v,p),w=new vl,S=a.getModel("textStyle");if(mt(r.getLegendIcon)&&(!y||y==="inherit"))w.add(r.getLegendIcon({itemWidth:h,itemHeight:d,icon:f,iconRotate:g,itemStyle:_.itemStyle,lineStyle:_.lineStyle,symbolKeepAspect:m}));else{var b=y==="inherit"&&r.getData().getVisual("symbol")?g==="inherit"?r.getData().getVisual("symbolRotate"):g:0;w.add(I5({itemWidth:h,itemHeight:d,icon:f,iconRotate:b,itemStyle:_.itemStyle,lineStyle:_.lineStyle,symbolKeepAspect:m}))}var x=s==="left"?h+5:-5,T=s,C=o.get("formatter"),D=n;it(C)&&C?D=C.replace("{name}",n!=null?n:""):mt(C)&&(D=C(n));var P=a.get("inactiveColor");w.add(new he({style:kn(S,{text:D,x,y:d/2,fill:p?S.getTextColor():P,align:T,verticalAlign:"middle"})}));var A=new ne({shape:w.getBoundingRect(),invisible:!0}),L=a.getModel("tooltip");return L.get("show")&&Rd({el:A,componentModel:o,itemName:n,itemTooltipOption:L.option}),w.add(A),w.eachChild(function(O){O.silent=!0}),A.silent=!c,this.getContentGroup().add(w),$h(w),w.__legendDataIndex=i,w},t.prototype.layoutInner=function(r,n,i,a,o,s){var l=this.getContentGroup(),u=this.getSelectorGroup();No(r.get("orient"),l,r.get("itemGap"),i.width,i.height);var f=l.getBoundingRect(),c=[-f.x,-f.y];if(u.markRedraw(),l.markRedraw(),o){No("horizontal",u,r.get("selectorItemGap",!0));var v=u.getBoundingRect(),h=[-v.x,-v.y],d=r.get("selectorButtonGap",!0),p=r.getOrient().index,g=p===0?"width":"height",m=p===0?"height":"width",y=p===0?"y":"x";s==="end"?h[p]+=f[g]+d:c[p]+=v[g]+d,h[1-p]+=f[m]/2-v[m]/2,u.x=h[0],u.y=h[1],l.x=c[0],l.y=c[1];var _={x:0,y:0};return _[g]=f[g]+d+v[g],_[m]=Math.max(f[m],v[m]),_[y]=Math.min(0,v[y]+h[1-p]),_}else return l.x=c[0],l.y=c[1],this.group.getBoundingRect()},t.prototype.remove=function(){this.getContentGroup().removeAll(),this._isFirstRender=!0},t.type="legend.plain",t}(qr);function P5(e,t,r,n,i,a){function o(d,p){d.lineWidth==="auto"&&(d.lineWidth=p.lineWidth>0?2:0),gv(d,function(g,m){d[m]==="inherit"&&(d[m]=p[m])})}var s=t.getModel("itemStyle"),l=s.getItemStyle(),u=e.lastIndexOf("empty",0)===0?"fill":"stroke";l.decal=n.decal,l.fill==="inherit"&&(l.fill=n[i]),l.stroke==="inherit"&&(l.stroke=n[u]),l.opacity==="inherit"&&(l.opacity=(i==="fill"?n:r).opacity),o(l,n);var f=t.getModel("lineStyle"),c=f.getLineStyle();if(o(c,r),l.fill==="auto"&&(l.fill=n.fill),l.stroke==="auto"&&(l.stroke=n.fill),c.stroke==="auto"&&(c.stroke=n.fill),!a){var v=t.get("inactiveBorderWidth"),h=l[u];l.lineWidth=v==="auto"?n.lineWidth>0&&h?2:0:l.lineWidth,l.fill=t.get("inactiveColor"),l.stroke=t.get("inactiveBorderColor"),c.stroke=f.get("inactiveColor"),c.lineWidth=f.get("inactiveWidth")}return{itemStyle:l,lineStyle:c}}function I5(e){var t=e.icon||"roundRect",r=Ia(t,0,0,e.itemWidth,e.itemHeight,e.itemStyle.fill,e.symbolKeepAspect);return r.setStyle(e.itemStyle),r.rotation=(e.iconRotate||0)*Math.PI/180,r.setOrigin([e.itemWidth/2,e.itemHeight/2]),t.indexOf("empty")>-1&&(r.style.stroke=r.style.fill,r.style.fill="#fff",r.style.lineWidth=2),r}function H0(e,t,r,n){yv(e,t,r,n),r.dispatchAction({type:"legendToggleSelect",name:e!=null?e:t}),mv(e,t,r,n)}function Dx(e){for(var t=e.getZr().storage.getDisplayList(),r,n=0,i=t.length;n<i&&!(r=t[n].states.emphasis);)n++;return r&&r.hoverLayer}function mv(e,t,r,n){Dx(r)||r.dispatchAction({type:"highlight",seriesName:e,name:t,excludeSeriesId:n})}function yv(e,t,r,n){Dx(r)||r.dispatchAction({type:"downplay",seriesName:e,name:t,excludeSeriesId:n})}var Px=D5;function E5(e){var t=e.findComponents({mainType:"legend"});t&&t.length&&e.filterS Line truncated
`||u==="")){var f=o.isSelected(u);n.hasOwnProperty(u)?n[u]=n[u]&&f:n[u]=f}})}),e==="allSelect"||e==="inverseSelect"?{selected:n}:{name:t.name,selected:n}}function L5(e){e.registerAction("legendToggleSelect","legendselectchanged",Xt(uo,"toggleSelected")),e.registerAction("legendAllSelect","legendselectall",Xt(uo,"allSelect")),e.registerAction("legendInverseSelect","legendinverseselect",Xt(uo,"inverseSelect")),e.registerAction("legendSelect","legendselected",Xt(uo,"select")),e.registerAction("legendUnSelect","legendunselected",Xt(uo,"unSelect"))}function Ix(e){e.registerComponentModel(pv),e.registerComponentView(Px),e.registerProcessor(e.PRIORITY.PROCESSOR.SERIES_FILTER,E5),e.registerSubTypeDefaulter("legend",function(){return"plain"}),L5(e)}var R5=function(e){J(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r}return t.prototype.setScrollDataIndex=function(r){this.option.scrollDataIndex=r},t.prototype.init=function(r,n,i){var a=nf(r);e.prototype.init.call(this,r,n,i),$0(this,r,a)},t.prototype.mergeOption=function(r,n){e.prototype.mergeOption.call(this,r,n),$0(this,this.option,r)},t.type="legend.scroll",t.defaultOption=q2(pv.defaultOption,{scrollDataIndex:0,pageButtonItemGap:5,pageButtonGap:null,pageButtonPosition:"end",pageFormatter:"{current}/{total}",pageIcons:{horizontal:["M0,0L12,-10L12,10z","M0,0L-12,-10L-12,10z"],vertical:["M0,0L20,0L10,-20z","M0,0L20,0L10,20z"]},pageIconColor:"#2f4554",pageIconInactiveColor:"#aaa",pageIconSize:15,pageTextStyle:{color:"#333"},animationDurationUpdate:800}),t}(pv);function $0(e,t,r){var n=e.getOrient(),i=[1,1];i[n.index]=0,Aa(t,r,{type:"box",ignoreSize:!!i})}var O5=R5,V0=ye,Nc=["width","height"],Bc=["x","y"],k5=function(e){J(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r.newlineDisabled=!0,r._currentIndex=0,r}return t.prototype.init=function(){e.prototype.init.call(this),this.group.add(this._containerGroup=new V0),this._containerGroup.add(this.getContentGroup()),this.group.add(this._controllerGroup=new V0)},t.prototype.resetInner=function(){e.prototype.resetInner.call(this),this._controllerGroup.removeAll(),this._containerGroup.removeClipPath(),this._containerGroup.__rectSize=null},t.prototype.renderInner=function(r,n,i,a,o,s,l){var u=this;e.prototype.renderInner.call(this,r,n,i,a,o,s,l);var f=this._controllerGroup,c=n.get("pageIconSize",!0),v=et(c)?c:[c,c];d("pagePrev",0);var h=n.getModel("pageTextStyle");f.add(new he({name:"pageText",style:{text:"xx/xx",fill:h.getTextColor(),font:h.getFont(),verticalAlign:"middle",align:"center"},silent:!0})),d("pageNext",1);function d(p,g){var m=p+"DataIndex",y=Ld(n.get("pageIcons",!0)[n.getOrient().name][g],{onclick:Ft(u._pageGo,u,m,n,a)},{x:-v[0]/2,y:-v[1]/2,width:v[0],height:v[1]});y.name=p,f.add(y)}},t.prototype.layoutInner=function(r,n,i,a,o,s){var l=this.getSelectorGroup(),u=r.getOrient().index,f=Nc[u],c=Bc[u],v=Nc[1-u],h=Bc[1-u];o&&No("horizontal",l,r.get("selectorItemGap",!0));var d=r.get("selectorButtonGap",!0),p=l.getBoundingRect(),g=[-p.x,-p.y],m=xt(i);o&&(m[f]=i[f]-p[f]-d);var y=this._layoutContentAndController(r,a,m,u,f,v,h,c);if(o){if(s==="end")g[u]+=y[f]+d;else{var _=p[f]+d;g[u]-=_,y[c]-=_}y[f]+=p[f]+d,g[1-u]+=y[h]+y[v]/2-p[v]/2,y[v]=Math.max(y[v],p[v]),y[h]=Math.min(y[h],p[h]+g[1-u]),l.x=g[0],l.y=g[1],l.markRedraw()}return y},t.prototype._layoutContentAndController=function(r,n,i,a,o,s,l,u){var f=this.getContentGroup(),c=this._containerGroup,v=this._controllerGroup;No(r.get("orient"),f,r.get("itemGap"),a?i.width:null,a?null:i.height),No("horizontal",v,r.get("pageButtonItemGap",!0));var h=f.getBoundingRect(),d=v.getBoundingRect(),p=this._showController=h[o]>i[o],g=[-h.x,-h.y];n||(g[a]=f[u]);var m=[0,0],y=[-d.x,-d.y],_=Mt(r.get("pageButtonGap",!0),r.get("itemGap",!0));if(p){var w=r.get("pageButtonPosition",!0);w==="end"?y[a]+=i[o]-d[o]:m[a]+=d[o]+_}y[1-a]+=h[s]/2-d[s]/2,f.setPosition(g),c.setPosition(m),v.setPosition(y);var S={x:0,y:0};if(S[o]=p?i[o]:h[o],S[s]=Math.max(h[s],d[s]),S[l]=Math.min(0,d[l]+y[1-a]),c.__rectSize=i[o],p){var b={x:0,y:0};b[o]=Math.max(i[o]-d[o]-_,0),b[s]=S[s],c.setClipPath(new ne({shape:b})),c.__rectSize=b[o]}else v.eachChild(function(T){T.attr({invisible:!0,silent:!0})});var x=this._getPageInfo(r);return x.pageIndex!=null&&Dr(f,{x:x.contentPosition[0],y:x.contentPosition[1]},p?r:null),this._updatePageInfoView(r,x),S},t.prototype._pageGo=function(r,n,i){var a=this._getPageInfo(n)[r];a!=null&&i.dispatchAction({type:"legendScroll",scrollDataIndex:a,legendId:n.id})},t.prototype._updatePageInfoView=function(r,n){var i=this._controllerGroup;I(["pagePrev","pageNext"],function(f){var c=f+"DataIndex",v=n[c]!=null,h=i.childOfName(f);h&&(h.setStyle("fill",v?r.get("pageIconColor",!0):r.get("pageIconInactiveColor",!0)),h.cursor=v?"pointer":"default")});var a=i.childOfName("pageText"),o=r.get("pageFormatter"),s=n.pageIndex,l=s!=null?s+1:0,u=n.pageCount;a&&o&&a.setStyle("text",it(o)?o.replace("{current}",l==null?"":l+"").replace("{tot Line truncated
`||u==="")){var f=o.isSelected(u);n.hasOwnProperty(u)?n[u]=n[u]&&f:n[u]=f}})}),e==="allSelect"||e==="inverseSelect"?{selected:n}:{name:t.name,selected:n}}function L5(e){e.registerAction("legendToggleSelect","legendselectchanged",Xt(uo,"toggleSelected")),e.registerAction("legendAllSelect","legendselectall",Xt(uo,"allSelect")),e.registerAction("legendInverseSelect","legendinverseselect",Xt(uo,"inverseSelect")),e.registerAction("legendSelect","legendselected",Xt(uo,"select")),e.registerAction("legendUnSelect","legendunselected",Xt(uo,"unSelect"))}function Ix(e){e.registerComponentModel(pv),e.registerComponentView(Px),e.registerProcessor(e.PRIORITY.PROCESSOR.SERIES_FILTER,E5),e.registerSubTypeDefaulter("legend",function(){return"plain"}),L5(e)}var R5=function(e){J(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r}return t.prototype.setScrollDataIndex=function(r){this.option.scrollDataIndex=r},t.prototype.init=function(r,n,i){var a=nf(r);e.prototype.init.call(this,r,n,i),$0(this,r,a)},t.prototype.mergeOption=function(r,n){e.prototype.mergeOption.call(this,r,n),$0(this,this.option,r)},t.type="legend.scroll",t.defaultOption=q2(pv.defaultOption,{scrollDataIndex:0,pageButtonItemGap:5,pageButtonGap:null,pageButtonPosition:"end",pageFormatter:"{current}/{total}",pageIcons:{horizontal:["M0,0L12,-10L12,10z","M0,0L-12,-10L-12,10z"],vertical:["M0,0L20,0L10,-20z","M0,0L20,0L10,20z"]},pageIconColor:"#2f4554",pageIconInactiveColor:"#aaa",pageIconSize:15,pageTextStyle:{color:"#333"},animationDurationUpdate:800}),t}(pv);function $0(e,t,r){var n=e.getOrient(),i=[1,1];i[n.index]=0,Aa(t,r,{type:"box",ignoreSize:!!i})}var O5=R5,V0=ye,Nc=["width","height"],Bc=["x","y"],k5=function(e){J(t,e);function t(){var r=e!==null&&e.apply(this,arguments)||this;return r.type=t.type,r.newlineDisabled=!0,r._currentIndex=0,r}return t.prototype.init=function(){e.prototype.init.call(this),this.group.add(this._containerGroup=new V0),this._containerGroup.add(this.getContentGroup()),this.group.add(this._controllerGroup=new V0)},t.prototype.resetInner=function(){e.prototype.resetInner.call(this),this._controllerGroup.removeAll(),this._containerGroup.removeClipPath(),this._containerGroup.__rectSize=null},t.prototype.renderInner=function(r,n,i,a,o,s,l){var u=this;e.prototype.renderInner.call(this,r,n,i,a,o,s,l);var f=this._controllerGroup,c=n.get("pageIconSize",!0),v=et(c)?c:[c,c];d("pagePrev",0);var h=n.getModel("pageTextStyle");f.add(new he({name:"pageText",style:{text:"xx/xx",fill:h.getTextColor(),font:h.getFont(),verticalAlign:"middle",align:"center"},silent:!0})),d("pageNext",1);function d(p,g){var m=p+"DataIndex",y=Ld(n.get("pageIcons",!0)[n.getOrient().name][g],{onclick:Ft(u._pageGo,u,m,n,a)},{x:-v[0]/2,y:-v[1]/2,width:v[0],height:v[1]});y.name=p,f.add(y)}},t.prototype.layoutInner=function(r,n,i,a,o,s){var l=this.getSelectorGroup(),u=r.getOrient().index,f=Nc[u],c=Bc[u],v=Nc[1-u],h=Bc[1-u];o&&No("horizontal",l,r.get("selectorItemGap",!0));var d=r.get("selectorButtonGap",!0),p=l.getBoundingRect(),g=[-p.x,-p.y],m=xt(i);o&&(m[f]=i[f]-p[f]-d);var y=this._layoutContentAndController(r,a,m,u,f,v,h,c);if(o){if(s==="end")g[u]+=y[f]+d;else{var _=p[f]+d;g[u]-=_,y[c]-=_}y[f]+=p[f]+d,g[1-u]+=y[h]+y[v]/2-p[v]/2,y[v]=Math.max(y[v],p[v]),y[h]=Math.min(y[h],p[h]+g[1-u]),l.x=g[0],l.y=g[1],l.markRedraw()}return y},t.prototype._layoutContentAndController=function(r,n,i,a,o,s,l,u){var f=this.getContentGroup(),c=this._containerGroup,v=this._controllerGroup;No(r.get("orient"),f,r.get("itemGap"),a?i.width:null,a?null:i.height),No("horizontal",v,r.get("pageButtonItemGap",!0));var h=f.getBoundingRect(),d=v.getBoundingRect(),p=this._showController=h[o]>i[o],g=[-h.x,-h.y];n||(g[a]=f[u]);var m=[0,0],y=[-d.x,-d.y],_=Mt(r.get("pageButtonGap",!0),r.get("itemGap",!0));if(p){var w=r.get("pageButtonPosition",!0);w==="end"?y[a]+=i[o]-d[o]:m[a]+=d[o]+_}y[1-a]+=h[s]/2-d[s]/2,f.setPosition(g),c.setPosition(m),v.setPosition(y);var S={x:0,y:0};if(S[o]=p?i[o]:h[o],S[s]=Math.max(h[s],d[s]),S[l]=Math.min(0,d[l]+y[1-a]),c.__rectSize=i[o],p){var b={x:0,y:0};b[o]=Math.max(i[o]-d[o]-_,0),b[s]=S[s],c.setClipPath(new ne({shape:b})),c.__rectSize=b[o]}else v.eachChild(function(T){T.attr({invisible:!0,silent:!0})});var x=this._getPageInfo(r);return x.pageIndex!=null&&Dr(f,{x:x.contentPosition[0],y:x.contentPosition[1]},p?r:null),this._updatePageInfoView(r,x),S},t.prototype._pageGo=function(r,n,i){var a=this._getPageInfo(n)[r];a!=null&&i.dispatchAction({type:"legendScroll",scrollDataIndex:a,legendId:n.id})},t.prototype._updatePageInfoView=function(r,n){var i=this._controllerGroup;I(["pagePrev","pageNext"],function(f){var c=f+"DataIndex",v=n[c]!=null,h=i.childOfName(f);h&&(h.setStyle("fill",v?r.get("pageIconColor",!0):r.get("pageIconInactiveColor",!0)),h.cursor=v?"pointer":"default")});var a=i.childOfName("pageText"),o=r.get("pageFormatter"),s=n.pageIndex,l=s!=null?s+1:0,u=n.pageCount;a&&o&&a.setStyle("text",it(o)?o.replace("{current}",l==null?"":l+"").replace("{tot Line truncated
+4 -4
View File
@@ -1,14 +1,14 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=mihomo-alpha
PKG_VERSION:=2026.06.23
PKG_RELEASE:=17
PKG_VERSION:=2026.07.08
PKG_RELEASE:=18
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
PKG_SOURCE_VERSION:=59ffb637343092d3fc94d6cc600b86e1f78d6c1a
PKG_SOURCE_VERSION:=cbd11db1e13a75d8e680e0fe7742c95be4cba2be
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL3.0+
@@ -18,7 +18,7 @@ PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16
PKG_BUILD_VERSION:=alpha-59ffb63
PKG_BUILD_VERSION:=alpha-cbd11db
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
+4 -4
View File
@@ -1,14 +1,14 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=mihomo-meta
PKG_VERSION:=1.19.27
PKG_RELEASE:=11
PKG_VERSION:=1.19.28
PKG_RELEASE:=12
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
PKG_SOURCE_VERSION:=v1.19.27
PKG_SOURCE_VERSION:=v1.19.28
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL3.0+
@@ -18,7 +18,7 @@ PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_BUILD_FLAGS:=no-mips16
PKG_BUILD_VERSION:=v1.19.27
PKG_BUILD_VERSION:=v1.19.28
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=modeminfo
PKG_VERSION:=0.4.7
PKG_RELEASE:=8
PKG_RELEASE:=9
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
include $(INCLUDE_DIR)/package.mk
@@ -8,8 +8,10 @@ function modem_data(){
set -- $(echo "$O" | awk '
/\+GTPKGVER:/{gsub("\"","");fw=$2}
/CGMI:/ {gsub("\"|\r","",$0); split($0,a,":"); manuf=a[2]}
/GMM:/ {gsub("\"|\r","",$0); split($0,a,/[:,]/); model=a[2]}
/GMM:|FMM:/ {gsub("\"|\r","",$0); split($0,a,/[:,]/); model=a[2]}
/CCID:/ {iccid=$2; getline imsi; getline imei}
/\+CIMI:/ {imsi=$2}
/\+CGSN:/ {imei=$2}
/\+MTSM:/ {split($0,a,":"); temp=a[2]}
END { print manuf "|" model "|" fw "|" iccid "|" imsi "|" imei "|" temp }
')
@@ -17,13 +19,13 @@ function modem_data(){
IFS="$OIFS"; unset OIFS
# signal data
OIFS="$IFS";IFS="|"
set -- $(echo "$O" | awk -v mode=$MODE '
set -- $(echo "$O" | awk -v mode="$MODE" '
# define maps and variables
BEGIN {
map[1]=15; map[2]=25; map[3]=50; map[4]=75; map[5]=100
csq_range["black"] = "0:0"; csq_range["red"] = "1:10"
csq_range["orange"] = "11:20"; csq_range["green"] = "21:50"
rsrp = bwc = csq = rssi = csq_per = 0; csq_col = "black";
rsrp = rsrq = bwc = csq = rssi = csq_per = ""; csq_col = "black";
}
# text color percentage
function get_csq_color(csq_raw_val) {
@@ -38,25 +40,33 @@ function modem_data(){
# lte mode
mode ~ /LTE/ {
if (/\+CEREG/){gsub("\"","");split($0,a,",");reg=a[2];lac=a[3]; cid=a[4]}
if (/\+XLEC:/){gsub("\r","");split($0,a,/[:,]/);lte_ca=a[3]-1;bw=a[4];bwc=map[bw]}
if (/\+XLEC:/){gsub("\r","");split($0,a,/[:,]/);lte_ca=a[3]-1;bw=a[4];bwc=(bw in map)?map[bw]:""}
if (/\+RSRP:/){split($0,a,/[:,]/);pci=a[2];earfcn=a[3];rsrp=sprintf("%.0f",a[4])}
if (/\+RSRQ:/){split($0,a,/[:,]/);rsrq=sprintf("%.0f",a[4])}
if (/\+XMCI: 4/){gsub(/"/,"");split($0,a,/[:,]/)
pci=sprintf("%d", a[7]);sinr=sprintf("%.0f", a[13]/4+5)
distance=sprintf("%.2f", (a[14]*78/1000))
if (/\+XMCI: 4/){gsub(/"/,"");gsub("\r","");split($0,a,/[:,]/)
pci=sprintf("%d", a[7])
if (a[8] != "") earfcn=sprintf("%d", a[8])
if (a[11] != "" && (rsrp == "" || rsrp !~ /^-?[0-9]+$/ || rsrp < -141 || rsrp > -41))
rsrp=sprintf("%.0f", a[11] - 141)
if (a[12] != "" && (rsrq == "" || rsrq !~ /^-?[0-9]+$/ || rsrq < -40 || rsrq > -3))
rsrq=sprintf("%.0f", a[12] / 2 - 20)
if (a[13] != "") sinr=sprintf("%.0f", a[13] / 4 + 5)
if (a[14] != "") distance=sprintf("%.2f", (a[14] * 78.125 / 1000))
}
# RSSI not correct sending via modem. Calc manually.
csq_raw=sprintf("%.0f", ((rsrp+10*log(12*bwc)/log(10))+113)/2)
csq = (csq_raw > 50) ? 50 : csq = csq_raw
csq_col = get_csq_color(csq)
csq_per = (csq > 30) ? 100 : sprintf("%.0f", csq*100/31); rssi=(2*csq-113)
if (rsrp != "" && bwc != "" && bwc > 0) {
csq_raw=sprintf("%.0f", ((rsrp+10*log(12*bwc)/log(10))+113)/2)
csq = (csq_raw > 50) ? 50 : csq_raw
csq_col = get_csq_color(csq)
csq_per = (csq > 30) ? 100 : sprintf("%.0f", csq*100/31); rssi=(2*csq-113)
}
}
# 3g mode
mode !~ /LTE/{
if (/\+CREG/){gsub("\"","");split($0,a,",");reg=a[2];lac=a[3];cid=a[4]}
if (/\+RSCP:/){split($0,a,",");earfcn=a[2]}
if (/\+XMCI: 2/) {n=split($0,a,",");sinr_raw=a[n-1]; rssi_raw=a[n-2]; sinr = sprintf("%.0f", (sinr_raw*24)/24-50); rssi = (rssi_raw*64/64-110)
csq=rssi_raw/2; csq_col = get_csq_color(csq); csq_per = (csq > 30) ? 100 : sprintf("%.0f", csq*100/31)}
csq=rssi_raw/2; csq_col = get_csq_color(csq); csq_per = (csq > 30) ? 100 : sprintf("%.0f", csq*100/31)}
rsrp=""
}
END {print reg "|" earfcn "|" lac "|" cid "|" rssi "|" sinr "|" rsrp "|" rsrq "|" bw "|" bwc "|" pci "|" lte_ca "|" csq_col "|" csq_per "|" distance}
@@ -64,12 +74,13 @@ function modem_data(){
REGST=${1}; EARFCN=${2}; LAC=${3}; CID=${4}; CSQ_RSSI=${5}; SINR=${6}; RSRP=${7}; RSRQ=${8}; BWDL=${9}; BWCx=${10}
PCI=${11}; LTE_CA=${12}; CSQ_COL=${13}; CSQ_PER=${14}; DISTANCE=${15}
IFS="$OIFS"; unset OIFS
if [ "$MODE" = "LTE" ]; then
ENBx=$(echo $CID | sed -e 's/..$//')
CELL=$(printf %d 0x${CID: -2})
if [ "$MODE" = LTE ]; then
ENBx=$(echo "$CID" | sed -e 's/..$//')
CELLx=$(echo "$CID" | sed -e 's/^.*\(..\)$/\1/')
CELL=$(printf %d 0x$CELLx)
ENBID=$(printf %d 0x$ENBx)
OIFS="$IFS";IFS="|"
set -- $(echo "$O"| awk -F [:,] -v lte_ca=$LTE_CA -v mode="$MODE" '
set -- $(echo "$O"| awk -F '[:,]' -v lte_ca="${LTE_CA:-0}" -v mode="$MODE" '
# define maps and variables
BEGIN { map[1]=3; map[2]=5; map[3]=10; map[4]=15; map[5]=20; scc = "" }
function earfcn_to_band(channel, cmd, band) {
@@ -115,7 +126,7 @@ function modem_data(){
IFS="$OIFS"; unset OIFS
fi
if [ $(uci -q get modeminfo.@general[0].decimail) = "1" ]; then
if [ "$(uci -q get modeminfo.@general[0].decimail)" = "1" ]; then
LAC=$(printf %d 0x$LAC)
CID=$(printf %d 0x$CID)
fi
+3 -3
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=mwan3-ledhelper
PKG_VERSION:=0.0.2
PKG_RELEASE:=3
PKG_RELEASE:=4
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
include $(INCLUDE_DIR)/package.mk
@@ -53,10 +53,10 @@ define Package/$(PKG_NAME)-nft/install
$(CP) ./root/* $(1)/
endef
ifneq ($(BUILD_NFT_ONLY),1)
ifneq ($(MWAN3_LEDHELPER_BUILD_NFT_ONLY),1)
$(eval $(call BuildPackage,$(PKG_NAME)))
endif
ifneq ($(BUILD_IPT_ONLY),1)
ifneq ($(MWAN3_LEDHELPER_BUILD_IPT_ONLY),1)
$(eval $(call BuildPackage,$(PKG_NAME)-nft))
endif
+2 -2
View File
@@ -2,14 +2,14 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=ssw
PKG_VERSION:=0.0.4
PKG_RELEASE:=5
PKG_RELEASE:=6
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
PKGARCH:=all
DEPENDS:=+modemmanager +mwan3
DEPENDS:=+modemmanager $(if $(filter 1,$(SSW_BUILD_WITH_NFT)),+mwan3-nft,+mwan3)
TITLE:=SIM-card switcher and tracker
endef