Backup & Recovery

Database backup, restore, and disaster recovery.

Fortémi Backup Guide

This guide covers backup and restore procedures for Fortémi.

Overview

Fortémi provides multiple backup options:

MethodUse CaseFormatIncludes
JSON ExportApp-level backupJSONNotes, collections, tags, templates
Knowledge ShardReceipt-scoped data exchange.shardOnly the components and byte policy of its exact `(schema, profile)` tuple
Knowledge ArchiveBackup + metadata bundle.archiveBackup file + metadata.json
Database SnapshotFull database backuppg_dumpComplete database with embeddings
Shell scriptAutomated scheduled backupspg_dumpFull database with compression

Encryption Options

All backup methods support optional PKE encryption:

EncryptionFormatUse Case
PKE.mmpke (MMPKE01)Multi-recipient wallet-style encryption

See Encryption Guide for cryptographic details and Shard Exchange Primer for practical sharing workflows.

Choosing a Backup Method

  • JSON Export (`/api/v1/backup/export`): Quick export of note content. Best for migration to other systems.
  • Knowledge Shard (`/api/v1/backup/knowledge-shard`): Named-profile

interchange. Default `1.2.0/core-v1` exports are reference-only unless `include_blobs=true`; exact `2.0.0/full-v1` requires every declared byte. Treat a shard as portable only for a receipt-backed producer/consumer cell.

  • Knowledge Archive (`/api/v1/backup/knowledge-archive`): Bundles any backup with its metadata sidecar. Best for transferring backups between systems.

Shard Versioning

Knowledge shards use semantic versioning (MAJOR.MINOR.PATCH) to ensure compatibility across different Fortémi versions.

Supported versions

  • Default export schema: `1.2.0`
  • Opt-in reader/export schema: exact `2.0.0` tuples, with advertised

support limited to `2.0.0/full-v1`

  • Unadvertised schema-2 profiles: `2.0.0/core-v1` and

`2.0.0/record-v1`

  • Default profile: `core-v1`
  • Authority: `contracts/knowledge-shard/contract.json` and the immutable

versioned schema roots

Version Compatibility

When importing a shard, Fortémi automatically checks version compatibility:

ScenarioBehavior
Exact registered tupleImport after schema, profile, integrity, relationship, byte, and limit preflight
Historical registered tupleValidate source, run only its registered migration path, revalidate, then import
Exact advertised `2.0.0/full-v1` tupleOpt-in import/export with direct JSON-key presence semantics
Unadvertised `2.0.0` tupleDo not claim support without its own receipt
Unregistered tuple or schema `3.x`Reject before persistent or blob mutation
`min_reader_version` newer than the supported schema readerReject before mutation

Migration Support

The default contract is `1.2.0`. Its registered historical path records any legacy default instead of pretending the source carried that state. Schema `2.0.0` distinguishes absent, JSON `null`, empty, and non-empty values and persists those distinctions transactionally. `min_reader_version` is a schema compatibility floor. Application releases belong in `producer.version` and do not participate in compatibility.

Profiles and evidence boundaries

ProfilePortable surface
`core-v1`Notes, collections, tags, templates, and links; attachment references with explicitly optional sidecars
`record-v1`The declared reduced RecordStore record set; every out-of-profile or lossy projection requires a machine-readable loss entry
`full-v1`All 33 declared components, 34 counts, required attachment bytes, signatures, embeddings and lineage, SKOS, provenance, graph, and community data

Fortemi's signed `1.2.0/full-v1` fixture and server self-import/re-export prove only the Fortemi-to-Fortemi boundary. The hardened nine-cell schema `1.2.0` matrix is a set of per-cell claims, not a suite-wide parity statement. Exact `2.0.0/full-v1` is now an advertised opt-in backed by the immutable Fortemi #1084 and fortemi-react #382 receipts. That receipt covers only its named React and AIWG producers, PGlite and Fortemi destinations, and HotM pass-through recovery cell. The separate supported-platform aggregate in Gitea run 6393 binds the declared Fortemi authority-to-React/core-to-HotM surface on Linux x86_64, Linux arm64, and macOS arm64 at exact revisions. Windows is deferred under Fortemi #1096. Full portability, complete backup, and schema parity claims remain blocked. Launched GUI/native-dialog coverage also remains unproven; Fortemi #1081 remains `NO-GO` pending independent audit.

