Skip to content

Instantly share code, notes, and snippets.

@Ostico
Last active April 16, 2026 23:42
Show Gist options
  • Select an option

  • Save Ostico/076db2e8c1a713dd66058675b0abac55 to your computer and use it in GitHub Desktop.

Select an option

Save Ostico/076db2e8c1a713dd66058675b0abac55 to your computer and use it in GitHub Desktop.
Back up your AI-assisted development environment and restore it on a fresh machine

Developer Workstation Backup & Restore Recipe

Back up your AI-assisted development environment and restore it on a fresh machine

Prerequisite: The target machine must have completed developer-workstation-setup.md before restoring
Target: Ubuntu 24.04 LTS / macOS 14+ (same platforms as the setup recipe)
Time: ~5 minutes backup, ~10 minutes restore
Tested with: Signet 0.98.7, OpenCode 1.4.3, Oh-My-OpenAgent 3.12.3

Note: This is a best-effort backup process. It captures configuration and memory data reliably, but local tool installations (MCP servers, Ollama, npm globals) must be reinstalled on the target machine via the setup recipe.


What Gets Backed Up

Tier 1 — Critical (irreplaceable data)

Path Contents Typical Size
~/.agents/memory/memories.db Signet vector DB (SQLite + sqlite-vec + FTS5) 100–200 MB
~/.agents/memory/*.md Session transcripts (one per conversation) 50–150 MB
~/.agents/AGENTS.md Agent instructions (auto-generated from identity files) < 1 KB
~/.agents/SOUL.md Agent personality and communication style < 2 KB
~/.agents/IDENTITY.md Agent identity definition < 1 KB
~/.agents/USER.md User profile (preferences, context) < 2 KB
~/.agents/MEMORY.md Working memory snapshot < 2 KB
~/.agents/agent.yaml Signet configuration (embedding model, daemon settings) < 1 KB
~/.local/share/opencode/opencode.db OpenCode session history (SQLite) 200–500 MB

Tier 2 — Configuration (reproducible but tedious to recreate)

Path Contents Typical Size
~/.config/opencode/opencode.json OpenCode config (plugins, providers, MCP servers) < 2 KB
~/.config/opencode/oh-my-opencode.json Agent-model mappings < 2 KB
~/.config/opencode/skills/ Installed skills (12+ skill directories) ~500 KB
~/.config/opencode/plugins/signet.mjs Signet plugin (generated by signet sync) < 500 KB
~/tools/dbhub.toml DBHub config (MySQL connections + tool settings) < 1 KB

Tier 3 — Regenerable (skip to save space)

These are recreated automatically by the setup recipe or by running install commands.

Path Why Skip
~/.agents/.daemon/bin/ Signet predictor binary — redownloaded on daemon start
~/.agents/.daemon/logs/ Daemon logs — not portable
~/.agents/.daemon/pid, last-healthy-start Runtime state — regenerated
~/tools/fermat-mcp/, ~/tools/uml-mcp/ Git clones with venvs — uv sync recreates them
Ollama models (~/.ollama/models/) Re-pull via ollama pull nomic-embed-text (~274 MB); optionally ollama pull llama3.1:8b (~1.9 GB) if using local extraction
DBHub npm package Reinstall via npm install -g @bytebase/dbhub@latest
API Testing MCP npm package Reinstall via npm install -g @cocaxcode/api-testing-mcp@latest
~/.agents/skills/ Skill metadata — regenerated by signet sync

Backup

Option A — Full Backup Script

Save this script as backup-workstation.sh and run it.

#!/usr/bin/env bash
set -euo pipefail

# ── Configuration ─────────────────────────────────────────────
BACKUP_DIR="${BACKUP_DIR:-$HOME/workstation-backup}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/workstation-${TIMESTAMP}.tar.gz"
SHELL_RC="${SHELL##*/}rc"

# ── Pre-flight ────────────────────────────────────────────────
echo "=== Workstation Backup ==="
echo "Backup file: ${BACKUP_FILE}"
echo ""

mkdir -p "${BACKUP_DIR}"

# Stop Signet daemon to get a clean SQLite snapshot (WAL mode)
echo "[1/4] Stopping Signet daemon for consistent DB backup..."
if command -v signet &>/dev/null; then
    signet daemon stop 2>/dev/null || true
    sleep 2
    SIGNET_WAS_RUNNING=1
