mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-13 01:08:35 +00:00
feat(apps/config): Config Merger in python (#24081)
Co-authored-by: FlyingArowana <TheSCREWEDSoftware@users.noreply.github.com> Co-authored-by: Brian Aldridge <baldridge@resourcedata.com>
This commit is contained in:
@@ -1,22 +1,66 @@
|
||||
# ==== PHP merger (index.php + merge.php) ====
|
||||
# AzerothCore Config Merger
|
||||
|
||||
This is a PHP script for merging a new .dist file with your existing .conf file (worldserver.conf.dist and authserver.conf.dist)
|
||||
This directory contains configuration file merger tools to help update your AzerothCore server and module configurations with new options from distribution files.
|
||||
|
||||
It uses sessions so it is multi user safe, it adds any options that are removed to the bottom of the file commented out, just in case it removes something it shouldn't.
|
||||
If you add your custom patch configs below "# Custom" they will be copied exactly as they are.
|
||||
**Available Options:** PHP and Python versions (**Python recommended for new users**)
|
||||
|
||||
Your new config will be found under $basedir/session_id/newconfig.conf.merge
|
||||
## Purpose
|
||||
|
||||
If you do not run a PHP server on your machiene you can read this guide on ["How to execute PHP code using command line?"](https://www.geeksforgeeks.org/how-to-execute-php-code-using-command-line/) on geeksforgeeks.org.
|
||||
The config merger tools help you update your existing configuration files (`.conf`) to include new options that have been added to the distribution files (`.conf.dist`). Distribution files always contain the most recent configuration changes and new options, while your personal config files may be missing these updates. These tools will:
|
||||
|
||||
```
|
||||
php -S localhost:port -t E:\Azerothcore-wotlk\apps\config-merger\
|
||||
- Compare your existing config files with the latest distribution files
|
||||
- Show you new configuration options that are missing from your files
|
||||
- Allow you to selectively add new options to your configs
|
||||
- Create automatic backups before making any changes
|
||||
- Support authserver.conf, worldserver.conf, and all module configs
|
||||
|
||||
## Available Versions
|
||||
|
||||
### PHP Version
|
||||
|
||||
**Requirements:**
|
||||
- PHP 5.6 or higher
|
||||
- **Requires a web server** (Apache, Nginx, IIS, etc.) to function
|
||||
- No additional libraries required (uses built-in PHP functions only)
|
||||
|
||||
**Features:**
|
||||
- Web-based interface
|
||||
- Configuration file parsing and merging
|
||||
- Browser-accessible configuration management
|
||||
|
||||
**Usage:**
|
||||
- Deploy to web server with PHP support (can be local - XAMPP, WAMP, or built-in PHP server)
|
||||
- Access via web browser
|
||||
- Follow web interface instructions
|
||||
|
||||
### Python Version (Recommended)
|
||||
|
||||
**Requirements:**
|
||||
- Python 3.6 or higher
|
||||
- No additional setup required beyond installing Python
|
||||
- No additional libraries required (uses built-in modules only)
|
||||
|
||||
**Features:**
|
||||
- Interactive menu-driven interface
|
||||
- Support for server configs (authserver.conf, worldserver.conf)
|
||||
- Support for module configs with bulk or selective updates
|
||||
- Automatic backup creation with timestamps
|
||||
- Cross-platform compatibility (Windows, Linux, macOS, and others)
|
||||
- Can be run via command line or by double-clicking the .py file
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Via command line
|
||||
cd /path/to/configs
|
||||
python config_merger.py
|
||||
|
||||
# Or double-click config_merger.py to open in terminal
|
||||
```
|
||||
|
||||
Change port to an available port to use. i.e 8000
|
||||
## Installation
|
||||
|
||||
Then go to your browser and type:
|
||||
When building AzerothCore with the `TOOL_CONFIG_MERGER` CMake option enabled, **only the Python version** will be automatically copied to your configs directory during the build process. The PHP version must be manually deployed to a web server.
|
||||
|
||||
```
|
||||
localhost:8000/index.php
|
||||
```
|
||||
## Support
|
||||
|
||||
Both versions provide the same core functionality for merging configuration files. Choose the version that best fits your environment and preferences. Python is recommended for most users due to its simplicity and no web server requirement.
|
||||
22
apps/config-merger/php/README.md
Normal file
22
apps/config-merger/php/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# ==== PHP merger (index.php + merge.php) ====
|
||||
|
||||
This is a PHP script for merging a new .dist file with your existing .conf file (worldserver.conf.dist and authserver.conf.dist)
|
||||
|
||||
It uses sessions so it is multi user safe, it adds any options that are removed to the bottom of the file commented out, just in case it removes something it shouldn't.
|
||||
If you add your custom patch configs below "# Custom" they will be copied exactly as they are.
|
||||
|
||||
Your new config will be found under $basedir/session_id/newconfig.conf.merge
|
||||
|
||||
If you do not run a PHP server on your machine you can read this guide on ["How to execute PHP code using command line?"](https://www.geeksforgeeks.org/how-to-execute-php-code-using-command-line/) on geeksforgeeks.org.
|
||||
|
||||
```
|
||||
php -S localhost:port -t E:\Azerothcore-wotlk\apps\config-merger\php\
|
||||
```
|
||||
|
||||
Change port to an available port to use. i.e 8000
|
||||
|
||||
Then go to your browser and type:
|
||||
|
||||
```
|
||||
localhost:8000/index.php
|
||||
```
|
||||
148
apps/config-merger/python/README.md
Normal file
148
apps/config-merger/python/README.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# AzerothCore Config Updater/Merger - Python Version
|
||||
|
||||
A command-line tool to update your AzerothCore configuration files with new options from distribution files.
|
||||
|
||||
> [!NOTE]
|
||||
> Based on and modified from [@BoiseComputer](https://github.com/BoiseComputer) (Brian Aldridge)'s [update_module_confs](https://github.com/Brian-Aldridge/update_module_confs) project to meet AzerothCore's needs
|
||||
|
||||
## Overview
|
||||
|
||||
This tool compares your existing configuration files (`.conf`) with the latest distribution files (`.conf.dist`) and helps you add new configuration options that may have been introduced in updates. It ensures your configs stay up-to-date while preserving your custom settings.
|
||||
|
||||
## Features
|
||||
|
||||
- **Interactive Menu System** - Easy-to-use numbered menu options
|
||||
- **Server Config Support** - Update authserver.conf and worldserver.conf
|
||||
- **Module Config Support** - Update all or selected module configurations
|
||||
- **Automatic Backups** - If you choose a valid option and there are changes, a timestamped backup is created before any changes are made (e.g. `filename(d11_m12_y2025_14h_30m_45s).bak`)
|
||||
- **Selective Updates** - Choose which new config options to add (y/n prompts)
|
||||
- **Safe Operation** - Only creates backups and makes changes when new options are found
|
||||
|
||||
## How to Use
|
||||
|
||||
### Interactive Mode (Default)
|
||||
|
||||
1. **Run the script** in your configs directory:
|
||||
```bash
|
||||
python config_merger.py
|
||||
```
|
||||
Or simply **double-click** the `config_merger.py` file to run it directly.
|
||||
|
||||
2. **Specify configs path** (or press Enter for current directory):
|
||||
```
|
||||
Enter the path to your configs folder (default: .) which means current folder:
|
||||
```
|
||||
|
||||
3. **Choose from the menu**:
|
||||
```
|
||||
AzerothCore Config Updater/Merger (v. 1)
|
||||
--------------------------
|
||||
1 - Update Auth Config
|
||||
2 - Update World Config
|
||||
3 - Update Auth and World Configs
|
||||
4 - Update All Modules Configs
|
||||
5 - Update Modules (Selection) Configs
|
||||
0 - Quit
|
||||
```
|
||||
|
||||
### Command Line Interface (CLI)
|
||||
|
||||
For automation and scripting, you can use CLI mode:
|
||||
|
||||
```bash
|
||||
python config_merger.py [config_dir] [target] [options]
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
- `config_dir` (optional): Path to configs directory (default: current directory)
|
||||
- `target` (optional): What to update:
|
||||
- `auth` - Update authserver.conf only
|
||||
- `world` - Update worldserver.conf only
|
||||
- `both` - Update both server configs
|
||||
- `modules` - Update all module configs
|
||||
- `modules-select` - Interactive module selection
|
||||
|
||||
**Options:**
|
||||
- `-y, --yes`: Skip prompts and auto-add all new config options (default: prompt for each option)
|
||||
- `--version`: Show version information
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Interactive mode (default)
|
||||
python config_merger.py
|
||||
|
||||
# Update auth config with prompts
|
||||
python config_merger.py . auth
|
||||
|
||||
# Update both configs automatically (no prompts)
|
||||
python config_merger.py /path/to/configs both -y
|
||||
|
||||
# Update all modules with confirmation
|
||||
python config_merger.py . modules
|
||||
```
|
||||
|
||||
## Menu Options Explained
|
||||
|
||||
- **Option 1**: Updates only `authserver.conf` from `authserver.conf.dist`
|
||||
- **Option 2**: Updates only `worldserver.conf` from `worldserver.conf.dist`
|
||||
- **Option 3**: Updates both server config files
|
||||
- **Option 4**: Automatically processes all module config files in the `modules/` folder
|
||||
- **Option 5**: Shows you a list of available modules and lets you select specific ones to update
|
||||
- **Option 0**: Exit the program
|
||||
|
||||
## Interactive Process
|
||||
|
||||
For each missing configuration option found, the tool will:
|
||||
|
||||
1. **Show you the option** with its comments and default value
|
||||
2. **Ask for confirmation**: `Add [option_name] to config? (y/n):`
|
||||
3. **Add or skip** based on your choice
|
||||
4. **Create backup** (before any changes are made) only if you choose a valid option and there are changes (format: `filename(d11_m12_y2025_14h_30m_45s).bak`)
|
||||
|
||||
## Example Session
|
||||
|
||||
```
|
||||
Processing worldserver.conf ...
|
||||
Backup created: worldserver.conf(d11_m12_y2025_14h_30m_45s).bak
|
||||
|
||||
# New feature for XP rates
|
||||
XP.Rate = 1
|
||||
Add XP.Rate to config? (y/n): y
|
||||
Added XP.Rate.
|
||||
|
||||
# Database connection pool size
|
||||
Database.PoolSize = 5
|
||||
Add Database.PoolSize to config? (y/n): n
|
||||
Skipped Database.PoolSize.
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.6 or higher
|
||||
- No additional libraries needed (uses built-in modules only)
|
||||
|
||||
## File Structure Expected
|
||||
|
||||
```
|
||||
configs/
|
||||
├── config_merger.py (this script)
|
||||
├── authserver.conf.dist
|
||||
├── authserver.conf
|
||||
├── worldserver.conf.dist
|
||||
├── worldserver.conf
|
||||
└── modules/
|
||||
├── mod_example.conf.dist
|
||||
├── mod_example.conf
|
||||
└── ...
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This file is part of the AzerothCore Project. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
**Note:** Original code portions were licensed under the MIT License by Brian Aldridge (https://github.com/BoiseComputer)
|
||||
Original project: https://github.com/Brian-Aldridge/update_module_confs
|
||||
276
apps/config-merger/python/config_merger.py
Normal file
276
apps/config-merger/python/config_merger.py
Normal file
@@ -0,0 +1,276 @@
|
||||
# Version 1
|
||||
# Based and modified from: https://github.com/Brian-Aldridge/update_module_confs
|
||||
#
|
||||
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Original code portions licensed under MIT License by Brian Aldridge (https://github.com/BoiseComputer)
|
||||
# Original project: https://github.com/Brian-Aldridge/update_module_confs
|
||||
|
||||
VERSION = "1"
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import argparse
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
def find_modules(folder):
|
||||
dist_files = []
|
||||
try:
|
||||
files = os.listdir(folder)
|
||||
except (OSError, IOError) as e:
|
||||
print(f"[ERROR] Could not list directory '{folder}': {e}")
|
||||
return []
|
||||
for file in files:
|
||||
if file.endswith('.conf.dist'):
|
||||
dist_files.append(file)
|
||||
return sorted(dist_files)
|
||||
|
||||
def prompt_module_selection(dist_files):
|
||||
print("Found the following modules:")
|
||||
for idx, fname in enumerate(dist_files, 1):
|
||||
print(f" {idx}. {fname}")
|
||||
nums = input("Enter numbers of modules to update (comma-separated): ").strip()
|
||||
raw_inputs = [x.strip() for x in nums.split(",") if x.strip()]
|
||||
indices = []
|
||||
invalid = []
|
||||
for x in raw_inputs:
|
||||
if not x.isdigit():
|
||||
invalid.append(f"'{x}' (not a number)")
|
||||
continue
|
||||
idx = int(x)
|
||||
if 0 < idx <= len(dist_files):
|
||||
indices.append(idx-1)
|
||||
else:
|
||||
invalid.append(f"'{x}' (out of range, must be 1-{len(dist_files)})")
|
||||
if invalid:
|
||||
print("Invalid input:")
|
||||
for msg in invalid:
|
||||
print(f" {msg}")
|
||||
if not indices:
|
||||
print("No valid module numbers were entered.")
|
||||
return []
|
||||
selected = [dist_files[i] for i in indices]
|
||||
return selected
|
||||
|
||||
def backup_file(filepath):
|
||||
timestamp = datetime.now().strftime("d%d_m%m_y%Y_%Hh_%Mm_%Ss")
|
||||
bakpath = f"{filepath}({timestamp}).bak"
|
||||
try:
|
||||
shutil.copy2(filepath, bakpath)
|
||||
print(f" Backup created: {bakpath}")
|
||||
except (OSError, IOError) as e:
|
||||
print(f"[ERROR] Failed to create backup '{bakpath}': {e}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def parse_conf(filepath):
|
||||
# Returns a dict of key: (line, [preceding_comments])
|
||||
try:
|
||||
with open(filepath, encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
except (OSError, IOError) as e:
|
||||
print(f"[ERROR] Failed to read config file '{filepath}': {e}")
|
||||
return None
|
||||
conf = {}
|
||||
comments = []
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith("#"):
|
||||
comments.append(line)
|
||||
continue
|
||||
if stripped.startswith("[") and stripped.endswith("]"):
|
||||
# Ignore [headers of configs]
|
||||
comments.clear()
|
||||
continue
|
||||
if stripped.count("=") == 1:
|
||||
key, value = [s.strip() for s in stripped.split("=", 1)]
|
||||
if '#' in value:
|
||||
value = value.split('#', 1)[0].rstrip()
|
||||
if key:
|
||||
conf[key] = (f"{key} = {value}\n", comments.copy())
|
||||
comments.clear()
|
||||
continue
|
||||
return conf
|
||||
|
||||
def find_missing_keys(dist_conf, user_conf):
|
||||
missing = {}
|
||||
for key, (line, comments) in dist_conf.items():
|
||||
if key not in user_conf:
|
||||
missing[key] = (line, comments)
|
||||
return missing
|
||||
|
||||
def update_conf(dist_path, conf_path, skip_prompts=False):
|
||||
if not os.path.exists(conf_path):
|
||||
print(f" User config {conf_path} does not exist, skipping.")
|
||||
return False
|
||||
dist_conf = parse_conf(dist_path)
|
||||
user_conf = parse_conf(conf_path)
|
||||
missing = find_missing_keys(dist_conf, user_conf)
|
||||
if not missing:
|
||||
print(" No new config options to add.")
|
||||
return False
|
||||
updated = False
|
||||
lines_to_add = []
|
||||
for key, (line, comments) in missing.items():
|
||||
if skip_prompts:
|
||||
lines_to_add.append((comments, line, key))
|
||||
else:
|
||||
print("\n" + "".join(comments if comments else []) + line, end="")
|
||||
add = input(f" Add {key} to config? (y/n): ").strip().lower()
|
||||
if add in ("", "y", "yes"):
|
||||
lines_to_add.append((comments, line, key))
|
||||
else:
|
||||
print(f" Skipped {key}.")
|
||||
if lines_to_add:
|
||||
backup_file(conf_path)
|
||||
# Write using system's default line ending to avoid mixing CRLF and LF in the config file
|
||||
newline = os.linesep.encode('utf-8')
|
||||
with open(conf_path, "ab") as f:
|
||||
for comments, line, key in lines_to_add:
|
||||
if comments:
|
||||
for c in comments:
|
||||
f.write(c.rstrip('\r\n').encode('utf-8') + newline)
|
||||
f.write(line.rstrip('\r\n').encode('utf-8') + newline)
|
||||
print(f" Added {key}.")
|
||||
updated = True
|
||||
return updated
|
||||
|
||||
def update_server_config(config_name, config_dir, skip_prompts=False):
|
||||
dist_path = os.path.join(config_dir, f"{config_name}.conf.dist")
|
||||
conf_path = os.path.join(config_dir, f"{config_name}.conf")
|
||||
|
||||
if not os.path.exists(dist_path):
|
||||
print(f" Distribution config {dist_path} does not exist, skipping.")
|
||||
return False
|
||||
|
||||
print(f"\nProcessing {config_name}.conf ...")
|
||||
return update_conf(dist_path, conf_path, skip_prompts)
|
||||
|
||||
def update_modules(config_dir, selected_only=False, skip_prompts=False):
|
||||
modules_dir = os.path.join(config_dir, "modules")
|
||||
if not os.path.exists(modules_dir):
|
||||
print(f" Modules directory {modules_dir} does not exist, skipping.")
|
||||
return
|
||||
|
||||
dist_files = find_modules(modules_dir)
|
||||
if not dist_files:
|
||||
print(" No .conf.dist files found in modules folder.")
|
||||
return
|
||||
|
||||
if selected_only:
|
||||
selected = prompt_module_selection(dist_files)
|
||||
if not selected:
|
||||
print(" No modules selected.")
|
||||
return
|
||||
else:
|
||||
selected = dist_files
|
||||
|
||||
for dist_fname in selected:
|
||||
module = dist_fname[:-5] # Removes ".dist"
|
||||
conf_fname = module # e.g., mod_x.conf
|
||||
dist_path = os.path.join(modules_dir, dist_fname)
|
||||
conf_path = os.path.join(modules_dir, conf_fname)
|
||||
print(f"\nProcessing {conf_fname} ...")
|
||||
update_conf(dist_path, conf_path, skip_prompts)
|
||||
|
||||
def show_main_menu():
|
||||
print(f"\nAzerothCore Config Updater/Merger (v. {VERSION})")
|
||||
print("--------------------------")
|
||||
print("1 - Update Auth Config")
|
||||
print("2 - Update World Config")
|
||||
print("3 - Update Auth and World Configs")
|
||||
print("4 - Update All Modules Configs")
|
||||
print("5 - Update Modules (Selection) Configs")
|
||||
print("0 - Quit")
|
||||
return input("Select an option: ").strip()
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='AzerothCore Config Updater/Merger')
|
||||
parser.add_argument('config_dir', nargs='?', default='.',
|
||||
help='Path to configs directory (default: current directory)')
|
||||
parser.add_argument('target', nargs='?',
|
||||
choices=['auth', 'world', 'both', 'modules', 'modules-select'],
|
||||
help='What to update: auth, world, both, modules, modules-select')
|
||||
parser.add_argument('-y', '--yes', action='store_true',
|
||||
help='Automatically answer yes to all prompts')
|
||||
parser.add_argument('--version', action='version', version=f'%(prog)s {VERSION}')
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
# If no target specified, run interactive mode
|
||||
if args.target is None:
|
||||
print(f"AzerothCore Config Updater/Merger (v. {VERSION})")
|
||||
print("==========================")
|
||||
config_dir = input("Enter the path to your configs folder (Default / Empty will use the folder where this script is located): ").strip()
|
||||
if not config_dir:
|
||||
config_dir = "."
|
||||
|
||||
if not os.path.isdir(config_dir):
|
||||
print("Provided path is not a valid directory.")
|
||||
return
|
||||
|
||||
while True:
|
||||
choice = show_main_menu()
|
||||
|
||||
if choice == "1":
|
||||
update_server_config("authserver", config_dir)
|
||||
elif choice == "2":
|
||||
update_server_config("worldserver", config_dir)
|
||||
elif choice == "3":
|
||||
update_server_config("authserver", config_dir)
|
||||
update_server_config("worldserver", config_dir)
|
||||
elif choice == "4":
|
||||
update_modules(config_dir, selected_only=False)
|
||||
elif choice == "5":
|
||||
update_modules(config_dir, selected_only=True)
|
||||
elif choice == "0":
|
||||
print("Goodbye!")
|
||||
break
|
||||
else:
|
||||
print("Invalid selection. Please try again.")
|
||||
else:
|
||||
# CLI mode
|
||||
config_dir = args.config_dir
|
||||
|
||||
if not os.path.isdir(config_dir):
|
||||
print(f"Error: Directory '{config_dir}' does not exist.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"AzerothCore Config Updater/Merger (v. {VERSION}) - CLI Mode")
|
||||
print(f"Config directory: {os.path.abspath(config_dir)}")
|
||||
print(f"Target: {args.target}")
|
||||
if args.yes:
|
||||
print("Skip prompts: Yes")
|
||||
|
||||
if args.target == 'auth':
|
||||
update_server_config("authserver", config_dir, args.yes)
|
||||
elif args.target == 'world':
|
||||
update_server_config("worldserver", config_dir, args.yes)
|
||||
elif args.target == 'both':
|
||||
update_server_config("authserver", config_dir, args.yes)
|
||||
update_server_config("worldserver", config_dir, args.yes)
|
||||
elif args.target == 'modules':
|
||||
update_modules(config_dir, selected_only=False, skip_prompts=args.yes)
|
||||
elif args.target == 'modules-select':
|
||||
if args.yes:
|
||||
print("Warning: --yes flag ignored for modules-select (requires interactive selection)")
|
||||
update_modules(config_dir, selected_only=True, skip_prompts=False)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user