diff --git a/CHANGELOG.md b/CHANGELOG.md index 5526dee..cc75dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。 +## [1.0.5] - 2026-03-05 + +### 修复配置管理页面 "spawn script ENOENT" 启动失败 (#3, #4) + +#### 修复 +- **Web PTY 启动失败**: `web-pty.js` 硬编码依赖 `script` 命令 (来自 `util-linux-script`),但部分 OpenWrt 固件默认不包含该命令,导致 `spawn script ENOENT` 错误并无限循环重启 + - 新增 `script` 命令自动检测,不存在时回退到 `sh` 直接执行 `oc-config.sh` + - 新增连续失败计数器 (最多 5 次),防止启动失败时的无限重试循环 + - 失败时向用户终端显示明确的错误提示和修复命令 +- **Makefile 依赖补全**: `LUCI_DEPENDS` 新增 `+util-linux-script`,确保新安装自动拉取 `script` 命令 + ## [1.0.4] - 2026-03-05 ### 适配 OpenClaw 2026.3.2 diff --git a/Makefile b/Makefile index e166d55..03c50ed 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ PKG_MAINTAINER:=10000ge10000 <10000ge10000@users.noreply.github.com> PKG_LICENSE:=GPL-3.0 LUCI_TITLE:=OpenClaw AI 网关 LuCI 管理插件 -LUCI_DEPENDS:=+luci-compat +luci-base +curl +openssl-util +LUCI_DEPENDS:=+luci-compat +luci-base +curl +openssl-util +util-linux-script LUCI_PKGARCH:=all # 优先使用 luci.mk (feeds 模式), 不可用时回退 package.mk diff --git a/VERSION b/VERSION index ee90284..90a27f9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.4 +1.0.5 diff --git a/root/usr/share/openclaw/web-pty.js b/root/usr/share/openclaw/web-pty.js index 5c5855e..3ca6b51 100644 --- a/root/usr/share/openclaw/web-pty.js +++ b/root/usr/share/openclaw/web-pty.js @@ -104,6 +104,8 @@ class PtySession { this.rows = 24; this.buffer = Buffer.alloc(0); this.alive = true; + this._spawnFailCount = 0; + this._MAX_SPAWN_RETRIES = 5; activeSessions++; console.log(`[oc-config] Session created (active: ${activeSessions}/${MAX_SESSIONS})`); this._setupWSReader(); @@ -153,14 +155,36 @@ class PtySession { OPENCLAW_CONFIG_PATH: `${OC_DATA}/.openclaw/openclaw.json`, PATH: `${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`, }; - this.proc = spawn('script', ['-qc', `stty rows ${this.rows} cols ${this.cols} 2>/dev/null; printf '\\e[?2004l'; sh "${SCRIPT_PATH}"`, '/dev/null'], - { stdio: ['pipe', 'pipe', 'pipe'], env, detached: true }); + // 检测 script 命令是否可用 (OpenWrt 默认不包含 util-linux-script) + // 如不可用则回退到直接用 sh 执行,牺牲 PTY 但保证功能可用 + const hasScript = (() => { + try { + const { execFileSync } = require('child_process'); + execFileSync('which', ['script'], { stdio: 'pipe', timeout: 2000 }); + return true; + } catch { return false; } + })(); + if (hasScript) { + this.proc = spawn('script', ['-qc', `stty rows ${this.rows} cols ${this.cols} 2>/dev/null; printf '\\e[?2004l'; sh "${SCRIPT_PATH}"`, '/dev/null'], + { stdio: ['pipe', 'pipe', 'pipe'], env, detached: true }); + } else { + console.log('[oc-config] "script" command not found, falling back to sh (install util-linux-script for full PTY support)'); + this.proc = spawn('sh', [SCRIPT_PATH], + { stdio: ['pipe', 'pipe', 'pipe'], env, detached: true }); + } - this.proc.stdout.on('data', (d) => { if (this.alive) this.socket.write(encodeWSFrame(d, 0x01)); }); - this.proc.stderr.on('data', (d) => { if (this.alive) this.socket.write(encodeWSFrame(d, 0x01)); }); + this.proc.stdout.on('data', (d) => { if (this.alive) { this._spawnFailCount = 0; this.socket.write(encodeWSFrame(d, 0x01)); } }); + this.proc.stderr.on('data', (d) => { if (this.alive) { this._spawnFailCount = 0; this.socket.write(encodeWSFrame(d, 0x01)); } }); this.proc.on('close', (code) => { if (!this.alive) return; - console.log(`[oc-config] Script exited with code ${code}, auto-restarting...`); + this._spawnFailCount++; + if (this._spawnFailCount > this._MAX_SPAWN_RETRIES) { + console.log(`[oc-config] Script failed ${this._spawnFailCount} times, stopping retries`); + this.socket.write(encodeWSFrame(`\r\n\x1b[31m配置脚本连续启动失败 ${this._spawnFailCount} 次,已停止重试。\r\n请检查是否已安装 util-linux-script 包: opkg install coreutils-script\x1b[0m\r\n`, 0x01)); + this.proc = null; + return; + } + console.log(`[oc-config] Script exited with code ${code}, auto-restarting (attempt ${this._spawnFailCount}/${this._MAX_SPAWN_RETRIES})...`); this.socket.write(encodeWSFrame(`\r\n\x1b[33m配置脚本已退出 (code: ${code}),正在自动重启...\x1b[0m\r\n`, 0x01)); this.proc = null; // 自动重启脚本,保持 WebSocket 连接 @@ -171,6 +195,7 @@ class PtySession { }, 1500); }); this.proc.on('error', (err) => { + this._spawnFailCount++; if (this.alive) this.socket.write(encodeWSFrame(`\r\n\x1b[31m启动失败: ${err.message}\x1b[0m\r\n`, 0x01)); }); } diff --git a/scripts/build_ipk.sh b/scripts/build_ipk.sh index db5909c..28efd34 100755 --- a/scripts/build_ipk.sh +++ b/scripts/build_ipk.sh @@ -86,7 +86,7 @@ mkdir -p "$CTRL_DIR" cat > "$CTRL_DIR/control" << EOF Package: ${PKG_NAME} Version: ${PKG_VERSION}-${PKG_RELEASE} -Depends: luci-compat, luci-base, curl, openssl-util +Depends: luci-compat, luci-base, curl, openssl-util, util-linux-script Source: https://github.com/10000ge10000/luci-app-openclaw SourceName: ${PKG_NAME} License: GPL-3.0