Files
AzerothCore-RealmMaster/scripts/bash/cleanup-orphaned-sql.sh
2025-11-22 16:56:02 -05:00

266 lines
8.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# cleanup-orphaned-sql.sh
#
# Cleans up orphaned SQL update entries from the database.
# These are entries in the 'updates' table that reference files no longer on disk.
#
# This happens when:
# - Modules are removed/uninstalled
# - Modules are updated and old SQL files are deleted
# - Manual SQL cleanup occurs
#
# NOTE: These warnings are informational and don't affect server operation.
# This script is optional - it just cleans up the logs.
#
set -euo pipefail
# Configuration
MYSQL_CONTAINER="${MYSQL_CONTAINER:-ac-mysql}"
WORLDSERVER_CONTAINER="${WORLDSERVER_CONTAINER:-ac-worldserver}"
MYSQL_USER="${MYSQL_USER:-root}"
MYSQL_PASSWORD="${MYSQL_ROOT_PASSWORD:-}"
DRY_RUN=false
VERBOSE=false
DATABASES=("acore_world" "acore_characters" "acore_auth")
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Usage
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Clean up orphaned SQL update entries from AzerothCore databases.
OPTIONS:
-p, --password PASSWORD MySQL root password (or use MYSQL_ROOT_PASSWORD env var)
-c, --container NAME MySQL container name (default: ac-mysql)
-w, --worldserver NAME Worldserver container name (default: ac-worldserver)
-d, --database DB Clean only specific database (world, characters, auth)
-n, --dry-run Show what would be cleaned without making changes
-v, --verbose Show detailed output
-h, --help Show this help message
EXAMPLES:
# Dry run to see what would be cleaned
$0 --dry-run
# Clean all databases
$0 --password yourpassword
# Clean only world database
$0 --password yourpassword --database world
# Verbose output
$0 --password yourpassword --verbose
NOTES:
- This script only removes entries from the 'updates' table
- It does NOT remove any actual data or tables
- It does NOT reverse any SQL that was applied
- This is safe to run and only cleans up tracking metadata
- Orphaned entries occur when modules are removed/updated
EOF
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-p|--password)
MYSQL_PASSWORD="$2"
shift 2
;;
-c|--container)
MYSQL_CONTAINER="$2"
shift 2
;;
-w|--worldserver)
WORLDSERVER_CONTAINER="$2"
shift 2
;;
-d|--database)
case $2 in
world) DATABASES=("acore_world") ;;
characters) DATABASES=("acore_characters") ;;
auth) DATABASES=("acore_auth") ;;
*) echo -e "${RED}Error: Invalid database '$2'${NC}"; exit 1 ;;
esac
shift 2
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
usage
;;
*)
echo -e "${RED}Error: Unknown option '$1'${NC}"
usage
;;
esac
done
# Check password
if [[ -z "$MYSQL_PASSWORD" ]]; then
echo -e "${RED}Error: MySQL password required${NC}"
echo "Use --password or set MYSQL_ROOT_PASSWORD environment variable"
exit 1
fi
# Check containers exist
if ! docker ps --format '{{.Names}}' | grep -q "^${MYSQL_CONTAINER}$"; then
echo -e "${RED}Error: MySQL container '$MYSQL_CONTAINER' not found or not running${NC}"
exit 1
fi
if ! docker ps --format '{{.Names}}' | grep -q "^${WORLDSERVER_CONTAINER}$"; then
echo -e "${RED}Error: Worldserver container '$WORLDSERVER_CONTAINER' not found or not running${NC}"
exit 1
fi
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ AzerothCore Orphaned SQL Cleanup ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
echo
if [[ "$DRY_RUN" == true ]]; then
echo -e "${YELLOW}DRY RUN MODE - No changes will be made${NC}"
echo
fi
# Function to get SQL files from worldserver container
get_sql_files() {
local db_type=$1
docker exec "$WORLDSERVER_CONTAINER" find "/azerothcore/data/sql/updates/${db_type}/" -name "*.sql" -type f 2>/dev/null | \
xargs -I {} basename {} 2>/dev/null || true
}
# Function to clean orphaned entries
clean_orphaned_entries() {
local database=$1
local db_type=$2
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}Processing: $database${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# Get list of SQL files on disk
local sql_files
sql_files=$(get_sql_files "$db_type")
if [[ -z "$sql_files" ]]; then
echo -e "${YELLOW}⚠ No SQL files found in /azerothcore/data/sql/updates/${db_type}/${NC}"
echo
return
fi
local file_count
file_count=$(echo "$sql_files" | wc -l)
echo -e "📁 Found ${file_count} SQL files on disk"
# Get entries from updates table
local total_updates
total_updates=$(docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$database" -sN \
-e "SELECT COUNT(*) FROM updates" 2>/dev/null || echo "0")
echo -e "📊 Total updates in database: ${total_updates}"
if [[ "$total_updates" == "0" ]]; then
echo -e "${YELLOW}⚠ No updates found in database${NC}"
echo
return
fi
# Find orphaned entries (in DB but not on disk)
# We'll create a temp table with file names and do a LEFT JOIN
local orphaned_count=0
local orphaned_list=""
# Get all update names from DB
local db_updates
db_updates=$(docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$database" -sN \
-e "SELECT name FROM updates ORDER BY name" 2>/dev/null || true)
if [[ -n "$db_updates" ]]; then
# Check each DB entry against disk files
while IFS= read -r update_name; do
if ! echo "$sql_files" | grep -qF "$update_name"; then
((orphaned_count++))
if [[ "$VERBOSE" == true ]] || [[ "$DRY_RUN" == true ]]; then
orphaned_list="${orphaned_list}${update_name}\n"
fi
# Delete if not dry run
if [[ "$DRY_RUN" == false ]]; then
docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$database" -e \
"DELETE FROM updates WHERE name='${update_name}'" 2>/dev/null
fi
fi
done <<< "$db_updates"
fi
# Report results
if [[ $orphaned_count -gt 0 ]]; then
echo -e "${YELLOW}🗑️ Orphaned entries: ${orphaned_count}${NC}"
if [[ "$VERBOSE" == true ]] || [[ "$DRY_RUN" == true ]]; then
echo
echo -e "${YELLOW}Orphaned files:${NC}"
echo -e "$orphaned_list" | head -20
if [[ $orphaned_count -gt 20 ]]; then
echo -e "${YELLOW}... and $((orphaned_count - 20)) more${NC}"
fi
fi
if [[ "$DRY_RUN" == false ]]; then
echo -e "${GREEN}✅ Cleaned ${orphaned_count} orphaned entries${NC}"
else
echo -e "${YELLOW}Would clean ${orphaned_count} orphaned entries${NC}"
fi
else
echo -e "${GREEN}✅ No orphaned entries found${NC}"
fi
echo
}
# Process each database
for db in "${DATABASES[@]}"; do
case $db in
acore_world)
clean_orphaned_entries "$db" "db_world"
;;
acore_characters)
clean_orphaned_entries "$db" "db_characters"
;;
acore_auth)
clean_orphaned_entries "$db" "db_auth"
;;
esac
done
# Summary
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}Cleanup Complete${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
if [[ "$DRY_RUN" == true ]]; then
echo
echo -e "${YELLOW}This was a dry run. To actually clean orphaned entries, run:${NC}"
echo -e "${YELLOW} $0 --password yourpassword${NC}"
fi