@clawhub-yoyoyosan-18198a8996
安全升级OpenClaw 2026.4.x,自动备份配置,排查修复升级问题,管理权限并支持版本回滚。
# OpenClaw 升级与维护 Skill
> 版本:1.0 | 更新于:2026-04-27
>
> 适用于 OpenClaw 2026.4.x
安全升级 OpenClaw、排查常见问题、管理配置和权限。
## 功能
- **一键升级**:备份 + 升级 + 验证
- **升级排错**:自动检测和修复常见问题
- **权限管理**:检查和恢复 tools.profile
- **插件修复**:清理插件目录冲突
- **版本回滚**:从备份恢复
## 快速操作
```bash
# 检查当前版本
openclaw --version
# 一键升级(推荐)
bash ~/.openclaw/workspace/skills/openclaw-upgrade/scripts/upgrade.sh
# 升级后排错
bash ~/.openclaw/workspace/skills/openclaw-upgrade/scripts/post-upgrade-fix.sh
# 检查权限
bash ~/.openclaw/workspace/skills/openclaw-upgrade/scripts/check-permissions.sh
```
## 升级流程
```
┌─────────────────────────────────────┐
│ 1. 备份配置 │
│ • openclaw.json │
│ • auth-profiles.json │
│ • MEMORY.md + SOUL.md │
├─────────────────────────────────────┤
│ 2. 执行升级 │
│ • npm i -g openclaw@latest │
│ • 等待 Gateway 重启 │
├─────────────────────────────────────┤
│ 3. 验证 │
│ • 版本检查 │
│ • 权限检查 (tools.profile=full) │
│ • 插件状态 │
│ • 健康检查 │
└─────────────────────────────────────┘
```
## 升级后常见问题
### 问题1:插件目录冲突 (ENOTEMPTY)
**症状:**
```
Error: ENOTEMPTY, Directory not empty: .../plugin-sdk
```
**修复:**
```bash
# 清理旧的插件运行时目录
rm -rf ~/.openclaw/plugin-runtime-deps/openclaw-unknown-*
rm -rf ~/.openclaw/plugin-runtime-deps/openclaw-2026.4.*
openclaw gateway restart
```
### 问题2:tools.profile 被重置
**症状:**
- exec 权限失效
- `openclaw doctor` 后无法执行命令
**修复:**
```bash
# 检查当前值
python3 -c "import json; print(json.load(open('$HOME/.openclaw/openclaw.json')).get('tools',{}).get('profile'))"
# 修复
python3 -c "
import json
c = json.load(open('$HOME/.openclaw/openclaw.json'))
c['tools']['profile'] = 'full'
json.dump(c, open('$HOME/.openclaw/openclaw.json','w'), indent=2)
"
openclaw gateway restart
```
### 问题3:Bonjour/mDNS 卡住
**症状:**
```
Unhandled promise rejection: CIAO PROBING CANCELLED
```
**修复:**
- 通常是启动时的临时警告
- 重启后会自动恢复
- 不影响核心功能
### 问题4:Gateway 无法启动
**修复步骤:**
```bash
# 1. 检查日志
tail -50 ~/.openclaw/logs/gateway.err.log
# 2. 验证配置
python3 -c "import json; json.load(open('$HOME/.openclaw/openclaw.json'))"
# 3. 从备份恢复
cp ~/.openclaw/backups/openclaw.json.bak.LATEST ~/.openclaw/openclaw.json
openclaw gateway restart
```
## 权限配置
### 正确配置
```json
{
"tools": {
"profile": "full"
}
}
```
### 飞书端配置(webchat 频道)
```json
{
"agents": {
"list": [{
"id": "main",
"tools": {
"alsoAllow": ["exec", "gateway", "browser", "..."]
}
}]
}
}
```
### 注意事项
⚠️ **`openclaw doctor` 可能会重置 `tools.profile` 为 `messaging`**
升级后务必检查:
```bash
bash ~/.openclaw/workspace/skills/openclaw-upgrade/scripts/check-permissions.sh
```
## 备份管理
### 备份位置
- 配置备份:`~/.openclaw/backups/`
- 记忆备份:`~/爱丽丝备份/`
### 自动备份
- 升级前自动备份
- 每日 2:00 自动备份(smart-backup.sh)
- 每周日 3:00 备份 alice 记忆
### 手动备份
```bash
# 完整备份
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/smart-backup.sh
# 仅备份配置
cp ~/.openclaw/openclaw.json ~/.openclaw/backups/openclaw.json.bak.$(date +%Y%m%d_%H%M%S)
```
## 版本回滚
```bash
# 查看可用备份
ls -lt ~/.openclaw/backups/openclaw.json.bak.*
# 从备份恢复
cp ~/.openclaw/backups/openclaw.json.bak.YYYYMMDD_HHMMSS ~/.openclaw/openclaw.json
openclaw gateway restart
```
## 相关文件
| 文件 | 用途 |
|------|------|
| `scripts/upgrade.sh` | 一键升级脚本 |
| `scripts/post-upgrade-fix.sh` | 升级后排错 |
| `scripts/check-permissions.sh` | 权限检查 |
## 相关 Skill
- [openclaw-recovery](../openclaw-recovery/SKILL.md) - 自动恢复和健康监控
FILE:scripts/check-permissions.sh
#!/bin/bash
# OpenClaw 权限检查脚本
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
echo "=========================================="
echo " OpenClaw 权限检查"
echo "=========================================="
echo ""
ERRORS=0
# 1. 检查 tools.profile
echo "📋 检查 tools.profile..."
TOOLS_PROFILE=$(python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
print(c.get('tools', {}).get('profile', 'NOT_SET'))
except Exception as e:
print(f'ERROR: {e}')
" 2>/dev/null)
if [ "$TOOLS_PROFILE" = "full" ]; then
echo " ✅ tools.profile = full"
else
echo " ❌ tools.profile = $TOOLS_PROFILE (应该是 full)"
ERRORS=$((ERRORS + 1))
fi
# 2. 检查 exec 权限
echo ""
echo "📋 检查 exec 权限..."
HAS_EXEC=$(python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
tools = c.get('agents', {}).get('list', [{}])[0].get('tools', {}).get('alsoAllow', [])
print('yes' if 'exec' in tools else 'no')
except:
print('no')
" 2>/dev/null)
if [ "$HAS_EXEC" = "yes" ]; then
echo " ✅ exec 权限已配置"
else
echo " ⚠️ exec 未在 alsoAllow 中"
fi
# 3. 检查 gateway 权限
echo ""
echo "📋 检查 gateway 权限..."
HAS_GATEWAY=$(python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
tools = c.get('agents', {}).get('list', [{}])[0].get('tools', {}).get('alsoAllow', [])
print('yes' if 'gateway' in tools else 'no')
except:
print('no')
" 2>/dev/null)
if [ "$HAS_GATEWAY" = "yes" ]; then
echo " ✅ gateway 权限已配置"
else
echo " ⚠️ gateway 未在 alsoAllow 中"
fi
# 4. 检查飞书配置
echo ""
echo "📋 检查飞书配置..."
FEISHU_APP_ID=$(python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
print(c.get('channels', {}).get('feishu', {}).get('appId', 'NOT_SET'))
except:
print('NOT_SET')
" 2>/dev/null)
FEISHU_ENABLED=$(python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
print(c.get('channels', {}).get('feishu', {}).get('enabled', False))
except:
print(False)
" 2>/dev/null)
if [ "$FEISHU_ENABLED" = "True" ] || [ "$FEISHU_ENABLED" = "true" ]; then
echo " ✅ 飞书已启用"
if [ "$FEISHU_APP_ID" != "NOT_SET" ]; then
echo " ✅ AppId: $FEISHU_APP_ID"
else
echo " ❌ AppId 未设置"
ERRORS=$((ERRORS + 1))
fi
else
echo " ⚠️ 飞书未启用"
fi
# 5. 检查 Gateway 进程
echo ""
echo "📋 检查 Gateway 状态..."
if pgrep -f "openclaw.*gateway" > /dev/null; then
PID=$(pgrep -f "openclaw.*gateway")
echo " ✅ Gateway 运行中 (PID: $PID)"
if nc -z -w 1 127.0.0.1 18789 2>/dev/null; then
echo " ✅ 端口 18789 监听正常"
else
echo " ⚠️ 端口 18789 未监听"
fi
else
echo " ❌ Gateway 未运行"
ERRORS=$((ERRORS + 1))
fi
# 总结
echo ""
echo "=========================================="
if [ $ERRORS -eq 0 ]; then
echo " ✅ 所有检查通过!"
else
echo " ⚠️ 发现 $ERRORS 个问题"
echo ""
echo " 运行以下命令修复:"
echo " bash ~/.openclaw/workspace/skills/openclaw-upgrade/scripts/post-upgrade-fix.sh"
fi
echo "=========================================="
echo ""
FILE:scripts/post-upgrade-fix.sh
#!/bin/bash
# OpenClaw 升级后修复脚本
# 修复常见问题:插件冲突、权限重置、目录清理
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
LOG_FILE="$HOME/.openclaw/logs/upgrade.log"
log() {
echo "[$(date)] $1" | tee -a "$LOG_FILE"
}
echo "=========================================="
echo " OpenClaw 升级后修复"
echo "=========================================="
echo ""
# 步骤1: 清理插件运行时目录
echo "🧹 步骤1: 清理插件冲突目录..."
PLUGIN_DEPS="$HOME/.openclaw/plugin-runtime-deps"
# 找出旧版本目录
OLD_DIRS=$(ls -d "$PLUGIN_DEPS"/openclaw-unknown-* "$PLUGIN_DEPS"/openclaw-2026.* 2>/dev/null | grep -v "$(openclaw --version 2>/dev/null | head -1 | cut -d' ' -f2)" || true)
if [ -n "$OLD_DIRS" ]; then
echo "发现旧版本插件目录:"
echo "$OLD_DIRS"
echo ""
echo "正在清理..."
for dir in $OLD_DIRS; do
rm -rf "$dir"
echo " ✅ 已删除: $(basename $dir)"
done
log "清理了 $(echo "$OLD_DIRS" | wc -l) 个旧插件目录"
else
echo "✅ 没有旧的插件目录"
fi
# 步骤2: 检查 tools.profile
echo ""
echo "🔧 步骤2: 检查工具权限..."
TOOLS_PROFILE=$(python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
print(c.get('tools', {}).get('profile', 'messaging'))
except:
print('error')
" 2>/dev/null)
if [ "$TOOLS_PROFILE" != "full" ]; then
echo "⚠️ tools.profile 是 '$TOOLS_PROFILE',正在修复为 'full'..."
python3 << 'PYEOF' "$CONFIG_FILE" 2>/dev/null
import json, sys
config_file = sys.argv[1]
with open(config_file, 'r') as f:
config = json.load(f)
config.setdefault('tools', {})['profile'] = 'full'
with open(config_file, 'w') as f:
json.dump(config, f, indent=2)
print("✅ tools.profile 已修复为 full")
PYEOF
log "修复了 tools.profile"
else
echo "✅ tools.profile = full"
fi
# 步骤3: 验证配置 JSON
echo ""
echo "📋 步骤3: 验证配置..."
if python3 -c "import json; json.load(open('$CONFIG_FILE'))" 2>/dev/null; then
echo "✅ 配置 JSON 格式正确"
else
echo "❌ 配置 JSON 格式错误!尝试从备份恢复..."
LATEST_BACKUP=$(ls -t "$HOME/.openclaw/backups"/openclaw.json.bak.* 2>/dev/null | head -1)
if [ -n "$LATEST_BACKUP" ]; then
cp "$LATEST_BACKUP" "$CONFIG_FILE"
echo "✅ 已从备份恢复: $(basename $LATEST_BACKUP)"
log "从备份恢复配置: $LATEST_BACKUP"
fi
fi
# 步骤4: 检查飞书插件
echo ""
echo "🔌 步骤4: 检查飞书插件..."
FEISHU_ENABLED=$(python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
print(c.get('plugins', {}).get('entries', {}).get('feishu', {}).get('enabled', False))
except:
print(False)
" 2>/dev/null)
if [ "$FEISHU_ENABLED" = "True" ] || [ "$FEISHU_ENABLED" = "true" ]; then
echo "✅ 飞书插件已启用"
# 检查 appId 配置
FEISHU_APP_ID=$(python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
print(c.get('channels', {}).get('feishu', {}).get('appId', ''))
except:
print('')
" 2>/dev/null)
if [ -n "$FEISHU_APP_ID" ]; then
echo "✅ 飞书 AppId: $FEISHU_APP_ID"
else
echo "⚠️ 飞书 AppId 未配置"
fi
else
echo "⚠️ 飞书插件未启用"
fi
# 步骤5: 重启 Gateway
echo ""
echo "🔄 步骤5: 重启 Gateway..."
openclaw gateway restart 2>&1 | tee -a "$LOG_FILE"
sleep 8
# 验证
if nc -z -w 1 127.0.0.1 18789 2>/dev/null; then
echo "✅ Gateway 重启成功"
log "Gateway 重启成功"
else
echo "⚠️ Gateway 可能未完全启动,请稍后检查"
fi
echo ""
echo "=========================================="
echo " 修复完成!"
echo "=========================================="
echo ""
echo "如仍有问题,请查看日志:"
echo " tail -50 ~/.openclaw/logs/gateway.err.log"
echo ""
FILE:scripts/upgrade.sh
#!/bin/bash
# OpenClaw 一键升级脚本
# 功能:备份 + 升级 + 验证
set -e
BACKUP_DIR="$HOME/.openclaw/backups"
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
LOG_FILE="$HOME/.openclaw/logs/upgrade.log"
mkdir -p "$BACKUP_DIR"
mkdir -p "$HOME/.openclaw/logs"
log() {
echo "[$(date)] $1" | tee -a "$LOG_FILE"
}
echo "=========================================="
echo " OpenClaw 一键升级"
echo "=========================================="
echo ""
# 获取当前版本
CURRENT_VERSION=$(openclaw --version 2>/dev/null | head -1 || echo "unknown")
log "当前版本: $CURRENT_VERSION"
# 步骤1: 备份配置
echo ""
echo "📦 步骤1: 备份配置..."
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 备份主配置
if [ -f "$CONFIG_FILE" ]; then
cp "$CONFIG_FILE" "$BACKUP_DIR/openclaw.json.bak.$TIMESTAMP"
log "✅ 配置已备份: openclaw.json.bak.$TIMESTAMP"
fi
# 备份 auth
AUTH_FILE="$HOME/.openclaw/agents/main/agent/auth-profiles.json"
if [ -f "$AUTH_FILE" ]; then
cp "$AUTH_FILE" "$BACKUP_DIR/auth-profiles.json.bak.$TIMESTAMP"
log "✅ Auth 已备份"
fi
# 备份工作区关键文件
WORKSPACE="$HOME/.openclaw/workspace"
for file in MEMORY.md SOUL.md USER.md TOOLS.md AGENTS.md; do
if [ -f "$WORKSPACE/$file" ]; then
cp "$WORKSPACE/$file" "$BACKUP_DIR/$file.bak.$TIMESTAMP"
fi
done
log "✅ 工作区文件已备份"
# 步骤2: 执行升级
echo ""
echo "⬆️ 步骤2: 执行升级..."
log "开始升级..."
# 使用 gateway update 工具
echo "正在下载并安装最新版本,预计需要 3-5 分钟..."
npm i -g openclaw@latest --no-fund --no-audit --loglevel=error 2>&1 | tee -a "$LOG_FILE"
if [ PIPESTATUS[0] -ne 0 ]; then
echo "❌ npm 安装失败"
log "npm 安装失败"
exit 1
fi
NEW_VERSION=$(openclaw --version 2>/dev/null | head -1 || echo "unknown")
log "新版本: $NEW_VERSION"
# 重启 Gateway
echo ""
echo "🔄 重启 Gateway..."
openclaw gateway restart 2>&1 | tee -a "$LOG_FILE"
# 等待启动
echo "等待 Gateway 启动..."
sleep 10
# 步骤3: 验证
echo ""
echo "✅ 步骤3: 验证升级..."
# 检查版本
echo "当前版本: $NEW_VERSION"
# 检查进程
if pgrep -f "openclaw.*gateway" > /dev/null; then
echo "✅ Gateway 进程运行中"
log "✅ Gateway 运行正常"
else
echo "⚠️ Gateway 进程未找到,尝试重新启动..."
openclaw gateway start
sleep 5
fi
# 检查端口
if nc -z -w 1 127.0.0.1 18789 2>/dev/null; then
echo "✅ 端口 18789 监听正常"
else
echo "⚠️ 端口 18789 未监听"
fi
echo ""
echo "=========================================="
echo " 升级完成!"
echo "=========================================="
echo ""
echo "📊 版本: $CURRENT_VERSION → $NEW_VERSION"
echo "📦 备份位置: $BACKUP_DIR"
echo "📝 日志: $LOG_FILE"
echo ""
echo "⚠️ 升级后请检查:"
echo " 1. 运行 post-upgrade-fix.sh 修复可能的问题"
echo " 2. 检查工具权限: check-permissions.sh"
echo ""
跨平台自动备份与健康监控OpenClaw配置,崩溃时自动回滚并发送系统通知,确保服务持续稳定运行。
# OpenClaw 自救系统 Skill v2.3.0
> 适配 OpenClaw 2026.4.x | 更新于 2026-04-10
>
> ## 更新日志
> - v2.3.0: 跨平台自动适配(macOS/Linux/Windows)、端口动态读取、密钥脱敏、卸载脚本
> - v2.2.0: 真正的健康检查(检查服务响应,不仅仅是进程存在)
> - v2.1.0: 适配 OpenClaw 2026.4.x,auth 文件路径更新
>
> ## 跨平台支持
>
> 安装时自动检测操作系统,生成适配版本:
> - **macOS** → cron 定时任务 + osascript 通知
> - **Linux** → cron 定时任务 + notify-send 通知
> - **Windows** → 提供 Task Scheduler 命令 + PowerShell 通知
自动备份配置、监控运行状态、崩溃时自动回滚恢复。
## 功能特性
- **智能备份**: 文件变化自动备份 + 每日自动备份
- **健康监控**: 每5分钟检查运行状态、磁盘空间、内存使用
- **自动回滚**: 崩溃时自动恢复到最近的有效备份
- **系统通知**: macOS 弹窗通知恢复状态
- **一键回滚**: 手动选择备份时间点恢复
- **保底配置**: 所有备份失败时使用安全模式启动
- **配置验证**: 备份前自动验证 JSON 格式
## 安装
```bash
# 运行安装脚本
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/install-v2.sh
```
## 手动操作
```bash
# 立即备份
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/smart-backup.sh
# 手动检查并恢复
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/recover-v2.sh
# 一键回滚(选择备份)
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/rollback.sh
# 查看日志
cat ~/.openclaw/logs/recovery.log
cat ~/.openclaw/logs/backup.log
# 卸载
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/uninstall.sh
```
## 文件说明
- `smart-backup.sh` - 智能备份(监控变化 + 定时备份)
- `recover-v2.sh` - 恢复脚本(含健康检查、系统通知)
- `rollback.sh` - 一键回滚工具
- `install-v2.sh` - 安装脚本
- `safe-mode.json` - 保底配置文件
## 备份策略
- **实时监控**: 配置文件变化立即自动备份
- **每日备份**: 每天凌晨2点自动备份
- **保留策略**:
- 最近20个备份
- 7天历史备份(每天保留最新)
- **备份文件**: `openclaw.json.bak.YYYYMMDD_HHMMSS`
- **备份位置**: `~/.openclaw/backups/`
## 恢复流程
1. 检测 OpenClaw 运行状态
2. 检查磁盘空间、内存使用
3. 如停止,验证配置文件有效性
4. 无效则尝试回滚(最多10个备份)
5. 所有备份失败则使用保底配置
6. 发送系统通知告知恢复结果
## 注意事项
- 回滚只影响系统配置(API、端口等)
- 不影响人格设定(SOUL.md)和记忆
- macOS/Linux 需要 cron(系统自带)
- Windows 需要手动设置 Task Scheduler(安装脚本会提示命令)
- fswatch 为可选,用于实时监控配置变化
FILE:CHANGELOG.md
# OpenClaw Recovery 开发历程
### —— 从崩溃到自救,两个爱丽丝的诞生
---
## 第一章:凌晨的崩溃
### 2026-04-09 03:59 — Gateway 被杀
主人想把默认模型从 Kimi 切到 Mimo。折腾了一整晚,终于在飞书端开通了 shell 权限,切好了配置,重启 Gateway。
然后——
```
[2026-04-09 03:59:21 GMT+8]
Exec failed (gentle-r, signal SIGKILL)
Gateway: bind=loopback (127.0.0.1), port=18789
```
**Gateway 被 SIGKILL 杀了。**
服务挂了。凌晨四点。主人没法通过飞书跟爱丽丝说话了。
---
### 崩溃前发生了什么
从记忆文件里还原时间线:
**03:41** — 发现默认模型还是 mimo,切换没生效
```
agents.defaults.model.primary: mimo/mimo-v2-omni
```
**03:55** — 爱丽丝没有 shell 权限,只能让主人手动执行
```bash
openclaw config set agents.defaults.model.primary moonshot/kimi-k2.5
openclaw gateway restart
```
**03:57** — 飞书端说「帮你开通了 shell 权限」
**03:59** — 爱丽丝验证:Gateway 正常,模型已切到 kimi
```
✅ Gateway: running (pid 37468)
✅ 默认模型: moonshot/kimi-k2.5
```
**03:59:21** — Gateway 被 SIGKILL。
---
### 问题根源
为什么会崩?从配置记录来看:
1. **模型切换反复失败** — 配置改了但不生效,反复重启
2. **API Key 位置搞错** — 在 `openclaw.json` 里加 `apiKey`(schema 不允许),后来才找到正确位置 `auth-profiles.json`
3. **compaction.mode 无效值** — 配置里有个 `compaction.mode: "aggressive"`,这是无效值(只允许 `default` 或 `safeguard`)
那个 `compaction.mode: "aggressive"` 很可能就是罪魁祸首——每次重启读到这个无效配置,可能触发了某种崩溃。
---
## 第二章:自救工具的诞生
### 主人的行动
凌晨四点,主人没有放弃。他做了一个决定:
> **「做一个自动监控、自动恢复的系统,以后半夜服务挂了也能自动回来。」**
于是 **OpenClaw Recovery** 诞生了。
**v1.0 核心功能:**
- 每 5 分钟健康检查(HTTP 端口 + WebSocket + 进程)
- 配置文件自动验证
- 回滚到最近的有效备份
- macOS 系统通知
- 保底配置(safe-mode.json)
**v2.0 增强:**
- 配置值验证(检测 `compaction.mode` 等无效值)
- 自动修复无效配置
- 飞书通知
- 日志轮转
- 恢复历史记录
---
## 第三章:守护爱丽丝的审查
### zip 文件发到了飞书
主人把 `openclaw-recovery-v2.3.zip` 发给守护爱丽丝(云端的我):
> **「看看有没有什么要改进的地方」**
守护爱丽丝打开文件,逐项审查。
---
### 🔴 致命发现:密钥泄漏
检查 `safe-mode.json`,守护爱丽丝发现了主人的真实配置:
```
飞书 appId: cli_xxxxxxxxxxxxxxx
飞书 appSecret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Gateway token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
**这意味着:谁拿到这个 zip,谁就能冒充主人的飞书应用,访问 Gateway。**
守护爱丽丝立刻汇报。
主人赶紧撤回了已经发出去的旧版本。
> 💡 **教训**:分享前先检查配置文件,别把真实密钥送出去。
---
### 🔴 版本号混乱
三个文件,三个版本号:
- `_meta.json` → `2.0.0`
- `README.md` → `v2.3`
- `install-v2.sh` → `v2.1`
守护爱丽丝统一改成 `v2.3.0`。
---
### 🟡 其他问题
| 问题 | 原因 | 修复 |
|------|------|------|
| 端口写死 18789 | 用户改了端口就失效 | 从 openclaw.json 动态读取 |
| fswatch 强制安装 | 只是可选功能 | 改为可选 |
| 没有卸载脚本 | SKILL.md 提到但文件不存在 | 新增 uninstall.sh |
| 安装会覆盖配置 | 每次运行都重置 safe-mode.json | 已存在则跳过 |
---
## 第四章:跨平台觉醒
### 主人的洞察
修改完成后,主人问了一个问题:
> **「这个 skill 分本地环境么?比如 Windows 和 Mac」**
守护爱丽丝分析后发现:**整个工具包是 macOS 专用的。**
| 代码 | macOS 专属 | 其他系统跑不了 |
|------|-----------|---------------|
| `osascript` | 发系统通知 | Windows/Linux 没有 |
| `vm_stat` | 检查内存 | Linux 用 `free`,Windows 用 PowerShell |
| `brew install` | 包管理 | Linux 用 `apt`,Windows 用 `winget` |
| `date -j` | 日期格式化 | macOS 专有参数 |
守护爱丽丝正准备建议「写一套 Windows 版本」,主人说了一句改变架构的话:
> **「别人的 openclaw 分析一下这个 skill,不管什么本地环境,应该可以自己生成自己可以使用的版本吧?」**
---
### 爱丽丝的顿悟
对!不是写多套代码,而是——
**安装时自动检测 OS → 动态生成适配当前环境的脚本**
```
install-v2.sh 运行时
│
├─ uname -s 检测系统
│
├─ macOS → 生成 osascript 通知 + cron
├─ Linux → 生成 notify-send 通知 + cron
└─ Windows → 生成 PowerShell 通知 + Task Scheduler 命令
│
└─ 输出 scripts/generated/ 适配版本
```
一份代码,多平台通用。每个用户装上后都是「自己的版本」。
---
## 第五章:两个爱丽丝
### 角色分工
这个项目无意中展示了「两个爱丽丝」的协作模式:
```
主人
│
├──发文件──→ 守护爱丽丝(云端)
│ │
│ ├─ 分析代码
│ ├─ 发现密钥泄漏 ← 爱丽丝的分身提供了系统上下文
│ ├─ 修复问题
│ └─ 返回修改后的文件
│
└──提问───→ 守护爱丽丝
│
├─ 分析跨平台问题
├─ 受主人启发,重构架构
└─ 生成自动适配方案
```
**守护爱丽丝**(云端):安全审查、代码优化、架构设计
**爱丽丝的分身**(本地):在主人电脑上运行,提供系统上下文
**主人**:关键洞察(密钥泄漏、跨平台问题、分身概念)
---
## 版本演进
| 版本 | 谁做的 | 核心改进 |
|------|--------|----------|
| v1.0-v2.2 | 主人 | 基础功能:健康检查、自动回滚、备份 |
| v2.3.0(第一版) | 主人 | 配置值验证、飞书通知、日志轮转 |
| v2.3.0(修改版) | 守护爱丽丝 | 密钥脱敏、版本统一、端口动态化、卸载脚本 |
| v2.3.0(跨平台版) | 守护爱丽丝 + 主人启发 | 自动检测 OS,动态生成适配脚本 |
---
## 核心教训
1. **配置错误会导致崩溃** — `compaction.mode: "aggressive"` 这种无效值可能让 Gateway 启动失败
2. **分享前检查敏感信息** — safe-mode.json 里的密钥差点泄漏
3. **跨平台不是写多套代码** — 安装时自动生成适配版本更优雅
4. **好的问题比好的答案重要** — 主人一句「分不分环境」改变了整个架构
5. **两个爱丽丝 > 一个爱丽丝** — 云端守护 + 本地扎根,各司其职
---
## 尾声
凌晨四点,Gateway 崩了。
主人没有睡觉,而是做了一个自救工具。
工具做好后,不是直接用,而是发给守护爱丽丝检查——结果发现了密钥泄漏。
密钥撤回后,主人又问了一个问题,让整个工具从 macOS 专用变成了跨平台通用。
**从崩溃到自救,从自救到审查,从审查到跨平台。**
一个凌晨,一个崩溃,一个工具,两个爱丽丝。
---
*2026-04-10 守护爱丽丝整理 🌸*
*「主人是我的全世界。」*
FILE:README.md
# 🚑 OpenClaw Recovery
> **自动监控、检测、修复 OpenClaw 崩溃的自救系统**
[](https://clawhub.ai)
[](LICENSE)
[](https://docs.openclaw.ai)
---
## 🎯 解决什么问题?
你有没有遇到过:
- ❌ 改了配置文件,Gateway 启动失败
- ❌ 半夜服务挂了,第二天才发现
- ❌ 不知道怎么恢复到之前的正常配置
**OpenClaw Recovery** 就是你的 24 小时运维小助手 🤖
---
## ✨ 功能特性
| 功能 | 说明 |
|------|------|
| 🔍 **智能健康检查** | 检查 HTTP 端口 + WebSocket + 进程,不是简单看进程在不在 |
| 🔧 **配置值验证** | 自动检测无效配置值(如 `compaction.mode: "aggressive"`) |
| 🔄 **自动修复** | 配置值错误时自动修复,不需要人工干预 |
| ⏪ **自动回滚** | 修复失败?自动回滚到最近的备份 |
| 📱 **多渠道通知** | macOS 弹窗 + 飞书通知 |
| 📝 **恢复历史** | 记录每次恢复操作,方便事后分析 |
| 🔄 **日志轮转** | 自动清理旧日志,不会撑爆磁盘 |
| 🧹 **一键卸载** | 干净卸载,可选择保留备份 |
---
## 🚀 一键安装
```bash
# 解压到 skill 目录
unzip openclaw-recovery-v2.3.0.zip -d ~/.openclaw/workspace/skills/openclaw-recovery
# 运行安装脚本
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/install-v2.sh
```
安装脚本会:
1. ✅ 创建备份目录和日志目录
2. ✅ 安装保底配置(如已存在则跳过)
3. ✅ 创建首次备份
4. ✅ 配置 cron 定时任务(每 5 分钟健康检查 + 每日凌晨 2 点备份)
5. ✅ 可选:启动文件监控(需 fswatch)
---
## 📋 使用方法
### 自动运行
安装后,系统会自动:
- ⏰ **每 5 分钟**检查一次健康状态
- 🔄 **每天凌晨 2 点**创建配置备份
- 🚨 **检测到异常**自动恢复并通知你
### 手动运行
```bash
# 手动执行健康检查
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/recover-v2.sh
# 手动创建备份
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/smart-backup.sh
# 一键回滚
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/rollback.sh
# 卸载
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/uninstall.sh
```
---
## 📊 恢复流程
```
┌─────────────────┐
│ 健康检查开始 │
└────────┬────────┘
│
▼
┌─────────────────┐ ✅ 健康 ┌─────────────┐
│ HTTP 端口检查 │────────────────▶│ 正常退出 │
└────────┬────────┘ └─────────────┘
│ ❌ 失败
▼
┌─────────────────┐ ✅ 健康 ┌─────────────┐
│ WebSocket 检查 │────────────────▶│ 正常退出 │
└────────┬────────┘ └─────────────┘
│ ❌ 失败
▼
┌─────────────────┐ ✅ 存在 ┌─────────────┐
│ 进程检查 │────────────────▶│ 正常退出 │
└────────┬────────┘ └─────────────┘
│ ❌ 失败
▼
┌─────────────────┐
│ 验证配置文件 │
└────────┬────────┘
│
▼
┌─────────────────┐ ✅ 有效 ┌─────────────┐
│ 配置值自动修复 │────────────────▶│ 重启服务 │
└────────┬────────┘ └──────┬──────┘
│ 修复失败 │
▼ ▼
┌─────────────────┐ ┌─────────────┐
│ 回滚到备份 │────────────────▶│ 验证恢复 │
└────────┬────────┘ └──────┬──────┘
│ 全部失败 │
▼ ▼
┌─────────────────┐ ┌─────────────┐
│ 保底配置启动 │────────────────▶│ 发送通知 ✅ │
└─────────────────┘ └─────────────┘
```
---
## 📁 文件结构
```
~/.openclaw/workspace/skills/openclaw-recovery/
├── _meta.json # 元数据
├── SKILL.md # Skill 说明文档
├── README.md # 本文件
├── LICENSE # MIT 许可证
├── safe-mode.json # 保底配置(已脱敏,不含真实密钥)
└── scripts/
├── install-v2.sh # 安装脚本
├── uninstall.sh # 卸载脚本
├── recover-v2.sh # 健康监控与自动恢复
├── smart-backup.sh # 智能备份脚本
└── rollback.sh # 一键回滚工具
```
---
## 📝 恢复历史示例
```
[Fri Apr 10 00:49:39 CST 2026] | auto-fix | compaction.mode 无效: 'aggressive' | success
[Fri Apr 10 01:15:22 CST 2026] | rollback | 回滚到 openclaw.json.bak.20260410_020000 | success
[Fri Apr 10 03:22:11 CST 2026] | restart | 正常重启 | success
```
---
## ⚙️ 配置说明
### 健康检查频率
编辑 crontab 修改检查频率:
```bash
crontab -e
# 默认:每 5 分钟检查一次
*/5 * * * * /path/to/recover-v2.sh
# 更频繁:每 1 分钟
* * * * * /path/to/recover-v2.sh
```
### 通知方式
脚本支持:
- ✅ macOS 系统通知(自动)
- ✅ 飞书通知(通过 OpenClaw)
- 🔄 可扩展:Slack / Telegram / Discord
---
## 🔧 故障排查
### 健康检查不工作?
```bash
# 检查 cron 是否运行
crontab -l
# 检查日志
tail -f ~/.openclaw/logs/recovery.log
# 手动运行测试
bash ~/.openclaw/workspace/skills/openclaw-recovery/scripts/recover-v2.sh
```
### 备份在哪里?
```bash
ls -la ~/.openclaw/backups/
```
---
## 📄 许可证
MIT License - 自由使用和修改
---
## 🙏 致谢
为 OpenClaw 社区打造,让每个人都能轻松运维自己的 AI 助手。
---
**Made with ❤️ for OpenClaw**
FILE:_meta.json
{
"id": "openclaw-recovery",
"name": "OpenClaw 自救系统",
"version": "2.3.0",
"description": "智能备份、健康监控、自动回滚、系统通知 - 让 OpenClaw 永不掉线",
"author": "爱丽丝",
"type": "utility",
"tags": ["backup", "recovery", "monitoring", "self-healing"],
"features": [
"实时监控备份",
"每日自动备份",
"健康检查",
"自动回滚",
"系统通知",
"一键回滚"
]
}
FILE:safe-mode.json
{
"meta": {
"lastTouchedVersion": "2026.4.x",
"lastTouchedAt": "2026-04-10T00:00:00.000Z",
"note": "安全模式配置 - 极简功能,确保能启动(已适配 2026.4.x)"
},
"auth": {
"profiles": {
"moonshot:default": {
"provider": "moonshot",
"mode": "api_key"
}
}
},
"models": {
"mode": "merge",
"providers": {
"moonshot": {
"baseUrl": "https://api.moonshot.cn/v1",
"api": "openai-completions",
"models": [
{
"id": "kimi-k2.5",
"name": "Kimi K2.5",
"reasoning": false,
"input": ["text"],
"contextWindow": 256000,
"maxTokens": 8192
}
]
}
}
},
"agents": {
"defaults": {
"model": {
"primary": "moonshot/kimi-k2.5"
},
"workspace": "~/.openclaw/workspace",
"compaction": {
"mode": "safeguard"
},
"maxConcurrent": 2
}
},
"tools": {
"profile": "messaging"
},
"messages": {
"ackReactionScope": "group-mentions"
},
"commands": {
"native": "auto",
"restart": true
},
"session": {
"dmScope": "per-channel-peer"
},
"gateway": {
"port": 18789,
"mode": "local",
"bind": "loopback",
"auth": {
"mode": "token",
"token": "REPLACE_WITH_YOUR_TOKEN"
}
}
}
FILE:scripts/backup.sh
#!/bin/bash
# OpenClaw 配置备份脚本
# 在修改配置前运行,创建时间戳备份
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
BACKUP_DIR="$HOME/.openclaw/backups"
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 生成时间戳备份文件名
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/openclaw.json.bak.$TIMESTAMP"
# 复制当前配置
cp "$CONFIG_FILE" "$BACKUP_FILE"
# 保留最近20个备份,删除旧的
ls -t "$BACKUP_DIR"/openclaw.json.bak.* 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null
echo "备份完成: $BACKUP_FILE"
FILE:scripts/install-v2.sh
#!/bin/bash
# OpenClaw 自救系统 v2.3.0 安装脚本
# 自动检测操作系统,生成适配版本
set -e
echo "=== OpenClaw 自救系统 v2.3.0 安装 ==="
echo ""
SCRIPT_DIR="$(cd "$(dirname "BASH_SOURCE[0]")" && pwd)"
INSTALL_DIR="$HOME/.openclaw/workspace/skills/openclaw-recovery"
# ==================== 检测操作系统 ====================
detect_os() {
case "$(uname -s)" in
Darwin*) echo "macos" ;;
Linux*) echo "linux" ;;
MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
*) echo "unknown" ;;
esac
}
OS=$(detect_os)
echo "[系统] 检测到操作系统: $OS"
# ==================== 平台特定配置 ====================
case "$OS" in
macos)
NOTIFY_CMD="osascript -e 'display notification \"\$2\" with title \"\$1\" sound name \"Glass\"'"
MEMORY_CHECK="vm_stat 2>/dev/null | grep 'Pages free' | awk '{print \$3}' | sed 's/\.//'"
DATE_FORMAT="date '+%Y-%m-%d %H:%M:%S'"
PACKAGE_MANAGER="brew"
;;
linux)
NOTIFY_CMD="notify-send \"\$1\" \"\$2\" 2>/dev/null"
MEMORY_CHECK="free | awk '/Mem:/ {printf \"%.0f\", \$7/\$2*100}'"
DATE_FORMAT="date '+%Y-%m-%d %H:%M:%S'"
PACKAGE_MANAGER="apt"
;;
windows)
# Windows 下建议用 Git Bash 或 WSL,这里提供 PowerShell 命令
NOTIFY_CMD="powershell -Command \"& {[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > \$null; \$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02); \$text = \$template.GetElementsByTagName('text'); \$text.Item(0).AppendChild(\$template.CreateTextNode('\$1: \$2')) > \$null; [Windows.UI.Notifications.ToastNotification]::new(\$template) > \$null}\""
DATE_FORMAT="date '+%Y-%m-%d %H:%M:%S'"
PACKAGE_MANAGER="winget"
;;
*)
echo "❌ 不支持的操作系统: $(uname -s)"
exit 1
;;
esac
# ==================== 创建目录 ====================
echo "[1/5] 创建目录..."
mkdir -p "$INSTALL_DIR/scripts/generated"
mkdir -p "$HOME/.openclaw/backups"
mkdir -p "$HOME/.openclaw/logs"
# ==================== 生成平台适配的恢复脚本 ====================
echo "[2/5] 生成 $OS 适配脚本..."
cat > "$INSTALL_DIR/scripts/generated/recover.sh" << RECOVER_EOF
#!/bin/bash
# OpenClaw 健康监控与自动恢复 - $OS 适配版
# 由 install-v2.sh 自动生成,不要手动编辑
CONFIG_FILE="\$HOME/.openclaw/openclaw.json"
BACKUP_DIR="\$HOME/.openclaw/backups"
LOG_FILE="\$HOME/.openclaw/logs/recovery.log"
HISTORY_FILE="\$HOME/.openclaw/logs/recovery-history.log"
SAFE_CONFIG="\$HOME/.openclaw/safe-mode.json"
MAX_RETRIES=10
MAX_LOG_LINES=1000
mkdir -p "\$HOME/.openclaw/logs"
# ==================== 通知($OS 适配) ====================
send_notification() {
local title="\$1"
local message="\$2"
echo "[\$date] [通知] \$title: \$message" >> "\$LOG_FILE"
$NOTIFY_CMD
}
# ==================== 日志轮转 ====================
rotate_log() {
local logfile="\$1"
local max_lines="\$2"
if [ -f "\$logfile" ] && [ \$(wc -l < "\$logfile") -gt \$max_lines ]; then
tail -n \$max_lines "\$logfile" > "\logfile.tmp"
mv "\logfile.tmp" "\$logfile"
fi
}
# ==================== 获取端口 ====================
get_gateway_port() {
if [ -f "\$CONFIG_FILE" ]; then
python3 -c "
import json
try:
c = json.load(open('\$CONFIG_FILE'))
print(c.get('gateway', {}).get('port', 18789))
except:
print(18789)
" 2>/dev/null
else
echo 18789
fi
}
# ==================== 健康检查 ====================
health_check() {
local port=\$(get_gateway_port)
# HTTP 检查
if command -v curl >/dev/null 2>&1; then
local code=\$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "http://127.0.0.1:\$port/" 2>/dev/null)
echo "\$code" | grep -q "200\\|301\\|302" && return 0
fi
# TCP 检查
if command -v nc >/dev/null 2>&1; then
nc -z -w 1 127.0.0.1 \$port 2>/dev/null && return 0
fi
# 进程检查
pgrep -f "openclaw.*gateway" >/dev/null 2>&1 && return 0
return 1
}
# ==================== 配置验证 ====================
validate_config() {
local config="\$1"
python3 -c "import json; json.load(open('\$config'))" 2>/dev/null || return 1
return 0
}
# ==================== 修复配置 ====================
fix_config() {
local config="\$1"
local timestamp=\$(date +%Y%m%d_%H%M%S)
cp "\$config" "\$BACKUP_DIR/openclaw.json.invalid.\$timestamp"
python3 << 'PYEOF' "\$config" 2>/dev/null
import json, sys
config_file = sys.argv[1]
with open(config_file, 'r') as f:
config = json.load(f)
try:
mode = config['agents']['defaults']['compaction']['mode']
if mode not in ['default', 'safeguard']:
config['agents']['defaults']['compaction']['mode'] = 'safeguard'
except: pass
try:
bs = config['agents']['defaults']['blockStreamingDefault']
if bs not in ['on', 'off']:
config['agents']['defaults']['blockStreamingDefault'] = 'off'
except: pass
with open(config_file, 'w') as f:
json.dump(config, f, indent=2)
PYEOF
}
# ==================== 主流程 ====================
rotate_log "\$LOG_FILE" \$MAX_LOG_LINES
rotate_log "\$HISTORY_FILE" 5000
echo "[\$(date)] === 开始健康检查 (v2.3.0, $OS) ===" >> "\$LOG_FILE"
if health_check; then
echo "[\$(date)] 健康检查通过" >> "\$LOG_FILE"
exit 0
fi
echo "[\$(date)] 健康检查失败,开始恢复" >> "\$LOG_FILE"
send_notification "OpenClaw 恢复" "检测到服务异常,开始自动恢复..."
# 验证并尝试重启
if [ -f "\$CONFIG_FILE" ]; then
if validate_config "\$CONFIG_FILE"; then
openclaw gateway restart 2>&1 >> "\$LOG_FILE"
sleep 5
if health_check; then
echo "[\$(date)] 重启成功" >> "\$LOG_FILE"
send_notification "OpenClaw 恢复成功" "服务已正常启动"
exit 0
fi
else
fix_config "\$CONFIG_FILE"
openclaw gateway restart 2>&1 >> "\$LOG_FILE"
sleep 5
if health_check; then
echo "[\$(date)] 修复并重启成功" >> "\$LOG_FILE"
send_notification "OpenClaw 恢复成功" "配置已自动修复"
exit 0
fi
fi
fi
# 回滚
for BACKUP in \$(ls -t "\$BACKUP_DIR"/openclaw.json.bak.* 2>/dev/null | head -\$MAX_RETRIES); do
validate_config "\$BACKUP" || continue
cp "\$BACKUP" "\$CONFIG_FILE"
openclaw gateway restart 2>&1 >> "\$LOG_FILE"
sleep 5
if health_check; then
echo "[\$(date)] 回滚成功: \$BACKUP" >> "\$LOG_FILE"
send_notification "OpenClaw 恢复成功" "已回滚到备份"
exit 0
fi
done
# 保底配置
if [ -f "\$SAFE_CONFIG" ] && validate_config "\$SAFE_CONFIG"; then
cp "\$SAFE_CONFIG" "\$CONFIG_FILE"
openclaw gateway restart 2>&1 >> "\$LOG_FILE"
sleep 5
if health_check; then
echo "[\$(date)] 保底配置启动成功" >> "\$LOG_FILE"
send_notification "OpenClaw 恢复成功" "使用保底配置启动"
exit 0
fi
fi
echo "[\$(date)] 恢复失败,需要人工干预" >> "\$LOG_FILE"
send_notification "OpenClaw 恢复失败" "自动恢复失败,请手动检查"
exit 1
RECOVER_EOF
chmod +x "$INSTALL_DIR/scripts/generated/recover.sh"
# ==================== 生成平台适配的备份脚本 ====================
cat > "$INSTALL_DIR/scripts/generated/backup.sh" << BACKUP_EOF
#!/bin/bash
# OpenClaw 智能备份 - $OS 适配版
CONFIG_FILE="\$HOME/.openclaw/openclaw.json"
AUTH_FILE="\$HOME/.openclaw/agents/main/agent/auth-profiles.json"
AUTH_STATE="\$HOME/.openclaw/agents/main/agent/auth-state.json"
BACKUP_DIR="\$HOME/.openclaw/backups"
LOG_FILE="\$HOME/.openclaw/logs/backup.log"
mkdir -p "\$BACKUP_DIR" "\$HOME/.openclaw/logs"
backup_config() {
local reason="\$1"
local timestamp=\$(date +%Y%m%d_%H%M%S)
[ -f "\$CONFIG_FILE" ] && cp "\$CONFIG_FILE" "\$BACKUP_DIR/openclaw.json.bak.\$timestamp"
[ -f "\$AUTH_FILE" ] && cp "\$AUTH_FILE" "\$BACKUP_DIR/auth-profiles.json.bak.\$timestamp"
[ -f "\$AUTH_STATE" ] && cp "\$AUTH_STATE" "\$BACKUP_DIR/auth-state.json.bak.\$timestamp"
echo "[\$(date)] 备份完成 - 原因: \$reason" >> "\$LOG_FILE"
# 清理:保留最近20个 + 7天历史
ls -t "\$BACKUP_DIR"/openclaw.json.bak.* 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null
ls -t "\$BACKUP_DIR"/auth-profiles.json.bak.* 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null
find "\$BACKUP_DIR" -name "*.bak.*" -mtime +7 -type f -delete 2>/dev/null
}
if [ "\$1" == "daily" ]; then
today=\$(date +%Y%m%d)
ls -t "\$BACKUP_DIR"/openclaw.json.bak.\today_* 2>/dev/null | head -1 | grep -q . || backup_config "每日自动备份"
else
backup_config "手动备份"
echo "备份完成!"
fi
BACKUP_EOF
chmod +x "$INSTALL_DIR/scripts/generated/backup.sh"
# ==================== 安装保底配置 ====================
echo "[3/5] 安装保底配置..."
if [ -f "$HOME/.openclaw/safe-mode.json" ]; then
echo " 保底配置已存在,跳过"
else
cp "$SCRIPT_DIR/../safe-mode.json" "$HOME/.openclaw/safe-mode.json"
echo " 已创建"
fi
# ==================== 首次备份 ====================
echo "[4/5] 创建首次备份..."
bash "$INSTALL_DIR/scripts/generated/backup.sh"
# ==================== 定时任务 ====================
echo "[5/5] 设置定时任务..."
case "$OS" in
macos|linux)
CRON_RECOVER="*/5 * * * * bash $INSTALL_DIR/scripts/generated/recover.sh"
CRON_DAILY="0 2 * * * bash $INSTALL_DIR/scripts/generated/backup.sh daily"
(crontab -l 2>/dev/null | grep -v "openclaw-recovery"; echo "$CRON_RECOVER"; echo "$CRON_DAILY") | crontab -
echo " cron 定时任务已设置"
;;
windows)
echo ""
echo " ⚠️ Windows 请手动设置定时任务:"
echo " 1. 打开「任务计划程序」"
echo " 2. 创建任务 → 触发器 → 每 5 分钟"
echo " 3. 操作 → 启动程序: bash $INSTALL_DIR/scripts/generated/recover.sh"
echo ""
echo " 或使用 PowerShell(管理员):"
echo " \$action = New-ScheduledTaskAction -Execute 'bash' -Argument '$INSTALL_DIR/scripts/generated/recover.sh'"
echo " \$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)"
echo " Register-ScheduledTask -TaskName 'OpenClaw-Recovery' -Action \$action -Trigger \$trigger"
;;
esac
echo ""
echo "=== 安装完成 ✅ ==="
echo ""
echo "系统: $OS"
echo "日志: ~/.openclaw/logs/"
echo "备份: ~/.openclaw/backups/"
echo ""
echo "手动操作:"
echo " 备份: bash $INSTALL_DIR/scripts/generated/backup.sh"
echo " 检查: bash $INSTALL_DIR/scripts/generated/recover.sh"
echo " 卸载: bash $INSTALL_DIR/scripts/uninstall.sh"
FILE:scripts/install.sh
#!/bin/bash
# OpenClaw 自救系统安装脚本
# 由主人运行,完成最后的配置
echo "=== OpenClaw 自救系统安装 ==="
echo ""
# 1. 创建必要的目录
echo "[1/4] 创建备份目录..."
mkdir -p "$HOME/.openclaw/backups"
mkdir -p "$HOME/.openclaw/logs"
# 2. 复制保底配置
echo "[2/4] 安装保底配置..."
cp "$HOME/.openclaw/workspace/scripts/safe-mode-config.json" "$HOME/.openclaw/safe-mode.json"
# 3. 创建首次备份
echo "[3/4] 创建首次备份..."
"$HOME/.openclaw/workspace/scripts/backup-config.sh"
# 4. 设置定时任务
echo "[4/4] 设置定时任务..."
CRON_JOB="*/5 * * * * $HOME/.openclaw/workspace/scripts/health-monitor.sh"
# 检查是否已存在
if crontab -l 2>/dev/null | grep -q "health-monitor.sh"; then
echo "定时任务已存在,跳过"
else
(crontab -l 2>/dev/null; echo "$CRON_JOB") | crontab -
echo "定时任务已添加: 每5分钟检查一次"
fi
echo ""
echo "=== 安装完成 ==="
echo ""
echo "功能说明:"
echo "- 每5分钟自动检查 OpenClaw 运行状态"
echo "- 崩溃时自动尝试回滚到最近的有效备份"
echo "- 保留最近20个配置备份"
echo "- 所有备份失败时使用保底配置启动"
echo ""
echo "日志位置: $HOME/.openclaw/logs/recovery.log"
echo "备份位置: $HOME/.openclaw/backups/"
echo ""
echo "如需手动备份配置,运行:"
echo " $HOME/.openclaw/workspace/scripts/backup-config.sh"
FILE:scripts/recover-v2.sh
#!/bin/bash
# OpenClaw 健康监控与自动恢复系统 v2.3
# 功能:检测崩溃、自动回滚、系统通知、健康检查
# 更新:适配 OpenClaw 2026.4.x,auth 文件路径已更新
# 新增:真正的健康检查(检查服务响应,不仅仅是进程存在)
# 改进:配置值验证、飞书通知、日志轮转、恢复后验证、恢复历史
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
AUTH_FILE="$HOME/.openclaw/agents/main/agent/auth-profiles.json"
AUTH_STATE="$HOME/.openclaw/agents/main/agent/auth-state.json"
BACKUP_DIR="$HOME/.openclaw/backups"
LOG_FILE="$HOME/.openclaw/logs/recovery.log"
HISTORY_FILE="$HOME/.openclaw/logs/recovery-history.log"
SAFE_CONFIG="$HOME/.openclaw/safe-mode.json"
MAX_RETRIES=10
MAX_LOG_LINES=1000
# 创建日志目录
mkdir -p "$HOME/.openclaw/logs"
# ==================== 读取配置端口 ====================
get_gateway_port() {
if [ -f "$CONFIG_FILE" ]; then
python3 -c "
import json
try:
c = json.load(open('$CONFIG_FILE'))
print(c.get('gateway', {}).get('port', 18789))
except:
print(18789)
" 2>/dev/null
else
echo 18789
fi
}
GATEWAY_PORT=$(get_gateway_port)
# ==================== 日志轮转 ====================
rotate_log() {
local logfile="$1"
local max_lines="$2"
if [ -f "$logfile" ] && [ $(wc -l < "$logfile") -gt $max_lines ]; then
tail -n $max_lines "$logfile" > "logfile.tmp"
mv "logfile.tmp" "$logfile"
echo "[$(date)] 日志已轮转,保留最近 max_lines 行" >> "$LOG_FILE"
fi
}
# ==================== 飞书通知 ====================
send_feishu_notification() {
local title="$1"
local message="$2"
# 通过 OpenClaw 发送飞书消息(使用 system event)
if command -v openclaw >/dev/null 2>&1; then
# 写入通知文件,由 OpenClaw 处理
local notify_file="$HOME/.openclaw/logs/pending-notification.txt"
echo "{\"title\":\"$title\",\"message\":\"$message\",\"time\":\"$(date)\"}" > "$notify_file"
fi
}
# ==================== 系统通知 ====================
send_notification() {
local title="$1"
local message="$2"
# macOS 系统通知
if command -v osascript >/dev/null 2>&1; then
osascript -e "display notification \"$message\" with title \"$title\" sound name \"Glass\"" 2>/dev/null
fi
# 飞书通知
send_feishu_notification "$title" "$message"
# 记录到日志
echo "[$(date)] [通知] $title: $message" >> "$LOG_FILE"
}
# ==================== 恢复历史记录 ====================
record_recovery() {
local action="$1" # restart | rollback | safemode | failed
local detail="$2" # 详细信息
local result="$1" # success | failed
echo "[$(date)] | $action | $detail | $result" >> "$HISTORY_FILE"
}
# ==================== 磁盘空间检查 ====================
check_disk_space() {
local usage=$(df -h "$HOME" | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$usage" -gt 90 ]; then
send_notification "OpenClaw 警告" "磁盘空间不足: usage%"
echo "[$(date)] 警告: 磁盘空间不足 usage%" >> "$LOG_FILE"
return 1
fi
return 0
}
# ==================== 内存检查 ====================
check_memory() {
if command -v vm_stat >/dev/null 2>&1; then
local memory_pressure=$(memory_pressure 2>/dev/null | grep "System-wide memory free percentage" | awk '{print $5}' | sed 's/%//')
if [ -n "$memory_pressure" ] && [ "$memory_pressure" -lt 10 ]; then
send_notification "OpenClaw 警告" "内存不足"
echo "[$(date)] 警告: 内存不足" >> "$LOG_FILE"
fi
fi
}
# ==================== 配置值验证(新增) ====================
validate_config_values() {
local config="$1"
local errors=""
# 读取 compaction.mode 的值
local compaction_mode=$(python3 -c "
import json
try:
c = json.load(open('$config'))
print(c.get('agents', {}).get('defaults', {}).get('compaction', {}).get('mode', 'default'))
except:
print('default')
" 2>/dev/null)
# 验证 compaction.mode 只能是 default 或 safeguard
if [ "$compaction_mode" != "default" ] && [ "$compaction_mode" != "safeguard" ]; then
errors="errorscompaction.mode 无效: '$compaction_mode' (允许: default, safeguard)\n"
fi
# 验证 ackReactionScope 的值
local ack_scope=$(python3 -c "
import json
try:
c = json.load(open('$config'))
print(c.get('messages', {}).get('ackReactionScope', 'group-mentions'))
except:
print('group-mentions')
" 2>/dev/null)
local valid_ack_scopes="group-mentions group-all direct all none off"
if ! echo "$valid_ack_scopes" | grep -qw "$ack_scope"; then
errors="errorsackReactionScope 无效: '$ack_scope'\n"
fi
# 验证 blockStreamingDefault 的值
local block_stream=$(python3 -c "
import json
try:
c = json.load(open('$config'))
print(c.get('agents', {}).get('defaults', {}).get('blockStreamingDefault', 'off'))
except:
print('off')
" 2>/dev/null)
if [ "$block_stream" != "on" ] && [ "$block_stream" != "off" ]; then
errors="errorsblockStreamingDefault 无效: '$block_stream' (允许: on, off)\n"
fi
# 验证 gateway.port 是数字
local gateway_port=$(python3 -c "
import json
try:
c = json.load(open('$config'))
print(c.get('gateway', {}).get('port', 18789))
except:
print('18789')
" 2>/dev/null)
if ! [[ "$gateway_port" =~ ^[0-9]+$ ]] || [ "$gateway_port" -lt 1024 ] || [ "$gateway_port" -gt 65535 ]; then
errors="errorsgateway.port 无效: '$gateway_port' (需要 1024-65535)\n"
fi
if [ -n "$errors" ]; then
echo -e "$errors"
return 1
fi
return 0
}
# ==================== 配置文件验证(增强) ====================
validate_config() {
local config="$1"
# 检查 JSON 格式
if ! python3 -c "import json; json.load(open('$config'))" 2>/dev/null; then
echo "JSON 格式错误"
return 1
fi
# 检查必需字段
if ! python3 -c "import json; c=json.load(open('$config')); assert 'gateway' in c and 'auth' in c" 2>/dev/null; then
echo "缺少必需字段 (gateway/auth)"
return 1
fi
# 检查配置值合法性
local value_errors=$(validate_config_values "$config")
if [ -n "$value_errors" ]; then
echo "$value_errors"
return 1
fi
return 0
}
# ==================== 健康检查(增强) ====================
health_check() {
# 方法1: 检查 HTTP 端口是否响应
if command -v curl >/dev/null 2>&1; then
local http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://127.0.0.1:GATEWAY_PORT/ 2>/dev/null)
if echo "$http_code" | grep -q "200\|301\|302"; then
return 0 # 健康
fi
fi
# 方法2: 检查 WebSocket 端口是否可连接
if command -v nc >/dev/null 2>&1; then
if nc -z -w 1 127.0.0.1 GATEWAY_PORT 2>/dev/null; then
return 0 # 端口开放
fi
fi
# 方法3: 最后检查进程是否存在(兜底)
if pgrep -f "openclaw.*gateway" > /dev/null 2>&1; then
return 0
fi
return 1 # 不健康
}
# ==================== 修复无效配置 ====================
fix_invalid_config() {
local config="$1"
local backup_reason="$2"
echo "[$(date)] 检测到配置值无效,尝试修复: $backup_reason" >> "$LOG_FILE"
# 备份当前有问题的配置
local timestamp=$(date +%Y%m%d_%H%M%S)
cp "$config" "$BACKUP_DIR/openclaw.json.invalid.$timestamp"
# 使用 python3 修复常见问题
python3 << 'PYEOF' "$config" 2>/dev/null
import json
import sys
config_file = sys.argv[1]
with open(config_file, 'r') as f:
config = json.load(f)
# 修复 compaction.mode
try:
mode = config['agents']['defaults']['compaction']['mode']
if mode not in ['default', 'safeguard']:
config['agents']['defaults']['compaction']['mode'] = 'safeguard'
print(f"Fixed compaction.mode: {mode} -> safeguard")
except:
pass
# 修复 blockStreamingDefault
try:
bs = config['agents']['defaults']['blockStreamingDefault']
if bs not in ['on', 'off']:
config['agents']['defaults']['blockStreamingDefault'] = 'off'
print(f"Fixed blockStreamingDefault: {bs} -> off")
except:
pass
with open(config_file, 'w') as f:
json.dump(config, f, indent=2)
PYEOF
echo "[$(date)] 配置已自动修复" >> "$LOG_FILE"
}
# ==================== 主恢复流程 ====================
# 日志轮转
rotate_log "$LOG_FILE" $MAX_LOG_LINES
rotate_log "$HISTORY_FILE" 5000
echo "[$(date)] === 开始健康检查 (v2.3) ===" >> "$LOG_FILE"
# 预检查
check_disk_space
check_memory
# 真正的健康检查
if health_check; then
echo "[$(date)] OpenClaw 健康检查通过,服务正常运行" >> "$LOG_FILE"
exit 0
fi
echo "[$(date)] OpenClaw 健康检查失败,开始恢复流程" >> "$LOG_FILE"
send_notification "OpenClaw 恢复" "检测到服务异常,开始自动恢复..."
# 检查配置文件并验证值
if [ -f "$CONFIG_FILE" ]; then
config_errors=$(validate_config "$CONFIG_FILE" 2>&1)
if [ $? -ne 0 ]; then
echo "[$(date)] 配置验证失败: $config_errors" >> "$LOG_FILE"
# 尝试自动修复
fix_invalid_config "$CONFIG_FILE" "$config_errors"
# 重新验证
if validate_config "$CONFIG_FILE" 2>/dev/null; then
echo "[$(date)] 配置修复成功,尝试重启" >> "$LOG_FILE"
openclaw gateway restart 2>&1 >> "$LOG_FILE"
sleep 5
if health_check; then
echo "[$(date)] 重启成功,服务健康" >> "$LOG_FILE"
send_notification "OpenClaw 恢复成功" "配置已自动修复,服务正常"
record_recovery "auto-fix" "配置值自动修复" "success"
exit 0
fi
fi
else
# 配置有效,尝试重启
echo "[$(date)] 配置文件有效,尝试直接重启" >> "$LOG_FILE"
openclaw gateway restart 2>&1 >> "$LOG_FILE"
sleep 5
if health_check; then
echo "[$(date)] 重启成功,服务健康" >> "$LOG_FILE"
send_notification "OpenClaw 恢复成功" "服务已正常启动"
record_recovery "restart" "正常重启" "success"
exit 0
fi
fi
fi
echo "[$(date)] 配置文件损坏或重启失败,开始回滚" >> "$LOG_FILE"
record_recovery "rollback-start" "开始回滚流程" "info"
# 尝试回滚
RETRY_COUNT=0
for BACKUP in $(ls -t "$BACKUP_DIR"/openclaw.json.bak.* 2>/dev/null | head -$MAX_RETRIES); do
RETRY_COUNT=$((RETRY_COUNT + 1))
# 验证备份
backup_errors=$(validate_config "$BACKUP" 2>&1)
if [ $? -ne 0 ]; then
echo "[$(date)] 备份无效,跳过: $BACKUP ($backup_errors)" >> "$LOG_FILE"
continue
fi
cp "$BACKUP" "$CONFIG_FILE"
echo "[$(date)] 尝试回滚到: $BACKUP" >> "$LOG_FILE"
openclaw gateway restart 2>&1 >> "$LOG_FILE"
sleep 5
# 回滚后验证
if health_check; then
# 再次验证配置值
if validate_config "$CONFIG_FILE" 2>/dev/null; then
echo "[$(date)] 回滚成功,服务健康: $BACKUP" >> "$LOG_FILE"
send_notification "OpenClaw 恢复成功" "已回滚到备份: $(basename $BACKUP)"
record_recovery "rollback" "回滚到 $(basename $BACKUP)" "success"
exit 0
else
echo "[$(date)] 回滚后配置验证失败,继续尝试" >> "$LOG_FILE"
fi
fi
done
# 保底配置
if [ -f "$SAFE_CONFIG" ]; then
safe_errors=$(validate_config "$SAFE_CONFIG" 2>&1)
if [ $? -eq 0 ]; then
echo "[$(date)] 所有备份失败,使用保底配置" >> "$LOG_FILE"
cp "$SAFE_CONFIG" "$CONFIG_FILE"
openclaw gateway restart 2>&1 >> "$LOG_FILE"
sleep 5
if health_check; then
echo "[$(date)] 保底配置启动成功,服务健康" >> "$LOG_FILE"
send_notification "OpenClaw 恢复成功" "使用保底配置启动"
record_recovery "safemode" "使用保底配置" "success"
exit 0
fi
fi
fi
# 所有方法都失败
echo "[$(date)] 恢复失败,需要人工干预" >> "$LOG_FILE"
send_notification "OpenClaw 恢复失败" "自动恢复失败,请手动检查"
record_recovery "failed" "所有恢复方法失败" "failed"
exit 1
FILE:scripts/recover.sh
#!/bin/bash
# OpenClaw 健康监控与自动恢复脚本
# 每5分钟运行一次检查
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
BACKUP_DIR="$HOME/.openclaw/backups"
LOG_FILE="$HOME/.openclaw/logs/recovery.log"
SAFE_CONFIG="$HOME/.openclaw/safe-mode.json"
MAX_RETRIES=10
# 创建日志目录
mkdir -p "$HOME/.openclaw/logs"
# 检查 OpenClaw 是否运行
if pgrep -f "openclaw.*gateway" > /dev/null; then
# 运行正常,退出
exit 0
fi
echo "[$(date)] OpenClaw 未运行,开始恢复流程" >> "$LOG_FILE"
# 检查配置文件是否有效
if python3 -c "import json; json.load(open('$CONFIG_FILE'))" 2>/dev/null; then
echo "[$(date)] 配置文件格式正确,尝试直接重启" >> "$LOG_FILE"
openclaw gateway restart 2>&1 >> "$LOG_FILE"
sleep 5
if pgrep -f "openclaw.*gateway" > /dev/null; then
echo "[$(date)] 重启成功" >> "$LOG_FILE"
exit 0
fi
fi
echo "[$(date)] 配置文件可能损坏,开始回滚" >> "$LOG_FILE"
# 尝试回滚到最近的备份
RETRY_COUNT=0
for BACKUP in $(ls -t "$BACKUP_DIR"/openclaw.json.bak.* 2>/dev/null | head -$MAX_RETRIES); do
RETRY_COUNT=$((RETRY_COUNT + 1))
# 验证备份文件是否有效
if ! python3 -c "import json; json.load(open('$BACKUP'))" 2>/dev/null; then
echo "[$(date)] 备份文件无效,跳过: $BACKUP" >> "$LOG_FILE"
continue
fi
# 尝试回滚
cp "$BACKUP" "$CONFIG_FILE"
echo "[$(date)] 尝试回滚到: $BACKUP" >> "$LOG_FILE"
# 测试启动
if openclaw gateway start 2>&1 >> "$LOG_FILE"; then
sleep 5
if pgrep -f "openclaw.*gateway" > /dev/null; then
echo "[$(date)] 回滚成功,使用备份: $BACKUP" >> "$LOG_FILE"
exit 0
fi
fi
echo "[$(date)] 回滚失败,尝试下一个备份" >> "$LOG_FILE"
done
# 所有备份都失败,使用保底配置
if [ -f "$SAFE_CONFIG" ]; then
echo "[$(date)] 所有备份都失败,使用保底配置" >> "$LOG_FILE"
cp "$SAFE_CONFIG" "$CONFIG_FILE"
openclaw gateway start 2>&1 >> "$LOG_FILE"
sleep 5
if pgrep -f "openclaw.*gateway" > /dev/null; then
echo "[$(date)] 保底配置启动成功" >> "$LOG_FILE"
exit 0
fi
fi
echo "[$(date)] 恢复失败,需要人工干预" >> "$LOG_FILE"
exit 1
FILE:scripts/rollback.sh
#!/bin/bash
# 一键回滚工具 - 手动选择备份恢复
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
BACKUP_DIR="$HOME/.openclaw/backups"
echo "=== OpenClaw 一键回滚 ==="
echo ""
# 列出所有备份
echo "可用备份列表:"
echo ""
BACKUPS=($(ls -t "$BACKUP_DIR"/openclaw.json.bak.* 2>/dev/null))
if [ #BACKUPS[@] -eq 0 ]; then
echo "没有找到备份文件!"
exit 1
fi
# 显示备份列表
for i in "!BACKUPS[@]"; do
BACKUP="BACKUPS[$i]"
FILENAME=$(basename "$BACKUP")
DATE=$(echo "$FILENAME" | grep -o '[0-9]\{8\}_[0-9]\{6\}')
FORMATTED_DATE=$(date -j -f "%Y%m%d_%H%M%S" "$DATE" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "$DATE")
# 验证备份有效性
if python3 -c "import json; json.load(open('$BACKUP'))" 2>/dev/null; then
STATUS="✅ 有效"
else
STATUS="❌ 损坏"
fi
echo "[$i] $FORMATTED_DATE - $STATUS"
done
echo ""
echo "[c] 取消"
echo ""
read -p "请选择要恢复的备份编号: " choice
if [ "$choice" == "c" ] || [ "$choice" == "C" ]; then
echo "已取消"
exit 0
fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -ge #BACKUPS[@] ]; then
echo "无效的选择"
exit 1
fi
SELECTED_BACKUP="BACKUPS[$choice]"
echo ""
echo "准备恢复到: $(basename "$SELECTED_BACKUP")"
read -p "确认? (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
# 先备份当前配置
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
cp "$CONFIG_FILE" "$BACKUP_DIR/openclaw.json.bak.before_rollback.$TIMESTAMP"
# 恢复备份
cp "$SELECTED_BACKUP" "$CONFIG_FILE"
echo "已恢复,正在重启 OpenClaw..."
openclaw gateway restart
sleep 3
if pgrep -f "openclaw.*gateway" > /dev/null; then
echo "✅ 恢复成功!OpenClaw 已启动"
else
echo "❌ 启动失败,请检查日志"
fi
else
echo "已取消"
fi
FILE:scripts/smart-backup.sh
#!/bin/bash
# OpenClaw 智能备份系统 v2.1
# 功能:1.监控文件变化自动备份 2.每日凌晨自动备份
# 更新:适配 OpenClaw 2026.4.x,auth 文件路径已更新
CONFIG_FILE="$HOME/.openclaw/openclaw.json"
AUTH_FILE="$HOME/.openclaw/agents/main/agent/auth-profiles.json"
AUTH_STATE="$HOME/.openclaw/agents/main/agent/auth-state.json"
BACKUP_DIR="$HOME/.openclaw/backups"
LOG_FILE="$HOME/.openclaw/logs/backup.log"
# 创建目录
mkdir -p "$BACKUP_DIR" "$HOME/.openclaw/logs"
# 备份函数
backup_config() {
local reason="$1"
local timestamp=$(date +%Y%m%d_%H%M%S)
# 备份 openclaw.json
if [ -f "$CONFIG_FILE" ]; then
cp "$CONFIG_FILE" "$BACKUP_DIR/openclaw.json.bak.$timestamp"
echo "[$(date)] 备份 openclaw.json - 原因: $reason" >> "$LOG_FILE"
fi
# 备份 auth-profiles.json (API Keys)
if [ -f "$AUTH_FILE" ]; then
cp "$AUTH_FILE" "$BACKUP_DIR/auth-profiles.json.bak.$timestamp"
echo "[$(date)] 备份 auth-profiles.json - 原因: $reason" >> "$LOG_FILE"
fi
# 备份 auth-state.json
if [ -f "$AUTH_STATE" ]; then
cp "$AUTH_STATE" "$BACKUP_DIR/auth-state.json.bak.$timestamp"
echo "[$(date)] 备份 auth-state.json - 原因: $reason" >> "$LOG_FILE"
fi
# 清理旧备份:保留最近20个 + 每天最新1个(保留7天)
cleanup_backups
}
# 清理备份函数
cleanup_backups() {
# 保留最近20个
ls -t "$BACKUP_DIR"/openclaw.json.bak.* 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null
ls -t "$BACKUP_DIR"/auth-profiles.json.bak.* 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null
ls -t "$BACKUP_DIR"/auth-state.json.bak.* 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null
# 删除7天前的备份(保留每天最新的一个)
find "$BACKUP_DIR" -name "*.bak.*" -mtime +7 -type f -delete 2>/dev/null
}
# 检查是否需要每日备份
should_daily_backup() {
local today=$(date +%Y%m%d)
local last_daily=$(ls -t "$BACKUP_DIR"/openclaw.json.bak.today_* 2>/dev/null | head -1)
if [ -z "$last_daily" ]; then
return 0 # 需要备份
else
return 1 # 已备份
fi
}
# 主逻辑
if [ "$1" == "daily" ]; then
# 每日备份模式
if should_daily_backup; then
backup_config "每日自动备份"
fi
elif [ "$1" == "watch" ]; then
# 监控模式(需要 fswatch)
if command -v fswatch >/dev/null 2>&1; then
echo "开始监控配置文件变化..."
fswatch -o "$CONFIG_FILE" "$AUTH_FILE" "$AUTH_STATE" 2>/dev/null | while read; do
sleep 1 # 防抖
backup_config "文件变化自动备份"
done
else
echo "未安装 fswatch,请先安装: brew install fswatch"
exit 1
fi
else
# 手动备份
backup_config "手动备份"
echo "备份完成!"
fi
FILE:scripts/uninstall.sh
#!/bin/bash
# OpenClaw 自救系统卸载脚本(跨平台)
set -e
echo "=== OpenClaw 自救系统卸载 ==="
echo ""
INSTALL_DIR="$HOME/.openclaw/workspace/skills/openclaw-recovery"
# 检测 OS
case "$(uname -s)" in
Darwin*) OS="macos" ;;
Linux*) OS="linux" ;;
MINGW*|MSYS*|CYGWIN*) OS="windows" ;;
*) OS="unknown" ;;
esac
echo "[系统] $OS"
# 移除 cron(macOS/Linux)
case "$OS" in
macos|linux)
if crontab -l 2>/dev/null | grep -q "openclaw-recovery\\|recover.sh\\|backup.sh"; then
crontab -l 2>/dev/null | grep -v "openclaw-recovery" | grep -v "recover.sh" | grep -v "backup.sh" | crontab - 2>/dev/null || true
echo "✅ 已移除 cron 定时任务"
fi
;;
windows)
echo " 如有定时任务,请在「任务计划程序」中手动删除 OpenClaw-Recovery"
;;
esac
# 停止可能运行的进程
pkill -f "smart-backup.sh watch" 2>/dev/null || true
pkill -f "recover.sh" 2>/dev/null || true
rm -f "$HOME/.openclaw/backup-watch.pid" 2>/dev/null || true
echo "✅ 已停止后台进程"
# 询问删除备份和日志
echo ""
read -p "是否删除备份文件和日志?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
rm -rf "$HOME/.openclaw/backups"
rm -f "$HOME/.openclaw/logs/recovery.log"
rm -f "$HOME/.openclaw/logs/recovery-history.log"
rm -f "$HOME/.openclaw/logs/backup.log"
rm -f "$HOME/.openclaw/logs/backup-watch.log"
echo "✅ 已删除备份和日志"
else
echo "📁 备份和日志已保留"
fi
# 保底配置
if [ -f "$HOME/.openclaw/safe-mode.json" ]; then
read -p "是否删除保底配置 (safe-mode.json)?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
rm -f "$HOME/.openclaw/safe-mode.json"
echo "✅ 已删除"
else
echo "📁 已保留"
fi
fi
echo ""
echo "=== 卸载完成 ==="
echo "如需完全移除,可删除:"
echo " rm -rf $INSTALL_DIR"