release: v1.0.7 — 依赖包名修正、Gateway crash loop 修复

This commit is contained in:
10000ge10000
2026-03-06 00:33:17 +08:00
parent 20a8c016a4
commit e844b4754b
9 changed files with 130 additions and 9 deletions

View File

@@ -4,6 +4,29 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
## [1.0.7] - 2026-03-06
### 修复依赖包名错误 & 补充 GNU tar 依赖 (感谢 [@esir](https://github.com/esir) 建议)
#### 修复
- **依赖包名修正**: `util-linux-script` 在 OpenWrt/iStoreOS 软件源中不存在,正确的包名是 `script-utils` (提供 `/usr/bin/script` 命令)。此错误会导致通过 iStore/opkg 安装插件时依赖解析失败
- **补充 GNU tar 依赖**: `openclaw-env` 安装脚本使用 `tar --strip-components=1` 解压 Node.js但 busybox 内置的 tar 不支持该参数。新增 `tar` (GNU tar) 为必需依赖,确保解压操作正常
#### 变更
- `Makefile`: `LUCI_DEPENDS``+util-linux-script``+script-utils +tar`
- `scripts/build_ipk.sh`: 同步更新 Depends 字段
- `scripts/build_run.sh`: 同步更新 Depends 字段
### 修复重启服务时 Gateway crash loop 端口冲突
#### 修复
- **端口冲突 crash loop**: OpenClaw gateway 的架构是主进程 (`openclaw`) fork 出子进程 (`openclaw-gateway`) 监听端口restart 时 procd 只杀主进程,子进程退出慢导致新实例端口冲突反复崩溃
- `stop_service()`: 从空函数改为主动清理 `openclaw-gateway` 子进程 + 等待端口释放 (最长 8 秒)
- `start_service()`: 启动前预检查端口,清理残留进程后再注册 procd 实例
- `reload_service()`: stop 和 start 之间增加等待确保内核回收端口
- LuCI controller: restart 改为先同步 stop 等端口释放,再后台 start
- procd respawn 间隔从 5s → 10s降低连续端口冲突概率
## [1.0.6] - 2026-03-06 ## [1.0.6] - 2026-03-06
### 修复 Docker 环境下安装失败 "mkdir: can't create directory: Directory not empty" ### 修复 Docker 环境下安装失败 "mkdir: can't create directory: Directory not empty"

View File

@@ -13,7 +13,7 @@ PKG_MAINTAINER:=10000ge10000 <10000ge10000@users.noreply.github.com>
PKG_LICENSE:=GPL-3.0 PKG_LICENSE:=GPL-3.0
LUCI_TITLE:=OpenClaw AI 网关 LuCI 管理插件 LUCI_TITLE:=OpenClaw AI 网关 LuCI 管理插件
LUCI_DEPENDS:=+luci-compat +luci-base +curl +openssl-util +util-linux-script LUCI_DEPENDS:=+luci-compat +luci-base +curl +openssl-util +script-utils +tar
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
# 优先使用 luci.mk (feeds 模式), 不可用时回退 package.mk # 优先使用 luci.mk (feeds 模式), 不可用时回退 package.mk

View File

@@ -1 +1 @@
1.0.6 1.0.7

View File