For detailed information about versioning, compatibility, and troubleshooting, see the Shard Migration Guide.

  • Database Snapshot (`/api/v1/backup/database/snapshot`): Full pg_dump backup. Best for disaster recovery.

Quick Start

Using REST API

# Export all notes as JSON
curl http://localhost:3000/api/v1/backup/export

# Create the default self-importable core-v1 shard
curl http://localhost:3000/api/v1/backup/knowledge-shard -o backup.shard

# Create shard with specific components
curl "http://localhost:3000/api/v1/backup/knowledge-shard?include=notes,links" -o backup.shard

# Opt into a self-contained core-v1 shard for available stored attachments
curl "http://localhost:3000/api/v1/backup/knowledge-shard?include_blobs=true" -o backup-with-blobs.shard

# Trigger database backup
curl -X POST http://localhost:3000/api/v1/backup/trigger

# Check backup status
curl http://localhost:3000/api/v1/backup/status
// Export all notes to JSON
export_all_notes()

// Create the default core-v1 knowledge shard
knowledge_shard()

// Create shard with specific components
knowledge_shard({ include: "notes,links" })

// Validate/import bytes through the multipart REST boundary or an MCP client
// that explicitly supports local file transfer; local server paths are not a
// portable interchange contract.

// Download backup as portable knowledge archive (.archive)
knowledge_archive_download({ filename: "snapshot_database_20260117.sql.gz" })

// Upload and extract knowledge archive
knowledge_archive_upload({ archive_base64: "..." })

// Trigger database backup
backup_now()

// Check backup status
backup_status()

Using Shell Script

# Run immediate backup
./scripts/backup.sh

# Run with verbose output
./scripts/backup.sh -v

# Dry run (show what would happen)
./scripts/backup.sh -n

# Backup to specific destination
./scripts/backup.sh -d local

Using Systemd (Scheduled)

# Enable daily backups at 2:30 AM
sudo systemctl enable matric-backup.timer
sudo systemctl start matric-backup.timer

# Check timer status
systemctl list-timers matric-backup.timer

Per-Memory Backup

The multi-memory architecture allows schema-level backup and restore operations. Each memory archive maintains independent data in its own PostgreSQL schema.

How X-Fortemi-Memory Header Scopes Backup Operations

  • Application-level backups (JSON export, knowledge shards) use the `X-Fortemi-Memory` header to scope operations to a specific memory
  • Database-level backups (pg_dump) can target specific schemas or the entire database
  • Without the header, all backup operations default to the `default` memory

Per-Schema pg_dump for Memory-Level Backup

# Backup a specific memory schema
docker exec fortemi-matric-1 pg_dump -U matric -d matric -n archive_work_2026 > work-memory-backup.sql

# Backup all memories
for schema in $(docker exec fortemi-matric-1 psql -U matric -d matric -t -c "SELECT schema_name FROM archive_registry"); do
  docker exec fortemi-matric-1 pg_dump -U matric -d matric -n "$schema" > "backup_${schema}.sql"
done

# Backup multiple specific memories
docker exec fortemi-matric-1 pg_dump -U matric -d matric \
  -n archive_work_2026 \
  -n archive_personal_2026 \
  > multi-memory-backup.sql

Full Database pg_dump Includes ALL Schemas

A full database backup captures all memory archives plus shared infrastructure:

# Full backup (all memories + shared tables)
docker exec fortemi-matric-1 pg_dump -U matric -d matric > full-backup.sql

# This includes:
# - public schema (shared infrastructure: archive_registry, oauth tables, etc.)
# - All archive_* schemas (per-memory data)

Restore Caveats

Restoring a memory backup requires the target schema to exist:

# Option 1: Create memory first, then restore data
curl -X POST http://localhost:3000/api/v1/memories \
  -H "Content-Type: application/json" \
  -d '{"name": "work-2026", "description": "Restored work memory"}'

# Then restore the schema backup
docker exec -i fortemi-matric-1 psql -U matric -d matric < work-memory-backup.sql

# Option 2: Restore full database backup (recreates all schemas)
docker exec -i fortemi-matric-1 psql -U matric -d matric < full-backup.sql

