Skill: halfmoon82/gateway-auto-rollback (version 1.1.0)

--- name: gateway-auto-rollback description: | Automatic configuration rollback mechanism for OpenClaw Gateway. Provides three-layer protection: pre-modification backup, post-modification validation, and automatic rollback on failure. Includes a file watcher daemon, JSON validation, Gateway health checks, and SHA256 content-addressed backups. Use when modifying openclaw.json or other critical config files to prevent accidental breakage and ensure zero-downtime configuration changes. --- # Gateway Auto-Rollback **Three-layer configuration protection for OpenClaw Gateway** — never break your config again. ## What It Does Automatically protects your OpenClaw configuration files with: 1. **Pre-modification backup** — SHA256 content-addressed snapshots before any change 2. **Post-modification validation** — JSON syntax check + Gateway health probe 3. **Automatic rollback** — instant restore if validation fails ## When to Use - Before modifying `openclaw.json`, `exec-approvals.json`, or `skills.json` - When running automated config changes (cron jobs, scripts) - As a background safety net during development - When you want peace of mind that a bad config won't take down your agent ## Quick Start ### One-shot check (before manual edits) ```bash python3 gateway-auto-rollback.py ``` This initializes the backup directory, validates current config, and logs status. ### Watch mode (background daemon) ```bash python3 gateway-auto-rollback.py --watch & ``` Monitors critical config files every 3 minutes. Auto-exits after 3 consecutive healthy checks (config is stable). ## How It Works ``` Before Modification During After Modification ↓ ↓ ↓ Backup + Hash ───→ Execute Change ───→ JSON Validate + Health Check │ │ └──────────────────────────────────────→ Auto-rollback on failure ``` ### Protected Files | File | Description | |------|-------------| | `openclaw.json` | Main Gateway configuration | | `exec-approvals.json` | Command execution approvals | | `skills.json` | Skills registry | ### Backup Naming Backups are stored in `~/.openclaw/backup/` with content-addressed names: ``` openclaw.json.20260301_053612.a1b2c3d4.bak ↑ timestamp ↑ SHA256 prefix (dedup) ``` ## API Reference ### Python Functions ```python from gateway_auto_rollback import ( pre_modification_check, # Call before modifying config post_modification_verify, # Call after modifying config create_backup, # Manual backup creation validate_json, # JSON syntax validation check_gateway_health, # Gateway health probe rollback_to_backup, # Manual rollback watch_config_files, # Start watch daemon ) ``` ### Pre-modification flow ```python from pathlib import Path config = Path.home() / ".openclaw" / "openclaw.json" # Returns backup path on success, False on failure backup = pre_modification_check(config) # ... make your changes ... # Validates and auto-rolls back if needed success = post_modification_verify(config, backup) ``` ### Watch mode details The watcher: - Polls every **3 minutes** (gives Gateway time to restart) - Detects changes via SHA256 hash comparison - Auto-creates backup when change detected - Validates JSON + health check after each change - **Auto-exits** after 3 consecutive healthy checks (config stabilized) - Logs all events to `~/.openclaw/logs/config-modification.log` ## Integration with Cron Set up periodic health checks: ```bash # Cron job example: check every hour 0 * * * * python3 /path/to/gateway-auto-rollback.py ``` Or use OpenClaw's built-in cron: ```json { "name": "Gateway-Auto-Rollback", "schedule": { "kind": "cron", "expr": "0 */6 * * *", "tz": "Asia/Shanghai" }, "payload": { "kind": "agentTurn", "message": "Run gateway health check. If unhealthy, rollback to latest backup." }, "sessionTarget": "isolated" } ``` ## Manual Rollback If you need to manually restore a config: ```bash # List available backups (newest first) ls -lt ~/.openclaw/backup/ | head -10 # Restore a specific backup cp ~/.openclaw/backup/openclaw.json.20260301_053612.a1b2c3d4.bak \ ~/.openclaw/openclaw.json # Restart Gateway openclaw gateway restart # Verify curl -s http://127.0.0.1:18789/api/health ``` ## Testing Run the included test suite to verify the mechanism works: ```bash bash test-rollback-mechanism.sh ``` Tests cover: - Backup directory existence - JSON validation - SHA256 hash computation - Backup creation and restore - Watch daemon status - Log file integrity - Script permissions ## Logs All events are logged to `~/.openclaw/logs/config-modification.log`: ``` [2026-03-01 05:37:00] INFO: ✅ 备份创建: openclaw.json.20260301_053612.a1b2c3d4.bak [2026-03-01 05:37:01] INFO: ✅ 修改验证通过 [2026-03-01 05:40:00] WARN: ⚠️ 检测到修改: openclaw.json [2026-03-01 05:40:01] ERROR: JSON 验证失败 — 触发回滚 ``` ## Requirements - Python 3.8+ - OpenClaw Gateway running (for health checks) - No additional pip packages needed (stdlib only) ## File Structure ``` gateway-auto-rollback/ ├── SKILL.md # This file ├── _meta.json # ClawHub metadata ├── gateway-auto-rollback.py # Main script (backup/validate/rollback/watch) └── test-rollback-mechanism.sh # Test suite ```

