之前开了 GPT Pro 共享车,需要把已经登录的 Web 端共享给车友。直接提供账密码或者分发浏览器资料(比如指纹浏览器),账号凭证和登录状态都不容易控制。
后来我改成用 Neko 共享一台已经登录 ChatGPT 的 Chromium 。车友只进入 Neko 房间,不直接接触账号密码;房间口令、控制权和浏览器可访问的网站可以分别限制。下面记录完整的手动部署过程。
网络结构 先总览一下网络拓扑,帮助大家理解 Neko + 共享浏览器的网络链路:
Neko 站点的 HTTPS 、登录接口和 WebSocket 经过 443 ,再由 Nginx 转发到 Neko 的 8080 。
Neko 页面中浏览器画面和音频通过 WebRTC 传输,本文开放 UDP 52000–52100 ,并保留 TCP 52101 作为回退。
ChatGPT 账号网络:Chromium 访问网站时使用容器自己的出站网络。给浏览器增加代理,不会替代 WebRTC 的入站端口。
域名页面能打开,只能证明 443 和 8080 正常,不能证明 WebRTC 已经连通。Neko WebRTC 文档 也明确要求媒体端口直接可达,或者通过 TURN 中继。(部署时就踩了这个坑) Docker Compose 部署 准备一台带公网 IP 的 VPS 、一个已解析到该 IP 的域名,以及 Docker Engine 和 Compose 插件。新建目录后写入 compose.yaml: services: neko: image: ghcr.io/m1k1o/neko/chromium:latest container_name: neko restart: unless-stopped shm_size: "2gb" ports:
- "127.0.0.1:8080:8080"
- "52000-52100:52000-52100/udp"
- "52101:52101/tcp" environment: NEKO_MEMBER_PROVIDER: "multiuser" NEKO_MEMBER_MULTIUSER_USER_PASSWORD: "${NEKO_USER_PASSWORD:?required}" NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: "${NEKO_ADMIN_PASSWORD:?required}"
NEKO_SERVER_BIND: "0.0.0.0:8080"
NEKO_SERVER_PROXY: "true"
NEKO_DESKTOP_SCREEN: "1920x1080@30"
NEKO_SESSION_IMPLICIT_HOSTING: "false"
NEKO_SESSION_CONTROL_PROTECTION: "true"
NEKO_FILETRANSFER_ENABLED: "false"
NEKO_DESKTOP_UPLOAD_DROP: "false"
NEKO_WEBRTC_EPR: "52000-52100"
NEKO_WEBRTC_NAT1TO1: "${NEKO_PUBLIC_IP:?required}"
NEKO_WEBRTC_ICELITE: "true"
NEKO_WEBRTC_TCPMUX: "52101"
同目录创建 .env: NEKO_PUBLIC_IP=203.0.113.10 NEKO_USER_PASSWORD=replace-with-a-long-random-value NEKO_ADMIN_PASSWORD=replace-with-another-long-random-value
普通用户和管理员使用不同口令。当前配置关闭了点击画面自动取得控制权,并要求管理员在房间内时普通用户才能取得控制权;如果需要无人值守的远程控制,应按使用场景调整这两个选项。 把 .env 权限收紧,并在启动前检查 Compose 展开结果: chmod 600 .env docker compose config --quiet docker compose up -d docker compose ps curl -fsS http://127.0.0.1:8080/health
NEKO_SERVER_BIND=0.0.0.0:8080 让 Docker 能访问容器内服务;宿主机的端口映射仍限定在 127.0.0.1,外部访问只能经过反向代理。NEKO_SERVER_PROXY=true 用于信任反向代理传入的客户端地址头,不能在 Neko 直接暴露公网时开启。完整变量说明可在官方配置页核对。 官方示例使用 latest。完成测试后可以固定到验证过的镜像版本或摘要,避免重新拉取镜像时引入未验证变更。 反向代理和防火墙 Nginx 配置需要保留 WebSocket Upgrade 头: server { listen 443 ssl http2; server_name neko.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header 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 300s;
proxy_send_timeout 300s;
}
}
修改后执行 nginx -t,确认无误再 reload 。Neko 会定期发送 WebSocket 心跳,代理超时时间不能短于会话心跳周期。官方反向代理示例给出了相同的 Upgrade 配置。 需要开放的端口如下:
端口 协议 用途
443 TCP HTTPS 、登录接口和 WebSocket
80 TCP 可选;用于 ACME HTTP-01 签发或跳转 HTTPS
52000–52100 UDP WebRTC 媒体端口,必须与 NEKO_WEBRTC_EPR 完全一致
52101 TCP 可选的 WebRTC TCP mux 回退
不要把 8080 开放到公网。云防火墙和系统防火墙都要检查;只配置其中一层仍可能导致媒体连接超时。 关键配置 1.持久化 Chromium 资料 默认情况下,重建容器会丢失 Chromium 的 Cookie 、扩展和设置,为了避免每次重启容器需要重新登录 chatgpt 账号。需要保留资料时增加卷挂载: volumes:
- ./chromium-data:/home/neko/.config/chromium
挂载后先检查目录属主。浏览器无法启动、只有黑屏时,可以在容器内确认目录是否属于 neko 用户: docker exec -it neko ls -la /home/neko/.config/chromium docker exec -it neko chown -R neko:neko /home/neko/.config/chromium
持久化目录会同时保存网站登录态。允许多人进入同一个房间时,应把整个 profile 视为共享数据,并单独决定备份和销毁方式。 把 WebRTC 收敛到单端口 如果不想开放一段 UDP 端口,可以改用 UDP/TCP mux 。删除 NEKO_WEBRTC_EPR 及其端口映射,改成: ports:
- "127.0.0.1:8080:8080"
- "52000:52000/udp"
- "52000:52000/tcp" environment: NEKO_WEBRTC_UDPMUX: "52000" NEKO_WEBRTC_TCPMUX: "52000"
UDP 延迟通常更低,TCP 适合作为受限网络中的回退。端口不能在 Docker 层改映射,例如不能把宿主机 52000 转到容器 59000 。 2.单独设计浏览器出站
为了降低 WebRTC 延迟,可以把 Neko 部署在离用户较近的服务器;如果服务器无法直接访问 ChatGPT ,再单独为 Chromium 配置出站代理。
Neko v3 没有用于设置 Chromium 代理的环境变量。当前 Chromium 镜像会读取 /etc/chromium/policies/managed/ 下的浏览器策略,因此可以新建 chromium-proxy.json:
{ "ProxySettings": { "ProxyMode": "fixed_servers", "ProxyServer": "socks5://proxy.example.com:1080", "ProxyBypassList": "localhost,127.0.0.1" } }
HTTP 代理可以把 ProxyServer 改成 http://proxy.example.com:3128。先检查 JSON ,再把文件挂载到 Chromium 的 managed policy 目录: jq empty chromium-proxy.json
volumes:
- ./chromium-proxy.json:/etc/chromium/policies/managed/20-proxy.json:ro
如果前面已经配置了 Chromium 资料持久化,把两个挂载项放在同一个 volumes 列表中。重新创建容器使策略生效: docker compose up -d --force-recreate
先从容器测试代理是否可达。下面以 SOCKS5 为例,HTTP 代理将参数改为 --proxy http://proxy.example.com:3128: docker exec neko curl -fsS --proxy socks5h://proxy.example.com:1080 https://api.ipify.org
最后在 Neko 的 Chromium 中打开 https://api.ipify.org。浏览器显示代理出口 IP ,说明 ProxySettings 已经生效;
docker exec neko curl -fsS https://api.ipify.org 仍会显示服务器自己的出口 IP ,因为这份策略只作用于 Chromium ,不会改变 Neko 容器的出口 IP 。
示例没有加入 direct:// 回退:代理断开时 Chromium 会直接报错,不会改用服务器出口。Chromium 也不会读取写在代理 URL 中的用户名和密码;需要认证时,优先让上游代理按服务器 IP 放行,或者增加一个只在 Docker 网络内监听的本地转发代理。
HTTP/SOCKS5 只能代理 Chromium 的 HTTP 、HTTPS 和 WebSocket 请求,但不能转发 UDP 。如果需要让语音等 UDP 流量走代理,应改用 VPN 或网关容器;使用 network_mode: service:
3.限制用户使用共享浏览器的访问地址 Neko 的 Chromium 镜像已经内置了一份浏览器策略,其中包括禁用开发者工具、下载和访客模式等限制。为了保留这些默认项,先从正在运行的容器复制策略文件,只修改其中的 URLBlocklist 和 URLAllowlist: docker cp neko:/etc/chromium/policies/managed/policies.json ./chromium-policies.json
用 jq 只替换这两个字段,输出一份新的策略文件: jq ' .URLBlocklist = ["", "file://"] | .URLAllowlist = [ "chatgpt.com", "chat.openai.com", ".auth.openai.com", ".auth0.openai.com", ".setup.auth.openai.com", "oaistatic.com", "oaiusercontent.com", "oaistatsig.com", ".cdn.openaimerge.com", ".challenges.cloudflare.com", ".cdn.workos.com", ".forwarder.workos.com", ".setup.workos.com", ".images.workoscdn.com", ".workos.imgix.net", "chrome://policy" ] ' chromium-policies.json > chromium-policies-restricted.json
Chromium 的策略语法中,chatgpt.com 会同时匹配它的子域名;以点开头的 .auth.openai.com 只匹配这个主机名。上面的列表用于 ChatGPT Web 、登录、静态资源和文件访问,域名取自 OpenAI 当前的网络配置建议。语音、付款或第三方登录需要使用时,再从官方列表加入对应域名。 先检查 JSON 格式,然后把文件挂载回原路径: jq empty chromium-policies-restricted.json
volumes:
- ./chromium-data:/home/neko/.config/chromium
- ./chromium-policies-restricted.json:/etc/chromium/policies/managed/policies.json:ro
如果不需要持久化 Chromium 资料,删除第一行卷挂载即可。重新创建容器后,在 Chromium 中打开 chrome://policy,点击 Reload policies (重新加载策略),确认 URLBlocklist 和 URLAllowlist 的状态为 OK: docker compose up -d --force-recreate
最后分别打开 https://chatgpt.com 和一个未放行的网站,确认前者可用、后者显示被管理员阻止。验证完成后可以从白名单中删除 chrome://policy,再重新创建容器。 URLBlocklist 限制的是 Chromium 中的地址访问,不是容器级防火墙;chatgpt.com 内部的会话、设置等功能也仍在放行范围内。需要限制容器内其他进程的出站连接时,还要在出口代理或防火墙上单独配置白名单。 排障
现象 检查 处理
域名无法打开 curl http://127.0.0.1:8080/health 、Nginx 错误日志 先确认容器健康,再检查反代目标、证书和 443
登录页正常,画面超时 EPR 、Docker UDP 映射、两层防火墙、nat_ips 日志 让端口范围和协议完全一致;修正 NEKO_WEBRTC_NAT1TO1
有光标但 Chromium 黑屏 shm_size 、profile 路径和属主 保持至少 2 GB /dev/shm ;修复或暂时移除持久化目录
外网正常,内网无法连接 路由器是否支持 NAT loopback 使用公网地址回环、VPN 或 TURN
Chromium 没有网络 先访问 https://1.1.1.1 ,再检查 DNS 和 Docker 网段 能访问 IP 但不能访问域名时修 DNS ;同时排除 Docker 子网冲突
服务端可以先看 Neko 识别到的公网地址: docker compose logs neko | grep nat_ips
客户端使用 Chromium 时打开 chrome://webrtc-internals。如果 candidate 中只有不可达的内网地址,继续检查 NAT1TO1 、端口映射或 TURN ,不必反复修改 Nginx 。Neko Troubleshooting 也采用这个排查顺序。
博客原文