feat: adds config/db import functionality

This commit is contained in:
uprightbass360
2025-11-08 19:36:50 -05:00
parent d5bb4e3525
commit 23456e0ab9
15 changed files with 1405 additions and 4 deletions

323
scripts/apply-config.py Executable file
View File

@@ -0,0 +1,323 @@
#!/usr/bin/env python3
"""
AzerothCore Configuration Manager
Reads server-overrides.conf and preset files to update actual .conf files
while preserving comments and structure.
"""
import argparse
import configparser
import os
import re
import shutil
import sys
from pathlib import Path
from typing import Dict, List, Optional, Set
class ConfigManager:
"""Manages AzerothCore configuration file updates."""
def __init__(self, storage_path: str, overrides_file: str, dry_run: bool = False):
self.storage_path = Path(storage_path)
self.config_dir = self.storage_path / "config"
self.modules_config_dir = self.storage_path / "config" / "modules"
self.overrides_file = Path(overrides_file)
self.dry_run = dry_run
if not self.config_dir.exists():
raise FileNotFoundError(f"Config directory not found: {self.config_dir}")
def load_overrides(self) -> Dict[str, Dict[str, str]]:
"""Load configuration overrides from INI-style file."""
if not self.overrides_file.exists():
print(f"⚠️ Override file not found: {self.overrides_file}")
return {}
config = configparser.ConfigParser(interpolation=None)
config.optionxform = str # Preserve case sensitivity
try:
config.read(self.overrides_file, encoding='utf-8')
except Exception as e:
print(f"❌ Error reading override file: {e}")
return {}
overrides = {}
for section in config.sections():
overrides[section] = dict(config.items(section))
return overrides
def find_conf_file(self, filename: str) -> Optional[Path]:
"""Find a configuration file in the config directory."""
# Check main config directory first (for core server configs)
conf_file = self.config_dir / filename
if conf_file.exists():
return conf_file
# Check modules config directory (for module configs)
modules_conf_file = self.modules_config_dir / filename
if modules_conf_file.exists():
return modules_conf_file
# Try to create from .dist file in main config directory
dist_file = self.config_dir / f"{filename}.dist"
if dist_file.exists():
print(f"📄 Creating {filename} from {filename}.dist")
if not self.dry_run:
shutil.copy2(dist_file, conf_file)
return conf_file
# Try to create from .dist file in modules directory
modules_dist_file = self.modules_config_dir / f"{filename}.dist"
if modules_dist_file.exists():
print(f"📄 Creating {filename} from modules/{filename}.dist")
if not self.dry_run:
if not self.modules_config_dir.exists():
self.modules_config_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(modules_dist_file, modules_conf_file)
return modules_conf_file
return None
def update_conf_file(self, conf_file: Path, settings: Dict[str, str]) -> bool:
"""Update a .conf file with new settings while preserving structure."""
if not conf_file.exists():
print(f"❌ Configuration file not found: {conf_file}")
return False
try:
with open(conf_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
except Exception as e:
print(f"❌ Error reading {conf_file}: {e}")
return False
updated_lines = []
updated_keys = set()
# Process each line
for line in lines:
original_line = line
stripped = line.strip()
# Skip empty lines and comments
if not stripped or stripped.startswith('#'):
updated_lines.append(original_line)
continue
# Check if this line contains a setting we want to override
setting_match = re.match(r'^([^=]+?)\s*=\s*(.*)$', stripped)
if setting_match:
key = setting_match.group(1).strip()
if key in settings:
# Replace with our override value
new_value = settings[key]
# Preserve the original indentation
indent = len(line) - len(line.lstrip())
new_line = ' ' * indent + f"{key} = {new_value}\n"
updated_lines.append(new_line)
updated_keys.add(key)
print(f"{key} = {new_value}")
else:
# Keep original line
updated_lines.append(original_line)
else:
# Keep original line (could be section header or other content)
updated_lines.append(original_line)
# Add any settings that weren't found in the file
for key, value in settings.items():
if key not in updated_keys:
updated_lines.append(f"{key} = {value}\n")
print(f" {key} = {value} (added)")
# Write the updated file
if not self.dry_run:
try:
with open(conf_file, 'w', encoding='utf-8') as f:
f.writelines(updated_lines)
except Exception as e:
print(f"❌ Error writing {conf_file}: {e}")
return False
return True
def apply_overrides(self, overrides: Dict[str, Dict[str, str]],
filter_files: Optional[Set[str]] = None) -> bool:
"""Apply all configuration overrides."""
success = True
if not overrides:
print(" No configuration overrides to apply")
return True
print(f"🔧 Applying configuration overrides{' (DRY RUN)' if self.dry_run else ''}...")
for conf_filename, settings in overrides.items():
# Skip if we're filtering and this file isn't in the filter
if filter_files and conf_filename not in filter_files:
continue
if not settings:
continue
print(f"\n📝 Updating {conf_filename}:")
# Find the configuration file
conf_file = self.find_conf_file(conf_filename)
if not conf_file:
print(f" ⚠️ Configuration file not found: {conf_filename}")
success = False
continue
# Update the file
if not self.update_conf_file(conf_file, settings):
success = False
return success
def load_preset(preset_file: Path) -> Dict[str, Dict[str, str]]:
"""Load a preset configuration file."""
if not preset_file.exists():
raise FileNotFoundError(f"Preset file not found: {preset_file}")
config = configparser.ConfigParser(interpolation=None)
config.optionxform = str # Preserve case sensitivity
config.read(preset_file, encoding='utf-8')
overrides = {}
for section in config.sections():
overrides[section] = dict(config.items(section))
return overrides
def list_available_presets(preset_dir: Path) -> List[str]:
"""List available preset files."""
if not preset_dir.exists():
return []
presets = []
for preset_file in preset_dir.glob("*.conf"):
presets.append(preset_file.stem)
return sorted(presets)
def main():
parser = argparse.ArgumentParser(
description="Apply AzerothCore configuration overrides and presets"
)
parser.add_argument(
"--storage-path",
default="./storage",
help="Path to storage directory (default: ./storage)"
)
parser.add_argument(
"--overrides-file",
default="./config/server-overrides.conf",
help="Path to server overrides file (default: ./config/server-overrides.conf)"
)
parser.add_argument(
"--preset",
help="Apply a preset from config/presets/<name>.conf"
)
parser.add_argument(
"--list-presets",
action="store_true",
help="List available presets"
)
parser.add_argument(
"--files",
help="Comma-separated list of .conf files to update (default: all)"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be changed without making modifications"
)
args = parser.parse_args()
# Handle list presets
if args.list_presets:
preset_dir = Path("./config/presets")
presets = list_available_presets(preset_dir)
if presets:
print("📋 Available presets:")
for preset in presets:
preset_file = preset_dir / f"{preset}.conf"
print(f"{preset}")
# Try to read description from preset file
if preset_file.exists():
try:
with open(preset_file, 'r') as f:
first_line = f.readline().strip()
if first_line.startswith('#') and len(first_line) > 1:
description = first_line[1:].strip()
print(f" {description}")
except:
pass
else:
print(" No presets found in config/presets/")
return
try:
# Initialize configuration manager
config_manager = ConfigManager(
storage_path=args.storage_path,
overrides_file=args.overrides_file,
dry_run=args.dry_run
)
# Determine which files to filter (if any)
filter_files = None
if args.files:
filter_files = set(f.strip() for f in args.files.split(','))
# Load configuration overrides
overrides = {}
# Load preset if specified
if args.preset:
preset_file = Path(f"./config/presets/{args.preset}.conf")
print(f"📦 Loading preset: {args.preset}")
try:
preset_overrides = load_preset(preset_file)
overrides.update(preset_overrides)
except FileNotFoundError as e:
print(f"{e}")
return 1
# Load server overrides (this can override preset values)
server_overrides = config_manager.load_overrides()
overrides.update(server_overrides)
# Apply all overrides
success = config_manager.apply_overrides(overrides, filter_files)
if success:
if args.dry_run:
print("\n✅ Configuration validation complete")
else:
print("\n✅ Configuration applied successfully")
print(" Restart your server to apply changes")
return 0
else:
print("\n❌ Some configuration updates failed")
return 1
except Exception as e:
print(f"❌ Error: {e}")
return 1
if __name__ == "__main__":
sys.exit(main())

162
scripts/configure-server.sh Executable file
View File

@@ -0,0 +1,162 @@
#!/bin/bash
# Simple wrapper script for server configuration management
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m' # No Color
print_header() {
echo -e "\n${BLUE}🔧 AzerothCore Configuration Manager${NC}\n"
}
show_usage() {
cat << EOF
Usage: $(basename "$0") [COMMAND] [OPTIONS]
Commands:
apply Apply configuration overrides from config/server-overrides.conf
preset <name> Apply a preset configuration
list List available presets
edit Open server-overrides.conf in editor
status Show current configuration status
Examples:
$(basename "$0") apply # Apply custom overrides
$(basename "$0") preset fast-leveling # Apply fast-leveling preset
$(basename "$0") list # Show available presets
$(basename "$0") edit # Edit configuration file
EOF
}
edit_config() {
local config_file="$PROJECT_DIR/config/server-overrides.conf"
local editor="${EDITOR:-nano}"
echo -e "${YELLOW}📝 Opening configuration file in $editor...${NC}"
if [[ ! -f "$config_file" ]]; then
echo -e "${YELLOW}⚠️ Configuration file doesn't exist. Creating template...${NC}"
mkdir -p "$(dirname "$config_file")"
# Create a minimal template if it doesn't exist
cat > "$config_file" << 'EOF'
# AzerothCore Server Configuration Overrides
# Edit this file and run './scripts/configure-server.sh apply' to update settings
[worldserver.conf]
# Example settings - uncomment and modify as needed
# Rate.XP.Kill = 2.0
# Rate.XP.Quest = 2.0
# MaxPlayerLevel = 80
[playerbots.conf]
# Example playerbot settings
# AiPlayerbot.MinRandomBots = 100
# AiPlayerbot.MaxRandomBots = 300
EOF
echo -e "${GREEN}✅ Created template configuration file${NC}"
fi
"$editor" "$config_file"
echo -e "\n${YELLOW}Would you like to apply these changes now? (y/N)${NC}"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
python3 "$SCRIPT_DIR/apply-config.py"
else
echo -e "${BLUE} Run '$(basename "$0") apply' when ready to apply changes${NC}"
fi
}
show_status() {
echo -e "${BLUE}📊 Configuration Status${NC}\n"
# Check if config files exist
local storage_path="${STORAGE_PATH:-./storage}"
local config_dir="$storage_path/config"
if [[ -d "$config_dir" ]]; then
echo -e "${GREEN}✅ Config directory found: $config_dir${NC}"
local conf_count
conf_count=$(find "$config_dir" -name "*.conf" -type f | wc -l)
echo -e "${GREEN}📄 Configuration files: $conf_count${NC}"
# Show some key files
for conf in worldserver.conf authserver.conf playerbots.conf; do
if [[ -f "$config_dir/$conf" ]]; then
echo -e "${GREEN}$conf${NC}"
else
echo -e "${YELLOW} ⚠️ $conf (missing)${NC}"
fi
done
else
echo -e "${RED}❌ Config directory not found: $config_dir${NC}"
echo -e "${YELLOW} Run './deploy.sh' first to initialize storage${NC}"
fi
# Check override file
local override_file="$PROJECT_DIR/config/server-overrides.conf"
if [[ -f "$override_file" ]]; then
echo -e "${GREEN}✅ Override file: $override_file${NC}"
else
echo -e "${YELLOW}⚠️ Override file not found${NC}"
echo -e "${BLUE} Run '$(basename "$0") edit' to create one${NC}"
fi
# Show available presets
echo -e "\n${BLUE}📋 Available Presets:${NC}"
python3 "$SCRIPT_DIR/apply-config.py" --list-presets
}
main() {
print_header
case "${1:-}" in
"apply")
echo -e "${YELLOW}🔄 Applying configuration overrides...${NC}"
python3 "$SCRIPT_DIR/apply-config.py" "${@:2}"
echo -e "\n${GREEN}✅ Configuration applied!${NC}"
echo -e "${YELLOW} Restart your server to apply changes:${NC} docker compose restart"
;;
"preset")
if [[ -z "${2:-}" ]]; then
echo -e "${RED}❌ Please specify a preset name${NC}"
echo -e "Available presets:"
python3 "$SCRIPT_DIR/apply-config.py" --list-presets
exit 1
fi
echo -e "${YELLOW}🎯 Applying preset: $2${NC}"
python3 "$SCRIPT_DIR/apply-config.py" --preset "$2" "${@:3}"
echo -e "\n${GREEN}✅ Preset '$2' applied!${NC}"
echo -e "${YELLOW} Restart your server to apply changes:${NC} docker compose restart"
;;
"list")
python3 "$SCRIPT_DIR/apply-config.py" --list-presets
;;
"edit")
edit_config
;;
"status")
show_status
;;
"help"|"--help"|"-h"|"")
show_usage
;;
*)
echo -e "${RED}❌ Unknown command: $1${NC}"
show_usage
exit 1
;;
esac
}
main "$@"

