Skill: solidexu/git-backup-publish (version 1.0.0)

--- name: git-backup version: 1.0.0 description: Backup OpenClaw workspace to Git repository (Gitee/GitHub/GitLab). Use when: (1) setting up backup for the first time, (2) manually triggering a backup, (3) configuring auto-backup, (4) user mentions "备份", "backup", "git", "gitee", "github", or asks about preserving workspace data. --- # Git Backup Backup OpenClaw agent workspace to Git repository for data safety and migration capability. **Author**: 徐琛 ([REDACTED_SECRET]) **License**: Apache 2.0 ## ⚠️ 使用前必读 **使用此技能前，你需要准备：** | 信息 | 如何获取 | |------|----------| | **Git 平台 Token** | Gitee: https://gitee.com/profile/personal_access_tokens<br>GitHub: https://github.com/settings/tokens<br>GitLab: https://gitlab.com/-/profile/personal_access_tokens | | **Git 用户名** | 你的 Git 平台用户名 | | **Agent 名称** | 你的 agent 标识符（用于仓库命名） | **Token 权限要求：** - `projects` / `repo` - 创建和管理仓库 - `user_info` / `read:user` - 读取用户信息 ## Quick Start ### 1. 创建仓库 ```bash # Gitee curl -X POST "https://gitee.com/api/v5/user/repos" \ -H "Content-Type: application/json" \ -d '{ "access_token": "YOUR_TOKEN", "name": "openclaw-agent-YOUR_AGENT_NAME", "description": "OpenClaw Agent Backup", "private": true, "auto_init": true }' # GitHub (使用 gh cli) gh repo create openclaw-agent-YOUR_AGENT_NAME --private --description "OpenClaw Agent Backup" ``` ### 2. 执行备份 ```bash export GITEE_TOKEN="your_token_here" export GITEE_REPO="https://gitee.com/YOUR_USERNAME/openclaw-agent-YOUR_AGENT_NAME.git" export AGENT_NAME="your_agent_name" ./scripts/backup-to-gitee.sh "Backup message" ``` ### 3. 配置自动备份（可选） 在你的 `HEARTBEAT.md` 中添加： ```markdown ### 文件变更检测与备份 - 检查核心文件变更时自动备份 - 脚本: /path/to/git-backup/scripts/watch-and-backup.sh ``` ## 备份内容 | 类型 | 文件 | |------|------| | 核心文档 | `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, `MEMORY.md`, `TOOLS.md`, `HEARTBEAT.md` | | 记忆 | `memory/` 目录 | | 技能 | `skills/` 目录 | **不备份**: 数据库、任务脚本、临时文件、`.env`、密钥等 ## 脚本说明 | 脚本 | 用途 | |------|------| | `setup-gitee.sh` | 初始化配置（创建仓库、保存配置） | | `backup-to-gitee.sh` | 执行备份 | | `create-repo.sh` | 快速创建仓库工具 | | `watch-and-backup.sh` | 文件变更监控（用于心跳检测） | ## 环境变量 | 变量 | 必需 | 说明 | |------|------|------| | `GITEE_TOKEN` | 是 | Git 平台的 Personal Access Token | | `GITEE_REPO` | 是 | 仓库 URL | | `AGENT_NAME` | 是 | Agent 标识符 | | `WORKSPACE_DIR` | 否 | 工作区路径，默认 `~/.openclaw/workspace` | ## 安全提示 1. **不要将 Token 提交到公开仓库** 2. 使用环境变量或配置文件存储敏感信息 3. 定期轮换 Token 4. 使用私有仓库备份敏感数据 ## 支持的平台 - ✅ Gitee（默认） - ✅ GitHub（修改 API 端点即可） - ✅ GitLab（修改 API 端点即可） - ✅ 自建 Git 服务器

path: scripts/backup-to-gitee.sh
content: #!/bin/bash
# Git Backup Script for OpenClaw Agents
# Backs up core workspace files to Gitee repository
#
# Usage: ./backup-to-gitee.sh [commit-message]

set -e

# Configuration (can be overridden by environment variables)
WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/.openclaw/workspace}"
BACKUP_DIR="${BACKUP_DIR:-$HOME/.openclaw/backup-temp}"
GITEE_REPO="${GITEE_REPO:-}"           # e.g., https://gitee.com/username/openclaw-agent-name.git
GITEE_TOKEN="${GITEE_TOKEN:-}"         # Personal Access Token
AGENT_NAME="${AGENT_NAME:-agent}"      # For commit signature

