Back to skills
SkillHub ClubShip Full StackFull Stack

openclaw-backup-restore

Backup and restore OpenClaw configuration, agents, sessions, and workspace to/from a private Git repository. Use when the user wants to manually trigger a backup, migrate to a new machine, or restore from a previous state.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,097
Hot score
99
Updated
March 20, 2026
Overall rating
C0.0
Composite score
0.0
Best-practice grade
B84.8

Install command

npx @skill-hub/cli install openclaw-skills-openclaw-backup-restore

Repository

openclaw/skills

Skill path: skills/darinrowe/openclaw-backup-restore

Backup and restore OpenClaw configuration, agents, sessions, and workspace to/from a private Git repository. Use when the user wants to manually trigger a backup, migrate to a new machine, or restore from a previous state.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install openclaw-backup-restore into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding openclaw-backup-restore to shared team environments
  • Use openclaw-backup-restore for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: openclaw-backup-restore
description: Backup and restore OpenClaw configuration, agents, sessions, and workspace to/from a private Git repository. Use when the user wants to manually trigger a backup, migrate to a new machine, or restore from a previous state.
---

# OpenClaw Backup & Restore

A specialized skill for managing the lifecycle of your OpenClaw data. This skill utilizes an external Git-managed backup directory to keep your production environment clean while ensuring full recoverability.

## Strategy

1. **Isolation**: Git operations happen in a dedicated directory outside the live `.openclaw` runtime to avoid pollution.
2. **Minimalism**: Large `node_modules`, logs, and temporary files are excluded.
3. **Redundancy**: Regular backups can be scheduled via Cron.

---

## Setup

Before using this skill, you must set your private backup repository URL in `openclaw.json`. This URL is used by the scripts to push and pull data.

```bash
openclaw config set skills.entries.openclaw-backup-restore.env.OPENCLAW_BACKUP_REPO "[email protected]:your-username/your-repo.git"
```

## How to Backup

To trigger a manual backup and sync to your remote repository:
1. The agent should execute the `backup.sh` script located within this skill's `scripts/` directory.
2. The script will:
   - Read the repo URL from the OpenClaw config.
   - Sync `${HOME}/.openclaw/` to `${HOME}/openclaw-backup/` using `rsync` (respecting `.gitignore`).
   - Generate a readable commit summary from changed paths (for example workspace/config/runtime/memory).
   - Commit and push to the remote `main` branch.

**Trigger Phrases**: "Backup OpenClaw now", "Sync my data to GitHub".

---

## How to Restore

To restore your environment on a new or existing machine:
1. Ensure your SSH key is added to your Git provider (e.g., GitHub).
2. The agent should execute the `restore.sh` script located within this skill's `scripts/` directory.
3. The process involves:
   - Reading the repo URL from the OpenClaw config.
   - Cloning or pulling the latest backup from the configured repository.
   - Syncing files back to `${HOME}/.openclaw/`.
   - Reinstalling node dependencies and running `openclaw doctor --yes` to fix environment paths.
4. **Restart the Gateway**: `openclaw gateway restart`.

**Trigger Phrases**: "Restore OpenClaw from backup", "Migrate my data".

---

## Technical Details

- **Backup Directory**: `${HOME}/openclaw-backup`
- **Source Directory**: `${HOME}/.openclaw`
- **Exclusions**: Defined in the skill's `.gitignore` (includes `node_modules/`, `logs/`, `completions/`, `tmp/`, `dist/`).
- **Automatic Setup**: The `.gitignore` file is included in this skill and will be copied to `${HOME}/openclaw-backup/` during the first backup run.

---

## Recovery Checklist

If restoring to a **completely new machine**:
1. Install OpenClaw CLI first.
2. Set the `OPENCLAW_BACKUP_REPO` config value.
3. Configure SSH access for your Git provider.
4. Run the restore script provided by this skill.
5. Run `openclaw onboard` if you need to re-install the daemon service.

 

---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### _meta.json

```json
{
  "owner": "darinrowe",
  "slug": "openclaw-backup-restore",
  "displayName": "OpenClaw Backup & Restore",
  "latest": {
    "version": "1.0.3",
    "publishedAt": 1773546292889,
    "commit": "https://github.com/openclaw/skills/commit/ebe5501a89119236426e981dd069f1a2742359fb"
  },
  "history": [
    {
      "version": "1.0.2",
      "publishedAt": 1773369985150,
      "commit": "https://github.com/openclaw/skills/commit/55dac59d9390d3333c1549e8c02a204fdea208f0"
    },
    {
      "version": "1.0.0",
      "publishedAt": 1772696981216,
      "commit": "https://github.com/openclaw/skills/commit/9bec765c90ef35da0f839f03fc315442a8664333"
    }
  ]
}

```

### scripts/backup.sh

