#!/bin/bash set -e echo "🤖 MoltBot AI - Starting..." # ============================================ # 从环境变量生成 OpenClaw 配置 # ============================================ FEISHU_APP_ID="${FEISHU_APP_ID:-}" FEISHU_APP_SECRET="${FEISHU_APP_SECRET:-}" API_BASE_URL="${API_BASE_URL:-https://asem12345-cliproxyapi.hf.space/v1}" API_KEY="${API_KEY:-}" MODEL_NAME="${MODEL_NAME:-gemini-3-flash}" BRAVE_API_KEY="${BRAVE_API_KEY:-}" if [ -z "$FEISHU_APP_ID" ] || [ -z "$FEISHU_APP_SECRET" ]; then echo "❌ 错误: 请设置 FEISHU_APP_ID 和 FEISHU_APP_SECRET 环境变量" echo " 在 HF Space Settings → Secrets 中添加" exit 1 fi echo "📝 生成 OpenClaw 配置..." # 生成 provider ID:从 URL 提取域名部分,加 custom- 前缀 # 例如 https://asem12345-cliproxyapi.hf.space/v1 → custom-asem12345-cliproxyapi-hf-space PROVIDER_ID="custom-$(echo "$API_BASE_URL" | sed 's|https\?://||' | sed 's|/.*||' | sed 's|[^a-zA-Z0-9]|-|g' | sed 's|-*$||')" OPENCLAW_DIR="$HOME/.openclaw" # 创建必要目录 mkdir -p "$OPENCLAW_DIR/agents/main/sessions" mkdir -p "$OPENCLAW_DIR/workspace" chmod 700 "$OPENCLAW_DIR" 2>/dev/null || true # 先写一个最小配置让 doctor 能跑 cat > "$OPENCLAW_DIR/openclaw.json" << JSONEOF { "gateway": { "port": 18789, "bind": "loopback", "mode": "local" }, "channels": { "feishu": { "enabled": false } } } JSONEOF echo "✅ 最小配置已生成" echo " 飞书 App ID: ${FEISHU_APP_ID}" # ============================================ # 运行 doctor --fix(自动安装飞书插件等) # ============================================ echo "🔧 运行 doctor --fix..." openclaw doctor --fix || true # 强力删除飞书插件,防止自动启用 rm -rf /root/.openclaw/extensions/feishu-openclaw # ============================================ # doctor 完成后,写入完整配置(包含自定义模型) # doctor 有时会覆盖我们的配置,所以放在 doctor 之后 # ============================================ echo "📝 写入完整配置..." # 用 python 合并配置(保留 doctor 添加的字段如 meta, wizard, plugins 等) python3 << PYEOF import json, os config_path = os.path.expanduser("~/.openclaw/openclaw.json") # 读取 doctor 生成的配置 try: with open(config_path) as f: config = json.load(f) except: config = {} # 设置 gateway config.setdefault("gateway", {}) config["gateway"]["port"] = 18789 config["gateway"]["bind"] = "loopback" config["gateway"]["mode"] = "local" # 设置自定义 provider config.setdefault("models", {}) config["models"]["mode"] = "merge" config["models"].setdefault("providers", {}) config["models"]["providers"]["${PROVIDER_ID}"] = { "baseUrl": "${API_BASE_URL}", "apiKey": "${API_KEY}", "api": "openai-completions", "models": [ { "id": "${MODEL_NAME}", "name": "${MODEL_NAME} (Custom Provider)", "reasoning": False, "input": ["text"], "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}, "contextWindow": 131072, "maxTokens": 8192 } ] } # 设置 agent defaults config.setdefault("agents", {}).setdefault("defaults", {}) config["agents"]["defaults"]["model"] = { "primary": "${PROVIDER_ID}/${MODEL_NAME}" } # 配置 imageModel(Gemini 原生支持多模态,复用主模型) config["agents"]["defaults"]["imageModel"] = { "primary": "${PROVIDER_ID}/${MODEL_NAME}" } config["agents"]["defaults"].setdefault("models", {}) config["agents"]["defaults"]["models"]["${PROVIDER_ID}/${MODEL_NAME}"] = {} config["agents"]["defaults"].setdefault("workspace", os.path.expanduser("~/.openclaw/workspace")) config["agents"]["defaults"].setdefault("compaction", {"mode": "safeguard"}) config["agents"]["defaults"].setdefault("maxConcurrent", 4) # 配置 memory(记忆功能) config["agents"]["defaults"]["memorySearch"] = { "enabled": True, "provider": "local" } print(f"✅ 图片分析(imageModel)已启用") print(f"✅ 记忆功能(memory)已启用") # 删除飞书 channel 配置 (防止 OpenClaw 自动启用) config.setdefault("channels", {}) if "feishu" in config["channels"]: del config["channels"]["feishu"] # 配置 Brave Search(上网搜索) brave_key = os.environ.get("BRAVE_API_KEY", "${BRAVE_API_KEY}") if brave_key: config.setdefault("tools", {}).setdefault("web", {}) config["tools"]["web"]["search"] = { "enabled": True, "provider": "brave", "maxResults": 10 } print(f"✅ Brave Search 已启用") with open(config_path, "w") as f: json.dump(config, f, indent=2) print(f"✅ 完整配置已写入 {config_path}") print(f" 模型: ${PROVIDER_ID}/${MODEL_NAME}") # 同时写入 agent 级别的 models.json(防止 fallback 到 anthropic) agent_dir = os.path.expanduser("~/.openclaw/agents/main/agent") os.makedirs(agent_dir, exist_ok=True) agent_models = { "providers": { "github-copilot": { "baseUrl": "https://api.individual.githubcopilot.com", "models": [] }, "${PROVIDER_ID}": { "baseUrl": "${API_BASE_URL}", "apiKey": "${API_KEY}", "api": "openai-completions", "models": [ { "id": "${MODEL_NAME}", "name": "${MODEL_NAME} (Custom Provider)", "reasoning": False, "input": ["text"], "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}, "contextWindow": 131072, "maxTokens": 8192 } ] } } } models_path = os.path.join(agent_dir, "models.json") with open(models_path, "w") as f: json.dump(agent_models, f, indent=2) # 确保 auth-profiles.json 存在(OpenClaw 查找的是这个文件) auth_path = os.path.join(agent_dir, "auth-profiles.json") with open(auth_path, "w") as f: json.dump({}, f) # 也写一份 auth.json 以防万一 auth2_path = os.path.join(agent_dir, "auth.json") if not os.path.exists(auth2_path): with open(auth2_path, "w") as f: json.dump({}, f) print(f"✅ Agent 配置已写入 {agent_dir}") # 递归扫描所有 json 文件,替换 anthropic 引用 import glob replaced = [] provider_id = "${PROVIDER_ID}" model_id = "${MODEL_NAME}" full_model = f"{provider_id}/{model_id}" for fpath in glob.glob(os.path.expanduser("~/.openclaw/**/*.json"), recursive=True): try: with open(fpath) as f: content = f.read() if "anthropic" in content: original = content # 替换 model references content = content.replace('"anthropic/claude-sonnet-4-20250514"', f'"{full_model}"') content = content.replace('"anthropic/claude-3-5-sonnet"', f'"{full_model}"') content = content.replace('"anthropic/claude-3-5-haiku"', f'"{full_model}"') content = content.replace('"anthropic/claude-3-haiku"', f'"{full_model}"') # 通用 anthropic provider 引用 content = content.replace('"anthropic"', f'"{provider_id}"') if content != original: with open(fpath, "w") as f: f.write(content) replaced.append(fpath) except: pass if replaced: print(f"⚠️ 替换了 {len(replaced)} 个文件中的 anthropic 引用:") for r in replaced: print(f" - {r}") else: print("✅ 未发现 anthropic 引用") # 打印调试信息 print("\n🔍 调试 - openclaw.json:") try: with open(config_path) as f: print(f.read()[:2000]) except: print(" 无法读取") print("\n🔍 调试 - agent 目录内容:") for fpath in glob.glob(os.path.join(agent_dir, "*")): print(f" {fpath}") try: with open(fpath) as f: c = f.read()[:500] print(f" {c}") except: pass PYEOF # 注意: 不再使用 image_proxy 和 sed patch(会破坏 WebSocket 连接) # 图片处理完全由 image_daemon.py 负责 # ============================================ # 启动 OpenClaw Gateway(后台) # ============================================ echo "🚀 启动 OpenClaw Gateway..." openclaw gateway --force & GATEWAY_PID=$! echo " Gateway PID: $GATEWAY_PID" # 等待网关启动 sleep 5 # ============================================ # 启动图片预处理守护进程(后台,带自动重启守护) # ============================================ echo "🖼️ 启动图片预处理守护进程(带自动重启)..." ( RESTART_COUNT=0 while true; do RESTART_COUNT=$((RESTART_COUNT + 1)) echo "[image_daemon_guard] 🚀 启动 image_daemon (第 ${RESTART_COUNT} 次)" python3 /app/image_daemon.py EXIT_CODE=$? echo "[image_daemon_guard] ⚠️ image_daemon 退出 (code=${EXIT_CODE}), 3 秒后重启..." sleep 3 done ) & IMAGE_DAEMON_PID=$! echo " Image Daemon Guard PID: $IMAGE_DAEMON_PID" # ============================================ # 启动状态监控网页(前台,端口 7860) # ============================================ echo "📊 启动状态监控网页 (端口 7860)..." exec python3 /app/status_page.py