#!/bin/bash # --- 1. DNS 优化 --- echo "⚙️ 正在配置 DNS (优化解析速度)..." echo "nameserver 1.1.1.1" > /etc/resolv.conf echo "nameserver 8.8.8.8" >> /etc/resolv.conf # --- 2. 基础配置 --- DATA_DIR="/root/.openclaw" INDEX_FILE="backups.txt" MAX_BACKUPS="${MAX_BACKUPS:-7}" CHECK_INTERVAL="${CHECK_INTERVAL:-10}" export TZ=Asia/Shanghai [[ -n "${WEBDAV_URL}" && "${WEBDAV_URL}" != */ ]] && WEBDAV_URL="${WEBDAV_URL}/" INDEX_URL="${WEBDAV_URL}${INDEX_FILE}" MARKER_FILE="/tmp/last_backup_marker" CURL_ARGS="-s -L --fail --connect-timeout 20 --retry 3" if [ -n "$WEBDAV_USER" ]; then CURL_ARGS="$CURL_ARGS -u $WEBDAV_USER:$WEBDAV_PASSWORD" fi # --- 3. 动态生成 openclaw.json --- generate_config() { echo "🔧 正在从环境变量生成 openclaw.json..." mkdir -p "$DATA_DIR" # 先写入基础配置(无模型) cat > "$DATA_DIR/openclaw.json" <<'EOFBASE' { "models": {}, "agents": { "defaults": { "workspace": "/root/.openclaw/workspace" } }, "messages": { "ackReactionScope": "group-mentions" }, "commands": { "native": "auto", "nativeSkills": "auto", "restart": true }, "channels": {}, "gateway": { "port": 7860, "mode": "local", "bind": "lan", "controlUi": { "dangerouslyDisableDeviceAuth": true }, "auth": { "mode": "token" }, "trustedProxies": ["127.0.0.1", "10.0.0.0/8"] }, "skills": { "install": { "nodeManager": "npm" } }, "plugins": { "entries": {} } } EOFBASE CONFIG="$DATA_DIR/openclaw.json" # 如果安装了 jq 则用 jq 注入,否则使用基础配置 if command -v jq &>/dev/null; then # 模型配置(可选) if [ -n "$API_KEY" ]; then MID="${MODEL_ID:-claude-opus-4-5-20251101}" MNAME="${MODEL_NAME:-Claude Opus 4.5}" MALIAS="${MODEL_ALIAS:-opus}" jq --arg url "${API_BASE_URL}" \ --arg key "${API_KEY}" \ --arg mid "$MID" --arg mname "$MNAME" --arg malias "$MALIAS" \ --argjson ctx "${MODEL_CONTEXT_WINDOW:-200000}" \ --argjson maxt "${MODEL_MAX_TOKENS:-32000}" \ '.models = { mode: "merge", providers: { openai: { baseUrl: $url, apiKey: $key, models: [{ id: $mid, name: $mname, api: "openai-chat", reasoning: true, input: ["text","image"], contextWindow: $ctx, maxTokens: $maxt }] }} } | .agents.defaults.model = { primary: ("openai/" + $mid) } | .agents.defaults.models = { ("openai/" + $mid): { alias: $malias } }' \ "$CONFIG" > "$CONFIG.tmp" && mv "$CONFIG.tmp" "$CONFIG" fi # HF Space URL(可选) if [ -n "$HF_SPACE_URL" ]; then jq --arg url "$HF_SPACE_URL" \ '.gateway.controlUi.allowedOrigins = [$url]' \ "$CONFIG" > "$CONFIG.tmp" && mv "$CONFIG.tmp" "$CONFIG" fi # Gateway auth token(可选) if [ -n "$GATEWAY_AUTH_TOKEN" ]; then jq --arg tok "$GATEWAY_AUTH_TOKEN" \ '.gateway.auth.token = $tok' \ "$CONFIG" > "$CONFIG.tmp" && mv "$CONFIG.tmp" "$CONFIG" fi else echo "⚠️ 未找到 jq,使用基础配置(可通过控制面板修改)" fi echo "✅ openclaw.json 生成完成" } # --- 4. 核心备份函数 --- do_backup_and_rotate() { [ -z "$WEBDAV_URL" ] && return [ ! -d "$DATA_DIR" ] && return TIMESTAMP=$(date +"%Y%m%d_%H%M%S") NEW_BACKUP_NAME="openclaw_${TIMESTAMP}.tar.gz" TEMP_PATH="/tmp/$NEW_BACKUP_NAME" echo "📦 [$(date)] 正在执行关键备份 (保存配置或定时监控)..." # 打包并忽略变动警告 tar --exclude='logs' --exclude='tmp' --ignore-failed-read -czf "$TEMP_PATH" -C "$DATA_DIR" . if [ $? -le 1 ]; then if curl $CURL_ARGS -o /dev/null -T "$TEMP_PATH" "${WEBDAV_URL}${NEW_BACKUP_NAME}"; then echo "✅ 备份上传成功" # 更新索引 curl $CURL_ARGS "$INDEX_URL" -o /tmp/backups.txt 2>/dev/null || touch /tmp/backups.txt echo "$NEW_BACKUP_NAME" >> /tmp/backups.txt sed -i '/^$/d' /tmp/backups.txt # 清理旧备份 TOTAL_LINES=$(wc -l < /tmp/backups.txt) if [ "$TOTAL_LINES" -gt "$MAX_BACKUPS" ]; then DELETE_COUNT=$((TOTAL_LINES - MAX_BACKUPS)) FILES_TO_DELETE=$(head -n "$DELETE_COUNT" /tmp/backups.txt) for FILE in $FILES_TO_DELETE; do curl -s $CURL_ARGS -X DELETE "${WEBDAV_URL}${FILE}" done tail -n "$MAX_BACKUPS" /tmp/backups.txt > /tmp/new_backups.txt mv /tmp/new_backups.txt /tmp/backups.txt fi curl $CURL_ARGS -o /dev/null -T /tmp/backups.txt "$INDEX_URL" touch "$MARKER_FILE" else echo "❌ 备份上传失败,请检查 WebDAV 配置" fi rm -f "$TEMP_PATH" fi } # --- 5. 捕获退出信号 --- CLEANUP_DONE=0 cleanup() { [ "$CLEANUP_DONE" -eq 1 ] && return CLEANUP_DONE=1 echo "⚠️ 接收到退出信号,正在保存最后一份配置到 WebDAV..." do_backup_and_rotate echo "👋 备份完成,进程退出。" exit 0 } trap cleanup SIGTERM EXIT # --- 6. 恢复逻辑 --- restore_latest() { if [ -z "$WEBDAV_URL" ]; then echo "ℹ️ 未配置 WEBDAV_URL,跳过备份恢复" if [ ! -f "$DATA_DIR/openclaw.json" ]; then generate_config fi return fi # 先验证 WebDAV 连通性(用 OPTIONS 方法,WebDAV 标准) echo "🔍 正在验证 WebDAV 连接..." HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X OPTIONS -L --connect-timeout 10 \ ${WEBDAV_USER:+-u "$WEBDAV_USER:$WEBDAV_PASSWORD"} \ "${WEBDAV_URL}") if [[ "$HTTP_CODE" == "000" ]]; then echo "❌ WebDAV 连接失败(无法连接),请检查 WEBDAV_URL" generate_config return fi echo "✅ WebDAV 连接正常 (HTTP $HTTP_CODE)" echo "🔍 正在从 WebDAV 检查数据..." if curl $CURL_ARGS "$INDEX_URL" -o /tmp/backups.txt; then LATEST_FILE=$(tail -n 1 /tmp/backups.txt) # 验证文件名格式,防止 WebDAV 返回 HTML if [[ "$LATEST_FILE" =~ ^openclaw_[0-9]+_[0-9]+\.tar\.gz$ ]]; then echo "⬇️ 正在恢复备份: $LATEST_FILE" curl $CURL_ARGS "${WEBDAV_URL}${LATEST_FILE}" -o "/tmp/$LATEST_FILE" mkdir -p "$DATA_DIR" if tar -xzf "/tmp/$LATEST_FILE" -C "$DATA_DIR" --warning=no-timestamp; then echo "✅ 备份恢复成功" else echo "❌ 备份解压失败,将重新生成配置" fi rm -f "/tmp/$LATEST_FILE" else echo "⚠️ 索引内容无效(可能是首次部署),跳过恢复" fi fi # 兜底:如果没有配置文件,从环境变量生成 if [ ! -f "$DATA_DIR/openclaw.json" ]; then echo "⚠️ 未找到已有配置,从环境变量生成" generate_config fi } # --- 7. 执行流程 --- restore_latest touch "$MARKER_FILE" # 启动后台监控 (仅在配置了 WebDAV 时) if [ -n "$WEBDAV_URL" ]; then ( while true; do sleep "$CHECK_INTERVAL" CHANGED=$(find "$DATA_DIR" -type f -newer "$MARKER_FILE" -not -path "*/logs/*" -not -path "*/tmp/*" -print -quit) if [ -n "$CHANGED" ]; then do_backup_and_rotate fi done ) & fi # 启动主程序 (不使用 exec,确保 trap 能捕捉到退出) echo "🚀 OpenClaw 启动成功" openclaw gateway run