else
    echo "  ⚠ signet not in PATH — skipping daemon stop"
    echo "    If Signet is installed, activate the correct Node version first:"
    echo "    nvm use 22 && npm ls -g signetai"
    SIGNET_WAS_RUNNING=0
fi

# ── Collect files ─────────────────────────────────────────────
echo "[2/4] Collecting files..."

# Build the file list dynamically (only existing paths)
FILE_LIST=()

# Tier 1 — Critical
[ -d "$HOME/.agents/memory" ]              && FILE_LIST+=(".agents/memory/")
[ -f "$HOME/.agents/AGENTS.md" ]           && FILE_LIST+=(".agents/AGENTS.md")
[ -f "$HOME/.agents/SOUL.md" ]             && FILE_LIST+=(".agents/SOUL.md")
[ -f "$HOME/.agents/IDENTITY.md" ]         && FILE_LIST+=(".agents/IDENTITY.md")
[ -f "$HOME/.agents/USER.md" ]             && FILE_LIST+=(".agents/USER.md")
[ -f "$HOME/.agents/MEMORY.md" ]           && FILE_LIST+=(".agents/MEMORY.md")
[ -f "$HOME/.agents/agent.yaml" ]          && FILE_LIST+=(".agents/agent.yaml")
[ -f "$HOME/.local/share/opencode/opencode.db" ] && FILE_LIST+=(".local/share/opencode/opencode.db")

# Tier 2 — Configuration
[ -f "$HOME/.config/opencode/opencode.json" ]         && FILE_LIST+=(".config/opencode/opencode.json")
[ -f "$HOME/.config/opencode/oh-my-opencode.json" ]  && FILE_LIST+=(".config/opencode/oh-my-opencode.json")
[ -d "$HOME/.config/opencode/skills" ]                && FILE_LIST+=(".config/opencode/skills/")
[ -f "$HOME/.config/opencode/plugins/signet.mjs" ]    && FILE_LIST+=(".config/opencode/plugins/signet.mjs")
[ -f "$HOME/tools/dbhub.toml" ]                        && FILE_LIST+=("tools/dbhub.toml")

# Note: Plugin-generated files are intentionally excluded:
#   ~/.config/opencode/plugins/superpowers.js       — auto-installed from opencode.json on restart
#   ~/.config/opencode/plugins/signet-first-bootstrap.js — auto-installed from opencode.json on restart
#   ~/.config/opencode/AGENTS.md                    — auto-generated by Signet from identity files

