| #!/bin/bash |
|
|
| |
| echo "⚙️ 正在配置 DNS (优化解析速度)..." |
| echo "nameserver 1.1.1.1" > /etc/resolv.conf |
| echo "nameserver 8.8.8.8" >> /etc/resolv.conf |
|
|
| |
| 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 |
|
|
| |
| 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" |
|
|
| |
| 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 |
|
|
| |
| if [ -n "$HF_SPACE_URL" ]; then |
| jq --arg url "$HF_SPACE_URL" \ |
| '.gateway.controlUi.allowedOrigins = [$url]' \ |
| "$CONFIG" > "$CONFIG.tmp" && mv "$CONFIG.tmp" "$CONFIG" |
| fi |
|
|
| |
| 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 生成完成" |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| 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 |
|
|
| |
| restore_latest() { |
| if [ -z "$WEBDAV_URL" ]; then |
| echo "ℹ️ 未配置 WEBDAV_URL,跳过备份恢复" |
| if [ ! -f "$DATA_DIR/openclaw.json" ]; then |
| generate_config |
| fi |
| return |
| fi |
|
|
| |
| 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) |
| |
| 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 |
| } |
|
|
| |
| restore_latest |
| touch "$MARKER_FILE" |
|
|
| |
| 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 |
|
|
| |
| echo "🚀 OpenClaw 启动成功" |
| openclaw gateway run |