|
|
688dce88d1
|
refactor(rename)!: llmsproxy → modelrouter(module / 二进制 / 路径 / env)
产品名早已是 ModelRouter,但仓库标识符仍沿用旧名。这次统一,读写两侧都做兼容,
已部署的机器不会因为顺序问题立刻失效。
- go module `llmsproxy` → `modelrouter`;全仓 import 与 `cmd/modelrouter` 同步。
- `Makefile BINARY=modelrouter`;发行包名、systemd unit、/usr/share 资源、
`/usr/local/bin` 目标全部改为 modelrouter。
- 默认配置目录 `/etc/llmsproxy` → `/etc/modelrouter`。**读取侧回退**:插件与
`storeDir` 仍会检查旧路径,所以"先换二进制、后改 unit"不会立即失效。
- 环境变量 `LLMSPROXY_*`(PLUGIN_DIR / XDISPLAY_BASE / ARCH / LIVE_ZEN /
BUILD_DIR)改为 `MODELROUTER_*`;`LLMS_PROXY_MASTER_KEY` →
`MODELROUTER_MASTER_KEY`。旧名在读取侧继续接受。
- 桌面端内嵌核心 `bin/llmsproxy` → `bin/modelrouter`,并保留旧文件名回退,
避免升级到一半的 profile 被判成"core binary missing"。
- 新增 `packaging/migrate-to-modelrouter.sh`:**默认 dry-run**,复制而非移动、
旧目录永不删除、支持 `--rollback`;随 release tar 分发并由 core-dist 校验。
- `deploy.sh` 增加改名保护:发现旧 `/etc/llmsproxy` 而新配置不存在时**中止**,
否则会部署出一个"能启动但没有 key、没有源"的空实例。
- 临时目录前缀 `/tmp/llmsproxy-*` → `/tmp/modelrouter-*`;清理逻辑同时回收两代。
兼容别名(`__llmsproxy_*` 全局、`llmsproxy` SDK 别名、旧路径/旧 env)刻意保留,
它们是升级路径而非遗留;docs/plugins.md 已标注为废弃。
本提交未包含:`adapters/opencodezen.lua`、`adapter_opencodezen_test.go`、
`adapter_opencodezen_live_test.go`(OpenCode UA/免费池的既有改动,另批提交)。
|
2026-10-26 22:28:08 +08:00 |
|
|
|
68446fb6fe
|
fix(vnc): noVNC 的 path 必须用相对名,否则 WebSocket 路径重复拼接
真实缺陷(白屏真因之一):view_url 里传了**站点根路径**作为 noVNC 的
path 参数:
.../proxy/<id>/vnc.html?path=api%2Fplugins%2Fhttp%2Fchannels%2Fbrowser%2Fproxy%2F<id>%2Fwebsockify
但 noVNC 用 `new URL(path, location.href)` 解析它(app/ui.js 的 connect()),
即**相对 vnc.html 所在目录**解析,默认值就是相对名 "websockify"。传根路径
会被当相对路径 ⇒ 实际请求变成路径重复拼接:
.../proxy/<id>/api/plugins/http/channels/browser/proxy/<id>/websockify
于是 WebSocket 永远连不上,noVNC 显示连接失败,页面看似白屏。
修法:只传相对名 path=websockify,与 noVNC 默认值一致。
★ 判据:必须解析**产品自己给出的 URL**(而不是测试里手拼)。我上一轮验证时
手拼 URL 并省掉了 path 参数(默认值恰好正确)⇒ 掩盖了这个真实缺陷。
新判据断言:path 存在、不以 / 开头、且 ResolveReference 后等于同级 websockify。
|
2026-10-15 20:30:33 +08:00 |
|
|
|
7a8d97001c
|
feat(插件代理): 同源反向代理 —— VNC 不再暴露额外端口
问题:登录返回的 view_url 是 http://127.0.0.1:60100/vnc.html,WebUI 直接把它
嵌进 iframe。远程运维打开必白屏 —— 127.0.0.1 在客户端指向客户端自己,且
websockify/x11vnc 只绑回环,换服务器 IP 也连不上(实测 LAN IP 访问 000)。
★ 教训:只验证"服务端截图有画面"就宣布 VNC 可用是不充分的。四层链路里
①X 渲染 ②noVNC 客户端渲染 一直是好的,白屏真因在第④层「客户端如何拿到地址」。
内核新增通用「插件前缀代理」能力(不是为 channels 写的专用反向代理):
- plugin.http 支持 prefix = true:声明前缀路由,承接所有子路径
- handler 返回 { proxy = "http://127.0.0.1:PORT" },内核每次请求都问一遍
⇒ 会话停止/过期后插件不再给目标,旧地址自然失效(撤销点唯一)
- 鉴权与角色门禁复用现有链路,无旁路
- statusRecorder 增加 Unwrap(),让 Hijack 穿透访问日志中间件(否则 WebSocket 升级失败)
- 目标只允许带显式端口的**字面回环 IP**(用户拍板方案 A):拒绝 https、localhost
主机名、无端口、带 userinfo/path/query;理由是主机名可能被 hosts/DNS 改写
- 拒绝跨站 Origin(WebSocket 带环境 Cookie,否则第三方网页可借管理员会话开隧道)
- 不向插件后端转发 Authorization/Cookie/api_key
channels 接入:
- 新增 channels/browser/proxy/:session(prefix=true)
- 会话代号 = seq-随机8位,防猜到活跃会话
- view_url 改为网关同源路径,noVNC 的 WebSocket 走同前缀 /websockify
- 会话过期即回收浏览器栈并返回 404
判据(每条咬真实外部效应):
- 静态资源转发
- 真实 HTTP 101 升级 + 双向数据回传(只断言静态转发不够 —— 升级是白屏真考点)
- 网关凭据不泄漏(在服务端断言请求头)
- 非法目标全拒(公网/localhost/https/无端口/带 path、query、userinfo)
- 跨站 Origin 403
- 未鉴权 401、过期会话 404、非子路径 404
回归:go test -tags luajit ./... 9 包全绿
|
2026-10-15 14:57:13 +08:00 |
|
|
|
d86514c474
|
feat(channels): VNC 兜底真正可用(依赖预检 + 就绪探针 + CDP 取凭据)
用户指出「vnc 无头浏览器兜底登陆你根本没有实现」。核对后确认:进程层代码存在,
但**只接在一个我编的假平台上**(loopbackish,已随夹具移出),四个真实平台全标
flow="manual" 并给一句"请你自己去某处搞定"。且代码写着「运行 deploy 脚本一键
安装」,而那个安装机制根本不存在(deploy.sh 里零浏览器栈内容)。
## 实现
- **依赖预检** browserDeps():探测 Xvfb/x11vnc/websockify/noVNC/chromium/
python3-websocket,如实报告缺什么、装哪个包、干什么用。
- **就绪探针** waitPort + X socket 等待:不再 sleep 2 拍脑袋(view_url 先于
服务可用会让 iframe 白屏)。
- **CDP 取凭据**:浏览器带 --remote-debugging-port,登录完成后经 CDP 读
localStorage/sessionStorage/cookie 或平台声明的 collect_expr。人只负责在 VNC
窗口里交互,机器负责把结果取出来 —— VNC 不该只是"看屏幕"。
实测:在真 HTTP 页面上 `localStorage.getItem('access_token')` → REAL-TOKEN-XYZ,
`document.cookie` → session=COOKIE-VAL,全部正确。
- **新端点**:channels/deps/status、browser/collect、browser/stop、browser/status。
- **安装入口** packaging/install-browser-stack.sh:识别 apt/dnf/yum/apk,
--check 只检查;装进发行包(/usr/share/llmsproxy/)。插件 install_hint 指向它。
默认不自动装:系统级包、数百 MB、chromium 安全面大,多数部署用不到 VNC。
## 过程中修掉三个真 bug(都是测试逼出来的)
1. **Xvfb 从未被启动**:steps 数组里有 Xvfb,但循环明确 `if st[1] ~= "Xvfb"`
跳过它(想先等 socket)→ 等一个永远不会出现的 socket。
2. **display 号按 seq 取模**:`90 + (seq % 10)`。Xvfb 锁文件在强杀后残留,
重启后 seq 从头 → 又选同一个号 → `Fatal: Server is already active`,
而报错只显示"Xvfb 未就绪"。改为**探测空闲**号(锁+socket+端口)。
3. **io.open 判 socket 存在**(两处):X socket 是 socket 文件,io.open 返回 nil
→ 探测误判"空闲"、等待循环永远等不到。改用 `[ -e ]`。
4. **孤儿进程**:spawn 往局部 pids 表追加,只在函数末尾回写 b.pids →
中途失败时 reapBrowser 读到空表,已起的 Xvfb 不被杀(实测留下 4 个孤儿)。
改为即时回写。
## 判据
- TestChannelsVncStackEndToEnd:注入 flow=vnc 平台 → 真起浏览器栈(view_url 含
vnc.html + CDP 端口)→ 回收。缺依赖则 SKIP 并说明。
- TestChannelsVncNoOrphanOnFailure:回收后 running=false、无孤儿。
- TestChannelsBrowserStackDeps:依赖清单三个字段齐全、缺件必须给安装提示。
- TestChannelsBrowserEndpointsRequireSession:无会话时三个端点如实报错/幂等。
- 全量 core 回归后确认:生产插件目录未被写入、无孤儿进程。
内核对照 v2.0.2:diff 为空(本次零内核改动)。
|
2026-10-13 09:33:18 +08:00 |
|