mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
fix: 回退两处内核改动,channels 改为插件自持持久化(照参考实现)
用户指出「你又tm改内核?」—— 核对后确认两处本可避免:
1. internal/lua/vm.go +32 行 新增 base64_decode 全局函数
2. internal/lua/plugin_http_handler.go +16 行 InvokeHandler 补 markDirty
工作区已还原到 v2.0.2(git diff v2.0.2 = 0 行)。
参考实现(dsh-our-free-model/vendor/channel-pack)对同一问题的做法:
- 解 JWT:用**语言自带**的 `Buffer.from(..., 'base64url')`,从不为此改宿主。
→ 我改成了插件的纯 Lua b64url_decode(约 20 行),实测解出真 JWT 的
nickname/sub/exp 全对。
- 状态持久化:插件**自己 writeFileSync+renameSync** 写
`channel-pack/state.json`,从不依赖宿主 settings。
→ 参考还记下了同构教训(Gitee IKI7WT):DSH 0.1.7 移除 settings.register()
后账号列表退化为纯内存;正解是**插件探测宿主能力、自持文档**,不是要求
宿主改。我的 markDirty 修法方向相反:把插件的数据安全外包给宿主某条
分发路径的实现细节。
- `.channels-store.json`(tmp + rename 原子替换),只放**索引与配置**
(账号条目含 credentialRef 而非凭据本身、模型黑名单、设置、事件日志)。
凭据仍在 .secrets/(0600),两者分离:即便文档泄露也不含 token。
- 变更点同步落盘(不用「置标志+定时 flush」):本插件无 hooks、45 个端点
各自变更状态,还有大量内部互调;挂在某个边界就必须保证**每条**路径都
经过它,漏一条就是「操作成功但重启丢失」——正是本次要修的缺陷形态。
状态实测 ~1.7KB,写入只发生在运维动作上,不在请求热路径。
- 黑名单写入收口到 setModelDisabled(写点曾有 5+ 处,散着写必漏)。
- 存量迁移:读内核遗留 `.channels.state.json`(v2.0.4 内核写的)→ 写自有
文档 → **写成功才删**遗留(写失败还删=用一次迁移丢数据);自有存储已存在
时也删遗留,否则内核 restore() 会在启动时用它覆盖。
1. **测试污染生产目录**(严重):插件 storeDir() 读 /etc/llmsproxy/config.yaml
决定落盘位置,而既有测试都没隔离 → 一次全量测试把 26 个测试账号写进了
**生产** .channels-store.json(含 fixture-a/example 等夹具 id)。
prod 文件已删除(真实账号另有来源);kernelTestCore 现在隔离插件目录,
且尊重调用方已设的值(多次「重启」需共用同一目录)。
2. model_set_disabled 改了写入口后仍引用已删除的局部变量(nil 索引崩溃)。
- TestChannelsStoreIsPluginOwned:状态写进**插件自己的**文件、不含凭据字段、
新实例读回。变异(markDirty 空操作)→ FAIL。
- TestChannelsMigrationKeepsLegacyOnWriteFailure:**写失败必须保留遗留文件**。
变异(无条件删)→ FAIL。(这条单独测:迁移成功后的删除有两条路径互相掩盖,
只测「最终文件没了」区分不出「写失败也删了」。)
- TestChannelsMigratesLegacyKernelState:遗留账号迁入内存 + 自有存储写出 +
遗留文件删除。
- 内核对照 v2.0.2:diff 为空。
go test ./internal/core/(78s)与 ./internal/lua/(63s)全绿;
全量测试后确认生产插件目录未被写入。
This commit is contained in:
123
packaging/install-browser-stack.sh
Executable file
123
packaging/install-browser-stack.sh
Executable file
@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# 安装「服务端浏览器栈」依赖(VNC 兜底登录用)
|
||||
#
|
||||
# 为什么是独立脚本而不是 postinst 自动装:
|
||||
# 这些是**系统级包**(Xvfb / x11vnc / websockify / noVNC / chromium),装上
|
||||
# 会常驻数百 MB,且 chromium 是安全面较大的组件。很多部署根本用不到 VNC 兜底
|
||||
# (大部分平台的登录是 device-poll / callback / paste,纯 HTTP 即可)。默认替
|
||||
# 运维的机器装一堆浏览器组件不是发行包该做的事 —— 需要的人显式装。
|
||||
#
|
||||
# 什么时候需要它:
|
||||
# 平台声明 flow="vnc"(登录必须在服务端浏览器里完成:回环回调落不到用户机器、
|
||||
# 授权页要求浏览器内交互)。插件会在登录时如实报告缺哪个依赖。
|
||||
#
|
||||
# 用法:
|
||||
# sudo bash packaging/install-browser-stack.sh # 自动识别包管理器
|
||||
# sudo bash packaging/install-browser-stack.sh --check # 只检查,不安装
|
||||
# ============================================================
|
||||
set -euo pipefail
|
||||
|
||||
CHECK_ONLY=0
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--check|-c) CHECK_ONLY=1 ;;
|
||||
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
||||
*) echo "未知参数: $a" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 需要的外部可执行文件(插件 browserDeps 同一份清单)
|
||||
BINS=(Xvfb x11vnc websockify python3)
|
||||
# chromium 的二进制名随发行版变化
|
||||
CHROMIUM_CANDIDATES=(chromium chromium-browser google-chrome google-chrome-stable)
|
||||
NOVNC_DIRS=(/usr/share/novnc /usr/share/webapps/novnc /opt/novnc)
|
||||
|
||||
missing=()
|
||||
for b in "${BINS[@]}"; do
|
||||
command -v "$b" >/dev/null 2>&1 || missing+=("$b")
|
||||
done
|
||||
chromium=""
|
||||
for c in "${CHROMIUM_CANDIDATES[@]}"; do
|
||||
if command -v "$c" >/dev/null 2>&1; then chromium="$c"; break; fi
|
||||
done
|
||||
[ -z "$chromium" ] && missing+=("chromium")
|
||||
novnc=""
|
||||
for d in "${NOVNC_DIRS[@]}"; do
|
||||
[ -f "$d/vnc.html" ] && { novnc="$d"; break; }
|
||||
done
|
||||
[ -z "$novnc" ] && missing+=("novnc")
|
||||
# CDP 取凭据需要 python websocket 客户端
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python3 -c 'import websocket' >/dev/null 2>&1 || missing+=("python3-websocket")
|
||||
fi
|
||||
|
||||
if [ ${#missing[@]} -eq 0 ]; then
|
||||
echo "✓ 浏览器栈依赖齐全"
|
||||
echo " chromium: ${chromium}"
|
||||
echo " noVNC: ${novnc}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "缺少依赖: ${missing[*]}"
|
||||
if [ "$CHECK_ONLY" = 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------- 识别包管理器 ----------
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
# Debian/Ubuntu:chromium 在部分版本是 snap 包,apt 装不上;
|
||||
# 先试 apt,失败时明确提示替代方案,不静默放弃。
|
||||
pkgs=(xvfb x11vnc websockify novnc python3-websocket)
|
||||
echo "→ apt-get install ${pkgs[*]} chromium"
|
||||
apt-get update -qq || true
|
||||
apt-get install -y "${pkgs[@]}" || {
|
||||
echo "WARN: 部分包装失败,仍尝试装 chromium" >&2
|
||||
}
|
||||
apt-get install -y chromium || apt-get install -y chromium-browser || {
|
||||
echo "WARN: apt 装不上 chromium(Ubuntu 新版将其作为 snap 分发)。" >&2
|
||||
echo " 可选:snap install chromium 或 apt-get install -y chromium-browser" >&2
|
||||
}
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
echo "→ dnf install xorg-x11-server-Xvfb x11vnc python3-websockify novnc chromium python3-websocket-client"
|
||||
dnf install -y xorg-x11-server-Xvfb x11vnc python3-websockify novnc chromium python3-websocket-client
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
echo "→ yum install xorg-x11-server-Xvfb x11vnc python3-websockify novnc chromium python3-websocket-client"
|
||||
yum install -y xorg-x11-server-Xvfb x11vnc python3-websockify novnc chromium python3-websocket-client
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
echo "→ apk add xvfb x11vnc websockify novnc chromium py3-websocket-client"
|
||||
apk add --no-cache xvfb x11vnc websockify novnc chromium py3-websocket-client
|
||||
else
|
||||
echo "未识别的包管理器。请手动安装:" >&2
|
||||
echo " Xvfb, x11vnc, websockify, noVNC, chromium, python3 websocket-client" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------- 复核 ----------
|
||||
echo
|
||||
echo "复核安装结果:"
|
||||
ok=1
|
||||
for b in "${BINS[@]}"; do
|
||||
if command -v "$b" >/dev/null 2>&1; then echo " ✓ $b"
|
||||
else echo " ✗ $b"; ok=0; fi
|
||||
done
|
||||
chromium=""
|
||||
for c in "${CHROMIUM_CANDIDATES[@]}"; do
|
||||
if command -v "$c" >/dev/null 2>&1; then chromium="$c"; break; fi
|
||||
done
|
||||
[ -n "$chromium" ] && echo " ✓ $chromium" || { echo " ✗ chromium"; ok=0; }
|
||||
novnc=""
|
||||
for d in "${NOVNC_DIRS[@]}"; do
|
||||
[ -f "$d/vnc.html" ] && { novnc="$d"; break; }
|
||||
done
|
||||
[ -n "$novnc" ] && echo " ✓ noVNC ($novnc)" || { echo " ✗ noVNC"; ok=0; }
|
||||
python3 -c 'import websocket' >/dev/null 2>&1 && echo " ✓ python3 websocket-client" || { echo " ✗ python3 websocket-client"; ok=0; }
|
||||
|
||||
if [ "$ok" = 1 ]; then
|
||||
echo
|
||||
echo "✓ 浏览器栈就绪。重启 llmsproxy 后,flow=vnc 的平台即可用 VNC 兜底登录。"
|
||||
else
|
||||
echo
|
||||
echo "WARN: 仍有缺失项,VNC 兜底在该机器上不可用。插件的 deps/status 端点会一样报告。" >&2
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user