Important: Schema-level restores assume the schema structure already exists. If restoring to a fresh instance: 1. Let Fortemi create the memory (which runs migrations) 2. Then restore data into the schema

OR

1. Restore full database backup (includes schema creation)

Knowledge Shard Export Respects Active Memory Context

Knowledge shards export data from the memory specified by `X-Fortemi-Memory`:

# Export work memory as knowledge shard
curl http://localhost:3000/api/v1/backup/knowledge-shard \
  -H "X-Fortemi-Memory: work-2026" \
  -o work-2026.shard

# Export default memory (no header)
curl http://localhost:3000/api/v1/backup/knowledge-shard \
  -o default-memory.shard

# Restore shard to different memory (multipart upload)
curl -X POST http://localhost:3000/api/v1/backup/knowledge-shard/upload?on_conflict=skip \
  -H "X-Fortemi-Memory: work-2026-restored" \
  -F "[email protected]"

Memory-Scoped Backups

All backup operations respect the `X-Fortemi-Memory` header. Without it, backups operate on the `default` memory.

Backup Specific Memory

# Export work memory only
curl http://localhost:3000/api/v1/backup/export \
  -H "X-Fortemi-Memory: work-notes" \
  -o work-notes-backup.json

# Create knowledge shard for specific memory
curl http://localhost:3000/api/v1/backup/knowledge-shard \
  -H "X-Fortemi-Memory: work-notes" \
  -o work-notes.shard

Full Database Backup (All Memories)

Database backup includes all memories and shared tables:

# Database backup includes all memories and shared tables
curl http://localhost:3000/api/v1/backup/database -o full-backup.sql

Restore to Different Memory

# First, create the target memory
curl -X POST http://localhost:3000/api/v1/memories \
  -H "Content-Type: application/json" \
  -d '{
    "name": "work-notes-restored",
    "description": "Restored from backup"
  }'

# Restore shard to target memory (multipart upload)
curl -X POST http://localhost:3000/api/v1/backup/knowledge-shard/upload?on_conflict=skip \
  -H "X-Fortemi-Memory: work-notes-restored" \
  -F "[email protected]"

Memory-scoped backups are useful for:

  • Creating per-project backups before major changes
  • Migrating specific memories between instances
  • Isolating backup/restore operations by project or team

See the Multi-Memory Guide for comprehensive memory management documentation.

MCP Backup Tools

export_all_notes

Exports all notes as a complete JSON export.

Parameters:

  • `filter.starred_only` - Only export starred notes
  • `filter.tags` - Only notes with these tags
  • `filter.created_after` / `created_before` - Date range

Returns:

{
  "manifest": {
    "version": "1.2.0",
    "format": "matric-backup",
    "created_at": "2026-01-17T12:00:00Z",
    "counts": {
      "notes": 150,
      "collections": 5,
      "tags": 30,
      "templates": 3
    }
  },
  "notes": [...],
  "collections": [...],
  "tags": [...],
  "templates": [...]
}

Use cases:

  • Complete knowledge base backup
  • Migration to another instance
  • Offline analysis
  • Snapshots before major changes

backup_now

Triggers an immediate database backup using the backup script.

Parameters:

  • `destinations` - Array of: `["local", "s3", "rsync"]`
  • `dry_run` - Preview without executing

Returns:

{
  "status": "success",
  "output": "[2026-01-17 12:00:00] Starting Fortémi backup...\
...",
  "timestamp": "2026-01-17T12:00:00Z"
}

backup_status

Check the status of the backup system.

Returns:

{
  "backup_directory": "/var/backups/fortemi",
  "disk_usage": "1.2G",
  "latest_backup": {
    "path": "/var/backups/fortemi/matric_backup_20260117_120000.sql.gz",
    "size_bytes": 52428800,
    "timestamp": "2026-01-17T12:00:00Z"
  },
  "status": "healthy"
}

knowledge_shard

Create a self-importable `core-v1` knowledge shard.

Parameters:

  • `include` - Components to include (comma-separated or array):
  • `notes` - All notes with original/revised content
  • `collections` - Folder hierarchy
  • `tags` - All tags
  • `templates` - Note templates
  • `links` - Semantic relationships between notes
  • Default: `notes,collections,tags,templates,links`

Returns:

{
  "success": true,
  "filename": "matric-shard-20260117-143800.tar.gz",
  "size_bytes": 52428,
  "size_human": "51.20 KB",
  "content_type": "application/gzip",
  "base64_data": "H4sIAAAAAAAA...",
  "message": "Archive created. Use base64_data to save the file."
}

Shard contents:

manifest.json           # Version, counts, SHA256 checksums
notes.jsonl             # Notes in streaming JSONL format
collections.json        # Folder hierarchy
tags.json               # Tags with timestamps
templates.json          # Note templates
links.jsonl             # Semantic links between notes

Server-generated shards are reference-only for binary attachments by default: attachment metadata appears in note records and import restores it as digest-deduplicated reference records. With `include_blobs=true`, export adds available verified bytes at `blobs/<digest>` and import materializes present valid referenced sidecars. The format and remaining limits are defined in Binary Attachment Projection.

Import safety limits:

  • Compressed shard bytes: 50 MiB maximum, or a smaller

`MATRIC_MAX_UPLOAD_SIZE_BYTES` operator limit.

  • Expanded component bytes: four times the effective compressed limit in

total; no component may exceed the effective compressed limit.

  • Manifest: 1 MiB maximum.
  • Archive inventory: 64 regular-file entries with safe UTF-8 relative names;

duplicate names and all non-regular tar entry types are rejected.

  • Component records: 250,000 records per component and 4 MiB per record.

Multipart upload reads the request file incrementally up to the compressed limit. The current shard archive reader then buffers bounded entries for validation and apply; this is not streaming archive processing. Base64 and on-disk swap inputs are bounded before decode or allocation. Dry-run and real import use the same structure, checksum, schema, count, and relationship preflight before normal writes. Ordinary import applies all selected database components in one schema-scoped transaction: any database failure rolls back collections, notes, tags, templates, and links together. Reference attachment records and blob metadata use the same transaction, so they also roll back on a late failure. NLP regeneration is queued only after that transaction commits.

Destructive on-disk swap accepts only `wipe` or `merge`. The `wipe` strategy validates the complete shard before mutation, then deletes the existing core-v1 entity families and applies the validated shard in the same schema-scoped transaction. A late apply failure restores the pre-swap state. This atomic database boundary does not make `core-v1` a complete disaster-recovery profile: attachment bytes and richer profile data remain outside its current preservation contract.

Knowledge Shard import

Upload archive bytes to the multipart `POST /api/v1/backup/knowledge-shard/import/upload` boundary. Validate the exact schema/profile tuple and use `dry_run=true` before a mutating import. Filesystem paths are client-local concerns and are not part of the portable wire contract.

  • `skip_embedding_regen` - Don't regenerate embeddings (default: false)

Returns:

{
  "status": "success",
  "manifest": { "version": "1.2.0", "profile": "core-v1", "counts": {...} },
  "imported": { "notes": 4, "collections": 2, "links": 12 },
  "skipped": { "notes": 0 },
  "errors": [],
  "dry_run": false
}

Conflict behavior:

  • Same shard twice → `on_conflict` determines behavior
  • Different shard → New IDs create new notes (merge)

knowledge_archive_download

Download a backup file bundled with its metadata as a portable `.archive` file.

Parameters:

  • `filename` - Backup filename from list_backups (e.g., `snapshot_database_20260117.sql.gz`)

Returns:

{
  "success": true,
  "filename": "snapshot_database_20260117.archive",
  "size_bytes": 1234567,
  "base64_data": "..."
}

Archive contents:

snapshot_database_20260117.archive (tar format)
├── snapshot_database_20260117.sql.gz  # The backup file
└── metadata.json                       # Title, description, note_count, etc.

Use case: Transfer backups between systems while preserving metadata context.

knowledge_archive_upload

Upload a `.archive` file and extract both the backup and its metadata.

Parameters:

  • `archive_base64` - Base64-encoded .archive file
  • `filename` - Original filename (optional, for logging)

Returns:

{
  "success": true,
  "filename": "upload_snapshot_database_20260117.sql.gz",
  "path": "/var/backups/fortemi/upload_snapshot_database_20260117.sql.gz",
  "size_bytes": 1234567,
  "size_human": "1.23 MB",
  "metadata": {
    "title": "Pre-migration backup",
    "description": "Full backup before schema changes",
    "backup_type": "snapshot",
    "note_count": 42
  }
}