# Files to backup (relative to workspace)
BACKUP_FILES=(
    "AGENTS.md"
    "SOUL.md"
    "IDENTITY.md"
    "USER.md"
    "MEMORY.md"
    "TOOLS.md"
    "HEARTBEAT.md"
)

# Directories to backup (relative to workspace)
BACKUP_DIRS=(
    "memory"
    "skills"
)

# Validate configuration
if [[ -z "$GITEE_REPO" ]]; then
    echo "Error: GITEE_REPO not set. Run setup-gitee.sh first or export GITEE_REPO=<repo-url>"
    exit 1
fi

if [[ -z "$GITEE_TOKEN" ]]; then
    echo "Error: GITEE_TOKEN not set. Export GITEE_TOKEN=<your-token>"
    exit 1
fi

# Prepare backup directory
rm -rf "$BACKUP_DIR"
mkdir -p "$BACKUP_DIR"

echo "=== OpenClaw Git Backup ==="
echo "Agent: $AGENT_NAME"
echo "Workspace: $WORKSPACE_DIR"
echo "Repo: $GITEE_REPO"
echo ""

# Clone or initialize repo
if [[ -d "$BACKUP_DIR/.git" ]]; then
    cd "$BACKUP_DIR"
    git pull
else
    # Clone with token authentication
    AUTH_REPO="${GITEE_REPO/https:\/\//https:\/\/oauth2:${GITEE_TOKEN}@}"
    git clone "$AUTH_REPO" "$BACKUP_DIR" 2>/dev/null || {
        # If clone fails, repo might not exist - initialize new
        mkdir -p "$BACKUP_DIR"
        cd "$BACKUP_DIR"
        git init
        git remote add origin "$AUTH_REPO"
    }
fi

cd "$BACKUP_DIR"

# Configure git
git config user.name "$AGENT_NAME"
git config user.email "${AGENT_NAME}@openclaw.local"

# Copy files
echo "Copying files..."
for file in "${BACKUP_FILES[@]}"; do
    src="$WORKSPACE_DIR/$file"
    if [[ -f "$src" ]]; then
        cp "$src" "$BACKUP_DIR/"
        echo "  ✓ $file"
    fi
done

# Copy directories
for dir in "${BACKUP_DIRS[@]}"; do
    src="$WORKSPACE_DIR/$dir"
    if [[ -d "$src" ]]; then
        rm -rf "$BACKUP_DIR/$dir"
        cp -r "$src" "$BACKUP_DIR/"
        echo "  ✓ $dir/"
    fi
done

# Create .gitignore for backup
cat > "$BACKUP_DIR/.gitignore" << 'EOF'
# Secrets and sensitive files
.env
*.key
*.pem
*_token*
*_secret*
.secrets/

# Node modules
node_modules/

# Temporary files
*.tmp
*.log
*.bak
EOF

# Check for changes
if [[ -z $(git status --porcelain) ]]; then
    echo ""
    echo "No changes to backup."
    exit 0
fi

# Commit message
COMMIT_MSG="${1:-Backup: $(date '+%Y-%m-%d %H:%M:%S')}"

# Stage and commit
git add -A
git commit -m "$COMMIT_MSG"

# Push
git push -u origin main 2>/dev/null || git push -u origin master 2>/dev/null || {
    echo "Push failed, trying force..."
    git push -f origin main 2>/dev/null || git push -f origin master
}

echo ""
echo "✅ Backup complete: $COMMIT_MSG"
echo "   Files: $(git diff-tree --no-commit-id --name-only -r HEAD | wc -l) changed"

path: scripts/create-repo.sh
content: #!/bin/bash
# Quick repo creation for Gitee
# Usage: ./create-repo.sh <repo-name> [description]

REPO_NAME="${1:-}"
DESCRIPTION="${2:-OpenClaw Agent Backup}"
GITEE_TOKEN="${GITEE_TOKEN:-}"

if [[ -z "$REPO_NAME" ]]; then
    echo "Usage: ./create-repo.sh <repo-name> [description]"
    exit 1
fi

if [[ -z "$GITEE_TOKEN" ]]; then
    echo "Error: GITEE_TOKEN not set"
    exit 1
fi

curl -s -X POST "https://gitee.com/api/v5/user/repos" \
    -H "Content-Type: application/json" \
    -d "{
        \"access_token\": \"$GITEE_TOKEN\",
        \"name\": \"$REPO_NAME\",
        \"description\": \"$DESCRIPTION\",
        \"private\": true,
        \"has_issues\": false,
        \"has_wiki\": false,
        \"auto_init\": true
    }" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Repo: {d.get(\"html_url\", \"ERROR\")}') if 'html_url' in d else print(f'Error: {d}')"

path: scripts/setup-gitee.sh
content: #!/bin/bash
# Setup Gitee Repository for OpenClaw Agent Backup
#
# Usage: ./setup-gitee.sh <agent-name> [gitee-username]
#
# This script:
# 1. Creates a new Gitee repository (if token has projects permission)
# 2. Configures local git for backup
# 3. Saves configuration for backup script

set -e

AGENT_NAME="${1:-}"
GITEE_USERNAME="${2:-}"
GITEE_TOKEN="${GITEE_TOKEN:-}"

# Validate inputs
if [[ -z "$AGENT_NAME" ]]; then
    echo "Usage: ./setup-gitee.sh <agent-name> [gitee-username]"
    echo ""
    echo "Example: ./setup-gitee.sh elder myusername"
    echo ""
    echo "Environment variables:"
    echo "  GITEE_TOKEN - Your Gitee Personal Access Token (required)"
    exit 1
fi

if [[ -z "$GITEE_TOKEN" ]]; then
    echo "Error: GITEE_TOKEN not set."
    echo ""
    echo "Create a token at: https://gitee.com/profile/personal_access_tokens"
    echo "Required scopes: projects (for repo creation), user_info"
    echo ""
    echo "Then export: export GITEE_TOKEN=your_token_here"
    exit 1
fi

REPO_NAME="openclaw-${AGENT_NAME}"
WORKSPACE_DIR="$HOME/.openclaw/workspace"
CONFIG_FILE="$WORKSPACE_DIR/.gitee-backup"

echo "=== Gitee Backup Setup ==="
echo "Agent: $AGENT_NAME"
echo "Repo: $REPO_NAME"
echo ""

# Get username from API if not provided
if [[ -z "$GITEE_USERNAME" ]]; then
    echo "Fetching Gitee username..."
    GITEE_USERNAME=$(curl -s "https://gitee.com/api/v5/user?access_token=$GITEE_TOKEN" | grep -o '"login":"[^"]*"' | cut -d'"' -f4)
    if [[ -z "$GITEE_USERNAME" ]]; then
        echo "Error: Could not fetch username. Please provide it manually."
        exit 1
    fi
    echo "Username: $GITEE_USERNAME"
fi

REPO_URL="https://gitee.com/${GITEE_USERNAME}/${REPO_NAME}.git"

# Try to create repository
echo ""
echo "Creating repository: $REPO_NAME"
CREATE_RESULT=$(curl -s -X POST "https://gitee.com/api/v5/user/repos" \
    -H "Content-Type: application/json" \
    -d "{
        \"access_token\": \"$GITEE_TOKEN\",
        \"name\": \"$REPO_NAME\",
        \"description\": \"OpenClaw Agent $AGENT_NAME - Workspace Backup\",
        \"private\": true,
        \"has_issues\": false,
        \"has_wiki\": false,
        \"auto_init\": true
    }" 2>&1)

if echo "$CREATE_RESULT" | grep -q '"id"'; then
    echo "✅ Repository created successfully"
elif echo "$CREATE_RESULT" | grep -q "already exists"; then
    echo "ℹ️  Repository already exists, continuing..."
else
    echo "⚠️  Could not create repository (may need manual creation)"
    echo "   Response: $CREATE_RESULT"
fi

# Save configuration
echo ""
echo "Saving configuration..."
cat > "$CONFIG_FILE" << EOF
# Gitee Backup Configuration
# Generated: $(date)
AGENT_NAME=$AGENT_NAME
GITEE_USERNAME=$GITEE_USERNAME
GITEE_REPO=$REPO_URL
EOF

echo "✅ Configuration saved to: $CONFIG_FILE"
echo ""
echo "=== Setup Complete ==="
echo ""
echo "Repo URL: $REPO_URL"
echo ""
echo "Next steps:"
echo "1. Test backup: GITEE_TOKEN=xxx ./scripts/backup-to-gitee.sh"
echo "2. Add to cron for auto-backup:"
echo "   crontab -e"
echo "   # Add: 0 */4 * * * GITEE_TOKEN=xxx /path/to/backup-to-gitee.sh >> /tmp/backup.log 2>&1"
echo ""
echo "Or set GITEE_TOKEN in your environment for persistent use."

