在手机上敲一句
device-harness,服务器上就起来一个 codex 会话。它知道自己在跟哪台设备说话,也能直接读写那台设备里的文件。
手工接一台设备要走的流程:生成密钥 → 两边换公钥 → 挑一个不冲突的端口 → 建 SSH 别名 → 记笔记。接过两台之后就会发现它慢、易错、没记录 —— 真实事故是手工抄公钥漏了一个字符,排查了 20 轮。
这个仓库把那套流程收敛成一条命令,并把「这台设备是谁」变成服务器上一个可读的档案,而不是某个人脑子里的记忆。
三个角色:设备(你手边这台,Mac 或 Android 手机)、服务器(常开、算力大)、harness(服务器上真正干活的东西,默认 codex)。
设备是主动方:所有连接都由设备发起,服务器不需要能直接访问设备。这靠一条由设备建立的反向隧道实现。
flowchart LR
subgraph DEV["设备 · macOS / Termux"]
direction TB
I["install.sh<br/>① 首次接入"]
T["device-tunnel<br/>② 只开隧道"]
H["device-harness<br/>③ 隧道 + harness"]
end
subgraph SRV["服务器 · Ubuntu"]
direction TB
SSH["sshd"]
RUN["harness<br/>codex / claude"]
ARCH["设备档案<br/>skills/device-onboard/SKILL.md"]
CFG["~/.ssh/config<br/>Host onboard-device-<名字>"]
end
I -->|"换钥 · 写 config · 写档案"| SSH
T -->|"反向隧道 -R"| SSH
H --> T
H -->|"ssh -t 启动"| RUN
RUN -.->|"读档案,知道自己在谁的会话里"| ARCH
RUN -.->|"ssh onboard-device-<名字>"| SSH
CFG -.-> SSH
接入成功后,两边的 ~/.ssh/config 里各多出几个 Host:
| 别名 | 在哪用 | 干什么 |
|---|---|---|
onboard-server-<名字> |
设备 | 进服务器 / 在服务器上跑命令 |
onboard-tunnel-<名字> |
设备 | 起反向隧道(带 RemoteForward) |
onboard-device-<名字> |
服务器 | 通过反向隧道连回设备 |
- 设备:macOS,或装了 Termux 的 Android。只需要系统自带的
ssh/ssh-keygen。 - 服务器:一台常开、能 SSH 登录的机器(局域网、公网、VPN 都行)。
- macOS 需要打开 系统设置 → 通用 → 共享 → 远程登录(脚本会检查并提示)。
在设备上:
git clone https://github.com/MuRain37/device-onboard.git
cd device-onboard
sh install.sh脚本会安装 device-tunnel / device-harness 两个本地命令,然后直接进入接入设置,依次问你设备名、服务器地址、用户名、SSH 端口。
密码只由 ssh 自己提示输一次,脚本不读取、不保存、不落盘 —— 这一步是唯一的人工交互,刻意不做全自动。之后全程免密。
device-harness # 一键:起隧道 + 在服务器上跑 codex
device-harness claude # 换别的 harness
device-harness -C '~/项目' claude # 指定服务器上的工作目录
device-tunnel # 只起反向隧道,前台保持,Ctrl-C 结束
ssh onboard-server-<名字> # 进服务器普通 shell| 想做什么 | 在哪跑 | 命令 |
|---|---|---|
| 首次接入 | 设备 | sh install.sh |
| 重新安装本地命令 | 设备 | sh install.sh(检测到已有配置就跳过接入) |
| 一键干活 | 设备 | device-harness [-C <服务器目录>] [harness] [参数…] |
| 只开反向隧道 | 设备 | device-tunnel |
| 进服务器 | 设备 | ssh onboard-server-<名字> |
| 从服务器操作设备 | 服务器 | ssh onboard-device-<名字> '<命令>' |
| 跑测试 | 任意 | sh tests/smoke.sh |
这几条看着啰嗦,但每一条都是真机踩出来的。
① 就绪信号由服务器打印,不是本地瞎报。
device-tunnel 的 Host 块里有 ExitOnForwardFailure yes,所以只有服务器真把端口绑上了,才可能执行到打印「✅ 反向隧道已建立」那行。本地「我发出去了」毫无意义 —— 这是唯一能区分「隧道通了」和「端口被占」的证据。device-harness 等的就是这个信号,而不是轮询端口。
② 首次接入做端到端握手,不看进程也不看端口。
进程活着但转发已断的情况真实存在。接入时会启动一条临时隧道,让服务器真连回设备一次,必须回显 DEVICE-ONBOARD-E2E-OK 才算通过。绑上但握手失败会立刻中止 —— 并明确告诉你「换端口没用」。
③ 设备身份只走环境变量。
DEVICE_ONBOARD_ID / DEVICE_ONBOARD_DEVICE_ALIAS 由 device-harness 在 ssh -t 时传进服务器会话。没有 ~/<设备名> 这类锚点目录 —— 目录可以随处 cd,身份不行。设备里的文件也不再同步,直接 ssh onboard-device-<名字> / scp 读写。
④ codex 要带 --no-daemon。
交互式 Codex 把「执行工具命令」交给常驻的 app-server 守护进程,而那个进程继承的是它启动那一刻的环境,于是本次会话才有的 DEVICE_ONBOARD_ID 传不到工具手上(实测:工具 shell 里的 SSH_CLIENT 与两天前那个守护进程逐字一致)。--no-daemon 让工具在本次会话进程里执行,环境变量才有效。
⑤ 隧道和 harness 同生共死。
分两条命令不行就在于生命期:隧道是后台长跑,harness 是前台交互,中间断了谁都发现不了。device-harness 把它们绑在一起,退出时一定收干净。
⑥ 只改自己带标记的区块。
所有对 ~/.ssh/config、~/.codex/AGENTS.md、~/.claude/CLAUDE.md 的写入都限制在 BEGIN / END 标记之间,改前备份,标记外的手写内容一律不动。首次接入半路失败会按标记撤销、还原备份。
设备侧
| 路径 | 内容 |
|---|---|
~/.config/device-onboard/config |
本机接入配置(别名、端口、设备 ID) |
~/.ssh/device-onboard/ |
设备私钥(私钥永不出设备) |
~/.ssh/config |
新增 onboard-server-* 与 onboard-tunnel-* Host 块 |
$DEVICE_ONBOARD_BIN_DIR |
安装的 device-tunnel / device-harness |
服务器侧
| 路径 | 内容 |
|---|---|
~/.codex/skills/device-onboard/SKILL.md |
设备档案(只给探测到的 harness 建) |
~/.claude/skills/device-onboard/SKILL.md |
同上,claude 一份 |
~/.codex/AGENTS.md |
「设备会话」标记块 |
~/.claude/CLAUDE.md |
「设备会话」标记块 |
~/.ssh/config |
onboard-device-* 回连 Host 块 |
档案只记重新生成 SSH 配置所需的最小信息(设备名、端口、钥匙名、用户),不存私钥,也不存易变字段(比如「最后在线时间」—— 那是运行时事实,用时现场查)。
install.sh 支持这些覆盖项(sh install.sh --help 也能看到):
| 变量 | 默认 | 作用 |
|---|---|---|
DEVICE_ONBOARD_BIN_DIR |
macOS /usr/local/bin,Termux $PREFIX/bin |
本地命令安装目录 |
DEVICE_ONBOARD_CONFIG_DIR |
~/.config/device-onboard |
配置目录 |
DEVICE_ONBOARD_KEY_DIR |
~/.ssh/device-onboard |
密钥目录 |
DEVICE_ONBOARD_DEVICE_SSH_PORT |
macOS 22,Termux 8022 |
本机 sshd 端口 |
DEVICE_ONBOARD_REVERSE_PORT_START / _END |
2230 / 2299 |
反向端口范围(避开手工占用的低位端口) |
DEVICE_ONBOARD_AGENTS_MD |
1 |
设为 0 跳过服务器 ~/.codex/AGENTS.md 的标记块 |
DEVICE_ONBOARD_CLAUDE_MD |
1 |
设为 0 跳过服务器 ~/.claude/CLAUDE.md 的标记块 |
sh tests/smoke.sh冒烟测试用一个假的 ssh 桩件在临时 HOME 里跑完整流程,不碰真实服务器、不需要网络。
install.sh # 首次接入 + 安装本地命令(757 行,平台差异全收在 detect_platform)
bin/device-tunnel # 起反向隧道,前台保持
bin/device-harness # 编排:隧道 + harness,同生共死
tests/smoke.sh # 冒烟测试(假 ssh 桩件)
docs/flow.md # 运行流程详解,含完整 mermaid 图
docs/mvp-spec.md # MVP 规格
onboard-design.md # 方案讨论稿(背景、设计决定、待打磨的问题)
design-questions.md # 设计问题清单
CONTEXT.md # 领域词汇表
docs/adr/ # 架构决策记录
| 想了解 | 看 |
|---|---|
| 完整运行流程(装了什么、两条命令各干了什么、出错怎么退) | docs/flow.md |
| MVP 规格 | docs/mvp-spec.md |
| 为什么这么设计、还有哪些没想清楚 | onboard-design.md |
| 术语定义 | CONTEXT.md |
这套设计绑定「自己有一台服务器」,不是通用工具。明确不做:
- 不追求全自动 —— 第一次鉴权必然要人参与,也刻意不加免鉴权接口。
- 不做后台隧道服务 —— 反向隧道由
device-tunnel前台运行,不后台保活、不自启。 - 不做自动恢复 —— 首次接入以外不自动恢复,中断后重跑脚本。
- 不共享服务器 —— harness、skills、项目环境都集中在服务器,设备侧不重复安装 harness。
- 不做多设备协同 —— 不含工作区隔离、任务分发、自动合并。
- 不做受限钥匙 —— 权限给足,靠「随时可吊销」兜底。
首版支持 macOS 与 Termux;Linux 桌面端的 sshd、自启方式、路径差异都不同,尚未覆盖。