tao-shen Claude Opus 4.6 commited on
Commit
e01d625
Β·
1 Parent(s): ebf240b

fix: revert auth to password mode, add Telegram API auto-probe, background DNS

Browse files

1. Revert auth from token back to password (fixes token_mismatch on Control UI)
2. Add Telegram API base auto-probe: probes api.telegram.org, mirrors, and
Cloudflare Pages proxy at startup; selects first working endpoint
3. Revert DNS resolution from blocking (141s delay) to background
4. Add TELEGRAM_BOT_TOKEN and TELEGRAM_API_BASE env vars to README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (4) hide show
  1. README.md +4 -0
  2. openclaw.json +1 -1
  3. scripts/entrypoint.sh +7 -6
  4. scripts/sync_hf.py +102 -5
README.md CHANGED
@@ -116,6 +116,8 @@ Fine-tune persistence and performance. Set these as **Repository Secrets** in HF
116
  | `AUTO_CREATE_DATASET` | `false` | **Auto-create the Dataset repo.** Default is `false` for security. Set to `true` to let HuggingClaw automatically create a **private** Dataset repo on first startup (and auto-derive the repo name from your `HF_TOKEN` if `OPENCLAW_DATASET_REPO` is not set). Accepted values: `true`, `1`, `yes` / `false`, `0`, `no`. |
117
  | `SYNC_INTERVAL` | `60` | **Backup interval in seconds.** How often HuggingClaw syncs `~/.openclaw` to the Dataset repo. Lower = safer but more API calls. Recommended: `60`–`300`. |
118
  | `NODE_MEMORY_LIMIT` | `512` | **Node.js heap memory limit in MB.** HF free tier provides 16 GB RAM; 512 MB is enough for most cases. Increase for complex agent workflows. |
 
 
119
  | `TZ` | `UTC` | **Timezone** for log timestamps. Example: `Asia/Shanghai`, `America/New_York`. |
120
 
121
  > For the full list (including `OPENAI_BASE_URL`, `OLLAMA_HOST`, proxy settings, etc.), see [`.env.example`](.env.example).
@@ -126,6 +128,8 @@ Visit your Space URL. Click the settings icon, enter your password, and connect.
126
 
127
  Messaging integrations (Telegram, WhatsApp) can be configured directly inside the Control UI after connecting.
128
 
 
 
129
  ## Configuration
130
 
