#!/bin/bash ################################################################################ # Consolidated Plex Database Management Script ################################################################################ # # Author: Peter Wood # Description: Consolidated database management functionality combining the best # features from plex-database-repair.sh, recover-plex-database.sh, # and nuclear-plex-recovery.sh into a single, safe interface. # # Features: # - Read-only integrity checking # - Progressive repair strategies (gentle to aggressive) # - Service management with proper synchronization # - Comprehensive logging and error handling # - Manual intervention prompts for safety # # Usage: # ./plex-db-manager.sh check # Read-only integrity check # ./plex-db-manager.sh repair # Interactive repair # ./plex-db-manager.sh repair --gentle # Gentle repair only # ./plex-db-manager.sh repair --force # Aggressive repair # ./plex-db-manager.sh nuclear # Nuclear recovery # # Exit Codes: # 0 - Success # 1 - Database issues detected (no repair attempted) # 2 - Repair failed # 3 - Service management failure # ################################################################################ set -euo pipefail # Color codes for output readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly BLUE='\033[0;34m' readonly CYAN='\033[0;36m' readonly WHITE='\033[1;37m' readonly BOLD='\033[1m' readonly DIM='\033[2m' 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" 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")")" readonly SCRIPT_DIR readonly LOG_FILE="$SCRIPT_DIR/logs/db-manager-$(date +%Y%m%d_%H%M%S).log" # Create log directory mkdir -p "$SCRIPT_DIR/logs" # Logging functions log_message() { local message="$1" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${CYAN}[${timestamp}]${RESET} ${message}" echo "[${timestamp}] ${message}" >> "$LOG_FILE" 2>/dev/null || true } log_error() { local message="$1" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${RED}[${timestamp}] ERROR:${RESET} ${message}" >&2 echo "[${timestamp}] ERROR: ${message}" >> "$LOG_FILE" 2>/dev/null || true } log_success() { local message="$1" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${GREEN}[${timestamp}] SUCCESS:${RESET} ${message}" echo "[${timestamp}] SUCCESS: ${message}" >> "$LOG_FILE" 2>/dev/null || true } log_warning() { local message="$1" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${YELLOW}[${timestamp}] WARNING:${RESET} ${message}" echo "[${timestamp}] WARNING: ${message}" >> "$LOG_FILE" 2>/dev/null || true } # Print header print_header() { echo -e "\n${BOLD}${CYAN}+================================================================+${RESET}" echo -e "${BOLD}${CYAN}| PLEX DATABASE MANAGEMENT TOOL |${RESET}" echo -e "${BOLD}${CYAN}+================================================================+${RESET}\n" } # Check prerequisites check_prerequisites() { log_message "Checking prerequisites..." if [[ $EUID -eq 0 ]]; then log_error "Don't run this script as root! Use your regular user account." exit 3 fi if [[ ! -f "$PLEX_SQLITE" ]]; then log_error "Plex SQLite binary not found at: $PLEX_SQLITE" exit 3 fi if ! sudo chmod +x "$PLEX_SQLITE" 2>/dev/null; then log_warning "Could not make Plex SQLite executable, but will try to use it" fi if [[ ! -f "$MAIN_DB" ]]; then log_error "Main database not found at: $MAIN_DB" exit 3 fi log_success "Prerequisites check passed" } # Safe service management with proper synchronization manage_service() { local action="$1" local timeout="${2:-30}" case "$action" in "stop") log_message "Stopping Plex service..." if ! systemctl is-active --quiet "$PLEX_SERVICE"; then log_message "Plex service is already stopped" return 0 fi if sudo systemctl stop "$PLEX_SERVICE"; then # Wait for complete shutdown local count=0 while systemctl is-active --quiet "$PLEX_SERVICE" && [[ $count -lt $timeout ]]; do sleep 1 ((count++)) done if systemctl is-active --quiet "$PLEX_SERVICE"; then log_error "Service failed to stop within ${timeout}s" return 1 else log_success "Plex service stopped successfully" # Additional wait for complete cleanup sleep 3 return 0 fi else log_error "Failed to stop Plex service" return 1 fi ;; "start") log_message "Starting Plex service..." if systemctl is-active --quiet "$PLEX_SERVICE"; then log_message "Plex service is already running" return 0 fi if sudo systemctl start "$PLEX_SERVICE"; then # Wait for startup local count=0 while ! systemctl is-active --quiet "$PLEX_SERVICE" && [[ $count -lt $timeout ]]; do sleep 1 ((count++)) done if systemctl is-active --quiet "$PLEX_SERVICE"; then log_success "Plex service started successfully" return 0 else log_error "Service failed to start within ${timeout}s" return 1 fi else log_error "Failed to start Plex service" return 1 fi ;; esac } # Safe database integrity check (read-only) check_database_integrity() { local db_file="$1" local db_name db_name=$(basename "$db_file") log_message "Checking database integrity: $db_name" # WAL checkpoint if needed (read-only operation) local wal_file="${db_file}-wal" if [[ -f "$wal_file" ]]; then log_message "WAL file detected, performing read-only checkpoint..." if sudo "$PLEX_SQLITE" "$db_file" "PRAGMA wal_checkpoint(PASSIVE);" >/dev/null 2>&1; then log_success "WAL checkpoint completed" else log_warning "WAL checkpoint failed, proceeding with integrity check" fi fi # Run 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" return 1 fi if echo "$integrity_result" | grep -q "^ok$"; then log_success "Database integrity check passed: $db_name" return 0 else log_warning "Database integrity issues detected in $db_name:" echo "$integrity_result" | while IFS= read -r line; do log_warning " $line" done return 1 fi } # Check all databases check_all_databases() { local issues=0 log_message "Starting comprehensive database integrity check..." for db in "$MAIN_DB" "$BLOBS_DB"; do if [[ -f "$db" ]]; then if ! check_database_integrity "$db"; then ((issues++)) fi else log_warning "Database file not found: $(basename "$db")" ((issues++)) fi done if [[ $issues -eq 0 ]]; then log_success "All database integrity checks passed" return 0 else log_warning "Found integrity issues in $issues database(s)" return 1 fi } # Show help show_help() { echo -e "${BOLD}${WHITE}Usage:${RESET} ${CYAN}$(basename "$0")${RESET} ${YELLOW}${RESET} ${DIM}[options]${RESET}" echo "" echo -e "${BOLD}${WHITE}Commands:${RESET}" echo -e " ${GREEN}${BOLD}check${RESET} Read-only database integrity check" echo -e " ${YELLOW}${BOLD}repair${RESET} Interactive database repair" echo -e " ${YELLOW}${BOLD}repair --gentle${RESET} Gentle repair methods only" echo -e " ${RED}${BOLD}repair --force${RESET} Aggressive repair methods" echo -e " ${RED}${BOLD}nuclear${RESET} Nuclear recovery (replace from backup)" echo -e " ${CYAN}${BOLD}help${RESET} Show this help message" echo "" echo -e "${BOLD}${WHITE}Examples:${RESET}" echo -e " ${DIM}$(basename "$0") check # Safe integrity check${RESET}" echo -e " ${DIM}$(basename "$0") repair # Interactive repair${RESET}" echo -e " ${DIM}$(basename "$0") repair --gentle # Minimal repair only${RESET}" echo "" echo -e "${BOLD}${YELLOW}⚠️ WARNING:${RESET} Always run ${CYAN}check${RESET} first before attempting repairs!" echo "" } # Main function main() { if [[ $# -eq 0 ]]; then print_header show_help exit 0 fi case "${1,,}" in "check") print_header check_prerequisites # Stop service for clean check if ! manage_service stop; then log_error "Cannot perform integrity check while service is running" exit 3 fi # Check databases if check_all_databases; then exit_code=0 else exit_code=1 fi # Restart service if ! manage_service start; then log_error "Failed to restart service after check" exit 3 fi exit $exit_code ;; "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 ;; "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 ;; "help"|"--help"|"-h") print_header show_help ;; *) print_header log_error "Unknown command: $1" echo "" show_help exit 1 ;; esac } # Execute main function main "$@"