path: _meta.json
content: slug: gateway-auto-rollback
version: 1.1.0

path: gateway-auto-rollback.py
content: #!/usr/bin/env python3
"""
配置修改前置钩子 - 自动触发回滚机制
- 监听所有 .json 修改
- 自动备份 + 验证 + 回滚
"""

import os
import json
import hashlib
import shutil
import subprocess
from datetime import datetime
from pathlib import Path

CONFIG_DIR = Path.home() / ".openclaw"
BACKUP_DIR = CONFIG_DIR / "backup"
LOG_FILE = CONFIG_DIR / "logs" / "config-modification.log"
CRITICAL_FILES = {
    "openclaw.json",
    "exec-approvals.json",
    "skills.json"
}

def ensure_dirs():
    """确保必要目录存在"""
    BACKUP_DIR.mkdir(exist_ok=True)
    LOG_FILE.parent.mkdir(exist_ok=True)

def log_event(level, message):
    """记录事件到日志"""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] {level}: {message}\n"
    
    with open(LOG_FILE, "a") as f:
        f.write(log_entry)
    
    print(log_entry.strip())

def compute_hash(file_path):
    """计算文件 SHA256 哈希"""
    sha256_hash = hashlib.sha256()
    with open(file_path, "rb") as f:
        for byte_block in iter(lambda: f.read(4096), b""):
            sha256_hash.update(byte_block)
    return sha256_hash.hexdigest()

def validate_json(file_path):
    """验证 JSON 文件完整性"""
    try:
        with open(file_path) as f:
            json.load(f)
        return True
    except json.JSONDecodeError as e:
        log_event("ERROR", f"JSON 验证失败: {file_path} - {e}")
        return False

def create_backup(file_path):
    """创建备份（带哈希前缀，防止重复备份）"""
    ensure_dirs()
    
    file_hash = compute_hash(file_path)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_name = f"{file_path.name}.{timestamp}.{file_hash[:8]}.bak"
    backup_path = BACKUP_DIR / backup_name
    
    try:
        shutil.copy2(file_path, backup_path)
        log_event("INFO", f"✅ 备份创建: {backup_name}")
        return backup_path
    except Exception as e:
        log_event("ERROR", f"备份失败: {e}")
        return None

def check_gateway_health():
    """检查 Gateway 健康状态"""
    try:
        result = subprocess.run(
            ["curl", "-s", "http://127.0.0.1:18789/api/health"],
            timeout=3,
            capture_output=True
        )
        return result.returncode == 0
    except:
        return False

def rollback_to_backup(backup_path):
    """回滚到备份"""
    config_file = CONFIG_DIR / backup_path.name.split(".")[0]
    
    try:
        shutil.copy2(backup_path, config_file)
        log_event("INFO", f"✅ 已回滚到: {backup_path.name}")
        return True
    except Exception as e:
        log_event("ERROR", f"回滚失败: {e}")
        return False

def pre_modification_check(file_path):
    """修改前的完整检查流程"""
    log_event("INFO", f"🔍 开始前置检查: {file_path.name}")
    
    # 1. 验证源文件
    if not validate_json(file_path):
        log_event("ERROR", f"源文件 JSON 无效: {file_path}")
        return False
    
    # 2. 创建备份
    backup = create_backup(file_path)
    if not backup:
        log_event("ERROR", "备份创建失败，中止修改")
        return False
    
    # 3. 记录修改前的 Gateway 状态
    gateway_ok = check_gateway_health()
    log_event("INFO", f"修改前 Gateway 状态: {'健康' if gateway_ok else '异常'}")
    
    return backup

