mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -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"
|
||||
@@ -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
@@ -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/*
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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):
|
||||
|
||||
[](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
|
||||
```
|
||||
Reference in New Issue
Block a user