| --- |
| 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) |
|
|