131
  HuggingClaw supports **all OpenClaw environment variables** β€” it passes the entire environment to the OpenClaw process (`env=os.environ.copy()`), so any variable from the [OpenClaw docs](https://openclawdoc.com/docs/reference/environment-variables) works out of the box in HF Spaces. This includes:
 
116
  | `AUTO_CREATE_DATASET` | `false` | **Auto-create the Dataset repo.** Default is `false` for security. Set to `true` to let HuggingClaw automatically create a **private** Dataset repo on first startup (and auto-derive the repo name from your `HF_TOKEN` if `OPENCLAW_DATASET_REPO` is not set). Accepted values: `true`, `1`, `yes` / `false`, `0`, `no`. |
117
  | `SYNC_INTERVAL` | `60` | **Backup interval in seconds.** How often HuggingClaw syncs `~/.openclaw` to the Dataset repo. Lower = safer but more API calls. Recommended: `60`–`300`. |
118
  | `NODE_MEMORY_LIMIT` | `512` | **Node.js heap memory limit in MB.** HF free tier provides 16 GB RAM; 512 MB is enough for most cases. Increase for complex agent workflows. |
119
+ | `TELEGRAM_BOT_TOKEN` | β€” | **Telegram bot token.** If set, HuggingClaw auto-probes Telegram API endpoints at startup and selects a working one (HF Spaces blocks `api.telegram.org`). You can also configure the bot token in the Control UI. |
120
+ | `TELEGRAM_API_BASE` | β€” | **Force a specific Telegram API base URL.** Skips auto-probe. Example: `https://telegram-api.mykdigi.com`. Only needed if auto-probe doesn't find a working endpoint. |
121
  | `TZ` | `UTC` | **Timezone** for log timestamps. Example: `Asia/Shanghai`, `America/New_York`. |
122
 
123
  > For the full list (including `OPENAI_BASE_URL`, `OLLAMA_HOST`, proxy settings, etc.), see [`.env.example`](.env.example).
 
128
 
129
  Messaging integrations (Telegram, WhatsApp) can be configured directly inside the Control UI after connecting.
130
 
131
+ > **Telegram note:** HF Spaces blocks `api.telegram.org` DNS. HuggingClaw automatically probes alternative API endpoints at startup and selects one that works β€” no manual configuration needed.
132
+
133
  ## Configuration
134
 
135
  HuggingClaw supports **all OpenClaw environment variables** β€” it passes the entire environment to the OpenClaw process (`env=os.environ.copy()`), so any variable from the [OpenClaw docs](https://openclawdoc.com/docs/reference/environment-variables) works out of the box in HF Spaces. This includes:
openclaw.json CHANGED
@@ -3,7 +3,7 @@
3
  "mode": "local",
4
  "bind": "lan",
5
  "port": 7860,
6
- "auth": { "token": "__OPENCLAW_PASSWORD__" },
7
  "trustedProxies": [
8
  "0.0.0.0/0"
9
  ],
 
3
  "mode": "local",
4
  "bind": "lan",
5
  "port": 7860,
6
+ "auth": { "password": "__OPENCLAW_PASSWORD__" },
7
  "trustedProxies": [
8
  "0.0.0.0/0"
9
  ],
scripts/entrypoint.sh CHANGED
@@ -6,12 +6,13 @@ BOOT_START=$(date +%s)
6
  echo "[entrypoint] OpenClaw HuggingFace Spaces Entrypoint"
7
  echo "[entrypoint] ======================================="
8
 
9
- # ── DNS pre-resolution (BLOCKING β€” needed before app starts) ──────────────
10
- echo "[entrypoint] Resolving WhatsApp & Telegram domains via DNS-over-HTTPS..."
11
- DNS_START=$(date +%s)
12
- python3 /home/node/scripts/dns-resolve.py /tmp/dns-resolved.json 2>&1
13
- DNS_END=$(date +%s)
14
- echo "[TIMER] DNS pre-resolve: $((DNS_END - DNS_START))s"
 
15
 
16
  # ── Node.js memory limit (only if explicitly set) ─────────────────────────
17
  if [ -n "$NODE_MEMORY_LIMIT" ]; then
 
6
  echo "[entrypoint] OpenClaw HuggingFace Spaces Entrypoint"
7
  echo "[entrypoint] ======================================="
8
 
9
+ # ── DNS pre-resolution (background β€” non-blocking) ───────────────────────
10
+ # Resolves WhatsApp domains via DoH for dns-fix.cjs to consume.
11
+ # Telegram connectivity is handled by API base auto-probe in sync_hf.py.
12
+ echo "[entrypoint] Starting DNS resolution in background..."
13
+ python3 /home/node/scripts/dns-resolve.py /tmp/dns-resolved.json 2>&1 &
14
+ DNS_PID=$!
15
+ echo "[entrypoint] DNS resolver PID: $DNS_PID"
16
 
17
  # ── Node.js memory limit (only if explicitly set) ─────────────────────────
18
  if [ -n "$NODE_MEMORY_LIMIT" ]; then
scripts/sync_hf.py CHANGED
@@ -22,6 +22,8 @@ import shutil
22
  import tempfile
23
  import traceback
24
  import re
 
 
25
  from pathlib import Path
26
  from datetime import datetime
27
  # Set timeout BEFORE importing huggingface_hub
@@ -100,6 +102,73 @@ log_dir.mkdir(parents=True, exist_ok=True)
100
  sys.stdout = TeeLogger(log_dir / "sync.log", sys.stdout)
101
  sys.stderr = sys.stdout
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  # ── Sync Manager ────────────────────────────────────────────────────────────
104
 
105
  class OpenClawFullSync:
@@ -292,8 +361,8 @@ class OpenClawFullSync:
292
  cfg = json.load(f)
293
  # Replace token placeholder
294
  if "gateway" in cfg and "auth" in cfg["gateway"]:
295
- if cfg["gateway"]["auth"].get("token") == "__OPENCLAW_PASSWORD__":
296
- cfg["gateway"]["auth"]["token"] = OPENCLAW_PASSWORD
297
  if OPENAI_API_KEY and "models" in cfg and "providers" in cfg["models"] and "openai" in cfg["models"]["providers"]:
298
  cfg["models"]["providers"]["openai"]["apiKey"] = OPENAI_API_KEY
299
  if OPENAI_BASE_URL:
@@ -364,10 +433,9 @@ class OpenClawFullSync:
364
  data["plugins"]["locations"] = [l for l in locs if l != "/dev/null"]
365
 
366
  # Force full gateway config for HF Spaces
367
- # Token auth: persisted in browser localStorage after first entry
368
  if not OPENCLAW_PASSWORD:
369
  print("[SYNC] WARNING: OPENCLAW_PASSWORD not set! Gateway will have no auth.")
370
- auth = {"token": OPENCLAW_PASSWORD} if OPENCLAW_PASSWORD else {}
371
  # Dynamic allowedOrigins from SPACE_HOST (auto-set by HF runtime)
372
  allowed_origins = [
373
  "https://huggingface.co",
@@ -388,7 +456,7 @@ class OpenClawFullSync:
388
  "allowedOrigins": allowed_origins
389
  }
390
  }
391
- print(f"[SYNC] Set gateway config (auth={'token' if OPENCLAW_PASSWORD else 'none'}, origins={len(allowed_origins)})")
392
 
393
  # Ensure agents defaults
394
  data.setdefault("agents", {}).setdefault("defaults", {}).setdefault("model", {})
@@ -428,6 +496,35 @@ class OpenClawFullSync:
428
  elif isinstance(data["plugins"]["entries"]["telegram"], dict):
429
  data["plugins"]["entries"]["telegram"]["enabled"] = True
430
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  with open(config_path, "w") as f:
432
  json.dump(data, f, indent=2)
433
  print("[SYNC] Config patched and saved.")
 
22
  import tempfile
23
  import traceback
24
  import re
25
+ import urllib.request
26
+ import ssl
27
  from pathlib import Path
28
  from datetime import datetime
29
  # Set timeout BEFORE importing huggingface_hub
 
102
  sys.stdout = TeeLogger(log_dir / "sync.log", sys.stdout)
103
  sys.stderr = sys.stdout
104
 
105
+ # ── Telegram API Base Auto-Probe ────────────────────────────────────────────
106
+
107
+ # HF Spaces blocks DNS for api.telegram.org. Probe multiple API bases at
108
+ # startup and pick the first one that responds to getMe. The working base
109
+ # is then injected into the OpenClaw Telegram channel config.
110
+
111
+ # User can force a specific base via env var (skip auto-probe)
112
+ TELEGRAM_API_BASE = os.environ.get("TELEGRAM_API_BASE", "")
113
+
114
+ TELEGRAM_API_BASES = [
115
+ "https://api.telegram.org", # official
116
+ "https://telegram-api.mykdigi.com", # known mirror
117
+ "https://telegram-api-proxy-anonymous.pages.dev/api", # Cloudflare Pages proxy
118
+ ]
119
+
120
+ def probe_telegram_api(bot_token: str, timeout: int = 10) -> str:
121
+ """Probe multiple Telegram API base URLs and return the first working one.
122
+ Returns the working base URL (without trailing slash), or empty string if none work.
123
+ """
124
+ if not bot_token:
125
+ return ""
126
+
127
+ ctx = ssl.create_default_context()
128
+ for base in TELEGRAM_API_BASES:
129
+ url = f"{base}/bot{bot_token}/getMe"
130
+ try:
131
+ req = urllib.request.Request(url, method="GET")
132
+ resp = urllib.request.urlopen(req, timeout=timeout, context=ctx)
133
+ data = json.loads(resp.read().decode())
134
+ if data.get("ok"):
135
+ bot_name = data.get("result", {}).get("username", "unknown")
136
+ print(f"[TELEGRAM] βœ“ API base works: {base} (bot: @{bot_name})")
137
+ return base.rstrip("/")
138
+ except Exception as e:
139
+ reason = str(e)[:80]
140
+ print(f"[TELEGRAM] βœ— API base failed: {base} ({reason})")
141
+ continue
142
+
143
+ print("[TELEGRAM] WARNING: All API bases failed! Telegram will not work.")
144
+ return ""
145
+
146
+ def get_telegram_bot_token() -> str:
147
+ """Extract Telegram bot token from OpenClaw config or environment."""
148
+ # 1. Environment variable
149
+ token = os.environ.get("TELEGRAM_BOT_TOKEN", "")
150
+ if token:
151
+ return token
152
+
153
+ # 2. From openclaw.json channels config
154
+ config_path = OPENCLAW_HOME / "openclaw.json"
155
+ if config_path.exists():
156
+ try:
157
+ with open(config_path) as f:
158
+ cfg = json.load(f)
159
+ # Check channels.telegram.botToken (single account)
160
+ tg = cfg.get("channels", {}).get("telegram", {})
161
+ if tg.get("botToken"):
162
+ return tg["botToken"]
163
+ # Check channels.telegram.accounts.*.botToken (multi account)
164
+ for acc in tg.get("accounts", {}).values():
165
+ if isinstance(acc, dict) and acc.get("botToken"):
166
+ return acc["botToken"]
167
+ except Exception:
168
+ pass
169
+ return ""
170
+
171
+
172
  # ── Sync Manager ────────────────────────────────────────────────────────────
173
 
174
  class OpenClawFullSync:
 
361
  cfg = json.load(f)
362
  # Replace token placeholder
363
  if "gateway" in cfg and "auth" in cfg["gateway"]:
364
+ if cfg["gateway"]["auth"].get("password") == "__OPENCLAW_PASSWORD__":
365
+ cfg["gateway"]["auth"]["password"] = OPENCLAW_PASSWORD
366
  if OPENAI_API_KEY and "models" in cfg and "providers" in cfg["models"] and "openai" in cfg["models"]["providers"]:
367
  cfg["models"]["providers"]["openai"]["apiKey"] = OPENAI_API_KEY
368
  if OPENAI_BASE_URL:
 
433
  data["plugins"]["locations"] = [l for l in locs if l != "/dev/null"]
434
 
435
  # Force full gateway config for HF Spaces
 
436
  if not OPENCLAW_PASSWORD:
437
  print("[SYNC] WARNING: OPENCLAW_PASSWORD not set! Gateway will have no auth.")
438
+ auth = {"password": OPENCLAW_PASSWORD} if OPENCLAW_PASSWORD else {}
439
  # Dynamic allowedOrigins from SPACE_HOST (auto-set by HF runtime)
440
  allowed_origins = [
441
  "https://huggingface.co",
 
456
  "allowedOrigins": allowed_origins
457
  }
458
  }