Workflow for transferring backups: 1. Source system: `list_backups` → `knowledge_archive_download` 2. Transfer the .archive file 3. Target system: `knowledge_archive_upload` → `database_restore`

REST API Endpoints

The backup system exposes REST API endpoints that can be called directly or via MCP tools.

GET /api/v1/backup/export

Export all notes as a complete JSON export.

Query Parameters:

  • `starred_only` - Only export starred notes (boolean)
  • `tags` - Comma-separated list of tags to filter by
  • `created_after` - Only notes created after this date (ISO 8601)
  • `created_before` - Only notes created before this date (ISO 8601)

Example:

# Export all notes
curl http://localhost:3000/api/v1/backup/export

# Export starred notes created in 2024
curl "http://localhost:3000/api/v1/backup/export?starred_only=true&created_after=2024-01-01T00:00:00Z"

Response:

{
  "manifest": {
    "version": "1.2.0",
    "format": "matric-backup",
    "created_at": "2026-01-17T12:00:00Z",
    "counts": { "notes": 150, "collections": 5, "tags": 30, "templates": 3 }
  },
  "notes": [...],
  "collections": [...],
  "tags": [...],
  "templates": [...]
}

POST /api/v1/backup/trigger

Trigger an immediate database backup using the backup script.

Request Body (optional):

{
  "destinations": ["local", "s3", "rsync"],
  "dry_run": false
}

Example:

# Trigger backup to all destinations
curl -X POST http://localhost:3000/api/v1/backup/trigger

# Dry run (preview only)
curl -X POST http://localhost:3000/api/v1/backup/trigger \
  -H "Content-Type: application/json" \
  -d '{"dry_run": true}'

Response:

{
  "status": "success",
  "output": "[2026-01-17 12:00:00] Starting Fortémi backup...\
...",
  "timestamp": "2026-01-17T12:00:00Z"
}

GET /api/v1/backup/status

Get the current status of the backup system.

Example:

curl http://localhost:3000/api/v1/backup/status

Response:

{
  "backup_directory": "/var/backups/fortemi",
  "disk_usage": "1.2G",
  "backup_count": 7,
  "latest_backup": {
    "path": "/var/backups/fortemi/matric_backup_20260117_120000.sql.gz",
    "filename": "matric_backup_20260117_120000.sql.gz",
    "size_bytes": 52428800,
    "modified": "2026-01-17T12:00:00Z"
  },
  "status": "healthy"
}

Status Values:

  • `healthy` - Backups exist and are recent
  • `no_backups` - No backup files found (directory auto-created if needed)
  • `cannot_create_directory: <error>` - Failed to create backup directory (permission error)

Note: The backup directory is automatically created if it doesn't exist. If creation fails due to permissions, the status will indicate the error.

GET /api/v1/backup/knowledge-shard

Create a comprehensive knowledge shard with selected components.

Query Parameters:

  • `include` - Comma-separated components: `notes,collections,tags,templates,links`
  • Default: `notes,collections,tags,templates,links`

Example:

# Knowledge shard with all data
curl http://localhost:3000/api/v1/backup/knowledge-shard -o backup.shard

# Shard with specific components
curl "http://localhost:3000/api/v1/backup/knowledge-shard?include=notes,links" -o backup.shard

Response: Binary .shard file (gzipped tar) with `Content-Disposition: attachment` header.

Verify shard:

# List contents (it's a gzipped tar internally)
tar -tzf backup.shard

# View manifest
tar -xzf backup.shard -O manifest.json | jq .

POST /api/v1/backup/knowledge-shard/upload

Import a knowledge shard via multipart file upload. Preferred over the legacy JSON/base64 endpoint.

Query Parameters:

  • `on_conflict` - `skip` (default), `replace`, or `merge`
  • `dry_run` - `true` or `false` (default)
  • `include` - Comma-separated components (default: all)
  • `skip_embedding_regen` - `true` or `false` (default)

Example:

# Import shard (multipart upload)
curl -X POST http://localhost:3000/api/v1/backup/knowledge-shard/upload?on_conflict=skip \
  -F "[email protected]"

# Dry run first
curl -X POST http://localhost:3000/api/v1/backup/knowledge-shard/upload?dry_run=true \
  -F "[email protected]"