```bash
#!/usr/bin/env bash
# OpenClaw Backup Script ☺️🌸
# Purpose: Sync .openclaw configuration and workspace to a Git-managed directory and push readable history.

set -euo pipefail

join_human() {
  local items=("$@")
  local count=${#items[@]}
  if [ "$count" -eq 0 ]; then
    printf ''
  elif [ "$count" -eq 1 ]; then
    printf '%s' "${items[0]}"
  elif [ "$count" -eq 2 ]; then
    printf '%s and %s' "${items[0]}" "${items[1]}"
  else
    local last="${items[$((count-1))]}"
    unset 'items[$((count-1))]'
    printf '%s, and %s' "$(IFS=', '; echo "${items[*]}")" "$last"
  fi
}

summarize_status() {
  local status_lines="$1"
  local lowered summary count
  local -a categories=()
  local -a details=()
  local -a skill_paths=()
  local skill_names='' skill_summary=''

  lowered=$(printf '%s\n' "$status_lines" | tr '[:upper:]' '[:lower:]')

  while IFS= read -r path; do
    [ -n "$path" ] && skill_paths+=("$path")
  done < <(printf '%s\n' "$lowered" | sed -n 's#.*workspace/skills/\([^/]*\)/.*#\1#p' | awk '!seen[$0]++')

  if [ ${#skill_paths[@]} -gt 0 ]; then
    skill_names=$(join_human "${skill_paths[@]}")
    if printf '%s\n' "$lowered" | grep -q 'workspace/skills/.*/scripts/'; then
      skill_summary="skills: update ${skill_names} scripts"
    else
      skill_summary="skills: update ${skill_names}"
    fi
  fi

  if printf '%s\n' "$lowered" | grep -qE '(^|[[:space:]])(openclaw\.json|identity/|devices/|cron/|extensions/|exec-approvals\.json|update-check\.json)'; then
    categories+=("config")
  fi

  if printf '%s\n' "$lowered" | grep -qE '(^|[[:space:]])(workspace/)'; then
    categories+=("workspace")
    if printf '%s\n' "$lowered" | grep -q 'workspace/archive/'; then
      details+=("archive")
    fi
  fi

  if printf '%s\n' "$lowered" | grep -qE '(^|[[:space:]])(memory/|memory\.sqlite)'; then
    categories+=("memory")
  fi

  if printf '%s\n' "$lowered" | grep -qE '(^|[[:space:]])(agents/|telegram/|delivery-queue/|canvas/|completions/)'; then
    categories+=("runtime")
  fi

  mapfile -t categories < <(printf '%s\n' "${categories[@]:-}" | awk 'NF && !seen[$0]++')
  mapfile -t details < <(printf '%s\n' "${details[@]:-}" | awk 'NF && !seen[$0]++')

  if [ -n "$skill_summary" ]; then
    printf '%s\n' "$skill_summary"
    return
  fi

  count=${#categories[@]}
  if [ "$count" -eq 0 ]; then
    summary="backup: update openclaw state"
  elif [ "$count" -eq 1 ]; then
    case "${categories[0]}" in
      workspace)
        if [ ${#details[@]} -gt 0 ]; then
          summary="workspace: update $(join_human "${details[@]}")"
        else
          summary="workspace: update workspace files"
        fi
        ;;
      config)
        summary="config: update runtime configuration"
        ;;
      memory)
        summary="memory: refresh memory data"
        ;;
      runtime)
        summary="runtime: update sessions and channel state"
        ;;
      *)
        summary="backup: update openclaw state"
        ;;
    esac
  else
    summary="backup: update $(join_human "${categories[@]}")"
  fi

  printf '%s\n' "$summary"
}

build_commit_body() {
  local status_lines="$1"
  local max_lines=12
  local count=0
  local extra=0
  local body=''
  local line code path rest old new action display

  while IFS= read -r line; do
    [ -z "$line" ] && continue
    code=$(printf '%s' "$line" | cut -c1-2)
    rest=$(printf '%s' "$line" | cut -c4-)
    action='updated'
    display="$rest"

    case "$code" in
      '??') action='added' ;;
      'A '|' A'|'AM'|'AA') action='added' ;;
      'M '|' M'|'MM'|'RM'|'MR') action='modified' ;;
      'D '|' D'|'MD'|'DM') action='deleted' ;;
      'R '|' R'|'RR')
        old=${rest%% -> *}
        new=${rest#* -> }
        action='renamed'
        if printf '%s' "$new" | grep -q '^workspace/archive/'; then
          action='archived'
          display="$old to $new"
        else
          display="$old to $new"
        fi
        ;;
      'C '|' C')
        old=${rest%% -> *}
        new=${rest#* -> }
        action='copied'
        display="$old to $new"
        ;;
      *) action='updated' ;;
    esac

    count=$((count+1))
    if [ "$count" -le "$max_lines" ]; then
      body+="- ${action} ${display}"$'\n'
    else
      extra=$((extra+1))
    fi
  done <<< "$status_lines"

  if [ "$extra" -gt 0 ]; then
    body+="- ... and ${extra} more changes"$'\n'
  fi

  body+=$'\n'
  body+="Backup run at $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
  printf '%s' "$body"
}

REPO_URL=$(openclaw config get skills.entries.openclaw-backup-restore.env.OPENCLAW_BACKUP_REPO 2>/dev/null | tr -d '"')

if [ -z "$REPO_URL" ] || [ "$REPO_URL" = "null" ]; then
    echo "Error: OPENCLAW_BACKUP_REPO is not set in openclaw.json."
    echo 'Please set it via: openclaw config set skills.entries.openclaw-backup-restore.env.OPENCLAW_BACKUP_REPO "your-git-repo-url"'
    exit 1
fi

SOURCE="${HOME}/.openclaw/"
BACKUP_DIR="${HOME}/openclaw-backup/"
LOG_FILE="/tmp/openclaw-backup.log"

echo "[$(date)] Starting OpenClaw backup to ${REPO_URL}..." | tee -a "$LOG_FILE"

if [ ! -d "$BACKUP_DIR" ]; then
    echo "Initializing backup directory at $BACKUP_DIR..."
    mkdir -p "$BACKUP_DIR"
    cd "$BACKUP_DIR"
    git init
    git remote add origin "$REPO_URL"
else
    cd "$BACKUP_DIR"
    git remote set-url origin "$REPO_URL" || true
fi

SKILL_DIR_SHARED="${HOME}/.openclaw/skills/openclaw-backup-restore"
SKILL_DIR_WORKSPACE="${HOME}/.openclaw/workspace/skills/openclaw-backup-restore"
SKILL_DIR=""
if [ -f "${SKILL_DIR_SHARED}/.gitignore" ]; then
    SKILL_DIR="$SKILL_DIR_SHARED"
elif [ -f "${SKILL_DIR_WORKSPACE}/.gitignore" ]; then
    SKILL_DIR="$SKILL_DIR_WORKSPACE"
fi

if [ ! -f "${BACKUP_DIR}/.gitignore" ] && [ -n "$SKILL_DIR" ] && [ -f "${SKILL_DIR}/.gitignore" ]; then
    echo "Copying .gitignore from skill directory..."
    cp "${SKILL_DIR}/.gitignore" "${BACKUP_DIR}/.gitignore"
fi

rsync -av --delete \
  --exclude-from="${BACKUP_DIR}/.gitignore" \
  --exclude=".git/" \
  --exclude=".gitignore" \
  "$SOURCE" "$BACKUP_DIR"

cd "$BACKUP_DIR"
STATUS=$(git status --short)
if [ -n "$STATUS" ]; then
    COMMIT_MSG=$(summarize_status "$STATUS")
    COMMIT_BODY=$(build_commit_body "$STATUS")
    git add .
    git commit -m "$COMMIT_MSG" -m "$COMMIT_BODY"
    git push origin main
    echo "[$(date)] Changes committed and pushed successfully: $COMMIT_MSG" | tee -a "$LOG_FILE"
else
    git push origin main || true
    echo "[$(date)] No changes detected, ensuring remote is synced." | tee -a "$LOG_FILE"
fi

echo "[$(date)] Backup complete! ☺️🌸" | tee -a "$LOG_FILE"

```