def post_modification_verify(file_path, backup_path):
    """修改后的验证流程"""
    log_event("INFO", f"🔍 开始后置验证: {file_path.name}")
    
    # 1. JSON 验证
    if not validate_json(file_path):
        log_event("ERROR", f"修改后 JSON 无效，触发回滚")
        rollback_to_backup(backup_path)
        return False
    
    # 2. Gateway 健康检查
    if not check_gateway_health():
        log_event("ERROR", "Gateway 修改后不健康，触发回滚")
        rollback_to_backup(backup_path)
        return False
    
    log_event("INFO", "✅ 修改验证通过")
    return True

def watch_config_files():
    """监视配置文件修改（轮询方案）
    
    轮询间隔: 3 分钟（给 Gateway 充足重启时间）
    退出条件: 连续 3 次健康检查通过 → 自动关闭回滚监视
    """
    ensure_dirs()
    
    file_hashes = {}
    consecutive_healthy = 0  # 连续健康计数器
    HEALTHY_THRESHOLD = 3    # 连续 3 次健康就退出
    POLL_INTERVAL = 180      # 3 分钟轮询间隔
    
    # 初始化文件哈希
    for file in CRITICAL_FILES:
        file_path = CONFIG_DIR / file
        if file_path.exists():
            file_hashes[str(file_path)] = compute_hash(file_path)
    
    log_event("INFO", f"开始监视 {len(file_hashes)} 个关键配置文件 (间隔 {POLL_INTERVAL}s, 连续 {HEALTHY_THRESHOLD} 次健康后退出)")
    
    # 长期监视循环
    import time
    while True:
        try:
            change_detected = False
            
            for file_path_str, old_hash in list(file_hashes.items()):
                file_path = Path(file_path_str)
                
                if not file_path.exists():
                    continue
                
                new_hash = compute_hash(file_path)
                
                if new_hash != old_hash:
                    log_event("WARN", f"⚠️ 检测到修改: {file_path.name}")
                    change_detected = True
                    consecutive_healthy = 0  # 有变更，重置计数
                    
                    # 创建备份并验证
                    backup = create_backup(file_path)
                    
                    if not post_modification_verify(file_path, backup):
                        log_event("ERROR", "修改验证失败，已回滚")
                    
                    # 更新哈希记录
                    file_hashes[file_path_str] = compute_hash(file_path)
            
            # 无变更时检查 Gateway 健康状态
            if not change_detected:
                gateway_ok = check_gateway_health()
                if gateway_ok:
                    consecutive_healthy += 1
                    log_event("INFO", f"✅ Gateway 健康 ({consecutive_healthy}/{HEALTHY_THRESHOLD})")
                else:
                    consecutive_healthy = 0
                    log_event("WARN", f"⚠️ Gateway 不健康，重置计数")
                
                # 连续 N 次健康 → 安全退出
                if consecutive_healthy >= HEALTHY_THRESHOLD:
                    log_event("INFO", f"🎉 连续 {HEALTHY_THRESHOLD} 次健康检查通过，配置稳定，自动关闭监视")
                    break
            
            time.sleep(POLL_INTERVAL)
        
        except KeyboardInterrupt:
            log_event("INFO", "监视已停止 (用户中断)")
            break
        except Exception as e:
            log_event("ERROR", f"监视循环异常: {e}")
            consecutive_healthy = 0  # 异常也重置计数
            time.sleep(POLL_INTERVAL)

if __name__ == "__main__":
    import sys
    
    if len(sys.argv) > 1 and sys.argv[1] == "--watch":
        # 后台监视模式
        watch_config_files()
    else:
        # 单次检查模式
        ensure_dirs()
        log_event("INFO", "配置修改钩子已初始化")


path: test-rollback-mechanism.sh
content: #!/bin/bash
# 测试回滚机制的完整性

set -e

CONFIG_FILE="$HOME/.openclaw/openclaw.json"
BACKUP_DIR="$HOME/.openclaw/backup"
TEST_DIR="/tmp/rollback-test"

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log_test() {
    echo -e "${YELLOW}[TEST]${NC} $1"
}

log_pass() {
    echo -e "${GREEN}✅ PASS${NC} $1"
}

log_fail() {
    echo -e "${RED}❌ FAIL${NC} $1"
    exit 1
}

echo "======================================"
echo "回滚机制完整性测试"
echo "======================================"

# 测试 1: 检查备份目录
log_test "检查备份目录..."
if [ -d "$BACKUP_DIR" ]; then
    log_pass "备份目录存在"
else
    log_fail "备份目录不存在"
fi

# 测试 2: 检查配置文件
log_test "检查配置文件..."
if [ -f "$CONFIG_FILE" ]; then
    log_pass "配置文件存在"
else
    log_fail "配置文件不存在"
fi

# 测试 3: JSON 验证
log_test "验证 JSON 语法..."
if python3 -c "import json; json.load(open('$CONFIG_FILE'))" 2>/dev/null; then
    log_pass "JSON 语法有效"
else
    log_fail "JSON 语法无效"
fi

# 测试 4: 哈希计算
log_test "测试哈希计算..."
HASH=$(python3 -c "
import hashlib
with open('$CONFIG_FILE', 'rb') as f:
    print(hashlib.sha256(f.read()).hexdigest()[:8])
")
if [ ${#HASH} -eq 8 ]; then
    log_pass "哈希计算成功: $HASH"
else
    log_fail "哈希计算失败"
fi

# 测试 5: Gateway 健康检查
log_test "检查 Gateway 健康状态..."
if timeout 3 curl -s http://127.0.0.1:18789/api/health > /dev/null 2>&1; then
    log_pass "Gateway 健康"
else
    echo -e "${YELLOW}⚠️ WARNING${NC} Gateway 可能不运行，测试跳过"
fi

# 测试 6: 备份创建测试
log_test "测试备份创建..."
mkdir -p "$TEST_DIR"
cp "$CONFIG_FILE" "$TEST_DIR/test-config.json"

# 计算测试文件哈希
TEST_HASH=$(python3 -c "
import hashlib
with open('$TEST_DIR/test-config.json', 'rb') as f:
    print(hashlib.sha256(f.read()).hexdigest()[:8])
")

# 模拟备份命名
TEST_BACKUP="$TEST_DIR/test-config.json.20260301_053612.$TEST_HASH.bak"
cp "$TEST_DIR/test-config.json" "$TEST_BACKUP"

if [ -f "$TEST_BACKUP" ]; then
    log_pass "备份创建成功: $(basename $TEST_BACKUP)"
else
    log_fail "备份创建失败"
fi

# 测试 7: 回滚操作
log_test "测试回滚操作..."
cp "$TEST_BACKUP" "$TEST_DIR/test-restore.json"

# 验证恢复文件
if python3 -c "import json; json.load(open('$TEST_DIR/test-restore.json'))" 2>/dev/null; then
    log_pass "回滚文件验证通过"
else
    log_fail "回滚文件验证失败"
fi

# 测试 8: 监视进程检查
log_test "检查监视守护进程..."
if pgrep -f "gateway-auto-rollback.py.*--watch" > /dev/null 2>&1; then
    PID=$(pgrep -f "gateway-auto-rollback.py.*--watch")
    log_pass "监视进程运行中 (PID: $PID)"
elif pgrep -f "config-modification-hook.py.*--watch" > /dev/null 2>&1; then
    PID=$(pgrep -f "config-modification-hook.py.*--watch")
    log_pass "监视进程运行中 (PID: $PID)"
else
    echo -e "${YELLOW}⚠️ WARNING${NC} 监视进程未运行"
fi

# 测试 9: 日志文件检查
log_test "检查日志文件..."
LOG_FILE="$HOME/.openclaw/logs/config-modification.log"
if [ -f "$LOG_FILE" ]; then
    LOG_SIZE=$(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE")
    log_pass "日志文件存在 (大小: $LOG_SIZE 字节)"
else
    echo -e "${YELLOW}⚠️ WARNING${NC} 日志文件不存在（首次运行是正常的）"
fi

# 测试 10: 脚本权限检查
log_test "检查脚本权限..."
SKILL_DIR="$(cd "$(dirname "$0")" && pwd)"
SCRIPT_FILE="$SKILL_DIR/gateway-auto-rollback.py"
if [ -x "$SCRIPT_FILE" ]; then
    log_pass "脚本可执行: $SCRIPT_FILE"
else
    # Fallback to legacy path
    SCRIPT_FILE="$HOME/.openclaw/workspace/.lib/config-modification-hook.py"
    if [ -x "$SCRIPT_FILE" ]; then
        log_pass "脚本可执行 (legacy path)"
    else
        log_fail "脚本不可执行"
    fi
fi

# 清理测试文件
log_test "清理测试文件..."
rm -rf "$TEST_DIR"
log_pass "清理完成"

# 总结
echo ""
echo "======================================"
echo -e "${GREEN}✅ 所有测试完成${NC}"
echo "======================================"
echo ""
echo "📝 建议："
echo "1. 监视守护进程应 24/7 运行"
echo "2. 定期检查 ~/.openclaw/backup/ 目录大小"
echo "3. 配置 cron 任务进行自动备份和清理"
echo "4. 每月审查一次日志文件"
echo ""


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)