feat(cloud): add edge host enrollment
This commit is contained in:
@@ -41,6 +41,49 @@ defaults to `./tasks`.
|
||||
The Host Agent only initiates outbound HTTP requests. It does not expose an
|
||||
inbound port.
|
||||
|
||||
## Managed Edge Enrollment
|
||||
|
||||
New edge installations do not need a pre-coordinated Host or device ID. The
|
||||
Cloud API accepts configured one-time enrollment credentials:
|
||||
|
||||
```powershell
|
||||
$env:CLOUD_ENROLLMENT_TOKENS_JSON = '[{"principal_id":"edge-installer","token":"replace-with-a-long-random-one-time-token"}]'
|
||||
```
|
||||
|
||||
On the edge Host, omit `HOST_AGENT_HOST_ID` and `HOST_AGENT_TOKEN` and provide
|
||||
the enrollment token only for the first successful enrollment:
|
||||
|
||||
```bash
|
||||
export HOST_AGENT_CONTROL_PLANE_URL="https://cloud.example.com"
|
||||
export HOST_AGENT_ENROLLMENT_TOKEN="replace-with-a-long-random-one-time-token"
|
||||
export HOST_AGENT_IDENTITY_PATH="tasks/host_identity.json"
|
||||
export HOST_AGENT_DISPLAY_NAME="Edge Mac 01"
|
||||
uv run --package device-host-agent device-host-agent
|
||||
```
|
||||
|
||||
Before its first request the Host Agent creates `HOST_AGENT_IDENTITY_PATH` with
|
||||
an instance identifier and long-lived random Host secret. The cloud consumes
|
||||
the enrollment token, assigns `host_id`, stores only credential digests, and
|
||||
returns the assigned ID. The Host Agent then enrolls each record from
|
||||
`tasks/device_config.sqlite3`, stores its cloud-assigned `device_id` in that
|
||||
database, connects the resulting devices, and starts heartbeat/claim loops.
|
||||
|
||||
Keep the identity file and device configuration database on persistent edge
|
||||
storage with permissions limited to the service account. The identity file is
|
||||
a bearer secret: do not put it in an image, repository, log, or general backup.
|
||||
After successful enrollment, remove `HOST_AGENT_ENROLLMENT_TOKEN` from the edge
|
||||
environment. An intact identity file is sufficient for restart; if only a
|
||||
device mapping is lost, device enrollment reconstructs the same cloud ID.
|
||||
|
||||
Enrollment tokens are one-time even when they remain in Cloud API environment
|
||||
configuration: their consumed digest is stored in the database. Reusing a token
|
||||
for another edge instance returns a conflict. Create a distinct token for every
|
||||
edge installation.
|
||||
|
||||
Explicit `HOST_AGENT_HOST_ID` plus `HOST_AGENT_TOKEN` takes precedence and keeps
|
||||
the previous legacy behavior, including locally selected device IDs. This is
|
||||
the rollback and staged-migration path for existing deployments.
|
||||
|
||||
## PostgreSQL Deployment
|
||||
|
||||
Start from `.env.example`, replace every `change-me-*` value, and keep the
|
||||
@@ -106,11 +149,40 @@ only the scopes required by each integration:
|
||||
include exactly one `host_id`; its token is valid only for heartbeat, claim,
|
||||
renewal, and result operations for that host.
|
||||
|
||||
`CLOUD_ENROLLMENT_TOKENS_JSON` contains bootstrap principals with only
|
||||
`principal_id` and `token`. These credentials cannot submit tasks, read the
|
||||
pool, or operate as a Host; they can only create one durable Host binding.
|
||||
Use high-entropy values generated by the deployment secret manager.
|
||||
|
||||
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.
|
||||
|
||||
Dynamically enrolled Host credentials are stored as digests in the cloud
|
||||
database. This release exposes repository-level revocation rather than a public
|
||||
administration endpoint. An operator with database deployment access can revoke
|
||||
a Host without deleting its task history:
|
||||
|
||||
```bash
|
||||
export HOST_ID="host-..."
|
||||
uv run --package device-cloud-platform python - <<'PY'
|
||||
import os
|
||||
from cloud.database import CloudDatabase
|
||||
from core.models import utc_now
|
||||
|
||||
database = CloudDatabase(os.environ["CLOUD_DATABASE_URL"], create_schema=False)
|
||||
try:
|
||||
changed = database.repository.revoke_enrolled_host(
|
||||
os.environ["HOST_ID"],
|
||||
revoked_at=utc_now(),
|
||||
)
|
||||
print("revoked" if changed else "not an enrolled host")
|
||||
finally:
|
||||
database.close()
|
||||
PY
|
||||
```
|
||||
|
||||
## Runtime AI Planner
|
||||
|
||||
The Host Agent reuses the local Runtime planner. AI planning is disabled by
|
||||
@@ -167,17 +239,24 @@ 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
|
||||
3. Before rolling back to a release without enrollment support, provision
|
||||
temporary static Host credentials for every managed edge that must continue
|
||||
operating. Stop those Host Agents and set their explicit Host ID/token.
|
||||
4. 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
|
||||
5. Restore the previous application image or checkout and start the Cloud API.
|
||||
6. Verify `/health/ready`, then restart Host Agents with credentials compatible
|
||||
with the restored Cloud API.
|
||||
|
||||
Downgrading revision 0002 removes dynamic credential bindings and durable
|
||||
device enrollment mappings. It retains the revision-0001 Host heartbeat rows,
|
||||
pooled devices, queued tasks, attempts, and plugins.
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user