### scripts/restore.sh

```bash
#!/usr/bin/env bash
# OpenClaw Restore Script ☺️🌸
# Purpose: Clones/pulls the backup from Git and restores it to the .openclaw directory.

set -e

# Fetch repository URL from OpenClaw config
REPO_URL=$(openclaw config get skills.entries.openclaw-backup-restore.env.OPENCLAW_BACKUP_REPO 2>/dev/null | tr -d '"')

if [ -z "$REPO_URL" ] || [ "$REPO_URL" == "null" ]; then
    echo "Error: OPENCLAW_BACKUP_REPO is not set."
    exit 1
fi

BACKUP_DIR="${HOME}/openclaw-backup/"
DEST_DIR="${HOME}/.openclaw/"

echo "[$(date)] Starting OpenClaw restore process..."

# 1. Ensure backup directory exists and is up to date
if [ ! -d "$BACKUP_DIR" ]; then
    echo "Cloning backup repository..."
    git clone "$REPO_URL" "$BACKUP_DIR"
else
    echo "Updating backup repository..."
    cd "$BACKUP_DIR" && git pull origin main
fi

# 2. Sync back to the production directory
# Does not delete new files in production unless explicitly managed
echo "Syncing files to $DEST_DIR..."
mkdir -p "$DEST_DIR"
rsync -av --exclude=".git/" --exclude=".gitignore" "$BACKUP_DIR" "$DEST_DIR"

# 3. Restore dependencies
echo "Restoring node dependencies..."
cd "$DEST_DIR"
# Check for package.json in typical plugin/extension locations
find . -name "package.json" -not -path "*/node_modules/*" -execdir npm install \;

# 4. Fix environment using OpenClaw doctor
echo "Running openclaw doctor to fix environment..."
openclaw doctor --yes

echo "[$(date)] Restore complete! Please restart the gateway. ☺️🌸"

```

openclaw-backup-restore | SkillHub