Ubuntu 25.04 容器环境配置 VNC + noVNC + Xfce + Playwright MCP 全流程¶
日期:2026-06-13 主机:huxiping (Proxmox VE 容器,6.17.13-2-pve 内核,amd64) 操作账户:root 起始磁盘:64G 已用 57G(89%),剩余 7.5G
1. 更新日志¶
- 2026-06-13 23:30 — 追加 §9.2/§11/§14:发现
root + headed + DISPLAY=:1可让 Chrome 出现在 VNC 桌面上,6 种方案测试矩阵完整记录。opencode MCP 配置已切到 headed 模式。
2. 0. 环境画像¶
| 项 | 值 |
|---|---|
| 发行版 | Ubuntu 25.04 (plucky) |
| 内核 | 6.17.13-2-pve (PVE 容器) |
| 架构 | amd64 |
| 用户 | root |
| 包管理器 | apt |
| systemd | 可用 |
| 已用磁盘 | 89% (7.5G 可用) |
| 已有 node | 系统 v20.18.1 (/usr/bin/node) + /opt/node-v24.14.1-linux-x64/ |
| 已有 python | 3.13.3 |
| 桌面环境 | 无(全新安装) |
| VNC | 无(全新安装) |
重要约束:磁盘紧张(<8G),全程优先选择轻量方案(Xfce 优于 GNOME)。
3. 1. 安装 Xfce 桌面¶
- 安装包约 300+ 个,耗时数分钟
- 装完后
/usr/share/xsessions/xfce.desktop出现 - 关键二进制:
startxfce4、xfce4-session
4. 2. 安装 TigerVNC server¶
版本:1.14.1+dfsg-1
关键二进制:/usr/bin/vncserver(perl 包装)、/usr/bin/Xvnc(实际 X server)、/usr/bin/vncpasswd
4.1 2.1 配置 root 用户的 VNC 启动脚本¶
/root/.vnc/xstartup:
权限:chmod +x /root/.vnc/xstartup
4.2 2.2 关键决策¶
- 不设置 VNC 密码(用户选"暂不设置密码")
- 只监听 localhost(最安全,外部必须走 SSH 隧道)
- 显示尺寸:
1920x1080,色深:24
5. 3. 创建 systemd 服务(开机自启)¶
/etc/systemd/system/vncserver@.service:
注意:
- 使用
Type=simple而不是forking—— perl vncserver 是 fork 模式,systemd 的 PIDFile 检测不可靠 - 启动命令中显式传
-localhost(不要用~/.config/tigervnc/config的$localhost = "..."perl 语法,会解析失败)
5.1 3.1 启动¶
5.2 3.2 验证¶
6. 4. 安装 noVNC(浏览器访问 VNC)¶
novnc1.3.0:/usr/share/novnc/websockify:/usr/bin/websockify(将浏览器 WebSocket 协议桥接到 VNC RFB 协议)
6.1 4.1 systemd 服务¶
/etc/systemd/system/novnc@.service:
6.2 4.2 启动¶
6.3 4.3 验证¶
7. 5. 使用方法¶
由于 VNC 5901 和 noVNC 6080 都只监听 127.0.0.1,必须通过 SSH 隧道访问。
7.1 5.1 SSH 隧道(本地机器执行)¶
7.2 5.2 连接方式¶
| 方式 | 步骤 |
|---|---|
| VNC 客户端 | macOS 屏幕共享 vnc://127.0.0.1:5901;Windows/Linux 用 TigerVNC Viewer 连 127.0.0.1:5901 |
| 浏览器 | 打开 http://127.0.0.1:6080/vnc.html,点 Connect |
因
SecurityTypes=None,客户端会弹"无加密"警告,正常接受即可(数据已被 SSH 加密)。
8. 6. 安装 Google Chrome¶
版本:149.0.7827.114
路径:/usr/bin/google-chrome
9. 7. 安装 Playwright (Node SDK)¶
- Playwright SDK 1.60.0 装到
/usr/lib/node_modules/playwright - Chromium 1223(fallback build,ubuntu24.04-x64)下载到
/root/.cache/ms-playwright/ - 系统依赖(libnss3、libasound2t64 等)大部分已被 Xfce 安装顺带覆盖,0 新包需要
10. 8. 安装 @playwright/mcp¶
版本:0.0.76
入口:/usr/lib/node_modules/@playwright/mcp/cli.js(全局命令 playwright-mcp)
11. 9. 注册到 opencode MCP¶
文件:/root/.config/opencode/opencode.json
11.1 9.1 方案 A:headless 模式(默认推荐)¶
| 参数 | 作用 |
|---|---|
--isolated |
每次会话用临时独立 profile |
--no-sandbox |
root 跑 Chrome 必需 |
--executable-path /usr/bin/google-chrome |
用系统 Chrome 而非 playwright 自带 chromium |
VNC 上看不到 Chrome,但截图、evaluate、navigate 等所有 API 正常工作。
11.2 9.2 方案 B:headed 模式(Chrome 出现在 VNC 桌面)¶
相比方案 A 多出三个关键参数:
| 参数 | 作用 |
|---|---|
--disable-setuid-sandbox |
关掉 Chrome 自己的 setuid sandbox 创建逻辑(这是 root + headed 启动失败的根因) |
--disable-dev-shm-usage |
容器内 /dev/shm 通常只有 64MB,不关掉会因内存不足崩溃 |
environment.DISPLAY=":1" |
核心:让 Chrome 连到 Xtigervnc 提供的 X display,绕开 sandbox 检查 |
备份原文件后再改:
cp /root/.config/opencode/opencode.json /root/.config/opencode/opencode.json.bak-$(date +%Y%m%d-%H%M%S)
12. 10. 验证 Playwright MCP¶
12.1 10.1 启动 MCP 并列出 tools¶
结果:返回 23 个 tools(browser_navigate / browser_click / browser_take_screenshot / browser_evaluate / ...)
12.2 10.2 端到端测试(系统 Chrome 真实访问 example.com)¶
结果:HTTP 200, title 正确,截图 1280x720 PNG 正常生成。
13. 11. Chrome 为何默认 VNC 看不到?¶
Chrome 跑在 headless 模式(无头),没有真窗口画到 X server 上。
证据:
navigator.userAgent含HeadlessChrome/149.0.0.0navigator.webdriver === true(headless 特有)- 进程启动参数含
--type=crashpad-handler(无头模式子进程)
VNC 看到的是 display :1 上的 Xfce 桌面(Xtigervnc + xfce4-session),这是另一条独立画布,与 headless Chrome 互不干扰。截图工具和 playwright API 仍能拿到完整渲染内容(输出在内存里),只是没有窗口画到屏幕上。
13.1 11.1 Chrome 为何拒绝 root 跑非 headless?¶
安全机制,不是技术限制。
- Chrome 采用多进程架构:主进程(root 权限)+ 沙箱化的 Renderer 进程
- Renderer 进程需要从 root 降权到 nobody/nogroup,与不可信 JS 隔离
- Linux 内核不允许 root 调用
setuid(nobody)(root 永远是 root,降权对 root 无效) - Chromium M48+ 在
sandbox/linux/seccomp-bpf-helpers.cc硬编码此断言:UID==0 + headed → 启动后立即自杀 - headless 模式是唯一例外:不创建真实窗口,攻击面集中在内存里
playwright 把 stderr 吞掉只剩 Target page, context or browser has been closed 这条笼统错误。
13.2 11.2 实测:6 种绕过方案的测试矩阵¶
| # | 配置 | 结果 |
|---|---|---|
| 1 | root + headless(基线) | ✅ 正常 |
| 2 | headless:false + --disable-setuid-sandbox |
❌ 启动后自杀 |
| 3 | headless:'new'(playwright API 不支持字符串) |
❌ API 报错 |
| 4 | headless:false + DISPLAY=:1 + --disable-setuid-sandbox + --disable-dev-shm-usage |
✅ 成功 |
| 5 | headless:false + --single-process |
❌ 启动后自杀 |
| 6 | headless:false + --in-process-gpu --no-zygote ... |
❌ 启动后自杀 |
13.3 11.3 唯一可行的方案(路径 4)¶
为什么能成:不是 Chrome 放行了 root + headed,而是当 Chrome 指向已存在的 X server(Xtigervnc 提供的 display :1)时,它的安全模型重新评估,把信任边界扩展到"我画的 X 已被验证过"。配合 --disable-setuid-sandbox,Chrome 不再尝试自己创建 sandbox 子进程,从而绕开"root 降权失败"那个核心断言。
严格说并非"忽略安全",而是把沙箱职责外包给了 X server 的访问控制。
13.4 11.4 风险评估¶
| 风险 | 实际威胁 |
|---|---|
| Chrome Renderer 以 root 跑 | ⚠️ XSS/RCE = root 沦陷(真风险) |
--disable-setuid-sandbox |
⚠️ Renderer 攻击面无隔离(真风险) |
| X server 已是 root 起的 Xtigervnc | ⚠️ 整个 Xfce 桌面本身就是 root 跑的,攻击面被放大 |
| 外部访问需 SSH 隧道 | ✅ 网络层挡住大部分攻击者 |
| 你的 root 容器日常操作 | ⚠️ 同样风险等级,VNC 桌面就是 root |
结论:与"用 root 跑 Xfce 桌面 + root 跑 VNC + SSH 隧道鉴权"是同一安全等级,没有本质额外风险。
13.5 11.5 验证 headed 模式生效¶
确认 VNC 桌面上能看到 Chrome 窗口后,清理测试进程:
13.6 11.6 切到 headed 后 opencode 重启会发生什么¶
- 模型调用 playwright 工具 → MCP spawn Chrome → Chrome 启动后画在你 VNC 桌面上
- 窗口位置默认由 Chrome 自管理(MCP 不控制),可能遮挡 Xfce 任务栏
- 调
browser_tabs切标签、browser_take_screenshot截屏等操作,都会在这个真窗口上发生 - 想关掉 VNC 上的 Chrome 残影:
pkill -f google-chrome
14. 12. 端口与进程总览¶
| 端口 | 进程 | 监听 | 用途 |
|---|---|---|---|
| 22 | sshd | 默认对外 | 建 SSH 隧道 |
| 5901 | Xtigervnc | 127.0.0.1 | 原生 VNC 客户端 |
| 6080 | websockify | 127.0.0.1 | 浏览器 noVNC |
| 进程 | PID 来源 | 命令 |
|---|---|---|
| Xtigervnc | 459505 | /usr/bin/Xtigervnc :1 -localhost=1 ... |
| xfce4-session | 459513 | xfce4-session |
| websockify | 461202 | websockify --web=/usr/share/novnc 127.0.0.1:6080 127.0.0.1:5901 |
| playwright-mcp | 按需启动 | playwright-mcp --isolated --no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage --executable-path /usr/bin/google-chrome(DISPLAY=:1) |
15. 13. 常用管理命令¶
16. 14. 故障排查记录¶
16.1 14.1 systemd 报 protocol failed¶
原因:使用 Type=forking + PIDFile=/root/.vnc/%H:%i.pid,但 perl vncserver 的 PID 文件名包含主机名(huxiping.ip-139-99-68.net:1.pid),systemd 找不到。
解决:改为 Type=simple,去掉 PIDFile。
16.2 14.2 Can't parse '$localhost = "localhost:1";' from config file¶
原因:~/.config/tigervnc/config 的 perl 语法 $localhost 在当前版本下解析失败。
解决:rm -rf /root/.config/tigervnc,让 -localhost 命令行参数生效(systemd ExecStart 里已加)。
16.3 14.3 apt update 报 W: https://download.docker.com/linux/ubuntu ...¶
警告信息,不影响功能(Docker 源本身在用)。
16.4 14.4 Playwright 报 your OS is not officially supported¶
原因:Ubuntu 25.04 不在 Playwright 官方支持列表,下载 fallback build (ubuntu24.04-x64)。
解决:忽略警告,fallback build 兼容 Ubuntu 25.04。
16.5 14.5 headed 模式下 Chrome 启动后闪退 / /dev/shm 不足¶
Warning
原因:容器内 /dev/shm 通常只有 64MB(docker/k8s 默认),Chrome 默认把共享内存用在这;超过会触发 renderer 崩溃。
解决:启动参数加 --disable-dev-shm-usage,改用 /tmp 目录。这是 headed 模式必须加的参数。
16.6 14.6 headed 模式下 Chrome 启动后 Target page, context or browser has been closed¶
Warning
原因:缺 --disable-setuid-sandbox,Chrome 仍尝试用 setuid helper 创建 sandbox 子进程;root 跑时该路径必败。
解决:见 §11.3,三件套(--no-sandbox + --disable-setuid-sandbox + DISPLAY=:1)缺一不可。
16.7 14.7 headed 模式下 Chrome 出现但点击/键入无响应¶
原因:X11 焦点问题或窗口未激活。xdotool windowactivate <window_id> 可强制激活。
解决:
17. 15. 注意事项¶
Warning
- 磁盘只剩 ~7.3G,建议定期清理
/root下归档资料 - 未对外暴露端口:所有图形服务仅 localhost,安全但需 SSH 隧道
- VNC 鉴权 = None:纯靠 SSH 加密,未授权用户即使知道 IP 也连不进
- Chrome 默认以 root + headless 跑:性能最佳、攻击面最小。可切到 headed 模式(见 §11),代价是失去 sandbox 隔离
- headed 模式三件套:
--no-sandbox+--disable-setuid-sandbox+DISPLAY=:1,缺一不可 - headed 模式容器内必须加:
--disable-dev-shm-usage(/dev/shm 通常 64MB 不够) - headed 模式安全等级:与 root 跑 Xfce/VNC 同级,本机可控;如对外暴露需要更多防御
- 重启后:
vncserver@1和novnc@1systemd unit 都会自动拉起