#!/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()