Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| G3: 配置生成器 - OpenClaw 配置管理 | |
| 基于 OpenClaw 正确的配置格式生成 openclaw.json | |
| """ | |
| import os | |
| import json | |
| import secrets | |
| def deep_merge(base, override): | |
| """Deep merge two dicts, override wins""" | |
| result = base.copy() | |
| for key, val in override.items(): | |
| if key in result and isinstance(result[key], dict) and isinstance(val, dict): | |
| result[key] = deep_merge(result[key], val) | |
| else: | |
| result[key] = val | |
| return result | |
| def load_backup_config(state_dir): | |
| """G3.2: 尝试加载备份的 openclaw.json 用于合并""" | |
| backup_path = os.path.join(state_dir, "config_backup", "openclaw.json") | |
| if os.path.exists(backup_path): | |
| try: | |
| with open(backup_path, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except Exception as e: | |
| print(f"[Config] Failed to load backup config: {e}") | |
| return None | |
| def generate_agents(state_dir): | |
| """Generate agents list and providers config""" | |
| agents_list = [] | |
| providers = {} | |
| for i in range(1, 7): | |
| name = os.environ.get(f"AGENT{i}_NAME", "") | |
| if not name: | |
| continue | |
| model_id = os.environ.get(f"MODEL{i}_ID", "") | |
| model_name = os.environ.get(f"MODEL{i}_NAME", "") | |
| base_url = os.environ.get(f"MODEL{i}_BASEURL", "") | |
| api_key = os.environ.get(f"MODEL{i}_APIKEY", "") | |
| agent = { | |
| "id": f"agent_{i}", | |
| "name": name, | |
| "workspace": os.path.join(state_dir, "workspace", f"agent_{i}") | |
| } | |
| # Model config goes into models.providers, agent.model is just a string ref | |
| if base_url or api_key or model_id: | |
| provider_name = f"agent{i}" | |
| mid = model_id or model_name or "default" | |
| agent["model"] = f"{provider_name}/{mid}" | |
| # Build provider config for this agent | |
| providers[provider_name] = { | |
| "baseUrl": base_url or "", | |
| "apiKey": api_key or "", | |
| "models": [{ | |
| "id": mid, | |
| "name": model_name or model_id or "Default model", | |
| "reasoning": False, | |
| "input": ["text"], | |
| "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}, | |
| "contextWindow": 128000, | |
| "maxTokens": 4096 | |
| }] | |
| } | |
| agents_list.append(agent) | |
| return agents_list, providers | |
| def main(): | |
| state_dir = os.environ.get("OPENCLAW_STATE_DIR", "/root/.openclaw") | |
| config_path = os.path.join(state_dir, "openclaw.json") | |
| print("[Config] Generating OpenClaw configuration...") | |
| # 生成密码 | |
| pw = os.environ.get("OPENCLAW_GATEWAY_PASSWORD", "") or os.environ.get("OPENCLAW_GATEWAY_TOKEN", "") | |
| pw_path = os.path.join(state_dir, "logs", "gateway_password.txt") | |
| if not pw: | |
| pw = "openclaw-" + secrets.token_hex(8) | |
| print(f"[Config] Generated gateway password (saved to {pw_path})") | |
| # 将密码保存到单独文件(供 debug 页面读取) | |
| os.makedirs(os.path.join(state_dir, "logs"), exist_ok=True) | |
| with open(pw_path, 'w') as f: | |
| f.write(pw) | |
| print(f"[Config] Password saved to {pw_path}") | |
| # 按 OpenClaw 正确配置格式 | |
| config = { | |
| "gateway": { | |
| "port": int(os.environ.get("OPENCLAW_GATEWAY_PORT", 18889)), | |
| "mode": "local", | |
| "bind": "lan", | |
| "auth": { | |
| "mode": "password", | |
| "password": pw | |
| } | |
| }, | |
| "agents": { | |
| "defaults": { | |
| "model": {}, | |
| "workspace": os.path.join(state_dir, "workspace"), | |
| "compaction": { | |
| "mode": "safeguard" | |
| } | |
| } | |
| }, | |
| "tools": { | |
| "profile": "full" | |
| }, | |
| "models": { | |
| "pricing": { | |
| "enabled": False | |
| } | |
| }, | |
| "channels": {}, | |
| "bindings": [] | |
| } | |
| # Gateway control UI config - allow local connections and HF Spaces | |
| origins = ["http://localhost:18889", "http://127.0.0.1:18889"] | |
| space_host = os.environ.get("SPACE_HOST", "") | |
| if space_host: | |
| origins.append(f"https://{space_host}") | |
| origins.append(f"https://{space_host}:18888") | |
| config["gateway"]["controlUi"] = { | |
| "enabled": True, | |
| "allowedOrigins": origins, | |
| "allowInsecureAuth": True, | |
| "dangerouslyDisableDeviceAuth": True, | |
| "dangerouslyAllowHostHeaderOriginFallback": False, | |
| } | |
| # G3.4: 添加 Agent 列表 | |
| agents_list, providers = generate_agents(state_dir) | |
| if agents_list: | |
| config["agents"]["list"] = agents_list | |
| # Only allow specific configured model keys (no wildcards) | |
| default_models = {} | |
| for agent in agents_list: | |
| if "model" in agent: | |
| default_models[agent["model"]] = {} | |
| config["agents"]["defaults"]["models"] = default_models if default_models else {} | |
| # Add model providers config (baseUrl, apiKey) | |
| if providers: | |
| config["models"]["providers"] = providers | |
| print(f"[Config] Generated {len(agents_list)} agents") | |
| # G3.5: 飞书通道(使用第一个配置的飞书机器人) | |
| feishu_app_id = os.environ.get("FEISHU1_APPID", "") | |
| feishu_app_secret = os.environ.get("FEISHU1_SECRET", "") | |
| if feishu_app_id and feishu_app_secret: | |
| config["channels"]["feishu"] = { | |
| "enabled": True, | |
| "appId": feishu_app_id, | |
| "appSecret": feishu_app_secret, | |
| "connectionMode": "websocket", | |
| "dmPolicy": "open", | |
| "groupPolicy": "open", | |
| "streaming": True | |
| } | |
| config["bindings"].append({ | |
| "agentId": "agent_1", | |
| "match": { | |
| "channel": "feishu", | |
| "accountId": "default" | |
| } | |
| }) | |
| print(f"[Config] Added Feishu channel with bot 1") | |
| # G3.5: 为其他飞书机器人添加绑定 | |
| for i in range(2, 7): | |
| aid = os.environ.get(f"FEISHU{i}_APPID", "") | |
| sec = os.environ.get(f"FEISHU{i}_SECRET", "") | |
| if aid and sec: | |
| config["bindings"].append({ | |
| "agentId": f"agent_{i}", | |
| "match": { | |
| "channel": "feishu", | |
| "accountId": f"bot_{i}" | |
| } | |
| }) | |
| print(f"[Config] Added Feishu binding for agent_{i} (bot_{i})") | |
| # G3.5: 微信通道 | |
| if os.environ.get("WEIXIN_ENABLED", "true").lower() == "true": | |
| wechat_token = os.environ.get("WECHAT_TOKEN", "") | |
| if wechat_token: | |
| config["channels"]["wechat"] = { | |
| "enabled": True, | |
| "token": wechat_token, | |
| "dmPolicy": "open", | |
| "groupPolicy": "open", | |
| "streaming": True | |
| } | |
| config["bindings"].append({ | |
| "agentId": "agent_1", | |
| "match": { | |
| "channel": "wechat", | |
| "accountId": "default" | |
| } | |
| }) | |
| print(f"[Config] Added WeChat channel") | |
| # G3.2: 跳过与备份配置的深度合并(避免旧版配置的无效字段导致 OpenClaw 启动失败) | |
| # 如果后续需要保留用户配置,应通过 env vars 设置 | |
| # G3.6: Skills - 不在根级配置skills对象(OpenClaw v2026.5+ 不支持) | |
| # G3.6: CCSwitch 配置 | |
| if os.environ.get("CCSWITCH_ENABLED", "true").lower() == "true": | |
| print(f"[Config] CCSwitch integration enabled") | |
| # 保存 | |
| os.makedirs(state_dir, exist_ok=True) | |
| with open(config_path, 'w', encoding='utf-8') as f: | |
| json.dump(config, f, indent=4, ensure_ascii=False) | |
| print(f"[Config] Configuration saved to {config_path}") | |
| print(f"[Config] Agents: {len(agents_list)}, Channels: {len(config['channels'])}, Bindings: {len(config['bindings'])}") | |
| if __name__ == "__main__": | |
| main() | |