Spaces:
Running
Running
| set -e | |
| # 强制使用北京时间(解决 HF Spaces 默认 UTC 导致汇报时间错误) | |
| export TZ="Asia/Shanghai" | |
| CONFIG="/root/.openclaw/openclaw.json" | |
| IP_RECORD="/root/.openclaw/.last_outbound_ip" | |
| ENV_FILE="/root/.openclaw/.env" | |
| # ============================================================ | |
| # 从 Supabase Storage(Private Bucket)下载所有敏感部署文件 | |
| # DEPLOY_FILES_BASE_URL 和 SUPABASE_ANON_KEY 通过 HF Secrets 注入 | |
| # 格式示例:https://xxx.supabase.co/storage/v1/object/hf-deploy | |
| # ============================================================ | |
| download_file() { | |
| local src="$1" | |
| local dest="$2" | |
| local url="${DEPLOY_FILES_BASE_URL}/${src}" | |
| mkdir -p "$(dirname "$dest")" | |
| echo " Downloading: ${src}" | |
| echo " From: ${url}" | |
| echo " To: ${dest}" | |
| if curl -fsSL \ | |
| -H "apikey: ${SUPABASE_ANON_KEY}" \ | |
| -H "Authorization: Bearer ${SUPABASE_ANON_KEY}" \ | |
| "${url}" -o "$dest"; then | |
| echo "✅ Downloaded: $src" | |
| return 0 | |
| else | |
| echo "❌ Failed: $src (HTTP error or file not found)" | |
| return 1 | |
| fi | |
| } | |
| if [ -n "$DEPLOY_FILES_BASE_URL" ] && [ -n "$SUPABASE_ANON_KEY" ]; then | |
| echo "=== Downloading deploy files from Supabase ===" | |
| FAIL_COUNT=0 | |
| # 下载 .env 文件(包含所有环境变量) | |
| echo "=== Attempting to download .env file ===" | |
| if download_file ".env" "$ENV_FILE"; then | |
| echo "✅ .env file downloaded successfully" | |
| # 加载 .env 文件中的环境变量 | |
| if [ -f "$ENV_FILE" ]; then | |
| echo "=== Loading environment variables from .env ===" | |
| set -a # 自动导出所有变量 | |
| source "$ENV_FILE" | |
| set +a | |
| echo "✅ Environment variables loaded from .env" | |
| fi | |
| else | |
| echo "⚠️ .env file download failed or not found in Supabase" | |
| echo "⚠️ Using HF Secrets as fallback for environment variables" | |
| ((FAIL_COUNT++)) | |
| fi | |
| # 配置文件 | |
| download_file "openclaw.json" "/root/.openclaw/openclaw.json" || ((FAIL_COUNT++)) | |
| # workspace 文件(角色设定、任务文档等) | |
| download_file "SOUL.md" "/root/.openclaw/workspace/SOUL.md" || ((FAIL_COUNT++)) | |
| download_file "IDENTITY.md" "/root/.openclaw/workspace/IDENTITY.md" || ((FAIL_COUNT++)) | |
| download_file "USER.md" "/root/.openclaw/workspace/USER.md" || ((FAIL_COUNT++)) | |
| download_file "AGENTS.md" "/root/.openclaw/workspace/AGENTS.md" || ((FAIL_COUNT++)) | |
| download_file "TOOLS.md" "/root/.openclaw/workspace/TOOLS.md" || ((FAIL_COUNT++)) | |
| download_file "BOOTSTRAP.md" "/root/.openclaw/workspace/BOOTSTRAP.md" || ((FAIL_COUNT++)) | |
| download_file "MEMORY.md" "/root/.openclaw/workspace/MEMORY.md" || ((FAIL_COUNT++)) | |
| download_file "HEARTBEAT.md" "/root/.openclaw/workspace/HEARTBEAT.md" || ((FAIL_COUNT++)) | |
| download_file "WORKFLOW_AUTO.md" "/root/.openclaw/workspace/WORKFLOW_AUTO.md" || ((FAIL_COUNT++)) | |
| download_file "task-dispatch.md" "/root/.openclaw/workspace/task-dispatch.md" || ((FAIL_COUNT++)) | |
| # cron 任务配置 | |
| download_file "cron/jobs.json" "/root/.openclaw/cron/jobs.json" || ((FAIL_COUNT++)) | |
| # 角色资源(Maggie) | |
| download_file "characters/maggie/character-prompt.md" "/root/.openclaw/extensions/clawmate-companion/skills/clawmate-companion/assets/characters/maggie/character-prompt.md" || ((FAIL_COUNT++)) | |
| download_file "characters/maggie/meta.json" "/root/.openclaw/extensions/clawmate-companion/skills/clawmate-companion/assets/characters/maggie/meta.json" || ((FAIL_COUNT++)) | |
| download_file "characters/maggie/README.md" "/root/.openclaw/extensions/clawmate-companion/skills/clawmate-companion/assets/characters/maggie/README.md" || ((FAIL_COUNT++)) | |
| download_file "characters/maggie/images/reference.png" "/root/.openclaw/extensions/clawmate-companion/skills/clawmate-companion/assets/characters/maggie/images/reference.png" || ((FAIL_COUNT++)) | |
| # clawmate 补丁文件 | |
| download_file "patches/clawmate/types.ts" "/root/.openclaw/extensions/clawmate-companion/src/core/types.ts" || ((FAIL_COUNT++)) | |
| download_file "patches/clawmate/config.ts" "/root/.openclaw/extensions/clawmate-companion/src/core/config.ts" || ((FAIL_COUNT++)) | |
| download_file "patches/clawmate/prepare.ts" "/root/.openclaw/extensions/clawmate-companion/src/core/prepare.ts" || ((FAIL_COUNT++)) | |
| download_file "patches/clawmate/plugin.ts" "/root/.openclaw/extensions/clawmate-companion/src/plugin.ts" || ((FAIL_COUNT++)) | |
| download_file "patches/clawmate/video-pipeline.ts" "/root/.openclaw/extensions/clawmate-companion/src/video-pipeline.ts" || ((FAIL_COUNT++)) | |
| download_file "patches/clawmate/image-fast-pipeline.ts" "/root/.openclaw/extensions/clawmate-companion/src/image-fast-pipeline.ts" || ((FAIL_COUNT++)) | |
| download_file "patches/clawmate/SKILL.md" "/root/.openclaw/extensions/clawmate-companion/skills/clawmate-companion/SKILL.md" || ((FAIL_COUNT++)) | |
| download_file "patches/clawmate/SKILL.zh.md" "/root/.openclaw/extensions/clawmate-companion/skills/clawmate-companion/SKILL.zh.md" || ((FAIL_COUNT++)) | |
| download_file "patches/clawmate/openclaw.plugin.json" "/root/.openclaw/extensions/clawmate-companion/openclaw.plugin.json" || ((FAIL_COUNT++)) | |
| echo "=== Deploy files download complete (failures: $FAIL_COUNT) ===" | |
| if [ "$FAIL_COUNT" -gt 0 ]; then | |
| echo "⚠️ WARNING: $FAIL_COUNT file(s) failed to download, service may not work correctly" | |
| fi | |
| else | |
| echo "⚠️ DEPLOY_FILES_BASE_URL or SUPABASE_ANON_KEY not set, skipping Supabase file download" | |
| 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 中的占位符 | |
| # 环境变量已从 .env 文件加载,或使用 HF Secrets 作为后备 | |
| # ============================================================ | |
| echo "=== Replacing placeholders in openclaw.json ===" | |
| sed -i "s|__GATEWAY_TOKEN__|${GATEWAY_TOKEN}|g" "$CONFIG" | |
| sed -i "s|__CLIPROXY_BASE_URL__|${CLIPROXY_BASE_URL}|g" "$CONFIG" | |
| sed -i "s|__CLIPROXY_API_KEY__|${CLIPROXY_API_KEY}|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" | |
| 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|__HFOPQQ_GROK2API_KEY__|${HFOPQQ_GROK2API_KEY}|g" "$CONFIG" | |
| sed -i "s|__HFOPQQ_GROK2API_BASE_URL__|${HFOPQQ_GROK2API_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|__HFOPIMAGINESOUGOU_GROK_API_KEY__|${HFOPIMAGINESOUGOU_GROK_API_KEY}|g" "$CONFIG" | |
| sed -i "s|__HFOPIMAGINESOUGOU_GROK_BASE_URL__|${HFOPIMAGINESOUGOU_GROK_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" | |
| 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 -c "__.*__" "$CONFIG" || echo "0") | |
| echo "Remaining placeholders: $PLACEHOLDER_COUNT" | |
| if [ "$PLACEHOLDER_COUNT" -gt 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") | |
| echo "=== HF Space outbound IP: ${CURRENT_IP} ===" | |
| 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 插件已安装(运行时再次检查) | |
| # ============================================================ | |
| echo "=== Verifying @openclaw-china/channels plugin ===" | |
| if ! node openclaw.mjs plugins list | grep -q "@openclaw-china/channels"; then | |
| echo "⚠️ Plugin not found, attempting to install..." | |
| node openclaw.mjs plugins install @openclaw-china/channels --force || \ | |
| echo "❌ Plugin installation failed, channels may not work" | |
| else | |
| echo "✅ @openclaw-china/channels plugin is installed" | |
| fi | |
| # ============================================================ | |
| # 自动修复配置文件(解决未知 channel ID 问题) | |
| # ============================================================ | |
| echo "=== Running openclaw doctor to fix config ===" | |
| node openclaw.mjs doctor --fix || echo "⚠️ Doctor fix failed, continuing anyway..." | |
| echo "✅ Config fixed" | |
| # Start gateway | |
| exec node openclaw.mjs gateway --port 7860 --bind lan --allow-unconfigured | |