mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 16:44:23 +08:00
✨ Sync 2026-03-14 19:52:12
This commit is contained in:
1 parent
364f734d16
commit
2d75925beb
43 files changed
+7870
-35
No files matched your search
Executable
+487
@@ -0,0 +1,487 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# luci-app-openclaw — procd init 脚本
|
||||
|
||||
USE_PROCD=1
|
||||
START=99
|
||||
STOP=10
|
||||
|
||||
EXTRA_COMMANDS="setup status_service restart_gateway"
|
||||
EXTRA_HELP=" setup 下载 Node.js 并安装 OpenClaw
|
||||
status_service 显示服务状态
|
||||
restart_gateway 仅重启 Gateway 实例 (不影响 Web PTY)"
|
||||
|
||||
NODE_BASE="/opt/openclaw/node"
|
||||
OC_GLOBAL="/opt/openclaw/global"
|
||||
OC_DATA="/opt/openclaw/data"
|
||||
|
||||
# ── OverlayFS 兼容性修复 ──
|
||||
# Docker bind mount (/overlay/upper/opt/docker) 会导致 /opt 不可写
|
||||
# 解决: bind mount upper 层的 /opt 到合并视图的 /opt
|
||||
_oc_fix_opt() {
|
||||
mkdir -p /opt/openclaw/.probe 2>/dev/null && { rmdir /opt/openclaw/.probe 2>/dev/null; return 0; }
|
||||
if [ -d /overlay/upper/opt ]; then
|
||||
mkdir -p /overlay/upper/opt/openclaw 2>/dev/null
|
||||
mount --bind /overlay/upper/opt /opt 2>/dev/null && return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
_oc_fix_opt
|
||||
|
||||
NODE_BIN="${NODE_BASE}/bin/node"
|
||||
CONFIG_FILE="${OC_DATA}/.openclaw/openclaw.json"
|
||||
|
||||
get_oc_entry() {
|
||||
local search_dirs="${OC_GLOBAL}/lib/node_modules/openclaw
|
||||
${OC_GLOBAL}/node_modules/openclaw
|
||||
${NODE_BASE}/lib/node_modules/openclaw"
|
||||
|
||||
# pnpm 全局安装路径形如: $OC_GLOBAL/5/node_modules/openclaw
|
||||
for ver_dir in "${OC_GLOBAL}"/*/node_modules/openclaw; do
|
||||
[ -d "$ver_dir" ] && search_dirs="$search_dirs
|
||||
$ver_dir"
|
||||
done
|
||||
|
||||
# v2026.3.8: 跟随符号链接解析真实包路径 (pnpm store / symlinked wrappers)
|
||||
for link_dir in "${OC_GLOBAL}/lib/node_modules/openclaw" "${OC_GLOBAL}/node_modules/openclaw"; do
|
||||
[ -L "$link_dir" ] && {
|
||||
local real_dir=$(readlink -f "$link_dir" 2>/dev/null)
|
||||
[ -n "$real_dir" ] && [ -d "$real_dir" ] && search_dirs="$search_dirs
|
||||
$real_dir"
|
||||
}
|
||||
done
|
||||
|
||||
local d _tmpf
|
||||
_tmpf=$(mktemp)
|
||||
echo "$search_dirs" > "$_tmpf"
|
||||
while read -r d; do
|
||||
[ -z "$d" ] && continue
|
||||
if [ -f "${d}/openclaw.mjs" ]; then
|
||||
echo "${d}/openclaw.mjs"
|
||||
rm -f "$_tmpf"
|
||||
return
|
||||
elif [ -f "${d}/dist/cli.js" ]; then
|
||||
echo "${d}/dist/cli.js"
|
||||
rm -f "$_tmpf"
|
||||
return
|
||||
fi
|
||||
done < "$_tmpf"
|
||||
rm -f "$_tmpf"
|
||||
}
|
||||
|
||||
patch_iframe_headers() {
|
||||
# 移除 OpenClaw 网关的 X-Frame-Options 和 frame-ancestors 限制,允许 LuCI iframe 嵌入
|
||||
# v2026.3.8: 资产路径可能通过符号链接解析,需同时搜索 OC_GLOBAL 和 NODE_BASE
|
||||
local gw_js
|
||||
for search_root in "${OC_GLOBAL}" "${NODE_BASE}/lib"; do
|
||||
[ -d "$search_root" ] || continue
|
||||
for f in $(find "$search_root" -name "gateway-cli-*.js" -type f 2>/dev/null); do
|
||||
if grep -q "X-Frame-Options.*DENY" "$f" 2>/dev/null; then
|
||||
sed -i "s|res.setHeader(\"X-Frame-Options\", \"DENY\");|// res.setHeader(\"X-Frame-Options\", \"DENY\"); // patched by luci-app-openclaw|g" "$f"
|
||||
sed -i "s|\"frame-ancestors 'none'\"|\"frame-ancestors *\"|g" "$f"
|
||||
logger -t openclaw "Patched iframe headers in $f"
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
sync_uci_to_json() {
|
||||
# 将 UCI 配置同步到 openclaw.json,同时确保 token 双向同步
|
||||
local port bind token
|
||||
port=$(uci -q get openclaw.main.port || echo "18789")
|
||||
bind=$(uci -q get openclaw.main.bind || echo "lan")
|
||||
token=$(uci -q get openclaw.main.token || echo "")
|
||||
|
||||
# 确保配置目录和文件存在 (路径已在脚本开头做 OverlayFS 重映射)
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")" 2>/dev/null
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo '{}' > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# UCI 没有 token 时,尝试从已有的 JSON 读取
|
||||
if [ -z "$token" ] && [ -x "$NODE_BIN" ]; then
|
||||
token=$("$NODE_BIN" -e "
|
||||
try{const d=JSON.parse(require('fs').readFileSync('${CONFIG_FILE}','utf8'));
|
||||
if(d.gateway&&d.gateway.auth&&d.gateway.auth.token)process.stdout.write(d.gateway.auth.token);}catch(e){}
|
||||
" 2>/dev/null)
|
||||
fi
|
||||
|
||||
# 如果仍然没有 token,生成一个新的
|
||||
if [ -z "$token" ]; then
|
||||
token=$(head -c 24 /dev/urandom | hexdump -e '24/1 "%02x"' 2>/dev/null || openssl rand -hex 24 2>/dev/null || dd if=/dev/urandom bs=24 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n' | head -c 48)
|
||||
fi
|
||||
|
||||
# 确保 token 写回 UCI
|
||||
local uci_token
|
||||
uci_token=$(uci -q get openclaw.main.token || echo "")
|
||||
if [ "$uci_token" != "$token" ]; then
|
||||
uci set openclaw.main.token="$token"
|
||||
uci commit openclaw 2>/dev/null
|
||||
fi
|
||||
|
||||
# 使用 Node.js 写入 JSON (如果可用)
|
||||
if [ -x "$NODE_BIN" ]; then
|
||||
OC_SYNC_PORT="$port" OC_SYNC_BIND="$bind" OC_SYNC_TOKEN="$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={};
|
||||
d.gateway.port=parseInt(process.env.OC_SYNC_PORT)||18789;
|
||||
d.gateway.bind=process.env.OC_SYNC_BIND||'lan';
|
||||
d.gateway.mode='local';
|
||||
if(!d.gateway.auth)d.gateway.auth={};
|
||||
d.gateway.auth.mode='token';
|
||||
d.gateway.auth.token=process.env.OC_SYNC_TOKEN||'';
|
||||
if(!d.gateway.controlUi)d.gateway.controlUi={};
|
||||
d.gateway.controlUi.allowInsecureAuth=true;
|
||||
d.gateway.controlUi.dangerouslyDisableDeviceAuth=true;
|
||||
d.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true;
|
||||
// 清理 v2026.3.1+ 已废弃的字段,避免配置验证失败
|
||||
delete d.gateway.name;
|
||||
delete d.gateway.bonjour;
|
||||
delete d.gateway.plugins;
|
||||
// v2026.3.2: 禁用 ACP dispatch 防止路由器内存溢出
|
||||
if(!d.acp)d.acp={};
|
||||
if(!d.acp.dispatch)d.acp.dispatch={};
|
||||
d.acp.dispatch.enabled=false;
|
||||
// v2026.3.2: tools.profile 默认改为 messaging,路由器场景需强制 coding
|
||||
if(!d.tools)d.tools={};
|
||||
d.tools.profile='coding';
|
||||
// 禁用 OpenClaw 内置更新检查,防止 Control UI 显示升级横幅
|
||||
if(!d.update)d.update={};
|
||||
d.update.checkOnStart=false;
|
||||
// v2026.3.2: 迁移 Ollama provider 到原生 API
|
||||
if(d.models&&d.models.providers&&d.models.providers.ollama){const ol=d.models.providers.ollama;if(ol.api==='openai-chat-completions'||ol.api==='openai-completions')ol.api='ollama';if(ol.baseUrl&&ol.baseUrl.endsWith('/v1'))ol.baseUrl=ol.baseUrl.replace(/\/v1$/,'');if(ol.apiKey==='ollama')ol.apiKey='ollama-local';}
|
||||
// v2026.3.7: 清理已废弃的顶层配置键 (loadConfig() 现在会严格校验)
|
||||
['cli','commands.native','commands.nativeSkills','commands.ownerDisplay'].forEach(k=>{const ks=k.split('.');let o=d;for(let i=0;i<ks.length-1;i++){if(!o[ks[i]])return;o=o[ks[i]];}delete o[ks[ks.length-1]];});
|
||||
fs.writeFileSync(f,JSON.stringify(d,null,2));
|
||||
" 2>/dev/null
|
||||
fi
|
||||
|
||||
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local enabled port bind pty_port
|
||||
config_load openclaw
|
||||
config_get enabled main enabled "0"
|
||||
config_get port main port "18789"
|
||||
config_get bind main bind "lan"
|
||||
config_get pty_port main pty_port "18793"
|
||||
|
||||
[ "$enabled" = "1" ] || {
|
||||
echo "openclaw 已禁用。请在 /etc/config/openclaw 中设置 enabled 为 1"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 检查 Node.js
|
||||
if [ ! -x "$NODE_BIN" ]; then
|
||||
echo "未找到 Node.js: $NODE_BIN"
|
||||
echo "请运行: openclaw-env setup"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查 openclaw 入口
|
||||
local oc_entry
|
||||
oc_entry=$(get_oc_entry)
|
||||
if [ -z "$oc_entry" ]; then
|
||||
echo "OpenClaw 未安装。请运行: openclaw-env setup"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 同步 UCI 到 JSON
|
||||
sync_uci_to_json
|
||||
|
||||
# 修复数据目录权限 (防止 root 用户操作后留下无法读取的文件)
|
||||
chown -R openclaw:openclaw "$OC_DATA" 2>/dev/null || true
|
||||
|
||||
# Patch iframe 安全头,允许 LuCI 嵌入
|
||||
patch_iframe_headers
|
||||
|
||||
# 将 UCI bind 映射到 openclaw gateway --bind 参数
|
||||
local gw_bind="loopback"
|
||||
case "$bind" in
|
||||
lan) gw_bind="lan" ;;
|
||||
loopback) gw_bind="loopback" ;;
|
||||
all) gw_bind="custom" ;; # custom = 0.0.0.0
|
||||
*) gw_bind="$bind" ;;
|
||||
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 -tulnp 2>/dev/null | grep -q ":${p} " || return 0
|
||||
else
|
||||
netstat -tulnp 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 -tulnp 2>/dev/null | grep ":${p} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)
|
||||
else
|
||||
port_pid=$(netstat -tulnp 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 (主服务, 前台运行)
|
||||
procd_open_instance "gateway"
|
||||
procd_set_param command "$NODE_BIN" "$oc_entry" gateway run \
|
||||
--port "$port" --bind "$gw_bind"
|
||||
procd_set_param env \
|
||||
HOME="$OC_DATA" \
|
||||
OPENCLAW_HOME="$OC_DATA" \
|
||||
OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
|
||||
OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \
|
||||
NODE_ICU_DATA="${NODE_BASE}/share/icu" \
|
||||
NODE_BASE="$NODE_BASE" \
|
||||
OC_GLOBAL="$OC_GLOBAL" \
|
||||
OC_DATA="$OC_DATA" \
|
||||
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 respawn 3600 10 5
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param pidfile /var/run/openclaw.pid
|
||||
procd_close_instance
|
||||
|
||||
# 启动 Web PTY 配置终端 (辅助服务)
|
||||
# 复用已有 PTY token,只在不存在时才生成新的
|
||||
# 这样避免服务重启时 PTY WebSocket 连接因 token 变化而断开
|
||||
local pty_token
|
||||
pty_token=$(uci -q get openclaw.main.pty_token || echo "")
|
||||
if [ -z "$pty_token" ]; then
|
||||
pty_token=$(head -c 16 /dev/urandom | hexdump -e '16/1 "%02x"' 2>/dev/null || openssl rand -hex 16 2>/dev/null || echo "pty_$(date +%s)")
|
||||
uci set openclaw.main.pty_token="$pty_token"
|
||||
uci commit openclaw 2>/dev/null
|
||||
fi
|
||||
|
||||
procd_open_instance "pty"
|
||||
procd_set_param command "$NODE_BIN" /usr/share/openclaw/web-pty.js
|
||||
procd_set_param env \
|
||||
OC_CONFIG_PORT="$pty_port" \
|
||||
OC_PTY_TOKEN="$pty_token" \
|
||||
OC_CONFIG_SCRIPT="/usr/share/openclaw/oc-config.sh" \
|
||||
NODE_ICU_DATA="${NODE_BASE}/share/icu" \
|
||||
NODE_BASE="$NODE_BASE" \
|
||||
OC_GLOBAL="$OC_GLOBAL" \
|
||||
OC_DATA="$OC_DATA" \
|
||||
HOME="$OC_DATA" \
|
||||
OPENCLAW_HOME="$OC_DATA" \
|
||||
OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
|
||||
OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \
|
||||
PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
procd_set_param respawn 3600 5 5
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param pidfile /var/run/openclaw-pty.pid
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
# procd 会自动停止它管理的主进程 (openclaw)
|
||||
# 但 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 -tulnp 2>/dev/null | grep -q ":${port} " || break
|
||||
else
|
||||
netstat -tulnp 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 -tulnp 2>/dev/null | grep ":${port} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)
|
||||
else
|
||||
port_pid=$(netstat -tulnp 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() {
|
||||
procd_add_reload_trigger "openclaw"
|
||||
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
# stop_service 已确保端口释放,但额外等待 1 秒让内核回收
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
setup() {
|
||||
echo "正在调用 openclaw-env setup..."
|
||||
/usr/bin/openclaw-env setup
|
||||
}
|
||||
|
||||
restart_gateway() {
|
||||
# 仅重启 Gateway 进程,通过 kill 触发 procd respawn
|
||||
# 绝对不能调用 start_service,否则会重启 PTY 实例
|
||||
|
||||
local port
|
||||
port=$(uci -q get openclaw.main.port || echo "18789")
|
||||
|
||||
# ── 第一步: kill 监听端口的 gateway 子进程 (openclaw-gateway) ──
|
||||
# openclaw 启动后会 fork 出 openclaw-gateway 子进程实际监听端口
|
||||
# 必须先杀子进程释放端口,否则 procd respawn 的新实例会因端口冲突而崩溃
|
||||
local port_pid=""
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
port_pid=$(ss -tulnp 2>/dev/null | grep ":${port} " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)
|
||||
else
|
||||
port_pid=$(netstat -tulnp 2>/dev/null | grep ":${port} " | sed -n 's|.* \([0-9]*\)/.*|\1|p' | head -1)
|
||||
fi
|
||||
[ -n "$port_pid" ] && kill "$port_pid" 2>/dev/null
|
||||
|
||||
# ── 第二步: kill procd 管理的 gateway 主进程 (openclaw) ──
|
||||
# procd 追踪的是主进程 PID,kill 它才能触发 respawn
|
||||
local gw_pid=""
|
||||
gw_pid=$(ubus call service list '{"name":"openclaw"}' 2>/dev/null | \
|
||||
jsonfilter -e '$.openclaw.instances.gateway.pid' 2>/dev/null) || true
|
||||
|
||||
if [ -n "$gw_pid" ] && kill -0 "$gw_pid" 2>/dev/null; then
|
||||
kill "$gw_pid" 2>/dev/null
|
||||
fi
|
||||
|
||||
# ── 第三步: 兜底 — kill 所有 openclaw gateway 相关残留进程 ──
|
||||
# 避免任何残留进程占据端口
|
||||
sleep 1
|
||||
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
|
||||
|
||||
# ── 第四步: 等待端口真正释放 (最多 5 秒) ──
|
||||
local wait_count=0
|
||||
while [ $wait_count -lt 5 ]; do
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
ss -tulnp 2>/dev/null | grep -q ":${port} " || break
|
||||
else
|
||||
netstat -tulnp 2>/dev/null | grep -q ":${port} " || break
|
||||
fi
|
||||
sleep 1
|
||||
wait_count=$((wait_count + 1))
|
||||
done
|
||||
|
||||
# ── 第五步: 如果 procd 中没有 gateway 服务注册 (首次/崩溃),调用 start ──
|
||||
if [ -z "$gw_pid" ]; then
|
||||
/etc/init.d/openclaw start >/dev/null 2>&1
|
||||
fi
|
||||
# 如果 gw_pid 存在,procd respawn 会自动重启 gateway
|
||||
}
|
||||
|
||||
status_service() {
|
||||
local port pty_port
|
||||
port=$(uci -q get openclaw.main.port || echo "18789")
|
||||
pty_port=$(uci -q get openclaw.main.pty_port || echo "18793")
|
||||
|
||||
echo "=== OpenClaw 服务状态 ==="
|
||||
|
||||
# Node.js
|
||||
if [ -x "$NODE_BIN" ]; then
|
||||
echo "Node.js: $($NODE_BIN --version 2>/dev/null)"
|
||||
else
|
||||
echo "Node.js: 未安装"
|
||||
fi
|
||||
|
||||
# OpenClaw
|
||||
local oc_entry
|
||||
oc_entry=$(get_oc_entry)
|
||||
if [ -n "$oc_entry" ]; then
|
||||
local ver
|
||||
ver=$("$NODE_BIN" "$oc_entry" --version 2>/dev/null | tr -d '[:space:]')
|
||||
echo "OpenClaw: v${ver:-未知}"
|
||||
else
|
||||
echo "OpenClaw: 未安装"
|
||||
fi
|
||||
|
||||
# 端口检测函数 (ss 优先, 回退 netstat)
|
||||
_check_port() {
|
||||
local p="$1"
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
ss -tulnp 2>/dev/null | grep -q ":${p} "
|
||||
else
|
||||
netstat -tulnp 2>/dev/null | grep -q ":${p} "
|
||||
fi
|
||||
}
|
||||
|
||||
_get_pid_by_port() {
|
||||
local p="$1"
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
ss -tulnp 2>/dev/null | grep ":${p} " | head -1 | sed -n 's/.*pid=\([0-9]*\).*/\1/p'
|
||||
else
|
||||
netstat -tulnp 2>/dev/null | grep ":${p} " | head -1 | sed 's|.* \([0-9]*\)/.*|\1|'
|
||||
fi
|
||||
}
|
||||
|
||||
# Gateway port
|
||||
if _check_port "$port"; then
|
||||
echo "网关: 运行中 (端口 $port)"
|
||||
# PID
|
||||
local pid
|
||||
pid=$(_get_pid_by_port "$port")
|
||||
if [ -n "$pid" ]; then
|
||||
echo "进程ID: $pid"
|
||||
# Memory
|
||||
local rss
|
||||
rss=$(awk '/VmRSS/{print $2}' /proc/$pid/status 2>/dev/null)
|
||||
[ -n "$rss" ] && echo "内存: ${rss} kB"
|
||||
# Uptime
|
||||
local start_time now_time
|
||||
start_time=$(stat -c %Y /proc/$pid 2>/dev/null || echo "0")
|
||||
now_time=$(date +%s)
|
||||
if [ "$start_time" -gt 0 ]; then
|
||||
local uptime=$((now_time - start_time))
|
||||
local hours=$((uptime / 3600))
|
||||
local mins=$(( (uptime % 3600) / 60 ))
|
||||
echo "运行时间: ${hours}小时 ${mins}分钟"
|
||||
fi
|
||||
fi
|
||||
elif pgrep -f "openclaw.*gateway" >/dev/null 2>&1; then
|
||||
echo "网关: 正在启动 (首次启动可能需要 2~5 分钟)"
|
||||
else
|
||||
echo "网关: 已停止"
|
||||
fi
|
||||
|
||||
# PTY port
|
||||
if _check_port "$pty_port"; then
|
||||
echo "Web PTY: 运行中 (端口 $pty_port)"
|
||||
else
|
||||
echo "Web PTY: 已停止"
|
||||
fi
|
||||
}
|
||||
Reference in new issue
Block a user