xiaoxiaxia / config-generator.py
sharween's picture
Upload config-generator.py with huggingface_hub
b34e8a9 verified
#!/usr/bin/env python3
"""
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:
# ── Layer 1: 基础模板 ──
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,
},
}
# ── Layer 2: 合并恢复的配置 ──
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}")
# ── Layer 3: 环境变量覆盖 ──
# 3a. 模型提供商 (只在有 API base + key 时生成)
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,
}
],
}
# 额外模型 (JSON array)
extra = os.environ.get("EXTRA_MODELS", "")
if extra:
try:
config["models"]["providers"][provider]["models"].extend(json.loads(extra))
except Exception:
pass
else:
# 无 API 配置时占位,避免配置验证失败
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")
# 3b. 兼容旧版 plugins.allow → plugins.bundledDiscovery
config["plugins"]["bundledDiscovery"] = "compat"
# 3b. Agent 默认配置
default_model = f"{provider}/{os.environ.get('MODEL', 'gpt-4o')}"
config["agents"]["defaults"] = {
"model": {"primary": default_model},
"workspace": os.path.join(STATE_DIR, "workspace"),
}
# 3c. Agents: 5 role-based + 1 coordinator
default_names = ["开发组-产品经理", "开发组-架构师", "开发组-开发", "开发组-测试", "开发组-项目经理", "总协调"]
for i in range(1, 7): # 1-5 role agents, 6 coordinator
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
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)
# 3d. Skills
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}
# 3e. Plugins
plugins_allow = os.environ.get("PLUGINS_ALLOW", "")
if plugins_allow:
config["plugins"]["allow"] = [p.strip() for p in plugins_allow.split(",")]
else:
# Default plugin set (only bundled skills, not external plugins)
config["plugins"]["allow"] = [
"multi-search-cn",
"github",
"summarize",
]
# Enable bundled skills (these are in plugins.entries as skills)
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)
# Debug: output full config to logs
print("[config] Full configuration (JSON):\n" + json.dumps(config, indent=2, ensure_ascii=False))
print(f"[config] Generated → {out}")