diff --git a/.env.template b/.env.template index c04383a..ed3c0ee 100644 --- a/.env.template +++ b/.env.template @@ -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 diff --git a/.gitignore b/.gitignore index 5375ca4..6819212 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file +todo.md diff --git a/config/CONFIG_MANAGEMENT.md b/config/CONFIG_MANAGEMENT.md new file mode 100644 index 0000000..a45d9f8 --- /dev/null +++ b/config/CONFIG_MANAGEMENT.md @@ -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 \ No newline at end of file diff --git a/config/presets/blizzlike.conf b/config/presets/blizzlike.conf new file mode 100644 index 0000000..7565956 --- /dev/null +++ b/config/presets/blizzlike.conf @@ -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 \ No newline at end of file diff --git a/config/presets/casual-pve.conf b/config/presets/casual-pve.conf new file mode 100644 index 0000000..0bfb51d --- /dev/null +++ b/config/presets/casual-pve.conf @@ -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 \ No newline at end of file diff --git a/config/presets/fast-leveling.conf b/config/presets/fast-leveling.conf new file mode 100644 index 0000000..66d4e66 --- /dev/null +++ b/config/presets/fast-leveling.conf @@ -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 \ No newline at end of file diff --git a/config/presets/hardcore-pvp.conf b/config/presets/hardcore-pvp.conf new file mode 100644 index 0000000..4e3548c --- /dev/null +++ b/config/presets/hardcore-pvp.conf @@ -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 \ No newline at end of file diff --git a/config/presets/none.conf b/config/presets/none.conf new file mode 100644 index 0000000..ae727cb --- /dev/null +++ b/config/presets/none.conf @@ -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. \ No newline at end of file diff --git a/config/server-overrides.conf b/config/server-overrides.conf new file mode 100644 index 0000000..a01a794 --- /dev/null +++ b/config/server-overrides.conf @@ -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_.conf +# Check your storage/config/ directory for available .conf files +# to see what settings are available for each module. \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index 50ec3f1..0b13b37 100755 --- a/deploy.sh +++ b/deploy.sh @@ -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 diff --git a/scripts/apply-config.py b/scripts/apply-config.py new file mode 100755 index 0000000..ee54708 --- /dev/null +++ b/scripts/apply-config.py @@ -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/.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()) \ No newline at end of file diff --git a/scripts/configure-server.sh b/scripts/configure-server.sh new file mode 100755 index 0000000..91ae48b --- /dev/null +++ b/scripts/configure-server.sh @@ -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 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 "$@" \ No newline at end of file diff --git a/scripts/import-database-files.sh b/scripts/import-database-files.sh new file mode 100755 index 0000000..0a7ecb0 --- /dev/null +++ b/scripts/import-database-files.sh @@ -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" \ No newline at end of file diff --git a/scripts/parse-config-presets.py b/scripts/parse-config-presets.py new file mode 100755 index 0000000..1cadd13 --- /dev/null +++ b/scripts/parse-config-presets.py @@ -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() \ No newline at end of file diff --git a/setup.sh b/setup.sh index 7530344..6db9997 100755 --- a/setup.sh +++ b/setup.sh @@ -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/.json + --server-config NAME Use server preset NAME from config/presets/.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