claw / README.md
jkes6203's picture
Fix Codex auth profile import shape
70972f0
---
title: OpenClaw Gateway
emoji: 🦞
colorFrom: blue
colorTo: indigo
sdk: docker
app_port: 7860
---
# OpenClaw Gateway on Hugging Face Spaces
This Space runs the [OpenClaw](https://github.com/openclaw/openclaw) gateway so you can use the Control UI and connect clients over the internet.
## Quick start
1. **Create a new Space** at [huggingface.co/new-space](https://huggingface.co/new-space). Choose **Docker** as the SDK.
2. **Copy the contents of this folder** into your Space repo:
- This `README.md` (including the YAML block above)
- This repo's `Dockerfile`
- This repo's `setup-hf-config.mjs`
- This repo's `import-auth-profiles.mjs`
The Space installs the published `openclaw` npm package directly instead of cloning/building the full source tree, which is much more reliable on HF `cpu-basic`.
3. **Add Secrets** in your Space **Settings → Secrets** (all optional except one auth method for UI access):
- **`OPENCLAW_GATEWAY_TOKEN`** — long random string (e.g. `openssl rand -hex 24`). Paste this in the Control UI to log in. **Recommended.** (Alternative: use `OPENCLAW_GATEWAY_PASSWORD` for password auth; if both are set, token is used.)
- **`OPENCLAW_GATEWAY_PASSWORD`** — (optional) Gateway password; startup script sets `gateway.auth.mode: "password"`. Use when you prefer password over token.
- **`HF_TOKEN`** — your [Hugging Face token](https://huggingface.co/settings/tokens) with **Make calls to Inference Providers**. Needed only when you want to use the Hugging Face provider; without it, choose another provider such as Gemini or OpenCode.
- **`GEMINI_API_KEY`** — (optional) Google Gemini API key. Not needed for the default DeepSeek -> Qwen -> Codex chain.
- **`OPENCODE_API_KEY`** — (optional) OpenCode Zen API key. OpenClaw reads this directly from the gateway environment for the `opencode` provider.
- **`SUPABASE_URL`** — (recommended) Supabase project URL for external state backup.
- **`SUPABASE_KEY`** — (recommended) Supabase service key or project API key with insert/update access to your sync table.
- **`OPENCLAW_MODEL_PRIMARY`** — (recommended) Primary model ref. This repo now defaults to `huggingface/Qwen/Qwen3-8B`.
- **`OPENCLAW_MODEL_FALLBACK_1`** — (recommended) First fallback model ref. Default is `huggingface/deepseek-ai/DeepSeek-R1`.
- **`OPENCLAW_MODEL_FALLBACK_2`** — (recommended) Second fallback model ref. Default is `openai-codex/gpt-5.4`.
- **`OPENCLAW_GATEWAY_TRUSTED_PROXIES`** — (optional) Comma-separated proxy IPs (e.g. `10.20.31.87,10.20.26.157`). The startup script writes this into `gateway.trustedProxies`; set if you see “Proxy headers detected from untrusted address” or pairing/unauthorized. Use **Variables** if you prefer (IPs need not be secret).
- **`OPENCLAW_CONTROL_UI_ALLOWED_ORIGINS`** — (optional) Comma-separated origins (e.g. `https://your-space.hf.space`). Written to `gateway.controlUi.allowedOrigins` so only those origins can open the Control UI; useful to lock down to your Space URL.
- **`TELEGRAM_BOT_TOKEN`** — (optional) Telegram BotFather token. Required for Telegram channel support and startup notifications.
- **`OPENCLAW_TELEGRAM_INIT_NOTIFY_CHAT_IDS`** — (optional) Comma-separated chat IDs to receive startup notifications from the init script. Recommended if you want an explicit "boot completed" signal.
- **`OPENCLAW_TELEGRAM_INIT_NOTIFY_MESSAGE`** — (optional) Extra plain-text line appended to the startup notification.
- **`OPENCLAW_TELEGRAM_DROP_PENDING_UPDATES_ON_START`** — (optional) `1` / `0`; when startup finds a webhook and removes it, also drops queued Telegram updates. Default `0`.
- **`OPENCLAW_STARTUP_NOTIFY_TELEGRAM_TO`** — (optional) Single fallback chat ID for startup notifier when `OPENCLAW_TELEGRAM_INIT_NOTIFY_CHAT_IDS` is not set.
- **`OPENCLAW_TELEGRAM_DM_POLICY`** / **`OPENCLAW_TELEGRAM_ALLOW_FROM`** / **`OPENCLAW_TELEGRAM_GROUP_POLICY`** / **`OPENCLAW_TELEGRAM_GROUP_ALLOW_FROM`** / **`OPENCLAW_TELEGRAM_REQUIRE_MENTION`** — (optional) Advanced Telegram access-control settings.
4. **Build and run** — push to the Space repo; the Space will build and start the gateway. Startup writes config so the default model chain is `Qwen3-8B -> DeepSeek-R1 -> openai-codex/gpt-5.4`, the Control UI accepts token-only connections (no device pairing), and Supabase sync starts automatically when `SUPABASE_URL` + `SUPABASE_KEY` are present.
When the logs show `listening on ws://0.0.0.0:7860`, open your Space’s URL (e.g. `https://your-username-openclaw-gateway.hf.space`) and paste the gateway token in **Settings → token** (or use `https://your-space.hf.space#token=YOUR_TOKEN`).
## DeepSeek Primary, Qwen and Codex Fallback, and Telegram
- **Default behavior in this repo** is now:
- primary: `huggingface/Qwen/Qwen3-8B`
- fallbacks: `huggingface/deepseek-ai/DeepSeek-R1`, `openai-codex/gpt-5.4`
- **The startup script now writes both `primary` and `fallbacks`.**
- **Codex fallback only works if Codex itself is already authenticated and usable.** If `openai-codex/gpt-5.4` still lacks auth, fallback will still fail when traffic reaches it.
- **OpenCode Zen** also works well in Hugging Face Spaces. Set `OPENCODE_API_KEY` and use a default model such as `opencode/claude-opus-4-6`.
- **Codex auth remains the special case**: if your `openai-codex/gpt-5.4` path depends on OAuth-backed credentials, use a separate environment that can complete the login flow first, or expect the Codex fallback to fail.
- **Telegram minimal setup** for OpenClaw itself only needs `TELEGRAM_BOT_TOKEN`.
- **Telegram startup notification** additionally needs `OPENCLAW_TELEGRAM_INIT_NOTIFY_CHAT_IDS`.
- **HF Spaces receive-path fix** in this repo now checks `getWebhookInfo`, removes leftover webhooks, then probes `getUpdates` so long-polling bots can receive again.
- **Codex auth remains the special case**: in headless Spaces, the reliable path is to complete Codex OAuth elsewhere, then import the resulting OpenClaw auth profile via a Space Secret (see below). Running the interactive OAuth flow inside the Space is usually unreliable.
- **Telegram advanced settings** are optional and only needed when you want allowlists, group restrictions, or mention-only behavior.
Minimal example for `Qwen -> DeepSeek -> Codex` + Supabase + Telegram:
```text
Secrets
OPENCLAW_GATEWAY_TOKEN=<long random token>
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=<your supabase service key>
TELEGRAM_BOT_TOKEN=<botfather token>
Variables
OPENCLAW_MODEL_PRIMARY=huggingface/Qwen/Qwen3-8B
OPENCLAW_MODEL_FALLBACK_1=huggingface/deepseek-ai/DeepSeek-R1
OPENCLAW_MODEL_FALLBACK_2=openai-codex/gpt-5.4
OPENCLAW_CONTROL_UI_ALLOWED_ORIGINS=https://your-space.hf.space
OPENCLAW_TELEGRAM_INIT_NOTIFY_CHAT_IDS=123456789,-1001234567890
OPENCLAW_STARTUP_NOTIFY_TELEGRAM_TO=123456789
OPENCLAW_TELEGRAM_INIT_NOTIFY_MESSAGE=HF Space boot completed
OPENCLAW_TELEGRAM_DROP_PENDING_UPDATES_ON_START=0
OPENCLAW_SUPABASE_TABLE=openclaw_state
OPENCLAW_SYNC_INTERVAL_MS=300000
```
Telegram startup notification notes:
1. Ask the target user to start the bot first, or add the bot to the target group/channel before using that chat ID.
2. Use numeric chat IDs when possible; they are less fragile than usernames.
3. The startup notifier now tries several network transports: `fetch-json`, `https-family4-json`, `https-family4-form`, `https-resolve4-json`, and `curl-json`.
4. If no explicit startup chat ID is configured, the notifier falls back to `OPENCLAW_STARTUP_NOTIFY_TELEGRAM_TO`, then `OPENCLAW_TELEGRAM_ALLOW_FROM`, then recent `getUpdates` chat IDs.
5. Startup messages are tagged with both `transport=` and `payload=` in the message body so you can report exactly which path worked.
## Keep it running 24/7
- **Free hardware:** The Space will **sleep after ~48 hours** of inactivity. Anyone opening the URL will wake it.
- **Paid hardware:** In **Settings → Hardware**, upgrade to a paid CPU (or other tier). Upgraded Spaces run **indefinitely** by default. You can set **Sleep time** to “Never” if desired.
## Persist config and workspace
Without persistent storage, config and workspace are lost when the Space restarts. To keep them:
1. In **Settings → Storage**, add **persistent storage** for the Space.
2. The container uses `/data` when writable (state under `/data/.openclaw`). If `/data` is not available, it falls back to `/home/user/.openclaw` so the app still starts.
## External storage
This repo now includes a lightweight Supabase sync worker. It mirrors text state files from `.openclaw` such as config, session data, memory files, and logs into a Supabase table on a timer. This gives you an external backup even if the Space restarts.
Recommended setup:
1. Create a Supabase table:
```sql
create table if not exists openclaw_state (
path text primary key,
kind text not null,
content text not null,
sha256 text not null,
size_bytes bigint not null,
updated_at timestamptz not null,
synced_at timestamptz not null
);
```
2. Add Secrets in your Space:
- `SUPABASE_URL`
- `SUPABASE_KEY`
3. Optional Variables:
- `OPENCLAW_SUPABASE_TABLE=openclaw_state`
- `OPENCLAW_SYNC_INTERVAL_MS=300000`
- `OPENCLAW_SYNC_MAX_FILE_BYTES=5242880`
- `OPENCLAW_SYNC_INCLUDE_EXTENSIONS=.json,.jsonl,.md,.txt`
Notes:
- **Supabase is the implemented path in this repo.** It is the best fit when you want structured external storage and future RAG/vector expansion.
- **HF Dataset Repo** and **MongoDB Atlas** remain viable alternatives, but they are not automated by this wrapper yet.
- **OpenClaw still runs locally inside the container.** The sync worker is an external backup/mirroring layer that avoids data loss across rebuilds and restarts.
## Custom install script
The image runs this Space repo's `setup-hf-config.mjs` at startup to set the default model, gateway auth, and optional Telegram bootstrap config. The Dockerfile installs the published `openclaw` npm package, copies your helper scripts into `/app`, and runs them before starting the gateway. This avoids the heavy monorepo build that often OOMs on HF Spaces.
## Configuring trusted proxies (no CLI or file edit)
If logs show **"Proxy headers detected from untrusted address"** or connections close with `reason=pairing required` / `reason=unauthorized`, the gateway is behind Hugging Face’s reverse proxy. Add the proxy IP(s) so the gateway trusts them and uses `X-Forwarded-For` for client detection.
**Recommended: use a Secret or Variable (applied at startup)**
1. **Find the proxy IP(s)** — In the Space logs, look for `[ws] ... remote=10.20.31.87 ...`. The `remote=` value is the address the gateway sees. Note one or more such IPs.
2. In **Settings → Secrets** or **Settings → Variables**, add:
- **Name:** `OPENCLAW_GATEWAY_TRUSTED_PROXIES`
- **Value:** comma-separated IPs, e.g. `10.20.31.87,10.20.26.157`
3. **Restart** the Space. The entrypoint runs `setup-hf-config.mjs` before the gateway; it reads this env and writes `gateway.trustedProxies` into the config automatically. No CLI or file edit needed.
**Manual fallback (dev mode / shell access)**
If you have a shell (e.g. dev mode), you can instead edit `/data/.openclaw/openclaw.json` and set `gateway.trustedProxies: ["10.20.31.87"]`, or run `openclaw config set gateway.trustedProxies '["10.20.31.87"]'`. Restart the Space after changing config.
See [Gateway security – Reverse proxy](https://docs.openclaw.ai/gateway/security#reverse-proxy-configuration) for more on `gateway.trustedProxies`.
## Configuration from environment (summary)
The startup script `setup-hf-config.mjs` reads the following from **Secrets** or **Variables** and writes them into `openclaw.json` on every container start. So you can configure the gateway without CLI or file edit.
| Env variable | Config path | Format |
|--------------|------------|--------|
| `OPENCLAW_MODEL_PRIMARY` | `agents.defaults.model.primary` | Primary model ref string |
| `OPENCLAW_DEFAULT_MODEL` | `agents.defaults.model.primary` | Legacy alias for primary model |
| `OPENCLAW_HF_DEFAULT_MODEL` | `agents.defaults.model.primary` | Legacy alias for primary model |
| `OPENCLAW_MODEL_FALLBACK_1` | `agents.defaults.model.fallbacks[0]` | First fallback model ref |
| `OPENCLAW_MODEL_FALLBACK_2` | `agents.defaults.model.fallbacks[1]` | Second fallback model ref |
| `OPENCLAW_FALLBACK_MODELS` | `agents.defaults.model.fallbacks` | Comma-separated fallback model refs |
| `OPENCLAW_GATEWAY_TOKEN` | `gateway.auth.mode` + `gateway.auth.token` | Any string |
| `OPENCLAW_GATEWAY_PASSWORD` | `gateway.auth.mode` + `gateway.auth.password` | Any string (token wins if both set) |
| `OPENCLAW_GATEWAY_TRUSTED_PROXIES` | `gateway.trustedProxies` | Comma-separated IPs |
| `OPENCLAW_CONTROL_UI_ALLOWED_ORIGINS` | `gateway.controlUi.allowedOrigins` | Comma-separated origins (e.g. `https://you.hf.space`) |
| `OPENCLAW_CONTROL_UI_ALLOWED_ORIGIN` | `gateway.controlUi.allowedOrigins` | Legacy single-origin alias |
| `OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY` | `channels.telegram.network.autoSelectFamily=false` | Existing OpenClaw workaround for Telegram network failures on some hosts |
| `OPENCLAW_TELEGRAM_DNS_RESULT_ORDER` | `channels.telegram.network.dnsResultOrder` | Existing OpenClaw Telegram network workaround; default `ipv4first` |
| `OPENCLAW_TELEGRAM_PROXY` | `channels.telegram.proxy` | Optional SOCKS/HTTP proxy URL for Telegram Bot API calls |
| `OPENCLAW_TELEGRAM_NATIVE_COMMANDS` | `channels.telegram.commands.native` | `1` / `0`; default is enabled so the bot menu can appear |
| `OPENCLAW_TELEGRAM_NATIVE_SKILLS` | `channels.telegram.commands.nativeSkills` | `1` / `0`; defaults to the same value as native commands |
| `OPENCLAW_STARTUP_NOTIFY` | startup notify helper | `1` / `0`; send a best-effort Telegram startup notification |
| `OPENCLAW_STARTUP_NOTIFY_TELEGRAM_TO` | startup notify helper | Optional explicit Telegram chat id; defaults to first `OPENCLAW_TELEGRAM_ALLOW_FROM` entry |
| `OPENCLAW_TELEGRAM_API_HOST` | startup notify helper | Optional alternate Telegram Bot API host; default `api.telegram.org` |
| `OPENCLAW_AUTH_PROFILES_JSON_B64` | startup auth import | Base64-encoded full `auth-profiles.json` exported from another OpenClaw host |
| `OPENCLAW_AUTH_PROFILES_IMPORT_B64` | startup auth import | Alternate secret name for the same full `auth-profiles.json` import |
| `OPENCLAW_OPENAI_CODEX_PROFILE_B64` | startup auth import | Base64-encoded JSON object for one `openai-codex:default` profile |
| `OPENCLAW_LEGACY_OAUTH_JSON_B64` | startup auth import | Base64-encoded legacy `credentials/oauth.json` import file |
| `OPENCLAW_ENABLE_GEMINI_CLI_AUTH` | startup plugin bootstrap | `1` / `0`; enables `google-gemini-cli-auth` before gateway start |
| `TELEGRAM_BOT_TOKEN` | `channels.telegram.botToken` | BotFather token |
| `OPENCLAW_TELEGRAM_INIT_NOTIFY_CHAT_IDS` | init script startup notification targets | Comma-separated chat IDs |
| `OPENCLAW_TELEGRAM_INIT_NOTIFY_MESSAGE` | init script startup notification body | Extra line appended to the generated startup message |
| `OPENCLAW_TELEGRAM_DROP_PENDING_UPDATES_ON_START` | Telegram startup receive fix | `1` / `0`; only used when deleting a stale webhook |
| `OPENCLAW_TELEGRAM_DM_POLICY` | `channels.telegram.dmPolicy` | `pairing` \| `allowlist` \| `open` \| `disabled` |
| `OPENCLAW_TELEGRAM_ALLOW_FROM` | `channels.telegram.allowFrom` | Comma-separated Telegram user IDs |
| `OPENCLAW_TELEGRAM_GROUP_POLICY` | `channels.telegram.groupPolicy` | `open` \| `allowlist` \| `disabled` |
| `OPENCLAW_TELEGRAM_GROUP_ALLOW_FROM` | `channels.telegram.groupAllowFrom` | Comma-separated Telegram user IDs |
| `OPENCLAW_TELEGRAM_REQUIRE_MENTION` | `channels.telegram.groups."*".requireMention` | `1` / `0` |
| `SUPABASE_URL` | sync worker | Supabase project URL |
| `SUPABASE_KEY` | sync worker | Supabase key with table write access |
| `OPENCLAW_SUPABASE_TABLE` | sync worker | Destination table; default `openclaw_state` |
| `OPENCLAW_SYNC_INTERVAL_MS` | sync worker | Background sync interval in ms |
| `OPENCLAW_SYNC_MAX_FILE_BYTES` | sync worker | Per-file size limit |
| `OPENCLAW_SYNC_INCLUDE_EXTENSIONS` | sync worker | Comma-separated extensions to mirror |
**Could be added the same way** (same script pattern; not implemented yet):
| Env variable | Config path | Notes |
|--------------|------------|--------|
| `OPENCLAW_GATEWAY_PORT` | `gateway.port` | Number; default entrypoint passes `--port 7860` so only useful with a custom entrypoint. |
| `OPENCLAW_GATEWAY_BIND` | `gateway.bind` | `lan` \| `loopback` \| `auto` \| `tailnet` \| `custom`; entrypoint passes `--bind lan`. |
| `OPENCLAW_CONTROL_UI_BASE_PATH` | `gateway.controlUi.basePath` | e.g. `/openclaw` for reverse-proxy subpath. |
| `OPENCLAW_CONTROL_UI_ALLOW_INSECURE_AUTH` | `gateway.controlUi.allowInsecureAuth` | `1` / `0`; allow token/password over HTTP. |
| `OPENCLAW_CONTROL_UI_ENABLED` | `gateway.controlUi.enabled` | `0` to disable Control UI. |
| `OPENCLAW_CODEX_OAUTH_*` | auth profile store | Depends on upstream OpenClaw support and your runtime's ability to complete the OAuth flow with persistent auth storage. |
### Headless Codex OAuth import for Spaces
For `openai-codex/gpt-5.4` on Hugging Face Spaces, the reliable path is:
1. Complete `openclaw models auth login --provider openai-codex` on another machine.
2. Copy the resulting `~/.openclaw/agents/main/agent/auth-profiles.json`.
3. Base64-encode it and store it in a Space Secret named `OPENCLAW_AUTH_PROFILES_JSON_B64` (or `OPENCLAW_AUTH_PROFILES_IMPORT_B64` if the first name collides with an existing Variable).
4. On startup, this repo imports that file into the Space state automatically.
If you only want the Codex profile instead of the whole file, you can provide a single profile JSON object in `OPENCLAW_OPENAI_CODEX_PROFILE_B64`.
Legacy `credentials/oauth.json` payloads are also accepted via `OPENCLAW_LEGACY_OAUTH_JSON_B64`; startup converts them into the current `auth-profiles.json` OAuth shape automatically.
Example encoding command:
```bash
base64 -w0 ~/.openclaw/agents/main/agent/auth-profiles.json
```
After restart, verify in Space logs that startup printed something like:
```text
[openclaw-hf-auth-import] imported=auth-profiles profiles=...
```
If the Space still logs `No API key found for provider "openai-codex"`, the startup import either did not run, did not receive the secret, or the imported payload was not a valid auth profile. The most reliable input is the full `auth-profiles.json` export in `OPENCLAW_AUTH_PROFILES_JSON_B64`.
To add more, extend `setup-hf-config.mjs` (or your copy) to read the env, parse it, and set the corresponding keys on `config.gateway` or `config.agents` before `fs.writeFileSync`. Schema reference: [Configuration](https://docs.openclaw.ai/gateway/configuration).
## Optional Space variables (build args)
You can set these in **Settings → Variables** (or as build args) to customize the build:
- `OPENCLAW_REPO` — Git URL of the OpenClaw repo (default: `https://github.com/openclaw/openclaw.git`).
- `OPENCLAW_REF` — Branch or tag to clone (default: `main`).
## More
- [OpenClaw docs](https://docs.openclaw.ai)
- [Docker install (generic)](https://docs.openclaw.ai/install/docker)
- [Hugging Face Spaces – Docker](https://huggingface.co/docs/hub/spaces-sdks-docker)