mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 10:05:13 +08:00
🎨 Sync 2026-04-10 15:34:26
This commit is contained in:
1 parent
9b1a57d792
commit
42174fc0b0
66 files changed
+6841
-2210
No files matched your search
@@ -77,8 +77,20 @@ rm -f "$_tmpf"
|
||||
|
||||
patch_iframe_headers() {
|
||||
# 移除 OpenClaw 网关的 X-Frame-Options 和 frame-ancestors 限制,允许 LuCI iframe 嵌入
|
||||
# v2026.3.8: 资产路径可能通过符号链接解析,需同时搜索 OC_GLOBAL 和 NODE_BASE
|
||||
local gw_js
|
||||
# v2026.4.x: 安全头设置迁移到 server.impl-*.js 文件
|
||||
local patched=0
|
||||
|
||||
# 1. 搜索 server.impl-*.js (v2026.4.x 新位置)
|
||||
for f in $(find "${OC_GLOBAL}/lib/node_modules/openclaw/dist" -name "server.impl-*.js" -type f 2>/dev/null); do
|
||||
if grep -q "X-Frame-Options.*DENY\|frame-ancestors.*none" "$f" 2>/dev/null; then
|
||||
sed -i 's|res\.setHeader("X-Frame-Options", "DENY")|res.setHeader("X-Frame-Options", "ALLOW-FROM *") // 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"
|
||||
patched=1
|
||||
fi
|
||||
done
|
||||
|
||||
# 2. 兼容旧版本: 搜索 gateway-cli-*.js (v2026.3.x)
|
||||
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
|
||||
@@ -86,14 +98,17 @@ for search_root in "${OC_GLOBAL}" "${NODE_BASE}/lib"; do
|
||||
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"
|
||||
patched=1
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
[ $patched -eq 1 ] && logger -t openclaw "iframe headers patched successfully"
|
||||
}
|
||||
|
||||
sync_uci_to_json() {
|
||||
# 将 UCI 配置同步到 openclaw.json,同时确保 token 双向同步
|
||||
local port bind token
|
||||
local port bind token json_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 "")
|
||||
@@ -104,14 +119,28 @@ if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo '{}' > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# UCI 没有 token 时,尝试从已有的 JSON 读取
|
||||
if [ -z "$token" ] && [ -x "$NODE_BIN" ]; then
|
||||
token=$("$NODE_BIN" -e "
|
||||
# 尝试从 JSON 读取 token (用于双向同步检测)
|
||||
if [ -x "$NODE_BIN" ]; then
|
||||
json_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
|
||||
|
||||
# v2026.4.10: 双向同步策略 - JSON 中的 token 优先
|
||||
# 原因: doctor --fix 或其他操作可能修改 JSON 中的 token
|
||||
# 如果 JSON 有 token 且与 UCI 不同,以 JSON 为准
|
||||
if [ -n "$json_token" ] && [ "$json_token" != "$token" ]; then
|
||||
token="$json_token"
|
||||
logger -t openclaw "Token 同步: JSON -> UCI"
|
||||
fi
|
||||
|
||||
# UCI 和 JSON 都没有 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)
|
||||
logger -t openclaw "Token 生成: 新 token"
|
||||
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)
|
||||
@@ -162,6 +191,37 @@ d.update.checkOnStart=false;
|
||||
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]];});
|
||||
// v2026.4.5: 清理已废弃的配置别名 (BREAKING CHANGE)
|
||||
// 1. talk.* 命名空间废弃 (voiceId, apiKey)
|
||||
if(d.talk){delete d.talk.voiceId;delete d.talk.apiKey;}
|
||||
// 2. browser.ssrfPolicy.allowPrivateNetwork 废弃
|
||||
if(d.browser&&d.browser.ssrfPolicy){delete d.browser.ssrfPolicy.allowPrivateNetwork;}
|
||||
// 3. hooks.internal.handlers 废弃
|
||||
if(d.hooks&&d.hooks.internal){delete d.hooks.internal.handlers;}
|
||||
// 4. channel/group/room 的 allow 字段迁移到 enabled
|
||||
['channel','group','room'].forEach(k=>{if(d[k]){Object.keys(d[k]).forEach(id=>{if(d[k][id]&&typeof d[k][id].allow!=='undefined'){d[k][id].enabled=d[k][id].allow;delete d[k][id].allow;}});}});
|
||||
// 5. agents.defaults.cliBackends 废弃
|
||||
if(d.agents&&d.agents.defaults){delete d.agents.defaults.cliBackends;}
|
||||
// v2026.4.9: 自动同步 plugins.allow (解决 "plugins.allow is empty" 警告)
|
||||
// 扫描已安装的插件并添加到 allow 列表
|
||||
if(!d.plugins)d.plugins={};
|
||||
if(!Array.isArray(d.plugins.allow))d.plugins.allow=[];
|
||||
// 从 plugins.installs 中提取已安装插件
|
||||
// 注意: 使用 installPath 中的目录名作为插件 ID (与 openclaw.plugin.json 中的 id 一致)
|
||||
if(d.plugins.installs){
|
||||
Object.keys(d.plugins.installs).forEach(key=>{
|
||||
const inst=d.plugins.installs[key];
|
||||
if(inst&&inst.installPath){
|
||||
// 从 installPath 提取插件目录名 (如 openclaw-weixin)
|
||||
const match=inst.installPath.match(/\/([^\/]+)\/?$/);
|
||||
if(match&&match[1]&&!d.plugins.allow.includes(match[1])){
|
||||
d.plugins.allow.push(match[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 始终信任 copilot-proxy (内置插件)
|
||||
if(!d.plugins.allow.includes('copilot-proxy'))d.plugins.allow.push('copilot-proxy');
|
||||
fs.writeFileSync(f,JSON.stringify(d,null,2));
|
||||
" 2>/dev/null
|
||||
fi
|
||||
@@ -252,6 +312,98 @@ fi
|
||||
# 修复数据目录权限 (防止 root 用户操作后留下无法读取的文件)
|
||||
chown -R openclaw:openclaw "$OC_DATA" 2>/dev/null || true
|
||||
|
||||
# v2026.4.9: 修复插件目录权限 (OpenClaw 要求插件目录属主为 root)
|
||||
# 详见: https://github.com/nicepkg/openclaw/releases/tag/v2026.4.9
|
||||
local ext_dir="${OC_DATA}/.openclaw/extensions"
|
||||
if [ -d "$ext_dir" ]; then
|
||||
chown -R root:root "$ext_dir" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# v2026.4.x: 清理 jiti 缓存目录 (修复微信插件加载权限问题)
|
||||
# jiti 编译 TypeScript 时会在 /tmp/jiti 创建缓存,如果由 root 创建则 openclaw 用户无法写入
|
||||
rm -rf /tmp/jiti 2>/dev/null || true
|
||||
|
||||
# v2026.4.5: 执行 OpenClaw 官方配置迁移工具
|
||||
# 自动迁移遗留配置到新版本格式
|
||||
_run_config_migration() {
|
||||
local oc_entry="$1"
|
||||
[ -z "$oc_entry" ] && return
|
||||
logger -t openclaw "执行配置迁移 (doctor --fix)..."
|
||||
OPENCLAW_HOME="$OC_DATA" OPENCLAW_CONFIG_PATH="$CONFIG_FILE" \
|
||||
"$NODE_BIN" "$oc_entry" doctor --fix 2>/dev/null && \
|
||||
logger -t openclaw "配置迁移完成" || \
|
||||
logger -t openclaw "配置迁移失败,请手动检查"
|
||||
}
|
||||
_run_config_migration "$oc_entry"
|
||||
|
||||
# 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
|
||||
|
||||
# v2026.4.10: doctor --fix 后确保关键配置字段存在
|
||||
# doctor --fix 可能会删除或遗漏某些必要字段,需要补充
|
||||
_ensure_critical_config() {
|
||||
[ ! -f "$CONFIG_FILE" ] && return
|
||||
[ ! -x "$NODE_BIN" ] && return
|
||||
"$NODE_BIN" -e "
|
||||
const fs = require('fs');
|
||||
const f = '${CONFIG_FILE}';
|
||||
let d = {};
|
||||
try { d = JSON.parse(fs.readFileSync(f, 'utf8')); } catch(e) {}
|
||||
|
||||
// 确保 gateway.mode 存在
|
||||
if (!d.gateway) d.gateway = {};
|
||||
if (!d.gateway.mode) d.gateway.mode = 'local';
|
||||
|
||||
// 确保端口和绑定配置
|
||||
if (!d.gateway.port) d.gateway.port = 18789;
|
||||
if (!d.gateway.bind) d.gateway.bind = 'lan';
|
||||
|
||||
// 确保 controlUi 安全配置存在 (允许非 loopback 绑定和 LuCI iframe 嵌入)
|
||||
if (!d.gateway.controlUi) d.gateway.controlUi = {};
|
||||
if (d.gateway.controlUi.allowInsecureAuth !== true) d.gateway.controlUi.allowInsecureAuth = true;
|
||||
if (d.gateway.controlUi.dangerouslyDisableDeviceAuth !== true) d.gateway.controlUi.dangerouslyDisableDeviceAuth = true;
|
||||
if (d.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback !== true) d.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback = true;
|
||||
|
||||
// 确保认证配置
|
||||
if (!d.gateway.auth) d.gateway.auth = {};
|
||||
if (!d.gateway.auth.mode) d.gateway.auth.mode = 'token';
|
||||
|
||||
// 禁用 ACP dispatch (路由器内存有限)
|
||||
if (!d.acp) d.acp = {};
|
||||
if (!d.acp.dispatch) d.acp.dispatch = {};
|
||||
d.acp.dispatch.enabled = false;
|
||||
|
||||
// 工具配置
|
||||
if (!d.tools) d.tools = {};
|
||||
d.tools.profile = 'coding';
|
||||
|
||||
// 禁用更新检查
|
||||
if (!d.update) d.update = {};
|
||||
d.update.checkOnStart = false;
|
||||
|
||||
fs.writeFileSync(f, JSON.stringify(d, null, 2));
|
||||
" 2>/dev/null
|
||||
chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
|
||||
}
|
||||
_ensure_critical_config
|
||||
|
||||
# v2026.4.10: 最终权限修复 - 确保 Gateway 启动前所有数据目录权限正确
|
||||
# doctor --fix 或 _ensure_critical_config 可能创建新的目录/文件
|
||||
# 关键: agents 目录下的 agent 子目录必须可被 openclaw 用户写入
|
||||
find "${OC_STATE_DIR}" -user root -type d -exec chown openclaw:openclaw {} \; 2>/dev/null || true
|
||||
find "${OC_STATE_DIR}" -user root -type f -exec chown openclaw:openclaw {} \; 2>/dev/null || true
|
||||
# 特别确保 agents/main/agent 目录权限 (模型配置写入目录)
|
||||
local agent_dir="${OC_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
|
||||
# extensions 目录除外 - 必须保持 root 权限 (OpenClaw 安全要求)
|
||||
if [ -d "${OC_STATE_DIR}/extensions" ]; then
|
||||
chown -R root:root "${OC_STATE_DIR}/extensions" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Patch iframe 安全头,允许 LuCI 嵌入
|
||||
patch_iframe_headers
|
||||
|
||||
@@ -305,19 +457,32 @@ _ensure_port_free() {
|
||||
_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"
|
||||
# v2026.4.7/4.9: 环境变量安全验证 (阻止危险变量注入)
|
||||
_validate_path() {
|
||||
local p="$1"
|
||||
case "$p" in
|
||||
*'$'*|*'`'*|*'|'*|*'&'*) return 1 ;;
|
||||
/proc/*|/sys/*|/dev/*) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
for _dir in "$NODE_BASE" "$OC_GLOBAL" "$OC_DATA"; do
|
||||
_validate_path "$_dir" || { logger -t openclaw "安全警告: 路径验证失败: $_dir"; return 1; }
|
||||
done
|
||||
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user