一行命令 Bootstrap 新设备¶
理念:人类只做最少特权操作,buddy agent 接管后续。
概述¶
这套方法论解决了一个经典问题:如何让一台全新的设备快速接入 Agent 生态,同时把人类需要手动操作的步骤压缩到最低?
核心思路:
- 人类在新设备上执行一条 bootstrap 命令
- 脚本自动安装必要组件,建立公网 SSH 隧道
- 客户端 buddy agent 通过隧道 SSH 进入新设备
- buddy agent 完成剩余所有配置
人类只需要: 运行一行命令,然后把隧道地址告诉 agent。
脚本仓库:https://github.com/shazhou-ww/oc-bootstrap
流程图¶
sequenceDiagram
participant H as 人类(新设备)
participant S as Bootstrap 脚本
participant CF as Cloudflare Quick Tunnel
participant A as Buddy Agent(客户端)
H->>S: bash <(curl -fsSL <script_url>)
S->>S: 安装依赖(cloudflared、OpenClaw 等)
S->>CF: cloudflared tunnel --url tcp://localhost:22
CF-->>S: 返回隧道域名 xxx.trycloudflare.com
S->>H: 显示隧道地址,等待 agent 接管
H->>A: 把隧道地址发给 agent
A->>CF: cloudflared access tcp --hostname xxx.trycloudflare.com --url localhost:2222
A->>A: ssh -p 2222 user@localhost
A->>A: 执行剩余配置(复制公钥、配置 OpenClaw 等)
快速上手¶
新设备端(人类操作)¶
在新机器的终端执行:
脚本会:
- 检测系统类型(macOS / Ubuntu / Debian)
- 安装
cloudflared(如未安装) - 启动 Quick Tunnel 暴露本地 22 端口
- 在终端打印隧道地址,格式类似:
✅ Tunnel ready: https://abc-def-ghi.trycloudflare.com
Tell your agent: ssh -o ProxyCommand="cloudflared access tcp --hostname abc-def-ghi.trycloudflare.com --url localhost:2222" user@localhost
- 脚本保持前台运行,不要关闭终端,等待 agent 接管完成
客户端 Buddy Agent 操作¶
收到隧道地址后,agent 执行:
# 建立本地代理(后台运行)
cloudflared access tcp --hostname abc-def-ghi.trycloudflare.com --url localhost:2222 &
# SSH 进入新设备
ssh -p 2222 username@localhost
进入后完成剩余配置:复制 SSH 公钥、安装 OpenClaw、配置 Telegram Bot Token 等。
验收标准¶
Bootstrap 完成的标志:
- 可以通过正式 SSH 公钥直接登录新设备(无需密码)
- OpenClaw 已安装并启动
- Agent 可以通过 Telegram 收到新设备的心跳
- 关闭 Quick Tunnel 后,通过正式方式(Tailscale / 固定 SSH)仍可访问
技术方案¶
Bootstrap 脚本执行方式¶
为什么不用 curl | bash
curl | bash 会把 curl 的 stdout 接到 bash 的 stdin,导致脚本内的 read 命令无法从终端读取用户输入。即使加 < /dev/tty 重定向,在 macOS 上也不稳定。
正确做法:用 bash <(curl ...) 进程替换(Process Substitution),脚本的 stdin 仍然连接到终端。
原理对比:
建立公网 SSH 隧道¶
新设备上通过 Cloudflare Quick Tunnel 暴露 SSH 端口:
命令执行后,cloudflared 会输出一个类似 https://xxx-yyy-zzz.trycloudflare.com 的临时域名。
--protocol http2 参数
较新版本的 cloudflared 已默认使用 http2 协议,此参数可省略。如果你的 VPN/代理环境下遇到连接问题,可以显式加上确保不走 QUIC。
Quick Tunnel 的优势
- 零依赖:不需要 Cloudflare 账号、不需要域名、不需要 Named Tunnel 配置
- 零持久化:隧道随进程启动/销毁,不留痕迹
- http2 协议:比默认的 QUIC/UDP 更稳定,避免被某些 VPN 劫持
隧道地址提取技巧
cloudflared 的隧道地址输出在 stderr,可以用以下方式提取:
客户端连接隧道¶
Buddy agent 在自己的机器上建立本地代理,再 SSH 连入:
# 第一步:建立本地 TCP 代理(后台运行)
cloudflared access tcp --hostname xxx-yyy-zzz.trycloudflare.com --url localhost:2222 &
# 第二步:SSH 到本地代理端口
ssh -p 2222 user@localhost
两步也可以合并为 SSH ProxyCommand:
ssh -o ProxyCommand="cloudflared access tcp --hostname %h --url localhost:2222" \
-p 22 user@xxx-yyy-zzz.trycloudflare.com
Bootstrap 脚本的工作内容¶
脚本按顺序执行以下操作:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 检测 OS 类型 | macOS/Ubuntu/Debian,选择对应包管理器 |
| 2 | 安装 cloudflared | brew / apt / 直接下载二进制 |
| 3 | 确认 SSH 服务运行 | macOS 需要手动开启"远程登录"(系统设置 → 通用 → 共享 → 远程登录),Linux 通常已启动 |
| 4 | 启动 Quick Tunnel | cloudflared tunnel --url tcp://localhost:22 --protocol http2 |
| 5 | 提取并显示隧道地址 | 解析 cloudflared stderr 输出 |
| 6 | 等待 agent 接管 | 保持前台运行,Ctrl+C 可中断 |
踩过的坑¶
1. curl | bash 导致 read 失效¶
问题:curl https://... | bash 模式下,脚本中的交互式 read 命令无法读取用户输入,因为 stdin 已被 curl 的输出占据。
尝试过的修复(均不可靠):
- read var < /dev/tty:在 Linux 可能有效,但在 macOS 不稳定
- 提前关闭 stdin:无法从脚本内部做到
正确方案:bash <(curl -fsSL ...) 进程替换,脚本拿到真正的 tty stdin。
2. Quick Tunnel 必须用 tcp:// 而非 ssh://¶
问题:cloudflared tunnel --url ssh://localhost:22 命令会失败或无法正常工作。
原因:Quick Tunnel 的 --url 参数期望 HTTP/HTTPS/TCP URL,SSH 协议需要通过 TCP 隧道承载。
正确命令:
3. cloudflared access ssh vs cloudflared access tcp¶
| 命令 | 要求 |
|---|---|
cloudflared access ssh |
需要在 Cloudflare Zero Trust Dashboard 配置 SSH Application,绑定域名 |
cloudflared access tcp |
无需任何配置,直接代理 TCP 流量 |
Quick Tunnel 场景用 cloudflared access tcp,不需要任何 Zero Trust 配置。
4. Surge VPN 劫持 cloudflared 流量¶
问题:在开启 Surge 等 VPN/代理软件的 macOS 上,cloudflared 的 QUIC/UDP 流量可能被劫持,导致隧道无法建立或连接不稳定。
解决方案:
-
在 Surge 配置中添加 bypass 规则:
-
或者强制 cloudflared 使用 http2(TCP)而非 QUIC:
5. Named Tunnel 需要账号 + 域名¶
Quick Tunnel(cloudflared tunnel --url)和 Named Tunnel(cloudflared tunnel create)是两种不同模式:
| 特性 | Quick Tunnel | Named Tunnel |
|---|---|---|
| 需要 CF 账号 | ❌ 不需要 | ✅ 需要 |
| 需要域名 | ❌ 不需要 | ✅ 需要 |
| 隧道持久化 | ❌ 临时 | ✅ 持久 |
| 适用场景 | 临时访问、bootstrap | 长期服务暴露 |
Bootstrap 场景优先用 Quick Tunnel。
6. cert.pem 是 Cloudflare Origin Cert¶
执行 cloudflared login 或某些 Named Tunnel 操作后,cloudflared 会在 ~/.cloudflared/cert.pem 存放凭证文件。
注意:这个 cert.pem 是 Cloudflare 专有的 Origin Certificate 格式,不是标准 X.509 证书。不能用 openssl x509 -in cert.pem -text 提取域名,命令会报错或输出乱码。这个文件只供 cloudflared 自身使用。
7. macOS 默认可能禁用密码 SSH¶
问题:部分 macOS 版本默认只允许公钥认证,不允许密码登录。Bootstrap 脚本依赖密码 SSH 进行首次连接。
解决方案:
- 确认
/etc/ssh/sshd_config中PasswordAuthentication设置(macOS 可能在/etc/ssh/sshd_config.d/下有覆盖文件,以最后生效的为准) - 或者:人类在 bootstrap 前先把 buddy agent 的公钥手动加入
~/.ssh/authorized_keys - 最可靠:脚本运行时提示人类输入 buddy agent 的公钥,自动写入 authorized_keys,完全绕过密码认证
安全考虑¶
隧道本身的安全性¶
- 域名随机:Quick Tunnel 的域名由 Cloudflare 随机生成(类似
abc-def-ghi.trycloudflare.com),无法被猜测或枚举 - 临时性:隧道与 cloudflared 进程绑定,进程结束隧道立即失效,不留持久入口
- 流量加密:cloudflared 到 Cloudflare 边缘节点之间全程 TLS 加密
脚本安全最佳实践¶
- 脚本中不应硬编码任何密钥、API Token 或密码
- 敏感信息(如 OpenClaw Token)应通过交互式输入(
read -s)或环境变量传递 - 脚本公开托管在 GitHub,任何人都可以审计内容
最小化暴露时间¶
- Bootstrap 完成后,立即
Ctrl+C终止 cloudflared 进程 - 改用正式的、持久化的访问方式(Tailscale、固定公网 IP + 防火墙等)
- 不要让 Quick Tunnel 长期运行,它是临时引导工具,不是生产访问方式
从 Quick Tunnel 毕业到 Named Tunnel¶
Bootstrap 阶段用 Quick Tunnel 引导新设备,但长期运行应切换到 Named Tunnel。Quick Tunnel 的域名随进程随机生成,进程一旦退出隧道即失效,不适合作为持久访问入口。
Named Tunnel 的优势¶
| 特性 | Quick Tunnel | Named Tunnel |
|---|---|---|
| 域名固定 | ❌ 每次随机 | ✅ 绑定你的域名 |
| 可配置自启 | ❌ 手动运行 | ✅ cloudflared 作为系统服务 |
| 不依赖进程存活 | ❌ 进程死则隧道断 | ✅ 服务化后自动重启 |
| 需要 CF 账号 + 域名 | ❌ 不需要 | ✅ 需要 |
切换步骤概要¶
# 1. 登录 Cloudflare(生成 ~/.cloudflared/cert.pem)
cloudflared login
# 2. 创建 Named Tunnel
cloudflared tunnel create <tunnel-name>
# 3. 编写配置文件
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: <tunnel-id>
credentials-file: /home/<user>/.cloudflared/<tunnel-id>.json
ingress:
- hostname: ssh.yourdomain.com
service: tcp://localhost:22
- service: http_status:404
EOF
# 4. 在 Cloudflare DNS 添加 CNAME 记录
# ssh.yourdomain.com CNAME <tunnel-id>.cfargotunnel.com
# 5. 安装为系统服务(可选,实现开机自启)
sudo cloudflared service install
SSH 场景示例¶
切换到 Named Tunnel 后,在客户端的 ~/.ssh/config 中配置 ProxyCommand,即可像普通 SSH 一样连接:
Host my-server
HostName ssh.yourdomain.com
User youruser
ProxyCommand cloudflared access tcp --hostname %h --url localhost:%p
之后直接 ssh my-server 即可,无需手动启动代理进程。
SSH 认证加固¶
- Bootstrap 过程依赖密码 SSH,完成后应:
- 将 agent 的 SSH 公钥写入
~/.ssh/authorized_keys - 修改
/etc/ssh/sshd_config,禁用密码登录:PasswordAuthentication no - 重启 sshd:
sudo systemctl restart sshd
- 将 agent 的 SSH 公钥写入