哪吒面板远程终端黑屏排查与修复:WS 101 但仍黑屏 2025-12-31 记录 暂无评论 2 次阅读 # 哪吒面板远程终端黑屏排查与修复:WS 101 但仍黑屏(根因是 xterm CDN 资源) ## 现象描述 - 在哪吒面板点击“远程控制 / Terminal”,弹出新窗口后是**纯黑屏**。 - 浏览器里看得到 WebSocket **`101 Switching Protocols`**(例如 `/ws` 是 101),但终端仍然不显示任何内容。 - 面板(后端)日志里可能只看到类似: - `POST "/terminal" 200` - `GET "/ws" 200/长连接一段时间后结束` - Nginx 代理日志看起来“都正常”,没有明显 4xx/5xx。 结论:**WS 101 不代表终端一定可用**;终端页面渲染还依赖前端 `xterm.js` 资源加载成功。 --- ## 关键知识点:终端用的 WS 不是 `/ws` 哪吒终端页返回的 HTML 中通常类似这样(你实际看到的是这一段): ```js const socket = new WebSocket( (window.location.protocol == 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/terminal/' + '5882a89c-...' ); ``` 也就是说: - `/ws` 很可能只是面板“实时数据通道”; - 真正终端数据通道是:`wss://你的域名/terminal/`; - **终端黑屏时必须重点检查 `/terminal/` 这条 WS 是否连上,以及终端页的 JS 是否执行成功。** --- ## 根因定位:xterm 资源从 ByteCDN 加载失败,导致 JS 不执行 终端页 HTML 引入了 ByteCDN 上的资源,例如: - `https://lf6-cdn-tos.bytecdntp.com/.../xterm.css` - `https://lf6-cdn-tos.bytecdntp.com/.../xterm.js` - `.../xterm-addon-attach.js` - `.../xterm-addon-fit.js` 如果这些资源在你的网络环境下无法访问、被拦截、或 CDN 失效: - `Terminal`/`AttachAddon`/`FitAddon` 对象不会存在; - 后续 `new WebSocket('/terminal/')` 这段代码可能根本没跑起来; - 页面只剩黑色背景,看起来就是“黑屏”,且不一定有明显提示。 --- ## 如何快速确认是不是 xterm/CDN 问题(建议按顺序做) ### 1) 看浏览器 Console(最快) 打开终端黑屏窗口,按 `F12` → `Console`,常见报错: - `Failed to load resource`(指向 ByteCDN 的 xterm js/css) - `Terminal is not defined` - `AttachAddon is not defined` - `FitAddon is not defined` 只要出现这些,基本就坐实是 **xterm 资源加载失败**。 ### 2) 看 Network 是否成功拉到 xterm 资源 `F12` → `Network` → 过滤 `xterm`,看是否是 `200`,是否有被阻断(`ERR_CONNECTION_RESET` / `403` / `blocked` 等)。 ### 3) 确认终端 WS 是否真的发起了 `/terminal/` `F12` → `Network` → `WS`,看看有没有: - `wss://你的域名/terminal/` 如果**根本没有**这条 WS,通常就是前端 JS 没执行(仍指向 xterm 资源问题)。 --- ## 解决方案(推荐):把 xterm 资源本地化,避免依赖 ByteCDN 目标:让终端页引用的 xterm 资源走你自己的域名,例如: - `https://nezha.badguy.top/vendor/xterm/4.11.0/xterm.js` ### 思路 1) 把 xterm 的 js/css 文件下载到你服务器本地; 2) Nginx 提供静态访问 `/vendor/...`; 3) 用 Nginx 对 `POST /terminal` 返回的 HTML 做 `sub_filter`,把 ByteCDN 链接替换为本地链接; 4) `/terminal/` 这条 WS 单独用 `location ^~ /terminal/` 做 Upgrade 代理。 --- ## Nginx 配置示例(可直接套用) ### A. 在 `http {}` 里准备 WS upgrade 变量(推荐) ```nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; } ``` ### B. server 中:静态托管本地 xterm 资源 假设你把文件放在服务器目录: - `/var/www/nezha-vendor/xterm/4.11.0/xterm.js` - `/var/www/nezha-vendor/xterm/4.11.0/xterm.css` - `/var/www/nezha-vendor/xterm/4.11.0/xterm-addon-attach.js` - `/var/www/nezha-vendor/xterm/4.11.0/xterm-addon-fit.js` Nginx: ```nginx location ^~ /vendor/xterm/ { alias /var/www/nezha-vendor/xterm/; add_header Cache-Control "public, max-age=31536000, immutable"; } ``` ### C. 仅对 `POST /terminal`(终端 HTML)做替换 ```nginx location = /terminal { proxy_pass http://127.0.0.1:18001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Accept-Encoding ""; # 关键:避免 gzip 导致 sub_filter 替换失败 sub_filter_once off; sub_filter 'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.css' '/vendor/xterm/4.11.0/xterm.css'; sub_filter 'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.js' '/vendor/xterm/4.11.0/xterm.js'; sub_filter 'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/addons/attach/xterm-addon-attach.js' '/vendor/xterm/4.11.0/xterm-addon-attach.js'; sub_filter 'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/addons/fit/xterm-addon-fit.js' '/vendor/xterm/4.11.0/xterm-addon-fit.js'; } ``` 说明: - `sub_filter_types text/html;` 一般可不写(默认就会处理 `text/html`),写了也没问题; - 前提是 Nginx 编译带 `http_sub_module`(用 `nginx -V` 检查是否有 `--with-http_sub_module`)。 ### D. 终端 WS:`/terminal/` 必须走 Upgrade ```nginx location ^~ /terminal/ { proxy_pass http://127.0.0.1:18001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 1h; proxy_send_timeout 1h; proxy_buffering off; } ``` --- ## 文件准备:把 xterm 资源下载到本地(示例命令) 在服务器上执行(一次性): ```bash mkdir -p /var/www/nezha-vendor/xterm/4.11.0 curl -L -o /var/www/nezha-vendor/xterm/4.11.0/xterm.css \ "https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.css" curl -L -o /var/www/nezha-vendor/xterm/4.11.0/xterm.js \ "https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.js" curl -L -o /var/www/nezha-vendor/xterm/4.11.0/xterm-addon-attach.js \ "https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/addons/attach/xterm-addon-attach.js" curl -L -o /var/www/nezha-vendor/xterm/4.11.0/xterm-addon-fit.js \ "https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/addons/fit/xterm-addon-fit.js" ``` 然后验证: - 直接在浏览器打开 `https://你的域名/vendor/xterm/4.11.0/xterm.js` 应返回 JS 内容。 --- ## 验证修复是否成功 1) `nginx -t && systemctl reload nginx` 2) 打开终端页: - `Network` 里 `xterm.js/xterm.css` 都是 `200`(并且是你域名 `/vendor/...`) - `WS` 里存在 `wss://你的域名/terminal/`,并且状态 `101` 3) Console 不再出现 `Terminal is not defined` 等报错,终端开始出字。 --- ## 常见坑总结 - **只看 `/ws 101` 不够**:终端 WS 是 `/terminal/`。 - **`sub_filter` 不生效**:没禁用压缩(要 `proxy_set_header Accept-Encoding ""`),或 Nginx 没编译 `http_sub_module`。 - **location 匹配不完整**:务必区分 `location = /terminal`(HTML)和 `location ^~ /terminal/`(WS)。 - **WS 访问日志延迟**:WebSocket 的 `access_log` 通常在连接关闭后才落盘,`request_time` 基本等于连接存活时间。 - **`duplicate MIME type "text/html"` 警告**:通常是你在 `types {}` 里重复定义了 `text/html`,与本方案无关,但建议清理(只在 `http {}` 里 include 一次 `mime.types`)。 --- 如果你愿意,我也可以帮你把你最终生效的整段 `server { ... }` 配置整理成“可复制粘贴的完整版本”(包含 `/`、`/ws`、`/terminal`、`/terminal/`、`/vendor/` 以及专用日志),避免后续维护时再踩 location 覆盖/顺序问题。 打赏: 微信, 支付宝 标签: none 本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。