Files
AzerothCore-RealmMaster/scripts/bash/db-health-check.sh
uprightbass360 e6231bb4a4 feat: comprehensive module system and database management improvements
This commit introduces major enhancements to the module installation system,
database management, and configuration handling for AzerothCore deployments.

## Module System Improvements

### Module SQL Staging & Installation
- Refactor module SQL staging to properly handle AzerothCore's sql/ directory structure
- Fix SQL staging path to use correct AzerothCore format (sql/custom/db_*/*)
- Implement conditional module database importing based on enabled modules
- Add support for both cpp-modules and lua-scripts module types
- Handle rsync exit code 23 (permission warnings) gracefully during deployment

### Module Manifest & Automation
- Add automated module manifest generation via GitHub Actions workflow
- Implement Python-based module manifest updater with comprehensive validation
- Add module dependency tracking and SQL file discovery
- Support for blocked modules and module metadata management

## Database Management Enhancements

### Database Import System
- Add db-guard container for continuous database health monitoring and verification
- Implement conditional database import that skips when databases are current
- Add backup restoration and SQL staging coordination
- Support for Playerbots database (4th database) in all import operations
- Add comprehensive database health checking and status reporting

### Database Configuration
- Implement 10 new dbimport.conf settings from environment variables:
  - Database.Reconnect.Seconds/Attempts for connection reliability
  - Updates.AllowedModules for module auto-update control
  - Updates.Redundancy for data integrity checks
  - Worker/Synch thread settings for all three core databases
- Auto-apply dbimport.conf settings via auto-post-install.sh
- Add environment variable injection for db-import and db-guard containers

### Backup & Recovery
- Fix backup scheduler to prevent immediate execution on container startup
- Add backup status monitoring script with detailed reporting
- Implement backup import/export utilities
- Add database verification scripts for SQL update tracking

## User Import Directory

- Add new import/ directory for user-provided database files and configurations
- Support for custom SQL files, configuration overrides, and example templates
- Automatic import of user-provided databases and configs during initialization
- Documentation and examples for custom database imports

## Configuration & Environment

- Eliminate CLIENT_DATA_VERSION warning by adding default value syntax
- Improve CLIENT_DATA_VERSION documentation in .env.template
- Add comprehensive database import settings to .env and .env.template
- Update setup.sh to handle new configuration variables with proper defaults

## Monitoring & Debugging

- Add status dashboard with Go-based terminal UI (statusdash.go)
- Implement JSON status output (statusjson.sh) for programmatic access
- Add comprehensive database health check script
- Add repair-storage-permissions.sh utility for permission issues

## Testing & Documentation

- Add Phase 1 integration test suite for module installation verification
- Add comprehensive documentation for:
  - Database management (DATABASE_MANAGEMENT.md)
  - Module SQL analysis (AZEROTHCORE_MODULE_SQL_ANALYSIS.md)
  - Implementation mapping (IMPLEMENTATION_MAP.md)
  - SQL staging comparison and path coverage
  - Module assets and DBC file requirements
- Update SCRIPTS.md, ADVANCED.md, and troubleshooting documentation
- Update references from database-import/ to import/ directory

## Breaking Changes

- Renamed database-import/ directory to import/ for clarity
- Module SQL files now staged to AzerothCore-compatible paths
- db-guard container now required for proper database lifecycle management

## Bug Fixes

- Fix module SQL staging directory structure for AzerothCore compatibility
- Handle rsync exit code 23 gracefully during deployments
- Prevent backup from running immediately on container startup
- Correct SQL staging paths for proper module installation
2025-11-22 16:56:02 -05:00

390 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Database Health Check Script
# Provides comprehensive health status of AzerothCore databases
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Colors
BLUE='\033[0;34m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
# Icons
ICON_SUCCESS="✅"
ICON_WARNING="⚠️"
ICON_ERROR="❌"
ICON_INFO=""
ICON_DB="🗄️"
ICON_SIZE="💾"
ICON_TIME="🕐"
ICON_MODULE="📦"
ICON_UPDATE="🔄"
# Default values
VERBOSE=0
SHOW_PENDING=0
SHOW_MODULES=1
CONTAINER_NAME="ac-mysql"
usage() {
cat <<'EOF'
Usage: ./db-health-check.sh [options]
Check the health status of AzerothCore databases.
Options:
-v, --verbose Show detailed information
-p, --pending Show pending updates
-m, --no-modules Hide module update information
-c, --container NAME MySQL container name (default: ac-mysql)
-h, --help Show this help
Examples:
./db-health-check.sh
./db-health-check.sh --verbose --pending
./db-health-check.sh --container ac-mysql-custom
EOF
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose) VERBOSE=1; shift;;
-p|--pending) SHOW_PENDING=1; shift;;
-m|--no-modules) SHOW_MODULES=0; shift;;
-c|--container) CONTAINER_NAME="$2"; shift 2;;
-h|--help) usage; exit 0;;
*) echo "Unknown option: $1"; usage; exit 1;;
esac
done
# Load environment
if [ -f "$PROJECT_ROOT/.env" ]; then
set -a
# shellcheck disable=SC1091
source "$PROJECT_ROOT/.env"
set +a
fi
MYSQL_HOST="${MYSQL_HOST:-ac-mysql}"
MYSQL_PORT="${MYSQL_PORT:-3306}"
MYSQL_USER="${MYSQL_USER:-root}"
MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-}"
DB_AUTH_NAME="${DB_AUTH_NAME:-acore_auth}"
DB_WORLD_NAME="${DB_WORLD_NAME:-acore_world}"
DB_CHARACTERS_NAME="${DB_CHARACTERS_NAME:-acore_characters}"
DB_PLAYERBOTS_NAME="${DB_PLAYERBOTS_NAME:-acore_playerbots}"
# MySQL query helper
mysql_query() {
local database="${1:-}"
local query="$2"
if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
echo "Error: MYSQL_ROOT_PASSWORD not set" >&2
return 1
fi
if command -v docker >/dev/null 2>&1; then
if [ -n "$database" ]; then
docker exec "$CONTAINER_NAME" mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_ROOT_PASSWORD" "$database" -N -B -e "$query" 2>/dev/null
else
docker exec "$CONTAINER_NAME" mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_ROOT_PASSWORD" -N -B -e "$query" 2>/dev/null
fi
else
if [ -n "$database" ]; then
mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_ROOT_PASSWORD" "$database" -N -B -e "$query" 2>/dev/null
else
mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_ROOT_PASSWORD" -N -B -e "$query" 2>/dev/null
fi
fi
}
# Format bytes to human readable
format_bytes() {
local bytes=$1
if [ "$bytes" -lt 1024 ]; then
echo "${bytes}B"
elif [ "$bytes" -lt 1048576 ]; then
echo "$(awk "BEGIN {printf \"%.1f\", $bytes/1024}")KB"
elif [ "$bytes" -lt 1073741824 ]; then
echo "$(awk "BEGIN {printf \"%.1f\", $bytes/1048576}")MB"
else
echo "$(awk "BEGIN {printf \"%.2f\", $bytes/1073741824}")GB"
fi
}
# Check if database exists
db_exists() {
local db_name="$1"
local count
count=$(mysql_query "" "SELECT COUNT(*) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$db_name'" 2>/dev/null || echo "0")
[ "$count" = "1" ]
}
# Get database size
get_db_size() {
local db_name="$1"
mysql_query "" "SELECT IFNULL(SUM(data_length + index_length), 0) FROM information_schema.TABLES WHERE table_schema='$db_name'" 2>/dev/null || echo "0"
}
# Get update count
get_update_count() {
local db_name="$1"
local state="${2:-}"
if [ -n "$state" ]; then
mysql_query "$db_name" "SELECT COUNT(*) FROM updates WHERE state='$state'" 2>/dev/null || echo "0"
else
mysql_query "$db_name" "SELECT COUNT(*) FROM updates" 2>/dev/null || echo "0"
fi
}
# Get last update timestamp
get_last_update() {
local db_name="$1"
mysql_query "$db_name" "SELECT IFNULL(MAX(timestamp), 'Never') FROM updates" 2>/dev/null || echo "Never"
}
# Get table count
get_table_count() {
local db_name="$1"
mysql_query "" "SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema='$db_name'" 2>/dev/null || echo "0"
}
# Get character count
get_character_count() {
mysql_query "$DB_CHARACTERS_NAME" "SELECT COUNT(*) FROM characters" 2>/dev/null || echo "0"
}
# Get active players (logged in last 24 hours)
get_active_players() {
mysql_query "$DB_CHARACTERS_NAME" "SELECT COUNT(*) FROM characters WHERE logout_time > UNIX_TIMESTAMP(NOW() - INTERVAL 1 DAY)" 2>/dev/null || echo "0"
}
# Get account count
get_account_count() {
mysql_query "$DB_AUTH_NAME" "SELECT COUNT(*) FROM account" 2>/dev/null || echo "0"
}
# Get pending updates
get_pending_updates() {
local db_name="$1"
mysql_query "$db_name" "SELECT name FROM updates WHERE state='PENDING' ORDER BY name" 2>/dev/null || true
}
# Check database health
check_database() {
local db_name="$1"
local display_name="$2"
if ! db_exists "$db_name"; then
printf " ${RED}${ICON_ERROR} %s (%s)${NC}\n" "$display_name" "$db_name"
printf " ${RED}Database does not exist${NC}\n"
return 1
fi
printf " ${GREEN}${ICON_SUCCESS} %s (%s)${NC}\n" "$display_name" "$db_name"
local update_count module_count last_update db_size table_count
update_count=$(get_update_count "$db_name" "RELEASED")
module_count=$(get_update_count "$db_name" "MODULE")
last_update=$(get_last_update "$db_name")
db_size=$(get_db_size "$db_name")
table_count=$(get_table_count "$db_name")
printf " ${ICON_UPDATE} Updates: %s applied" "$update_count"
if [ "$module_count" != "0" ] && [ "$SHOW_MODULES" = "1" ]; then
printf " (%s module)" "$module_count"
fi
printf "\n"
printf " ${ICON_TIME} Last update: %s\n" "$last_update"
printf " ${ICON_SIZE} Size: %s (%s tables)\n" "$(format_bytes "$db_size")" "$table_count"
if [ "$VERBOSE" = "1" ]; then
local custom_count archived_count
custom_count=$(get_update_count "$db_name" "CUSTOM")
archived_count=$(get_update_count "$db_name" "ARCHIVED")
if [ "$custom_count" != "0" ]; then
printf " ${ICON_INFO} Custom updates: %s\n" "$custom_count"
fi
if [ "$archived_count" != "0" ]; then
printf " ${ICON_INFO} Archived updates: %s\n" "$archived_count"
fi
fi
# Show pending updates if requested
if [ "$SHOW_PENDING" = "1" ]; then
local pending_updates
pending_updates=$(get_pending_updates "$db_name")
if [ -n "$pending_updates" ]; then
printf " ${YELLOW}${ICON_WARNING} Pending updates:${NC}\n"
while IFS= read -r update; do
printf " - %s\n" "$update"
done <<< "$pending_updates"
fi
fi
echo
}
# Show module updates summary
show_module_updates() {
if [ "$SHOW_MODULES" = "0" ]; then
return
fi
printf "${BOLD}${ICON_MODULE} Module Updates${NC}\n"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Get module updates from world database (most modules update world DB)
local module_updates
module_updates=$(mysql_query "$DB_WORLD_NAME" "SELECT SUBSTRING_INDEX(name, '_', 1) as module, COUNT(*) as count FROM updates WHERE state='MODULE' GROUP BY module ORDER BY module" 2>/dev/null || echo "")
if [ -z "$module_updates" ]; then
printf " ${ICON_INFO} No module updates detected\n\n"
return
fi
while IFS=$'\t' read -r module count; do
printf " ${GREEN}${ICON_SUCCESS}${NC} %s: %s update(s)\n" "$module" "$count"
done <<< "$module_updates"
echo
}
# Get backup information
get_backup_info() {
local backup_dir="$PROJECT_ROOT/storage/backups"
if [ ! -d "$backup_dir" ]; then
printf " ${ICON_INFO} No backups directory found\n"
return
fi
# Check for latest backup
local latest_hourly latest_daily
if [ -d "$backup_dir/hourly" ]; then
latest_hourly=$(ls -1t "$backup_dir/hourly" 2>/dev/null | head -n1 || echo "")
fi
if [ -d "$backup_dir/daily" ]; then
latest_daily=$(ls -1t "$backup_dir/daily" 2>/dev/null | head -n1 || echo "")
fi
if [ -n "$latest_hourly" ]; then
# Calculate time ago
local backup_timestamp="${latest_hourly:0:8}_${latest_hourly:9:6}"
local backup_epoch
backup_epoch=$(date -d "${backup_timestamp:0:4}-${backup_timestamp:4:2}-${backup_timestamp:6:2} ${backup_timestamp:9:2}:${backup_timestamp:11:2}:${backup_timestamp:13:2}" +%s 2>/dev/null || echo "0")
local now_epoch
now_epoch=$(date +%s)
local diff=$((now_epoch - backup_epoch))
local hours=$((diff / 3600))
local minutes=$(((diff % 3600) / 60))
if [ "$hours" -gt 0 ]; then
printf " ${ICON_TIME} Last hourly backup: %s hours ago\n" "$hours"
else
printf " ${ICON_TIME} Last hourly backup: %s minutes ago\n" "$minutes"
fi
fi
if [ -n "$latest_daily" ] && [ "$latest_daily" != "$latest_hourly" ]; then
local backup_timestamp="${latest_daily:0:8}_${latest_daily:9:6}"
local backup_epoch
backup_epoch=$(date -d "${backup_timestamp:0:4}-${backup_timestamp:4:2}-${backup_timestamp:6:2} ${backup_timestamp:9:2}:${backup_timestamp:11:2}:${backup_timestamp:13:2}" +%s 2>/dev/null || echo "0")
local now_epoch
now_epoch=$(date +%s)
local diff=$((now_epoch - backup_epoch))
local days=$((diff / 86400))
printf " ${ICON_TIME} Last daily backup: %s days ago\n" "$days"
fi
}
# Main health check
main() {
echo
printf "${BOLD}${BLUE}${ICON_DB} AZEROTHCORE DATABASE HEALTH CHECK${NC}\n"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo
# Test MySQL connection
if ! mysql_query "" "SELECT 1" >/dev/null 2>&1; then
printf "${RED}${ICON_ERROR} Cannot connect to MySQL server${NC}\n"
printf " Host: %s:%s\n" "$MYSQL_HOST" "$MYSQL_PORT"
printf " User: %s\n" "$MYSQL_USER"
printf " Container: %s\n\n" "$CONTAINER_NAME"
exit 1
fi
printf "${BOLD}${ICON_DB} Database Status${NC}\n"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo
# Check each database
check_database "$DB_AUTH_NAME" "Auth DB"
check_database "$DB_WORLD_NAME" "World DB"
check_database "$DB_CHARACTERS_NAME" "Characters DB"
# Optional: Check playerbots database
if db_exists "$DB_PLAYERBOTS_NAME"; then
check_database "$DB_PLAYERBOTS_NAME" "Playerbots DB"
fi
# Show character/account statistics
printf "${BOLD}${CYAN}📊 Server Statistics${NC}\n"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
local account_count character_count active_count
account_count=$(get_account_count)
character_count=$(get_character_count)
active_count=$(get_active_players)
printf " ${ICON_INFO} Accounts: %s\n" "$account_count"
printf " ${ICON_INFO} Characters: %s\n" "$character_count"
printf " ${ICON_INFO} Active (24h): %s\n" "$active_count"
echo
# Show module updates
show_module_updates
# Show backup information
printf "${BOLD}${ICON_SIZE} Backup Information${NC}\n"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
get_backup_info
echo
# Calculate total database size
local total_size=0
for db in "$DB_AUTH_NAME" "$DB_WORLD_NAME" "$DB_CHARACTERS_NAME"; do
if db_exists "$db"; then
local size
size=$(get_db_size "$db")
total_size=$((total_size + size))
fi
done
if db_exists "$DB_PLAYERBOTS_NAME"; then
local size
size=$(get_db_size "$DB_PLAYERBOTS_NAME")
total_size=$((total_size + size))
fi
printf "${BOLD}💾 Total Database Storage: %s${NC}\n" "$(format_bytes "$total_size")"
echo
printf "${GREEN}${ICON_SUCCESS} Health check complete!${NC}\n"
echo
}
main "$@"