Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions nemoclaw/docs/workspace/backup-restore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
title:
page: "Back Up and Restore Workspace Files"
nav: "Back Up & Restore"
description: "How to back up and restore OpenClaw workspace files before destructive operations."
keywords: ["nemoclaw backup", "nemoclaw restore", "workspace backup", "openshell sandbox download upload"]
topics: ["generative_ai", "ai_agents"]
tags: ["openclaw", "openshell", "sandboxing", "workspace", "backup", "nemoclaw"]
content:
type: how_to
difficulty: technical_beginner
audience: ["developer", "engineer"]
status: published
---

<!--
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
-->

# Back Up and Restore Workspace Files

Workspace files define your agent's personality, memory, and user context.
They persist across sandbox restarts but are **permanently deleted** when you run `nemoclaw <name> destroy`.

This guide covers manual backup with CLI commands and an automated script.

## Prerequisites

- A running NemoClaw sandbox (for backup) or a freshly created sandbox (for restore).
- The OpenShell CLI on your `PATH`.
- The sandbox name (shown by `nemoclaw list`).

## When to Back Up

- Before running `nemoclaw <name> destroy`.
- Before major NemoClaw version upgrades.
- Periodically, if you have invested time customizing your agent.

## Manual Backup

Use `openshell sandbox download` to copy files from the sandbox to your host.

```console
$ SANDBOX=my-assistant
$ BACKUP_DIR=~/.nemoclaw/backups/$(date +%Y%m%d-%H%M%S)
$ mkdir -p "$BACKUP_DIR"

$ openshell sandbox download "$SANDBOX" /sandbox/.openclaw/workspace/SOUL.md "$BACKUP_DIR/"
$ openshell sandbox download "$SANDBOX" /sandbox/.openclaw/workspace/USER.md "$BACKUP_DIR/"
$ openshell sandbox download "$SANDBOX" /sandbox/.openclaw/workspace/IDENTITY.md "$BACKUP_DIR/"
$ openshell sandbox download "$SANDBOX" /sandbox/.openclaw/workspace/AGENTS.md "$BACKUP_DIR/"
$ openshell sandbox download "$SANDBOX" /sandbox/.openclaw/workspace/MEMORY.md "$BACKUP_DIR/"
$ openshell sandbox download "$SANDBOX" /sandbox/.openclaw/workspace/memory/ "$BACKUP_DIR/memory/"
```

## Manual Restore

Use `openshell sandbox upload` to push files back into a sandbox.

```console
$ SANDBOX=my-assistant
$ BACKUP_DIR=~/.nemoclaw/backups/20260320-120000 # pick a timestamp

$ openshell sandbox upload "$SANDBOX" "$BACKUP_DIR/SOUL.md" /sandbox/.openclaw/workspace/
$ openshell sandbox upload "$SANDBOX" "$BACKUP_DIR/USER.md" /sandbox/.openclaw/workspace/
$ openshell sandbox upload "$SANDBOX" "$BACKUP_DIR/IDENTITY.md" /sandbox/.openclaw/workspace/
$ openshell sandbox upload "$SANDBOX" "$BACKUP_DIR/AGENTS.md" /sandbox/.openclaw/workspace/
$ openshell sandbox upload "$SANDBOX" "$BACKUP_DIR/MEMORY.md" /sandbox/.openclaw/workspace/
$ openshell sandbox upload "$SANDBOX" "$BACKUP_DIR/memory/" /sandbox/.openclaw/workspace/memory/
```

## Using the Backup Script

The repository includes a convenience script at `scripts/backup-workspace.sh`.

### Backup

```console
$ ./scripts/backup-workspace.sh backup my-assistant
Backing up workspace from sandbox 'my-assistant'...
Backup saved to /home/user/.nemoclaw/backups/20260320-120000/ (6 items)
```

### Restore

Restore from the most recent backup:

```console
$ ./scripts/backup-workspace.sh restore my-assistant
```

Restore from a specific timestamp:

```console
$ ./scripts/backup-workspace.sh restore my-assistant 20260320-120000
```

## Upgrade workflow (image rebuild / onboard)

Before you change the sandbox image (for example after editing `Dockerfile`) or run `nemoclaw onboard`, back up. Optional files such as `MEMORY.md` or `memory/` may not exist yet; the backup script skips them with a warning.

Use the upgrade helper to run workspace backup, optionally snapshot all of `/sandbox/.openclaw-data/`, and optionally run `nemoclaw onboard`:

```console
$ ./scripts/upgrade-sandbox.sh my-assistant
$ ./scripts/upgrade-sandbox.sh --full-data my-assistant
$ ./scripts/upgrade-sandbox.sh --run-onboard --yes my-assistant
```

- `--full-data` downloads the full OpenClaw state tree (larger; includes sessions and agents beyond the Markdown workspace).
- `--run-onboard` runs `nemoclaw onboard` from the repo root (or `NEMOCLAW_REPO_ROOT`). After a successful onboard, the script **automatically** runs `backup-workspace.sh restore` for the backup timestamp created at the start (so you do not need to run restore by hand). Use `--no-restore` to skip that step if you want a fresh workspace after the rebuild.
- Without `--run-onboard`, the script only backs up and prints suggested next commands.

See `scripts/upgrade-sandbox.sh --help` for options and environment variables.

## Verifying a Backup

List backed-up files to confirm completeness:

```console
$ ls ~/.nemoclaw/backups/20260320-120000/
AGENTS.md
IDENTITY.md
MEMORY.md
SOUL.md
USER.md
memory/
```

## Inspecting Files Inside the Sandbox

Connect to the sandbox to list or view workspace files directly:

```console
$ openshell sandbox connect my-assistant
$ ls -la /sandbox/.openclaw/workspace/
```

## Next Steps

- [Workspace Files overview](workspace-files.md) — learn what each file does
- [Commands reference](../reference/commands.md)
- [Monitor Sandbox Activity](../monitoring/monitor-sandbox-activity.md)
182 changes: 182 additions & 0 deletions nemoclaw/scripts/backup-workspace.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

WORKSPACE_PATH="/sandbox/.openclaw/workspace"
BACKUP_BASE="${HOME}/.nemoclaw/backups"

# Core persona files — expected once the agent has initialized workspace.
FILES_CORE=(SOUL.md USER.md IDENTITY.md AGENTS.md)
# Long-term memory — created later; missing paths are normal (no noisy tar errors).
FILES_OPTIONAL=(MEMORY.md)
DIRS_OPTIONAL=(memory)

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
DIM='\033[0;90m'
NC='\033[0m'

info() { echo -e "${GREEN}[backup]${NC} $1"; }
warn() { echo -e "${YELLOW}[backup]${NC} $1"; }
fail() {
echo -e "${RED}[backup]${NC} $1" >&2
exit 1
}

usage() {
cat <<EOF
Usage:
$(basename "$0") backup <sandbox-name>
$(basename "$0") restore <sandbox-name> [timestamp]

Commands:
backup Download workspace files from a sandbox to a timestamped local backup.
restore Upload workspace files from a local backup into a sandbox.
If no timestamp is given, the most recent backup is used.

Backup location: ${BACKUP_BASE}/<timestamp>/
EOF
exit 1
}

# Download from sandbox. Optional paths suppress stderr because openshell/tar
# emit multi-line errors when a file or directory does not exist yet.
sandbox_download() {
local sandbox="$1" remote="$2" dest="$3"
local quiet="${4:-0}"
if [[ "$quiet" == "1" ]]; then
openshell sandbox download "$sandbox" "$remote" "$dest" 2>/dev/null
else
openshell sandbox download "$sandbox" "$remote" "$dest"
fi
}

