-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat(scripts): add upgrade-sandbox.sh and non-interactive inference hydration #902
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 7 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
6d9e08e
feat(onboard): add inferRemoteProviderKeyFromStoredName mapping
HagegeR 44a1df5
feat(onboard): add hydrateNonInteractiveInferenceFromRegistry with en…
HagegeR 49b1343
feat(onboard): call hydrateNonInteractiveInferenceFromRegistry in set…
HagegeR b47e08f
fix(onboard): resolve NVIDIA_API_KEY from credentials.json in non-int…
HagegeR 0340608
fix(onboard): hydrate credentials for all remote providers in non-int…
HagegeR 484e7ec
feat(onboard): persist model/provider/endpointUrl in registry from on…
HagegeR 12e623e
feat(scripts): add upgrade-sandbox.sh and improve backup-workspace.sh
HagegeR 34a690c
fix(scripts): abort restore on core file upload failure
HagegeR 58094ee
fix(scripts): abort upgrade when --full-data download fails
HagegeR 0f39d3c
fix(scripts): guard nemoclaw onboard so restore hints print on failure
HagegeR d46fca8
fix(registry): add endpointUrl to SandboxEntry interface
HagegeR 0db7853
fix(onboard): use GATEWAY_NAME fallback when sandboxName is null in r…
HagegeR 4dc4c1f
chore: revert out-of-scope pre-commit-config changes
HagegeR File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}" | ||
|
|
||
| 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 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.