Spaces:
Sleeping
Sleeping
tao-shen commited on
Commit ยท
0d9d280
1
Parent(s): b19d31a
feat: add timing to all stages, fix OPENROUTER_API_KEY crash, async DNS
Browse files- Dockerfile +56 -44
- openclaw.json +1 -1
- scripts/entrypoint.sh +23 -10
- scripts/sync_hf.py +17 -0
Dockerfile
CHANGED
|
@@ -1,70 +1,82 @@
|
|
| 1 |
-
# OpenClaw on Hugging Face Spaces โ ไปๆบ็ ๆๅปบ
|
| 2 |
# ๆๆกฃ: https://huggingface.co/docs/hub/spaces-sdks-docker
|
| 3 |
|
| 4 |
FROM node:22-bookworm
|
|
|
|
| 5 |
|
| 6 |
-
#
|
| 7 |
-
RUN echo "
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
RUN
|
| 11 |
-
&&
|
| 12 |
-
|
| 13 |
|
| 14 |
-
|
| 15 |
-
RUN
|
|
|
|
|
|
|
|
|
|
| 16 |
ENV PATH="/root/.bun/bin:${PATH}"
|
| 17 |
|
|
|
|
| 18 |
WORKDIR /app
|
| 19 |
-
RUN
|
|
|
|
|
|
|
| 20 |
WORKDIR /app/openclaw
|
| 21 |
|
| 22 |
-
#
|
| 23 |
-
# ้ฟๅ
่งฃๅฏๅคฑ่ดฅ๏ผBad MAC๏ผ็ๆถๆฏ่ขซ่ฏฏ่ฎกไธบๅทฒๆฅๆถๅฏผ่ด lastInboundAt ๆๅผไฝๆ ๆณๅๅค
|
| 24 |
COPY patches /app/patches
|
| 25 |
-
RUN
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
RUN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
ENV OPENCLAW_PREFER_PNPM=1
|
| 30 |
-
RUN pnpm ui:build
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
import pathlib
|
| 42 |
-
p = pathlib.Path('dist/control-ui/index.html')
|
| 43 |
-
script = '<script>!function(){var K="openclaw.control.settings.v1";try{var s=JSON.parse(localStorage.getItem(K)||"{}")||{};if(!s.token){s.token="openclaw-space-default";localStorage.setItem(K,JSON.stringify(s))}}catch(e){}}()</script>'
|
| 44 |
-
h = p.read_text()
|
| 45 |
-
p.write_text(h.replace('</head>', script + '</head>'))
|
| 46 |
-
print('[build-check] Token auto-config injected into Control UI')
|
| 47 |
-
PYEOF
|
| 48 |
-
|
| 49 |
-
# ไธไฟฎๆนๅ
้จไปฃ็ ๏ผๆน็จๅค้จ WebSocket ็ๆค่ๆฌๅค็ 515 ้่ฟ
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
ENV NODE_ENV=production
|
| 52 |
-
# ็ฆ็จ bundled ๆไปถๅ็ฐ๏ผๆน็ฑ global symlink ๆไพ๏ผ๏ผ็จ็ฉบ็ฎๅฝๆฟไปฃ /dev/null ้ฟๅ
ENOTDIR ่ญฆๅ
|
| 53 |
RUN mkdir -p /app/openclaw/empty-bundled-plugins
|
| 54 |
ENV OPENCLAW_BUNDLED_PLUGINS_DIR=/app/openclaw/empty-bundled-plugins
|
| 55 |
RUN chown -R node:node /app
|
| 56 |
|
| 57 |
-
# ๅๅปบ ~/.openclaw ็ฎๅฝ็ปๆ
|
| 58 |
RUN mkdir -p /home/node/.openclaw/workspace /home/node/.openclaw/credentials
|
| 59 |
-
# Note: openclaw.json is NOT copied here - it will be restored from Dataset by openclaw_sync.py
|
| 60 |
-
# The new persistence system backs up and restores the entire ~/.openclaw directory
|
| 61 |
|
| 62 |
-
# ๆไน
ๅ่ๆฌ๏ผๅฎๆด็ฎๅฝๅคไปฝ๏ผ & DNS ไฟฎๅค
|
| 63 |
-
COPY --chown=node:node scripts /home/node/scripts
|
| 64 |
COPY --chown=node:node openclaw.json /home/node/scripts/openclaw.json.default
|
| 65 |
-
RUN chmod +x /home/node/scripts/entrypoint.sh
|
| 66 |
-
|
| 67 |
-
|
| 68 |
|
| 69 |
USER node
|
| 70 |
ENV HOME=/home/node
|
|
|
|
| 1 |
+
# OpenClaw on Hugging Face Spaces โ ไปๆบ็ ๆๅปบ๏ผๅธฆ่ฎกๆถ๏ผ
|
| 2 |
# ๆๆกฃ: https://huggingface.co/docs/hub/spaces-sdks-docker
|
| 3 |
|
| 4 |
FROM node:22-bookworm
|
| 5 |
+
SHELL ["/bin/bash", "-c"]
|
| 6 |
|
| 7 |
+
# โโ Step 1: System dependencies โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 8 |
+
RUN echo "[build][step1] Installing system deps..." && START=$(date +%s) \
|
| 9 |
+
&& apt-get update && apt-get install -y --no-install-recommends git ca-certificates curl python3 python3-pip \
|
| 10 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 11 |
+
&& echo "[build][step1] System deps: $(($(date +%s) - START))s"
|
| 12 |
|
| 13 |
+
# โโ Step 2: Python dependencies โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 14 |
+
RUN echo "[build][step2] Installing huggingface_hub..." && START=$(date +%s) \
|
| 15 |
+
&& pip3 install --no-cache-dir --break-system-packages huggingface_hub \
|
| 16 |
+
&& echo "[build][step2] huggingface_hub: $(($(date +%s) - START))s"
|
| 17 |
|
| 18 |
+
# โโ Step 3: Node tooling โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 19 |
+
RUN echo "[build][step3] Enabling corepack + bun..." && START=$(date +%s) \
|
| 20 |
+
&& corepack enable \
|
| 21 |
+
&& curl -fsSL https://bun.sh/install | bash \
|
| 22 |
+
&& echo "[build][step3] Corepack + Bun: $(($(date +%s) - START))s"
|
| 23 |
ENV PATH="/root/.bun/bin:${PATH}"
|
| 24 |
|
| 25 |
+
# โโ Step 4: Clone OpenClaw โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 26 |
WORKDIR /app
|
| 27 |
+
RUN echo "[build][step4] Cloning OpenClaw..." && START=$(date +%s) \
|
| 28 |
+
&& git clone --depth 1 https://github.com/openclaw/openclaw.git openclaw \
|
| 29 |
+
&& echo "[build][step4] Git clone: $(($(date +%s) - START))s"
|
| 30 |
WORKDIR /app/openclaw
|
| 31 |
|
| 32 |
+
# โโ Step 5: Apply patches โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
|
|
| 33 |
COPY patches /app/patches
|
| 34 |
+
RUN echo "[build][step5] Applying patches..." && START=$(date +%s) \
|
| 35 |
+
&& if [ -f /app/patches/web-inbound-record-activity-after-body.patch ]; then \
|
| 36 |
+
patch -p1 < /app/patches/web-inbound-record-activity-after-body.patch; \
|
| 37 |
+
fi \
|
| 38 |
+
&& echo "[build][step5] Patches: $(($(date +%s) - START))s"
|
| 39 |
|
| 40 |
+
# โโ Step 6: pnpm install โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 41 |
+
RUN echo "[build][step6] pnpm install..." && START=$(date +%s) \
|
| 42 |
+
&& pnpm install --frozen-lockfile \
|
| 43 |
+
&& echo "[build][step6] pnpm install: $(($(date +%s) - START))s"
|
| 44 |
+
|
| 45 |
+
# โโ Step 7: pnpm build โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 46 |
+
RUN echo "[build][step7] pnpm build..." && START=$(date +%s) \
|
| 47 |
+
&& pnpm build \
|
| 48 |
+
&& echo "[build][step7] pnpm build: $(($(date +%s) - START))s"
|
| 49 |
+
|
| 50 |
+
# โโ Step 8: pnpm ui:build โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 51 |
ENV OPENCLAW_PREFER_PNPM=1
|
| 52 |
+
RUN echo "[build][step8] pnpm ui:build..." && START=$(date +%s) \
|
| 53 |
+
&& pnpm ui:build \
|
| 54 |
+
&& echo "[build][step8] pnpm ui:build: $(($(date +%s) - START))s"
|
| 55 |
+
|
| 56 |
+
# โโ Step 9: Verify build artifacts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 57 |
+
RUN echo "[build][step9] Verifying build artifacts..." \
|
| 58 |
+
&& test -f dist/entry.js && echo " OK dist/entry.js" \
|
| 59 |
+
&& test -f dist/plugin-sdk/index.js && echo " OK dist/plugin-sdk/index.js" \
|
| 60 |
+
&& test -d extensions/telegram && echo " OK extensions/telegram" \
|
| 61 |
+
&& test -d extensions/whatsapp && echo " OK extensions/whatsapp" \
|
| 62 |
+
&& test -d dist/control-ui && echo " OK dist/control-ui"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
# โโ Step 10: Inject auto-token into Control UI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 65 |
+
COPY --chown=node:node scripts /home/node/scripts
|
| 66 |
+
RUN chmod +x /home/node/scripts/inject-token.sh && bash /home/node/scripts/inject-token.sh
|
| 67 |
+
|
| 68 |
+
# โโ Step 11: Final setup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 69 |
ENV NODE_ENV=production
|
|
|
|
| 70 |
RUN mkdir -p /app/openclaw/empty-bundled-plugins
|
| 71 |
ENV OPENCLAW_BUNDLED_PLUGINS_DIR=/app/openclaw/empty-bundled-plugins
|
| 72 |
RUN chown -R node:node /app
|
| 73 |
|
|
|
|
| 74 |
RUN mkdir -p /home/node/.openclaw/workspace /home/node/.openclaw/credentials
|
|
|
|
|
|
|
| 75 |
|
|
|
|
|
|
|
| 76 |
COPY --chown=node:node openclaw.json /home/node/scripts/openclaw.json.default
|
| 77 |
+
RUN chmod +x /home/node/scripts/entrypoint.sh \
|
| 78 |
+
&& chmod +x /home/node/scripts/sync_hf.py \
|
| 79 |
+
&& chown -R node:node /home/node
|
| 80 |
|
| 81 |
USER node
|
| 82 |
ENV HOME=/home/node
|
openclaw.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
| 22 |
"providers": {
|
| 23 |
"openrouter": {
|
| 24 |
"baseUrl": "https://openrouter.ai/api/v1",
|
| 25 |
-
"apiKey": "
|
| 26 |
"api": "openai-completions",
|
| 27 |
"models": [
|
| 28 |
{
|
|
|
|
| 22 |
"providers": {
|
| 23 |
"openrouter": {
|
| 24 |
"baseUrl": "https://openrouter.ai/api/v1",
|
| 25 |
+
"apiKey": "__OPENROUTER_API_KEY__",
|
| 26 |
"api": "openai-completions",
|
| 27 |
"models": [
|
| 28 |
{
|
scripts/entrypoint.sh
CHANGED
|
@@ -1,41 +1,54 @@
|
|
| 1 |
#!/bin/sh
|
| 2 |
set -e
|
| 3 |
|
|
|
|
|
|
|
| 4 |
echo "[entrypoint] OpenClaw HuggingFace Spaces Entrypoint"
|
| 5 |
echo "[entrypoint] ======================================="
|
| 6 |
|
| 7 |
-
# DNS pre-resolution
|
| 8 |
-
echo "[entrypoint] Resolving WhatsApp domains via DNS-over-HTTPS..."
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--require /home/node/scripts/dns-fix.cjs"
|
| 13 |
|
| 14 |
-
#
|
|
|
|
| 15 |
if [ ! -L /home/node/.openclaw/extensions ]; then
|
| 16 |
rm -rf /home/node/.openclaw/extensions 2>/dev/null || true
|
| 17 |
ln -s /app/openclaw/extensions /home/node/.openclaw/extensions
|
| 18 |
echo "[entrypoint] Created extensions symlink -> /app/openclaw/extensions"
|
| 19 |
fi
|
|
|
|
| 20 |
|
| 21 |
-
#
|
| 22 |
if [ -d /home/node/.openclaw/credentials/whatsapp ]; then
|
| 23 |
echo "[entrypoint] Found existing WhatsApp credentials - will use for auto-connect"
|
| 24 |
fi
|
| 25 |
|
| 26 |
-
# Build artifacts check
|
| 27 |
cd /app/openclaw
|
| 28 |
echo "[entrypoint] Build artifacts check:"
|
| 29 |
test -f dist/entry.js && echo " OK dist/entry.js" || echo " WARNING: dist/entry.js missing!"
|
| 30 |
test -f dist/plugin-sdk/index.js && echo " OK dist/plugin-sdk/index.js" || echo " WARNING: dist/plugin-sdk/index.js missing!"
|
| 31 |
echo " Extensions: $(ls extensions/ 2>/dev/null | wc -l | tr -d ' ') found"
|
| 32 |
echo " Global extensions link: $(readlink /home/node/.openclaw/extensions 2>/dev/null || echo 'NOT SET')"
|
| 33 |
-
echo " DNS resolved: $(cat /tmp/dns-resolved.json 2>/dev/null || echo 'file missing')"
|
| 34 |
|
| 35 |
# Create logs directory
|
| 36 |
mkdir -p /home/node/logs
|
| 37 |
touch /home/node/logs/app.log
|
| 38 |
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
| 40 |
echo "[entrypoint] Starting OpenClaw via sync_hf.py..."
|
|
|
|
| 41 |
exec python3 -u /home/node/scripts/sync_hf.py
|
|
|
|
| 1 |
#!/bin/sh
|
| 2 |
set -e
|
| 3 |
|
| 4 |
+
BOOT_START=$(date +%s)
|
| 5 |
+
|
| 6 |
echo "[entrypoint] OpenClaw HuggingFace Spaces Entrypoint"
|
| 7 |
echo "[entrypoint] ======================================="
|
| 8 |
|
| 9 |
+
# โโ DNS pre-resolution (run in BACKGROUND โ was 121s blocking) โโโโโโโโโโโโโโ
|
| 10 |
+
echo "[entrypoint] Resolving WhatsApp domains via DNS-over-HTTPS (background)..."
|
| 11 |
+
DNS_START=$(date +%s)
|
| 12 |
+
(
|
| 13 |
+
python3 /home/node/scripts/dns-resolve.py /tmp/dns-resolved.json 2>&1
|
| 14 |
+
DNS_END=$(date +%s)
|
| 15 |
+
echo "[TIMER] DNS pre-resolve (background): $((DNS_END - DNS_START))s"
|
| 16 |
+
) &
|
| 17 |
+
DNS_PID=$!
|
| 18 |
+
|
| 19 |
+
# Enable Node.js DNS fix (will use resolved file when ready)
|
| 20 |
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--require /home/node/scripts/dns-fix.cjs"
|
| 21 |
|
| 22 |
+
# โโ Extensions symlink โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 23 |
+
SYMLINK_START=$(date +%s)
|
| 24 |
if [ ! -L /home/node/.openclaw/extensions ]; then
|
| 25 |
rm -rf /home/node/.openclaw/extensions 2>/dev/null || true
|
| 26 |
ln -s /app/openclaw/extensions /home/node/.openclaw/extensions
|
| 27 |
echo "[entrypoint] Created extensions symlink -> /app/openclaw/extensions"
|
| 28 |
fi
|
| 29 |
+
echo "[TIMER] Extensions symlink: $(($(date +%s) - SYMLINK_START))s"
|
| 30 |
|
| 31 |
+
# โโ WhatsApp credentials check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 32 |
if [ -d /home/node/.openclaw/credentials/whatsapp ]; then
|
| 33 |
echo "[entrypoint] Found existing WhatsApp credentials - will use for auto-connect"
|
| 34 |
fi
|
| 35 |
|
| 36 |
+
# โโ Build artifacts check โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 37 |
cd /app/openclaw
|
| 38 |
echo "[entrypoint] Build artifacts check:"
|
| 39 |
test -f dist/entry.js && echo " OK dist/entry.js" || echo " WARNING: dist/entry.js missing!"
|
| 40 |
test -f dist/plugin-sdk/index.js && echo " OK dist/plugin-sdk/index.js" || echo " WARNING: dist/plugin-sdk/index.js missing!"
|
| 41 |
echo " Extensions: $(ls extensions/ 2>/dev/null | wc -l | tr -d ' ') found"
|
| 42 |
echo " Global extensions link: $(readlink /home/node/.openclaw/extensions 2>/dev/null || echo 'NOT SET')"
|
|
|
|
| 43 |
|
| 44 |
# Create logs directory
|
| 45 |
mkdir -p /home/node/logs
|
| 46 |
touch /home/node/logs/app.log
|
| 47 |
|
| 48 |
+
ENTRYPOINT_END=$(date +%s)
|
| 49 |
+
echo "[TIMER] Entrypoint (before sync_hf.py): $((ENTRYPOINT_END - BOOT_START))s"
|
| 50 |
+
|
| 51 |
+
# โโ Start OpenClaw via sync_hf.py (don't wait for DNS โ it runs in bg) โโโโโ
|
| 52 |
echo "[entrypoint] Starting OpenClaw via sync_hf.py..."
|
| 53 |
+
echo "[entrypoint] DNS resolution running in background (PID $DNS_PID), app will use it when ready"
|
| 54 |
exec python3 -u /home/node/scripts/sync_hf.py
|
scripts/sync_hf.py
CHANGED
|
@@ -252,6 +252,14 @@ class OpenClawFullSync:
|
|
| 252 |
default_src = Path(__file__).parent / "openclaw.json.default"
|
| 253 |
if default_src.exists():
|
| 254 |
shutil.copy2(str(default_src), str(config_path))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
print("[SYNC] Created openclaw.json from default template")
|
| 256 |
else:
|
| 257 |
with open(config_path, "w") as f:
|
|
@@ -499,10 +507,16 @@ class OpenClawFullSync:
|
|
| 499 |
|
| 500 |
def main():
|
| 501 |
try:
|
|
|
|
|
|
|
|
|
|
| 502 |
sync = OpenClawFullSync()
|
|
|
|
| 503 |
|
| 504 |
# 1. Restore
|
|
|
|
| 505 |
sync.load_from_repo()
|
|
|
|
| 506 |
|
| 507 |
# 2. Background sync
|
| 508 |
stop_event = threading.Event()
|
|
@@ -510,7 +524,10 @@ def main():
|
|
| 510 |
t.start()
|
| 511 |
|
| 512 |
# 3. Start application
|
|
|
|
| 513 |
process = sync.run_openclaw()
|
|
|
|
|
|
|
| 514 |
|
| 515 |
# Signal handler
|
| 516 |
def handle_signal(sig, frame):
|
|
|
|
| 252 |
default_src = Path(__file__).parent / "openclaw.json.default"
|
| 253 |
if default_src.exists():
|
| 254 |
shutil.copy2(str(default_src), str(config_path))
|
| 255 |
+
# Replace placeholder with actual env var value
|
| 256 |
+
text = config_path.read_text()
|
| 257 |
+
if "__OPENROUTER_API_KEY__" in text:
|
| 258 |
+
if OPENROUTER_API_KEY:
|
| 259 |
+
text = text.replace("__OPENROUTER_API_KEY__", OPENROUTER_API_KEY)
|
| 260 |
+
else:
|
| 261 |
+
text = text.replace("__OPENROUTER_API_KEY__", "")
|
| 262 |
+
config_path.write_text(text)
|
| 263 |
print("[SYNC] Created openclaw.json from default template")
|
| 264 |
else:
|
| 265 |
with open(config_path, "w") as f:
|
|
|
|
| 507 |
|
| 508 |
def main():
|
| 509 |
try:
|
| 510 |
+
t_main_start = time.time()
|
| 511 |
+
|
| 512 |
+
t0 = time.time()
|
| 513 |
sync = OpenClawFullSync()
|
| 514 |
+
print(f"[TIMER] sync_hf init: {time.time() - t0:.1f}s")
|
| 515 |
|
| 516 |
# 1. Restore
|
| 517 |
+
t0 = time.time()
|
| 518 |
sync.load_from_repo()
|
| 519 |
+
print(f"[TIMER] load_from_repo (restore): {time.time() - t0:.1f}s")
|
| 520 |
|
| 521 |
# 2. Background sync
|
| 522 |
stop_event = threading.Event()
|
|
|
|
| 524 |
t.start()
|
| 525 |
|
| 526 |
# 3. Start application
|
| 527 |
+
t0 = time.time()
|
| 528 |
process = sync.run_openclaw()
|
| 529 |
+
print(f"[TIMER] run_openclaw launch: {time.time() - t0:.1f}s")
|
| 530 |
+
print(f"[TIMER] Total startup (init โ app launched): {time.time() - t_main_start:.1f}s")
|
| 531 |
|
| 532 |
# Signal handler
|
| 533 |
def handle_signal(sig, frame):
|