# How to Restore an ai-memory SQLite Database from a Backup: Complete CLI Guide

> Learn to restore your ai-memory SQLite database from a backup. This CLI guide shows you how to use the ai-memory restore command to quickly recover your data. Stop the server and run the restore command.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: how-to-guide
- Published: 2026-08-31

---

**Restoring an ai-memory database requires stopping the server first, then using the `ai-memory restore --from <tarball> --force` command to extract a gzipped backup over the data directory.**

The **ai-memory** project by `akitaonrails/ai-memory` stores its knowledge graph in a SQLite file located at `db/memory.sqlite` within the data directory. Because a background writer actor continuously accesses this database, restoration is treated as a **disk-level lifecycle operation** that must run exclusively when no server instances are active. The CLI enforces this constraint through system process detection before extracting the tarball.

## Prerequisites and Safety Constraints

Before running any restore command, you must ensure complete process isolation. According to the lifecycle documentation in [`docs/lifecycle-ops.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/lifecycle-ops.md), the restore operation is only safe when the server is fully stopped.

The CLI implements this via the `sibling_processes()` function in [`crates/ai-memory-cli/src/commands/restore.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/commands/restore.rs). This routine scans for any running `ai-memory` processes and aborts immediately if siblings are detected, preventing data corruption from concurrent access:

```rust
// From restore.rs lines 34-37
if !sibling_processes.is_empty() {
    eprintln!("Error: Cannot restore while ai-memory is running.");
    process::exit(1);
}

```

Attempting to restore while the server is active would corrupt the SQLite file because the backup overwrites the entire data directory while the writer expects exclusive file locks.

## Understanding the Backup Format

The backup itself is produced by the admin endpoint `POST /admin/backup` implemented in [`crates/ai-memory-mcp/src/admin.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/admin.rs). The handler streams a **gzipped tarball** containing:

- An online SQLite backup captured via `rusqlite::Connection::backup` (implemented in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs))
- The entire wiki tree from the `wiki/` directory

This format allows hot backups (taken while the server runs) to be safely restored onto cold storage (when the server is stopped).

## Step-by-Step Restore Process

The restoration logic in [`crates/ai-memory-cli/src/commands/restore.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/commands/restore.rs) follows a strict validation pipeline:

### 1. Process-Safety Check

The command first invokes `sibling_processes()` to scan the system process table. If any `ai-memory` instance is found alive, the CLI exits with a clear error message before touching any files.

### 2. Validate the Source Tarball

The `--from` argument must point to an existing file on disk. The CLI verifies path existence and readability before attempting extraction.

### 3. Handle Existing Data with `--force`

If the target `wiki/` directory or `db/memory.sqlite` already exist, the command requires the `--force` flag. Without it, the CLI aborts with an explanatory error. When `--force` is provided, the existing directories are removed while preserving [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml), logs, and model files:

```rust
// From restore.rs lines 52-59
if force {
    if wiki_dir.exists() { fs::remove_dir_all(wiki_dir)?; }
    if db_dir.exists() { fs::remove_dir_all(db_dir)?; }
}

```

### 4. Extract and Validate Entries

The tarball is opened using `GzDecoder` and `tar::Archive`. Each entry passes through `validate_restore_entry`, which guarantees that only safe paths, regular files, and expected top-level directories (`wiki`, `db`, [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml)) are unpacked. This function prevents directory traversal attacks by validating paths before extraction.

### 5. Reopen and Migrate

After extraction, the code calls `Store::open(&config.data_dir)`. This triggers any pending **Refine** migrations and validates the restored SQLite file, ensuring the database is usable before the CLI reports success:

```rust
// From restore.rs lines 71-74
let store = Store::open(&config.data_dir)?;

```

## CLI Command Examples

Fetch a backup from a running server, stop the service, then restore:

```bash

# Create backup from live server

ai-memory backup --to /tmp/ai-memory-backup.tar.gz

# Stop any running instance

systemctl stop ai-memory

# Restore with forced overwrite

ai-memory restore --from /tmp/ai-memory-backup.tar.gz --force

```

## Programmatic Restoration (Rust API)

For custom tooling, you can replicate the CLI logic using the `ai-memory-store` crate:

```rust
use ai_memory_store::Store;
use anyhow::Result;
use std::path::Path;
use flate2::read::GzDecoder;
use tar::Archive;

fn restore_backup(data_dir: &Path, tarball: &Path) -> Result<Store> {
    // Open the gzipped tarball
    let file = std::fs::File::open(tarball)?;
    let decoder = GzDecoder::new(file);
    let mut archive = Archive::new(decoder);

    // Extract entries (validation logic omitted for brevity)
    for entry in archive.entries()? {
        let mut entry = entry?;
        entry.unpack_in(data_dir)?;
    }

    // Open store and apply pending migrations
    Store::open(data_dir)
}

```

## Summary

- **Stop the server** before restoring; the CLI enforces this via `sibling_processes()` to prevent corruption.
- Use `--force` to overwrite existing `wiki/` and `db/` directories when restoring to an initialized data directory.
- Backups are **gzipped tarballs** containing SQLite snapshots and wiki content, created via the online backup API in [`reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/reader.rs).
- The `validate_restore_entry` function blocks directory traversal attacks during extraction.
- Calling `Store::open()` after extraction automatically runs pending **Refine** migrations on the restored database.

## Frequently Asked Questions

### Can I restore while the ai-memory server is running?

No. The `restore` command aborts if `sibling_processes()` detects any active `ai-memory` instances. Restoration overwrites files that the writer actor expects to control exclusively, which would corrupt the database. Always stop the server first.

### What files are overwritten when using `--force`?

The `--force` flag removes only the `wiki/` and `db/` directories before extraction. Your [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml), log files, and downloaded model files remain untouched. This design preserves configuration while replacing data content.

### How does the restore command prevent directory traversal attacks?

The `validate_restore_entry` function inspects each tar entry before unpacking, ensuring paths stay within the data directory and only expected file types (regular files and safe directories) are extracted. Malformed entries containing `..` sequences or absolute paths are rejected.

### Does restoring trigger database migrations?

Yes. After extraction, the CLI calls `Store::open(&config.data_dir)`, which automatically applies any pending **Refine** schema migrations to the restored SQLite file. This ensures the database schema matches the current codebase version before the operation completes.