do_backup() {
local sandbox="$1"
local ts
ts="$(date +%Y%m%d-%H%M%S)"
local dest="${BACKUP_BASE}/${ts}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

mkdir -p "$BACKUP_BASE"
chmod 0700 "${HOME}/.nemoclaw" "$BACKUP_BASE" \
|| fail "Failed to set secure permissions on ${HOME}/.nemoclaw — check directory ownership."
mkdir -p "$dest"
chmod 0700 "$dest"

info "Backing up workspace from sandbox '${sandbox}'..."

local count=0
local f d

for f in "${FILES_CORE[@]}"; do
if sandbox_download "$sandbox" "${WORKSPACE_PATH}/${f}" "${dest}/" 0; then
count=$((count + 1))
else
warn "Skipped ${f} (not found or download failed)"
fi
done

for f in "${FILES_OPTIONAL[@]}"; do
if sandbox_download "$sandbox" "${WORKSPACE_PATH}/${f}" "${dest}/" 1; then
count=$((count + 1))
else
echo -e "${DIM}[backup]${NC} Optional ${f} not in sandbox — skipped (normal until created)."
fi
done

for d in "${DIRS_OPTIONAL[@]}"; do
if sandbox_download "$sandbox" "${WORKSPACE_PATH}/${d}/" "${dest}/${d}/" 1; then
count=$((count + 1))
else
echo -e "${DIM}[backup]${NC} Optional ${d}/ not in sandbox — skipped (normal until created)."
fi
done

if [ "$count" -eq 0 ]; then
fail "No files were backed up. Check that the sandbox '${sandbox}' exists and has workspace files."
fi

info "Backup saved to ${dest}/ (${count} items)"
}

# Latest backup directory name (mtime); portable (no find -printf).
latest_backup_timestamp() {
if [ ! -d "$BACKUP_BASE" ]; then
return 1
fi
# shellcheck disable=SC2012 # backup dirs are YYYYMMDD-HHMMSS — no special chars
ls -1t "$BACKUP_BASE" 2>/dev/null | head -n1
}

do_restore() {
local sandbox="$1"
local ts="${2:-}"

if [ -z "$ts" ]; then
ts="$(latest_backup_timestamp || true)"
[ -n "$ts" ] || fail "No backups found in ${BACKUP_BASE}/"
info "Using most recent backup: ${ts}"
fi

local src="${BACKUP_BASE}/${ts}"
[ -d "$src" ] || fail "Backup directory not found: ${src}"

info "Restoring workspace to sandbox '${sandbox}' from ${src}..."

local count=0
local f d

for f in "${FILES_CORE[@]}"; do
if [ -f "${src}/${f}" ]; then
if openshell sandbox upload "$sandbox" "${src}/${f}" "${WORKSPACE_PATH}/"; then
count=$((count + 1))
else
warn "Failed to restore ${f}"
fi
fi
done

for f in "${FILES_OPTIONAL[@]}"; do
if [ -f "${src}/${f}" ]; then
if openshell sandbox upload "$sandbox" "${src}/${f}" "${WORKSPACE_PATH}/"; then
count=$((count + 1))
else
warn "Failed to restore ${f}"
fi
fi
done

for d in "${DIRS_OPTIONAL[@]}"; do
if [ -d "${src}/${d}" ]; then
if openshell sandbox upload "$sandbox" "${src}/${d}/" "${WORKSPACE_PATH}/${d}/"; then
count=$((count + 1))
else
warn "Failed to restore ${d}/"
fi
fi
done

if [ "$count" -eq 0 ]; then
fail "No files were restored. Check that the sandbox '${sandbox}' is running."
fi

info "Restored ${count} items to sandbox '${sandbox}'."
}

# --- Main ---

[ $# -ge 2 ] || usage
command -v openshell >/dev/null 2>&1 || fail "'openshell' is required but not found in PATH."

action="$1"
sandbox="$2"
shift 2

case "$action" in
backup) do_backup "$sandbox" ;;
restore) do_restore "$sandbox" "$@" ;;
*) usage ;;
esac
Loading