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

View File

@@ -158,6 +158,13 @@ MODULES_REQUIRES_PLAYERBOT_SOURCE=0
# Client Data Settings
# =====================
CLIENT_DATA_VERSION=v18
# =====================
# Server Configuration
# =====================
# Configuration preset to apply during deployment
# Available: none, blizzlike, fast-leveling, hardcore-pvp, casual-pve
SERVER_CONFIG_PRESET=none
CLIENT_DATA_CACHE_PATH=${STORAGE_PATH_LOCAL}/client-data-cache
CLIENT_DATA_PATH=${STORAGE_PATH}/client-data

4
.gitignore vendored
View File

@@ -2,7 +2,7 @@ data/
backups/
manual-backups/
V1/
ImportBackup/
database-import
database-import/*.sql
database-import/*.sql.gz
source/*
@@ -20,4 +20,4 @@ package-lock.json
package.json
.modules_state
.modules-meta
todo.md
todo.md

279
config/CONFIG_MANAGEMENT.md Normal file
View File

@@ -0,0 +1,279 @@
# Configuration Management System
This system allows easy management of AzerothCore server settings using familiar INI/conf file syntax.
## 📁 File Structure
```
config/
├── server-overrides.conf # Main configuration overrides
└── presets/ # Pre-configured server types
├── blizzlike.conf # Authentic WotLK experience
├── fast-leveling.conf # 3x XP rates
├── hardcore-pvp.conf # Competitive PvP server
└── casual-pve.conf # Relaxed PvE experience
```
## 🚀 Quick Start
### 1. Edit Configuration Settings
Edit `config/server-overrides.conf` with your desired settings:
```ini
[worldserver.conf]
Rate.XP.Kill = 2.0
Rate.XP.Quest = 2.0
GM.InGMList.Level = 3
[playerbots.conf]
AiPlayerbot.MinRandomBots = 100
AiPlayerbot.MaxRandomBots = 300
```
### 2. Apply Configuration
```bash
# Apply your custom overrides
./scripts/apply-config.py
# Or apply a preset
./scripts/apply-config.py --preset fast-leveling
# Preview changes without applying
./scripts/apply-config.py --dry-run
```
### 3. Restart Server
Restart your AzerothCore containers to apply the changes:
```bash
docker compose down && docker compose up -d
```
## 🤖 Automated Integration
The configuration system is fully integrated into the setup and deployment workflow for seamless automation.
### **Setup Integration**
During `./setup.sh`, you can choose a server configuration preset:
```
SERVER CONFIGURATION PRESET
Choose a server configuration preset:
1) Default (No Preset)
Use default AzerothCore settings without any modifications
2) Blizzlike Server
Authentic WotLK experience with 1x rates and original mechanics
3) Fast Leveling Server
3x XP rates with quality of life improvements and cross-faction features
4) Hardcore PvP Server
Competitive PvP environment with 1.5x leveling and minimal bots
5) Casual PvE Server
Relaxed PvE experience with 2x rates and social features
```
The chosen preset is stored in `.env`:
```bash
SERVER_CONFIG_PRESET=fast-leveling
```
### **Deploy Integration**
During `./deploy.sh`, the configuration is automatically applied:
```
Step 5/6: Applying server configuration
✅ Applying server configuration preset: fast-leveling
✅ Server configuration preset 'fast-leveling' applied successfully
Restarting worldserver to apply configuration changes
```
### **Automated Usage Examples**
**Interactive Setup:**
```bash
./setup.sh
# Choose preset during interactive configuration
./deploy.sh
# Configuration automatically applied
```
**Non-Interactive Setup:**
```bash
./setup.sh --server-config fast-leveling --non-interactive
./deploy.sh
# Configuration automatically applied
```
**Skip Configuration:**
```bash
./deploy.sh --skip-config
# Deploys without applying any configuration changes
```
## 📋 Available Commands
### Apply Custom Overrides
```bash
./scripts/apply-config.py
```
### Apply a Preset
```bash
# List available presets
./scripts/apply-config.py --list-presets
# Apply specific preset
./scripts/apply-config.py --preset blizzlike
./scripts/apply-config.py --preset fast-leveling
./scripts/apply-config.py --preset hardcore-pvp
./scripts/apply-config.py --preset casual-pve
```
### Advanced Usage
```bash
# Apply only specific conf files
./scripts/apply-config.py --files "worldserver.conf,playerbots.conf"
# Preview changes without applying
./scripts/apply-config.py --dry-run
# Use different storage path
./scripts/apply-config.py --storage-path /custom/storage
# Use different overrides file
./scripts/apply-config.py --overrides-file /path/to/custom.conf
```
## ⚙️ Configuration Format
### Section Headers
Each section corresponds to a `.conf` file:
```ini
[worldserver.conf] # Settings for worldserver.conf
[authserver.conf] # Settings for authserver.conf
[playerbots.conf] # Settings for playerbots.conf
[mod_transmog.conf] # Settings for mod_transmog.conf
```
### Data Types
```ini
# Boolean values (0 = disabled, 1 = enabled)
SomeFeature.Enable = 1
# Numeric values
Rate.XP.Kill = 2.5
MaxPlayerLevel = 80
# String values (can be quoted or unquoted)
ServerMessage = "Welcome to our server!"
DatabaseInfo = "127.0.0.1;3306;user;pass;db"
```
### Comments
Lines starting with `#` are comments and are ignored:
```ini
# This is a comment
# Rate.XP.Kill = 1.0 # This setting is disabled
Rate.XP.Quest = 2.0 # Active setting with comment
```
## 🎯 Available Presets
### blizzlike.conf
- **Description**: Authentic WotLK experience
- **XP Rates**: 1x (Blizzlike)
- **Features**: No cross-faction interaction, standard death penalties
### fast-leveling.conf
- **Description**: 3x XP with quality of life improvements
- **XP Rates**: 3x Kill/Quest, 2.5x Money
- **Features**: Cross-faction interaction, faster corpse decay, autobalance
### hardcore-pvp.conf
- **Description**: Competitive PvP environment
- **XP Rates**: 1.5x (to reach endgame faster)
- **Features**: No cross-faction interaction, minimal bots, expensive transmog
### casual-pve.conf
- **Description**: Relaxed PvE with social features
- **XP Rates**: 2x XP, 2.5x Rest bonus
- **Features**: Full cross-faction interaction, high bot population, solo LFG
## 🔧 How It Works
1. **Preservation**: The system reads your existing `.conf` files and preserves all comments and structure
2. **Override**: Only the settings you specify are updated
3. **Fallback**: If a `.conf` file doesn't exist, it's created from the corresponding `.dist` file
4. **Safety**: Use `--dry-run` to preview changes before applying
## 📝 Common Settings Reference
### XP and Progression
```ini
[worldserver.conf]
Rate.XP.Kill = 2.0 # XP from killing monsters
Rate.XP.Quest = 2.0 # XP from completing quests
Rate.XP.Explore = 1.5 # XP from exploring new areas
Rate.Rest.InGame = 2.0 # Rest bonus while logged in
Rate.Rest.Offline.InTavernOrCity = 2.0 # Rest bonus while offline in safe zones
```
### Drop Rates
```ini
[worldserver.conf]
Rate.Drop.Money = 1.5 # Money drop rate
Rate.Drop.Items = 1.2 # Item drop rate
```
### Cross-Faction Settings
```ini
[worldserver.conf]
AllowTwoSide.Interaction.Chat = 1 # Cross-faction chat
AllowTwoSide.Interaction.Group = 1 # Cross-faction groups
AllowTwoSide.Interaction.Guild = 1 # Cross-faction guilds
AllowTwoSide.Interaction.Auction = 1 # Shared auction house
AllowTwoSide.Interaction.Mail = 1 # Cross-faction mail
```
### Playerbot Settings
```ini
[playerbots.conf]
AiPlayerbot.RandomBotMinLevel = 15 # Minimum bot level
AiPlayerbot.RandomBotMaxLevel = 80 # Maximum bot level
AiPlayerbot.MinRandomBots = 50 # Minimum number of bots
AiPlayerbot.MaxRandomBots = 200 # Maximum number of bots
AiPlayerbot.RandomBotJoinLfg = 1 # Bots join LFG
AiPlayerbot.RandomBotJoinBG = 1 # Bots join battlegrounds
```
### Module Settings
```ini
[mod_transmog.conf]
Transmogrification.Enable = 1 # Enable transmogrification
Transmogrification.Cost = 100000 # Cost in copper
[mod_autobalance.conf]
AutoBalance.enable = 1 # Enable dungeon scaling
AutoBalance.MinPlayerReward = 1 # Scale rewards for solo play
```
## 🆘 Troubleshooting
### Configuration Not Applied
- Ensure you restart the server after applying changes
- Check that the `.conf` files exist in your `storage/config/` directory
- Use `--dry-run` to verify what changes would be made
### Permission Errors
```bash
# Make sure the script is executable
chmod +x scripts/apply-config.py
# Check file permissions in storage/config/
ls -la storage/config/
```
### Finding Available Settings
- Look in your `storage/config/` directory for `.conf` files
- Each module's available settings are documented in their `.conf` files
- Use `--dry-run` to see which files would be affected

View File

@@ -0,0 +1,36 @@
# CONFIG_NAME: Blizzlike Server
# CONFIG_DESCRIPTION: Authentic WotLK experience with 1x rates and original mechanics
# Blizzlike Server Configuration - Authentic WotLK Experience
[worldserver.conf]
# Blizzlike experience rates (1x)
Rate.XP.Kill = 1.0
Rate.XP.Quest = 1.0
Rate.XP.Explore = 1.0
Rate.Rest.InGame = 1.0
Rate.Rest.Offline.InTavernOrCity = 1.0
# Blizzlike drop rates (1x)
Rate.Drop.Money = 1.0
Rate.Drop.Items = 1.0
# Standard player settings
MaxPlayerLevel = 80
GM.InGMList.Level = 3
# Faction restrictions (authentic)
AllowTwoSide.Interaction.Chat = 0
AllowTwoSide.Interaction.Channel = 0
AllowTwoSide.Interaction.Group = 0
AllowTwoSide.Interaction.Guild = 0
AllowTwoSide.Interaction.Auction = 0
AllowTwoSide.Interaction.Mail = 0
# Standard death penalties
Death.CorpseDecayType = 0
Death.Bones.BattlegroundOrArena = 1
Corpse.Decay.NORMAL = 300
Corpse.Decay.RARE = 900
Corpse.Decay.ELITE = 1800
Corpse.Decay.RAREELITE = 3600
Corpse.Decay.WORLDBOSS = 7200

View File

@@ -0,0 +1,63 @@
# CONFIG_NAME: Casual PvE Server
# CONFIG_DESCRIPTION: Relaxed PvE experience with 2x rates and social features
# Casual PvE Server - Relaxed PvE Experience with Quality of Life
[worldserver.conf]
# Moderate XP rates for casual play
Rate.XP.Kill = 2.0
Rate.XP.Quest = 2.0
Rate.XP.Explore = 2.0
Rate.Rest.InGame = 2.5
Rate.Rest.Offline.InTavernOrCity = 2.5
# Generous drops for casual enjoyment
Rate.Drop.Money = 2.0
Rate.Drop.Items = 1.3
# Standard level cap
MaxPlayerLevel = 80
GM.InGMList.Level = 3
# Full cross-faction interaction for social play
AllowTwoSide.Interaction.Chat = 1
AllowTwoSide.Interaction.Channel = 1
AllowTwoSide.Interaction.Group = 1
AllowTwoSide.Interaction.Guild = 1
AllowTwoSide.Interaction.Auction = 1
AllowTwoSide.Interaction.Mail = 1
# Forgiving death mechanics
Death.CorpseDecayType = 2
Death.Bones.BattlegroundOrArena = 1
Corpse.Decay.NORMAL = 600
Corpse.Decay.RARE = 1200
Corpse.Decay.ELITE = 2400
Corpse.Decay.RAREELITE = 4800
Corpse.Decay.WORLDBOSS = 9600
[playerbots.conf]
# High bot population for social atmosphere
AiPlayerbot.RandomBotMinLevel = 5
AiPlayerbot.RandomBotMaxLevel = 80
AiPlayerbot.MinRandomBots = 200
AiPlayerbot.MaxRandomBots = 500
AiPlayerbot.RandomBotJoinLfg = 1
AiPlayerbot.RandomBotJoinBG = 1
AiPlayerbot.RandomBotRpg = 1
[mod_transmog.conf]
# Affordable transmogrification
Transmogrification.Enable = 1
Transmogrification.Cost = 50000
Transmogrification.AllowMixedArmorTypes = 1
[mod_autobalance.conf]
# Enable autobalance for solo dungeon content
AutoBalance.enable = 1
AutoBalance.MinPlayerReward = 1
AutoBalance.InflectionPoint = 3
[mod_solo_lfg.conf]
# Enable solo LFG for convenience
SoloLFG.Enable = 1
SoloLFG.Announce = 1

View File

@@ -0,0 +1,53 @@
# CONFIG_NAME: Fast Leveling Server
# CONFIG_DESCRIPTION: 3x XP rates with quality of life improvements and cross-faction features
# Fast Leveling Server - 3x XP with Quality of Life Features
[worldserver.conf]
# Fast leveling rates (3x)
Rate.XP.Kill = 3.0
Rate.XP.Quest = 3.0
Rate.XP.Explore = 3.0
Rate.Rest.InGame = 3.0
Rate.Rest.Offline.InTavernOrCity = 3.0
# Generous drop rates
Rate.Drop.Money = 2.5
Rate.Drop.Items = 1.5
# Standard level cap
MaxPlayerLevel = 80
GM.InGMList.Level = 3
# Quality of life - cross-faction interaction
AllowTwoSide.Interaction.Chat = 1
AllowTwoSide.Interaction.Channel = 1
AllowTwoSide.Interaction.Group = 1
AllowTwoSide.Interaction.Guild = 1
AllowTwoSide.Interaction.Auction = 1
AllowTwoSide.Interaction.Mail = 1
# Faster corpse recovery
Death.CorpseDecayType = 2
Death.Bones.BattlegroundOrArena = 1
Corpse.Decay.NORMAL = 180
Corpse.Decay.RARE = 360
Corpse.Decay.ELITE = 720
Corpse.Decay.RAREELITE = 1440
Corpse.Decay.WORLDBOSS = 2880
[playerbots.conf]
# Moderate bot population for fast leveling
AiPlayerbot.RandomBotMinLevel = 10
AiPlayerbot.RandomBotMaxLevel = 80
AiPlayerbot.MinRandomBots = 100
AiPlayerbot.MaxRandomBots = 300
[mod_transmog.conf]
# Cheap transmogrification for appearance customization
Transmogrification.Enable = 1
Transmogrification.Cost = 10000
[mod_autobalance.conf]
# Enable autobalance for solo dungeon experience
AutoBalance.enable = 1
AutoBalance.MinPlayerReward = 1

View File

@@ -0,0 +1,49 @@
# CONFIG_NAME: Hardcore PvP Server
# CONFIG_DESCRIPTION: Competitive PvP environment with 1.5x leveling and minimal bots
# Hardcore PvP Server - Competitive WotLK PvP Experience
[worldserver.conf]
# Slightly accelerated leveling to reach endgame faster
Rate.XP.Kill = 1.5
Rate.XP.Quest = 1.5
Rate.XP.Explore = 1.0
Rate.Rest.InGame = 1.0
Rate.Rest.Offline.InTavernOrCity = 1.0
# Standard drop rates
Rate.Drop.Money = 1.0
Rate.Drop.Items = 1.0
# Level cap
MaxPlayerLevel = 80
GM.InGMList.Level = 3
# No cross-faction interaction (competitive environment)
AllowTwoSide.Interaction.Chat = 0
AllowTwoSide.Interaction.Channel = 0
AllowTwoSide.Interaction.Group = 0
AllowTwoSide.Interaction.Guild = 0
AllowTwoSide.Interaction.Auction = 0
AllowTwoSide.Interaction.Mail = 0
# Standard death penalties
Death.CorpseDecayType = 0
Death.Bones.BattlegroundOrArena = 1
Corpse.Decay.NORMAL = 300
Corpse.Decay.RARE = 900
Corpse.Decay.ELITE = 1800
Corpse.Decay.RAREELITE = 3600
Corpse.Decay.WORLDBOSS = 7200
[playerbots.conf]
# Minimal bot population to maintain authentic PvP experience
AiPlayerbot.RandomBotMinLevel = 70
AiPlayerbot.RandomBotMaxLevel = 80
AiPlayerbot.MinRandomBots = 20
AiPlayerbot.MaxRandomBots = 50
AiPlayerbot.RandomBotJoinBG = 1
[mod_transmog.conf]
# Expensive transmogrification (cosmetic luxury)
Transmogrification.Enable = 1
Transmogrification.Cost = 500000

6
config/presets/none.conf Normal file
View File

@@ -0,0 +1,6 @@
# CONFIG_NAME: Default (No Preset)
# CONFIG_DESCRIPTION: Use default AzerothCore settings without any modifications
# Default Configuration - No modifications applied
# This preset intentionally contains no configuration overrides.
# The server will use default AzerothCore settings from .dist files.

View File

@@ -0,0 +1,134 @@
# AzerothCore Server Configuration Overrides
#
# This file allows you to easily customize server settings without editing
# individual .conf files. Settings defined here will override defaults.
#
# Format: [filename.conf] followed by key = value pairs
# Boolean values: 0 = disabled, 1 = enabled
# Strings: Can be quoted or unquoted
# Numbers: Integer or decimal values
#
# After editing this file, run: ./scripts/apply-config.py
# =====================
# Core Server Settings
# =====================
[worldserver.conf]
# Experience and progression rates
Rate.XP.Kill = 2.0
Rate.XP.Quest = 2.0
Rate.XP.Explore = 1.5
Rate.Rest.InGame = 2.0
Rate.Rest.Offline.InTavernOrCity = 2.0
# Drop rates
Rate.Drop.Money = 1.5
Rate.Drop.Items = 1.2
# Player limits and GM settings
MaxPlayerLevel = 80
GM.InGMList.Level = 3
GM.InWhoList.Level = 3
# Quality of life features
AllowTwoSide.Interaction.Chat = 1
AllowTwoSide.Interaction.Channel = 1
AllowTwoSide.Interaction.Group = 1
AllowTwoSide.Interaction.Guild = 1
AllowTwoSide.Interaction.Auction = 1
AllowTwoSide.Interaction.Mail = 1
# Death and corpse settings
Death.CorpseDecayType = 2
Death.Bones.BattlegroundOrArena = 1
Corpse.Decay.NORMAL = 300
Corpse.Decay.RARE = 900
Corpse.Decay.ELITE = 1800
Corpse.Decay.RAREELITE = 3600
Corpse.Decay.WORLDBOSS = 7200
[authserver.conf]
# Login settings
LoginDatabase.WorkerThreads = 2
LoginDatabase.SynchThreads = 1
# Updates
Updates.AutoSetup = 1
Updates.EnableDatabases = 7
# =====================
# Playerbot Settings (if MODULE_PLAYERBOTS enabled)
# =====================
[playerbots.conf]
# Bot population control
AiPlayerbot.RandomBotMinLevel = 15
AiPlayerbot.RandomBotMaxLevel = 80
AiPlayerbot.MinRandomBots = 50
AiPlayerbot.MaxRandomBots = 200
# Bot behavior
AiPlayerbot.RandomBotJoinLfg = 1
AiPlayerbot.RandomBotJoinBG = 1
AiPlayerbot.RandomBotAutoJoinAR = 1
AiPlayerbot.RandomBotRpg = 1
# Bot restrictions
AiPlayerbot.DisableRandomLevels = 0
AiPlayerbot.RandomBotInGuildFraction = 0.1
# =====================
# Module Configurations
# =====================
[mod_transmog.conf]
# Transmogrification settings
Transmogrification.Enable = 1
Transmogrification.Cost = 100000
Transmogrification.MaxSets = 10
Transmogrification.AllowMixedArmorTypes = 0
Transmogrification.AllowMixedWeaponTypes = 0
[mod_aoe_loot.conf]
# AOE Looting configuration
AOELoot.Enable = 1
[mod_learnspells.conf]
# Auto-learn spells configuration
LearnSpells.Enable = 1
LearnSpells.Gamemasters = 1
[mod_fireworks.conf]
# Fireworks on level up
Fireworks.Enable = 1
[mod_solo_lfg.conf]
# Solo LFG settings
SoloLFG.Enable = 1
SoloLFG.Announce = 1
[mod_autobalance.conf]
# Autobalance for solo play (if enabled)
AutoBalance.enable = 1
AutoBalance.MinPlayerReward = 1
AutoBalance.InflectionPoint = 5
AutoBalance.InflectionPointRaid10M = 7.5
AutoBalance.InflectionPointRaid25M = 17
AutoBalance.InflectionPointRaidHeroic = 5
AutoBalance.InflectionPointHeroic = 5
[mod_individual_progression.conf]
# Individual progression settings (if enabled)
IndividualProgression.Enable = 1
IndividualProgression.VanillaExp = 80000000
IndividualProgression.TbcExp = 150000000
IndividualProgression.WotlkExp = 220000000
# =====================
# Additional Module Settings
# =====================
# Add more module configurations as needed.
# Module conf files are typically named: mod_<modulename>.conf
# Check your storage/config/ directory for available .conf files
# to see what settings are available for each module.

View File

@@ -17,6 +17,7 @@ WATCH_LOGS=1
KEEP_RUNNING=0
WORLD_LOG_SINCE=""
ASSUME_YES=0
SKIP_CONFIG=0
REMOTE_MODE=0
REMOTE_HOST=""
@@ -209,6 +210,7 @@ Options:
--remote-identity PATH SSH private key for remote migration
--remote-project-dir DIR Remote project directory (default: ~/AzerothCore-RealmMaster)
--remote-skip-storage Skip syncing the storage directory during migration
--skip-config Skip applying server configuration preset
-h, --help Show this help
This command automates deployment: sync modules, stage the correct compose profile,
@@ -234,6 +236,7 @@ while [[ $# -gt 0 ]]; do
--remote-identity) REMOTE_IDENTITY="$2"; REMOTE_MODE=1; REMOTE_ARGS_PROVIDED=1; shift 2;;
--remote-project-dir) REMOTE_PROJECT_DIR="$2"; REMOTE_MODE=1; REMOTE_ARGS_PROVIDED=1; shift 2;;
--remote-skip-storage) REMOTE_SKIP_STORAGE=1; REMOTE_MODE=1; REMOTE_ARGS_PROVIDED=1; shift;;
--skip-config) SKIP_CONFIG=1; shift;;
-h|--help) usage; exit 0;;
*) err "Unknown option: $1"; usage; exit 1;;
esac
@@ -667,6 +670,59 @@ wait_for_worldserver_ready(){
done
}
apply_server_config(){
if [ "$SKIP_CONFIG" -eq 1 ]; then
info "Skipping server configuration application (--skip-config flag set)"
return 0
fi
# Read the SERVER_CONFIG_PRESET from .env
local server_config_preset
server_config_preset="$(read_env SERVER_CONFIG_PRESET "none")"
if [ "$server_config_preset" = "none" ] || [ -z "$server_config_preset" ]; then
info "No server configuration preset selected - using defaults"
return 0
fi
info "Applying server configuration preset: $server_config_preset"
local config_script="$ROOT_DIR/scripts/apply-config.py"
if [ ! -x "$config_script" ]; then
warn "Configuration script not found or not executable: $config_script"
warn "Server will use default settings"
return 0
fi
local storage_path
storage_path="$(read_env STORAGE_PATH "./storage")"
# Check if preset file exists
local preset_file="$ROOT_DIR/config/presets/${server_config_preset}.conf"
if [ ! -f "$preset_file" ]; then
warn "Server configuration preset not found: $preset_file"
warn "Server will use default settings"
return 0
fi
# Apply the configuration
if python3 "$config_script" --storage-path "$storage_path" --preset "$server_config_preset"; then
ok "Server configuration preset '$server_config_preset' applied successfully"
info "Restart worldserver to apply configuration changes"
# Restart worldserver if it's running to apply config changes
if docker ps --format '{{.Names}}' | grep -q '^ac-worldserver$'; then
info "Restarting worldserver to apply configuration changes..."
docker restart ac-worldserver
info "Waiting for worldserver to become healthy after configuration..."
sleep 5 # Brief pause before health check
fi
else
warn "Failed to apply server configuration preset '$server_config_preset'"
warn "Server will continue with existing settings"
fi
}
main(){
if [ "$ASSUME_YES" -ne 1 ]; then
if [ -t 0 ]; then
@@ -723,11 +779,18 @@ main(){
stop_runtime_stack
fi
show_step 3 4 "Bringing your realm online"
show_step 3 5 "Importing user database files"
info "Checking for database files in ./database-import/"
bash "$ROOT_DIR/scripts/import-database-files.sh"
show_step 4 6 "Bringing your realm online"
info "Pulling images and waiting for containers to become healthy; this may take a few minutes on first deploy."
stage_runtime
show_step 4 4 "Finalizing deployment"
show_step 5 6 "Applying server configuration"
apply_server_config
show_step 6 6 "Finalizing deployment"
mark_deployment_complete
show_realm_ready

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()

View File

@@ -578,6 +578,7 @@ Options:
--backup-daily-time HH Daily backup hour 00-23 (default 09)
--module-mode MODE suggested, playerbots, manual, or none
--module-config NAME Use preset NAME from config/module-profiles/<NAME>.json
--server-config NAME Use server preset NAME from config/presets/<NAME>.conf
--enable-modules LIST Comma-separated module list (MODULE_* or shorthand)
--playerbot-enabled 0|1 Override PLAYERBOT_ENABLED flag
--playerbot-min-bots N Override PLAYERBOT_MIN_BOTS value
@@ -705,6 +706,13 @@ EOF
--module-config=*)
CLI_MODULE_PRESET="${1#*=}"; shift
;;
--server-config)
[[ $# -ge 2 ]] || { say ERROR "--server-config requires a value"; exit 1; }
CLI_CONFIG_PRESET="$2"; shift 2
;;
--server-config=*)
CLI_CONFIG_PRESET="${1#*=}"; shift
;;
--enable-modules)
[[ $# -ge 2 ]] || { say ERROR "--enable-modules requires a value"; exit 1; }
CLI_ENABLE_MODULES_RAW+=("$2"); shift 2
@@ -930,6 +938,61 @@ fi
BACKUP_RETENTION_HOURS=$(ask "Hourly backups retention (hours)" "${CLI_BACKUP_HOURS:-$DEFAULT_BACKUP_HOURS}" validate_number)
BACKUP_DAILY_TIME=$(ask "Daily backup hour (00-23, UTC)" "${CLI_BACKUP_TIME:-$DEFAULT_BACKUP_TIME}" validate_number)
# Server configuration
say HEADER "SERVER CONFIGURATION PRESET"
local SERVER_CONFIG_PRESET
if [ -n "$CLI_CONFIG_PRESET" ]; then
SERVER_CONFIG_PRESET="$CLI_CONFIG_PRESET"
say INFO "Using preset from command line: $SERVER_CONFIG_PRESET"
else
declare -A CONFIG_PRESET_NAMES=()
declare -A CONFIG_PRESET_DESCRIPTIONS=()
declare -A CONFIG_MENU_INDEX=()
local config_dir="$SCRIPT_DIR/config/presets"
local menu_index=1
echo "Choose a server configuration preset:"
if [ -x "$SCRIPT_DIR/scripts/parse-config-presets.py" ] && [ -d "$config_dir" ]; then
while IFS=$'\t' read -r preset_key preset_name preset_desc; do
[ -n "$preset_key" ] || continue
CONFIG_PRESET_NAMES["$preset_key"]="$preset_name"
CONFIG_PRESET_DESCRIPTIONS["$preset_key"]="$preset_desc"
CONFIG_MENU_INDEX[$menu_index]="$preset_key"
echo "$menu_index) $preset_name"
echo " $preset_desc"
menu_index=$((menu_index + 1))
done < <(python3 "$SCRIPT_DIR/scripts/parse-config-presets.py" list --presets-dir "$config_dir")
else
# Fallback if parser script not available
CONFIG_MENU_INDEX[1]="none"
CONFIG_PRESET_NAMES["none"]="Default (No Preset)"
CONFIG_PRESET_DESCRIPTIONS["none"]="Use default AzerothCore settings"
echo "1) Default (No Preset)"
echo " Use default AzerothCore settings without any modifications"
fi
local max_config_option=$((menu_index - 1))
if [ "$NON_INTERACTIVE" = "1" ]; then
SERVER_CONFIG_PRESET="none"
say INFO "Non-interactive mode: Using default configuration preset"
else
while true; do
read -p "$(echo -e "${YELLOW}🎯 Select server configuration [1-$max_config_option]: ${NC}")" choice
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$max_config_option" ]; then
SERVER_CONFIG_PRESET="${CONFIG_MENU_INDEX[$choice]}"
local chosen_name="${CONFIG_PRESET_NAMES[$SERVER_CONFIG_PRESET]}"
say INFO "Selected: $chosen_name"
break
else
say ERROR "Please select a number between 1 and $max_config_option"
fi
done
fi
fi
local MODE_SELECTION=""
local MODE_PRESET_NAME=""
declare -A MODULE_PRESET_CONFIGS=()
@@ -1604,6 +1667,9 @@ EOF
# Client data
CLIENT_DATA_VERSION=${CLIENT_DATA_VERSION:-$DEFAULT_CLIENT_DATA_VERSION}
# Server configuration
SERVER_CONFIG_PRESET=$SERVER_CONFIG_PRESET
# Playerbot runtime
PLAYERBOT_ENABLED=$PLAYERBOT_ENABLED
PLAYERBOT_MIN_BOTS=$PLAYERBOT_MIN_BOTS