Complete Aphoria claims system overhaul: - A1: Rename ExtractedClaim to Observation (extractors produce observations, not claims) - A2: Add AuthoredClaim with full provenance, invariants, and authority tiers - A3: Verify engine comparing observations against authored claims, CLI + formatters - A4: Corpus as first-class assertions with predicate indexing, authority lens, trust packs - A5: Coverage analysis, explain/docs generation, self-audit extractor, claim suggester skill Also includes: 42 extractors updated for Observation type, verifiable_predicates trait, conflict detection with comparison modes, claims TOML persistence, Grafana dashboard, backup/restore scripts, and comprehensive test coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
221 lines
6.8 KiB
Bash
Executable File
221 lines
6.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# StemeDB Restore Script
|
|
#
|
|
# Restores WAL and database files from a backup created by backup-stemedb.sh.
|
|
#
|
|
# Usage:
|
|
# ./scripts/restore-stemedb.sh backups/stemedb-backup-20260208-120000/
|
|
# ./scripts/restore-stemedb.sh backups/stemedb-backup-*/ --force
|
|
#
|
|
# Safety:
|
|
# - Checks that StemeDB is NOT running before restore
|
|
# - Refuses to overwrite non-empty target dirs without --force
|
|
# - With --force, renames existing dirs (never deletes)
|
|
#
|
|
# Exit codes:
|
|
# 0 - Restore completed successfully
|
|
# 1 - Restore failed
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
readonly API_HOST="${STEMEDB_BIND_ADDR:-127.0.0.1:18180}"
|
|
readonly TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
|
|
|
# Colors (if terminal supports it)
|
|
if [[ -t 1 ]]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
else
|
|
RED=''
|
|
GREEN=''
|
|
YELLOW=''
|
|
BLUE=''
|
|
NC=''
|
|
fi
|
|
|
|
# Logging helpers
|
|
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
fail() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; }
|
|
|
|
# Defaults
|
|
BACKUP_PATH=""
|
|
TARGET_WAL="${STEMEDB_WAL_DIR:-${PROJECT_DIR}/data/wal}"
|
|
TARGET_DB="${STEMEDB_DB_DIR:-${PROJECT_DIR}/data/db}"
|
|
FORCE=false
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--target-wal)
|
|
TARGET_WAL="$2"
|
|
shift 2
|
|
;;
|
|
--target-db)
|
|
TARGET_DB="$2"
|
|
shift 2
|
|
;;
|
|
--force)
|
|
FORCE=true
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
echo "Usage: $0 <backup-path> [--target-wal <dir>] [--target-db <dir>] [--force]"
|
|
echo ""
|
|
echo "Restore StemeDB from a backup."
|
|
echo ""
|
|
echo "Arguments:"
|
|
echo " <backup-path> Path to backup directory (must contain backup-metadata.json)"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --target-wal <dir> Target WAL directory (default: data/wal)"
|
|
echo " --target-db <dir> Target DB directory (default: data/db)"
|
|
echo " --force Overwrite existing data (renames to .pre-restore-TIMESTAMP)"
|
|
echo " --help Show this help message"
|
|
echo ""
|
|
echo "Environment:"
|
|
echo " STEMEDB_WAL_DIR WAL directory (default: data/wal)"
|
|
echo " STEMEDB_DB_DIR Database directory (default: data/db)"
|
|
echo " STEMEDB_BIND_ADDR API address for running check (default: 127.0.0.1:18180)"
|
|
exit 0
|
|
;;
|
|
-*)
|
|
fail "Unknown option: $1 (use --help for usage)"
|
|
;;
|
|
*)
|
|
if [[ -z "$BACKUP_PATH" ]]; then
|
|
BACKUP_PATH="$1"
|
|
else
|
|
fail "Unexpected argument: $1"
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$BACKUP_PATH" ]]; then
|
|
fail "Backup path is required. Usage: $0 <backup-path> [--force]"
|
|
fi
|
|
|
|
main() {
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " StemeDB Restore"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
# Validate backup
|
|
if [[ ! -d "$BACKUP_PATH" ]]; then
|
|
fail "Backup directory not found: ${BACKUP_PATH}"
|
|
fi
|
|
|
|
if [[ ! -f "${BACKUP_PATH}/backup-metadata.json" ]]; then
|
|
fail "Not a valid backup: missing backup-metadata.json in ${BACKUP_PATH}"
|
|
fi
|
|
|
|
info "Backup: ${BACKUP_PATH}"
|
|
info "Metadata:"
|
|
cat "${BACKUP_PATH}/backup-metadata.json" | sed 's/^/ /'
|
|
echo ""
|
|
|
|
# Check StemeDB is NOT running
|
|
info "Checking that StemeDB is not running..."
|
|
if curl -s --connect-timeout 2 "http://${API_HOST}/v1/health" > /dev/null 2>&1; then
|
|
fail "StemeDB is running at ${API_HOST}. Stop the server before restoring."
|
|
fi
|
|
success "StemeDB is not running"
|
|
|
|
# Check what's in the backup
|
|
local has_wal=false
|
|
local has_db=false
|
|
[[ -d "${BACKUP_PATH}/wal" ]] && has_wal=true
|
|
[[ -d "${BACKUP_PATH}/db" ]] && has_db=true
|
|
|
|
if [[ "$has_wal" == "false" ]]; then
|
|
fail "Backup contains no WAL directory"
|
|
fi
|
|
|
|
# Handle existing target directories
|
|
if [[ -d "$TARGET_WAL" && -n "$(ls -A "$TARGET_WAL" 2>/dev/null)" ]]; then
|
|
if [[ "$FORCE" == "false" ]]; then
|
|
fail "Target WAL directory is not empty: ${TARGET_WAL}\n Use --force to rename existing data and proceed."
|
|
fi
|
|
local renamed="${TARGET_WAL}.pre-restore-${TIMESTAMP}"
|
|
warn "Renaming existing WAL: ${TARGET_WAL} -> ${renamed}"
|
|
mv "$TARGET_WAL" "$renamed"
|
|
fi
|
|
|
|
if [[ "$has_db" == "true" && -d "$TARGET_DB" && -n "$(ls -A "$TARGET_DB" 2>/dev/null)" ]]; then
|
|
if [[ "$FORCE" == "false" ]]; then
|
|
fail "Target DB directory is not empty: ${TARGET_DB}\n Use --force to rename existing data and proceed."
|
|
fi
|
|
local renamed="${TARGET_DB}.pre-restore-${TIMESTAMP}"
|
|
warn "Renaming existing DB: ${TARGET_DB} -> ${renamed}"
|
|
mv "$TARGET_DB" "$renamed"
|
|
fi
|
|
|
|
# Restore WAL
|
|
info "Restoring WAL..."
|
|
mkdir -p "$TARGET_WAL"
|
|
rsync -a "${BACKUP_PATH}/wal/" "${TARGET_WAL}/"
|
|
local wal_files
|
|
wal_files=$(find "$TARGET_WAL" -type f | wc -l)
|
|
success "WAL restored: ${wal_files} files"
|
|
|
|
# Verify WAL header magic bytes (STEM = first 4 bytes)
|
|
local wal_valid=true
|
|
for wal_file in "${TARGET_WAL}"/*.wal; do
|
|
[[ -f "$wal_file" ]] || continue
|
|
local magic
|
|
magic=$(head -c 4 "$wal_file" | od -A n -t x1 | tr -d ' ')
|
|
if [[ "$magic" == "5354454d" ]]; then
|
|
success "WAL magic OK: $(basename "$wal_file")"
|
|
else
|
|
warn "WAL magic mismatch: $(basename "$wal_file") (got: ${magic})"
|
|
wal_valid=false
|
|
fi
|
|
done
|
|
|
|
# Restore DB (if present in backup)
|
|
if [[ "$has_db" == "true" ]]; then
|
|
info "Restoring DB..."
|
|
mkdir -p "$TARGET_DB"
|
|
rsync -a "${BACKUP_PATH}/db/" "${TARGET_DB}/"
|
|
local db_files
|
|
db_files=$(find "$TARGET_DB" -type f | wc -l)
|
|
success "DB restored: ${db_files} files"
|
|
else
|
|
info "Backup is WAL-only, skipping DB restore"
|
|
fi
|
|
|
|
# Summary
|
|
echo ""
|
|
echo "=========================================="
|
|
if [[ "$wal_valid" == "true" ]]; then
|
|
echo -e " ${GREEN}Restore complete${NC}"
|
|
else
|
|
echo -e " ${YELLOW}Restore complete (with WAL warnings)${NC}"
|
|
fi
|
|
echo "=========================================="
|
|
echo ""
|
|
echo " WAL: ${TARGET_WAL}"
|
|
if [[ "$has_db" == "true" ]]; then
|
|
echo " DB: ${TARGET_DB}"
|
|
fi
|
|
echo ""
|
|
echo "Start StemeDB with:"
|
|
echo " STEMEDB_WAL_DIR=${TARGET_WAL} STEMEDB_DB_DIR=${TARGET_DB} cargo run --bin stemedb-api"
|
|
echo ""
|
|
}
|
|
|
|
main "$@"
|