@@ -87,6 +87,7 @@ function action_status()
port = port, port = port,
pty_port = pty_port, pty_port = pty_port,
gateway_running = false, gateway_running = false,
gateway_starting = false,
pty_running = false, pty_running = false,
pid = "", pid = "",
memory_kb = 0, memory_kb = 0,
@@ -122,6 +123,14 @@ function action_status()
local gw_check = sys.exec("netstat -tlnp 2>/dev/null | grep -c ':" .. port .. " ' || echo 0"):gsub("%s+", "") local gw_check = sys.exec("netstat -tlnp 2>/dev/null | grep -c ':" .. port .. " ' || echo 0"):gsub("%s+", "")
result.gateway_running = (tonumber(gw_check) or 0) > 0 result.gateway_running = (tonumber(gw_check) or 0) > 0
-- 如果端口未监听但 procd 进程存在,说明正在启动中 (gateway 初始化需要数分钟)
if not result.gateway_running and enabled == "1" then
local procd_pid = sys.exec("pgrep -f 'openclaw.*gateway' 2>/dev/null | head -1"):gsub("%s+", "")
if procd_pid ~= "" then
result.gateway_starting = true
end
end
-- PTY 端口检查 -- PTY 端口检查
local pty_check = sys.exec("netstat -tlnp 2>/dev/null | grep -c ':" .. pty_port .. " ' || echo 0"):gsub("%s+", "") local pty_check = sys.exec("netstat -tlnp 2>/dev/null | grep -c ':" .. pty_port .. " ' || echo 0"):gsub("%s+", "")
result.pty_running = (tonumber(pty_check) or 0) > 0 result.pty_running = (tonumber(pty_check) or 0) > 0
@@ -183,8 +192,13 @@ function action_service_ctl()
sys.exec("/etc/init.d/openclaw start >/dev/null 2>&1 &") sys.exec("/etc/init.d/openclaw start >/dev/null 2>&1 &")
elseif action == "stop" then elseif action == "stop" then
sys.exec("/etc/init.d/openclaw stop >/dev/null 2>&1") sys.exec("/etc/init.d/openclaw stop >/dev/null 2>&1")
-- stop 后额外等待确保端口释放
sys.exec("sleep 2")
elseif action == "restart" then elseif action == "restart" then
sys.exec("/etc/init.d/openclaw restart >/dev/null 2>&1 &") -- 先完整 stop (确保端口释放),再后台 start
sys.exec("/etc/init.d/openclaw stop >/dev/null 2>&1")
sys.exec("sleep 2")
sys.exec("/etc/init.d/openclaw start >/dev/null 2>&1 &")
elseif action == "enable" then elseif action == "enable" then
sys.exec("/etc/init.d/openclaw enable 2>/dev/null") sys.exec("/etc/init.d/openclaw enable 2>/dev/null")
elseif action == "disable" then elseif action == "disable" then

View File

