Implement edge-host-self-enrollment
Tests / Test passed: 581

Host Agent:
- One-time local operator account bootstrap (PBKDF2-HMAC-SHA256, atomic
  0600-permission write) gating the daemon's first unattended start via a
  new `setup` CLI subcommand.
- Default control-plane URL now https://amcp.home.jerryyan.top (env var
  override unchanged).
- Enrollment no longer requires a pre-issued token; falls back to
  zero-token self-service enrollment when none is configured.

Cloud control plane:
- CLOUD_SELF_SERVICE_ENROLLMENT_ENABLED (default false) opt-in flag.
- SelfServiceEnrollmentAuthProvider + ChainedEnrollmentAuthProvider:
  configured tokens still take priority; self-service only applies when
  no token matches, preserving edge-host-enrollment's token-bound path.
- Fixed a latent bug in sql_repository.py::enroll_host: the token-conflict
  lookup used `== enrollment_token_digest`, which SQLAlchemy compiles to
  `IS NULL` when the value is None, so every self-service enrollment after
  the first would have falsely collided with an existing NULL-digest host.
  Skipped that lookup entirely when the digest is None.

Docs/deploy: .env.example, compose.yaml, compose.deploy.yaml,
CLOUD_DEPLOYMENT.md, MACOS_IPHONE_SETUP.md updated for the new flag,
URL default, and required `device-host-agent setup` step.

Verification: 494 non-integration tests pass; openspec validate --strict
passes. PostgreSQL-backed contract tests and full manual end-to-end
verification were not run (no Postgres/Docker or reachable cloud-api in
this environment); noted as unchecked in tasks.md 7.2/7.4.
This commit is contained in:
2026-07-13 18:30:49 +08:00
parent a2802c6320
commit efeb3eb926
24 changed files with 838 additions and 68 deletions
+20 -6
View File
@@ -367,11 +367,21 @@ DeviceConfigStore("tasks/device_config.sqlite3").add(
PY
```
从云端管理员获取一枚未使用的一次性 enrollment token,然后启动:
首次启动前,先在交互式终端创建一次性本地操作账号(仅用于门禁"首次启动"这个
动作本身,后续无人值守重启不会再次要求):
```bash
uv run --package device-host-agent device-host-agent setup
```
`HOST_AGENT_CONTROL_PLANE_URL` 默认已固定为 `https://amcp.home.jerryyan.top`
仅在连接其他云端环境(如本地/测试用的 `cloud-api`)时才需要覆盖该变量。未设置
`HOST_AGENT_ENROLLMENT_TOKEN` 时,Host Agent 会直接向云端发起零 token 自助注册
(需要云端 `CLOUD_SELF_SERVICE_ENROLLMENT_ENABLED=true`);若仍持有云端管理员
签发的一次性 enrollment token,也可以继续设置 `HOST_AGENT_ENROLLMENT_TOKEN`
走原有的 token 注册路径:
```bash
export HOST_AGENT_CONTROL_PLANE_URL="https://cloud.example.com"
export HOST_AGENT_ENROLLMENT_TOKEN="<one-time enrollment token>"
export HOST_AGENT_IDENTITY_PATH="tasks/host_identity.json"
export HOST_AGENT_DISPLAY_NAME="Edge Mac 01"
@@ -384,9 +394,13 @@ uv run --package device-host-agent device-host-agent
首次启动顺序为:持久化候选 Host secret、向云端换取 `host_id`、为每个本地设备
换取 `device_id`、保存映射、连接 WDA、发送 heartbeat、开始 long-poll 领取任务。
Host Agent 不监听入站端口。成功后可以从边缘环境移除 enrollment token,但必须
保留并保护 `tasks/host_identity.json` `tasks/device_config.sqlite3`;前者等同于
Host bearer credential。
Host Agent 不监听入站端口。若使用了一次性 enrollment token,成功后可以将其从
边缘环境移除;但必须保留并保护 `tasks/host_identity.json`
`tasks/device_config.sqlite3`;前者等同于 Host bearer credential。
零 token 自助注册意味着任何能访问该云端地址的设备都能自行注册成为 Host,没有
审批环节,也没有限流保护;这一取舍依赖网络边界(防火墙/反向代理)而非应用层
限制,仅应在受控网络中开启 `CLOUD_SELF_SERVICE_ENROLLMENT_ENABLED`
需要回滚到静态模式时,停止 Host Agent,由云端管理员配置匹配的静态 Host
credential,然后显式设置 `HOST_AGENT_HOST_ID``HOST_AGENT_TOKEN`。这两个变量