xiaoxiaxia / config-generator.py
Claude
feat: 统一环境变量命名规范为 MODEL{1-6}_ / AGENT{1-6}_ / FEISHU{1-6}_
e7216a1
#!/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()