POST /api/v1/backup/knowledge-shard/import (Legacy)

Import a knowledge shard via JSON body with base64-encoded data. Use the `/upload` endpoint above for large shards.

Request Body:

{
  "shard_base64": "H4sIAAAAAAAA...",
  "include": "notes,collections",
  "dry_run": false,
  "on_conflict": "skip",
  "skip_embedding_regen": false
}

Response:

{
  "status": "success",
  "manifest": { "version": "1.2.0", "profile": "core-v1", "counts": {...} },
  "imported": { "notes": 4, "collections": 2 },
  "skipped": { "notes": 0 },
  "errors": [],
  "dry_run": false
}

Backup Script

The backup script (`scripts/backup.sh`) provides full database backups with:

  • Compression: gzip (default), zstd, xz, or none
  • Encryption: Optional age encryption
  • Multiple destinations: Local, rsync, S3
  • Retention policy: Automatic cleanup of old backups
  • Verification: File integrity checks

Configuration

Create `/etc/Fortémi/backup.conf`:

# Destinations
BACKUP_DEST=/var/backups/fortemi
[email protected]:/backups/matric
BACKUP_REMOTE_S3=s3://my-bucket/matric-backups

# Retention (days)
BACKUP_RETAIN=7

# Compression: gzip, zstd, xz, none
BACKUP_COMPRESS=gzip

# Encryption (optional - path to age public key)
BACKUP_ENCRYPT=/etc/Fortémi/backup-key.pub

# Database
PGUSER=matric
PGPASSFILE=/run/secrets/fortemi-pgpass
PGHOST=localhost
PGPORT=5432
PGDATABASE=matric

# Logging
LOG_FILE=/var/log/Fortémi/backup.log

`PGPASSFILE` must point to an operator-managed secret file owned by the backup service account with mode `0600`. Its PostgreSQL password-file entry uses `hostname:port:database:username:<POSTGRES_PASSWORD>`; do not place the password in the tracked configuration file, command line, or shell history.

Or use environment variables:

BACKUP_DEST=/custom/path ./scripts/backup.sh

Command Line Options

Usage: backup.sh [options]

Options:
  -c, --config FILE      Configuration file
  -d, --destination STR  Specific destination: local, s3, rsync, or all
  -n, --dry-run          Show what would be done without executing
  -v, --verbose          Enable verbose output
  -q, --quiet            Quiet mode (errors only)
  -h, --help             Show help

Systemd Integration

Service Unit

The service unit (`deploy/matric-backup.service`) runs the backup script with:

  • Resource limits (50% CPU, 2GB RAM)
  • Security hardening (PrivateTmp, ProtectSystem)
  • 1-hour timeout
  • Journal logging

Timer Unit

The timer unit (`deploy/matric-backup.timer`) schedules backups:

  • Daily at 2:30 AM
  • 15-minute randomized delay
  • Persistent (catches up after downtime)
  • 5-minute delay on first boot

Installation

# Copy unit files
sudo cp deploy/matric-backup.service /etc/systemd/system/
sudo cp deploy/matric-backup.timer /etc/systemd/system/

# Reload systemd
sudo systemctl daemon-reload

# Enable and start timer
sudo systemctl enable matric-backup.timer
sudo systemctl start matric-backup.timer

# Verify
systemctl list-timers matric-backup.timer

Manual Trigger

# Run backup immediately
sudo systemctl start matric-backup.service

# Check status
sudo systemctl status matric-backup.service

# View logs
journalctl -u matric-backup.service -f

Restore Procedures

From JSON Export (export_all_notes)

1. Parse the JSON export 2. Use `create_note` or `bulk_create_notes` MCP tools to recreate notes 3. Use `create_collection` to recreate collections 4. Use `create_template` to recreate templates

From pg_dump Backup

# 1. Stop the API service
sudo systemctl stop matric-api

# Use an operator-managed PostgreSQL password file (mode 0600)
export PGPASSFILE=/run/secrets/fortemi-pgpass

# 2. List available backups
ls -lh /var/backups/fortemi/

# 3. Drop and recreate database
psql -U matric -h localhost -c "DROP DATABASE matric;"
psql -U matric -h localhost -c "CREATE DATABASE matric;"

# 4. Restore from backup
# For .sql files:
psql -U matric -h localhost -d matric -f backup.sql