if [ ${#FILE_LIST[@]} -eq 0 ]; then
    echo "  ✗ No files found to back up. Is this the right machine?"
    exit 1
fi

echo "  Files to archive:"
for f in "${FILE_LIST[@]}"; do
    SIZE=$(du -sh "$HOME/$f" 2>/dev/null | cut -f1)
    echo "    ${f} (${SIZE:-unknown})"
done

# ── Create archive ────────────────────────────────────────────
echo "[3/4] Creating archive..."
tar -czf "${BACKUP_FILE}" -C "$HOME" "${FILE_LIST[@]}"

ARCHIVE_SIZE=$(du -sh "${BACKUP_FILE}" | cut -f1)
echo "  Archive: ${BACKUP_FILE} (${ARCHIVE_SIZE})"

# ── Restart daemon ────────────────────────────────────────────
echo "[4/4] Restarting Signet daemon..."
if [ "$SIGNET_WAS_RUNNING" -eq 1 ] && command -v signet &>/dev/null; then
    signet daemon start 2>/dev/null || true
    echo "  ✓ Daemon restarted"
else
    echo "  ⚠ Skipped (daemon was not running or signet not in PATH)"
fi

echo ""
echo "=== Backup Complete ==="
echo "Archive: ${BACKUP_FILE}"
echo "Size:    ${ARCHIVE_SIZE}"
echo ""
echo "Copy this file to the target machine and run the restore script."

Option B — Quick One-Liner

Quick alternative for experienced users (no error handling — use only if you know all paths exist):

# Stop daemon, tar, restart
signet daemon stop 2>/dev/null; \
tar -czf ~/workstation-$(date +%Y%m%d).tar.gz \
  -C "$HOME" \
  .agents/memory/ \
  .agents/AGENTS.md .agents/SOUL.md .agents/IDENTITY.md .agents/USER.md .agents/MEMORY.md \
  .agents/agent.yaml \
  .local/share/opencode/opencode.db \
  .config/opencode/opencode.json \
  .config/opencode/oh-my-opencode.json \
  .config/opencode/skills/ \
  .config/opencode/plugins/signet.mjs \
  tools/dbhub.toml \
2>/dev/null; \
signet daemon start 2>/dev/null

Option C — Signet Export Only (memories, no config)

If you only need to transfer Signet memories (not the full environment):

signet export --json > signet-memories.json

This produces a portable JSON bundle that can be imported on any machine with Signet installed. The export does not include: identity files, OpenCode config, skills, or session history.


What Restore Does NOT Recreate

Review this table before starting the restore process — these items require manual reinstallation.

Item Why Fix
MCP server paths in opencode.json Absolute paths differ per user Restore script rewrites automatically; verify if username changed
Superpowers plugin Auto-installed from opencode.json on restart Already in opencode.json — just restart OpenCode
signet-first plugin Auto-installed from opencode.json on restart Already in opencode.json — just restart OpenCode
Fermat/UML MCP Git clones with Python venvs Re-clone + uv sync per setup recipe Step 8
Ollama + nomic-embed-text Local model server + embedding model Install Ollama per setup recipe Step 6, then ollama pull nomic-embed-text; optionally ollama pull llama3.1:8b for local extraction
DBHub npm global package npm install -g @bytebase/dbhub@latest — config restored from backup
API Testing MCP npm global package npm install -g @cocaxcode/api-testing-mcp@latest — no config file needed

Restore

Prerequisites

  1. The target machine has completed developer-workstation-setup.md
  2. Signet daemon is running (signet daemon status)
  3. You have the backup archive on the target machine
  4. Python 3 is available on the target machine (required for automatic path rewriting)

Restore Script

Save this as restore-workstation.sh and run it with the backup file as argument.

#!/usr/bin/env bash
set -euo pipefail

BACKUP_FILE="${1:-}"
SHELL_RC="${SHELL##*/}rc"

if [ -z "${BACKUP_FILE}" ] || [ ! -f "${BACKUP_FILE}" ]; then
    echo "Usage: bash restore-workstation.sh <backup-file.tar.gz>"
    echo "Example: bash restore-workstation.sh ~/workstation-20260408.tar.gz"
    exit 1
fi

echo "=== Workstation Restore ==="
echo "Archive: ${BACKUP_FILE}"
echo ""

# ── Stop services ─────────────────────────────────────────────
echo "[1/5] Stopping Signet daemon..."
if command -v signet &>/dev/null; then
    signet daemon stop 2>/dev/null || true
    sleep 2
else
    echo "  ⚠ signet not in PATH — run the setup recipe first"
    echo "    See: developer-workstation-setup.md"
    exit 1
fi

# ── Preview archive ──────────────────────────────────────────
echo "[2/5] Archive summary:"
TOTAL_FILES=$(tar -tzf "${BACKUP_FILE}" | wc -l)
ARCHIVE_SIZE=$(du -sh "${BACKUP_FILE}" | cut -f1)
echo "  ${TOTAL_FILES} files, ${ARCHIVE_SIZE} compressed"
echo ""

read -p "This will OVERWRITE existing files. Continue? [y/N] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "Aborted."
    exit 0
fi

# ── Extraction ───────────────────────────────────────────────────
echo "[3/5] Extracting archive to ${HOME}..."
tar -xzf "${BACKUP_FILE}" -C "$HOME"
echo "  ✓ Files extracted"

# ── Fix machine-specific config ──────────────────────────────
echo "[4/5] Post-restore fixup..."

# 4a. Rewrite absolute paths in opencode.json
OPENCODE_CONFIG="$HOME/.config/opencode/opencode.json"
if [ -f "${OPENCODE_CONFIG}" ]; then
    # Replace any /home/<old-user>/ or /Users/<old-user>/ with current $HOME
    # This handles the MCP server paths (fermat-mcp, uml-mcp, dbhub, etc.)
    # WARNING: raw-text rewrite — review opencode.json if you store unrelated home paths
    if command -v python3 &>/dev/null; then
        python3 -c "
import json, re, os

config_path = '${OPENCODE_CONFIG}'
home = os.path.expanduser('~')

with open(config_path) as f:
    raw = f.read()

# Replace /home/<user>/ or /Users/<user>/ patterns with current HOME
fixed = re.sub(r'/(?:home|Users)/[^/\"]+/', home + '/', raw)

if fixed != raw:
    with open(config_path, 'w') as f:
        f.write(fixed)
    print('  ✓ opencode.json paths rewritten to ' + home)
else:
    print('  ✓ opencode.json paths already correct')
"
    # Python not available — fall back to manual path editing
    else
        echo "  ⚠ python3 not found — manually update paths in ${OPENCODE_CONFIG}"
    fi
fi

# 4b. Run Signet schema migrations (handles version differences)
echo "  Running Signet migrations..."
signet migrate-schema 2>/dev/null && echo "  ✓ Schema migrated" || echo "  ⚠ migrate-schema skipped (may already be current)"
signet migrate-vectors 2>/dev/null && echo "  ✓ Vectors migrated" || echo "  ⚠ migrate-vectors skipped (may already be current)"

# 4c. Re-register Signet plugins and hooks
echo "  Re-registering Signet harness..."
signet sync 2>/dev/null && echo "  ✓ Signet synced" || echo "  ⚠ signet sync failed — run manually"

# ── Restart services ──────────────────────────────────────────
echo "[5/5] Starting Signet daemon..."
signet daemon start 2>/dev/null || true
sleep 3

# ── Verify ────────────────────────────────────────────────────
echo ""
echo "=== Verification ==="

# Check daemon
if signet daemon status 2>/dev/null | grep -qi "running"; then
    echo "  ✓ Signet daemon running"
else
    echo "  ✗ Signet daemon not running — check: signet daemon start"
fi

# Check memory accessibility
if command -v signet &>/dev/null; then
    if signet search "test" --limit 1 &>/dev/null; then
        echo "  ✓ Signet memory search operational"
    else
        echo "  ⚠ Signet memory search returned an error — check: signet doctor"
    fi
fi

# Check OpenCode DB
if [ -f "$HOME/.local/share/opencode/opencode.db" ]; then
    DB_SIZE=$(du -sh "$HOME/.local/share/opencode/opencode.db" | cut -f1)
    echo "  ✓ OpenCode session history restored (${DB_SIZE})"
else
    echo "  ⚠ OpenCode session history not found"
fi

# Check identity files
IDENTITY_OK=1
for f in AGENTS.md SOUL.md IDENTITY.md USER.md; do
    if [ ! -f "$HOME/.agents/$f" ]; then
        echo "  ✗ Missing: ~/.agents/$f"
        IDENTITY_OK=0
    fi
done
[ "$IDENTITY_OK" -eq 1 ] && echo "  ✓ Identity files present"

# Check skills directory
if [ -d "$HOME/.config/opencode/skills" ]; then
    echo "  ✓ Skills directory present"
else
    echo "  ⚠ Skills directory not found"
fi

echo ""
echo "=== Restore Complete ==="
echo ""
echo "Next steps:"
echo "  1. Open a new terminal (or: source ~/.${SHELL_RC})"
echo "  2. Run: opencode"
echo "  3. Test memory: ask the agent 'what do you remember about me?'"
echo "  4. If Signet-only export was used, also run: signet import ./signet-memories.json --json --conflict merge"

Restore from Signet Export Only

If you used Option C (JSON export):

signet import ./signet-memories.json --json --conflict merge

The --conflict merge flag keeps existing memories and adds new ones without duplicates.


Scheduling Automated Backups

Cron (Linux)

Save the backup script as ~/backup-workstation.sh first, then add a cron entry:

# Daily backup at 2 AM, keep last 7 days
(crontab -l 2>/dev/null; echo "0 2 * * * bash $HOME/backup-workstation.sh && find $HOME/workstation-backup -name '*.tar.gz' -mtime +7 -delete") | crontab -

Launchd (macOS)

Create ~/Library/LaunchAgents/com.workstation.backup.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>com.workstation.backup</string>
        <key>ProgramArguments</key>
        <array>
            <string>/bin/bash</string>
            <string>/Users/YOURUSER/backup-workstation.sh</string>
        </array>
        <key>StartCalendarInterval</key>
        <dict>
            <key>Hour</key>
            <integer>2</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <key>WorkingDirectory</key>
        <string>/Users/YOURUSER</string>
    </dict>
</plist>

Replace YOURUSER with your macOS username in both paths above.

Then load it:

launchctl load ~/Library/LaunchAgents/com.workstation.backup.plist

Troubleshooting

signet: command not found during backup

Signet is installed globally under a specific Node.js version. Activate it first:

nvm use 22
which signet  # should print a path

Archive is very large (> 1 GB)

The OpenCode session DB (opencode.db) grows with usage. To exclude it:

# Back up without OpenCode session history
tar -czf ~/workstation-lite-$(date +%Y%m%d).tar.gz \
  -C "$HOME" \
  .agents/memory/ \
  .agents/AGENTS.md .agents/SOUL.md .agents/IDENTITY.md .agents/USER.md .agents/MEMORY.md \
  .agents/agent.yaml \
  .config/opencode/opencode.json \
  .config/opencode/oh-my-opencode.json \
  .config/opencode/skills/ \
  .config/opencode/plugins/signet.mjs

This typically produces a ~100-200 MB archive.

SQLITE_BUSY errors during backup

The Signet daemon uses WAL mode on the SQLite database. If the daemon was not stopped before backup, you may get a corrupted snapshot. Always stop the daemon first:

signet daemon stop
sleep 2
# ... then tar ...
signet daemon start

Restored memories not showing up

After restore, run the full migration + sync sequence:

signet migrate-schema
signet migrate-vectors
signet sync
signet daemon stop && signet daemon start

Then test: signet search "any known topic"

opencode.json has wrong paths after restore

The restore script auto-rewrites /home/<old-user>//home/<new-user>/. If it missed something, manually edit ~/.config/opencode/opencode.json and fix any absolute paths pointing to the old home directory.


File Map (reference)

~/.agents/                          Signet workspace root
├── AGENTS.md                       ← BACKED UP (identity)
├── SOUL.md                         ← BACKED UP (personality)
├── IDENTITY.md                     ← BACKED UP (identity)
├── USER.md                         ← BACKED UP (user profile)
├── MEMORY.md                       ← BACKED UP (working memory)
├── agent.yaml                      ← BACKED UP (config)
├── memory/
│   ├── memories.db                 ← BACKED UP (vector DB — the big one)
│   ├── memories.db-wal             ← included if present at backup time (WAL sidecar)
│   ├── memories.db-shm             ← included if present at backup time (shared memory)
│   └── *.md                        ← BACKED UP (session transcripts)
├── skills/                         ✗ SKIPPED (regenerated by `signet sync`)
└── .daemon/                        ✗ SKIPPED (runtime state)

~/.config/opencode/                 OpenCode configuration
├── opencode.json                   ← BACKED UP (main config)
├── oh-my-opencode.json            ← BACKED UP (agent models)
├── skills/                         ← BACKED UP (installed skills)
│   ├── agent-architect/
│   ├── english-checker/
│   ├── memory-debug/
│   ├── onboarding/
│   ├── recall/
│   ├── remember/
│   ├── signet/
│   ├── signet-design/
│   ├── signet-first/
│   ├── skill-creator/
│   └── web-search/
├── plugins/
│   └── signet.mjs                  ← BACKED UP (generated plugin)
│                                   superpowers + signet-first plugins are auto-installed
│                                   from opencode.json entries on restart — no backup needed

~/.local/share/opencode/
└── opencode.db                     ← BACKED UP (session history)

~/tools/                            ✗ SKIPPED (git clones, recreated by setup)
├── fermat-mcp/
├── uml-mcp/
└── dbhub.toml                      ← BACKED UP (MySQL connection config)

~/.ollama/                          ✗ SKIPPED (model weights, re-pull via ollama pull)
└── models/
    ├── manifests/                   nomic-embed-text metadata (+ llama3.1:8b if local extraction)
    └── blobs/                       nomic-embed-text weights (~274 MB) + llama3.1:8b (~1.9 GB, optional)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment