# 云端部署

OpenHuman 是一个桌面应用，但它的 **Rust 核心** (`openhuman-core`）是一个可托管在云端的无头 JSON-RPC 服务器。单独部署核心适用于：

* 多设备访问，让多个桌面客户端指向同一个托管核心
* 没有本地 Rust 工具链的内部测试人员
* 应当比笔记本会话存活更久的长期运行 cron 作业 / webhook

本指南介绍四种部署路径，按容易程度排序如下：

1. [DigitalOcean App Platform：一键部署](#1-digitalocean-app-platform-one-click)
2. [DigitalOcean App Platform：通过 doctl 手动部署](#2-digitalocean-app-platform-manual-via-doctl)
3. [任何 VPS：通过 Docker Compose](#3-any-vps-via-docker-compose)
4. [Fly.io](#4-flyio)

每种路径中部署的内容：一个运行 `openhuman-core serve` 在端口 `7788`上的单个容器。公共主机应位于提供商的 TLS 后面，例如 `https://core.example.com/rpc`。仅私有主机在 localhost、RFC1918 网络或 Tailscale 等 tailnet 上时，在核心无法从公共互联网访问的情况下，可以使用纯 HTTP，例如 `http://100.x.x.x:7788/rpc`。桌面应用已经知道如何与远程核心通信；设置 `OPENHUMAN_CORE_RPC_URL` 和 `OPENHUMAN_CORE_TOKEN=...` 在 `app/.env.local` 中并启动。

***

## 远程 UI 选项

OpenHuman 支持的远程部署方式是 **核心远程，UI 本地**：在 Linux 服务器上运行 `openhuman-core` ，并让桌面客户端指向该 RPC URL。已部署的核心目前还不能作为生产 Web 应用提供完整的 React/Tauri UI。仅桌面功能仍然需要 Tauri shell，包括托盘控制、原生深度链接、CEF 账户扫描器、操作系统钥匙串集成以及窗口/屏幕相关能力。

如果你今天想在私有服务器上使用可通过浏览器访问的 UI，可将 Vite web 构建作为面向远程核心的开发/预览界面：

```bash
# 在服务器上，使用显式 token 运行核心。
export OPENHUMAN_CORE_HOST=0.0.0.0
export OPENHUMAN_CORE_PORT=7788
export OPENHUMAN_CORE_TOKEN="$(openssl rand -hex 32)"
openhuman-core serve

# 在服务器上的另一个 shell 中，仅在回环地址上提供 UI。
pnpm --dir app dev -- --host 127.0.0.1 --port 1420
```

然后从你的工作站隧道转发这两个端口：

```bash
ssh -L 1420:127.0.0.1:1420 -L 7788:127.0.0.1:7788 user@server
```

打开 `http://127.0.0.1:1420`，在首次运行界面选择 remote/core 选项，并输入 `http://127.0.0.1:7788/rpc` 以及 `OPENHUMAN_CORE_TOKEN` 在服务器上的值。

如果你从非回环来源提供浏览器 UI，请将该确切来源添加到核心的 CORS 允许列表中：

```bash
export OPENHUMAN_CORE_ALLOWED_ORIGINS="https://openhuman-ui.example.com"
```

像这样的回环 Vite 来源 `http://127.0.0.1:1420` 和 `http://localhost:1420` 会被自动允许。公共 `http://` 来源不推荐，因为每个 RPC 调用都会携带 bearer token。

***

## Bearer token 的唯一真实来源

每个 `/rpc` 调用都会携带 `Authorization: Bearer <token>`。核心在启动时有两种加载该 token 的方式（[`src/core/auth.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/core/auth.rs)):

1. **`OPENHUMAN_CORE_TOKEN` 环境变量** — 由调用方预先注入（Tauri shell、Docker、App Platform、systemd 单元，……）。核心会原样使用此值，并且 **绝不会** 写入文件。
2. **`{workspace}/core.token` 文件** — 由核心在首次启动时生成 *仅当 `OPENHUMAN_CORE_TOKEN` 未设置时*。独立运行的 `openhuman core run` 会使用这个方式，以便 CLI 客户端可以 `cat` 该文件。

**任何远程 / 容器化部署的经验法则：始终设置 `OPENHUMAN_CORE_TOKEN`.** 不要依赖 `core.token` 在容器中——临时文件系统会在重新部署时丢失它，而任何试图从容器外读取该文件的客户端都会得到过期或空值。这两条路径在启动时被刻意设计为互斥；把它们混用，是“我重新部署后仪表板收到 401”最常见的原因。

要检查 *正在运行的* 核心正在使用什么，请运行 [`scripts/print-core-token.sh`](https://github.com/tinyhumansai/openhuman/blob/main/scripts/print-core-token.sh) 在主机上（或在容器内使用 `docker compose exec`):

```bash
scripts/print-core-token.sh --where     # 打印 'env' 或 'file:/path'
scripts/print-core-token.sh --redact    # 前 8 个十六进制字符 + '…'（适合日志，安全）
scripts/print-core-token.sh             # 完整值（可直接管道传给客户端）
```

桌面应用的首次运行选择器还提供了一个 **测试连接** 按钮，位于 Core RPC URL + token 字段旁边，它会对 `core.ping` 使用输入的 token 向该 URL 发起请求，并报告 `已连接 ✓` / `认证失败` / `无法访问` ，在持久化配置之前内联显示。

***

## 开始之前你需要准备什么

| 设置项                    | 必需 | 说明                                                                                   |
| ---------------------- | -- | ------------------------------------------------------------------------------------ |
| `OPENHUMAN_CORE_TOKEN` | 是  | 客户端发送到 `/rpc`的 Bearer token。使用 `openssl rand -hex 32`. **生成。任何拥有此 token 的人都可以操控核心。** |
| `BACKEND_URL`          | 是  | 核心通信的 Tinyhumans 后端（`https://api.tinyhumans.ai` 用于生产环境）。                             |
| `OPENHUMAN_APP_ENV`    | 否  | `production` 或 `staging`。默认值为 `production`.                                          |
| `OPENHUMAN_CORE_HOST`  | 否  | 默认值为 `0.0.0.0` 在容器中。                                                                 |
| `OPENHUMAN_CORE_PORT`  | 否  | 默认值为 `7788`.                                                                         |
| `RUST_LOG`             | 否  | `info` 即可； `debug` 用于排障。                                                             |

运行中的容器暴露的端点：

* `GET /health`，公开的存活探针。每种部署路径的 healthcheck 都会使用它。
* `POST /rpc`，受 bearer 保护的 JSON-RPC 入口点。
* `GET /events`, `GET /ws/dictation`，公开的流式通道。

这个 `OPENHUMAN_WORKSPACE` 目录（容器内的`/home/openhuman/.openhuman` ）保存核心的配置、sqlite 数据库和技能状态。 **请将它挂载到持久卷上** 在每个生产部署中，否则重启时你会丢失数据。

***

## 1. DigitalOcean App Platform：一键部署

点击下面的按钮，从此仓库的以下文件创建一个新的 App Platform 应用： [`.do/app.yaml`](https://github.com/tinyhumansai/openhuman/blob/main/.do/app.yaml):

[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/tinyhumansai/openhuman/tree/main)

然后，在 App Platform UI 中， **在首次部署完成之前**:

1. 打开 **设置 → 应用级环境变量** 标签页。
2. 将占位的 `OPENHUMAN_CORE_TOKEN` 值替换为强密钥（`openssl rand -hex 32`）。将其标记为加密。
3. 如果你部署的是预发布环境，请将 `OPENHUMAN_APP_ENV` 改为 `staging` 和 `BACKEND_URL` 改为 `https://staging-api.tinyhumans.ai`.
4. 点击 **保存**。App Platform 会使用新的密钥重新部署。

App Platform 处理 TLS、崩溃自动重启、日志流和基于以下操作的滚动重部署： `git push` （设置 `deploy_on_push: true` 在 `.do/app.yaml` 即可启用）。

> **持久化说明：** App Platform Basic 不提供块存储。核心的工作区位于容器的临时文件系统中，重新部署时会丢失。若要获得持久存储，请附加托管数据库或升级到支持卷的套餐。参见 [Compose 路径](#3-any-vps-via-docker-compose) ，获取一种开箱即用支持持久卷的自托管替代方案。

***

## 2. DigitalOcean App Platform：通过 doctl 手动部署

如果你不想逐步点击 UI：

```bash
# 一次性操作：安装 doctl 并进行认证。
doctl auth init

# 编辑 .do/app.yaml - 将 OPENHUMAN_CORE_TOKEN 设置为真实值（或在
# 创建时通过带 envsubst 的 --spec 传入）。然后：
doctl apps create --spec .do/app.yaml

# 观察构建：
doctl apps list
doctl apps logs <app-id> --type build --follow
```

编辑 spec 后更新现有应用：

```bash
doctl apps update <app-id> --spec .do/app.yaml
```

***

## 3. 任何 VPS：通过 Docker Compose

适用于任何安装了 Docker Engine ≥ 24 和 Compose 插件的主机。DigitalOcean Droplet、Hetzner、Linode、EC2、家庭服务器。

每个生产版本都会向 GHCR 发布带多个标签的镜像：

```bash
docker pull ghcr.io/tinyhumansai/openhuman-core:latest        # 跟踪最新生产版本
docker pull ghcr.io/tinyhumansai/openhuman-core:v1.2.4        # 通过 GitHub Release 标签固定
docker pull ghcr.io/tinyhumansai/openhuman-core:1.2.4         # 通过 SemVer 固定
```

该镜像是 `linux/amd64`。arm64 主机会拉取附加在同一 GitHub Release 上的独立 tarball（`openhuman-core-<version>-aarch64-unknown-linux-gnu.tar.gz`）或在 arm64 构建器上从源码构建镜像。

使用已发布镜像的快速运行方式：

```bash
docker run -d --name openhuman-core -p 7788:7788 \
  -e OPENHUMAN_CORE_TOKEN="$(openssl rand -hex 32)" \
  -e BACKEND_URL=https://api.tinyhumans.ai \
  -e OPENHUMAN_APP_ENV=production \
  -v openhuman-workspace:/home/openhuman/.openhuman \
  ghcr.io/tinyhumansai/openhuman-core:latest
```

或者使用仓库内的 Compose 文件（仍会从本地 `Dockerfile`构建镜像；将 `image:` 字段切换到 `ghcr.io/tinyhumansai/openhuman-core:latest` 在 `docker-compose.yml` 以改为使用已发布镜像）：

```bash
# 在服务器上：
git clone https://github.com/tinyhumansai/openhuman.git
cd openhuman

# 配置密钥：
cp .env.example .env
# 编辑 .env - 至少包括：
#   BACKEND_URL=https://api.tinyhumans.ai
#   OPENHUMAN_CORE_TOKEN=<openssl rand -hex 32>
#   OPENHUMAN_APP_ENV=production

# 构建并启动：
docker compose up -d

# 验证：
docker compose ps
curl -fsS http://localhost:7788/health
```

### 无头安装，不使用 Docker

如果你无法在主机上运行 Docker，请抓取附加在最新 [GitHub Release](https://github.com/tinyhumansai/openhuman/releases/latest):

```bash
# 选择与你主机架构匹配的 tarball。
ARCH="$(uname -m)"
case "$ARCH" in
  x86_64)  TARGET=x86_64-unknown-linux-gnu  ;;
  aarch64) TARGET=aarch64-unknown-linux-gnu ;;
  *) echo "不支持的架构：$ARCH"; exit 1 ;;
esac
VERSION=1.2.4   # 设置为你想要的版本
curl -fsSL "https://github.com/tinyhumansai/openhuman/releases/download/v${VERSION}/openhuman-core-${VERSION}-${TARGET}.tar.gz" \
  | tar -xz -C /usr/local/bin
openhuman-core --version
```

然后运行 `openhuman-core serve` 在你选择的服务管理器下（systemd、supervisord，……），并使用上文记录的相同环境变量。

### 无头自更新约定

无头部署应将 `openhuman.update_apply` 视为安全原语：它会下载发布资源，以原子方式将其写入当前二进制文件旁边，然后返回。不会自动退出任何进程。

`openhuman.update_run` 遵循 `config.update.restart_strategy`:

* `self_replace` （默认）：暂存二进制文件，发布进程内重启请求，并让正在运行的核心自行重新生成。
* `supervisor`：暂存二进制文件并返回 `restart_requested=false`。你的外部服务管理器必须重启该进程。

对于长期运行的 Linux 服务，请设置：

```toml
[update]
restart_strategy = "supervisor"
rpc_mutations_enabled = false
```

或等效的环境变量：

```bash
OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY=supervisor
OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED=false
```

推荐的 `systemd` 做法：

```ini
Restart=always
ExecReload=/bin/kill -HUP $MAINPID
```

运维流程：

1. 调用 `openhuman.update_check` 以发现一个发布版本。
2. 在你的 `restart_strategy = "supervisor"` 中配置 `update.toml` （或设置 `OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY=supervisor`），这样核心会暂存新的二进制文件而不尝试自行重新执行，然后调用 `openhuman.update_apply` 或 `openhuman.update_run`. `restart_strategy` 是一个配置设置，不是 RPC 参数。
3. 显式重启该单元： `systemctl restart openhuman`.

如果下载或暂存失败，正在运行的二进制文件会保持不变，且不会请求重启。如果暂存的二进制文件在重启后被证明有问题，可通过从包管理器、镜像标签或发布工件恢复先前的二进制文件并再次重启 supervisor 来回滚。

Compose 文件（[`docker-compose.yml`](https://github.com/tinyhumansai/openhuman/blob/main/docker-compose.yml)）将核心映射到 `:7788`，挂载一个名为 `openhuman-workspace` 的卷用于持久化，并设置 `restart: unless-stopped` ，使核心在主机重启后恢复运行。

### 更新

```bash
git pull
docker compose build
docker compose up -d
```

对于暴露 RPC 的生产部署，建议保持会修改状态的更新 RPC 处于禁用状态（`OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED=false`），并通过你现有的镜像标签或包管理流程执行发布。

### 日志

```bash
docker compose logs -f openhuman-core
```

### 轮换 bearer token

`OPENHUMAN_CORE_TOKEN` 是公共互联网与完整 RPC 访问之间唯一的屏障。请按计划轮换，并在任何疑似泄露后立即轮换：

```bash
# 1. 生成一个新 token，并更新服务器端的 .env。
openssl rand -hex 32 > /tmp/new-token
sed -i.bak "s|^OPENHUMAN_CORE_TOKEN=.*|OPENHUMAN_CORE_TOKEN=$(cat /tmp/new-token)|" .env
rm /tmp/new-token .env.bak

# 2. 重启容器，让新值传递到核心进程。
docker compose up -d --force-recreate openhuman-core

# 3. 确认运行中的容器正在使用新 token（已脱敏）。
docker compose exec openhuman-core /bin/sh -c \
  'echo -n "$OPENHUMAN_CORE_TOKEN" | head -c 8; echo "…"'

# 4. 更新每一个桌面客户端（切换模式 → 在选择器中重新粘贴，或
# 编辑 app/.env.local 中的 OPENHUMAN_CORE_TOKEN 并重新启动）。仍然
# 持有旧 token 的客户端会在下一次 /rpc 调用时收到 HTTP 401 —— 这是
# 预期行为，不是回归。
```

对于 App Platform，也在 **设置 → 应用级环境变量**中执行相同操作：编辑 `OPENHUMAN_CORE_TOKEN` secret 并让 App Platform 重新部署。没有需要删除的单独 token 文件；环境变量是唯一状态。

### 将其置于 TLS 之后

在 `:7788`前面使用 Caddy、nginx 或 Traefik 作为反向代理。一个最小的 `Caddyfile`:

```caddy
core.example.com {
  reverse_proxy localhost:7788
}
```

***

## 让桌面应用指向托管核心

在桌面应用的环境文件中（`app/.env.local`):

```bash
# 使用托管核心，而不是生成本地 sidecar。
OPENHUMAN_CORE_RUN_MODE=external
OPENHUMAN_CORE_RPC_URL=https://core.example.com/rpc
OPENHUMAN_CORE_TOKEN=<你在服务器上设置的同一个 token>
```

对于没有公共 IP 的仅私有 tailnet VM，请改用 tailnet URL：

```bash
OPENHUMAN_CORE_RUN_MODE=external
OPENHUMAN_CORE_RPC_URL=http://100.x.x.x:7788/rpc
OPENHUMAN_CORE_TOKEN=<你在服务器上设置的同一个 token>
```

重启桌面应用。 `App.tsx` 中的 provider 链会将所有 RPC 调用路由到远程核心；其他内容都不会改变。公共 `http://` 主机会被应用选择器拒绝；对于任何可公开访问的核心，请使用 HTTPS。

***

## 命名卷的所有权和 Docker 入口点

Docker 创建的命名卷默认归 `root:root` 所有。因为核心以非 root 的 `openhuman` 用户（UID 10001）运行，所以在横幅之后的第一次写入—— `init_rpc_token → write_token_file` 到 `$OPENHUMAN_WORKSPACE` ——会引发 `Permission denied（os error 13）` ，如果没有先修复所有权的话。

该镜像在 `/usr/local/bin/docker-entrypoint-core.sh` 处附带了一个专用入口点，它会：

1. 以 `root`.
2. 身份启动 `运行` + `chown openhuman:openhuman` 于 `$OPENHUMAN_WORKSPACE` 和 `$HOME/.openhuman` （当目录 `core.token` 在 `OPENHUMAN_CORE_TOKEN` 未设置时会被写入）。
3. 调用 `exec gosu openhuman openhuman-core "$@"` 以降低权限并把控制权交给该二进制文件。

这是 **幂等的**：在新创建的卷上，chown 会修复 root 拥有的目录；在已经修复过的卷上，chown 不会产生任何效果。从早于此修复的镜像升级时，不需要手动执行 `docker volume rm` 。

该入口点名为 `docker-entrypoint-core.sh` 并且 **仅** 接入到根 `Dockerfile`中。E2E 镜像（`e2e/docker-entrypoint.sh`）不受影响。

***

## 4. Fly.io

[Fly.io](https://fly.io) 非常适合 `openhuman-core`：它会自动处理 TLS，在所有套餐上都支持持久卷，并且可以自动停止空闲机器以降低成本。

### 前置条件

* [flyctl](https://fly.io/docs/flyctl/install/) 已安装并已认证（`fly auth login`)
* 一个 Fly.io 账户

### 步骤 1 — 启动应用

```bash
fly launch --no-deploy --config .fly/fly.toml
```

Fly.io 会自动检测 `Dockerfile` 。请选择一个靠近用户的区域，并在提示时跳过首次部署。这会生成一个配置文件。

### 步骤 2 — 配置 `.fly/fly.toml`

仓库在 [`.fly/fly.toml`](https://github.com/tinyhumansai/openhuman/blob/main/.fly/fly.toml)处附带了一个模板。填写 `<你的应用名称>` 和 `<你的区域>` 为你在 `fly launch`:

```toml
期间选择的值
app = '<你的应用名称>'

primary_region = '<你的区域>'
  [build]

dockerfile = "Dockerfile"
  [env]
  OPENHUMAN_CORE_HOST = "0.0.0.0"
  OPENHUMAN_CORE_PORT = "7788"
  OPENHUMAN_WORKSPACE = "/home/openhuman/.openhuman"

RUST_LOG = "info"
  [[mounts]]
  source = "openhuman_workspace"

destination = "/home/openhuman/.openhuman"
  [http_service]
  internal_port = 7788
  force_https = true
  auto_stop_machines = 'stop'
  # min_machines_running = 0 会在空闲时完全停止机器（最便宜），但
  # 空闲后的第一请求会支付冷启动代价（容器启动 +
  # Rust 二进制初始化——需要几秒）。设为 1 可保持一台机器预热。
  min_machines_running = 0
  processes = ['app']

  [[http_service.checks]]
    interval = "30s"
    timeout = "5s"
    grace_period = "10s"
    method = "GET"
    path = "/health"

[[vm]]
  memory = '1gb'
  cpus = 1
```

### 步骤 3 — 创建持久卷

```bash
fly volumes create openhuman_workspace --size 5 --region <your-region> --config .fly/fly.toml
```

**将工作区挂载到持久卷上** 否则每次重新部署时数据都会丢失。

### 步骤 4 — 设置密钥

```bash
# 必需
fly secrets set OPENHUMAN_CORE_TOKEN="$(openssl rand -hex 32)"
fly secrets set BACKEND_URL="https://api.tinyhumans.ai"
fly secrets set OPENHUMAN_APP_ENV="production"

# 对任何可公开访问的部署，建议设置：
fly secrets set OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED="false"
fly secrets set OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY="supervisor"

# 可选 — 错误报告和分析：
fly secrets set OPENHUMAN_CORE_SENTRY_DSN="https://<key>@o<org>.ingest.sentry.io/<project>"
fly secrets set OPENHUMAN_ANALYTICS_ENABLED="true"
```

保存 `OPENHUMAN_CORE_TOKEN` 的值 — 之后将桌面应用连接起来时你会用到它。 **任何拥有此令牌的人都可以驱动核心**；请像对待密码一样对待它，并使用 `fly secrets set OPENHUMAN_CORE_TOKEN="$(openssl rand -hex 32)"` 在怀疑泄露后立即轮换。

### 步骤 5 — 部署

```bash
fly deploy --config .fly/fly.toml
```

验证核心是否健康：

```bash
curl -fsS https://<your-app-name>.fly.dev/health
```

### 步骤 6 — 将桌面应用指向托管的核心

在 `app/.env.local`:

```bash
OPENHUMAN_CORE_RUN_MODE=external
OPENHUMAN_CORE_RPC_URL=https://<your-app-name>.fly.dev/rpc
OPENHUMAN_CORE_TOKEN=<the token you set in Step 4>
```

或者使用 **首次运行选择器** 在桌面应用中（Core RPC URL + 令牌字段，以及一个 **测试连接** 按钮）进行配置，无需编辑文件。

### 持续部署

要在每次推送到时自动重新部署 `main`，请在以下位置添加一个工作流文件： `.github/workflows/fly-deploy.yml`:

```yaml
name: Fly Deploy
on:
  push:
    branches:
      - main
    paths:
      - 'src/**'
      - 'Cargo.toml'
      - 'Cargo.lock'
      - 'Dockerfile'
      - '.fly/fly.toml'
      - 'scripts/docker-entrypoint-core.sh'
jobs:
  deploy:
    name: Deploy openhuman-core
    runs-on: ubuntu-latest
    concurrency: deploy-group
    steps:
      - uses: actions/checkout@v4
      # 将 Fly action 锁定到带标签的发布版本（或完整的 commit SHA），而不是
      # `@master`——跟踪一个不断变化的分支意味着信任该分支未来的每一次提交，
      # 包括任何被入侵的维护者账号推送的提交。
      - uses: superfly/flyctl-actions/setup-flyctl@1.5
      - run: flyctl deploy --remote-only --config .fly/fly.toml
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
```

使用以下命令生成部署令牌 `fly tokens create deploy` 并将其作为一个仓库密钥添加，名称为 `FLY_API_TOKEN`.

### 更新

```bash
fly deploy --config .fly/fly.toml
```

对于版本锁定的部署，请更新中的镜像标签 `.fly/fly.toml` 并重新部署：

```toml
primary_region = '<你的区域>'
  image = "ghcr.io/tinyhumansai/openhuman-core:v1.2.4"
```

### 日志

```bash
fly logs --config .fly/fly.toml
```

### 已知坑点 — 卷上的 UID 不匹配

如果你在以下两种构建方式之间切换： `Dockerfile` （它会创建 `openhuman` UID 为 10001 的用户）和拉取预构建的 GHCR 镜像（它使用 UID 1000），那么已经写入持久卷的文件将归旧 UID 所有，并在启动时产生 `Permission denied（os error 13）` 。

通过 SSH 进入并重新设置工作区所有权来修复：

```bash
fly ssh console --config .fly/fly.toml
chown -R openhuman:openhuman /home/openhuman/.openhuman/
exit
fly machine restart --config .fly/fly.toml
```

***

## 冒烟测试

该仓库随附 [`.github/workflows/deploy-smoke.yml`](https://github.com/tinyhumansai/openhuman/blob/main/.github/workflows/deploy-smoke.yml)，它会在每个修改部署产物的 PR 上运行。它会构建 Docker 镜像、启动它，并轮询 `/health`，因此云部署路径中的回归会在合并到之前让 CI 失败 `main`.

该工作流包含两个作业：

* **`docker-image`** — 设置 `OPENHUMAN_CORE_TOKEN` 并且不挂载任何卷。保护 DigitalOcean App Platform 路径（`.do/app.yaml`），在该路径中令牌始终预先设置且不使用持久卷。
* **`docker-volume-permissions`** — 省略 `OPENHUMAN_CORE_TOKEN` 并在以下位置挂载一个新的匿名卷 `/home/openhuman/.openhuman`。重现 issue #2065 的精确失败模式，并断言 `/health` 返回 200，且 `Permission denied（os error 13）` 不出现在日志中。

要在本地运行相同检查：

```bash
docker build -t openhuman-core:smoke .

# 可选：调整构建配置和 Cargo 并行度。
# 在资源受限的构建机上保持 CARGO_BUILD_JOBS=1；在更大的机器上可以提高它。
docker build --build-arg CARGO_PROFILE=release --build-arg CARGO_BUILD_JOBS=4 -t openhuman-core:release .

# 已设置令牌路径（App Platform）：
docker run -d --name oh-smoke -p 7788:7788 \
  -e OPENHUMAN_CORE_TOKEN=smoke-test-token \
  openhuman-core:smoke
curl -fsS http://localhost:7788/health
docker rm -f oh-smoke

# 新卷 / 无令牌路径（Docker Compose，VPS）：
docker volume create oh-vol-test
docker run -d --name oh-vol-smoke -p 7789:7788 \
  -v oh-vol-test:/home/openhuman/.openhuman \
  openhuman-core:smoke
curl -fsS http://localhost:7789/health
docker rm -f oh-vol-smoke
docker volume rm oh-vol-test
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tinyhumans.gitbook.io/openhuman/zh/gong-neng/cloud-deploy.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
