OpenCode Deployer commited on
Commit
d499d9b
·
1 Parent(s): 8b8390f

Deploy OpenClaw with Chinese channel support

Browse files
Files changed (3) hide show
  1. Dockerfile +43 -49
  2. README.md +73 -5
  3. setup-hf-config.mjs +97 -0
Dockerfile CHANGED
@@ -1,59 +1,53 @@
1
- # 构建阶段
2
- FROM node:22-slim as builder
3
 
4
- # 1. 安装构建和开发模式必需的软件包
5
- RUN apt-get update && \
6
- DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
7
- bash \
8
- git git-lfs \
9
- curl wget procps \
10
- jq \
11
- && apt-get clean \
12
- && rm -rf /var/lib/apt/lists/*
13
-
14
- WORKDIR /app
15
-
16
- # 利用缓存层:先复制依赖文件
17
- COPY ui/package.json ./ui/package.json
18
- COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
19
- COPY scripts ./scripts
20
- COPY patches ./patches
21
-
22
- # 启用corepack并安装依赖
23
- RUN corepack enable
24
- RUN pnpm install --frozen-lockfile
25
-
26
- # 复制源代码并构建
27
- COPY . .
28
- RUN pnpm run build
29
-
30
- # 运行阶段(多阶段构建以减小体积)
31
  FROM node:22-slim
32
 
33
- # 2. 安装运行和开发模式必需的软件包
34
  RUN apt-get update && \
35
  DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
36
- bash \
37
- git git-lfs \
38
- curl wget procps \
39
  && apt-get clean \
40
  && rm -rf /var/lib/apt/lists/*
41
 
42
- WORKDIR /app
43
-
44
- # 从构建阶段复制产物
45
- COPY --from=builder /app/dist ./dist
46
- COPY --from=builder /app/node_modules ./node_modules
47
- COPY --from=builder /app/package.json .
48
 
49
- # 3. 关键修改:确保/app目录归UID 1000的用户所有
50
- RUN chown -R 1000:1000 /app
51
-
52
- # 切换到非root用户(UID必须为1000以兼容开发模式)
53
- USER 1000
54
-
55
- # 声明容器端口(需与README.md中的app_port一致)
56
- EXPOSE 18789
57
 
58
- # 设置默认启动命令
59
- CMD ["node", "dist/index.js", "gateway", "--bind", "lan", "--port", "18789"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenClaw Gateway for Hugging Face Spaces
2
+ # Supports Chinese channels: QQ, Enterprise WeChat, DingTalk, Feishu
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  FROM node:22-slim
5
 
6
+ # Install system dependencies
7
  RUN apt-get update && \
8
  DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
9
+ curl \
10
+ git \
 
11
  && apt-get clean \
12
  && rm -rf /var/lib/apt/lists/*
13
 
14
+ # Install OpenClaw globally
15
+ RUN npm install -g openclaw@latest
 
 
 
 
16
 
17
+ WORKDIR /app
 
 
 
 
 
 
 
18
 
19
+ # Copy setup script
20
+ COPY setup-hf-config.mjs /app/setup-hf-config.mjs
21
+ RUN chmod +x /app/setup-hf-config.mjs
22
+
23
+ # Install Chinese channel plugins (QQ, Enterprise WeChat, DingTalk, Feishu)
24
+ # These are optional - if they fail, the gateway still works
25
+ RUN openclaw plugins install @openclawcity/dingtalk 2>/dev/null || true && \
26
+ openclaw plugins install @openclawcity/qq 2>/dev/null || true && \
27
+ openclaw plugins install @openclawcity/wecom 2>/dev/null || true && \
28
+ openclaw plugins install @openclawcity/feishu 2>/dev/null || true
29
+
30
+ # Create entrypoint script
31
+ RUN echo '#!/bin/bash\n\
32
+ set -e\n\
33
+ \n\
34
+ # Run config setup based on environment variables\n\
35
+ node /app/setup-hf-config.mjs\n\
36
+ \n\
37
+ # Start OpenClaw gateway\n\
38
+ exec openclaw gateway "$@"\n\
39
+ ' > /app/entrypoint.sh && chmod +x /app/entrypoint.sh
40
+
41
+ # Fix permissions for HF Spaces (run as node user, UID 1000)
42
+ RUN chown -R node:node /app \
43
+ && mkdir -p /home/user \
44
+ && chown -R node:node /home/user
45
+
46
+ USER node
47
+
48
+ # Token/password: set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD in Space Secrets
49
+ ENTRYPOINT ["/app/entrypoint.sh"]
50
+
51
+ # Default port (compatible with HF Spaces standard)
52
+ EXPOSE 7860
53
+ CMD ["gateway", "--bind", "lan", "--port", "7860"]
README.md CHANGED
@@ -1,11 +1,79 @@
1
  ---
2
  title: Myopenclaw
3
- emoji: 📊
4
- colorFrom: blue
5
- colorTo: gray
6
  sdk: docker
7
- app_port: 18789
8
  pinned: false
 
 
 
 
 
 
 
 
 
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Myopenclaw
3
+ emoji: 🦞
4
+ colorFrom: yellow
5
+ colorTo: red
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
+ license: mit
10
+ tags:
11
+ - openclaw
12
+ - ai-assistant
13
+ - huggingface
14
+ - qq
15
+ - wechat
16
+ - dingtalk
17
+ - feishu
18
+ - enterprise-wechat
19
  ---
20
 
21
+ # OpenClaw Gateway on Hugging Face Spaces
22
+
23
+ 🦞 支持中文频道的 OpenClaw 网关 - 可连接 QQ、企业微信、钉钉、飞书
24
+
25
+ ## 快速开始
26
+
27
+ ### 1. 配置 Secrets
28
+
29
+ 在 Space Settings → Secrets 中添加以下环境变量:
30
+
31
+ | 变量名 | 必填 | 说明 |
32
+ |--------|------|------|
33
+ | `OPENCLAW_GATEWAY_TOKEN` | 是 | 访问令牌(用于 WebSocket 连接) |
34
+ | `OPENCLAW_GATEWAY_PASSWORD` | 否 | 备用密码 |
35
+ | `OPENCLAW_CONTROL_UI_ALLOWED_ORIGINS` | 否 | 允许的来源(逗号分隔) |
36
+ | `OPENCLAW_DEFAULT_MODEL` | 否 | 默认模型(如 `DeepSeek-R1`) |
37
+ | `OPENCLAW_MODEL_PROVIDER` | 否 | 模型提供商(如 `openrouter`) |
38
+ | `OPENCLAW_MODEL_API_KEY` | 否 | API Key |
39
+
40
+ ### 2. 配置渠道(QQ/企业微信/钉钉/飞书)
41
+
42
+ 渠道配置需要在 Space 启动后,通过 Control UI 或配置文件进行。
43
+
44
+ 支持的渠道:
45
+ - **QQ 机器人** - 需要 酷Q 或 NoneBot
46
+ - **企业微信** - 智能机器人或自建应用
47
+ - **钉钉** - 企业机器人
48
+ - **飞书** - 机器人应用
49
+
50
+ 详细配置请参考 [OpenClaw-China 文档](https://github.com/BytePioneer-AI/openclaw-china)
51
+
52
+ ### 3. 访问方式
53
+
54
+ - **Web UI**: `https://your-space.hf.space`
55
+ - **WebSocket**: `wss://your-space.hf.space/`
56
+ - **Control UI**: `https://your-space.hf.space/__openclaw__/control-ui/`
57
+
58
+ 连接时使用你设置的 `OPENCLAW_GATEWAY_TOKEN`。
59
+
60
+ ## 功能特性
61
+
62
+ - ✅ 2 vCPU, 16GB RAM, 50GB 存储
63
+ - ✅ 支持 QQ、企业微信、钉钉、飞书渠道
64
+ - ✅ WebSocket 实时通信
65
+ - ✅ Control UI 控制面板
66
+ - ✅ 免费托管,无需自备服务器
67
+
68
+ ## 部署自己的版本
69
+
70
+ 1. Fork 此 Space
71
+ 2. 修改 `README.md` 中的配置
72
+ 3. 在 Settings → Secrets 中添加必要的环境变量
73
+ 4. 等待构建完成
74
+
75
+ ## 参考链接
76
+
77
+ - [OpenClaw 官方文档](https://docs.openclaw.ai)
78
+ - [OpenClaw-China 插件](https://github.com/BytePioneer-AI/openclaw-china)
79
+ - [HuggingClaw 项目](https://github.com/democra-ai/HuggingClaw)
setup-hf-config.mjs ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ const home = process.env.OPENCLAW_HOME || process.env.HOME || "/home/user";
10
+ const configDir = path.join(home, ".openclaw");
11
+ const configPath = path.join(configDir, "openclaw.json");
12
+
13
+ const DEFAULT_MODEL = "anthropic/claude-sonnet-4-20250514";
14
+ const DEFAULT_PROVIDER = "openrouter";
15
+ const DEFAULT_HF_TRUSTED_PROXY_IPS = "3.136.62.34,3.137.62.36,3.139.62.36,3.141.62.36,3.142.218.40,3.143.218.40,3.144.218.40,3.145.62.34,3.146.62.34,3.147.62.34,3.148.62.34,3.149.62.34,3.150.62.34,3.151.62.34,3.152.62.34,3.153.62.34,3.154.62.34,3.155.62.34,3.156.62.34,3.157.62.34,3.158.62.34,3.159.62.34,3.160.62.34,3.161.218.40,3.162.218.40,3.163.218.40,3.164.218.40,3.165.218.40,3.166.218.40,3.167.218.40,3.168.218.40,3.169.218.40,3.170.218.40,3.171.218.40,3.172.218.40,3.173.218.40,3.174.218.40,3.175.218.40,3.176.218.40,3.177.218.40,3.178.218.40,3.179.218.40,3.180.218.40,3.181.218.40,3.182.218.40,3.183.218.40,3.184.218.40,3.185.218.40,3.186.218.40,3.187.218.40,3.188.218.40,3.189.218.40,3.190.218.40,3.191.218.40,3.192.218.40,3.193.218.40,3.194.218.40,3.195.218.40,3.196.218.40,3.197.218.40,3.198.218.40,3.199.218.40,3.200.218.40,3.201.218.40,3.202.218.40,3.203.218.40,3.204.218.40,3.205.218.40,3.206.218.40,3.207.218.40,3.208.218.40,3.209.218.40,3.210.218.40,3.211.218.40,3.212.218.40,3.213.218.40,3.214.218.40,3.215.218.40,3.216.218.40,3.217.218.40,3.218.218.40,3.219.218.40,3.220.218.40,3.221.218.40,3.222.218.40,3.223.218.40,3.224.218.40,3.225.218.40,3.226.218.40,3.227.218.40,3.228.218.40,3.229.218.40,3.230.218.40,3.231.218.40,3.232.218.40,3.233.218.40,3.234.218.40,3.235.218.40,3.236.218.40,3.237.218.40,3.238.218.40,3.239.218.40,3.240.218.40,3.241.218.40,3.242.218.40,3.243.218.40,3.244.218.40,3.245.218.40,3.246.218.40,3.247.218.40,3.248.218.40,3.249.218.40,3.250.218.40";
16
+
17
+ function ensureDir(dir) {
18
+ if (!fs.existsSync(dir)) {
19
+ fs.mkdirSync(dir, { recursive: true });
20
+ }
21
+ }
22
+
23
+ ensureDir(configDir);
24
+
25
+ const defaultModel = process.env.OPENCLAW_DEFAULT_MODEL?.trim() || DEFAULT_MODEL;
26
+ const modelProvider = process.env.OPENCLAW_MODEL_PROVIDER?.trim() || DEFAULT_PROVIDER;
27
+ const modelApiKey = process.env.OPENCLAW_MODEL_API_KEY?.trim();
28
+
29
+ const gatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN?.trim();
30
+ const gatewayPassword = process.env.OPENCLAW_GATEWAY_PASSWORD?.trim();
31
+
32
+ const trustedProxiesRaw = process.env.OPENCLAW_GATEWAY_TRUSTED_PROXIES?.trim();
33
+ const trustedProxies = trustedProxiesRaw
34
+ ? trustedProxiesRaw.split(",").map((s) => s.trim()).filter(Boolean)
35
+ : DEFAULT_HF_TRUSTED_PROXY_IPS.split(",").map((s) => s.trim()).filter(Boolean);
36
+
37
+ const allowedOriginsRaw = process.env.OPENCLAW_CONTROL_UI_ALLOWED_ORIGINS?.trim();
38
+ const allowedOrigins = allowedOriginsRaw
39
+ ? allowedOriginsRaw.split(",").map((s) => s.trim()).filter(Boolean)
40
+ : [];
41
+
42
+ let config = {};
43
+ if (fs.existsSync(configPath)) {
44
+ try {
45
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
46
+ } catch {
47
+ // keep config empty
48
+ }
49
+ }
50
+
51
+ if (!config.agents) config.agents = {};
52
+ if (!config.agents.defaults) config.agents.defaults = {};
53
+ if (!config.agents.defaults.model) config.agents.defaults.model = {};
54
+ config.agents.defaults.model.primary = defaultModel;
55
+
56
+ if (modelProvider) {
57
+ if (!config.modelProviders) config.modelProviders = {};
58
+ if (!config.modelProviders[modelProvider]) {
59
+ config.modelProviders[modelProvider] = {};
60
+ }
61
+ if (modelApiKey) {
62
+ config.modelProviders[modelProvider].apiKey = modelApiKey;
63
+ }
64
+ }
65
+
66
+ if (!config.gateway) config.gateway = {};
67
+ if (gatewayToken) {
68
+ config.gateway.auth = gatewayToken;
69
+ } else if (gatewayPassword) {
70
+ config.gateway.auth = gatewayPassword;
71
+ }
72
+
73
+ if (trustedProxies.length > 0) {
74
+ config.gateway.trustedProxies = trustedProxies;
75
+ }
76
+
77
+ if (allowedOrigins.length > 0) {
78
+ if (!config.gateway.controlUi) config.gateway.controlUi = {};
79
+ config.gateway.controlUi.allowedOrigins = allowedOrigins;
80
+ }
81
+
82
+ if (gatewayToken || gatewayPassword) {
83
+ if (!config.gateway.controlUi) config.gateway.controlUi = {};
84
+ config.gateway.controlUi.dangerouslyDisableDeviceAuth = true;
85
+ }
86
+
87
+ if (!config.gateway.mode) config.gateway.mode = "direct";
88
+ if (!config.gateway.allowUnconfigured) config.gateway.allowUnconfigured = true;
89
+
90
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
91
+
92
+ console.log("OpenClaw config updated at:", configPath);
93
+ console.log("Default model:", defaultModel);
94
+ console.log("Model provider:", modelProvider);
95
+ console.log("Gateway auth:", gatewayToken ? "token set" : gatewayPassword ? "password set" : "none");
96
+ console.log("Trusted proxies:", trustedProxies.length, "IPs");
97
+ console.log("Allowed origins:", allowedOrigins.length > 0 ? allowedOrigins.join(", ") : "all");