feat(deploy): one-click cloud deployment for OpenHuman Core (closes #1280) (#1304)

This commit is contained in:
CodeGhost21
2026-05-06 13:11:20 -07:00
committed by GitHub
parent 0c546fcffe
commit 5189ca95ef
6 changed files with 436 additions and 2 deletions
+56
View File
@@ -0,0 +1,56 @@
# DigitalOcean App Platform spec for OpenHuman Core.
#
# Deploys the headless Rust core (`openhuman-core`) from the repo Dockerfile,
# exposing the JSON-RPC server on the public HTTP port. Used by the "Deploy
# to DigitalOcean" button (see docs/CLOUD_DEPLOY.md) and by `doctl apps create`.
#
# After deploy:
# - /health is publicly reachable (no auth — used for liveness)
# - /rpc requires Authorization: Bearer $OPENHUMAN_CORE_TOKEN
#
# Operators MUST set OPENHUMAN_CORE_TOKEN to a strong secret in App Platform's
# environment editor before any client calls /rpc.
name: openhuman-core
region: nyc
services:
- name: core
dockerfile_path: Dockerfile
source_dir: /
github:
repo: tinyhumansai/openhuman
branch: main
deploy_on_push: false
instance_count: 1
instance_size_slug: basic-xs
http_port: 7788
health_check:
http_path: /health
initial_delay_seconds: 20
period_seconds: 30
timeout_seconds: 5
success_threshold: 1
failure_threshold: 3
envs:
- key: OPENHUMAN_CORE_HOST
scope: RUN_TIME
value: "0.0.0.0"
- key: OPENHUMAN_CORE_PORT
scope: RUN_TIME
value: "7788"
- key: OPENHUMAN_APP_ENV
scope: RUN_TIME
value: production
- key: BACKEND_URL
scope: RUN_TIME
value: https://api.tinyhumans.ai
- key: RUST_LOG
scope: RUN_TIME
value: info
# Required for clients to authenticate against /rpc. Set to a strong
# random value (e.g. `openssl rand -hex 32`) in the App Platform UI.
- key: OPENHUMAN_CORE_TOKEN
scope: RUN_TIME
type: SECRET
value: "CHANGE_ME_BEFORE_DEPLOY"
+123
View File
@@ -0,0 +1,123 @@
---
name: Deploy Smoke
on:
push:
branches: [main]
paths:
- Dockerfile
- .dockerignore
- docker-compose.yml
- .do/app.yaml
- docs/CLOUD_DEPLOY.md
- .github/workflows/deploy-smoke.yml
- Cargo.toml
- Cargo.lock
- rust-toolchain.toml
- src/**
pull_request:
paths:
- Dockerfile
- .dockerignore
- docker-compose.yml
- .do/app.yaml
- docs/CLOUD_DEPLOY.md
- .github/workflows/deploy-smoke.yml
- Cargo.toml
- Cargo.lock
- rust-toolchain.toml
- src/**
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
docker-image:
name: Build & smoke-test core image
runs-on: ubuntu-22.04
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build openhuman-core image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: false
load: true
tags: openhuman-core:smoke
cache-from: type=gha,scope=deploy-smoke
cache-to: type=gha,scope=deploy-smoke,mode=max
- name: Run container
run: |
docker run -d \
--name oh-smoke \
-p 7788:7788 \
-e OPENHUMAN_CORE_TOKEN=ci-smoke-token \
-e OPENHUMAN_APP_ENV=staging \
-e BACKEND_URL=https://staging-api.tinyhumans.ai \
openhuman-core:smoke
- name: Wait for /health
run: |
set -e
for i in $(seq 1 30); do
if curl -fsS http://localhost:7788/health > /tmp/health.json; then
echo "Healthy on attempt $i"
cat /tmp/health.json
exit 0
fi
echo "attempt $i: not ready, sleeping..."
sleep 2
done
echo "Container never became healthy. Logs:"
docker logs oh-smoke || true
exit 1
- name: Verify /rpc rejects without bearer token
run: |
set -e
status=$(curl -s -o /tmp/rpc.json -w "%{http_code}" \
-X POST http://localhost:7788/rpc \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"openhuman.about_app_list","params":{}}')
if [ "$status" != "401" ]; then
echo "Expected 401 from /rpc without token, got $status"
cat /tmp/rpc.json
docker logs oh-smoke || true
exit 1
fi
- name: Verify /rpc accepts the configured bearer token
run: |
set -e
status=$(curl -s -o /tmp/rpc-ok.json -w "%{http_code}" \
-X POST http://localhost:7788/rpc \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ci-smoke-token' \
-d '{"jsonrpc":"2.0","id":1,"method":"openhuman.about_app_list","params":{}}')
if [ "$status" != "200" ]; then
echo "Expected 200 from authenticated /rpc, got $status"
cat /tmp/rpc-ok.json
docker logs oh-smoke || true
exit 1
fi
cat /tmp/rpc-ok.json
- name: Container logs (always)
if: always()
run: docker logs oh-smoke || true
- name: Tear down
if: always()
run: docker rm -f oh-smoke || true
+17 -1
View File
@@ -13,11 +13,22 @@ FROM rust:1.93-bookworm AS builder
ENV DEBIAN_FRONTEND=noninteractive
# System dependencies required for compilation
# System dependencies required for compilation.
#
# ALSA / X11 / input headers are needed because `cpal`, `enigo`, `arboard`,
# and `rdev` are unconditional dependencies of the core crate (used by the
# voice, autocomplete, and clipboard subsystems). They link against system
# libraries even when the corresponding features are disabled at runtime.
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
cmake \
pkg-config \
libssl-dev \
libasound2-dev \
libxdo-dev \
libxtst-dev \
libx11-dev \
libevdev-dev \
clang \
mold \
ca-certificates \
@@ -51,6 +62,11 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
libssl3 \
libasound2 \
libxdo3 \
libxtst6 \
libx11-6 \
libevdev2 \
curl \
&& rm -rf /var/lib/apt/lists/*
+1 -1
View File
@@ -60,7 +60,7 @@ OpenHuman is an open-source agentic assistant that is designed to integrate with
- **Deep desktop integrations** — OpenHuman is a **native desktop** assistant, not a web-only chat: **memory-aware keyboard autocomplete**, **voice** (**STT** listening and **TTS** replies), **screen intelligence** that understands what is on screen and feeds your local context, plus windowing and OS-level permissions—so the agent meets you **on the machine**, not trapped in a browser tab.
Architecture: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). Contributor orientation: [`CONTRIBUTING.md`](./CONTRIBUTING.md). Running from source: [`docs/install.md`](docs/install.md#running-from-source).
Architecture: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). Contributor orientation: [`CONTRIBUTING.md`](./CONTRIBUTING.md). Running from source: [`docs/install.md`](docs/install.md#running-from-source). Cloud-hosting the headless core: [`docs/CLOUD_DEPLOY.md`](docs/CLOUD_DEPLOY.md).
## Highlights
+48
View File
@@ -0,0 +1,48 @@
# OpenHuman Core — Docker Compose for self-hosted cloud deploy.
#
# Brings up the headless Rust core (`openhuman-core`) on :7788, persists the
# workspace to a named volume, and reads secrets/config from a `.env` file
# next to this compose file.
#
# Usage:
# 1. cp .env.example .env (then edit values — at minimum BACKEND_URL and
# OPENHUMAN_CORE_TOKEN; the latter is required for any client that calls
# /rpc on this instance)
# 2. docker compose up -d
# 3. curl http://localhost:7788/health
#
# The image is built from the repo Dockerfile. To pin a published image
# instead of building, replace `build:` with `image: ghcr.io/.../openhuman-core:<tag>`.
services:
openhuman-core:
build:
context: .
dockerfile: Dockerfile
image: openhuman-core:local
container_name: openhuman-core
restart: unless-stopped
ports:
- "${OPENHUMAN_CORE_PORT:-7788}:7788"
env_file:
- .env
environment:
# Bind to 0.0.0.0 inside the container so port-forwarding works regardless
# of what `.env` says. The Dockerfile already sets this default, but make
# it explicit so an inherited shell value cannot override it.
OPENHUMAN_CORE_HOST: 0.0.0.0
OPENHUMAN_CORE_PORT: "7788"
OPENHUMAN_WORKSPACE: /home/openhuman/.openhuman
RUST_LOG: ${RUST_LOG:-info}
volumes:
- openhuman-workspace:/home/openhuman/.openhuman
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:7788/health"]
interval: 30s
timeout: 5s
start_period: 15s
retries: 3
volumes:
openhuman-workspace:
name: openhuman-workspace
+191
View File
@@ -0,0 +1,191 @@
# Cloud deployment
OpenHuman is a desktop app, but its **Rust core** (`openhuman-core`) is a
headless JSON-RPC server that can be hosted in the cloud. Deploying the core
separately is useful for:
- Multi-device access — point several desktop clients at the same hosted core
- Internal testers without local Rust toolchains
- Long-running cron jobs / webhooks that should outlive a laptop session
This guide covers three deploy paths, easiest first:
1. [DigitalOcean App Platform: one-click](#1-digitalocean-app-platform-one-click)
2. [DigitalOcean App Platform: manual via doctl](#2-digitalocean-app-platform-manual-via-doctl)
3. [Any VPS via Docker Compose](#3-any-vps-via-docker-compose)
What gets deployed in every path: a single container running
`openhuman-core serve` on port `7788`, behind the provider's TLS. The desktop
app already knows how to talk to a remote core — set
`OPENHUMAN_CORE_RPC_URL=https://your-host/rpc` and `OPENHUMAN_CORE_TOKEN=...`
in `app/.env.local` and launch.
---
## What you need before you start
| Setting | Required | Notes |
|----------------------------|----------|-----------------------------------------------------------------------|
| `OPENHUMAN_CORE_TOKEN` | yes | Bearer token clients send to `/rpc`. Generate with `openssl rand -hex 32`. **Anyone with this token can drive the core.** |
| `BACKEND_URL` | yes | Tinyhumans backend the core talks to (`https://api.tinyhumans.ai` for prod). |
| `OPENHUMAN_APP_ENV` | no | `production` or `staging`. Defaults to `staging`. |
| `OPENHUMAN_CORE_HOST` | no | Defaults to `0.0.0.0` in the container. |
| `OPENHUMAN_CORE_PORT` | no | Defaults to `7788`. |
| `RUST_LOG` | no | `info` is fine; `debug` for triage. |
Endpoints exposed by the running container:
- `GET /health` — public liveness probe. Used by every deploy path's healthcheck.
- `POST /rpc` — bearer-protected JSON-RPC entrypoint.
- `GET /events`, `GET /ws/dictation` — public streaming channels.
The `OPENHUMAN_WORKSPACE` directory (`/home/openhuman/.openhuman` inside the
container) holds the core's config, sqlite databases, and skill state. **Mount
it on a persistent volume** in every production deploy or you will lose data on
restart.
---
## 1. DigitalOcean App Platform: one-click
Click the button below to create a new App Platform application from this
repository's [`.do/app.yaml`](../.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)
Then, in the App Platform UI, **before the first deploy completes**:
1. Open the **Settings → App-Level Environment Variables** tab.
2. Replace the placeholder `OPENHUMAN_CORE_TOKEN` value with a strong secret
(`openssl rand -hex 32`). Mark it encrypted.
3. If you are deploying staging, change `OPENHUMAN_APP_ENV` to `staging` and
`BACKEND_URL` to `https://staging-api.tinyhumans.ai`.
4. Hit **Save** — App Platform redeploys with the new secret.
App Platform handles TLS, restart-on-crash, log streaming, and rolling
redeploys on `git push` (set `deploy_on_push: true` in `.do/app.yaml` to
opt-in).
> **Persistence note:** App Platform Basic does not provide block storage. The
> core's workspace lives in the container's ephemeral filesystem and is lost
> on redeploy. For durable storage, attach a managed database or upgrade to a
> tier that supports volumes. See the [Compose path](#3-any-vps-via-docker-compose)
> for a self-host alternative with persistent volumes out of the box.
---
## 2. DigitalOcean App Platform: manual via doctl
If you'd rather not click through the UI:
```bash
# One-time: install doctl and authenticate.
doctl auth init
# Edit .do/app.yaml — set OPENHUMAN_CORE_TOKEN to a real value (or pass it in
# at create time via --spec with envsubst). Then:
doctl apps create --spec .do/app.yaml
# Watch the build:
doctl apps list
doctl apps logs <app-id> --type build --follow
```
Update an existing app after editing the spec:
```bash
doctl apps update <app-id> --spec .do/app.yaml
```
---
## 3. Any VPS via Docker Compose
Works on any host with Docker Engine ≥ 24 and the Compose plugin —
DigitalOcean Droplet, Hetzner, Linode, EC2, a home server.
```bash
# On the server:
git clone https://github.com/tinyhumansai/openhuman.git
cd openhuman
# Configure secrets:
cp .env.example .env
# Edit .env — at minimum:
# BACKEND_URL=https://api.tinyhumans.ai
# OPENHUMAN_CORE_TOKEN=<openssl rand -hex 32>
# OPENHUMAN_APP_ENV=production
# Build and start:
docker compose up -d
# Verify:
docker compose ps
curl -fsS http://localhost:7788/health
```
The Compose file ([`docker-compose.yml`](../docker-compose.yml)) maps the core
on `:7788`, mounts a named volume `openhuman-workspace` for persistence, and
sets `restart: unless-stopped` so the core comes back after host reboots.
### Updating
```bash
git pull
docker compose build
docker compose up -d
```
### Logs
```bash
docker compose logs -f openhuman-core
```
### Putting it behind TLS
Use Caddy, nginx, or Traefik as a reverse proxy in front of `:7788`. A minimal
`Caddyfile`:
```caddy
core.example.com {
reverse_proxy localhost:7788
}
```
---
## Pointing the desktop app at a hosted core
In the desktop app's environment file (`app/.env.local`):
```bash
# Use the hosted core instead of spawning a local sidecar.
OPENHUMAN_CORE_RUN_MODE=external
OPENHUMAN_CORE_RPC_URL=https://core.example.com/rpc
OPENHUMAN_CORE_TOKEN=<the same token you set on the server>
```
Restart the desktop app. The provider chain in `App.tsx` will route all RPC
calls to the remote core; nothing else changes.
---
## Smoke test
The repo ships [`.github/workflows/deploy-smoke.yml`](../.github/workflows/deploy-smoke.yml),
which runs on every PR that touches the deploy artifacts. It builds the
Docker image, boots it, and polls `/health` — so a regression in the cloud
deploy path fails CI before it lands on `main`.
To run the same check locally:
```bash
docker build -t openhuman-core:smoke .
docker run -d --name oh-smoke -p 7788:7788 \
-e OPENHUMAN_CORE_TOKEN=smoke-test-token \
openhuman-core:smoke
# Wait ~15s for the binary to come up, then:
curl -fsS http://localhost:7788/health
docker rm -f oh-smoke
```