Spaces:
Running
Running
Anurag commited on
Commit ·
49f5bdf
1
Parent(s): 1d367af
Avoid protected browser config keys in generated OpenClaw config
Browse files- README.md +8 -6
- health-server.js +5 -2
- start.sh +25 -9
README.md
CHANGED
|
@@ -20,7 +20,7 @@ secrets:
|
|
| 20 |
- name: GATEWAY_TOKEN
|
| 21 |
description: "Strong token to secure your OpenClaw Control UI (generate: openssl rand -hex 32)."
|
| 22 |
- name: JUPYTER_TOKEN
|
| 23 |
-
description: "Optional
|
| 24 |
- name: CLOUDFLARE_WORKERS_TOKEN
|
| 25 |
description: "Cloudflare API token — auto-creates a Worker proxy and KeepAlive monitor."
|
| 26 |
- name: TELEGRAM_ALLOWED_USERS
|
|
@@ -78,7 +78,7 @@ secrets:
|
|
| 78 |
- 📊 **Visual Dashboard:** Beautiful Web UI to monitor uptime, sync status, and active models.
|
| 79 |
- 🔔 **Webhooks:** Get notified on restarts or backup failures via standard webhooks.
|
| 80 |
- 🔐 **Flexible Auth:** Secure the Control UI with either a gateway token or password.
|
| 81 |
-
- 💻 **
|
| 82 |
- 🏠 **100% HF-Native:** Runs entirely on HuggingFace’s free infrastructure (2 vCPU, 16GB RAM).
|
| 83 |
|
| 84 |
## 🎥 Video Tutorial
|
|
@@ -104,7 +104,9 @@ Navigate to your new Space's **Settings**, scroll down to the **Variables and se
|
|
| 104 |
> [!TIP]
|
| 105 |
> HuggingClaw is completely flexible! You only need these three secrets to get started. You can set other secrets later.
|
| 106 |
|
| 107 |
-
|
|
|
|
|
|
|
| 108 |
|
| 109 |
### Step 3: Deploy & Run
|
| 110 |
|
|
@@ -366,12 +368,12 @@ The merged Space includes the Hugging Face JupyterLab template behavior inside t
|
|
| 366 |
| :--- | :--- | :--- | :--- |
|
| 367 |
| `/` | HuggingClaw dashboard | `7861` | Public HF Spaces entrypoint |
|
| 368 |
| `/app/` | OpenClaw Control UI | `7860` | Mounted behind the local reverse proxy |
|
| 369 |
-
| `/terminal/` | JupyterLab terminal
|
| 370 |
|
| 371 |
When enabled, the terminal notebook root is `/home/node`, so you can inspect HuggingClaw files, logs, workspace state, and runtime scripts from the browser.
|
| 372 |
|
| 373 |
> [!IMPORTANT]
|
| 374 |
-
>
|
| 375 |
|
| 376 |
## 🔍 Merge Comparison
|
| 377 |
|
|
@@ -402,7 +404,7 @@ HuggingClaw uses a multi-layered approach to ensure stability and persistence on
|
|
| 402 |
2. Resolve backup namespace and restore workspace from HF Dataset.
|
| 403 |
3. Generate `openclaw.json` configuration.
|
| 404 |
4. Launch background tasks (auto-sync, channel helpers).
|
| 405 |
-
5. Start the local dashboard/reverse proxy and OpenClaw gateway (JupyterLab starts
|
| 406 |
|
| 407 |
</details>
|
| 408 |
|
|
|
|
| 20 |
- name: GATEWAY_TOKEN
|
| 21 |
description: "Strong token to secure your OpenClaw Control UI (generate: openssl rand -hex 32)."
|
| 22 |
- name: JUPYTER_TOKEN
|
| 23 |
+
description: "Optional token for the JupyterLab terminal at /terminal/. Defaults to GATEWAY_TOKEN when set — no extra secret needed."
|
| 24 |
- name: CLOUDFLARE_WORKERS_TOKEN
|
| 25 |
description: "Cloudflare API token — auto-creates a Worker proxy and KeepAlive monitor."
|
| 26 |
- name: TELEGRAM_ALLOWED_USERS
|
|
|
|
| 78 |
- 📊 **Visual Dashboard:** Beautiful Web UI to monitor uptime, sync status, and active models.
|
| 79 |
- 🔔 **Webhooks:** Get notified on restarts or backup failures via standard webhooks.
|
| 80 |
- 🔐 **Flexible Auth:** Secure the Control UI with either a gateway token or password.
|
| 81 |
+
- 💻 **Terminal Out of the Box:** JupyterLab is available at `/terminal/` automatically when `GATEWAY_TOKEN` is set — no extra config needed. `GATEWAY_TOKEN` is reused as the terminal auth token. Set `DEV_MODE=false` explicitly to opt out.
|
| 82 |
- 🏠 **100% HF-Native:** Runs entirely on HuggingFace’s free infrastructure (2 vCPU, 16GB RAM).
|
| 83 |
|
| 84 |
## 🎥 Video Tutorial
|
|
|
|
| 104 |
> [!TIP]
|
| 105 |
> HuggingClaw is completely flexible! You only need these three secrets to get started. You can set other secrets later.
|
| 106 |
|
| 107 |
+
**Terminal auto-enables when `GATEWAY_TOKEN` is set** — no extra secrets needed. `GATEWAY_TOKEN` is reused as `JUPYTER_TOKEN`, so the terminal is protected by the same credential as the Control UI. To set a different token, add `JUPYTER_TOKEN` as a Secret. To disable the terminal entirely, set `DEV_MODE=false` as a Variable.
|
| 108 |
+
|
| 109 |
+
If you want to pin a specific OpenClaw release instead of `latest`, add `OPENCLAW_VERSION` under **Variables** in your Space settings. For Docker Spaces, HF passes Variables as build args during image build, so these should be Variables, not Secrets (except tokens).
|
| 110 |
|
| 111 |
### Step 3: Deploy & Run
|
| 112 |
|
|
|
|
| 368 |
| :--- | :--- | :--- | :--- |
|
| 369 |
| `/` | HuggingClaw dashboard | `7861` | Public HF Spaces entrypoint |
|
| 370 |
| `/app/` | OpenClaw Control UI | `7860` | Mounted behind the local reverse proxy |
|
| 371 |
+
| `/terminal/` | JupyterLab terminal | `8888` | Auto-enabled when `GATEWAY_TOKEN` is set; uses `GATEWAY_TOKEN` as auth token unless `JUPYTER_TOKEN` is set separately. Set `DEV_MODE=false` to disable. |
|
| 372 |
|
| 373 |
When enabled, the terminal notebook root is `/home/node`, so you can inspect HuggingClaw files, logs, workspace state, and runtime scripts from the browser.
|
| 374 |
|
| 375 |
> [!IMPORTANT]
|
| 376 |
+
> No extra secret needed — `GATEWAY_TOKEN` is automatically reused as `JUPYTER_TOKEN`. Set a separate `JUPYTER_TOKEN` secret only if you want a different terminal credential.
|
| 377 |
|
| 378 |
## 🔍 Merge Comparison
|
| 379 |
|
|
|
|
| 404 |
2. Resolve backup namespace and restore workspace from HF Dataset.
|
| 405 |
3. Generate `openclaw.json` configuration.
|
| 406 |
4. Launch background tasks (auto-sync, channel helpers).
|
| 407 |
+
5. Start the local dashboard/reverse proxy and OpenClaw gateway (JupyterLab starts when `GATEWAY_TOKEN` is set, `DEV_MODE=true`, or `HUGGINGCLAW_JUPYTER_ENABLED=true`).
|
| 408 |
|
| 409 |
</details>
|
| 410 |
|
health-server.js
CHANGED
|
@@ -20,9 +20,12 @@ const GATEWAY_HOST = "127.0.0.1";
|
|
| 20 |
const JUPYTER_PORT = Number.parseInt(process.env.JUPYTER_PORT || "8888", 10);
|
| 21 |
const JUPYTER_HOST = "127.0.0.1";
|
| 22 |
const JUPYTER_BASE = normalizeBase(process.env.JUPYTER_BASE, "/terminal");
|
|
|
|
| 23 |
const DEV_MODE_ENABLED = isTrue(process.env.DEV_MODE);
|
|
|
|
|
|
|
| 24 |
const JUPYTER_ENABLED = /^(true|1|yes|on)$/i.test(
|
| 25 |
-
process.env.HUGGINGCLAW_JUPYTER_ENABLED || (DEV_MODE_ENABLED ? "true" : "false")
|
| 26 |
);
|
| 27 |
const startTime = Date.now();
|
| 28 |
const LLM_MODEL = process.env.LLM_MODEL || "Not Set";
|
|
@@ -634,7 +637,7 @@ const server = http.createServer(async (req, res) => {
|
|
| 634 |
if (pathname === JUPYTER_BASE || pathname.startsWith(JUPYTER_BASE + "/")) {
|
| 635 |
if (!JUPYTER_ENABLED) {
|
| 636 |
res.writeHead(404, { "Content-Type": "application/json" });
|
| 637 |
-
return res.end(JSON.stringify({ status: "disabled", message: "JupyterLab terminal is disabled. Set DEV_MODE=true to enable /terminal/." }));
|
| 638 |
}
|
| 639 |
if (isDirectHfSpaceRequest) {
|
| 640 |
res.writeHead(200, { "Content-Type": "text/html" });
|
|
|
|
| 20 |
const JUPYTER_PORT = Number.parseInt(process.env.JUPYTER_PORT || "8888", 10);
|
| 21 |
const JUPYTER_HOST = "127.0.0.1";
|
| 22 |
const JUPYTER_BASE = normalizeBase(process.env.JUPYTER_BASE, "/terminal");
|
| 23 |
+
const GATEWAY_TOKEN = (process.env.GATEWAY_TOKEN || "").trim();
|
| 24 |
const DEV_MODE_ENABLED = isTrue(process.env.DEV_MODE);
|
| 25 |
+
// Auto-enable Jupyter when DEV_MODE=true, HUGGINGCLAW_JUPYTER_ENABLED=true, or GATEWAY_TOKEN is set.
|
| 26 |
+
// GATEWAY_TOKEN doubles as JUPYTER_TOKEN in start.sh — no extra secret needed.
|
| 27 |
const JUPYTER_ENABLED = /^(true|1|yes|on)$/i.test(
|
| 28 |
+
process.env.HUGGINGCLAW_JUPYTER_ENABLED || (DEV_MODE_ENABLED ? "true" : GATEWAY_TOKEN ? "true" : "false")
|
| 29 |
);
|
| 30 |
const startTime = Date.now();
|
| 31 |
const LLM_MODEL = process.env.LLM_MODEL || "Not Set";
|
|
|
|
| 637 |
if (pathname === JUPYTER_BASE || pathname.startsWith(JUPYTER_BASE + "/")) {
|
| 638 |
if (!JUPYTER_ENABLED) {
|
| 639 |
res.writeHead(404, { "Content-Type": "application/json" });
|
| 640 |
+
return res.end(JSON.stringify({ status: "disabled", message: "JupyterLab terminal is disabled. Set GATEWAY_TOKEN or DEV_MODE=true to enable /terminal/ (or set HUGGINGCLAW_JUPYTER_ENABLED=true)." }));
|
| 641 |
}
|
| 642 |
if (isDirectHfSpaceRequest) {
|
| 643 |
res.writeHead(200, { "Content-Type": "text/html" });
|
start.sh
CHANGED
|
@@ -93,6 +93,12 @@ DEV_MODE_ENABLED=false
|
|
| 93 |
if hc_is_true "$DEV_MODE_NORMALIZED"; then
|
| 94 |
DEV_MODE_ENABLED=true
|
| 95 |
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
SYNC_INTERVAL="$(trim_var "${SYNC_INTERVAL:-180}")"
|
| 97 |
DEVDATA_DATASET_NAME="$(trim_var "${DEVDATA_DATASET_NAME:-huggingclaw-devdata}")"
|
| 98 |
DEVDATA_SYNC_INTERVAL="$(trim_var "${DEVDATA_SYNC_INTERVAL:-180}")"
|
|
@@ -514,10 +520,10 @@ inject_provider_models_from_env "github-copilot" "GITHUB_COPILOT_MODELS" "COPILO
|
|
| 514 |
|
| 515 |
# Browser configuration (managed local Chromium in HF/Docker)
|
| 516 |
BROWSER_EXECUTABLE_PATH=""
|
| 517 |
-
|
| 518 |
-
#
|
| 519 |
-
#
|
| 520 |
-
#
|
| 521 |
for candidate in \
|
| 522 |
/usr/lib/chromium/chromium \
|
| 523 |
/usr/lib/chromium-browser/chromium-browser \
|
|
@@ -529,10 +535,17 @@ for candidate in \
|
|
| 529 |
BROWSER_EXECUTABLE_PATH="$candidate"
|
| 530 |
break
|
| 531 |
fi
|
|
|
|
|
|
|
|
|
|
| 532 |
fi
|
| 533 |
done
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
if [ -z "$BROWSER_EXECUTABLE_PATH" ]; then
|
| 535 |
-
echo "Warning:
|
| 536 |
fi
|
| 537 |
|
| 538 |
BROWSER_SHOULD_ENABLE=false
|
|
@@ -585,15 +598,11 @@ CONFIG_JSON=$(jq \
|
|
| 585 |
|
| 586 |
if [ "$BROWSER_SHOULD_ENABLE" = "true" ]; then
|
| 587 |
CONFIG_JSON=$(jq \
|
| 588 |
-
--arg execPath "$BROWSER_EXECUTABLE_PATH" \
|
| 589 |
'.browser = {
|
| 590 |
"enabled": true,
|
| 591 |
"defaultProfile": "openclaw",
|
| 592 |
"headless": true,
|
| 593 |
"noSandbox": true,
|
| 594 |
-
"executablePath": $execPath,
|
| 595 |
-
"localLaunchTimeoutMs": 45000,
|
| 596 |
-
"localCdpReadyTimeoutMs": 30000,
|
| 597 |
"extraArgs": [
|
| 598 |
"--no-sandbox",
|
| 599 |
"--disable-setuid-sandbox",
|
|
@@ -925,6 +934,13 @@ start_jupyter_once() {
|
|
| 925 |
return 0
|
| 926 |
fi
|
| 927 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 928 |
# Security guard: refuse to start JupyterLab with the insecure default token.
|
| 929 |
# JupyterLab exposes a full shell — a weak token is equivalent to no auth.
|
| 930 |
if [ -z "${JUPYTER_TOKEN:-}" ] || [ "${JUPYTER_TOKEN}" = "huggingface" ]; then
|
|
|
|
| 93 |
if hc_is_true "$DEV_MODE_NORMALIZED"; then
|
| 94 |
DEV_MODE_ENABLED=true
|
| 95 |
fi
|
| 96 |
+
# Auto-enable DEV_MODE when GATEWAY_TOKEN is set and DEV_MODE was not explicitly configured.
|
| 97 |
+
# GATEWAY_TOKEN doubles as JUPYTER_TOKEN (see start_jupyter_once) — no extra secret required.
|
| 98 |
+
if [ "$DEV_MODE_ENABLED" != "true" ] && [ -z "${DEV_MODE:-}" ] && [ -n "${GATEWAY_TOKEN:-}" ]; then
|
| 99 |
+
DEV_MODE_ENABLED=true
|
| 100 |
+
echo "GATEWAY_TOKEN set and DEV_MODE not explicitly configured — auto-enabling terminal (set DEV_MODE=false to opt out)"
|
| 101 |
+
fi
|
| 102 |
SYNC_INTERVAL="$(trim_var "${SYNC_INTERVAL:-180}")"
|
| 103 |
DEVDATA_DATASET_NAME="$(trim_var "${DEVDATA_DATASET_NAME:-huggingclaw-devdata}")"
|
| 104 |
DEVDATA_SYNC_INTERVAL="$(trim_var "${DEVDATA_SYNC_INTERVAL:-180}")"
|
|
|
|
| 520 |
|
| 521 |
# Browser configuration (managed local Chromium in HF/Docker)
|
| 522 |
BROWSER_EXECUTABLE_PATH=""
|
| 523 |
+
BROWSER_WRAPPER_PATH=""
|
| 524 |
+
# On Debian/Ubuntu, /usr/bin/chromium is often a shell wrapper while the real
|
| 525 |
+
# ELF binary lives under /usr/lib/chromium/*. Prefer a real ELF binary, then
|
| 526 |
+
# fall back to wrapper launchers (Playwright/OpenClaw can execute those too).
|
| 527 |
for candidate in \
|
| 528 |
/usr/lib/chromium/chromium \
|
| 529 |
/usr/lib/chromium-browser/chromium-browser \
|
|
|
|
| 535 |
BROWSER_EXECUTABLE_PATH="$candidate"
|
| 536 |
break
|
| 537 |
fi
|
| 538 |
+
if [ -z "$BROWSER_WRAPPER_PATH" ]; then
|
| 539 |
+
BROWSER_WRAPPER_PATH="$candidate"
|
| 540 |
+
fi
|
| 541 |
fi
|
| 542 |
done
|
| 543 |
+
if [ -z "$BROWSER_EXECUTABLE_PATH" ] && [ -n "$BROWSER_WRAPPER_PATH" ]; then
|
| 544 |
+
BROWSER_EXECUTABLE_PATH="$BROWSER_WRAPPER_PATH"
|
| 545 |
+
echo "No ELF Chromium binary found; using launcher wrapper at $BROWSER_EXECUTABLE_PATH"
|
| 546 |
+
fi
|
| 547 |
if [ -z "$BROWSER_EXECUTABLE_PATH" ]; then
|
| 548 |
+
echo "Warning: Chromium executable not found. Browser plugin will be disabled."
|
| 549 |
fi
|
| 550 |
|
| 551 |
BROWSER_SHOULD_ENABLE=false
|
|
|
|
| 598 |
|
| 599 |
if [ "$BROWSER_SHOULD_ENABLE" = "true" ]; then
|
| 600 |
CONFIG_JSON=$(jq \
|
|
|
|
| 601 |
'.browser = {
|
| 602 |
"enabled": true,
|
| 603 |
"defaultProfile": "openclaw",
|
| 604 |
"headless": true,
|
| 605 |
"noSandbox": true,
|
|
|
|
|
|
|
|
|
|
| 606 |
"extraArgs": [
|
| 607 |
"--no-sandbox",
|
| 608 |
"--disable-setuid-sandbox",
|
|
|
|
| 934 |
return 0
|
| 935 |
fi
|
| 936 |
|
| 937 |
+
# GATEWAY_TOKEN fallback: if JUPYTER_TOKEN is unset or still the insecure default,
|
| 938 |
+
# reuse GATEWAY_TOKEN. Both protect the same Space, so the credential is equivalent.
|
| 939 |
+
if { [ -z "${JUPYTER_TOKEN:-}" ] || [ "${JUPYTER_TOKEN}" = "huggingface" ]; } && [ -n "${GATEWAY_TOKEN:-}" ]; then
|
| 940 |
+
JUPYTER_TOKEN="$GATEWAY_TOKEN"
|
| 941 |
+
echo "JUPYTER_TOKEN not set — using GATEWAY_TOKEN as terminal auth token"
|
| 942 |
+
fi
|
| 943 |
+
|
| 944 |
# Security guard: refuse to start JupyterLab with the insecure default token.
|
| 945 |
# JupyterLab exposes a full shell — a weak token is equivalent to no auth.
|
| 946 |
if [ -z "${JUPYTER_TOKEN:-}" ] || [ "${JUPYTER_TOKEN}" = "huggingface" ]; then
|