mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
672 lines
23 KiB
Markdown
672 lines
23 KiB
Markdown
---
|
|
description: Hosting the headless openhuman-core in the cloud - DigitalOcean App Platform, Fly.io, or Docker Compose on any VPS.
|
|
icon: cloud
|
|
---
|
|
|
|
# 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 four 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)
|
|
4. [Fly.io](#4-flyio)
|
|
|
|
What gets deployed in every path: a single container running
|
|
`openhuman-core serve` on port `7788`. Public hosts should sit behind the
|
|
provider's TLS, for example `https://core.example.com/rpc`. Private-only hosts
|
|
on localhost, RFC1918 networks, or tailnets such as Tailscale can use
|
|
plain HTTP, for example `http://100.x.x.x:7788/rpc`, when the core is not
|
|
reachable from the public internet. The desktop app already knows how to talk
|
|
to a remote core; set `OPENHUMAN_CORE_RPC_URL` and `OPENHUMAN_CORE_TOKEN=...`
|
|
in `app/.env.local` and launch.
|
|
|
|
---
|
|
|
|
## Remote UI choices
|
|
|
|
OpenHuman's supported remote deployment is **core remote, UI local**: run
|
|
`openhuman-core` on a Linux server and point a desktop client at that RPC URL.
|
|
The deployed core does not serve the full React/Tauri UI as a production web
|
|
app yet. Desktop-only features still need the Tauri shell, including tray
|
|
controls, native deep links, CEF account scanners, OS keychain integration, and
|
|
window/screen affordances.
|
|
|
|
For a browser-accessible UI on a private server today, use the Vite web build as
|
|
a development/preview surface against the remote core:
|
|
|
|
```bash
|
|
# On the server, run the core with an explicit 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
|
|
|
|
# In another shell on the server, serve the UI only on loopback.
|
|
pnpm --dir app dev -- --host 127.0.0.1 --port 1420
|
|
```
|
|
|
|
Then tunnel both ports from your workstation:
|
|
|
|
```bash
|
|
ssh -L 1420:127.0.0.1:1420 -L 7788:127.0.0.1:7788 user@server
|
|
```
|
|
|
|
Open `http://127.0.0.1:1420`, choose the remote/core option on the first-run
|
|
screen, and enter `http://127.0.0.1:7788/rpc` plus the
|
|
`OPENHUMAN_CORE_TOKEN` value from the server.
|
|
|
|
If you serve the browser UI from a non-loopback origin, add that exact origin to
|
|
the core's CORS allowlist:
|
|
|
|
```bash
|
|
export OPENHUMAN_CORE_ALLOWED_ORIGINS="https://openhuman-ui.example.com"
|
|
```
|
|
|
|
Loopback Vite origins such as `http://127.0.0.1:1420` and
|
|
`http://localhost:1420` are allowed automatically. Public `http://` origins are
|
|
not recommended because every RPC call carries the bearer token.
|
|
|
|
---
|
|
|
|
## Single source of truth for the bearer token
|
|
|
|
Every `/rpc` call carries `Authorization: Bearer <token>`. The core has two
|
|
ways to load that token at startup ([`src/core/auth.rs`](../../src/core/auth.rs)):
|
|
|
|
1. **`OPENHUMAN_CORE_TOKEN` environment variable** — pre-seeded by the caller
|
|
(Tauri shell, Docker, App Platform, systemd unit, …). The core uses this
|
|
value as-is and **never** writes a file.
|
|
2. **`{workspace}/core.token` file** — generated by the core on first boot
|
|
*only when `OPENHUMAN_CORE_TOKEN` is unset*. Standalone `openhuman core run`
|
|
uses this so CLI clients can `cat` the file.
|
|
|
|
**Rule of thumb for any remote / dockerized deploy: always set
|
|
`OPENHUMAN_CORE_TOKEN`.** Do not rely on `core.token` in a container —
|
|
ephemeral filesystems lose it on redeploy, and any client trying to read the
|
|
file from outside the container will get a stale or empty value. The two
|
|
paths are deliberately mutually exclusive at startup; mixing them is the most
|
|
common reason behind "the dashboard gets 401 after I redeployed".
|
|
|
|
To check what the *running* core is using, run [`scripts/print-core-token.sh`](../../scripts/print-core-token.sh)
|
|
on the host (or inside the container with `docker compose exec`):
|
|
|
|
```bash
|
|
scripts/print-core-token.sh --where # prints 'env' or 'file:/path'
|
|
scripts/print-core-token.sh --redact # first 8 hex chars + '…' (safe for logs)
|
|
scripts/print-core-token.sh # full value (pipe straight into a client)
|
|
```
|
|
|
|
The desktop app's first-run picker also exposes a **Test connection** button
|
|
next to the Core RPC URL + token fields, which fires `core.ping` against the
|
|
URL with the typed token and reports `Connected ✓` / `Auth failed` /
|
|
`Unreachable` inline before persisting the configuration.
|
|
|
|
---
|
|
|
|
## 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 `production`. |
|
|
| `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.
|
|
|
|
Each production release publishes a multi-tagged image to GHCR:
|
|
|
|
```bash
|
|
docker pull ghcr.io/tinyhumansai/openhuman-core:latest # tracks the latest prod cut
|
|
docker pull ghcr.io/tinyhumansai/openhuman-core:v1.2.4 # pinned by GitHub Release tag
|
|
docker pull ghcr.io/tinyhumansai/openhuman-core:1.2.4 # pinned by SemVer
|
|
```
|
|
|
|
The image is `linux/amd64`. arm64 hosts pull the standalone tarball
|
|
attached to the same GitHub Release (`openhuman-core-<version>-aarch64-unknown-linux-gnu.tar.gz`)
|
|
or build the image from source on an arm64 builder.
|
|
|
|
Quick run with a published image:
|
|
|
|
```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
|
|
```
|
|
|
|
Or use the in-repo Compose file (still builds the image locally from
|
|
`Dockerfile`; switch the `image:` field to `ghcr.io/tinyhumansai/openhuman-core:latest`
|
|
in `docker-compose.yml` to consume the published image instead):
|
|
|
|
```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
|
|
```
|
|
|
|
### Headless install without Docker
|
|
|
|
If you can't run Docker on the host, grab the standalone CLI tarball
|
|
attached to the latest [GitHub Release](https://github.com/tinyhumansai/openhuman/releases/latest):
|
|
|
|
```bash
|
|
# Pick the tarball that matches your host arch.
|
|
ARCH="$(uname -m)"
|
|
case "$ARCH" in
|
|
x86_64) TARGET=x86_64-unknown-linux-gnu ;;
|
|
aarch64) TARGET=aarch64-unknown-linux-gnu ;;
|
|
*) echo "Unsupported arch: $ARCH"; exit 1 ;;
|
|
esac
|
|
VERSION=1.2.4 # set to the release you want
|
|
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
|
|
```
|
|
|
|
Then run `openhuman-core serve` under your service manager of choice
|
|
(systemd, supervisord, …) with the same environment variables documented
|
|
above.
|
|
|
|
### Headless self-update contract
|
|
|
|
Headless deployments should treat `openhuman.update_apply` as the safe primitive:
|
|
it downloads the release asset, writes it atomically next to the current binary,
|
|
and returns. Nothing exits automatically.
|
|
|
|
`openhuman.update_run` follows `config.update.restart_strategy`:
|
|
|
|
- `self_replace` (default): stage the binary, publish an in-process restart request, and let the running core respawn itself.
|
|
- `supervisor`: stage the binary and return `restart_requested=false`. Your outer service manager must restart the process.
|
|
|
|
For long-running Linux services, set:
|
|
|
|
```toml
|
|
[update]
|
|
restart_strategy = "supervisor"
|
|
rpc_mutations_enabled = false
|
|
```
|
|
|
|
or the equivalent env vars:
|
|
|
|
```bash
|
|
OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY=supervisor
|
|
OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED=false
|
|
```
|
|
|
|
Recommended `systemd` stance:
|
|
|
|
```ini
|
|
Restart=always
|
|
ExecReload=/bin/kill -HUP $MAINPID
|
|
```
|
|
|
|
Operator flow:
|
|
|
|
1. Call `openhuman.update_check` to discover a release.
|
|
2. Configure `restart_strategy = "supervisor"` in your `update.toml` (or set
|
|
`OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY=supervisor`) so the core stages the
|
|
new binary without trying to re-exec itself, then call
|
|
`openhuman.update_apply` or `openhuman.update_run`. `restart_strategy` is a
|
|
configuration setting, not an RPC parameter.
|
|
3. Restart the unit explicitly: `systemctl restart openhuman`.
|
|
|
|
If download or staging fails, the running binary is left in place and no
|
|
restart is requested. If a staged binary proves bad after restart, roll back by
|
|
restoring the previous binary from your package manager, image tag, or release
|
|
artifact and restarting the supervisor again.
|
|
|
|
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
|
|
```
|
|
|
|
For RPC-exposed production deployments, prefer leaving mutating update RPCs
|
|
disabled (`OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED=false`) and perform
|
|
rollouts through your existing image tag or package-management flow instead.
|
|
|
|
### Logs
|
|
|
|
```bash
|
|
docker compose logs -f openhuman-core
|
|
```
|
|
|
|
### Rotating the bearer token
|
|
|
|
`OPENHUMAN_CORE_TOKEN` is the only thing standing between the public internet
|
|
and full RPC access. Rotate it on a schedule and after any suspected leak:
|
|
|
|
```bash
|
|
# 1. Generate a new token and update the server-side .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. Restart the container so the new value reaches the core process.
|
|
docker compose up -d --force-recreate openhuman-core
|
|
|
|
# 3. Confirm the running container is using the new token (redacted).
|
|
docker compose exec openhuman-core /bin/sh -c \
|
|
'echo -n "$OPENHUMAN_CORE_TOKEN" | head -c 8; echo "…"'
|
|
|
|
# 4. Update every desktop client (Switch mode → re-paste in the picker, or
|
|
# edit OPENHUMAN_CORE_TOKEN in app/.env.local and relaunch). Clients that
|
|
# still hold the old token will get HTTP 401 on the next /rpc call — that
|
|
# is expected, not a regression.
|
|
```
|
|
|
|
For App Platform, do the same in **Settings → App-Level Environment
|
|
Variables**: edit the `OPENHUMAN_CORE_TOKEN` secret and let App Platform
|
|
redeploy. There is no separate token file to delete; the env var is the only
|
|
state.
|
|
|
|
### 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>
|
|
```
|
|
|
|
For a private tailnet-only VM with no public IP, use the tailnet URL instead:
|
|
|
|
```bash
|
|
OPENHUMAN_CORE_RUN_MODE=external
|
|
OPENHUMAN_CORE_RPC_URL=http://100.x.x.x:7788/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. Public `http://` hosts are
|
|
rejected by the app picker; use HTTPS for any publicly reachable core.
|
|
|
|
---
|
|
|
|
## Named-volume ownership and the Docker entrypoint
|
|
|
|
Docker creates named volumes owned `root:root` by default. Because the core
|
|
runs as the non-root `openhuman` user (UID 10001), the first write after the
|
|
banner — `init_rpc_token → write_token_file` into `$OPENHUMAN_WORKSPACE` —
|
|
would raise `Permission denied (os error 13)` if nothing fixes the ownership
|
|
first.
|
|
|
|
The image ships a dedicated entrypoint at
|
|
`/usr/local/bin/docker-entrypoint-core.sh` that:
|
|
|
|
1. Starts as `root`.
|
|
2. Runs `mkdir -p` + `chown openhuman:openhuman` on both `$OPENHUMAN_WORKSPACE`
|
|
and `$HOME/.openhuman` (the directory `core.token` is written to when
|
|
`OPENHUMAN_CORE_TOKEN` is unset).
|
|
3. Calls `exec gosu openhuman openhuman-core "$@"` to drop privileges and
|
|
hand off to the binary.
|
|
|
|
This is **idempotent**: on a freshly-created volume the chown heals the
|
|
root-owned directory; on a volume that was already healed the chown is a
|
|
no-op. No manual `docker volume rm` is required when upgrading from images
|
|
predating this fix.
|
|
|
|
The entrypoint is named `docker-entrypoint-core.sh` and wired **only** into
|
|
the root `Dockerfile`. The E2E image (`e2e/docker-entrypoint.sh`) is
|
|
unaffected.
|
|
|
|
---
|
|
|
|
## 4. Fly.io
|
|
|
|
[Fly.io](https://fly.io) is a good fit for `openhuman-core`: it handles TLS
|
|
automatically, supports persistent volumes on all tiers, and can auto-stop
|
|
idle machines to cut costs.
|
|
|
|
### Prerequisites
|
|
|
|
- [flyctl](https://fly.io/docs/flyctl/install/) installed and authenticated (`fly auth login`)
|
|
- A Fly.io account
|
|
|
|
### Step 1 — Launch the app
|
|
|
|
```bash
|
|
fly launch --no-deploy --config .fly/fly.toml
|
|
```
|
|
|
|
Fly.io detects the `Dockerfile` automatically. Choose a region close to your
|
|
users and skip the first deploy when prompted. This generates a config file.
|
|
|
|
### Step 2 — Configure `.fly/fly.toml`
|
|
|
|
The repo ships a template at [`.fly/fly.toml`](../../.fly/fly.toml). Fill in
|
|
`<your-app-name>` and `<your-region>` with the values you chose during
|
|
`fly launch`:
|
|
|
|
```toml
|
|
app = '<your-app-name>'
|
|
primary_region = '<your-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'
|
|
auto_start_machines = true
|
|
# min_machines_running = 0 fully stops the machine when idle (cheapest), but
|
|
# the first request after idle pays a cold-start penalty (container boot +
|
|
# Rust binary init — several seconds). Set to 1 to keep one machine warm.
|
|
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
|
|
```
|
|
|
|
### Step 3 — Create a persistent volume
|
|
|
|
```bash
|
|
fly volumes create openhuman_workspace --size 5 --region <your-region> --config .fly/fly.toml
|
|
```
|
|
|
|
**Mount the workspace on a persistent volume** or data is lost on every
|
|
redeploy.
|
|
|
|
### Step 4 — Set secrets
|
|
|
|
```bash
|
|
# Required
|
|
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"
|
|
|
|
# Recommended for any publicly-reachable deployment:
|
|
fly secrets set OPENHUMAN_AUTO_UPDATE_RPC_MUTATIONS_ENABLED="false"
|
|
fly secrets set OPENHUMAN_AUTO_UPDATE_RESTART_STRATEGY="supervisor"
|
|
|
|
# Optional — error reporting and analytics:
|
|
fly secrets set OPENHUMAN_CORE_SENTRY_DSN="https://<key>@o<org>.ingest.sentry.io/<project>"
|
|
fly secrets set OPENHUMAN_ANALYTICS_ENABLED="true"
|
|
```
|
|
|
|
Save the value of `OPENHUMAN_CORE_TOKEN` — you will need it to connect the
|
|
desktop app later. **Anyone with this token can drive the core**; treat it
|
|
like a password and rotate it with `fly secrets set OPENHUMAN_CORE_TOKEN="$(openssl rand -hex 32)"`
|
|
after any suspected leak.
|
|
|
|
### Step 5 — Deploy
|
|
|
|
```bash
|
|
fly deploy --config .fly/fly.toml
|
|
```
|
|
|
|
Verify the core is healthy:
|
|
|
|
```bash
|
|
curl -fsS https://<your-app-name>.fly.dev/health
|
|
```
|
|
|
|
### Step 6 — Point the desktop app at the hosted core
|
|
|
|
In `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>
|
|
```
|
|
|
|
Or use the **first-run picker** in the desktop app (Core RPC URL + token
|
|
fields with a **Test connection** button) to configure without editing files.
|
|
|
|
### Continuous deployment
|
|
|
|
To redeploy automatically on every push to `main`, add a workflow file at
|
|
`.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
|
|
# Pin the Fly action to a tagged release (or a full commit SHA) rather
|
|
# than `@master` — tracking a moving branch trusts every future commit
|
|
# pushed there, including any made by a compromised maintainer account.
|
|
- 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 }}
|
|
```
|
|
|
|
Generate a deploy token with `fly tokens create deploy` and add it as a
|
|
repository secret named `FLY_API_TOKEN`.
|
|
|
|
### Updating
|
|
|
|
```bash
|
|
fly deploy --config .fly/fly.toml
|
|
```
|
|
|
|
For version-pinned deployments, update the image tag in `.fly/fly.toml` and
|
|
redeploy:
|
|
|
|
```toml
|
|
[build]
|
|
image = "ghcr.io/tinyhumansai/openhuman-core:v1.2.4"
|
|
```
|
|
|
|
### Logs
|
|
|
|
```bash
|
|
fly logs --config .fly/fly.toml
|
|
```
|
|
|
|
### Known gotcha — UID mismatch on volumes
|
|
|
|
If you switch between building from `Dockerfile` (which creates the
|
|
`openhuman` user at UID 10001) and pulling the pre-built GHCR image (which
|
|
uses UID 1000), files already written to the persistent volume will be owned
|
|
by the old UID and produce `Permission denied (os error 13)` on startup.
|
|
|
|
Fix by SSH-ing in and re-owning the workspace:
|
|
|
|
```bash
|
|
fly ssh console --config .fly/fly.toml
|
|
chown -R openhuman:openhuman /home/openhuman/.openhuman/
|
|
exit
|
|
fly machine restart --config .fly/fly.toml
|
|
```
|
|
|
|
---
|
|
|
|
## 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`.
|
|
|
|
The workflow contains two jobs:
|
|
|
|
- **`docker-image`** — sets `OPENHUMAN_CORE_TOKEN` and mounts no volume.
|
|
Protects the DigitalOcean App Platform path (`.do/app.yaml`) where the
|
|
token is always pre-set and no persistent volume is used.
|
|
- **`docker-volume-permissions`** — omits `OPENHUMAN_CORE_TOKEN` and mounts
|
|
a fresh anonymous volume at `/home/openhuman/.openhuman`. Reproduces the
|
|
exact failure mode of issue #2065 and asserts that `/health` returns 200
|
|
and that `Permission denied (os error 13)` is absent from the logs.
|
|
|
|
To run the same check locally:
|
|
|
|
```bash
|
|
docker build -t openhuman-core:smoke .
|
|
|
|
# Optional: tune build profile and Cargo parallelism.
|
|
# Keep CARGO_BUILD_JOBS=1 on constrained builders; raise it on larger machines.
|
|
docker build --build-arg CARGO_PROFILE=release --build-arg CARGO_BUILD_JOBS=4 -t openhuman-core:release .
|
|
|
|
# Token-set path (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
|
|
|
|
# Fresh-volume / no-token path (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
|
|
```
|