View File

@@ -0,0 +1,68 @@
#!/bin/bash
# Copy user database files from database-import/ to backup system
set -e
# Source environment variables
if [ -f ".env" ]; then
set -a
source .env
set +a
fi
IMPORT_DIR="./database-import"
STORAGE_PATH="${STORAGE_PATH:-./storage}"
STORAGE_PATH_LOCAL="${STORAGE_PATH_LOCAL:-./local-storage}"
BACKUP_DIR="${STORAGE_PATH}/backups/daily"
TIMESTAMP=$(date +%Y-%m-%d)
# Exit if no import directory or empty
if [ ! -d "$IMPORT_DIR" ] || [ -z "$(ls -A "$IMPORT_DIR" 2>/dev/null | grep -E '\.(sql|sql\.gz)$')" ]; then
echo "📁 No database files found in $IMPORT_DIR - skipping import"
exit 0
fi
# Exit if backup system already has databases restored
if [ -f "${STORAGE_PATH_LOCAL}/mysql-data/.restore-completed" ]; then
echo "✅ Database already restored - skipping import"
exit 0
fi
echo "📥 Found database files in $IMPORT_DIR"
echo "📂 Copying to backup system for import..."
# Ensure backup directory exists
mkdir -p "$BACKUP_DIR"
# Copy files with smart naming
for file in "$IMPORT_DIR"/*.sql "$IMPORT_DIR"/*.sql.gz; do
[ -f "$file" ] || continue
filename=$(basename "$file")
# Try to detect database type by filename
if echo "$filename" | grep -qi "auth"; then
target_name="acore_auth_${TIMESTAMP}.sql"
elif echo "$filename" | grep -qi "world"; then
target_name="acore_world_${TIMESTAMP}.sql"
elif echo "$filename" | grep -qi "char"; then
target_name="acore_characters_${TIMESTAMP}.sql"
else
# Fallback - use original name with timestamp
base_name="${filename%.*}"
ext="${filename##*.}"
target_name="${base_name}_${TIMESTAMP}.${ext}"
fi
# Add .gz extension if source is compressed
if [[ "$filename" == *.sql.gz ]]; then
target_name="${target_name}.gz"
fi
target_path="$BACKUP_DIR/$target_name"
echo "📋 Copying $filename$target_name"
cp "$file" "$target_path"
done
echo "✅ Database files copied to backup system"
echo "💡 Files will be automatically imported during deployment"

92
scripts/parse-config-presets.py Executable file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""
Parse configuration preset metadata for setup.sh
"""
import sys
import argparse
from pathlib import Path
def parse_preset_metadata(preset_file: Path):
"""Parse CONFIG_NAME and CONFIG_DESCRIPTION from a preset file."""
if not preset_file.exists():
return None, None
config_name = None
config_description = None
try:
with open(preset_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line.startswith('# CONFIG_NAME:'):
config_name = line[14:].strip()
elif line.startswith('# CONFIG_DESCRIPTION:'):
config_description = line[21:].strip()
elif not line.startswith('#'):
# Stop at first non-comment line
break
except Exception:
return None, None
return config_name, config_description
def list_presets(presets_dir: Path):
"""List all available presets with their metadata."""
if not presets_dir.exists():
return
presets = []
for preset_file in presets_dir.glob("*.conf"):
preset_key = preset_file.stem
config_name, config_description = parse_preset_metadata(preset_file)
if config_name is None:
config_name = preset_key.replace('-', ' ').title()
if config_description is None:
config_description = f"Configuration preset: {preset_key}"
presets.append((preset_key, config_name, config_description))
# Sort presets, but ensure 'none' comes first
presets.sort(key=lambda x: (0 if x[0] == 'none' else 1, x[0]))
for preset_key, config_name, config_description in presets:
print(f"{preset_key}\t{config_name}\t{config_description}")
def get_preset_info(presets_dir: Path, preset_key: str):
"""Get information for a specific preset."""
preset_file = presets_dir / f"{preset_key}.conf"
config_name, config_description = parse_preset_metadata(preset_file)
if config_name is None:
config_name = preset_key.replace('-', ' ').title()
if config_description is None:
config_description = f"Configuration preset: {preset_key}"
print(f"{config_name}\t{config_description}")
def main():
parser = argparse.ArgumentParser(description="Parse configuration preset metadata")
parser.add_argument("command", choices=["list", "info"], help="Command to execute")
parser.add_argument("--presets-dir", default="./config/presets", help="Presets directory")
parser.add_argument("--preset", help="Preset name for 'info' command")
args = parser.parse_args()
presets_dir = Path(args.presets_dir)
if args.command == "list":
list_presets(presets_dir)
elif args.command == "info":
if not args.preset:
print("Error: --preset required for 'info' command", file=sys.stderr)
sys.exit(1)
get_preset_info(presets_dir, args.preset)
if __name__ == "__main__":
main()