Files
Charon/scripts/db-recovery.sh
2026-03-04 18:34:49 +00:00

366 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# ==============================================================================
# Charon Database Recovery Script
# ==============================================================================
# This script performs database integrity checks and recovery operations for
# the Charon SQLite database. It can detect corruption, create backups, and
# attempt to recover data using SQLite's .dump command.
#
# Usage: ./scripts/db-recovery.sh [--force]
# --force: Skip confirmation prompts
#
# ⚠️ DEPRECATED: This script is deprecated and will be removed in v2.0.0
# Please use: .github/skills/scripts/skill-runner.sh utility-db-recovery
# For more info: docs/AGENT_SKILLS_MIGRATION.md
echo "⚠️ WARNING: This script is deprecated and will be removed in v2.0.0" >&2
echo " Please use: .github/skills/scripts/skill-runner.sh utility-db-recovery" >&2
echo " For more info: docs/AGENT_SKILLS_MIGRATION.md" >&2
echo "" >&2
sleep 1
# Exit codes:
# 0 - Success (database healthy or recovered)
# 1 - Failure (recovery failed or prerequisites missing)
# ==============================================================================
set -euo pipefail
# Configuration
DOCKER_DB_PATH="/app/data/charon.db"
LOCAL_DB_PATH="backend/data/charon.db"
BACKUP_DIR=""
DB_PATH=""
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
FORCE_MODE=false
# Colors for output (disabled if not a terminal)
if [ -t 1 ]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
else
RED=''
GREEN=''
YELLOW=''
BLUE=''
NC=''
fi
# ==============================================================================
# Helper Functions
# ==============================================================================
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if sqlite3 is available
check_prerequisites() {
if ! command -v sqlite3 &> /dev/null; then
log_error "sqlite3 is not installed or not in PATH"
log_info "Install with: apt-get install sqlite3 (Debian/Ubuntu)"
log_info " or: apk add sqlite (Alpine)"
log_info " or: brew install sqlite (macOS)"
exit 1
fi
log_info "sqlite3 found: $(sqlite3 --version)"
}
# Detect environment (Docker vs Local)
detect_environment() {
if [ -f "$DOCKER_DB_PATH" ]; then
DB_PATH="$DOCKER_DB_PATH"
BACKUP_DIR="/app/data/backups"
log_info "Running in Docker environment"
elif [ -f "$LOCAL_DB_PATH" ]; then
DB_PATH="$LOCAL_DB_PATH"
BACKUP_DIR="backend/data/backups"
log_info "Running in local development environment"
else
log_error "Database not found at expected locations:"
log_error " - Docker: $DOCKER_DB_PATH"
log_error " - Local: $LOCAL_DB_PATH"
exit 1
fi
log_info "Database path: $DB_PATH"
}
# Create backup directory if it doesn't exist
ensure_backup_dir() {
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
log_info "Created backup directory: $BACKUP_DIR"
fi
}
# Create a timestamped backup of the current database
create_backup() {
local backup_file="${BACKUP_DIR}/charon_backup_${TIMESTAMP}.db"
log_info "Creating backup: $backup_file"
cp "$DB_PATH" "$backup_file"
# Also backup WAL and SHM files if they exist
if [ -f "${DB_PATH}-wal" ]; then
cp "${DB_PATH}-wal" "${backup_file}-wal"
log_info "Backed up WAL file"
fi
if [ -f "${DB_PATH}-shm" ]; then
cp "${DB_PATH}-shm" "${backup_file}-shm"
log_info "Backed up SHM file"
fi
log_success "Backup created successfully"
echo "$backup_file"
}
# Run SQLite integrity check
run_integrity_check() {
log_info "Running SQLite integrity check..."
local result
result=$(sqlite3 "$DB_PATH" "PRAGMA integrity_check;" 2>&1) || true
echo "$result"
if [ "$result" = "ok" ]; then
return 0
else
return 1
fi
}
# Attempt to recover database using .dump
recover_database() {
local dump_file="${BACKUP_DIR}/charon_dump_${TIMESTAMP}.sql"
local recovered_db="${BACKUP_DIR}/charon_recovered_${TIMESTAMP}.db"
log_info "Attempting database recovery..."
# Export database using .dump (works even with some corruption)
log_info "Exporting database via .dump command..."
if ! sqlite3 "$DB_PATH" ".dump" > "$dump_file" 2>&1; then
log_error "Failed to export database dump"
return 1
fi
log_success "Database dump created: $dump_file"
# Check if dump file has content
if [ ! -s "$dump_file" ]; then
log_error "Dump file is empty - no data to recover"
return 1
fi
# Create new database from dump
log_info "Creating new database from dump..."
if ! sqlite3 "$recovered_db" < "$dump_file" 2>&1; then
log_error "Failed to create database from dump"
return 1
fi
log_success "Recovered database created: $recovered_db"
# Verify recovered database integrity
log_info "Verifying recovered database integrity..."
local verify_result
verify_result=$(sqlite3 "$recovered_db" "PRAGMA integrity_check;" 2>&1) || true
if [ "$verify_result" != "ok" ]; then
log_error "Recovered database failed integrity check"
log_error "Result: $verify_result"
return 1
fi
log_success "Recovered database passed integrity check"
# Replace original with recovered database
log_info "Replacing original database with recovered version..."
# Remove old WAL/SHM files first
rm -f "${DB_PATH}-wal" "${DB_PATH}-shm"
# Move recovered database to original location
mv "$recovered_db" "$DB_PATH"
log_success "Database replaced successfully"
return 0
}
# Enable WAL mode on database
enable_wal_mode() {
log_info "Enabling WAL (Write-Ahead Logging) mode..."
local current_mode
current_mode=$(sqlite3 "$DB_PATH" "PRAGMA journal_mode;" 2>&1) || true
if [ "$current_mode" = "wal" ]; then
log_info "WAL mode already enabled"
return 0
fi
if sqlite3 "$DB_PATH" "PRAGMA journal_mode=WAL;" > /dev/null 2>&1; then
log_success "WAL mode enabled"
return 0
else
log_warn "Failed to enable WAL mode (database may be locked)"
return 1
fi
}
# Cleanup old backups (keep last 10)
cleanup_old_backups() {
log_info "Cleaning up old backups (keeping last 10)..."
local backup_count
backup_count=$(find "$BACKUP_DIR" -name "charon_backup_*.db" -type f 2>/dev/null | wc -l)
if [ "$backup_count" -gt 10 ]; then
find "$BACKUP_DIR" -name "charon_backup_*.db" -type f -printf '%T@ %p\n' 2>/dev/null | \
sort -n | head -n -10 | cut -d' ' -f2- | \
while read -r file; do
rm -f "$file" "${file}-wal" "${file}-shm"
log_info "Removed old backup: $file"
done
fi
}
# Parse command line arguments
parse_args() {
while [ $# -gt 0 ]; do
case "$1" in
--force|-f)
FORCE_MODE=true
shift
;;
--help|-h)
echo "Usage: $0 [--force]"
echo ""
echo "Options:"
echo " --force, -f Skip confirmation prompts"
echo " --help, -h Show this help message"
exit 0
;;
*)
log_error "Unknown option: $1"
exit 1
;;
esac
done
}
# ==============================================================================
# Main Script
# ==============================================================================
main() {
echo "=============================================="
echo " Charon Database Recovery Tool"
echo "=============================================="
echo ""
parse_args "$@"
# Step 1: Check prerequisites
check_prerequisites
# Step 2: Detect environment
detect_environment
# Step 3: Ensure backup directory exists
ensure_backup_dir
# Step 4: Create backup before any operations
local backup_file
backup_file=$(create_backup)
echo ""
# Step 5: Run integrity check
echo "=============================================="
echo " Integrity Check Results"
echo "=============================================="
local integrity_result
if integrity_result=$(run_integrity_check); then
echo "$integrity_result"
log_success "Database integrity check passed!"
echo ""
# Even if healthy, ensure WAL mode is enabled
enable_wal_mode
# Cleanup old backups
cleanup_old_backups
echo ""
echo "=============================================="
echo " Summary"
echo "=============================================="
log_success "Database is healthy"
log_info "Backup stored at: $backup_file"
exit 0
fi
# Database has issues
echo "$integrity_result"
log_error "Database integrity check FAILED"
echo ""
# Step 6: Confirm recovery (unless force mode)
if [ "$FORCE_MODE" != "true" ]; then
echo -e "${YELLOW}WARNING: Database corruption detected!${NC}"
echo "This script will attempt to recover the database."
echo "A backup has already been created at: $backup_file"
echo ""
read -p "Continue with recovery? (y/N): " -r confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
log_info "Recovery cancelled by user"
exit 1
fi
fi
# Step 7: Attempt recovery
echo ""
echo "=============================================="
echo " Recovery Process"
echo "=============================================="
if recover_database; then
# Step 8: Enable WAL mode on recovered database
enable_wal_mode
# Cleanup old backups
cleanup_old_backups
echo ""
echo "=============================================="
echo " Summary"
echo "=============================================="
log_success "Database recovery completed successfully!"
log_info "Original backup: $backup_file"
log_info "Please restart the Charon application"
exit 0
else
echo ""
echo "=============================================="
echo " Summary"
echo "=============================================="
log_error "Database recovery FAILED"
log_info "Your original database backup is at: $backup_file"
log_info "SQL dump (if created) is in: $BACKUP_DIR"
log_info "Manual intervention may be required"
exit 1
fi
}
# Run main function with all arguments
main "$@"