Files
ModelRouter/packaging/install-browser-stack.sh
JianFeeeee 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

124 lines
5.0 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.

#!/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