From e28705b0f27281edce6d33f408863fb8e9b7ff6b Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 13 Jul 2026 07:58:24 +0800 Subject: [PATCH] docs(cloud): add deployment runbook --- .env.example | 7 + README.md | 3 + compose.yaml | 6 + docs/CLOUD_DEPLOYMENT.md | 142 ++++++++++++++++++ .../cloud-control-plane-integration/tasks.md | 2 +- tests/test_deployment_config.py | 3 + 6 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 docs/CLOUD_DEPLOYMENT.md diff --git a/.env.example b/.env.example index 213db69..fe6061f 100644 --- a/.env.example +++ b/.env.example @@ -18,3 +18,10 @@ HOST_AGENT_POLL_TIMEOUT_SECONDS=20 HOST_AGENT_RETRY_BACKOFF_SECONDS=1 HOST_AGENT_MAX_RETRY_BACKOFF_SECONDS=30 HOST_AGENT_MAX_RETRY_ATTEMPTS=5 + +AI_PLANNER_ENABLED=false +AI_PLANNER_PROVIDER=anthropic +AI_PLANNER_MODEL= +AI_PLANNER_TIMEOUT_SECONDS=30 +ANTHROPIC_API_KEY= +OPENAI_API_KEY= diff --git a/README.md b/README.md index 22a4bf6..2d6be9a 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,9 @@ invariants future changes must preserve are in ## Operator Guides +- [Cloud Control Plane deployment](docs/CLOUD_DEPLOYMENT.md): run local SQLite + or deployed PostgreSQL, configure credentials and Runtime AI planning, and + perform orderly shutdown or rollback. - [macOS migration and real iPhone setup](docs/MACOS_IPHONE_SETUP.md): install Xcode, Appium/XCUITest, sign WebDriverAgent, verify a real device, and start a connected Runtime API. diff --git a/compose.yaml b/compose.yaml index b5ea86a..63e76ee 100644 --- a/compose.yaml +++ b/compose.yaml @@ -63,6 +63,12 @@ services: HOST_AGENT_RETRY_BACKOFF_SECONDS: ${HOST_AGENT_RETRY_BACKOFF_SECONDS:-1} HOST_AGENT_MAX_RETRY_BACKOFF_SECONDS: ${HOST_AGENT_MAX_RETRY_BACKOFF_SECONDS:-30} HOST_AGENT_MAX_RETRY_ATTEMPTS: ${HOST_AGENT_MAX_RETRY_ATTEMPTS:-5} + AI_PLANNER_ENABLED: ${AI_PLANNER_ENABLED:-false} + AI_PLANNER_PROVIDER: ${AI_PLANNER_PROVIDER:-anthropic} + AI_PLANNER_MODEL: ${AI_PLANNER_MODEL:-} + AI_PLANNER_TIMEOUT_SECONDS: ${AI_PLANNER_TIMEOUT_SECONDS:-30} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + OPENAI_API_KEY: ${OPENAI_API_KEY:-} volumes: - ${HOST_AGENT_TASKS_PATH:-./tasks}:/app/tasks depends_on: diff --git a/docs/CLOUD_DEPLOYMENT.md b/docs/CLOUD_DEPLOYMENT.md new file mode 100644 index 0000000..28d2ec7 --- /dev/null +++ b/docs/CLOUD_DEPLOYMENT.md @@ -0,0 +1,142 @@ +# Cloud Control Plane Deployment + +This guide covers the deployable Cloud API and outbound Device Host Agent. +Run commands from the repository root after synchronizing the uv workspace: + +```bash +uv sync --locked --all-packages +``` + +## Local SQLite + +SQLite is intended for local development and tests with one Cloud API process. +Configure public and host credentials even in local mode because anonymous +development access cannot authorize a host identity. + +```powershell +$env:CLOUD_ENVIRONMENT = "local" +$env:CLOUD_DATABASE_URL = "sqlite:///cloud/cloud.sqlite3" +$env:CLOUD_PUBLIC_CREDENTIALS_JSON = '[{"principal_id":"local-sdk","token":"replace-public-token","scopes":["tasks:submit","tasks:read","pool:read","plugins:read","plugins:admin"]}]' +$env:CLOUD_HOST_CREDENTIALS_JSON = '[{"principal_id":"local-host","token":"replace-host-token","scopes":[],"host_id":"host-local"}]' +uv run --package device-cloud-api device-cloud-api --host 127.0.0.1 --port 8001 +``` + +In a second terminal, start the Host Agent with the matching host identity and +token: + +```powershell +$env:HOST_AGENT_CONTROL_PLANE_URL = "http://127.0.0.1:8001" +$env:HOST_AGENT_HOST_ID = "host-local" +$env:HOST_AGENT_TOKEN = "replace-host-token" +uv run --package device-host-agent device-host-agent +``` + +At startup, the Host Agent loads device registrations from +`tasks/device_config.sqlite3`, the same `DeviceConfigStore` used by the local +Runtime console API. Register or update devices before starting the Host Agent, +then restart it to reload changes. In Compose, +`HOST_AGENT_TASKS_PATH` selects the host directory mounted at `/app/tasks`; it +defaults to `./tasks`. + +The Host Agent only initiates outbound HTTP requests. It does not expose an +inbound port. + +## PostgreSQL Deployment + +Start from `.env.example`, replace every `change-me-*` value, and keep the +resulting `.env` file outside version control. The Compose stack contains +PostgreSQL, the Cloud API, and one Host Agent: + +```bash +docker compose up --build -d +docker compose ps +``` + +The Cloud API container waits for PostgreSQL, runs the committed Alembic +migrations, then starts the API on port `8001`. Readiness is available at: + +```text +GET http://127.0.0.1:8001/health/ready +``` + +For a deployment that manages processes outside Compose, apply migrations +before starting the new Cloud API version: + +```powershell +$env:CLOUD_DATABASE_URL = "postgresql+psycopg://USER:PASSWORD@HOST:5432/device_cloud" +uv run alembic -c packages/cloud-platform/cloud/migrations/alembic.ini upgrade head +uv run --package device-cloud-api device-cloud-api --host 0.0.0.0 --port 8001 +``` + +Production startup requires `CLOUD_ENVIRONMENT=production`, a current schema, +and at least one configured bearer credential. + +## Credentials And Scopes + +`CLOUD_PUBLIC_CREDENTIALS_JSON` is a JSON array of public API principals. Grant +only the scopes required by each integration: + +- `tasks:submit`: submit tasks. +- `tasks:read`: read task status and failure metadata. +- `pool:read`: list hosts and devices. +- `plugins:read`: list installed plugin registrations. +- `plugins:admin`: register installed plugin entry points. + +`CLOUD_HOST_CREDENTIALS_JSON` contains Host Agent principals. Every entry must +include exactly one `host_id`; its token is valid only for heartbeat, claim, +renewal, and result operations for that host. + +Do not place bearer tokens in command history, image layers, Compose files, or +logs. Use environment injection or the deployment platform's secret manager. +Rotate a token by deploying the updated Cloud API credential set and Host Agent +configuration together. + +## Runtime AI Planner + +The Host Agent reuses the local Runtime planner. AI planning is disabled by +default. Configure it in the Host Agent environment when goal assignments must +use a model: + +```text +AI_PLANNER_ENABLED=true +AI_PLANNER_PROVIDER=anthropic +AI_PLANNER_MODEL=claude-sonnet-5 +AI_PLANNER_TIMEOUT_SECONDS=30 +ANTHROPIC_API_KEY= +``` + +For OpenAI, set `AI_PLANNER_PROVIDER=openai`, choose the deployed model through +`AI_PLANNER_MODEL`, and provide `OPENAI_API_KEY`. Provider credentials belong +only on the Host Agent; the Cloud API does not need them. + +## Shutdown And Rollback + +For a normal shutdown, stop Host Agents first so they stop polling, interrupt +later cooperative actions, finish terminal reporting where the lease remains +valid, and attempt a final heartbeat. Stop the Cloud API after Host Agents have +exited, then stop PostgreSQL only if the database itself is being maintained: + +```bash +docker compose stop host-agent +docker compose stop cloud-api +docker compose stop postgres +``` + +For rollback: + +1. Stop all Host Agents and the Cloud API. +2. Back up PostgreSQL or the SQLite database file. +3. If the previous application version cannot use the current schema, run the + tested downgrade while no application process is connected: + + ```bash + uv run alembic -c packages/cloud-platform/cloud/migrations/alembic.ini downgrade -1 + ``` + +4. Restore the previous application image or checkout and start the Cloud API. +5. Verify `/health/ready`, then restart Host Agents with credentials compatible + with the restored Cloud API. + +Do not remove the PostgreSQL volume during an application rollback. Queued and +attempt history are durable database state and should remain available to the +restored or forward-deployed control plane. diff --git a/openspec/changes/cloud-control-plane-integration/tasks.md b/openspec/changes/cloud-control-plane-integration/tasks.md index 5b5fb48..73ef7e0 100644 --- a/openspec/changes/cloud-control-plane-integration/tasks.md +++ b/openspec/changes/cloud-control-plane-integration/tasks.md @@ -66,7 +66,7 @@ - [x] 8.1 Extend public task status models/routes with attempt count, lease expiry metadata, and terminal failure details without exposing lease credentials. - [x] 8.2 Add bearer authentication and typed authorization errors to `CloudClient` while preserving injectable HTTP clients for tests. - [x] 8.3 Add container definitions and example environment configuration for the cloud API, PostgreSQL, and Host Agent without committing secrets. -- [ ] 8.4 Document local SQLite startup, deployed PostgreSQL migration/startup, credential/scopes setup, Runtime AI Planner configuration, and shutdown/rollback procedures. +- [x] 8.4 Document local SQLite startup, deployed PostgreSQL migration/startup, credential/scopes setup, Runtime AI Planner configuration, and shutdown/rollback procedures. - [ ] 8.5 Document the single scheduler-enabled control-plane limitation and the at-least-once device-side-effect trade-off. ## 9. Verification And Project Records diff --git a/tests/test_deployment_config.py b/tests/test_deployment_config.py index 697b893..c1f225b 100644 --- a/tests/test_deployment_config.py +++ b/tests/test_deployment_config.py @@ -28,6 +28,9 @@ def test_compose_defines_database_control_plane_and_outbound_host_agent() -> Non assert services["host-agent"]["environment"][ "HOST_AGENT_CONTROL_PLANE_URL" ] == "http://cloud-api:8001" + assert services["host-agent"]["environment"]["AI_PLANNER_ENABLED"] == ( + "${AI_PLANNER_ENABLED:-false}" + ) def test_container_uses_locked_workspace_install_and_migrations() -> None: