Russ534

fygo-browser-cdp

"Drive fygo-browser for real desktop browser tasks."

Russ534 0 Updated 4d ago
GitHub

Install

npx skillscat add russ534/myopenclawbot/fygo-browser-cdp

Install via the SkillsCat registry.

SKILL.md

fygo-browser 真桌面 Chrome + CDP 全链路方案

适用场景

  • Google 账号申诉 / Gmail 登录(Google 会拦截 headless Chrome)
  • 任何被 WAF / 验证码识别的"自动化"场景
  • 需要真实浏览器指纹 + 真实 IP + 真实 UA 的任务

关键事实

  • fygo-browser 是 fnOS native 应用(不是 Docker 容器),跑在 host 用 fygo-browser 用户
  • 自带 Chrome 152.0.7977.82,User-Agent 是真桌面(无 HeadlessChrome 标记)
  • 自带 KasmVNC 提供远程桌面,端口是 16000 (KASM_WEB_PORT) / 16001 (KASM_RFB_PORT),实际 gateway 报 16001 timeout 要看 netstat,不是 env 写的值——不同 fygo-browser 版本可能把端口写到 data/run/runtime-ports.env 决定,写错会被 supervisor 跳过
  • 关键:fnos host 没外网,必须走 mihomo 代理(端口 7890 HTTP / 7891 SOCKS,无 auth)
  • fygo-browser-gateway 暴露 unix socket /vol1/@appcenter/fygo-browser/app.sock
  • chromium 实际 CDP 端口是 16005(不管 ports.env 怎么写都看 netstat)

关键坑(必看)

  • fnos sshd 默认禁 PortForwarding —— 改 /etc/ssh/sshd_config.d/trim_sshd.conf 里 AllowTcpForwarding no → yes + kill -HUP $(cat /var/run/sshd.pid)
  • SSH 隧道端口冲突 —— 之前 tunnel 死了 16005 还 TIME_WAIT 占着,用别的端口转发(16025)
  • agent-browser daemon 跟 chromium WebSocket 状态会错位 —— 报"Request ID 重复 + Connection lost",简单 list_tabs 能用,goto_url 卡死
  • agent-browser page_info 会卡 IPC timeout —— 别用
  • chromium 启参数必须有 --proxy-server=http://127.0.0.1:7890 —— 不加连不上 Google
  • fygo-browser start.sh RUNTIME_IDLE_TIMEOUT 默认 1m —— 改 24h,不然一会儿就 idle 关掉
  • fnoncebrowser start.sh 改 start_chromium_process 那段(line ~600 附近)加 proxy

完整启动流程

1. fnos sshd 开 PortForwarding

ssh fnos "cp /etc/ssh/sshd_config.d/trim_sshd.conf{,.bak} && sed -i 's/AllowTcpForwarding no/AllowTcpForwarding yes/' /etc/ssh/sshd_config.d/trim_sshd.conf && kill -HUP \$(cat /var/run/sshd.pid)"

2. 改 fygo-browser start.sh 加 proxy

ssh fnos "sed -i '/--disable-dev-shm-usage/a\\    --proxy-server=http://127.0.0.1:7890 \\\\' /vol1/@appcenter/fygo-browser/app/scripts/start.sh"
# + 改 RUNTIME_IDLE_TIMEOUT:-1m → :-24h

3. 启 fygo-browser runtime(用 fygo-browser 用户)

ssh fnos "runuser -u fygo-browser -- bash -c 'cd /vol1/@appcenter/fygo-browser/app && bash scripts/start.sh runtime-start'"

4. 写 ports.env(如果还没建)

ssh fnos "runuser -u fygo-browser -- bash -c 'cat > /vol1/@appcenter/fygo-browser/data/run/runtime-ports.env <<EOF
KASM_WEB_PORT=16000
KASM_RFB_PORT=16001
CDP_PORT=16002
CHROMIUM_CDP_PORT=16003
EOF'"

5. 验证 chromium + 拿到实际 CDP 端口

ssh fnos "netstat -tlnp 2>/dev/null | grep 16005"  # 实际是 16005
ssh fnos "curl -s -m 5 http://127.0.0.1:16005/json/version"  # 拿 webSocketDebuggerUrl

6. 建 SSH 隧道(用 16025 避开 TIME_WAIT)

ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -L 127.0.0.1:16025:127.0.0.1:16005 -N fnos &

7. 验证 CDP 过隧道

curl -s -m 5 http://127.0.0.1:16025/json/version  # 200 OK

Python CDP 客户端(绕开 agent-browser)

不要用 agent-browser daemon —— WebSocket 状态会错位。

直接用 Python websockets 库(hermes venv 已装 websockets==15.0.1):

import asyncio, websockets, json, base64
import urllib.request

# 拿 tab list
tabs = json.loads(urllib.request.urlopen('http://127.0.0.1:16025/json', timeout=5).read())
for t in tabs:
    if t.get('type') == 'page':
        print(t['title'], '|', t['url'])
        print('ws:', t['webSocketDebuggerUrl'])

WS_URL = 'ws://127.0.0.1:16025/devtools/page/{targetId}'

async def cdp_call(ws, method, params=None, _id=[1]):
    _id[0] += 1
    msg = {'id': _id[0], 'method': method}
    if params: msg['params'] = params
    await ws.send(json.dumps(msg))
    while True:
        r = json.loads(await ws.recv())
        if r.get('id') == _id[0]:
            return r

async def google_login():
    async with websockets.connect(WS_URL, max_size=10*1024*1024) as ws:
        await cdp_call(ws, 'Runtime.enable')
        # 找 input + set value
        r = await cdp_call(ws, 'Runtime.evaluate', {
            'expression': """(() => {
                const i = document.querySelector('input[type="email"]');
                if (!i) return 'no input';
                const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
                setter.call(i, 'russer1109@gmail.com');
                i.dispatchEvent(new Event('input', { bubbles: true }));
                return 'ok: ' + i.value;
            })()""", 'returnByValue': True
        })
        # click Next
        await cdp_call(ws, 'Input.dispatchMouseEvent', {'type':'mousePressed','x':1024,'y':440,'button':'left','clickCount':1})
        await cdp_call(ws, 'Input.dispatchMouseEvent', {'type':'mouseReleased','x':1024,'y':440,'button':'left','clickCount':1})
        await asyncio.sleep(3)
        # 截图
        r = await cdp_call(ws, 'Page.captureScreenshot', {'format': 'png'})
        with open('/tmp/x.png', 'wb') as f:
            f.write(base64.b64decode(r['result']['data']))

用 CDP 打开本机 / LAN 的 Web 看板(真浏览器登录 + 抓页面)

需要"用真浏览器登录某个内网看板 / 后台"时走这条,不要指望 hermes 自带 browser 工具(容器内没有 CDP,也进不了内网服务)。

两个必踩的网络坑(一起出现时最难辨):

  1. Chrome 带 --proxy-server=http://127.0.0.1:7890 启动 → 打 LAN IP 会被 mihomo 拦成 HTTP 502(Chrome 显示 chrome-error://chromewebdata/,正文 This page isn't working)。不是服务挂了。
  2. 目标服务可能只 bind 在 LAN IP 上(docker port <c> 显示 8788/tcp -> 192.168.31.201:8788)→ 从宿主打 http://127.0.0.1:8788 是 ERR_CONNECTION_REFUSED。

解法:在宿主上起一个只 bind localhost 的 TCP 转发——Chrome 即使带 --proxy-server 也会自动绕过 localhost,于是既避开 mihomo 502 又绕开 bind 限制:

ssh fnos-root 'cat > /tmp/wbfwd.py <<PY
import socket, threading
L=("127.0.0.1",8799); T=("192.168.31.201",8788)   # 改 T 成目标
...
PY
nohup python3 /tmp/wbfwd.py > /tmp/wbfwd.log 2>&1 &'
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8799/   # 期望 200

收尾清理用方括号技巧,否则 pkill -f wbfwd 会连你自己的 ssh 会话一起杀(ssh 命令行里含同一个字符串):

ssh fnos-root 'for p in $(pgrep -f "[w]bfwd"); do kill $p; done'

从 hermes 容器调 CDP 必须显式关代理:urllib.request.build_opener(ProxyHandler({}));curl 加 --noproxy '*'。否则容器的 HTTP_PROXY 把 127.0.0.1:16025 也送去 mihomo。

开新标签页要 PUT(GET 会 405/timeout):

curl -s --noproxy '*' -X PUT 'http://127.0.0.1:16025/json/new?http://127.0.0.1:8799/?pwd=<panel_password>'

抓内容 / 截图(SPA 看板导航项常是 <button>,没有 href——按 innerText 点,不要猜 ?tab= 参数):

await call("Runtime.evaluate", {"expression": "document.body.innerText", "returnByValue": True})
await call("Runtime.evaluate", {"expression": "(()=>{const el=[...document.querySelectorAll('button,a,div,span')].find(e=>e.innerText.trim()==='数据看板'); el.click(); return 'clicked';})()"})
await call("Page.captureScreenshot", {"format": "png"})   # base64 → 落盘后自己看

收尾:杀掉隧道(kill <ssh pid>,用后台进程管理工具别用 nohup)、杀掉宿主转发;看板 URL 里的 ?pwd= 会留在浏览器历史,公网暴露时不要用这种免密链接。

KasmVNC 远程桌面(爸爸手动操作时用)

  • https://192.168.31.201:5666/app/fygo-browser/(fnOS 认证后)
  • 或者直接 VNC: 127.0.0.1:16001 (noVNC web) / 127.0.0.1:16002 (VNC RFB)
  • KasmVNC disableBasicAuth + SecurityTypes None —— 无密码直接连

关键经验

  • 真桌面 Chrome 152 + mihomo 代理 = Google 接受登录(不像 agent-browser 报"This browser or app may not be secure")
  • fnoncebrowser 用户跑 mihomo 代理 = 走 host 127.0.0.1:7890
  • agent-browser daemon 简化操作可以,复杂操作(goto_url/page_info)会卡 IPC timeout
  • Python websockets 直连 CDP 是最稳的 —— 简单 list_tabs / set value / click / screenshot 都行
  • 点击坐标要先看 input 元素 rect 再点 —— Google 登录页 input 一般在 y=200-300 区域