Files
luci-app-openclaw/root/etc/init.d/openclaw

331 lines
9.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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"
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
local d
echo "$search_dirs" | while read -r d; do
[ -z "$d" ] && continue
if [ -f "${d}/openclaw.mjs" ]; then
echo "${d}/openclaw.mjs"
return
elif [ -f "${d}/dist/cli.js" ]; then
echo "${d}/dist/cli.js"
return
fi
done
}
patch_iframe_headers() {
# 移除 OpenClaw 网关的 X-Frame-Options 和 frame-ancestors 限制,允许 LuCI iframe 嵌入
local gw_js
for f in $(find "${OC_GLOBAL}" -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
}
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 "")
# 确保配置目录和文件存在
mkdir -p "$(dirname "$CONFIG_FILE")"
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 || echo "auto_$(date +%s)")
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;
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
# 启动 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_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 5 0
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 用于 WebSocket 认证
local pty_token
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
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_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 会自动处理进程停止
return 0
}
service_triggers() {
procd_add_reload_trigger "openclaw"
}
reload_service() {
stop
start
}
setup() {
echo "正在调用 openclaw-env setup..."
/usr/bin/openclaw-env setup
}
restart_gateway() {
# 仅重启 Gateway procd 实例,不影响 Web PTY
# 使用 stop+start 而非 kill可重置 procd crash loop 计数器
local port
port=$(uci -q get openclaw.main.port || echo "18789")
# 停止 gateway 实例 (通过 ubus不影响 PTY 实例)
ubus call service stop '{"name":"openclaw","instance":"gateway"}' 2>/dev/null || true
# 等待端口释放
local i=0
while [ $i -lt 8 ]; do
netstat -tln 2>/dev/null | grep -q ":${port} " || break
sleep 1; i=$((i+1))
done
# 重新启动整个服务 (procd 会重建 gateway 实例,同时保留 PTY 实例)
# procd 的 start_service 幂等:已运行的实例不会重复启动
/etc/init.d/openclaw start >/dev/null 2>&1
}
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 -tlnp 2>/dev/null | grep -q ":${p} "
else
netstat -tlnp 2>/dev/null | grep -q ":${p} "
fi
}
_get_pid_by_port() {
local p="$1"
if command -v ss >/dev/null 2>&1; then
ss -tlnp 2>/dev/null | grep ":${p} " | head -1 | sed -n 's/.*pid=\([0-9]*\).*/\1/p'
else
netstat -tlnp 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
else
echo "网关: 已停止"
fi
# PTY port
if _check_port "$pty_port"; then
echo "Web PTY: 运行中 (端口 $pty_port)"
else
echo "Web PTY: 已停止"
fi
}