@clawhub-xbcvv-df55dedf03
Protect openclaw.json with automatic rollback, lock mode, multi-version baseline snapshots, audit log, and SIGUSR1 gateway hot-reload. Use when you need to s...
---
name: config-guardian
description: Protect openclaw.json with automatic rollback, lock mode, multi-version baseline snapshots, audit log, and SIGUSR1 gateway hot-reload. Use when you need to safeguard OpenClaw config from bad writes, detect unauthorized changes, or recover from config corruption automatically.
license: MIT
metadata:
version: 1.0.4
author: 大黑 #F_SEC
tags: openclaw, config, guardian, security, systemd
openclaw:
requires:
bins:
- inotifywait
- jq
- sha256sum
permissions:
- root
platform:
- linux
env: []
install:
- id: inotify-tools
kind: apt
package: inotify-tools
bins: [inotifywait]
label: Install inotify-tools (apt)
- id: jq
kind: apt
package: jq
bins: [jq]
label: Install jq (apt)
---
# config-guardian(配置自愈)
自动监控 `openclaw.json`,写入失败自动回滚到上一个有效版本。支持磁盘持久化锁定模式、多版本快照、结构化审计日志、SIGUSR1 热重载。
## 功能一览
- **自动回滚**:任何写入触发验证,失败立即恢复到 baseline
- **锁定模式**:连续 3 次失败后进入锁定,所有写入强制回滚,监控不停止
- **持久化状态**:锁定状态落盘,守护重启后依然有效
- **多版本 baseline**:每次更新 baseline 前归档,保留最近 7 个有效版本
- **SIGUSR1 热重载**:回滚/验证通过后自动通知 gateway,无需手动重启
- **审计日志**:每次操作记录时间戳、事件类型、触发进程
- **自身完整性**:启动时 SHA256 自检,脚本被篡改则拒绝启动并告警
- **双端告警**:熔断时向 Discord + Telegram 发送告警
## 前提条件
- 系统:Linux,systemd 环境
- 依赖:`inotify-tools`(inotifywait)、`jq`
- 权限:root(守护以 root 运行)
- OpenClaw gateway 已部署(`/root/.openclaw/openclaw.json` 存在)
```bash
# 安装依赖
apt-get install -y inotify-tools jq
```
## 安装
```bash
bash scripts/install.sh
```
安装后验证:
```bash
systemctl status openclaw-config-guardian
# 应显示:active (running),日志含 🛡️ guardian v3.2 started
```
## 核心命令速查
```bash
# 查看守护状态
systemctl status openclaw-config-guardian
# 实时日志
journalctl -u openclaw-config-guardian -f
# 查看锁定状态
jq '.locked, .attempts, .last_error' /root/.openclaw/backups/config/.guardian_state.json
# 解锁(锁定模式下,配置修复后执行)
openclaw-config-guardian unlock
# 查看审计日志
tail -30 /root/.openclaw/backups/config/guardian_audit.log
# 查看 baseline 历史
ls -lth /root/.openclaw/backups/config/baseline_history/
# 重启守护
systemctl restart openclaw-config-guardian
```
## 解锁流程
1. 通过官方通道修复配置:`openclaw config.patch ...`
2. 执行解锁(内置 validate,通过才解锁):
```bash
openclaw-config-guardian unlock
```
3. 确认:`jq '.locked' /root/.openclaw/backups/config/.guardian_state.json` → `false`
## 更新 guardian 脚本后必须刷新 checksum
```bash
sha256sum /usr/local/bin/openclaw-config-guardian > \
/root/.openclaw/backups/config/.guardian_checksum
systemctl restart openclaw-config-guardian
```
## 安全说明
### 为什么需要 root 权限
guardian 以 root 运行是必要的:
1. **监控目标**:`/root/.openclaw/openclaw.json` 归属 root,非 root 无法可靠监控
2. **回滚操作**:需要覆盖写入配置文件,需要 root 权限
3. **systemd 交互**:发送 SIGUSR1 给 Gateway 进程需要匹配权限
4. **无网络访问**:guardian 不发起任何网络请求,无数据外传风险
5. **本地闭环**:所有操作(监控、验证、回滚、告警)完全在本机执行
guardian 不读取任何密钥、不执行动态代码,root 权限仅用于文件系统操作和进程信号。
**关于告警网络访问**:guardian 通过调用本机 `openclaw message send` CLI 命令发送熔断告警(Discord/Telegram)。网络连接由已运行的 OpenClaw Gateway 进程负责,guardian 脚本本身不直接发起任何网络连接,不持有任何凭证。如未部署 OpenClaw Gateway,告警调用会静默失败(`2>/dev/null || true`),不影响核心保护功能。
**关于 guardian 脚本**:runtime 脚本已打包在 `scripts/openclaw-config-guardian`,install.sh 从包内复制到 `/usr/local/bin/`,不从外部获取任何二进制文件。
## 参考文档
- `references/DESIGN.md` — 架构设计与状态机说明
- `references/USAGE.md` — 日常使用与失败处理
- `references/OPERATION.md` — 运维与故障排除
- `references/ARCHIVE.md` — 版本历史与文件清单
FILE:references/ARCHIVE.md
# 方案归档索引
## 项目:OpenClaw 配置守护(Config Guardian)
**归档日期**:2026-03-12(最后更新:2026-03-19)
**当前版本**:v3.1
**作者**:小七(OpenClaw 主控)/ 大黑 #F_SEC(加固实施)
**目标用户**:系统管理员、OpenClaw 运维人员
---
## 文件清单
| 文件 | 路径 | 说明 |
|------|------|------|
| `DESIGN.md` | `common_assets/Library/config-guardian/DESIGN.md` | 原理与架构设计 |
| `USAGE.md` | `common_assets/Library/config-guardian/USAGE.md` | 使用说明(安装、日常操作)|
| `OPERATION.md` | `common_assets/Library/config-guardian/OPERATION.md` | 运行维护与故障排除 |
| 本文件 | `common_assets/Library/config-guardian/ARCHIVE.md` | 归档索引 |
---
## 关联文件(已部署)
| 类型 | 路径 | 说明 |
|------|------|------|
| 守护脚本 | `/usr/local/bin/openclaw-config-guardian` | 主程序 v3.1,可执行 |
| systemd 服务 | `/etc/systemd/system/openclaw-config-guardian.service` | 系统服务单元 |
| 状态文件 | `/root/.openclaw/backups/config/.guardian_state.json` | 运行时状态(自动生成)|
| 基线备份 | `/root/.openclaw/backups/config/baseline.bak` | 最新有效配置(自动更新)|
| 基线校验 | `/root/.openclaw/backups/config/baseline.bak.sha256` | baseline SHA256(v3+)|
| 历史基线 | `/root/.openclaw/backups/config/baseline_history/` | 最近 7 个有效版本(v3+)|
| 快照目录 | `/root/.openclaw/backups/config/attempts/` | 历史变更快照(最近 20 个)|
| 审计日志 | `/root/.openclaw/backups/config/guardian_audit.log` | 结构化审计日志(v3+)|
| 自检 checksum | `/root/.openclaw/backups/config/.guardian_checksum` | guardian 脚本 SHA256(v3+)|
| 加固方案 | `common_assets/Protocols/CONFIG_GUARDIAN_HARDENING_V1.md` | 加固设计文档 |
---
## 部署状态
- [x] 脚本已写入 `/usr/local/bin/`
- [x] systemd 服务已写入 `/etc/systemd/system/`
- [x] 服务已 enable 并 active (running)
- [x] 文档已写入 `common_assets/Library/config-guardian/`
- [x] guardian v3.1 加固全部实施完成
---
## 版本历史
| 版本 | 日期 | 说明 | 操作人 |
|------|------|------|--------|
| v1.0 | 2026-03-12 | 初始版本,单文件监控、验证、回滚、3次限制后停止 | 小七 |
| v2.0 | 2026-03-12 | 目录级监控,覆盖原子替换(close_write,moved_to,create)| 大黑 |
| v3.0 | 2026-03-19 | 锁定模式熔断、7版本baseline历史、SIGUSR1、SHA256、审计日志、自身完整性 | 大黑 |
| v3.1 | 2026-03-19 | 修复锁定态自解锁bug、补SIGUSR1、去掉create事件、self-check fail-closed | 大黑 |
---
## 备注
- 本方案仅保护 `openclaw.json`,不覆盖技能安装、文件删除等其他操作。
- 更新 guardian 脚本后必须刷新 checksum,否则 fail-closed 自检拒绝启动。
- 建议结合 Git 做更完整的版本控制。
---
**归档完成** ✅
FILE:references/DESIGN.md
# OpenClaw 配置守护方案 - 设计文档
## 1. 背景与目标
### 问题
- 配置修改失误导致 Gateway 无法启动
- 缺乏自动回滚机制
- 多人操作时无法追溯
- 修改失败后不知道具体哪里出错
### 目标
- **被动监控**:任何方式修改 `openclaw.json` 都会被捕获
- **自动验证**:修改后立即调用 `openclaw config validate`
- **即时回滚**:验证失败自动恢复到上一个有效版本
- **错误反馈**:记录具体错误字段与原因
- **熔断机制**:连续 3 次失败后进入**锁定模式**(v3+),监控继续运行,任何写入自动回滚,同时向 Discord + TG 双发告警;不再停止守护
- **版本追溯**:保留每次修改的快照,便于人工审计
---
## 2. 架构设计
### 2.1 核心组件
```
┌─────────────────┐
│ inotifywait │ 监控文件系统事件
│ close_write, │─────────────┐
│ moved_to │ │
└─────────────────┘ │
▼
┌─────────────────┐ ┌─────────────────┐
│ 守护主进程 │───→ │ 创建快照 │
│ openclaw- │ │ attempts/xxx.bak│
│ config-guardian │ └─────────────────┘
└─────────────────┘ │
│
▼
┌─────────────────┐
│ 验证配置 │
│ openclaw config │
│ validate │
└─────────────────┘
│
┌───────────┴───────────┐
▼ ▼
✅ 成功 ❌ 失败
│ │
▼ ▼
更新 baseline.bak 回滚 baseline.bak
SIGUSR1 → gateway attempts++
│
▼
attempts >= 3 ?
│
┌───────┴───────┐
▼ ▼
write locked=true 继续监控
双端告警 重置计数
注:handle_change() 入口先 is_locked()(读磁盘)
locked=true → do_revert()+SIGUSR1 → return(不走 validate)
```
### 2.2 备份目录结构
```
/root/.openclaw/backups/config/
├── baseline.bak # 最新有效版本(回滚源)
├── attempts/
│ ├── openclaw.json.2026-03-12-09-50-22.bak # 每次变更快照
│ └── openclaw.json.2026-03-12-09-52-11.bak
└── .guardian_state.json # 守护状态(attempts、错误、锁定)
```
**关键点**:
- `baseline.bak` 永远代表「上一个验证通过版本」,只在验证成功后更新
- `attempts/` 保留所有被验证的修改快照(含成功与失败),保留最近 20 个
- `.guardian_state.json` 记录连续失败次数与锁定状态
---
## 3. 详细流程
### 3.1 守护启动
```bash
# 1. 确保目录存在
mkdir -p /root/.openclaw/backups/config/attempts
# 2. 如果 baseline.bak 不存在,复制当前 openclaw.json 作为基线
if [[ ! -f baseline.bak ]]; then
cp openclaw.json baseline.bak
fi
# 3. 初始化状态文件(attempts=0)
echo '{"attempts":0,"last_error":null,"last_backup":null,"locked":false}' > .guardian_state.json
# 4. 启动 inotifywait 监控(目录级,覆盖原子替换)
inotifywait -m -e close_write,moved_to --format '%e %f' /root/.openclaw/ |
while read -r evt fname; do
[[ "$fname" == "openclaw.json" ]] && handle_change
done
```
### 3.2 处理变更事件(v3.2 真状态机)
```bash
handle_change() {
# 0. 入口先读磁盘 locked 状态(每次都读,不信任内存)
if is_locked; then
# 锁定模式:直接 revert,绝不进入 validate
do_revert "locked_mode"
reload_gateway # SIGUSR1 热重载
return 0
fi
# 1. 创建本次变更快照(事后追溯)
snap=$(snapshot)
# 2. 验证配置
if validate_config; then
# ✅ 成功:更新 baseline + 归档历史 + SIGUSR1
after_success "$snap"
else
# ❌ 失败:回滚到 baseline + SIGUSR1 + attempts++
after_failure "$snap" "validate_failed"
# 达到 MAX_RETRIES → write_state locked=true + 双端告警
fi
}
```
---
## 4. 错误反馈机制
### 4.1 日志输出
所有操作写入 systemd journal:
```bash
journalctl -u openclaw-config-guardian -f
```
示例:
```
Mar 12 09:50:22 guardian[1234]: 🔄 检测到配置变更
Mar 12 09:50:22 guardian[1234]: ❌ 验证失败:agents.defaults.sandbox.mode: invalid value
Mar 12 09:50:22 guardian[1234]: 🔄 已回滚到 baseline.bak
Mar 12 09:50:22 guardian[1234]: 🔢 尝试次数: 1/3
```
### 4.2 状态文件
`/root/.openclaw/backups/config/.guardian_state.json` 实时记录:
```json
{
"attempts": 1,
"last_error": "agents.defaults.sandbox.mode: invalid value",
"last_backup": "/root/.openclaw/backups/config/attempts/openclaw.json.2026-03-12-09-50-22.bak",
"locked": false,
"failed_at": null
}
```
### 4.3 双端告警(内置)
熔断触发时通过 `openclaw message send` 自动向 Discord `#报告` + Telegram 双发告警,无需额外配置。
---
## 5. 恢复与调试
### 5.1 手动恢复
```bash
# 恢复 baseline(v3+ 自动 SIGUSR1 热重载,guardian 检测到变更后自动同步 gateway)
cp /root/.openclaw/backups/config/baseline.bak /root/.openclaw/openclaw.json
# 如需完整重启(补救选项):
# openclaw gateway restart
```
### 5.2 查看历史尝试
```bash
ls -lht /root/.openclaw/backups/config/attempts/
# 找到失败当天的快照,用 diff 对比
diff -u baseline.bak attempts/openclaw.json.2026-03-12-09-50-22.bak
```
### 5.3 重启守护
```bash
# 修复配置后(确保 baseline.bak 有效)
systemctl start openclaw-config-guardian
# 或重启
systemctl restart openclaw-config-guardian
```
---
## 6. 注意事项
1. **权限**:守护以 root 运行,确保能读写 `/root/.openclaw/`
2. **基线依赖**:`baseline.bak` 必须存在且有效,否则回滚失败
3. **监控范围**:仅监控 `openclaw.json`,不监控其他配置文件(如 `plugins/` 独立文件)
4. **inotify 限制**:极端高频写入可能丢事件,但配置修改频率低,无影响
5. **Gateway 热重载**:v3+ 验证通过/回滚后自动发 SIGUSR1 给 gateway 触发热重载,无需人工干预。
---
## 7. 扩展建议
- 结合 Git:守护前/后自动 `git add/commit`,提供完整版本历史
- 多文件监控:扩展监控 `plugins/*.json`、`agents/*.json` 等
- Webhook 通知:失败时调用外部 API(如 Slack、Discord)
- 自动 Gateway 重启:回滚成功后自动 `openclaw gateway restart`(需评估风险)
---
文档版本:v3.2
最后更新:2026-03-19(大黑 #F_SEC,对齐 guardian v3.2)
## 8. v3 / v3.1 加固变更摘要
| 优先级 | 项目 | 版本 | 状态 |
|---|---|---|---|
| P0 | 熔断后锁定模式(不停监控)+ 双端告警 | v3 | ✅ |
| P0 | inotifywait MOVED_TO 覆盖原子替换 | v2已有 | ✅ |
| P0 | 锁定态回滚后补发 SIGUSR1 | v3.1 | ✅ |
| P1 | 验证/回滚后 SIGUSR1 触发 Gateway 重载 | v3 | ✅ |
| P1 | 多版本 baseline 历史(保留7个)| v3 | ✅ |
| P1 | 去掉 create 事件,只保留 close_write,moved_to | v3.1 | ✅ |
| P2 | baseline.bak SHA256 校验保护 | v3 | ✅ |
| P2 | 结构化审计日志 | v3 | ✅ |
| P3 | guardian 自身 SHA256 完整性自检(fail-closed)| v3.1 | ✅ |
## 10. v3.2 真状态机锁定修复
| 变更 | v3.1 | v3.2 |
|---|---|---|
| 锁定检测 | 进程内布尔变量(已废弃)| 每次从磁盘读 `locked` 字段 |
| 锁定持久性 | 进程重启后失效 | 持久化磁盘,重启后仍有效 |
| 锁定路径逻辑 | 进入 after_failure 内部判断 | handle_change() 入口提前 return |
| 解锁方式 | 手动改 state.json | `openclaw-config-guardian unlock`(内置 validate)|
### handle_change() 流程(v3.2)
```
handle_change()
│
├─ is_locked()? ──读磁盘──► locked=true
│ │
│ └─► do_revert() + reload_gateway() → return ← 绝不进入 validate
│
└─ locked=false
│
├─ snapshot()
├─ validate_config()
│ ├─ OK → after_success()
│ └─ FAIL → after_failure() → do_revert() → [达到MAX_RETRIES → write locked=true]
└─ (监控继续)
```
### 解锁命令
```bash
openclaw-config-guardian unlock
# 1. 执行 validate_config()
# 2. 通过 → write_state locked=false, attempts=0 → update_baseline → reload_gateway
# 3. 不通过 → 打印原因,exit 1,拒绝解锁
```
## 9. v3.1 闭环修复详情
### 9.1 锁定态自解锁 bug(高风险,已修复 v3.2)
**问题**:锁定态执行 `cp baseline -> openclaw.json` 会再次触发 inotify,下一轮 handle_change() 验证通过后 after_success() 将 locked 重置为 false,锁定自动解除。
**v3.2 终版修法(方案 A:真状态机)**:
- `handle_change()` 入口第一件事调用 `is_locked()`,**每次从磁盘读取** `.guardian_state.json` 中的 `locked` 字段
- `locked=true` 时:直接执行 `do_revert()` + `reload_gateway()`,**绝不进入 validate / after_success 流程**,直接 return
- 锁定状态完全持久化于磁盘,guardian 崩溃重启后仍然有效
### 9.2 锁定态回滚后补 SIGUSR1
锁定态 auto-revert 完成后增加 `reload_gateway` 调用,确保 gateway 内存与磁盘配置同步。
### 9.3 去掉 create 事件
inotifywait 从 `close_write,moved_to,create` 改为 `close_write,moved_to`,减少不必要的触发面(create 事件在新文件创建时触发,与 openclaw.json 覆盖场景无关,且可能引发额外的误触发)。
### 9.4 self-check 改为 fail-closed
checksum 文件缺失时不再静默跳过,改为拒绝启动并发告警。任何环境部署必须先生成 checksum 才能运行 guardian。
FILE:references/OPERATION.md
# OpenClaw 配置守护 - 运行与维护
> 文档版本:v3.0
> 最后更新:2026-03-19(大黑 #F_SEC)
> 对应脚本版本:guardian v3
---
## 1. 服务生命周期
### 1.1 启动
```bash
systemctl daemon-reload
systemctl enable --now openclaw-config-guardian
systemctl status openclaw-config-guardian
# 应显示:active (running)
# 日志应显示:🛡️ guardian v3 started
```
### 1.2 停止 / 重启
```bash
systemctl stop openclaw-config-guardian
systemctl restart openclaw-config-guardian
# 强制重启
systemctl kill -s KILL openclaw-config-guardian && systemctl start openclaw-config-guardian
```
---
## 2. 监控与日志
### 2.1 实时日志
```bash
journalctl -u openclaw-config-guardian -f
journalctl -u openclaw-config-guardian -n 100 --no-pager
```
### 2.2 结构化审计日志(v3 新增)
位置:`/root/.openclaw/backups/config/guardian_audit.log`
格式:
```
<ISO时间> | GUARDIAN_PID=<pid> | ACTION=<事件> | writer=<进程>(pid=<pid>) | <详情>
```
事件类型:
- `GUARDIAN_START` — 守护启动
- `BASELINE_INIT` — 首次创建 baseline
- `VALIDATE_OK` — 验证通过,baseline 已更新
- `VALIDATE_FAIL` — 验证失败,已回滚
- `CIRCUIT_BREAK` — 熔断触发,进入锁定模式
- `LOCKED_REVERT` — 锁定模式下自动撤销写入
- `GATEWAY_RELOAD` — SIGUSR1 已发送至 gateway
- `BASELINE_CORRUPT` — baseline 校验失败
- `BASELINE_RESTORED` — 从历史恢复 baseline
```bash
# 查看最近事件
tail -20 /root/.openclaw/backups/config/guardian_audit.log
# 查找熔断事件
grep CIRCUIT_BREAK /root/.openclaw/backups/config/guardian_audit.log
```
### 2.3 状态文件
```bash
cat /root/.openclaw/backups/config/.guardian_state.json
jq '.attempts, .locked, .last_error' /root/.openclaw/backups/config/.guardian_state.json
```
### 2.4 日志标记速查
| 标记 | 含义 |
|---|---|
| 🛡️ | 守护启动 |
| 🔄 | 检测到变更 / 回滚中 |
| ✅ | 验证通过 |
| ❌ | 验证失败 |
| ⛔ | 熔断锁定 / 自身校验失败 |
| 📦 | baseline 归档到历史 |
| 📡 | SIGUSR1 已发给 gateway |
| ♻️ | 从历史恢复 baseline |
| ⚠️ | 软警告(不停止运行)|
---
## 3. 备份管理
### 3.1 目录结构
```
/root/.openclaw/backups/config/
├── baseline.bak # 最新有效版本(回滚源)
├── baseline.bak.sha256 # baseline 完整性校验(v3 新增)
├── baseline_history/ # 多版本历史(保留最近 7 个)
│ ├── baseline_20260319_152819.bak
│ └── ...
├── attempts/ # 每次变更快照(保留最近 20 个)
│ └── openclaw.json.2026-03-19-152819.bak
├── .guardian_state.json # 守护状态
├── .guardian_checksum # guardian 脚本自身 SHA256(v3 新增)
└── guardian_audit.log # 结构化审计日志(v3 新增)
```
### 3.2 从 baseline 历史恢复
```bash
# 查看历史版本
ls -lth /root/.openclaw/backups/config/baseline_history/
# 选择版本恢复
systemctl stop openclaw-config-guardian
cp /root/.openclaw/backups/config/baseline_history/baseline_20260319_XXXXXX.bak \
/root/.openclaw/openclaw.json
openclaw config validate
openclaw gateway restart
systemctl start openclaw-config-guardian
```
### 3.3 baseline 完整性校验
```bash
sha256sum -c /root/.openclaw/backups/config/baseline.bak.sha256
# OK = 完整
# FAILED = 损坏,guardian 会自动尝试从历史恢复
```
---
## 4. 熔断与锁定模式(v3 行为)
### v3 与 v2 的关键区别
| 行为 | v2 | v3 |
|---|---|---|
| 熔断后 | `exit 1`,停止监控 | 进入锁定模式,**监控继续** |
| 锁定期间写入 | 无保护(裸奔) | 自动回滚,不验证 |
| 熔断通知 | 无 | Discord #报告 + TG 双发告警 |
| 解锁方式 | 手动重置状态+重启 | 同左(不允许自动解锁)|
### 锁定模式解除流程
```bash
# 1. 用官方通道修复配置
openclaw config.patch ...
# 2. 验证配置有效
openclaw config validate
# 3. 手动解锁状态文件
jq '.locked = false | .attempts = 0' \
/root/.openclaw/backups/config/.guardian_state.json > /tmp/state_fix.json \
&& mv /tmp/state_fix.json /root/.openclaw/backups/config/.guardian_state.json
# 4. 重启守护
systemctl restart openclaw-config-guardian
```
---
## 5. Gateway 自动重载(v3 新增)
守护在以下时机自动发送 `SIGUSR1` 给 `openclaw-gateway` 进程:
- 验证通过,baseline 更新后
- 回滚成功后
**注意**:SIGUSR1 触发的是 gateway **热重载**(内存配置同步),而非重启。若需完整重启仍需手动执行:
```bash
openclaw gateway restart
```
---
## 6. Guardian 自身完整性(v3 新增)
```bash
# 查看当前 checksum
cat /root/.openclaw/backups/config/.guardian_checksum
# 手动验证
sha256sum -c /root/.openclaw/backups/config/.guardian_checksum
# 合法更新脚本后,必须刷新 checksum
sha256sum /usr/local/bin/openclaw-config-guardian > \
/root/.openclaw/backups/config/.guardian_checksum
systemctl restart openclaw-config-guardian
```
**重要**:每次更新 guardian 脚本后必须刷新 checksum,否则下次启动自检失败会拒绝运行。
---
## 7. 故障场景速查
### 守护无法启动
```bash
journalctl -u openclaw-config-guardian -n 50 --no-pager
# 常见原因: inotify-tools 未装、openclaw.json 不存在、checksum 校验失败
```
### 守护启动失败:自身校验失败
```bash
# 合法更新后刷新 checksum
sha256sum /usr/local/bin/openclaw-config-guardian > \
/root/.openclaw/backups/config/.guardian_checksum
systemctl restart openclaw-config-guardian
```
### baseline 损坏
```bash
# guardian 会自动从 baseline_history/ 恢复
# 如需手动:
ls -t /root/.openclaw/backups/config/baseline_history/
cp /root/.openclaw/backups/config/baseline_history/baseline_XXXXXX.bak \
/root/.openclaw/backups/config/baseline.bak
sha256sum /root/.openclaw/backups/config/baseline.bak > \
/root/.openclaw/backups/config/baseline.bak.sha256
```
### 误删 backups 目录
```bash
systemctl stop openclaw-config-guardian
mkdir -p /root/.openclaw/backups/config/attempts \
/root/.openclaw/backups/config/baseline_history
cp /root/.openclaw/openclaw.json /root/.openclaw/backups/config/baseline.bak
sha256sum /root/.openclaw/backups/config/baseline.bak > \
/root/.openclaw/backups/config/baseline.bak.sha256
sha256sum /usr/local/bin/openclaw-config-guardian > \
/root/.openclaw/backups/config/.guardian_checksum
echo '{"attempts":0,"last_error":null,"last_backup":null,"locked":false,"failed_at":null}' > \
/root/.openclaw/backups/config/.guardian_state.json
touch /root/.openclaw/backups/config/guardian_audit.log
systemctl start openclaw-config-guardian
```
---
## 附录 A:版本历史
| 版本 | 时间 | 内容 |
|---|---|---|
| v1.0 | 2026-03-12 | 初版(单文件监控)|
| v2.0 | 2026-03-12 | 目录级监控,覆盖原子替换 |
| v3.0 | 2026-03-19 | 锁定模式熔断、7版本baseline历史、SIGUSR1、SHA256校验、审计日志、自身完整性自检 |
FILE:references/USAGE.md
# OpenClaw 配置守护 - 使用说明
## 1. 安装与启用
### 1.1 检查依赖
```bash
# inotify-tools(监控文件事件)
apt-get update && apt-get install -y inotify-tools
# jq(状态文件读写,可选,脚本内置 fallback)
apt-get install -y jq
```
### 1.2 启用守护
```bash
# 1. 复制脚本(已自动完成)
# 脚本位置:/usr/local/bin/openclaw-config-guardian
# systemd 服务:/etc/systemd/system/openclaw-config-guardian.service
# 2. 重载 systemd
systemctl daemon-reload
# 3. 启用并启动
systemctl enable --now openclaw-config-guardian
# 4. 检查状态
systemctl status openclaw-config-guardian
```
### 1.3 初始化备份目录
守护启动会自动创建:
```
/root/.openclaw/backups/config/
├── baseline.bak (首次启动时从当前配置生成)
├── baseline.bak.sha256 (baseline 完整性校验,v3+)
├── baseline_history/ (最近 7 个有效 baseline,v3+)
├── attempts/ (变更快照,保留最近 20 个)
├── .guardian_state.json (状态文件:attempts/locked/failed_at)
├── .guardian_checksum (guardian 脚本自身 SHA256,v3+)
└── guardian_audit.log (结构化审计日志,v3+)
```
---
## 2. 日常使用
### 2.1 修改配置
**任何方式都可以**:
```bash
# 方式1:openclaw config set(推荐)
openclaw config set agents.defaults.sandbox.mode all
# 方式2:直接编辑
vim /root/.openclaw/openclaw.json
# 方式3:复制替换
cp new-config.json /root/.openclaw/openclaw.json
```
守护会自动:
1. 创建快照到 `attempts/`
2. 验证配置
3. 若成功 → 更新 `baseline.bak`,日志记录
4. 若失败 → 回滚到 `baseline.bak`,记录错误,计数+1
### 2.2 查看状态
```bash
# 查看守护状态
systemctl status openclaw-config-guardian
# 查看实时日志
journalctl -u openclaw-config-guardian -f
# 查看守护状态文件(JSON)
cat /root/.openclaw/backups/config/.guardian_state.json
```
### 2.3 查看备份
```bash
# 最新基线(有效版本)
ls -lh /root/.openclaw/backups/config/baseline.bak
# 所有变更快照(按时间倒序)
ls -lht /root/.openclaw/backups/config/attempts/
# 对比某次失败的修改
diff -u baseline.bak attempts/openclaw.json.2026-03-12-09-50-22.bak
```
---
## 3. 失败处理
### 3.1 单次失败
守护会自动回滚,日志会显示:
```
❌ 配置无效,已回滚到 baseline。
错误:agents.defaults.sandbox.mode: invalid value
尝试次数:1/3
```
**操作**:
- 检查错误描述,修正配置
- 重新修改(守护会继续处理)
### 3.2 连续 3 次失败 → 锁定模式(v3+ 行为)
守护**不停止**,进入锁定模式,日志:
```
⛔ reached max failures (3); entering LOCK MODE (monitoring continues)
```
同时向 Discord #报告 + TG 双发告警。
锁定模式行为:
- 监控继续运行
- 任何写入 openclaw.json 的操作都会被自动回滚到 baseline
- 不再尝试验证,直到人工解锁
**解锁流程**(v3.2+ 专用解锁命令):
1. 通过官方通道修复配置:
```bash
openclaw config.patch ...
# 或直接编辑后确保配置有效
```
2. 执行解锁命令(内置 validate,通过才解锁):
```bash
openclaw-config-guardian unlock
```
- 若配置仍无效:打印错误,拒绝解锁,需继续修复
- 若配置有效:自动清除 locked 状态、更新 baseline、发 SIGUSR1 给 gateway
3. 确认解锁成功:
```bash
jq '.locked' /root/.openclaw/backups/config/.guardian_state.json
# 应输出 false
systemctl status openclaw-config-guardian
```
**注意**:锁定状态持久化在磁盘(`.guardian_state.json`),guardian 崩溃重启后锁定依然有效。
---
## 4. 维护操作
### 4.1 临时禁用守护
```bash
systemctl stop openclaw-config-guardian
# 修改配置后手动重启
```
### 4.2 清理旧快照
```bash
# 脚本自动保留最近 20 个,手动清理更多
find /root/.openclaw/backups/config/attempts -type f -mtime +30 -delete
```
### 4.3 重置状态
若 `attempts` 计数需要重置(锁定已解除后正常重启即可):
```bash
# 重启守护(验证成功后自动重置 attempts=0)
systemctl restart openclaw-config-guardian
```
**解除锁定请使用专用命令**(不要手改 state.json):
```bash
openclaw-config-guardian unlock
```
### 4.4 强制恢复基线
```bash
cp /root/.openclaw/backups/config/baseline.bak /root/.openclaw/openclaw.json
# v3+ 已自动 SIGUSR1 热重载,无需手动 gateway restart
# 如需完整重启:openclaw gateway restart
```
---
## 5. 告警配置
告警通过 `openclaw message send` 内置实现,无需额外配置环境变量。熔断触发时自动向 Discord `#报告` 频道 + Telegram 双发告警。
---
## 6. 注意事项
1. **基线必须有效**:守护启动时如果 `openclaw.json` 已无效,`baseline.bak` 也会无效,后续回滚无意义。请在启动前先确保配置正确。
2. **Gateway 自动热重载**:v3+ 回滚/验证通过后会自动发 SIGUSR1 给 gateway 触发热重载,无需手动重启。若需完整重启仍可执行 `openclaw gateway restart`。
3. **监控单一文件**:仅 `openclaw.json`,其他配置变更(如技能配置)不受保护。
4. **权限要求**:守护以 root 运行,确保 `openclaw` 命令在 root PATH 中。
---
## 7. 故障排除
| 问题 | 可能原因 | 解决 |
|------|---------|------|
| 守护无法启动 | inotifywait 未安装 | `apt install inotify-tools` |
| 修改后无响应 | 监控路径错误 | 检查 `CONFIG_FILE` 变量 |
| 回滚失败 | baseline.bak 不存在 | 手动复制一个有效版本到 `backups/config/baseline.bak` |
| 日志不输出 | journald 未运行 | `systemctl status systemd-journald` |
---
文档版本:v3.0
最后更新:2026-03-19(大黑 #F_SEC,对齐 guardian v3.2)
FILE:scripts/install.sh
#!/bin/bash
# config-guardian install.sh
# Installs OpenClaw Config Guardian v3.2
# Usage: bash scripts/install.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "BASH_SOURCE[0]")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
GUARDIAN_BIN="/usr/local/bin/openclaw-config-guardian"
SERVICE_FILE="/etc/systemd/system/openclaw-config-guardian.service"
CONFIG_DIR="/root/.openclaw"
BACKUP_DIR="$CONFIG_DIR/backups/config"
echo "[install] Installing OpenClaw Config Guardian v3.2..."
# ── 1. Check dependencies ─────────────────────────────────────────────────────
for dep in inotifywait jq sha256sum; do
if ! command -v "$dep" >/dev/null 2>&1; then
echo "[install] ❌ Missing dependency: $dep"
echo "[install] Run: apt-get install -y inotify-tools jq coreutils"
exit 1
fi
done
echo "[install] ✅ Dependencies OK"
# ── 2. Check openclaw.json exists ────────────────────────────────────────────
if [[ ! -f "$CONFIG_DIR/openclaw.json" ]]; then
echo "[install] ❌ $CONFIG_DIR/openclaw.json not found. Deploy OpenClaw first."
exit 1
fi
echo "[install] ✅ openclaw.json found"
# ── 3. Copy guardian script ───────────────────────────────────────────────────
cp "$SKILL_DIR/scripts/openclaw-config-guardian.sh" "$GUARDIAN_BIN"
chmod +x "$GUARDIAN_BIN"
echo "[install] ✅ Guardian script installed: $GUARDIAN_BIN"
# ── 4. Write systemd service ──────────────────────────────────────────────────
cat > "$SERVICE_FILE" <<'SERVICE'
[Unit]
Description=OpenClaw Configuration Guardian
After=network.target
Documentation=file:///root/.openclaw/common_assets/Library/config-guardian/OPERATION.md
[Service]
Type=simple
# Root required: monitors /root/.openclaw/openclaw.json, writes rollback files,
# and sends SIGUSR1 to OpenClaw gateway. No network access. Local-only operation.
User=root
Group=root
ExecStart=/usr/local/bin/openclaw-config-guardian
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=openclaw-config-guardian
[Install]
WantedBy=multi-user.target
SERVICE
echo "[install] ✅ systemd service written: $SERVICE_FILE"
# ── 5. Create backup directories ──────────────────────────────────────────────
mkdir -p \
"$BACKUP_DIR" \
"$BACKUP_DIR/attempts" \
"$BACKUP_DIR/baseline_history"
touch "$BACKUP_DIR/guardian_audit.log"
echo "[install] ✅ Backup directories created: $BACKUP_DIR"
# ── 6. Initialize state file if missing ──────────────────────────────────────
if [[ ! -f "$BACKUP_DIR/.guardian_state.json" ]]; then
cat > "$BACKUP_DIR/.guardian_state.json" <<'JSON'
{
"attempts": 0,
"last_error": null,
"last_backup": null,
"locked": false,
"failed_at": null
}
JSON
echo "[install] ✅ State file initialized"
fi
# ── 7. Generate self-check checksum ───────────────────────────────────────────
sha256sum "$GUARDIAN_BIN" > "$BACKUP_DIR/.guardian_checksum"
echo "[install] ✅ Self-check checksum generated:"
cat "$BACKUP_DIR/.guardian_checksum"
# ── 8. Enable and start service ───────────────────────────────────────────────
systemctl daemon-reload
systemctl enable --now openclaw-config-guardian
echo "[install] ✅ Service enabled and started"
# ── 9. Verify ────────────────────────────────────────────────────────────────
sleep 2
if systemctl is-active --quiet openclaw-config-guardian; then
echo ""
echo "[install] ✅ ✅ ✅ OpenClaw Config Guardian v3.2 installed successfully!"
echo ""
echo " Status : $(systemctl is-active openclaw-config-guardian)"
echo " Logs : journalctl -u openclaw-config-guardian -f"
echo " Unlock : openclaw-config-guardian unlock"
echo " State : cat $BACKUP_DIR/.guardian_state.json"
else
echo "[install] ❌ Service failed to start. Check: journalctl -u openclaw-config-guardian -n 30"
exit 1
fi
FILE:scripts/openclaw-config-guardian.sh
#!/bin/bash
# OpenClaw Config Guardian v3.2
# Protects: /root/.openclaw/openclaw.json
# Triggers on: close_write + moved_to in /root/.openclaw/
# Behavior: snapshot -> validate -> update baseline or rollback
# Features: state-machine lock mode (disk-persistent), SIGUSR1 reload,
# 7-version baseline history, baseline SHA256, audit log,
# self-integrity (fail-closed), unlock subcommand
# Changelog:
# v3.0 2026-03-19 - lock-mode fuse, multi-baseline, SIGUSR1, sha256, audit, self-check
# v3.1 2026-03-19 - fail-closed self-check, remove create event, locked-revert SIGUSR1
# v3.2 2026-03-19 - true state-machine lock (disk-read every cycle, remove _REVERTING),
# unlock subcommand, locked path bypasses validate entirely
set -euo pipefail
SCRIPT_PATH="$(readlink -f "$0")"
CONFIG_DIR="/root/.openclaw"
CONFIG_FILE="$CONFIG_DIR/openclaw.json"
BACKUP_DIR="$CONFIG_DIR/backups/config"
BASELINE="$BACKUP_DIR/baseline.bak"
BASELINE_SHA="$BACKUP_DIR/baseline.bak.sha256"
BASELINE_HISTORY_DIR="$BACKUP_DIR/baseline_history"
ATTEMPTS_DIR="$BACKUP_DIR/attempts"
STATE_FILE="$BACKUP_DIR/.guardian_state.json"
AUDIT_LOG="$BACKUP_DIR/guardian_audit.log"
SELF_CHECKSUM="$BACKUP_DIR/.guardian_checksum"
MAX_RETRIES=3
BASELINE_KEEP=7
SNAPSHOT_KEEP=20
GATEWAY_PID_PATTERN='openclaw-gateway'
# ── Self-integrity check (fail-closed) ───────────────────────────────────────
self_check() {
if [[ ! -f "$SELF_CHECKSUM" ]]; then
echo "[GUARDIAN] ⛔ SELF-INTEGRITY FAIL: checksum file missing ($SELF_CHECKSUM), refusing to start" | logger -t openclaw-guardian
send_alert "⛔ [大黑 #F_SEC] guardian checksum 文件不存在,已拒绝启动。请运行:sha256sum $SCRIPT_PATH > $SELF_CHECKSUM"
exit 1
fi
if ! sha256sum -c "$SELF_CHECKSUM" --quiet 2>/dev/null; then
echo "[GUARDIAN] ⛔ SELF-INTEGRITY FAIL: script tampered, refusing to start" | logger -t openclaw-guardian
send_alert "⛔ [大黑 #F_SEC] guardian 自身完整性校验失败!脚本疑似被篡改,已拒绝启动。请立即检查 $SCRIPT_PATH"
exit 1
fi
}
# ── Logging ───────────────────────────────────────────────────────────────────
log() {
echo "[GUARDIAN] $*"
logger -t openclaw-guardian "$*" || true
}
audit() {
local action="$1" detail="-"
local ts writer_pid writer_cmd
ts=$(date -Iseconds)
writer_pid=$(fuser "$CONFIG_FILE" 2>/dev/null | tr -s ' ' | cut -d' ' -f1 || echo "unknown")
writer_cmd=$(ps -p "-0" -o comm= 2>/dev/null || echo "unknown")
echo "$ts | GUARDIAN_PID=$$ | ACTION=$action | writer=writer_cmd(pid=writer_pid) | $detail" >> "$AUDIT_LOG"
}
# ── Alert ─────────────────────────────────────────────────────────────────────
send_alert() {
local msg="$1"
openclaw message send --channel discord --channel-id 1483995509910667455 "$msg" 2>/dev/null || true
openclaw message send --channel telegram --target telegram:5189839048 "$msg" 2>/dev/null || true
}
# ── State helpers ─────────────────────────────────────────────────────────────
init_state() {
mkdir -p "$BACKUP_DIR" "$ATTEMPTS_DIR" "$BASELINE_HISTORY_DIR"
if [[ ! -f "$STATE_FILE" ]]; then
cat > "$STATE_FILE" <<JSON
{
"attempts": 0,
"last_error": null,
"last_backup": null,
"locked": false,
"failed_at": null
}
JSON
fi
touch "$AUDIT_LOG"
# Rotate audit log if too large (keep last 30000 lines)
local lines
lines=$(wc -l < "$AUDIT_LOG" 2>/dev/null || echo 0)
if (( lines > 50000 )); then
tail -n 30000 "$AUDIT_LOG" > "AUDIT_LOG.tmp" && mv "AUDIT_LOG.tmp" "$AUDIT_LOG"
log "📋 audit log rotated"
fi
}
read_state() { cat "$STATE_FILE"; }
# Read locked field directly from disk — single source of truth
is_locked() {
local locked
locked=$(jq -r '.locked' "$STATE_FILE" 2>/dev/null || echo "false")
[[ "$locked" == "true" ]]
}
write_state() {
local attempts="$1" last_error="$2" last_backup="$3" locked="$4" failed_at="$5"
if command -v jq >/dev/null 2>&1; then
jq -n \
--argjson attempts "$attempts" \
--arg last_error "$last_error" \
--arg last_backup "$last_backup" \
--argjson locked "$locked" \
--arg failed_at "$failed_at" \
'{attempts:$attempts,last_error:$last_error,last_backup:$last_backup,locked:$locked,failed_at:$failed_at}' \
> "$STATE_FILE"
else
cat > "$STATE_FILE" <<JSON
{
"attempts": $attempts,
"last_error": "last_error",
"last_backup": "last_backup",
"locked": $locked,
"failed_at": "failed_at"
}
JSON
fi
}
# ── Baseline helpers ──────────────────────────────────────────────────────────
ensure_baseline() {
if [[ ! -f "$CONFIG_FILE" ]]; then
log "❌ config file missing: $CONFIG_FILE"
exit 1
fi
if [[ ! -f "$BASELINE" ]]; then
cp "$CONFIG_FILE" "$BASELINE"
sha256sum "$BASELINE" > "$BASELINE_SHA"
log "✅ baseline created: $BASELINE"
audit "BASELINE_INIT" "first baseline created"
fi
}
verify_baseline_integrity() {
if [[ -f "$BASELINE_SHA" ]]; then
if ! sha256sum -c "$BASELINE_SHA" --quiet 2>/dev/null; then
log "⚠️ baseline.bak checksum mismatch — attempting restore from history"
audit "BASELINE_CORRUPT" "sha256 mismatch, trying history"
restore_baseline_from_history || {
log "❌ all history baselines unavailable — MANUAL INTERVENTION REQUIRED"
send_alert "【大黑 #F_SEC】⚠️ CRITICAL: baseline.bak 校验失败且历史版本均不可用,需要人工干预!"
return 1
}
fi
fi
return 0
}
update_baseline() {
if [[ -f "$BASELINE" ]]; then
local ts
ts=$(date +%Y%m%d_%H%M%S)
cp "$BASELINE" "$BASELINE_HISTORY_DIR/baseline_ts.bak"
( cd "$BASELINE_HISTORY_DIR" && ls -t baseline_*.bak 2>/dev/null | tail -n +$((BASELINE_KEEP + 1)) | xargs -r rm -f ) || true
log "📦 baseline archived (history kept: $BASELINE_KEEP versions)"
fi
cp "$CONFIG_FILE" "$BASELINE"
sha256sum "$BASELINE" > "$BASELINE_SHA"
}
restore_baseline_from_history() {
local hist
hist=$(ls -t "$BASELINE_HISTORY_DIR"/baseline_*.bak 2>/dev/null | head -1)
if [[ -z "$hist" ]]; then
return 1
fi
cp "$hist" "$BASELINE"
sha256sum "$BASELINE" > "$BASELINE_SHA"
log "♻️ baseline restored from history: $hist"
audit "BASELINE_RESTORED" "from $hist"
return 0
}
# ── Snapshot helpers ──────────────────────────────────────────────────────────
snapshot() {
local ts
ts=$(date +%F-%H%M%S)
local snap="$ATTEMPTS_DIR/openclaw.json.ts.bak"
cp "$CONFIG_FILE" "$snap"
echo "$snap"
}
rotate_snapshots() {
( cd "$ATTEMPTS_DIR" 2>/dev/null && ls -t *.bak 2>/dev/null | tail -n +$((SNAPSHOT_KEEP + 1)) | xargs -r rm -f ) || true
}
# ── Validate ──────────────────────────────────────────────────────────────────
validate_config() {
local out
out=$(openclaw config validate 2>&1) || true
if echo "$out" | grep -q "Config valid"; then
return 0
fi
log "❌ validate failed: $out"
return 1
}
# ── Gateway SIGUSR1 reload ────────────────────────────────────────────────────
reload_gateway() {
local gw_pid
gw_pid=$(pgrep -f "$GATEWAY_PID_PATTERN" 2>/dev/null | head -1 || true)
if [[ -n "$gw_pid" ]]; then
kill -SIGUSR1 "$gw_pid" 2>/dev/null \
&& log "📡 SIGUSR1 sent to gateway (pid=$gw_pid)" \
|| log "⚠️ failed to send SIGUSR1 to gateway (pid=$gw_pid)"
audit "GATEWAY_RELOAD" "SIGUSR1 -> pid=$gw_pid"
else
log "⚠️ gateway process not found, skipping reload signal"
audit "GATEWAY_RELOAD" "skipped: no gateway process"
fi
}
# ── Revert to baseline (shared by locked-path and failure-path) ───────────────
do_revert() {
local reason="$1"
if verify_baseline_integrity && [[ -f "$BASELINE" ]]; then
cp "$BASELINE" "$CONFIG_FILE"
reload_gateway
log "🔄 reverted to baseline ($reason)"
audit "REVERT" "reason=$reason"
return 0
else
log "❌ baseline missing or corrupt; cannot revert ($reason)"
audit "REVERT_FAIL" "reason=$reason; no valid baseline"
return 1
fi
}
# ── Success handler ───────────────────────────────────────────────────────────
after_success() {
local snap="$1"
update_baseline
write_state 0 "" "$snap" false ""
log "✅ config ok; baseline updated"
audit "VALIDATE_OK" "baseline updated; snap=$snap"
reload_gateway
}
# ── Failure handler ───────────────────────────────────────────────────────────
after_failure() {
local snap="$1" err="$2"
local state attempts
state=$(read_state)
attempts=$(echo "$state" | jq -r '.attempts' 2>/dev/null || echo 0)
attempts=$((attempts + 1))
do_revert "validate_fail attempt=$attempts/$MAX_RETRIES"
audit "VALIDATE_FAIL" "attempt=$attempts/$MAX_RETRIES; snap=$snap; err=$err"
if (( attempts >= MAX_RETRIES )); then
local failed_at
failed_at=$(date -Iseconds)
write_state "$attempts" "$err" "$snap" true "$failed_at"
log "⛔ reached max failures ($MAX_RETRIES); entering LOCK MODE (monitoring continues)"
audit "CIRCUIT_BREAK" "locked at $failed_at; err=$err"
send_alert "【大黑 #F_SEC】⛔ Config Guardian 熔断!\n\n连续 $MAX_RETRIES 次验证失败,已进入锁定模式。\n所有后续写入将自动回滚,监控继续运行。\n\n错误:$err\n时间:$failed_at\n快照:$snap\n\n解锁方式:openclaw-config-guardian unlock"
else
write_state "$attempts" "$err" "$snap" false ""
fi
return 0
}
# ── Main change handler (true state-machine: read lock from disk every cycle) ──
handle_change() {
init_state
ensure_baseline
# ── LOCKED PATH: read from disk, bypass validate entirely ──
if is_locked; then
log "⛔ [LOCKED] write detected; reverting without validate"
do_revert "locked_mode"
audit "LOCKED_REVERT" "reverted in lock mode; no validate"
return 0
fi
# ── NORMAL PATH ──
local snap
snap=$(snapshot)
rotate_snapshots
if validate_config; then
after_success "$snap"
else
after_failure "$snap" "validate_failed"
fi
}
# ── Unlock subcommand ─────────────────────────────────────────────────────────
cmd_unlock() {
echo "[GUARDIAN] unlock: validating current config before unlocking..."
if ! validate_config; then
echo "[GUARDIAN] ❌ unlock REFUSED: config is still invalid. Fix config first."
exit 1
fi
# Config valid — clear lock
local state attempts last_backup
state=$(read_state)
attempts=$(echo "$state" | jq -r '.attempts' 2>/dev/null || echo 0)
last_backup=$(echo "$state" | jq -r '.last_backup' 2>/dev/null || echo "")
write_state 0 "" "$last_backup" false ""
# Update baseline to current valid config
update_baseline
echo "[GUARDIAN] ✅ unlocked. Locked state cleared, baseline updated."
audit "UNLOCKED" "manual unlock; config validated ok"
reload_gateway
}
# ── Entry point ───────────────────────────────────────────────────────────────
main() {
# Handle subcommands
if [[ "-" == "unlock" ]]; then
init_state
cmd_unlock
exit 0
fi
self_check
init_state
ensure_baseline
log "🛡️ guardian v3.2 started; dir=$CONFIG_DIR file=$CONFIG_FILE"
log " features: state-machine lock(disk), baseline-history($BASELINE_KEEP), SIGUSR1, sha256, audit-log, self-check, unlock-cmd"
audit "GUARDIAN_START" "v3.2 pid=$$"
inotifywait -m -e close_write,moved_to --format '%e %f' "$CONFIG_DIR" 2>/dev/null | \
while read -r evt fname; do
if [[ "$fname" == "openclaw.json" ]]; then
log "🔄 change detected ($evt $fname)"
handle_change || true
fi
done
}
main "$@"