# For .sql.gz files:
gunzip -c matric_backup_YYYYMMDD.sql.gz | psql -U matric -h localhost -d matric

# For pg_dump custom format (.sql without compression):
pg_restore -U matric -h localhost -d matric backup.sql

# 5. Verify
psql -U matric -h localhost -d matric -c "SELECT COUNT(*) FROM note;"

# 6. Restart API
sudo systemctl start matric-api

# 7. Verify health
curl http://localhost:3000/health

Encryption Setup

Generate Encryption Key

# Install age
sudo apt install age

# Generate key pair
age-keygen -o /etc/Fortémi/backup-key.txt

# Extract public key
age-keygen -y /etc/Fortémi/backup-key.txt > /etc/Fortémi/backup-key.pub

# Secure private key
sudo chmod 600 /etc/Fortémi/backup-key.txt
sudo chown root:root /etc/Fortémi/backup-key.txt

Configure Encryption

Add to backup.conf:

BACKUP_ENCRYPT=/etc/Fortémi/backup-key.pub

Decrypt Backup

age --decrypt -i /etc/Fortémi/backup-key.txt backup.sql.gz.age > backup.sql.gz

Remote Destinations

S3 Setup

# Install AWS CLI
sudo apt install awscli

# Configure credentials
aws configure

# Test access
aws s3 ls s3://your-bucket/

# Configure backup
echo "BACKUP_REMOTE_S3=s3://your-bucket/matric-backups" >> /etc/Fortémi/backup.conf

Rsync Setup

# Generate SSH key
ssh-keygen -t ed25519 -f ~/.ssh/backup_key -N ""

# Copy to remote server
ssh-copy-id -i ~/.ssh/backup_key [email protected]

# Test connection
ssh -i ~/.ssh/backup_key [email protected] "mkdir -p /backups/matric"

# Configure backup
echo "[email protected]:/backups/matric" >> /etc/Fortémi/backup.conf

Pre-Migration Backup

Always create a backup before running database migrations:

# Create backup with special retention
BACKUP_DEST=/var/backups/fortemi/migrations \
BACKUP_RETAIN=30 \
./scripts/backup.sh

# Verify backup was created
ls -lh /var/backups/fortemi/migrations/

# Now run migration
PGPASSFILE=/run/secrets/fortemi-pgpass \
  psql -U matric -h localhost -d matric -f migrations/new_migration.sql

Monitoring

Check Backup Health

# Via MCP
backup_status()

# Via command line
ls -lh /var/backups/fortemi/ | tail -5

View Logs

# Script logs
tail -f /var/log/Fortémi/backup.log

# Systemd logs
journalctl -u matric-backup.service -f

Alerting

Configure webhook notifications in backup.conf:

NOTIFY_WEBHOOK=https://hooks.slack.com/services/XXX/YYY/ZZZ

Troubleshooting

Backup fails with "permission denied"

# Fix directory permissions
sudo chown -R fortemi:fortemi /var/backups/fortemi
sudo chmod 755 /var/backups/fortemi

Timer not running

# Check timer status
systemctl status matric-backup.timer

# Enable and start
sudo systemctl enable matric-backup.timer
sudo systemctl start matric-backup.timer

S3 upload fails

# Check AWS credentials
aws sts get-caller-identity

# Test S3 access
aws s3 ls s3://your-bucket/

Backup files too large

# Switch to zstd compression (better ratio)
echo "BACKUP_COMPRESS=zstd" >> /etc/Fortémi/backup.conf

Best Practices

1. Test restores regularly - Backups are only useful if they work 2. Use multiple destinations - Local + remote for redundancy 3. Enable encryption for sensitive data 4. Monitor disk space - Set up alerts for backup directory 5. Keep at least 3 backups - Never reduce below minimum retention 6. Backup before migrations - Always create a restore point 7. Document restore procedures - Keep runbooks up to date

File Locations

PathDescription
`/path/to/fortemi/scripts/backup.sh`Backup script
`/etc/Fortémi/backup.conf`Configuration file
`/var/backups/fortemi/`Default backup destination
`/var/log/Fortémi/backup.log`Backup logs
`/etc/systemd/system/matric-backup.service`Systemd service
`/etc/systemd/system/matric-backup.timer`Systemd timer