path: scripts/watch-and-backup.sh
content: #!/bin/bash
# Watch for file changes and trigger backup
# Usage: ./watch-and-backup.sh [--daemon]
#
# Requirements: inotify-tools (apt install inotify-tools)
#
# If inotify-tools not available, falls back to polling mode

set -e

WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/.openclaw/workspace}"
BACKUP_SCRIPT="$(dirname "$0")/backup-to-gitee.sh"
PID_FILE="/tmp/openclaw-backup-watcher.pid"
LOG_FILE="/tmp/openclaw-backup-watcher.log"

# Core files to watch (relative to workspace)
WATCH_FILES=(
    "AGENTS.md"
    "SOUL.md"
    "IDENTITY.md"
    "USER.md"
    "MEMORY.md"
    "TOOLS.md"
    "HEARTBEAT.md"
)

# Directories to watch
WATCH_DIRS=(
    "memory"
    "skills"
)

# Check if already running
if [[ -f "$PID_FILE" ]]; then
    PID=$(cat "$PID_FILE")
    if kill -0 "$PID" 2>/dev/null; then
        echo "Watcher already running (PID: $PID)"
        echo "Use: kill $PID to stop"
        exit 1
    fi
fi

# Check for inotify-tools
if command -v inotifywait &> /dev/null; then
    echo "Using inotify for real-time monitoring..."
    MODE="inotify"
else
    echo "inotify-tools not found, using polling mode (60s interval)..."
    echo "Install with: apt install inotify-tools"
    MODE="poll"
fi

# Build watch list
WATCH_PATHS=()
for file in "${WATCH_FILES[@]}"; do
    path="$WORKSPACE_DIR/$file"
    [[ -f "$path" ]] && WATCH_PATHS+=("$path")
done
for dir in "${WATCH_DIRS[@]}"; do
    path="$WORKSPACE_DIR/$dir"
    [[ -d "$path" ]] && WATCH_PATHS+=("$path")
done

echo "Watching ${#WATCH_PATHS[@]} paths:"
for p in "${WATCH_PATHS[@]}"; do
    echo "  - $(basename "$p")"
done

# Function to trigger backup
do_backup() {
    local reason="$1"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Change detected: $reason" >> "$LOG_FILE"
    echo "Triggering backup..."

    # Run backup script
    if [[ -n "$GITEE_TOKEN" && -n "$GITEE_REPO" ]]; then
        "$BACKUP_SCRIPT" "Auto backup: $reason" >> "$LOG_FILE" 2>&1
    else
        echo "Error: GITEE_TOKEN or GITEE_REPO not set" >> "$LOG_FILE"
    fi
}

# Daemon mode
if [[ "$1" == "--daemon" ]]; then
    echo "Starting in daemon mode..."
    echo $$ > "$PID_FILE"

    if [[ "$MODE" == "inotify" ]]; then
        # inotify mode
        inotifywait -m -r -e modify,create,delete,move "${WATCH_PATHS[@]}" 2>/dev/null | while read path action file; do
            # Debounce: wait 2 seconds for more changes
            sleep 2
            do_backup "$action on $file"
        done
    else
        # Polling mode
        while true; do
            sleep 60
            # Check for changes using git
            cd "$WORKSPACE_DIR"
            if [[ -n $(git status --porcelain 2>/dev/null) ]]; then
                do_backup "polling detected changes"
            fi
        done
    fi
else
    # One-shot check mode (for heartbeat)
    echo "Checking for changes..."

    # Use git to check for changes if this is a git repo
    cd "$WORKSPACE_DIR"

    # Create a simple state file to track changes
    STATE_FILE="$WORKSPACE_DIR/.backup-state"
    CURRENT_STATE=$(find "${WATCH_PATHS[@]}" -type f -exec md5sum {} \; 2>/dev/null | sort | md5sum | cut -d' ' -f1)

    if [[ -f "$STATE_FILE" ]]; then
        PREVIOUS_STATE=$(cat "$STATE_FILE")
        if [[ "$CURRENT_STATE" != "$PREVIOUS_STATE" ]]; then
            echo "$CURRENT_STATE" > "$STATE_FILE"
            do_backup "file state changed"
        else
            echo "No changes detected."
        fi
    else
        echo "$CURRENT_STATE" > "$STATE_FILE"
        echo "Initial state recorded."
    fi
fi

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