459
+ print(f"[SYNC] Set gateway config (auth={'password' if OPENCLAW_PASSWORD else 'none'}, origins={len(allowed_origins)})")
460
 
461
  # Ensure agents defaults
462
  data.setdefault("agents", {}).setdefault("defaults", {}).setdefault("model", {})
 
496
  elif isinstance(data["plugins"]["entries"]["telegram"], dict):
497
  data["plugins"]["entries"]["telegram"]["enabled"] = True
498
 
499
+ # ── Telegram API base auto-probe ──────────────────────────────
500
+ # HF Spaces blocks api.telegram.org DNS. Probe mirrors and set
501
+ # the working base URL in the channel config so grammY uses it.
502
+ if TELEGRAM_API_BASE:
503
+ # User explicitly set a base via env var β€” use it directly
504
+ data.setdefault("channels", {}).setdefault("telegram", {})
505
+ data["channels"]["telegram"]["apiRoot"] = TELEGRAM_API_BASE.rstrip("/")
506
+ print(f"[TELEGRAM] Using user-specified API base: {TELEGRAM_API_BASE}")
507
+ else:
508
+ bot_token = get_telegram_bot_token()
509
+ if bot_token:
510
+ print("[TELEGRAM] Probing Telegram API bases...")
511
+ working_base = probe_telegram_api(bot_token)
512
+ if working_base and working_base != "https://api.telegram.org":
513
+ # Set apiRoot in channels.telegram for OpenClaw/grammY
514
+ data.setdefault("channels", {}).setdefault("telegram", {})
515
+ data["channels"]["telegram"]["apiRoot"] = working_base
516
+ print(f"[TELEGRAM] Set channels.telegram.apiRoot = {working_base}")
517
+ elif working_base:
518
+ # Official API works β€” remove any previously set mirror
519
+ tg_ch = data.get("channels", {}).get("telegram", {})
520
+ if tg_ch.get("apiRoot"):
521
+ del tg_ch["apiRoot"]
522
+ print("[TELEGRAM] Official API works β€” cleared apiRoot override")
523
+ else:
524
+ print("[TELEGRAM] Official API works β€” no override needed")
525
+ else:
526
+ print("[TELEGRAM] No bot token found β€” skipping API probe")
527
+
528
  with open(config_path, "w") as f:
529
  json.dump(data, f, indent=2)
530
  print("[SYNC] Config patched and saved.")