#!/bin/bash set -e # 强制使用北京时间(解决 HF Spaces 默认 UTC 导致汇报时间错误) # 使用 OpenClaw 3.13 新增的 OPENCLAW_TZ 环境变量 export TZ="Asia/Shanghai" export OPENCLAW_TZ="Asia/Shanghai" CONFIG="/root/.openclaw/openclaw.json" IP_RECORD="/root/.openclaw/.last_outbound_ip" export OPENCLAW_STATE_DIR="/root/.openclaw" export OPENCLAW_CONFIG_PATH="$CONFIG" # ============================================================ # 从 GitHub 仓库根目录复制部署文件 # 默认仓库:Viciy2023/huggingface-new-worker # 默认路径:仓库根目录 # 需要通过 GitHub Token 认证 # ============================================================ GITHUB_OWNER="${GITHUB_OWNER:-Viciy2023}" GITHUB_REPO="${GITHUB_REPO:-huggingface-new-worker}" GITHUB_BRANCH="${GITHUB_BRANCH:-main}" GITHUB_TOKEN="${GITHUB_TOKEN:-}" GITHUB_CLONE_ROOT="/tmp/github-deploy-source" GITHUB_CLONE_DIR="${GITHUB_CLONE_ROOT}/repo" prepare_github_source() { if [ -z "$GITHUB_TOKEN" ]; then echo "❌ GITHUB_TOKEN is not set" return 1 fi rm -rf "$GITHUB_CLONE_ROOT" mkdir -p "$GITHUB_CLONE_ROOT" local repo_url="https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_OWNER}/${GITHUB_REPO}.git" echo "=== Cloning deploy files from GitHub repository ===" echo "Repository: ${GITHUB_OWNER}/${GITHUB_REPO}" echo "Branch: ${GITHUB_BRANCH}" echo "Source path: repository root" if ! git clone --depth 1 --branch "$GITHUB_BRANCH" "$repo_url" "$GITHUB_CLONE_DIR"; then echo "❌ Failed to clone GitHub repository" return 1 fi return 0 } copy_repo_file() { local src="$1" local dest="$2" local source_path="${GITHUB_CLONE_DIR}/${src}" mkdir -p "$(dirname "$dest")" echo " Copying: ${src}" echo " From: ${source_path}" echo " To: ${dest}" if [ -f "$source_path" ]; then cp "$source_path" "$dest" echo "✅ Copied: $src" return 0 fi echo "❌ Failed: $src (file not found in GitHub repository root)" return 1 } if [ -n "$GITHUB_OWNER" ] && [ -n "$GITHUB_REPO" ]; then FAIL_COUNT=0 echo "=== Using HF Secrets for environment variables ===" echo "⚠️ .env download is disabled; runtime secrets must come from HF Space Secrets" if prepare_github_source; then # 配置文件 copy_repo_file "openclaw.json" "/root/.openclaw/openclaw.json" || FAIL_COUNT=$((FAIL_COUNT + 1)) # workspace 文件(角色设定、任务文档等) # 注意:OpenClaw 3.13 只加载一个内存引导文件,MEMORY.md 优先 copy_repo_file "SOUL.md" "/root/.openclaw/workspace/SOUL.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "IDENTITY.md" "/root/.openclaw/workspace/IDENTITY.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "USER.md" "/root/.openclaw/workspace/USER.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "AGENTS.md" "/root/.openclaw/workspace/AGENTS.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "TOOLS.md" "/root/.openclaw/workspace/TOOLS.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "BOOTSTRAP.md" "/root/.openclaw/workspace/BOOTSTRAP.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "MEMORY.md" "/root/.openclaw/workspace/MEMORY.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "HEARTBEAT.md" "/root/.openclaw/workspace/HEARTBEAT.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "WORKFLOW_AUTO.md" "/root/.openclaw/workspace/WORKFLOW_AUTO.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "task-dispatch.md" "/root/.openclaw/workspace/task-dispatch.md" || FAIL_COUNT=$((FAIL_COUNT + 1)) # cron 任务配置 copy_repo_file "cron/jobs.json" "/root/.openclaw/cron/jobs.json" || FAIL_COUNT=$((FAIL_COUNT + 1)) # ClawEdit 项目文件 - 文章配套媒体生成系统 (原生插件模式) # 源路径:GitHub 子目录中的 clawedit 目录 # 目标路径:~/.openclaw/extensions/clawedit CLAWEDIT_DEST="/root/.openclaw/extensions/clawedit" copy_repo_file "clawedit/index.ts" "$CLAWEDIT_DEST/index.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/package.json" "$CLAWEDIT_DEST/package.json" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/openclaw.plugin.json" "$CLAWEDIT_DEST/openclaw.plugin.json" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/core/types.ts" "$CLAWEDIT_DEST/core/types.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/core/config.ts" "$CLAWEDIT_DEST/core/config.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/core/errors.ts" "$CLAWEDIT_DEST/core/errors.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/core/process.ts" "$CLAWEDIT_DEST/core/process.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/generator/image-pipeline.ts" "$CLAWEDIT_DEST/generator/image-pipeline.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/generator/video-pipeline.ts" "$CLAWEDIT_DEST/generator/video-pipeline.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/generator/batch-generator.ts" "$CLAWEDIT_DEST/generator/batch-generator.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/planner/article-planner.ts" "$CLAWEDIT_DEST/planner/article-planner.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) copy_repo_file "clawedit/editor/markdown-enricher.ts" "$CLAWEDIT_DEST/editor/markdown-enricher.ts" || FAIL_COUNT=$((FAIL_COUNT + 1)) echo "=== Deploy files copy complete (failures: $FAIL_COUNT) ===" if [ "$FAIL_COUNT" -gt 0 ]; then echo "⚠️ WARNING: $FAIL_COUNT file(s) failed to copy, service may not work correctly" fi else echo "❌ GitHub deploy source preparation failed" fi else echo "⚠️ GitHub deploy source is not configured, skipping deploy file copy" fi # ============================================================ # 调试:检查环境变量是否正确加载 # ============================================================ echo "=== Debug: Checking environment variables ===" echo "GATEWAY_TOKEN length: ${#GATEWAY_TOKEN}" echo "GATEWAY_TOKEN first 20 chars: ${GATEWAY_TOKEN:0:20}..." echo "CLIPROXY_BASE_URL: ${CLIPROXY_BASE_URL}" echo "WECOM_TOKEN length: ${#WECOM_TOKEN}" echo "" echo "=== Debug: Checking config file before replacement ===" grep -n "GATEWAY_TOKEN" "$CONFIG" | head -5 echo "" # ============================================================ # 替换 openclaw.json 中的占位符 # 环境变量全部由 HF Secrets 注入 # ============================================================ echo "=== Replacing placeholders in openclaw.json ===" CONTROL_UI_ORIGIN="${PUBLIC_BASE_URL%/}" if [ -n "$CONTROL_UI_ORIGIN" ]; then CONTROL_UI_ORIGIN="${CONTROL_UI_ORIGIN%%/*}" if [[ "$PUBLIC_BASE_URL" =~ ^https?:// ]]; then CONTROL_UI_ORIGIN="${PUBLIC_BASE_URL%/}" fi fi sed -i "s|__PUBLIC_BASE_URL__|${CONTROL_UI_ORIGIN}|g" "$CONFIG" sed -i "s|__WECOM_APP_API_BASE_URL__|${WECOM_APP_API_BASE_URL}|g" "$CONFIG" sed -i "s|__GATEWAY_TOKEN__|${GATEWAY_TOKEN}|g" "$CONFIG" # 主模型配置(动态替换) sed -i "s|__PRIMARY_PROVIDER_NAME__|${PRIMARY_PROVIDER_NAME}|g" "$CONFIG" sed -i "s|__PRIMARY_PROVIDER_BASE_URL__|${PRIMARY_PROVIDER_BASE_URL}|g" "$CONFIG" sed -i "s|__PRIMARY_PROVIDER_API_KEY__|${PRIMARY_PROVIDER_API_KEY}|g" "$CONFIG" sed -i "s|__PRIMARY_PROVIDER_API__|${PRIMARY_PROVIDER_API}|g" "$CONFIG" sed -i "s|__PRIMARY_MODEL_ID__|${PRIMARY_MODEL_ID}|g" "$CONFIG" sed -i "s|__PRIMARY_MODEL_NAME__|${PRIMARY_MODEL_NAME}|g" "$CONFIG" # CLIPROXY 配置已删除,不再使用 # sed -i "s|__CLIPROXY_BASE_URL__|${CLIPROXY_BASE_URL}|g" "$CONFIG" # sed -i "s|__CLIPROXY_API_KEY__|${CLIPROXY_API_KEY}|g" "$CONFIG" # 旧的 GROKPROXY 配置(保留兼容性,如果新变量未设置则使用旧变量) sed -i "s|__GROKPROXY_BASE_URL__|${GROKPROXY_BASE_URL}|g" "$CONFIG" sed -i "s|__GROKPROXY_API_KEY__|${GROKPROXY_API_KEY}|g" "$CONFIG" sed -i "s|__HFGROK2API_B_KEY__|${HFGROK2API_B_KEY}|g" "$CONFIG" sed -i "s|__HFGROK2API_B_BASE_URL__|${HFGROK2API_B_BASE_URL}|g" "$CONFIG" sed -i "s|__WECOM_WS_BOT_ID__|${WECOM_WS_BOT_ID}|g" "$CONFIG" sed -i "s|__WECOM_WS_SECRET__|${WECOM_WS_SECRET}|g" "$CONFIG" sed -i "s|__WECOM_TOKEN__|${WECOM_TOKEN}|g" "$CONFIG" sed -i "s|__WECOM_AES_KEY__|${WECOM_AES_KEY}|g" "$CONFIG" sed -i "s|__WECOM_CORP_ID__|${WECOM_CORP_ID}|g" "$CONFIG" sed -i "s|__WECOM_APP_TOKEN__|${WECOM_APP_TOKEN}|g" "$CONFIG" sed -i "s|__WECOM_APP_AES_KEY__|${WECOM_APP_AES_KEY}|g" "$CONFIG" sed -i "s|__WECOM_APP_SECRET__|${WECOM_APP_SECRET}|g" "$CONFIG" sed -i "s|__WECOM_APP_ASR_APP_ID__|${WECOM_APP_ASR_APP_ID}|g" "$CONFIG" sed -i "s|__WECOM_APP_ASR_SECRET_ID__|${WECOM_APP_ASR_SECRET_ID}|g" "$CONFIG" sed -i "s|__WECOM_APP_ASR_SECRET_KEY__|${WECOM_APP_ASR_SECRET_KEY}|g" "$CONFIG" sed -i "s|__QQBOT_APP_ID__|${QQBOT_APP_ID}|g" "$CONFIG" sed -i "s|__QQBOT_CLIENT_SECRET__|${QQBOT_CLIENT_SECRET}|g" "$CONFIG" sed -i "s|__QQBOT_ASR_APP_ID__|${QQBOT_ASR_APP_ID}|g" "$CONFIG" sed -i "s|__QQBOT_ASR_SECRET_ID__|${QQBOT_ASR_SECRET_ID}|g" "$CONFIG" sed -i "s|__QQBOT_ASR_SECRET_KEY__|${QQBOT_ASR_SECRET_KEY}|g" "$CONFIG" sed -i "s|__FEISHU_APP_ID__|${FEISHU_APP_ID}|g" "$CONFIG" sed -i "s|__FEISHU_APP_SECRET__|${FEISHU_APP_SECRET}|g" "$CONFIG" sed -i "s|__FEISHU_VERIFICATION_TOKEN__|${FEISHU_VERIFICATION_TOKEN}|g" "$CONFIG" sed -i "s|__FEISHU_ENCRYPT_KEY__|${FEISHU_ENCRYPT_KEY}|g" "$CONFIG" # 以下 Grok2 API 节点配置已删除,不再使用 # sed -i "s|__VOLCENGINE_ARK_API_KEY__|${VOLCENGINE_ARK_API_KEY}|g" "$CONFIG" # sed -i "s|__VOLCENGINE_PROXY_TOKEN__|${VOLCENGINE_PROXY_TOKEN}|g" "$CONFIG" # sed -i "s|__VOLCENGINE_PROXY_URL__|${VOLCENGINE_PROXY_URL}|g" "$CONFIG" # sed -i "s|__HFOPCF_GROK2API_KEY__|${HFOPCF_GROK2API_KEY}|g" "$CONFIG" # sed -i "s|__HFOPCF_GROK2API_BASE_URL__|${HFOPCF_GROK2API_BASE_URL}|g" "$CONFIG" # sed -i "s|__HFOP_GROK2API_B_KEY__|${HFOP_GROK2API_B_KEY}|g" "$CONFIG" # sed -i "s|__HFOP_GROK2API_B_BASE_URL__|${HFOP_GROK2API_B_BASE_URL}|g" "$CONFIG" # sed -i "s|__HFOPKUAKE_GROK2API_KEY__|${HFOPKUAKE_GROK2API_KEY}|g" "$CONFIG" # sed -i "s|__HFOPKUAKE_GROK2API_BASE_URL__|${HFOPKUAKE_GROK2API_BASE_URL}|g" "$CONFIG" # sed -i "s|__HFOPSOUGOU_GROK2API_KEY__|${HFOPSOUGOU_GROK2API_KEY}|g" "$CONFIG" # sed -i "s|__HFOPSOUGOU_GROK2API_BASE_URL__|${HFOPSOUGOU_GROK2API_BASE_URL}|g" "$CONFIG" # sed -i "s|__HFOPVIDEOSOUGOU_GROK_API_KEY__|${HFOPVIDEOSOUGOU_GROK_API_KEY}|g" "$CONFIG" # sed -i "s|__HFOPVIDEOSOUGOU_GROK_BASE_URL__|${HFOPVIDEOSOUGOU_GROK_BASE_URL}|g" "$CONFIG" # sed -i "s|__HFGROK2API_KEY__|${HFGROK2API_KEY}|g" "$CONFIG" # sed -i "s|__HFGROK2API_BASE_URL__|${HFGROK2API_BASE_URL}|g" "$CONFIG" echo "✅ Placeholders replaced" echo "" echo "=== Debug: Checking config file after replacement ===" grep -n '"token"' "$CONFIG" | head -10 echo "" echo "=== Debug: Checking if any placeholders remain ===" PLACEHOLDER_COUNT=$(grep -o "__[A-Z_]*__" "$CONFIG" | wc -l) echo "Remaining placeholders: $PLACEHOLDER_COUNT" if [ "$PLACEHOLDER_COUNT" -ne 0 ]; then echo "⚠️ WARNING: Found unreplaced placeholders:" grep -o "__[A-Z_]*__" "$CONFIG" | sort -u fi echo "" # Fix config file permission (suppress security audit warning) chmod 600 "$CONFIG" # Check outbound IP and notify if changed CURRENT_IP=$(curl -s --max-time 10 https://ifconfig.me || curl -s --max-time 10 https://api.ipify.org || echo "unknown") # ============================================================ # 打印HF空间当前动态IP地址(醒目显示) # ============================================================ echo "" echo "════════════════════════════════════════════════════════════════" echo "🌐 HF Space Outbound IP: ${CURRENT_IP}" echo "════════════════════════════════════════════════════════════════" echo "" # ============================================================ # 测试微信公众号 API 连通性 # ============================================================ if [ -n "$WECHAT_APP_ID" ] && [ -n "$WECHAT_APP_SECRET" ]; then echo "=== Testing WeChat Official Account API connectivity ===" WECHAT_TOKEN_URL="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${WECHAT_APP_ID}&secret=${WECHAT_APP_SECRET}" WECHAT_RESPONSE=$(curl -s --max-time 10 "$WECHAT_TOKEN_URL" || echo '{"errcode":-1,"errmsg":"network error"}') # 检查是否成功获取 access_token if echo "$WECHAT_RESPONSE" | grep -q '"access_token"'; then echo "✅ WeChat API connection successful" echo " Access Token obtained (expires in 7200s)" else echo "❌ WeChat API connection FAILED" echo " Response: $WECHAT_RESPONSE" # 解析错误信息 ERRCODE=$(echo "$WECHAT_RESPONSE" | grep -o '"errcode":[0-9-]*' | cut -d':' -f2) ERRMSG=$(echo "$WECHAT_RESPONSE" | grep -o '"errmsg":"[^"]*"' | cut -d'"' -f4) if [ -n "$ERRCODE" ]; then echo " ❌ Error Code: $ERRCODE" echo " ❌ Error Message: $ERRMSG" # 常见错误提示 case "$ERRCODE" in 40013) echo " 💡 Hint: Invalid AppID, please check WECHAT_APP_ID" ;; 40001) echo " 💡 Hint: Invalid AppSecret, please check WECHAT_APP_SECRET" ;; 40164) echo " 💡 Hint: IP not in whitelist, please add ${CURRENT_IP} to WeChat Official Account IP whitelist" ;; -1) echo " 💡 Hint: Network error or API timeout" ;; esac fi fi echo "" else echo "⚠️ WECHAT_APP_ID or WECHAT_APP_SECRET not set, skipping WeChat API test" echo "" fi LAST_IP="" if [ -f "$IP_RECORD" ]; then LAST_IP=$(cat "$IP_RECORD") fi if [ "$CURRENT_IP" != "unknown" ] && [ "$CURRENT_IP" != "$LAST_IP" ]; then echo "$CURRENT_IP" > "$IP_RECORD" if [ -n "$WECOM_WEBHOOK_KEY" ]; then WEBHOOK_URL="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WECOM_WEBHOOK_KEY}" if [ -z "$LAST_IP" ]; then MSG="🦞 OpenClaw HF Space 首次启动\n\n出口IP: ${CURRENT_IP}\n\n请确认该IP已添加到企业微信可信IP白名单中。" else MSG="⚠️ OpenClaw HF Space 出口IP已变更\n\n旧IP: ${LAST_IP}\n新IP: ${CURRENT_IP}\n\n请立即到企业微信后台更新可信IP白名单,否则 wecom-app 将无法主动发送消息。" fi curl -s -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${MSG}\"}}" \ || echo "Failed to send webhook notification" fi fi # ============================================================ # 验证 openclaw-china channels 插件(已在 Dockerfile 中安装) # ============================================================ echo "=== Verifying openclaw-china channels plugin ===" if node /app/openclaw.mjs plugins list 2>&1 | grep -qi "channels"; then echo "✅ @openclaw-china/channels plugin is installed and loaded" else echo "⚠️ @openclaw-china/channels plugin not found, attempting runtime install..." if node /app/openclaw.mjs plugins install @openclaw-china/channels; then echo "✅ @openclaw-china/channels plugin installed successfully at runtime" else echo "❌ Failed to install @openclaw-china/channels plugin" fi fi # ============================================================ # 克隆 wechat-allauto-gzh 项目(运行时克隆,确保始终获取 fork 最新版本) # ============================================================ echo "=== Cloning wechat-allauto-gzh project from fork ===" WECHAT_SKILL_DIR="/root/.openclaw/workspace/wechat-allauto-gzh" WECHAT_ALLAUTO_GZH_REPO_URL="${WECHAT_ALLAUTO_GZH_REPO_URL:-https://github.com/Viciy2023/wechat-allauto-gzh.git}" WECHAT_ALLAUTO_GZH_BRANCH="${WECHAT_ALLAUTO_GZH_BRANCH:-main}" if [ -d "$WECHAT_SKILL_DIR/.git" ]; then echo "⚠️ wechat-allauto-gzh already exists, pulling latest fork changes..." git -C "$WECHAT_SKILL_DIR" fetch origin "$WECHAT_ALLAUTO_GZH_BRANCH" && \ git -C "$WECHAT_SKILL_DIR" checkout "$WECHAT_ALLAUTO_GZH_BRANCH" && \ git -C "$WECHAT_SKILL_DIR" pull --ff-only origin "$WECHAT_ALLAUTO_GZH_BRANCH" || echo "Failed to pull latest fork changes" else echo "Cloning wechat-allauto-gzh from fork repository..." mkdir -p /root/.openclaw/workspace rm -rf "$WECHAT_SKILL_DIR" git clone --depth 1 --branch "$WECHAT_ALLAUTO_GZH_BRANCH" "$WECHAT_ALLAUTO_GZH_REPO_URL" "$WECHAT_SKILL_DIR" || \ echo "❌ Failed to clone wechat-allauto-gzh fork" fi if [ -d "$WECHAT_SKILL_DIR" ]; then echo "✅ wechat-allauto-gzh fork is ready at: $WECHAT_SKILL_DIR" # 安装 Python 依赖(Agent 只需要 Python 环境) echo "=== Installing Python dependencies ===" pip install --no-cache-dir requests pyyaml --break-system-packages || echo "⚠️ pip install failed" else echo "❌ wechat-allauto-gzh directory not found after clone attempt" fi # ============================================================ # ClawHub 技能、agent-browser CLI 兜底安装、Skill Node 依赖安装与技能校验 # 已迁移到 Dockerfile 构建阶段,运行时仅输出状态,避免容器启动时再进行网络安装。 # ============================================================ echo "=== ClawHub skills are preinstalled during Docker build ===" SKILLS_DIR="/root/.openclaw/workspace/skills" if [ -d "$SKILLS_DIR" ]; then echo "📂 Workspace skills directory contents:" ls -la "$SKILLS_DIR" || echo "⚠️ Failed to list skills directory" else echo "⚠️ Workspace skills directory not found at runtime: $SKILLS_DIR" fi echo "" echo "=== Verifying preinstalled agent-browser CLI ===" if command -v agent-browser >/dev/null 2>&1; then echo "✅ agent-browser CLI is available" agent-browser --version || echo "⚠️ agent-browser version check failed" else echo "⚠️ agent-browser CLI not found at runtime; image build verification may have failed" fi echo "" # ============================================================ # 跳过容器内 openclaw doctor --fix # doctor 会触发 systemd/gateway 相关检查,不适合 HF 容器前台启动模式 # ============================================================ echo "=== Skipping openclaw doctor --fix in container runtime ===" # ============================================================ # 安装微信 CLI # ============================================================ echo "=== Installing WeChat CLI ===" if npx -y @tencent-weixin/openclaw-weixin-cli@latest install; then echo "✅ WeChat CLI installed successfully" else echo "⚠️ WeChat CLI installation failed, continuing anyway..." fi # ============================================================ # 配置微信公众号凭证 # ============================================================ echo "=== Configuring WeChat Official Account credentials ===" WECHAT_CREDS_FILE="/root/.openclaw/workspace/wechat-allauto-gzh/credentials.json" if [ -n "$WECHAT_APP_ID" ] && [ -n "$WECHAT_APP_SECRET" ]; then cat > "$WECHAT_CREDS_FILE" <