diff --git a/plex/nuclear-plex-recovery.sh b/plex/nuclear-plex-recovery.sh index e97c578..4639a99 100755 --- a/plex/nuclear-plex-recovery.sh +++ b/plex/nuclear-plex-recovery.sh @@ -66,6 +66,7 @@ NC='\033[0m' # No Color # Configuration PLEX_DB_DIR="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases" +PLEX_SQLITE="/usr/lib/plexmediaserver/Plex SQLite" PLEX_USER="plex" PLEX_GROUP="plex" BACKUP_TIMESTAMP=$(date +"%Y%m%d_%H%M%S") @@ -244,27 +245,66 @@ restore_from_backup() { fi } -# Function to verify restored databases +# Function to verify restored databases (structural + FTS) verify_databases() { print_status "$YELLOW" "Verifying restored databases..." - # Check main database - if sqlite3 "${PLEX_DB_DIR}/com.plexapp.plugins.library.db" "PRAGMA integrity_check;" | grep -q "ok"; then - print_status "$GREEN" "Main database integrity check: PASSED" - else - print_status "$RED" "Main database integrity check: FAILED" - return 1 + # Use Plex's bundled SQLite for ICU compatibility; fall back to system sqlite3 + local sqlite_bin="sqlite3" + if [[ -x "$PLEX_SQLITE" ]]; then + sqlite_bin="$PLEX_SQLITE" fi - # Check blobs database - if sqlite3 "${PLEX_DB_DIR}/com.plexapp.plugins.library.blobs.db" "PRAGMA integrity_check;" | grep -q "ok"; then - print_status "$GREEN" "Blobs database integrity check: PASSED" + local overall_ok=true + + for db_file in \ + "${PLEX_DB_DIR}/com.plexapp.plugins.library.db" \ + "${PLEX_DB_DIR}/com.plexapp.plugins.library.blobs.db"; do + + local db_name + db_name=$(basename "$db_file") + + if [[ ! -f "$db_file" ]]; then + print_status "$RED" "$db_name: NOT FOUND" + overall_ok=false + continue + fi + + # Structural integrity + local result + result=$("$sqlite_bin" "$db_file" "PRAGMA integrity_check;" 2>&1) + if [[ "$result" == "ok" ]]; then + print_status "$GREEN" "$db_name structural integrity: PASSED" + else + print_status "$RED" "$db_name structural integrity: FAILED" + overall_ok=false + fi + + # FTS index integrity + local fts_tables + fts_tables=$("$sqlite_bin" "$db_file" \ + "SELECT name FROM sqlite_master WHERE type='table' AND sql LIKE '%fts%';" 2>/dev/null) || true + if [[ -n "$fts_tables" ]]; then + while IFS= read -r table; do + [[ -z "$table" ]] && continue + local fts_result + fts_result=$("$sqlite_bin" "$db_file" \ + "INSERT INTO ${table}(${table}) VALUES('integrity-check');" 2>&1) || true + if [[ -n "$fts_result" ]]; then + print_status "$RED" "$db_name FTS index '$table': DAMAGED" + overall_ok=false + fi + done <<< "$fts_tables" + fi + done + + if [[ "$overall_ok" == true ]]; then + print_status "$GREEN" "All database integrity checks passed!" + return 0 else - print_status "$RED" "Blobs database integrity check: FAILED" + print_status "$RED" "One or more database checks failed!" return 1 fi - - print_status "$GREEN" "All database integrity checks passed!" } # Function to fix ownership issues diff --git a/plex/plex-db-manager.sh b/plex/plex-db-manager.sh index 62b643e..f6cd70e 100755 --- a/plex/plex-db-manager.sh +++ b/plex/plex-db-manager.sh @@ -52,10 +52,26 @@ readonly MAIN_DB="$PLEX_DB_DIR/com.plexapp.plugins.library.db" readonly BLOBS_DB="$PLEX_DB_DIR/com.plexapp.plugins.library.blobs.db" readonly BACKUP_ROOT="/mnt/share/media/backups/plex" -SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" readonly SCRIPT_DIR readonly LOG_FILE="$SCRIPT_DIR/logs/db-manager-$(date +%Y%m%d_%H%M%S).log" +# DBRepair.sh location — searched in order: script dir, database dir, /usr/local/bin +find_dbrepair() { + local candidates=( + "${SCRIPT_DIR}/DBRepair.sh" + "${PLEX_DB_DIR}/DBRepair.sh" + "/usr/local/bin/DBRepair.sh" + ) + for path in "${candidates[@]}"; do + if [[ -x "$path" ]]; then + echo "$path" + return 0 + fi + done + return 1 +} + # Create log directory mkdir -p "$SCRIPT_DIR/logs" @@ -210,26 +226,54 @@ check_database_integrity() { fi fi - # Run integrity check + # Run structural integrity check local integrity_result integrity_result=$(sudo "$PLEX_SQLITE" "$db_file" "PRAGMA integrity_check;" 2>&1) local check_exit_code=$? - if [[ $check_exit_code -ne 0 ]]; then - log_error "Failed to run integrity check on $db_name" + if [[ $check_exit_code -ne 0 && -z "$integrity_result" ]]; then + log_error "Failed to open database: $db_name (exit code $check_exit_code)" return 1 fi - if echo "$integrity_result" | grep -q "^ok$"; then - log_success "Database integrity check passed: $db_name" - return 0 + local struct_ok=true + if [[ "$integrity_result" == "ok" ]]; then + log_success "Structural integrity check passed: $db_name" else - log_warning "Database integrity issues detected in $db_name:" - echo "$integrity_result" | while IFS= read -r line; do + struct_ok=false + log_warning "Structural integrity issues detected in $db_name:" + echo "$integrity_result" | head -n 10 | while IFS= read -r line; do log_warning " $line" done - return 1 fi + + # FTS (Full-Text Search) index integrity check + # Standard PRAGMA integrity_check does NOT detect FTS corruption. + local fts_ok=true + local fts_tables + fts_tables=$(sudo "$PLEX_SQLITE" "$db_file" \ + "SELECT name FROM sqlite_master WHERE type='table' AND sql LIKE '%fts%';" 2>/dev/null) || true + + if [[ -n "$fts_tables" ]]; then + log_message "Checking FTS (Full-Text Search) indexes in $db_name..." + while IFS= read -r table; do + [[ -z "$table" ]] && continue + local fts_result + fts_result=$(sudo "$PLEX_SQLITE" "$db_file" \ + "INSERT INTO ${table}(${table}) VALUES('integrity-check');" 2>&1) || true + if [[ -n "$fts_result" ]]; then + fts_ok=false + log_warning "FTS index '${table}' — DAMAGED: $fts_result" + else + log_success "FTS index '${table}' — OK" + fi + done <<< "$fts_tables" + fi + + if [[ "$struct_ok" == true && "$fts_ok" == true ]]; then + return 0 + fi + return 1 } # Check all databases @@ -315,20 +359,73 @@ main() { ;; "repair") - echo -e "${RED}${BOLD}⚠️ REPAIR FUNCTIONALITY TEMPORARILY DISABLED${RESET}" - echo -e "${YELLOW}Database repairs are disabled until corruption issues are resolved.${RESET}" - echo -e "${CYAN}Use the individual repair scripts if manual intervention is needed:${RESET}" - echo -e " ${DIM}- plex-database-repair.sh${RESET}" - echo -e " ${DIM}- recover-plex-database.sh${RESET}" - echo -e " ${DIM}- nuclear-plex-recovery.sh${RESET}" - exit 2 + print_header + check_prerequisites + + local dbrepair_bin + if dbrepair_bin=$(find_dbrepair); then + log_success "Found DBRepair.sh: $dbrepair_bin" + log_message "Running: stop → auto (check + repair + reindex + FTS rebuild) → start → exit" + if sudo "$dbrepair_bin" stop auto start exit; then + log_success "DBRepair automatic repair completed successfully" + exit 0 + else + log_error "DBRepair automatic repair failed" + exit 2 + fi + else + echo -e "${RED}${BOLD}⚠️ DBRepair.sh NOT FOUND${RESET}" + echo -e "${YELLOW}Install DBRepair for repair/optimize/reindex/FTS-rebuild:${RESET}" + echo -e " ${CYAN}wget -O ${SCRIPT_DIR}/DBRepair.sh https://github.com/ChuckPa/PlexDBRepair/releases/latest/download/DBRepair.sh${RESET}" + echo -e " ${CYAN}chmod +x ${SCRIPT_DIR}/DBRepair.sh${RESET}" + echo -e "${YELLOW}Then re-run: $(basename "$0") repair${RESET}" + exit 2 + fi ;; "nuclear") - echo -e "${RED}${BOLD}⚠️ NUCLEAR RECOVERY TEMPORARILY DISABLED${RESET}" - echo -e "${YELLOW}Nuclear recovery is disabled until corruption issues are resolved.${RESET}" - echo -e "${CYAN}Use nuclear-plex-recovery.sh directly if absolutely necessary.${RESET}" - exit 2 + print_header + check_prerequisites + + echo -e "\n${RED}${BOLD}⚠️ WARNING: NUCLEAR RECOVERY ⚠️${RESET}" + echo -e "${RED}This replaces your Plex database with the best available PMS backup!${RESET}" + echo -e "${YELLOW}All changes since the backup was created will be lost.${RESET}\n" + + echo -e "${CYAN}Type 'YES' to proceed: ${RESET}" + read -r confirmation + if [[ "$confirmation" != "YES" ]]; then + log_message "Nuclear recovery cancelled by user" + exit 0 + fi + + local dbrepair_bin + if dbrepair_bin=$(find_dbrepair); then + log_success "Found DBRepair.sh: $dbrepair_bin" + log_message "Running: stop → replace → reindex → start → exit" + if sudo "$dbrepair_bin" stop replace reindex start exit; then + log_success "Nuclear recovery (replace from backup) completed" + exit 0 + else + log_error "Nuclear recovery failed" + exit 2 + fi + else + # Fallback to dedicated nuclear script + local nuclear_script="${SCRIPT_DIR}/nuclear-plex-recovery.sh" + if [[ -x "$nuclear_script" ]]; then + log_message "DBRepair not found, falling back to nuclear-plex-recovery.sh" + if sudo "$nuclear_script" --auto; then + log_success "Nuclear recovery completed" + exit 0 + else + log_error "Nuclear recovery failed" + exit 2 + fi + else + log_error "Neither DBRepair.sh nor nuclear-plex-recovery.sh found" + exit 2 + fi + fi ;; "help"|"--help"|"-h") diff --git a/plex/plex.sh b/plex/plex.sh index 38989b4..10e2d82 100755 --- a/plex/plex.sh +++ b/plex/plex.sh @@ -70,11 +70,29 @@ readonly RESET='\033[0m' # 🔧 Configuration readonly PLEX_SERVICE="plexmediaserver" +readonly PLEX_SQLITE="/usr/lib/plexmediaserver/Plex SQLite" +readonly PLEX_DB_DIR="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases" SCRIPT_NAME="$(basename "$0")" readonly SCRIPT_NAME SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" readonly SCRIPT_DIR +# DBRepair.sh location — searched in order: script dir, database dir, /usr/local/bin +find_dbrepair() { + local candidates=( + "${SCRIPT_DIR}/DBRepair.sh" + "${PLEX_DB_DIR}/DBRepair.sh" + "/usr/local/bin/DBRepair.sh" + ) + for path in "${candidates[@]}"; do + if [[ -x "$path" ]]; then + echo "$path" + return 0 + fi + done + return 1 +} + # 🎭 ASCII symbols for compatible output readonly CHECKMARK="[✓]" readonly CROSS="[✗]" @@ -114,240 +132,227 @@ show_loading() { printf "\r%s%s %s %s%s\n" "${CYAN}" "${HOURGLASS}" "${message}" "${CHECKMARK}" "${RESET}" } -# 🔧 Enhanced function to repair database issues +# 🔧 Repair database using ChuckPa/DBRepair when available, else manual steps +# DBRepair: https://github.com/ChuckPa/PlexDBRepair repair_database() { print_status "${INFO}" "Attempting to repair Plex database..." "${BLUE}" - local db_dir="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases" - local main_db="$db_dir/com.plexapp.plugins.library.db" - local backup_db="$db_dir/com.plexapp.plugins.library.db.backup.$(date +%Y%m%d_%H%M%S)" - local corrupted_dir="$db_dir/corrupted-$(date +%Y%m%d_%H%M%S)" + local main_db="$PLEX_DB_DIR/com.plexapp.plugins.library.db" if [[ ! -f "$main_db" ]]; then print_status "${CROSS}" "Main database not found at: $main_db" "${RED}" return 1 fi + # ---------- Try DBRepair.sh (preferred) ---------- + local dbrepair_bin + if dbrepair_bin=$(find_dbrepair); then + print_status "${CHECKMARK}" "Found DBRepair.sh: $dbrepair_bin" "${GREEN}" + print_status "${INFO}" "Running: stop → auto (check + repair + reindex + FTS rebuild) → start → exit" "${BLUE}" + + # DBRepair supports scripted mode — pass commands directly + if sudo "$dbrepair_bin" stop auto start exit; then + print_status "${CHECKMARK}" "DBRepair automatic repair completed successfully!" "${GREEN}" + return 0 + else + print_status "${CROSS}" "DBRepair automatic repair failed (exit code $?)" "${RED}" + print_status "${INFO}" "Falling back to manual repair steps..." "${YELLOW}" + fi + else + print_status "${INFO}" "DBRepair.sh not found — using manual repair" "${YELLOW}" + echo -e "${DIM}${CYAN} For better repairs, install DBRepair:${RESET}" + echo -e "${DIM}${CYAN} wget -O ${SCRIPT_DIR}/DBRepair.sh https://github.com/ChuckPa/PlexDBRepair/releases/latest/download/DBRepair.sh${RESET}" + echo -e "${DIM}${CYAN} chmod +x ${SCRIPT_DIR}/DBRepair.sh${RESET}" + fi + + # ---------- Manual fallback (same approach DBRepair uses internally) ---------- + # Prefer Plex's bundled SQLite for ICU compatibility + local sqlite_bin="sqlite3" + if [[ -x "$PLEX_SQLITE" ]]; then + sqlite_bin="$PLEX_SQLITE" + fi + # Stop Plex service first print_status "${INFO}" "Stopping Plex service..." "${BLUE}" sudo systemctl stop "$PLEX_SERVICE" 2>/dev/null || true sleep 3 - # Check if critical tables exist - print_status "${INFO}" "Checking database structure..." "${BLUE}" - local has_metadata_table=false - if sudo -u plex sqlite3 "$main_db" "SELECT name FROM sqlite_master WHERE type='table' AND name='metadata_items';" 2>/dev/null | grep -q metadata_items; then - has_metadata_table=true + # Step 1: WAL checkpoint + if [[ -f "${main_db}-wal" ]]; then + print_status "${INFO}" "Checkpointing WAL journal..." "${BLUE}" + sudo -u plex "$sqlite_bin" "$main_db" "PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null || true fi - if [[ "$has_metadata_table" == "false" ]]; then - print_status "${CROSS}" "Critical table 'metadata_items' is missing! Database is severely corrupted." "${RED}" - print_status "${INFO}" "Attempting recovery from available backups..." "${YELLOW}" + # Step 2: Export → import (the core of DBRepair's repair/optimize) + local ts + ts=$(date +%Y%m%d_%H%M%S) + local sql_dump="/tmp/plex_dump_${ts}.sql" + local new_db="/tmp/plex_repaired_${ts}.db" + local backup_db="$PLEX_DB_DIR/com.plexapp.plugins.library.db-BACKUP-${ts}" - # Find the best recovery candidate - local recovery_db="" - local recovery_candidates=( - "$db_dir/com.plexapp.plugins.library.db.recovery-"* - "$db_dir/com.plexapp.plugins.library.db.20"* - ) - - for candidate in "${recovery_candidates[@]}"; do - if [[ -f "$candidate" && "$candidate" != *"tmp"* && "$candidate" != *"empty"* ]]; then - # Test if this candidate has the metadata_items table - if sudo -u plex sqlite3 "$candidate" "SELECT name FROM sqlite_master WHERE type='table' AND name='metadata_items';" 2>/dev/null | grep -q metadata_items; then - recovery_db="$candidate" - break - fi - fi - done - - if [[ -n "$recovery_db" ]]; then - print_status "${CHECKMARK}" "Found recovery database: $(basename "$recovery_db")" "${GREEN}" - - # Move corrupted database to backup location - print_status "${INFO}" "Moving corrupted database to backup location..." "${BLUE}" - sudo mkdir -p "$corrupted_dir" - sudo mv "$main_db" "$corrupted_dir/" - sudo mv "$main_db-shm" "$corrupted_dir/" 2>/dev/null || true - sudo mv "$main_db-wal" "$corrupted_dir/" 2>/dev/null || true - - # Copy recovery database as new main database - print_status "${INFO}" "Restoring database from recovery file..." "${BLUE}" - if sudo cp "$recovery_db" "$main_db"; then - sudo chown plex:plex "$main_db" - sudo chmod 644 "$main_db" - print_status "${CHECKMARK}" "Database restored successfully!" "${GREEN}" - - # Verify the restored database - print_status "${INFO}" "Verifying restored database..." "${BLUE}" - local integrity_result - integrity_result=$(sudo -u plex sqlite3 "$main_db" "PRAGMA integrity_check;" 2>&1) - - if echo "$integrity_result" | grep -q "ok"; then - print_status "${CHECKMARK}" "Restored database integrity verified!" "${GREEN}" - return 0 - elif echo "$integrity_result" | grep -q "no such collation sequence: icu"; then - print_status "${CROSS}" "ICU collation sequence issue detected!" "${YELLOW}" - print_status "${INFO}" "Attempting ICU-aware recovery..." "${BLUE}" - - # Try ICU-aware recovery script - local icu_script="${SCRIPT_DIR}/icu-aware-recovery.sh" - if [[ -f "$icu_script" ]]; then - if "$icu_script" --auto; then - print_status "${CHECKMARK}" "ICU-aware recovery completed!" "${GREEN}" - return 0 - else - print_status "${CROSS}" "ICU-aware recovery failed!" "${RED}" - fi - else - print_status "${INFO}" "ICU recovery script not found, trying manual fix..." "${YELLOW}" - - # Try to recreate database without ICU dependencies - local temp_db="/tmp/plex_temp_$(date +%Y%m%d_%H%M%S).db" - print_status "${INFO}" "Attempting to dump and recreate database..." "${BLUE}" - - if sudo -u plex sqlite3 "$recovery_db" ".dump" | grep -v "COLLATE icu_" | sudo -u plex sqlite3 "$temp_db"; then - print_status "${INFO}" "Database dump successful, replacing main database..." "${BLUE}" - sudo mv "$temp_db" "$main_db" - sudo chown plex:plex "$main_db" - sudo chmod 644 "$main_db" - - # Verify the recreated database - if sudo -u plex sqlite3 "$main_db" "PRAGMA integrity_check;" 2>/dev/null | grep -q "ok"; then - print_status "${CHECKMARK}" "Database recreated successfully without ICU!" "${GREEN}" - return 0 - fi - fi - - # Clean up temp file if it exists - sudo rm -f "$temp_db" 2>/dev/null || true - fi - - print_status "${CROSS}" "Failed to resolve ICU collation issues!" "${RED}" - return 1 - else - print_status "${CROSS}" "Restored database failed integrity check!" "${RED}" - print_status "${INFO}" "Integrity check result:" "${YELLOW}" - echo -e "${DIM}${YELLOW} $integrity_result${RESET}" - return 1 - fi - else - print_status "${CROSS}" "Failed to restore database!" "${RED}" - return 1 - fi - else - print_status "${CROSS}" "No valid recovery databases found!" "${RED}" - print_status "${INFO}" "Available options:" "${YELLOW}" - echo -e "${DIM}${YELLOW} 1. Check manual backups in /mnt/share/media/backups/plex/${RESET}" - echo -e "${DIM}${YELLOW} 2. Let Plex rebuild database (will lose all metadata)${RESET}" - echo -e "${DIM}${YELLOW} 3. Run: sudo rm '$main_db' && sudo systemctl start plexmediaserver${RESET}" - return 1 - fi - fi - - # Create backup of current database - print_status "${INFO}" "Creating backup of current database..." "${BLUE}" - if ! sudo cp "$main_db" "$backup_db"; then - print_status "${CROSS}" "Failed to create database backup!" "${RED}" + print_status "${INFO}" "Exporting database to SQL..." "${BLUE}" + if sudo -u plex "$sqlite_bin" "$main_db" ".dump" | sudo -u plex tee "$sql_dump" >/dev/null 2>&1; then + print_status "${CHECKMARK}" "SQL export completed ($(du -sh "$sql_dump" | cut -f1))" "${GREEN}" + else + print_status "${CROSS}" "SQL export failed — database may be too damaged for this method" "${RED}" + rm -f "$sql_dump" 2>/dev/null + print_status "${INFO}" "Install DBRepair.sh for more robust repair capabilities" "${YELLOW}" return 1 fi - print_status "${CHECKMARK}" "Database backed up to: $backup_db" "${GREEN}" - - # Try to vacuum the database - print_status "${INFO}" "Running VACUUM on database..." "${BLUE}" - if sudo -u plex sqlite3 "$main_db" "VACUUM;" 2>/dev/null; then - print_status "${CHECKMARK}" "Database VACUUM completed successfully" "${GREEN}" - - # Test integrity again - if sudo -u plex sqlite3 "$main_db" "PRAGMA integrity_check;" 2>/dev/null | grep -q "ok"; then - print_status "${CHECKMARK}" "Database integrity restored!" "${GREEN}" - print_status "${INFO}" "You can now try starting Plex again" "${BLUE}" - return 0 - else - print_status "${CROSS}" "Database still corrupted after VACUUM" "${RED}" - fi + print_status "${INFO}" "Importing into fresh database..." "${BLUE}" + if sudo cat "$sql_dump" | sudo -u plex "$sqlite_bin" "$new_db" 2>/dev/null; then + print_status "${CHECKMARK}" "Import into fresh database succeeded" "${GREEN}" else - print_status "${CROSS}" "VACUUM operation failed" "${RED}" + print_status "${CROSS}" "Import failed" "${RED}" + rm -f "$sql_dump" "$new_db" 2>/dev/null + return 1 + fi + rm -f "$sql_dump" 2>/dev/null + + # Step 3: Verify repaired database (structural + FTS) + print_status "${INFO}" "Verifying repaired database..." "${BLUE}" + local verify_out + verify_out=$(sudo -u plex "$sqlite_bin" "$new_db" "PRAGMA integrity_check;" 2>&1) + if [[ "$verify_out" != "ok" ]]; then + print_status "${CROSS}" "Repaired database failed integrity check" "${RED}" + rm -f "$new_db" 2>/dev/null + return 1 fi - # Try reindex as last resort - print_status "${INFO}" "Attempting REINDEX operation..." "${BLUE}" - if sudo -u plex sqlite3 "$main_db" "REINDEX;" 2>/dev/null; then - print_status "${CHECKMARK}" "Database REINDEX completed" "${GREEN}" + # Step 4: Reindex (rebuilds all indexes INCLUDING FTS) + print_status "${INFO}" "Rebuilding indexes (including FTS)..." "${BLUE}" + sudo -u plex "$sqlite_bin" "$new_db" "REINDEX;" 2>/dev/null || true - # Test integrity one more time - if sudo -u plex sqlite3 "$main_db" "PRAGMA integrity_check;" 2>/dev/null | grep -q "ok"; then - print_status "${CHECKMARK}" "Database integrity restored after REINDEX!" "${GREEN}" - return 0 + # Rebuild FTS index content explicitly + local fts_tables + fts_tables=$(sudo -u plex "$sqlite_bin" "$new_db" \ + "SELECT name FROM sqlite_master WHERE type='table' AND sql LIKE '%fts%';" 2>/dev/null) || true + if [[ -n "$fts_tables" ]]; then + while IFS= read -r table; do + [[ -z "$table" ]] && continue + print_status "${INFO}" "Rebuilding FTS index: $table" "${BLUE}" + sudo -u plex "$sqlite_bin" "$new_db" \ + "INSERT INTO ${table}(${table}) VALUES('rebuild');" 2>/dev/null || true + done <<< "$fts_tables" + fi + + # Step 5: Swap databases + print_status "${INFO}" "Backing up old database and activating repaired copy..." "${BLUE}" + sudo cp "$main_db" "$backup_db" + sudo mv "$new_db" "$main_db" + sudo chown plex:plex "$main_db" + sudo chmod 644 "$main_db" + # Remove stale journals for the old database + sudo rm -f "${main_db}-shm" "${main_db}-wal" 2>/dev/null || true + + print_status "${CHECKMARK}" "Database repaired and activated. Backup at: $(basename "$backup_db")" "${GREEN}" + return 0 +} + +# 🔍 Function to check FTS (Full-Text Search) index integrity +# Standard PRAGMA integrity_check does NOT detect FTS corruption. +# This is the exact class of damage shown in the user's screenshot. +check_fts_integrity() { + local db_file="$1" + local sqlite_bin="${2:-sqlite3}" # Use Plex SQLite if available + local issues=0 + + # Discover FTS tables dynamically from sqlite_master + local fts_tables + fts_tables=$(sudo -u plex "$sqlite_bin" "$db_file" \ + "SELECT name FROM sqlite_master WHERE type='table' AND sql LIKE '%fts%';" 2>/dev/null) || return 0 + + if [[ -z "$fts_tables" ]]; then + return 0 # No FTS tables — nothing to check + fi + + print_status "${INFO}" "Checking FTS (Full-Text Search) indexes..." "${BLUE}" + + while IFS= read -r table; do + [[ -z "$table" ]] && continue + local result + result=$(sudo -u plex "$sqlite_bin" "$db_file" \ + "INSERT INTO ${table}(${table}) VALUES('integrity-check');" 2>&1) || true + if [[ -n "$result" ]]; then + print_status "${CROSS}" "FTS index '${table}' — DAMAGED" "${RED}" + echo -e "${DIM}${YELLOW} $result${RESET}" + ((issues++)) + else + print_status "${CHECKMARK}" "FTS index '${table}' — OK" "${GREEN}" fi + done <<< "$fts_tables" + + if (( issues > 0 )); then + print_status "${CROSS}" "FTS integrity check complete. $issues index(es) damaged." "${RED}" + print_status "${INFO}" "Run: ${SCRIPT_NAME} repair (uses DBRepair reindex to rebuild FTS)" "${YELLOW}" + return 1 fi - - print_status "${CROSS}" "All repair attempts failed" "${RED}" - print_status "${INFO}" "Manual intervention required. Consider:" "${YELLOW}" - echo -e "${DIM}${YELLOW} 1. Restore from external backup using restore-plex.sh${RESET}" - echo -e "${DIM}${YELLOW} 2. Use nuclear recovery: ./nuclear-plex-recovery.sh${RESET}" - echo -e "${DIM}${YELLOW} 3. Check corrupted database moved to: $corrupted_dir${RESET}" - - return 1 + return 0 } # 🔍 Function to check database integrity check_database_integrity() { print_status "${INFO}" "Checking database integrity..." "${BLUE}" - local db_dir="/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases" - local main_db="$db_dir/com.plexapp.plugins.library.db" + local main_db="$PLEX_DB_DIR/com.plexapp.plugins.library.db" if [[ ! -f "$main_db" ]]; then print_status "${CROSS}" "Main database not found at: $main_db" "${RED}" return 1 fi + # Prefer Plex's bundled SQLite for ICU compatibility + local sqlite_bin="sqlite3" + if [[ -x "$PLEX_SQLITE" ]]; then + sqlite_bin="$PLEX_SQLITE" + fi + # Clean up stale WAL/SHM journals left by non-clean shutdowns. - # If Plex was killed or the system rebooted unexpectedly, these files - # can prevent sqlite3 from opening the database at all, producing - # false "corruption" reports. if [[ -f "${main_db}-wal" ]]; then print_status "${INFO}" "WAL journal found — attempting checkpoint before integrity check..." "${BLUE}" - # TRUNCATE mode replays the journal and then removes it. - if ! sudo -u plex sqlite3 "$main_db" "PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null; then + if ! sudo -u plex "$sqlite_bin" "$main_db" "PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null; then print_status "${INFO}" "WAL checkpoint failed (non-critical, continuing check)" "${YELLOW}" fi fi - # Run the actual integrity check and capture stdout+stderr separately. - # PRAGMA integrity_check prints "ok" on success or error descriptions; - # the sqlite3 exit code alone is NOT reliable for detecting corruption. + # --- Standard structural integrity check --- local integrity_output local sqlite_exit_code=0 - integrity_output=$(sudo -u plex sqlite3 "$main_db" "PRAGMA integrity_check;" 2>&1) || sqlite_exit_code=$? + integrity_output=$(sudo -u plex "$sqlite_bin" "$main_db" "PRAGMA integrity_check;" 2>&1) || sqlite_exit_code=$? + + local struct_ok=true - # Case 1: Output is exactly "ok" — database is healthy. if [[ "$integrity_output" == "ok" ]]; then - print_status "${CHECKMARK}" "Database integrity check passed" "${GREEN}" - return 0 - fi - - # Case 2: sqlite3 couldn't open the file at all (lock, permission, not a db). - if [[ $sqlite_exit_code -ne 0 && -z "$integrity_output" ]]; then + print_status "${CHECKMARK}" "Database structural integrity check passed" "${GREEN}" + elif [[ $sqlite_exit_code -ne 0 && -z "$integrity_output" ]]; then print_status "${CROSS}" "sqlite3 failed to open the database (exit code $sqlite_exit_code)" "${RED}" print_status "${INFO}" "Check file permissions and ensure Plex is fully stopped" "${YELLOW}" return 1 + else + struct_ok=false + print_status "${CROSS}" "Database structural integrity check reported issues:" "${RED}" + echo "$integrity_output" | head -n 5 | while IFS= read -r line; do + echo -e "${DIM}${YELLOW} $line${RESET}" + done + local total_lines + total_lines=$(echo "$integrity_output" | wc -l) + if (( total_lines > 5 )); then + echo -e "${DIM}${YELLOW} ... and $((total_lines - 5)) more issue(s)${RESET}" + fi fi - # Case 3: Genuine corruption reported by PRAGMA integrity_check. - print_status "${CROSS}" "Database integrity check reported issues:" "${RED}" - # Show first 5 lines of the report to avoid flooding the terminal. - echo "$integrity_output" | head -n 5 | while IFS= read -r line; do - echo -e "${DIM}${YELLOW} $line${RESET}" - done - local total_lines - total_lines=$(echo "$integrity_output" | wc -l) - if (( total_lines > 5 )); then - echo -e "${DIM}${YELLOW} ... and $((total_lines - 5)) more issue(s)${RESET}" + # --- FTS index integrity (the most common unreported corruption) --- + local fts_ok=true + if ! check_fts_integrity "$main_db" "$sqlite_bin"; then + fts_ok=false fi - print_status "${INFO}" "Consider running database repair: plex repair" "${YELLOW}" + + if [[ "$struct_ok" == true && "$fts_ok" == true ]]; then + return 0 + fi + + print_status "${INFO}" "Consider running database repair: ${SCRIPT_NAME} repair" "${YELLOW}" return 1 }