| |
| """ |
| config-generator.py — 环境变量驱动的 openclaw.json 生成器 |
| |
| 三层配置合并: |
| 1. 基础模板 (安全默认值) |
| 2. 从备份恢复的 openclaw.json |
| 3. 环境变量覆盖 (最高优先级) |
| |
| 端口: 18889 (内部端口,由 Caddy 反向代理) |
| """ |
|
|
| import json, os, copy |
|
|
| STATE_DIR = os.environ.get("OPENCLAW_STATE_DIR", "/root/.openclaw") |
| GATEWAY_PORT = int(os.environ.get("OPENCLAW_GATEWAY_PORT", 18889)) |
|
|
| def deep_merge(base: dict, overlay: dict) -> dict: |
| result = copy.deepcopy(base) |
| for k, v in overlay.items(): |
| if k in result and isinstance(result[k], dict) and isinstance(v, dict): |
| result[k] = deep_merge(result[k], v) |
| else: |
| result[k] = copy.deepcopy(v) |
| return result |
|
|
| def _get_gw_password() -> str: |
| pw = os.environ.get("OPENCLAW_GATEWAY_PASSWORD", "") or os.environ.get("OPENCLAW_GATEWAY_TOKEN", "") |
| if not pw: |
| import secrets |
| pw = "openclaw-" + secrets.token_hex(8) |
| print(f"[config] Generated gateway password: {pw}") |
| return pw |
|
|
| def generate() -> dict: |
| |
| config = { |
| "models": {"providers": {}}, |
| "agents": {"defaults": {}, "list": []}, |
| "skills": {"entries": {}, "allowBundled": []}, |
| "plugins": {"entries": {}, "allow": []}, |
| "gateway": { |
| "mode": "local", |
| "bind": "lan", |
| "port": GATEWAY_PORT, |
| "trustedProxies": ["0.0.0.0/0"], |
| "auth": { |
| "mode": "token", |
| "token": _get_gw_password(), |
| }, |
| "controlUi": { |
| "enabled": True, |
| "allowInsecureAuth": True, |
| "dangerouslyDisableDeviceAuth": True, |
| "dangerouslyAllowHostHeaderOriginFallback": True, |
| }, |
| }, |
| "browser": { |
| "enabled": False, |
| "headless": True, |
| }, |
| } |
|
|
| |
| restored_path = os.path.join(STATE_DIR, "openclaw.json") |
| if os.path.exists(restored_path): |
| try: |
| with open(restored_path) as f: |
| restored = json.load(f) |
| config = deep_merge(config, restored) |
| except Exception as e: |
| print(f"[config] Warning: could not load restored config: {e}") |
|
|
| |
|
|
| |
| provider = os.environ.get("LLM_PROVIDER", "default") |
| base_url = os.environ.get("OPENAI_API_BASE", "").rstrip("/") |
| base_url = base_url.replace("/chat/completions", "") |
|
|
| if base_url and os.environ.get("OPENAI_API_KEY"): |
| config["models"]["providers"][provider] = { |
| "baseUrl": base_url, |
| "apiKey": os.environ.get("OPENAI_API_KEY", ""), |
| "api": "openai-completions", |
| "models": [ |
| { |
| "id": os.environ.get("MODEL", "gpt-4o"), |
| "name": os.environ.get("MODEL", "gpt-4o"), |
| "contextWindow": 128000, |
| } |
| ], |
| } |
| |
| extra = os.environ.get("EXTRA_MODELS", "") |
| if extra: |
| try: |
| config["models"]["providers"][provider]["models"].extend(json.loads(extra)) |
| except Exception: |
| pass |
| else: |
| |
| config["models"]["providers"]["placeholder"] = { |
| "baseUrl": "http://localhost:8080/v1", |
| "apiKey": "not-configured-yet", |
| "api": "openai-completions", |
| "models": [{"id": "placeholder", "name": "placeholder", "contextWindow": 4096}], |
| } |
| print("[config] Warning: OPENAI_API_BASE or OPENAI_API_KEY not set, using placeholder") |
|
|
| |
| config["plugins"]["bundledDiscovery"] = "compat" |
|
|
| |
| default_model = f"{provider}/{os.environ.get('MODEL', 'gpt-4o')}" |
| config["agents"]["defaults"] = { |
| "model": {"primary": default_model}, |
| "workspace": os.path.join(STATE_DIR, "workspace"), |
| } |
|
|
| |
| default_names = ["开发组-产品经理", "开发组-架构师", "开发组-开发", "开发组-测试", "开发组-项目经理", "总协调"] |
| for i in range(1, 7): |
| if i == 6: |
| agent_id = "coordinator" |
| name = os.environ.get("COORDINATOR_NAME", default_names[5]) |
| workspace = os.path.join(STATE_DIR, "workspace-coordinator") |
| model_override = default_model |
| else: |
| agent_id = f"agent-{i}" |
| name = os.environ.get(f"AGENT_{i}_NAME", default_names[i - 1]) |
| workspace = os.path.join(STATE_DIR, f"workspace-agent-{i}") |
| model_override = os.environ.get(f"AGENT_{i}_MODEL", default_model) |
|
|
| agent_def = { |
| "id": agent_id, |
| "name": name, |
| "workspace": workspace, |
| "model": {"primary": model_override}, |
| } |
|
|
| |
| skills_str = os.environ.get(f"AGENT_{i}_SKILLS", "") |
| if skills_str: |
| agent_def["skills"] = [s.strip() for s in skills_str.split(",")] |
|
|
| config["agents"]["list"].append(agent_def) |
|
|
| |
| skills_enabled = os.environ.get("SKILLS_ENABLED", "") |
| if skills_enabled: |
| for s in skills_enabled.split(","): |
| s = s.strip() |
| if s: |
| config["skills"]["entries"][s] = {"enabled": True} |
|
|
| |
| plugins_allow = os.environ.get("PLUGINS_ALLOW", "") |
| if plugins_allow: |
| config["plugins"]["allow"] = [p.strip() for p in plugins_allow.split(",")] |
| else: |
| |
| config["plugins"]["allow"] = [ |
| "multi-search-cn", |
| "github", |
| "summarize", |
| ] |
|
|
| |
| core_skills = ["multi-search-cn", "github", "summarize"] |
| for p in core_skills: |
| if p not in config["plugins"]["allow"]: |
| config["plugins"]["allow"].append(p) |
| config["plugins"]["entries"][p] = {"enabled": True} |
|
|
| return config |
|
|
| if __name__ == "__main__": |
| config = generate() |
| os.makedirs(STATE_DIR, exist_ok=True) |
| out = os.path.join(STATE_DIR, "openclaw.json") |
| with open(out, "w") as f: |
| json.dump(config, f, indent=2, ensure_ascii=False) |
| |
| print("[config] Full configuration (JSON):\n" + json.dumps(config, indent=2, ensure_ascii=False)) |
| print(f"[config] Generated → {out}") |