@@ -136,6 +136,14 @@ local port = uci:get("openclaw", "main", "port") or "18789"
openBtn.href = url; openBtn.href = url;
openBtn.style.display = ''; openBtn.style.display = '';
showIframe(url); showIframe(url);
} else if (d.gateway_starting) {
statusTextEl.innerHTML = '<span style="color:#9a6700;">⏳ 网关正在启动</span>';
openBtn.style.display = 'none';
loading.innerHTML = '<div style="text-align:center;color:#666;">' +
'<div style="font-size:40px;margin-bottom:12px;">⏳</div>' +
'<div style="font-size:15px;margin-bottom:6px;">OpenClaw 网关正在启动中...</div>' +
'<div style="font-size:12px;color:#999;">首次启动可能需要 2~5 分钟,请耐心等待,页面会自动刷新。</div>' +
'</div>';
} else { } else {
statusTextEl.innerHTML = '<span style="color:#cf222e;">● 网关未运行</span>'; statusTextEl.innerHTML = '<span style="color:#cf222e;">● 网关未运行</span>';
openBtn.style.display = 'none'; openBtn.style.display = 'none';

View File

@@ -54,6 +54,7 @@
} }
.oc-badge-running { background: #e6f7e9; color: #1a7f37; } .oc-badge-running { background: #e6f7e9; color: #1a7f37; }
.oc-badge-stopped { background: #ffeef0; color: #cf222e; } .oc-badge-stopped { background: #ffeef0; color: #cf222e; }
.oc-badge-starting { background: #fff8c5; color: #9a6700; }
.oc-badge-disabled { background: #f0f0f0; color: #656d76; } .oc-badge-disabled { background: #f0f0f0; color: #656d76; }
.oc-badge-unknown { background: #fff8c5; color: #9a6700; } .oc-badge-unknown { background: #fff8c5; color: #9a6700; }
.oc-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; } .oc-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
@@ -95,6 +96,8 @@
stEl.innerHTML = '<span class="oc-badge oc-badge-disabled">已禁用</span>'; stEl.innerHTML = '<span class="oc-badge oc-badge-disabled">已禁用</span>';
} else if (d.gateway_running) { } else if (d.gateway_running) {
stEl.innerHTML = '<span class="oc-badge oc-badge-running">运行中</span>'; stEl.innerHTML = '<span class="oc-badge oc-badge-running">运行中</span>';
} else if (d.gateway_starting) {
stEl.innerHTML = '<span class="oc-badge oc-badge-starting">⏳ 正在启动...</span>';
} else { } else {
stEl.innerHTML = '<span class="oc-badge oc-badge-stopped">已停止</span>'; stEl.innerHTML = '<span class="oc-badge oc-badge-stopped">已停止</span>';
} }
@@ -102,6 +105,8 @@
var gwEl = document.getElementById('oc-st-gateway'); var gwEl = document.getElementById('oc-st-gateway');
if (d.gateway_running) { if (d.gateway_running) {
gwEl.innerHTML = '<span class="oc-dot oc-dot-green"></span>监听中 :' + d.port; gwEl.innerHTML = '<span class="oc-dot oc-dot-green"></span>监听中 :' + d.port;
} else if (d.gateway_starting) {
gwEl.innerHTML = '<span class="oc-dot oc-dot-gray"></span>初始化中,首次启动可能需要 2~5 分钟...';
} else { } else {
gwEl.innerHTML = '<span class="oc-dot oc-dot-red"></span>未监听'; gwEl.innerHTML = '<span class="oc-dot oc-dot-red"></span>未监听';
} }

View File

@@ -190,6 +190,37 @@ all) gw_bind="custom" ;; # custom = 0.0.0.0
*) gw_bind="$bind" ;; *) gw_bind="$bind" ;;
esac esac
# 确保网关端口未被残留进程占用 (防止 restart 时 crash loop)
_ensure_port_free() {
local p="$1" max_wait="${2:-5}" i=0
while [ $i -lt $max_wait ]; do
if command -v ss >/dev/null 2>&1; then
ss -tlnp 2>/dev/null | grep -q ":${p} " || return 0
else
netstat -tlnp 2>/dev/null | grep -q ":${p} " || return 0
fi
# 尝试杀掉占用端口的 gateway 进程
if [ $i -eq 0 ]; then
local occ_pid
for occ_pid in $(pgrep -f "openclaw-gateway" 2>/dev/null); do
kill "$occ_pid" 2>/dev/null
done
fi
sleep 1
i=$((i + 1))
done
# 最后手段: SIGKILL
local port_pid=""
if command -v ss >/dev/null 2>&1; then
port_pid=$(ss -tlnp 2>/dev/null | grep ":${p} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)
else
port_pid=$(netstat -tlnp 2>/dev/null | grep ":${p} " | sed -n 's|.* \([0-9]*\)/.*|\1|p' | head -1)
fi
[ -n "$port_pid" ] && kill -9 "$port_pid" 2>/dev/null && sleep 1
return 0
}
_ensure_port_free "$port"
# 启动 OpenClaw Gateway (主服务, 前台运行) # 启动 OpenClaw Gateway (主服务, 前台运行)
procd_open_instance "gateway" procd_open_instance "gateway"
procd_set_param command "$NODE_BIN" "$oc_entry" gateway run \ procd_set_param command "$NODE_BIN" "$oc_entry" gateway run \
@@ -205,7 +236,7 @@ OC_GLOBAL="$OC_GLOBAL" \
OC_DATA="$OC_DATA" \ OC_DATA="$OC_DATA" \
PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
procd_set_param user openclaw procd_set_param user openclaw
procd_set_param respawn 3600 5 5 procd_set_param respawn 3600 10 5
procd_set_param stdout 1 procd_set_param stdout 1
procd_set_param stderr 1 procd_set_param stderr 1
procd_set_param pidfile /var/run/openclaw.pid procd_set_param pidfile /var/run/openclaw.pid
@@ -245,8 +276,44 @@ procd_close_instance
} }
stop_service() { stop_service() {
# procd 会自动处理进程停止 # procd 会自动停止它管理的主进程 (openclaw)
return 0 # 但 openclaw 会 fork 出 openclaw-gateway 子进程实际监听端口
# procd 不一定能正确追踪并杀掉子进程树,需要手动清理
local port
port=$(uci -q get openclaw.main.port || echo "18789")
# 杀掉所有 openclaw / openclaw-gateway 残留进程
# (排除 web-pty.js 和 oc-config.sh它们由 pty 实例管理)
local pid
for pid in $(pgrep -f "openclaw-gateway" 2>/dev/null) \
$(pgrep -f "openclaw.*gateway.*run" 2>/dev/null); do
kill "$pid" 2>/dev/null
done
# 等待端口真正释放 (最多 8 秒)
local wait_count=0
while [ $wait_count -lt 8 ]; do
if command -v ss >/dev/null 2>&1; then
ss -tlnp 2>/dev/null | grep -q ":${port} " || break
else
netstat -tlnp 2>/dev/null | grep -q ":${port} " || break
fi
sleep 1
wait_count=$((wait_count + 1))
done
# 如果端口仍被占用,强制杀掉占用者
if [ $wait_count -ge 8 ]; then
local port_pid=""
if command -v ss >/dev/null 2>&1; then
port_pid=$(ss -tlnp 2>/dev/null | grep ":${port} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)
else
port_pid=$(netstat -tlnp 2>/dev/null | grep ":${port} " | sed -n 's|.* \([0-9]*\)/.*|\1|p' | head -1)
fi
[ -n "$port_pid" ] && kill -9 "$port_pid" 2>/dev/null
sleep 1
fi
} }
service_triggers() { service_triggers() {
@@ -255,6 +322,8 @@ procd_add_reload_trigger "openclaw"
reload_service() { reload_service() {
stop stop
# stop_service 已确保端口释放,但额外等待 1 秒让内核回收
sleep 1
start start
} }
@@ -384,6 +453,8 @@ local mins=$(( (uptime % 3600) / 60 ))
echo "运行时间: ${hours}小时 ${mins}分钟" echo "运行时间: ${hours}小时 ${mins}分钟"
fi fi
fi fi
elif pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
echo "网关: 正在启动 (首次启动可能需要 2~5 分钟)"
else else
echo "网关: 已停止" echo "网关: 已停止"
fi fi

View File

@@ -86,7 +86,7 @@ mkdir -p "$CTRL_DIR"
cat > "$CTRL_DIR/control" << EOF cat > "$CTRL_DIR/control" << EOF
Package: ${PKG_NAME} Package: ${PKG_NAME}
Version: ${PKG_VERSION}-${PKG_RELEASE} Version: ${PKG_VERSION}-${PKG_RELEASE}
Depends: luci-compat, luci-base, curl, openssl-util, util-linux-script Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar
Source: https://github.com/10000ge10000/luci-app-openclaw Source: https://github.com/10000ge10000/luci-app-openclaw
SourceName: ${PKG_NAME} SourceName: ${PKG_NAME}
License: GPL-3.0 License: GPL-3.0

View File

@@ -124,7 +124,7 @@ mkdir -p "$INFO_DIR"
cat > "$INFO_DIR/$PKG.control" << CTLEOF cat > "$INFO_DIR/$PKG.control" << CTLEOF
Package: $PKG Package: $PKG
Version: $PKG_VER Version: $PKG_VER
Depends: luci-compat, luci-base, curl, openssl-util Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar
Section: luci Section: luci
Architecture: all Architecture: all
Installed-Size: 0 Installed-Size: 0
@@ -160,7 +160,7 @@ cat >> "$STATUS_FILE" << STEOF
Package: $PKG Package: $PKG
Version: $PKG_VER Version: $PKG_VER
Depends: luci-compat, luci-base, curl, openssl-util Depends: luci-compat, luci-base, curl, openssl-util, script-utils, tar
Status: install user installed Status: install user installed
Architecture: all Architecture: all
Conffiles: Conffiles: