openclaw-deploy

在远程服务器上一键部署 OpenClaw。当用户需要安装 OpenClaw、部署 OpenClaw、配置 OpenClaw 到服务器时使用

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "openclaw-deploy" with this command: npx skills add majiayu000/claude-arsenal/majiayu000-claude-arsenal-openclaw-deploy

OpenClaw 远程一键部署

在远程 Linux 服务器上自动部署 OpenClaw + AI 模型 + 聊天频道。


第零步:输出傻瓜教程

skill 启动后,必须先输出以下准备清单,让用户知道需要什么,然后再用 AskUserQuestion 收集信息:

# OpenClaw 一键部署

欢迎!部署前你需要准备以下东西:

| 序号 | 准备什么 | 说明 | 必需? |
|------|----------|------|--------|
| 1 | 一台 Linux 服务器 | 最低 1GB 内存 + 500MB 磁盘,支持 SSH 登录 | 必需 |
| 2 | SSH 免密登录 | 确保本机能 `ssh user@host` 直接连上 | 必需 |
| 3 | AI 模型 API Key | 智谱GLM / DeepSeek / OpenAI / Claude / Kimi / 通义 任选 | 必需 |
| 4 | 聊天频道 Bot Token | Telegram(@BotFather) 或 Discord 或 飞书,也可以暂不配 | 可选 |
| 5 | 代理地址 | 中国大陆服务器 + 海外频道(Telegram/Discord)时需要 | 视情况 |
| 6 | 浏览器控制 | 让 Bot 能操控浏览器搜索/截图/填表,需额外安装 Playwright | 可选 |

第一步:收集信息

用 AskUserQuestion 收集($ARGUMENTS 已提供的跳过)。分两轮问:

第一轮(必填 3 项):

用 AskUserQuestion 同时问以下 3 个问题:

  1. SSH 连接信息(header: "SSH 连接")
    • 选项根据上下文动态生成(如之前用过的服务器),兜底选项"其他服务器"
  2. AI 模型(header: "AI 模型")
    • 智谱 GLM-5(国产,无需代理)
    • DeepSeek(国产,无需代理)
    • OpenAI GPT-4o(需代理或海外服务器)
    • Anthropic Claude(需代理或海外服务器)
  3. 聊天频道(header: "聊天频道")
    • Telegram(需 Bot Token,中国大陆需代理)
    • Discord(需 Bot Token)
    • 飞书(需 Bot Token)
    • 暂不配置
  4. 浏览器控制(header: "浏览器")
    • 启用(Bot 可操控浏览器搜索/截图/填表)
    • 暂不配置

第二轮(根据第一轮结果追问):

  • 如果用户没提供 API Key → 问 API Key
  • 如果选了聊天频道 → 问 Bot Token
    • 如果用户没有 Bot Token → 给出创建教程:
      • Telegram:找 @BotFather,发 /newbot
      • Discord:去 discord.com/developers 创建 Application → Bot
      • 飞书:去 open.feishu.cn 创建自建应用
  • 如果选了 Telegram/Discord 且服务器在中国大陆 → 问代理地址
    • 自动判断:如果 SSH 到服务器后 curl -s --connect-timeout 3 https://api.telegram.org 失败,则判定需要代理

第二步:环境检查

SSH 连接到服务器,单条命令获取全部信息:

ssh <SSH_ARGS> "echo '=== CPU ===' && nproc && echo '=== 内存 ===' && free -h && echo '=== 磁盘 ===' && df -h / && echo '=== 系统 ===' && uname -a && echo '=== Node.js ===' && node -v 2>/dev/null || echo '未安装' && echo '=== npm ===' && npm -v 2>/dev/null || echo '未安装' && echo '=== 包管理器 ===' && which pacman apt yum dnf 2>/dev/null && echo '=== OpenClaw ===' && openclaw --version 2>/dev/null || echo '未安装'"

最低要求:1GB RAM + 500MB 磁盘。不满足则告知用户并停止。


第三步:安装 Node.js(如未安装)

根据检测到的包管理器:

包管理器命令
pacman (Arch)pacman -S --noconfirm nodejs npm
apt (Debian/Ubuntu)curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && apt install -y nodejs
dnf (RHEL/Fedora)dnf install -y nodejs npm
yum (CentOS)curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - && yum install -y nodejs

安装后验证:node -v && npm -v,确认 Node.js >= 22。


第四步:安装 OpenClaw

curl -fsSL https://openclaw.ai/install.sh | bash

重要:安装脚本会尝试启动交互式 onboarding wizard,在非 TTY 环境会报 /dev/tty: No such device or address 错误。这是正常的,OpenClaw 本体已安装成功。

验证:openclaw --version


第五步:写入配置文件

直接写 ~/.openclaw/openclaw.json,不用交互式 wizard。

模型配置模板

{
  "models": {
    "mode": "merge",
    "providers": {
      "<PROVIDER_NAME>": {
        "baseUrl": "<BASE_URL>",
        "apiKey": "<API_KEY>",
        "api": "openai-completions",
        "models": [
          {
            "id": "<MODEL_ID>",
            "name": "<DISPLAY_NAME>",
            "reasoning": false,
            "input": ["text"],
            "contextWindow": 128000,
            "maxTokens": 32000
          }
        ]
      }
    }
  },
  "agents": {
    "defaults": {
      "model": {
        "primary": "<PROVIDER_NAME>/<MODEL_ID>"
      },
      "memorySearch": {
        "enabled": false
      }
    }
  },
  "gateway": {
    "mode": "local"
  }
}

常见模型参考

Provider 名MODEL_IDbaseUrl
zhipuglm-5https://open.bigmodel.cn/api/paas/v4
openaigpt-4ohttps://api.openai.com/v1
deepseekdeepseek-chathttps://api.deepseek.com/v1
kimimoonshot-v1-128khttps://api.moonshot.cn/v1
qwenqwen-maxhttps://dashscope.aliyuncs.com/compatible-mode/v1
anthropicclaude-sonnet-4-5-20250929https://api.anthropic.com/v1

注意:Anthropic 模型的 api 字段应为 "anthropic-messages" 而非 "openai-completions"

写入后立即修复权限:

mkdir -p ~/.openclaw/agents/main/sessions ~/.openclaw/credentials
chmod 700 ~/.openclaw
chmod 600 ~/.openclaw/openclaw.json

第六步:启动网关(systemd)

按顺序执行:

# 安装 systemd 服务
openclaw gateway install

# 启用 linger(退出 SSH 后服务不会停)
loginctl enable-linger $(whoami)

# 启动
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user start openclaw-gateway.service

等待 3 秒后验证:

systemctl --user status openclaw-gateway.service | head -10

确认 Active: active (running)

轮换 device token

网关首次启动后,轮换 operator token 修复可能的认证问题:

# 先获取 device ID
DEVICE_ID=$(openclaw devices list 2>&1 | grep -oP '[a-f0-9]{40,}')
# 轮换 token
openclaw devices rotate --role operator --device "$DEVICE_ID"

第七步:配置聊天频道(如需要)

根据用户选择的频道执行对应配置。

7a. 设置频道

Telegram:

openclaw config set channels.telegram.enabled true
openclaw config set channels.telegram.botToken '<BOT_TOKEN>'
openclaw config set channels.telegram.dmPolicy pairing

Discord:

openclaw config set channels.discord.enabled true
openclaw config set channels.discord.botToken '<BOT_TOKEN>'
openclaw config set channels.discord.dmPolicy pairing

飞书:

openclaw config set channels.feishu.enabled true
openclaw config set channels.feishu.token '<BOT_TOKEN>'
openclaw config set channels.feishu.dmPolicy pairing

7b. 配置代理(中国大陆服务器 + 海外频道时必须)

适用场景:服务器在中国大陆,且频道是 Telegram 或 Discord(需访问海外 API)。 飞书是国内服务,不需要代理。

先测试是否需要代理(自动判断):

# Telegram
curl -s --connect-timeout 3 https://api.telegram.org/bot<BOT_TOKEN>/getMe
# Discord
curl -s --connect-timeout 3 https://discord.com/api/v10/users/@me -H "Authorization: Bot <BOT_TOKEN>"

如果超时/失败,说明需要代理。用用户提供的代理测试:

export http_proxy='<PROXY_URL>' && export https_proxy='<PROXY_URL>'
curl -s --connect-timeout 5 https://api.telegram.org/bot<BOT_TOKEN>/getMe

确认返回成功后,写入 systemd override。

重要:proxy.conf 必须一次性写完所有环境变量(代理 + no_proxy + DISPLAY), 不要分多次追加,后续浏览器步骤也不需要再改这个文件。

mkdir -p ~/.config/systemd/user/openclaw-gateway.service.d
cat > ~/.config/systemd/user/openclaw-gateway.service.d/proxy.conf << EOF
[Service]
Environment="http_proxy=<PROXY_URL>"
Environment="https_proxy=<PROXY_URL>"
Environment="no_proxy=127.0.0.1,localhost,::1"
Environment="NO_PROXY=127.0.0.1,localhost,::1"
Environment="DISPLAY=:99"
EOF

为什么要 no_proxy:没有它,OpenClaw 连本地 Chrome CDP 端口也会走代理, 导致浏览器启动成功但 OpenClaw 检测不到,报 Failed to start Chrome CDP。 即使当前不配浏览器,也要加上 no_proxy,防止后续开启浏览器时踩坑。

为什么要 DISPLAY=:99:浏览器需要显示服务器,即使 headless 模式也需要。 如果不配浏览器,这个变量无害。

7c. 重启网关(必须通过 systemd!)

关键坑点openclaw config set 会触发内部热重启,但热重启不会加载 systemd 的环境变量(代理)。 所有涉及频道/代理的配置改完后,必须用 systemctl --user restart 来重启。

export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user daemon-reload
systemctl --user restart openclaw-gateway.service

7d. 验证频道连接

等待 5 秒后:

openclaw channels status

确认输出包含 enabled, configured, running

如果看到 fetch failedNetwork request failed:代理没生效,检查 proxy.conf 并确认是通过 systemctl 重启的。

7e. 配对用户

让用户在对应频道给 Bot 发一条消息。Bot 会回复配对码。

关键坑点:配对命令是 openclaw pairing approve <channel> <CODE>不是 openclaw devices approve <CODE>(那个会报 unknown requestId)。

# 查看待配对列表(必须指定频道名)
openclaw pairing list <channel>
# 例如:openclaw pairing list telegram

# 批准配对
openclaw pairing approve <channel> <PAIRING_CODE>
# 例如:openclaw pairing approve telegram BAEWET79

配对码有效期约 1 小时。如果过期,让用户重新发消息即可生成新码。


第八步:配置浏览器控制(可选)

让 Bot 能操控浏览器,执行搜索、截图、填表等操作。 如果第一步用户选了「暂不配置」,跳过此步。

8a. 安装 Playwright Chromium + Xvfb

不要用系统自带的 Chromium。滚动发行版(Arch 等)会出现 ICU/libFLAC 等库版本不匹配, 稳定发行版也可能版本过旧。统一用 Playwright 自带的 Chromium,一条命令解决。

# 安装 Playwright Chromium(自带所有依赖,不依赖系统库)
npx playwright install chromium

# 安装 Xvfb 虚拟显示(无头服务器必须)
# Arch:
pacman -S --noconfirm xorg-server-xvfb
# Debian/Ubuntu:
apt install -y xvfb

验证 Playwright Chromium 可运行:

PLAYWRIGHT_CHROME=$(find ~/.cache/ms-playwright/chromium-*/chrome-linux/chrome 2>/dev/null | head -1)
$PLAYWRIGHT_CHROME --version

8b. 创建 Chromium Wrapper + 代理中继(需要代理时)

为什么需要 wrapper

  1. OpenClaw 自动检测 /usr/bin/chromium 来启动浏览器,wrapper 让它用 Playwright 版本
  2. Chromium 不支持 http_proxy URL 中嵌入的认证(user:pass@host),会报 ERR_INVALID_AUTH_CREDENTIALS
  3. wrapper 自动 unset 代理环境变量,改用 --proxy-server 指向本地无认证代理中继

如果需要代理(中国大陆访问海外站点),先搭建本地代理中继:

# 创建代理中继脚本(Node.js,去掉认证层,Chrome 直连)
cat > ~/.openclaw/proxy-relay.js << 'RELAYEOF'
const http = require("http");
const net = require("net");
const UPSTREAM_HOST = process.env.PROXY_HOST || "127.0.0.1";
const UPSTREAM_PORT = parseInt(process.env.PROXY_PORT || "7890", 10);
const UPSTREAM_USER = process.env.PROXY_USER || "";
const UPSTREAM_PASS = process.env.PROXY_PASS || "";
const LOCAL_PORT = parseInt(process.env.RELAY_PORT || "7891", 10);
const authHeader = UPSTREAM_USER
  ? "Basic " + Buffer.from(UPSTREAM_USER + ":" + UPSTREAM_PASS).toString("base64")
  : null;
const server = http.createServer((req, res) => {
  const opts = { host: UPSTREAM_HOST, port: UPSTREAM_PORT, path: req.url,
    method: req.method, headers: { ...req.headers } };
  if (authHeader) opts.headers["Proxy-Authorization"] = authHeader;
  const proxy = http.request(opts, (pRes) => { res.writeHead(pRes.statusCode, pRes.headers); pRes.pipe(res); });
  proxy.on("error", (e) => { res.writeHead(502); res.end(String(e)); });
  req.pipe(proxy);
});
server.on("connect", (req, clientSocket, head) => {
  const connectReq = "CONNECT " + req.url + " HTTP/1.1\r\nHost: " + req.url + "\r\n"
    + (authHeader ? "Proxy-Authorization: " + authHeader + "\r\n" : "") + "\r\n";
  const proxySocket = net.connect(UPSTREAM_PORT, UPSTREAM_HOST, () => { proxySocket.write(connectReq); });
  let buf = Buffer.alloc(0);
  const onData = (chunk) => {
    buf = Buffer.concat([buf, chunk]);
    const idx = buf.indexOf("\r\n\r\n");
    if (idx === -1) return;
    proxySocket.removeListener("data", onData);
    const resp = buf.slice(0, idx).toString();
    const rest = buf.slice(idx + 4);
    if (resp.includes(" 200 ")) {
      clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
      if (rest.length) clientSocket.write(rest);
      if (head.length) proxySocket.write(head);
      proxySocket.pipe(clientSocket); clientSocket.pipe(proxySocket);
    } else { clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n"); proxySocket.end(); }
  };
  proxySocket.on("data", onData);
  proxySocket.on("error", () => clientSocket.destroy());
  clientSocket.on("error", () => proxySocket.destroy());
});
server.listen(LOCAL_PORT, "127.0.0.1", () => {
  console.log("proxy-relay listening on 127.0.0.1:" + LOCAL_PORT + " -> " + UPSTREAM_HOST + ":" + UPSTREAM_PORT);
});
RELAYEOF

# 创建 systemd 服务(用实际的代理信息替换变量)
cat > ~/.config/systemd/user/proxy-relay.service << EOF
[Unit]
Description=Local Proxy Relay (auth-stripping for Chrome)
Before=openclaw-gateway.service

[Service]
ExecStart=/usr/bin/node /root/.openclaw/proxy-relay.js
Environment="PROXY_HOST=<UPSTREAM_PROXY_HOST>"
Environment="PROXY_PORT=<UPSTREAM_PROXY_PORT>"
Environment="PROXY_USER=<UPSTREAM_PROXY_USER>"
Environment="PROXY_PASS=<UPSTREAM_PROXY_PASS>"
Environment="RELAY_PORT=7891"
Restart=always
RestartSec=3

[Install]
WantedBy=default.target
EOF

export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user daemon-reload
systemctl --user enable --now proxy-relay.service

# 验证中继工作
curl -s --connect-timeout 5 -x http://127.0.0.1:7891 https://api.telegram.org | head -1

架构说明

Chrome --proxy-server=127.0.0.1:7891 → proxy-relay(无认证)→ 上游代理(带认证)→ 海外网站

创建 wrapper(根据是否需要代理,选择对应版本):

# 备份系统 chromium(如果有)
mv /usr/bin/chromium /usr/bin/chromium.system 2>/dev/null

# 获取 Playwright Chrome 路径
PLAYWRIGHT_CHROME=$(find ~/.cache/ms-playwright/chromium-*/chrome-linux/chrome 2>/dev/null | head -1)

# === 需要代理的版本 ===
cat > /usr/bin/chromium << EOF
#!/bin/bash
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY
exec $PLAYWRIGHT_CHROME --proxy-server=http://127.0.0.1:7891 "\$@"
EOF

# === 不需要代理的版本 ===
# cat > /usr/bin/chromium << EOF
# #!/bin/bash
# unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY
# exec $PLAYWRIGHT_CHROME "\$@"
# EOF

chmod +x /usr/bin/chromium

# 验证
/usr/bin/chromium --version

注意:写 wrapper 文件时避免通过 SSH 传递 #!/bin/bash, 某些 shell(zsh)会把 ! 转义为 \!。建议用 scp 或在远程服务器上直接编辑。

8c. 启动 Xvfb 服务

cat > ~/.config/systemd/user/xvfb.service << 'EOF'
[Unit]
Description=Xvfb Virtual Framebuffer
Before=openclaw-gateway.service

[Service]
ExecStart=/usr/bin/Xvfb :99 -screen 0 1280x720x24 -nolisten tcp
Restart=always
RestartSec=3

[Install]
WantedBy=default.target
EOF

export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user daemon-reload
systemctl --user enable --now xvfb.service

8d. 配置 OpenClaw 浏览器(三个必设项 + profile)

三个配置缺一不可,否则 Chrome 无法在无头服务器上以 root 运行:

openclaw config set browser.enabled true
openclaw config set browser.headless true    # 无头模式,不开会尝试打开 GUI
openclaw config set browser.noSandbox true   # root 运行必须,否则 Chrome 静默崩溃

创建 headless profile(使用 openclaw 驱动,不用 Chrome 扩展):

openclaw browser create-profile --name headless --driver openclaw --color '#0066CC'
openclaw config set browser.defaultProfile headless

不要用默认的 extension driver。extension driver 需要在 Chrome 里安装 OpenClaw 扩展, 无头服务器上不可能操作。--driver openclaw 使用 Playwright 直接驱动。

8e. 确保 systemd 环境变量完整

如果第 7 步已经写了 proxy.conf(含 no_proxyDISPLAY=:99),这里不需要再改。 如果第 7 步没配代理(不需要代理的场景),这里只需加 DISPLAY

# 仅当第 7 步没写过 proxy.conf 时才需要
mkdir -p ~/.config/systemd/user/openclaw-gateway.service.d
cat > ~/.config/systemd/user/openclaw-gateway.service.d/proxy.conf << 'EOF'
[Service]
Environment="DISPLAY=:99"
EOF

8f. 清理 + 验证配置 + 重启

重要:先运行 openclaw doctor --fix 清理可能的无效配置 key, 避免网关因配置校验失败反复崩溃重启。

# 清理无效配置
openclaw doctor --fix

# 杀掉可能残留的 Chrome 进程
killall -9 chrome chrome_crashpad 2>/dev/null
rm -f ~/.openclaw/browser/headless/user-data/SingletonLock 2>/dev/null

# 重启网关
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user daemon-reload
systemctl --user restart openclaw-gateway.service
sleep 5

# 测试导航(用简单站点,首次连接较慢属正常)
openclaw browser navigate https://example.com --timeout 60000

# 测试快照
openclaw browser snapshot | head -20

# 确认状态(应显示 running: true)
openclaw browser status

8g. 低功耗设备性能调优(ARM/树莓派等)

适用场景:树莓派、ARM 开发板等低功耗设备。x86 服务器通常不需要。 核心问题:Chrome 使用 swiftshader-webgl(CPU 软件渲染),ARM CPU 处理复杂页面极慢。 内存不是瓶颈(8GB 足够),CPU 才是。

问题 1:浏览器操作超时 20 秒不够

OpenClaw 硬编码了 20 秒超时(timeoutMs: 2e4),低功耗设备上需要 60 秒。

# 修改所有浏览器操作超时:20s → 60s
for f in pi-embedded-*.js reply-*.js; do
  FILE="/usr/lib/node_modules/openclaw/dist/$f"
  [ -f "$FILE" ] && sed -i 's/timeoutMs: 2e4/timeoutMs: 6e4/g' "$FILE"
done

# 修改浏览器 CLI 命令超时
for f in browser-cli-*.js; do
  FILE="/usr/lib/node_modules/openclaw/dist/$f"
  [ -f "$FILE" ] && sed -i 's/timeoutMs: 2e4/timeoutMs: 6e4/g; s/?? 2e4/?? 6e4/g' "$FILE"
done

# 修改默认代理超时常量
sed -i 's/DEFAULT_BROWSER_PROXY_TIMEOUT_MS = 2e4/DEFAULT_BROWSER_PROXY_TIMEOUT_MS = 6e4/g' \
  /usr/lib/node_modules/openclaw/dist/pi-embedded-*.js \
  /usr/lib/node_modules/openclaw/dist/reply-*.js

问题 2:Playwright CDP 连接超时太短

Playwright 连接 Chrome 有 3 次重试(5s/7s/9s),ARM 设备上不够。改为 15s/20s/25s:

# 增加 Playwright CDP 连接超时
sed -i 's/const timeout = 5e3 + attempt \* 2e3/const timeout = 15e3 + attempt * 5e3/g' \
  /usr/lib/node_modules/openclaw/dist/pw-ai-*.js

问题 3:Chrome 重启后恢复旧标签

Chrome 从 user-data 恢复上次会话的所有标签页,在低功耗设备上多标签会耗尽 CPU。 每次重启网关前需要清理:

# 关闭所有 Chrome 标签(保留一个空白页)
curl -s http://127.0.0.1:18801/json/list 2>/dev/null | \
  python3 -c "
import sys,json,urllib.request
try:
  tabs=json.load(sys.stdin)
  for t in tabs[1:]:
    urllib.request.urlopen(f'http://127.0.0.1:18801/json/close/{t[\"id\"]}')
  print(f'Closed {len(tabs)-1} tabs')
except: pass
" 2>/dev/null

注意:以上源码修改会在 OpenClaw 更新后被覆盖,需要重新执行。

低功耗设备浏览器使用建议

  • 避免重型 SPA(Google News、Twitter 等),超过 60 秒仍会超时
  • 优先用轻量级/文本版网站(CNN Lite、RSS feeds、API 接口)
  • 简单页面(example.com、百度)通常 3-5 秒内完成
  • 首次浏览器操作较慢(Playwright 需建立 CDP 连接),后续操作快很多

8h. X/Twitter 登录(可选)

适用场景:让 Bot 能以已登录身份浏览 X/Twitter(查看时间线、搜索等)。 需要先完成 8a-8f 的浏览器配置。

前置条件:用户需提供 X/Twitter 的 auth_token(40 位十六进制)。 这个 token 可从已登录浏览器的 Cookies 中提取(开发者工具 → Application → Cookies → x.com → auth_token)。

核心经验不要尝试在服务器上自动化登录流程! 直接注入 auth_token 是唯一可靠方案。 我们尝试过 7+ 种登录方案全部失败(见踩坑总结 22-30),原因包括:

  • X 的登录页在 ARM + swiftshader 上无法渲染
  • API 登录流程会触发 IP 限频(error 399)
  • Python HTTP 客户端被 Cloudflare TLS 指纹识别拦截
  • Chrome 跨域请求(CORS)限制无法绕过
  • CDP Fetch 拦截开启后 fetch 调用全部报错

获取 auth_token

用户需要在自己的电脑浏览器上获取 auth_token(服务器上无法完成登录):

  1. 在电脑浏览器登录 x.com
  2. 打开开发者工具(F12)→ Application → Cookies → https://x.com
  3. 找到 auth_token,复制值(40 位十六进制字符串)

关键理解:之前"成功登录"实际上不是在服务器上完成登录,而是复用了一个已有的有效 auth_token。 本质是"贴了一张有效的门票",不是"重新买票"。服务器上没有任何可靠方式完成 X 的登录流程。

注入 auth_token 到 Chrome

# /tmp/x-inject-token.py — 通过 CDP 注入 auth_token
import json, socket, struct, hashlib, base64, time, os, urllib.request

CDP_URL = "http://127.0.0.1:18801"
AUTH_TOKEN = "<用户提供的auth_token>"

# ... CDP 类(见下方完整代码)...

# 1. 清除旧的 x.com/twitter.com cookies
all_ck = c.cmd("Network.getAllCookies")
for ck in all_ck.get("cookies", []):
    dom = ck.get("domain", "")
    if "x.com" in dom or "twitter.com" in dom:
        c.cmd("Network.deleteCookies", {"name": ck["name"], "domain": dom})

# 2. 注入 auth_token(必须带 expires 参数!)
expires = time.time() + 365 * 86400
for dom in [".x.com", ".twitter.com"]:
    c.cmd("Network.setCookie", {
        "name": "auth_token", "value": AUTH_TOKEN,
        "domain": dom, "path": "/", "secure": True, "httpOnly": True,
        "expires": expires, "sameSite": "None"
    })

# 3. 导航到 x.com/home 触发会话建立
c.cmd("Page.navigate", {"url": "https://x.com/home"})
time.sleep(25)  # ARM 设备需要更长等待

# 4. 验证:页面标题应为 "(N) Home / X",且 cookies 包含 ct0 和 twid
all_ck = c.cmd("Network.getAllCookies")
# 从 Network 层读取 cookies(ct0 是 httpOnly,JS 读不到)

# 5. 保存全部 cookies(含 ct0、twid 等会话后生成的 cookie)
x_ck = {}
for ck in all_ck.get("cookies", []):
    dom = ck.get("domain", "")
    if "x.com" in dom or "twitter.com" in dom:
        x_ck[ck["name"]] = ck["value"]
with open(os.path.expanduser("~/.openclaw/x-cookies.json"), "w") as f:
    json.dump(x_ck, f, indent=2)

# 6. 回写全部 cookies 并带 expires(确保持久化到 Chrome 用户目录)
for name, val in x_ck.items():
    if isinstance(val, str):
        for dom in [".x.com", ".twitter.com"]:
            c.cmd("Network.setCookie", {
                "name": name, "value": val, "domain": dom,
                "path": "/", "secure": True,
                "httpOnly": name in ("auth_token", "ct0"),
                "expires": expires, "sameSite": "None"
            })

Cookie 持久化(关键!)

大坑Network.setCookie 不带 expires 参数时,cookie 只存在内存中, Chrome 重启后全部丢失,Twitter 检测到 session 断开会吊销 auth_token。 一旦 token 被吊销就永久失效,必须用户重新在电脑上登录获取新 token。

必须做的两件事:

  1. 注入时必须带 expires 参数(设为 1 年后),让 Chrome 写入磁盘持久化
  2. 登录成功后回写全部 cookies:导航到 x.com/home 后,Twitter 会生成 ct0twid 等新 cookie, 这些 cookie 也需要用 Network.setCookie + expires 回写一遍,否则它们也是内存态

验证 cookie 是否持久化: 重启 Chrome 后,检查 cookies 是否还在:

all_ck = c.cmd("Network.getAllCookies")
auth = [ck for ck in all_ck.get("cookies", []) if ck["name"] == "auth_token"]
print(f"auth_token exists: {len(auth) > 0}")

验证成功的标志

  • 页面标题包含 Home / X(不是 Login 或空白页)
  • Cookies 中出现 ct0(CSRF token)和 twid(用户 ID)
  • URL 保持在 x.com/home(没被重定向到登录页)

保存 cookies 供其他工具使用:

# cookies 保存位置
~/.openclaw/x-cookies.json

重要ct0 cookie 是 httpOnly 的,只能通过 CDP Network.getAllCookies 读取, 不能用 document.cookie 读(会返回空)。这是一个常见误判——看到 ct0: none 就以为登录失败, 实际上页面标题 Home / X 已经证明登录成功了。

安装 x-tweet-fetcher skill(可选):

让 Bot 能通过命令获取 X/Twitter 推文内容:

# 手动下载安装(clawhub install 经常限频)
SKILL_DIR="/usr/lib/node_modules/openclaw/skills/x-tweet-fetcher"
mkdir -p "$SKILL_DIR/scripts"

# 下载 SKILL.md 和 fetch_tweet.py
curl -sL "https://raw.githubusercontent.com/ythx-101/x-tweet-fetcher/main/SKILL.md" \
  -o "$SKILL_DIR/SKILL.md"
curl -sL "https://raw.githubusercontent.com/ythx-101/x-tweet-fetcher/main/scripts/fetch_tweet.py" \
  -o "$SKILL_DIR/scripts/fetch_tweet.py"
chmod +x "$SKILL_DIR/scripts/fetch_tweet.py"

# 验证
openclaw skills list 2>&1 | grep tweet

fetch_tweet.py 使用 urllib.request.urlopen(),这个函数不会自动使用 https_proxy 环境变量。 如果服务器需要代理才能访问外网,必须手动添加 ProxyHandler:

# 在 fetch_tweet.py 的 import 部分加 import os
# 在 urlopen 之前加:
proxy_url = os.environ.get("https_proxy") or os.environ.get("http_proxy") or ""
if proxy_url:
    opener = urllib.request.build_opener(
        urllib.request.ProxyHandler({"https": proxy_url, "http": proxy_url})
    )
else:
    opener = urllib.request.build_opener()
# 然后用 opener.open(url) 替换 urllib.request.urlopen(url)

第九步:最终验证

# 1. 健康检查
openclaw health

# 2. 频道状态
openclaw channels status

# 3. 测试模型连通(命令行)
openclaw agent --local --agent main -m '你好,简短确认你是什么模型'

# 4. systemd 服务状态
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user status openclaw-gateway.service | head -5

# 5. 浏览器状态(如已配置)
openclaw browser status

全部通过后,输出总结表格:

项目状态
OpenClaw版本号
模型provider/model-id
网关systemd active (running)
Linger已启用
频道频道名 + running
Bot 链接t.me/xxx_bot
代理有/无
浏览器running / 未配置

保存凭证

在用户本地工作目录保存一份凭证备忘录:

文件名:openclaw-credentials.txt
内容:服务器地址、Bot 名称、Bot Token、模型 provider

踩坑总结(按流程顺序)

编号原因正确做法
1安装脚本报 /dev/tty 错误非交互式 SSH 无 TTY忽略,OpenClaw 已装好,手动写配置
2openclaw channels add --channel telegramUnknown channelTelegram 不是通过 channels add 配置的openclaw config set channels.telegram.*
3Telegram API 连不上(fetch failed中国大陆无法直连 api.telegram.org必须配代理,写入 systemd 环境变量
4代理配了但重启后还是 fetch failedopenclaw config set 触发热重启不加载 systemd 环境变量必须用 systemctl --user restart
5openclaw devices approve <CODE>unknown requestId用错命令了正确命令:openclaw pairing approve telegram <CODE>
6openclaw pairing listChannel required必须指定频道参数openclaw pairing list telegram
7配置验证报错 dmPolicy="open" requires allowFrom to include "*"open 模式需要显式设置 allowFrompairing 模式,不要改成 open
8gateway 需要 gateway.mode 才能启动手动写配置时漏了配置文件里加 "gateway": {"mode": "local"}
9State integrity 报目录缺失首次安装未创建必要目录mkdir -p ~/.openclaw/agents/main/sessions ~/.openclaw/credentials
10退出 SSH 后网关停止systemd user session 无 lingerloginctl enable-linger
11系统 Chromium 启动报库版本不匹配Arch 等滚动发行版 ICU/libFLAC 版本不兼容用 Playwright 自带 Chromium,创建 wrapper 替换 /usr/bin/chromium
12Chrome extension relay not reachable at 127.0.0.1:18792默认 profile 用 extension driver,需要 Chrome 扩展创建 headless profile:--driver openclaw(用 Playwright 直接驱动)
13Failed to start Chrome CDP on port 18801(Chrome 已启动但检测不到)http_proxy 导致 OpenClaw 连 localhost CDP 也走代理systemd 环境变量加 no_proxy=127.0.0.1,localhost,::1
14浏览器导航报 net::ERR_INVALID_AUTH_CREDENTIALSChromium 不支持 http_proxy URL 中的 user:pass@ 认证格式wrapper 脚本中 unset http_proxy https_proxy
15browser.headlessbrowser.noSandbox 未设置默认都是 false,root 无头运行必须开启openclaw config set browser.headless true + browser.noSandbox true
16浏览器操作超时 timed out after 20000msOpenClaw 硬编码 20 秒超时,ARM 设备 CPU 软件渲染太慢修改源码 timeoutMs: 2e46e4(见 8g 节)
17Playwright CDP 连接超时 Timeout 9000ms exceeded首次连接重试 5s/7s/9s 不够,ARM 上 Chrome 初始化慢修改源码 5e3 + attempt * 2e315e3 + attempt * 5e3(见 8g 节)
18重启后浏览器操作卡死(14 个标签恢复)Chrome 从 user-data 恢复旧会话标签,多标签耗尽 CPU重启前用 CDP API 关闭多余标签,或清理 user-data
19Google News 等重型 SPA 始终超时ARM + swiftshader 软件渲染,复杂 JS/CSS 渲染超过 60 秒用轻量级网站或 RSS/API 替代,无法通过增加超时解决
20Chrome 能访问国内站但无法访问海外站wrapper 中 unset 了代理变量,Chrome 无法直连海外搭建本地代理中继 + --proxy-server=127.0.0.1:7891(见 8b 节)
21SSH 传输 wrapper 脚本 shebang 变成 #\!zsh 的历史扩展把 ! 转义成 \!用 scp 传输文件,或在远程服务器上直接编辑
22X 登录页显示 "Something went wrong"ARM + swiftshader 软件渲染无法运行 X 的 React SPA不要用浏览器自动化登录,直接注入 auth_token
23Twitter API 登录返回 error 399 "Could not log you in now"多次失败尝试触发 IP 级别限频不要反复重试登录 API,一旦被限频需等待数小时
24Python twikit/httpx 被 Cloudflare 403 拦截Python HTTP 客户端的 TLS 指纹与浏览器不同只能通过真实浏览器(Chrome CDP)发起请求
25Chrome fetch + credentials: 'include' 报 CORS 错误x.com 向 api.twitter.com 带凭据的跨域请求触发 preflight 失败用 CDP Fetch.enable 在协议层注入 Cookie 头绕过 CORS
26document.cookie 读不到 ct0ct0 是 httpOnly cookie,JS 无权访问用 CDP Network.getAllCookies 从协议层读取
27urllib.request.urlopen() 不走代理urllib 的 urlopen 默认不使用 https_proxy 环境变量显式创建 ProxyHandler + build_opener
28env 文件 source 报 command not found密码/token 中含 | 等特殊字符未加引号,bash 把 | 后面当命令执行env 文件中所有值用单引号包裹
29Chrome 重启后 X 登录丢失,auth_token 被吊销Network.setCookie 不带 expires 只存内存,重启后丢失;Twitter 检测 session 断开后吊销 token注入 cookie 时必须带 expires(1 年),登录后回写全部 cookie 也带 expires
30服务器上所有自动登录方案均失败(API/twikit/CDP Fetch/Chrome JS/CORS)ARM 软件渲染 + Cloudflare TLS 指纹 + CORS 限制 = 无法在服务器完成登录放弃在服务器登录,让用户在自己电脑浏览器获取 auth_token 后注入

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

product-discovery

No summary provided by upstream source.

Repository SourceNeeds Review
General

app-ui-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

harmonyos-app

No summary provided by upstream source.

Repository SourceNeeds Review
General

product-ux-expert

No summary provided by upstream source.

Repository SourceNeeds Review
openclaw-deploy | V50.AI