Introduce dynamic overrides and rename module manifest

This commit is contained in:
uprightbass360
2025-11-08 01:49:21 -05:00
parent 662af4b3a7
commit 622fd518d2
29 changed files with 261 additions and 155 deletions

View File

@@ -2,6 +2,14 @@
# Docker Compose will auto-load .env in the same folder as docker-compose.yml.
# Template for acore-compose profiles-based compose
# =====================
# Compose overrides (set to 1 to include matching file under compose-overrides/)
# =====================
# mysql-expose.yml -> exposes MySQL externally via COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED
# worldserver-debug-logging.yml -> raises log verbosity via COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED
COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED=0
COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED=0
# =====================
# Project name
# =====================
@@ -43,10 +51,10 @@ CONTAINER_POST_INSTALL=ac-post-install
# =====================
# Images
# =====================
AC_DB_IMPORT_IMAGE=acore/ac-wotlk-db-import:14.0.0-dev
AC_DB_IMPORT_IMAGE=acore/ac-wotlk-db-import:master
# Services (Standard)
AC_AUTHSERVER_IMAGE=acore/ac-wotlk-authserver:14.0.0-dev
AC_WORLDSERVER_IMAGE=acore/ac-wotlk-worldserver:14.0.0-dev
AC_AUTHSERVER_IMAGE=acore/ac-wotlk-authserver:master
AC_WORLDSERVER_IMAGE=acore/ac-wotlk-worldserver:master
# Services (Playerbots)
AC_AUTHSERVER_IMAGE_PLAYERBOTS=acore-compose:authserver-playerbots
AC_WORLDSERVER_IMAGE_PLAYERBOTS=acore-compose:worldserver-playerbots
@@ -55,7 +63,7 @@ AC_WORLDSERVER_IMAGE_PLAYERBOTS=acore-compose:worldserver-playerbots
AC_AUTHSERVER_IMAGE_MODULES=acore-compose:authserver-modules-latest
AC_WORLDSERVER_IMAGE_MODULES=acore-compose:worldserver-modules-latest
# Client Data
AC_CLIENT_DATA_IMAGE=acore/ac-wotlk-client-data:14.0.0-dev
AC_CLIENT_DATA_IMAGE=acore/ac-wotlk-client-data:master
AC_CLIENT_DATA_IMAGE_PLAYERBOTS=uprightbass360/azerothcore-wotlk-playerbots:client-data-Playerbot
# Build artifacts
DOCKER_IMAGE_TAG=master
@@ -101,7 +109,6 @@ MYSQL_ROOT_HOST=%
MYSQL_USER=root
MYSQL_PORT=3306
MYSQL_EXTERNAL_PORT=64306
MYSQL_EXPOSE_PORT=0
MYSQL_CHARACTER_SET=utf8mb4
MYSQL_COLLATION=utf8mb4_unicode_ci
MYSQL_MAX_CONNECTIONS=1000

View File

@@ -97,6 +97,7 @@ cd AzerothCore-RealmMaster
The setup wizard will guide you through:
- **Server Configuration**: IP address, ports, timezone
- **Module Selection**: Choose from 30+ available modules or use presets
- **Module Definitions**: Customize defaults in `config/module-manifest.json` and optional presets under `config/module-profiles/`
- **Storage Paths**: Configure NFS/local storage locations
- **Playerbot Settings**: Max bots, account limits (if enabled)
- **Backup Settings**: Retention policies for automated backups
@@ -116,7 +117,7 @@ The setup wizard will guide you through:
**Required when:**
- Playerbots enabled (`MODULE_PLAYERBOTS=1`)
- Any C++ module enabled (modules with `"type": "cpp"` in `config/modules.json`)
- Any C++ module enabled (modules with `"type": "cpp"` in `config/module-manifest.json`)
**Build process:**
1. Clones AzerothCore source to `local-storage/source/`
@@ -249,7 +250,7 @@ The remote deployment process transfers:
- ❌ Build artifacts (source code, compilation files stay local)
#### Module Presets
- Define JSON presets in `profiles/*.json`. Each file contains:
- Define JSON presets in `config/module-profiles/*.json`. Each file contains:
- `modules` (array, required) list of `MODULE_*` identifiers to enable.
- `label` (string, optional) text shown in the setup menu (emoji welcome).
- `description` (string, optional) short help text for maintainers.
@@ -266,11 +267,12 @@ The remote deployment process transfers:
```
- `setup.sh` automatically adds these presets to the module menu and enables the listed modules when selected or when `--module-config <name>` is provided.
- Built-in presets:
- `profiles/suggested-modules.json` default solo-friendly QoL stack.
- `profiles/playerbots-suggested-modules.json` suggested stack plus playerbots.
- `profiles/playerbots-only.json` playerbot-focused profile (adjust `--playerbot-max-bots`).
- `config/module-profiles/suggested-modules.json` default solo-friendly QoL stack.
- `config/module-profiles/playerbots-suggested-modules.json` suggested stack plus playerbots.
- `config/module-profiles/playerbots-only.json` playerbot-focused profile (adjust `--playerbot-max-bots`).
- Custom example:
- `profiles/sam.json` Sam's playerbot-focused profile (set `--playerbot-max-bots 3000` when using this preset).
- `config/module-profiles/sam.json` Sam's playerbot-focused profile (set `--playerbot-max-bots 3000` when using this preset).
- Module metadata lives in `config/module-manifest.json`; update that file if you need to add new modules or change repositories/branches.
### Post-Installation Steps
@@ -757,7 +759,7 @@ flowchart TB
| Service / Container | Role | Ports (host → container) | Profile |
|---------------------|------|--------------------------|---------|
| `ac-mysql` | MySQL 8.0 database | *(optional)* `64306 → 3306` (`MYSQL_EXPOSE_PORT=1`) | `db` |
| `ac-mysql` | MySQL 8.0 database | *(optional)* `64306 → 3306` (`COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED=1`) | `db` |
| `ac-db-init` | Database schema initialization | | `db` |
| `ac-db-import` | Database content import | | `db` |
| `ac-backup` | Automated backup system | | `db` |
@@ -775,10 +777,22 @@ flowchart TB
### Database Hardening
- **MySQL port exposure** By default `MYSQL_EXPOSE_PORT=0`, so `ac-mysql` is reachable only from the internal Docker network. Set `MYSQL_EXPOSE_PORT=1` to publish `${MYSQL_EXTERNAL_PORT}` on the host; RealmMaster scripts automatically include `docker-compose.mysql-expose.yml` so the override Just Works. If you invoke Compose manually, remember to add `-f docker-compose.mysql-expose.yml`.
- **MySQL port exposure** By default `COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED=0`, so `ac-mysql` is reachable only from the internal Docker network. Set it to `1` to publish `${MYSQL_EXTERNAL_PORT}` on the host; RealmMaster scripts automatically include `compose-overrides/mysql-expose.yml` so the override Just Works. If you invoke Compose manually, remember to add `-f compose-overrides/mysql-expose.yml`. You can follow the same `COMPOSE_OVERRIDE_<NAME>_ENABLED=1` pattern for any custom override files you drop into `compose-overrides/`.
- **Worldserver debug logging** Need extra verbosity temporarily? Flip `COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED=1` to include `compose-overrides/worldserver-debug-logging.yml`, which bumps `AC_LOG_LEVEL` across all worldserver profiles. Turn it back off once youre done to avoid noisy logs.
- **Binary logging toggle** `MYSQL_DISABLE_BINLOG=1` appends `--skip-log-bin` via the MySQL wrapper entrypoint to keep disk churn low (and match Playerbot guidance). Flip the flag to `0` to re-enable binlogs for debugging or replication.
- **Drop-in configs** Any `.cnf` placed in `${STORAGE_PATH}/config/mysql/conf.d` (exposed via `MYSQL_CONFIG_DIR`) is mounted into `/etc/mysql/conf.d`. Use this to add custom tunables or temporarily override the binlog setting without touching the image.
### Compose Overrides
All helper scripts automatically include any override file found in `compose-overrides/` when its matching flag `COMPOSE_OVERRIDE_<NAME>_ENABLED` is set to `1` in `.env`. Each override declares its flag at the top with `# override-flag: ...`. This lets you ship opt-in tweaks without editing `docker-compose.yml`.
Current examples:
- `compose-overrides/mysql-expose.yml` (`COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED`) Publishes MySQL to `${MYSQL_EXTERNAL_PORT}` for external clients.
- `compose-overrides/worldserver-debug-logging.yml` (`COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED`) Raises `AC_LOG_LEVEL` to `3` across all worldserver profiles for troubleshooting.
Add your own override by dropping a new `.yml` file into `compose-overrides/`, documenting the flag name in a comment, and toggling that flag in `.env`.
### Storage Structure
The project uses a dual-storage approach for optimal performance:
@@ -986,9 +1000,9 @@ Internal script that runs inside the `ac-modules` container to handle module lif
- Manages module configuration files
- Tracks installation state
#### `config/modules.json` & `scripts/modules.py`
#### `config/module-manifest.json` & `scripts/modules.py`
Central module registry and management system:
- **`config/modules.json`** - Declarative manifest defining all 30+ supported modules with metadata:
- **`config/module-manifest.json`** - Declarative manifest defining all 30+ supported modules with metadata:
- Repository URLs
- Module type (cpp, data, lua)
- Build requirements

View File

@@ -114,7 +114,7 @@ generate_module_state(){
storage_root="$(resolve_local_storage_path)"
local output_dir="${storage_root}/modules"
ensure_modules_dir_writable "$storage_root"
if ! python3 "$MODULE_HELPER" --env-path "$ENV_PATH" --manifest "$ROOT_DIR/config/modules.json" generate --output-dir "$output_dir"; then
if ! python3 "$MODULE_HELPER" --env-path "$ENV_PATH" --manifest "$ROOT_DIR/config/module-manifest.json" generate --output-dir "$output_dir"; then
err "Module manifest validation failed. See errors above."
exit 1
fi

View File

@@ -13,6 +13,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="${SCRIPT_DIR}"
DEFAULT_COMPOSE_FILE="${PROJECT_DIR}/docker-compose.yml"
ENV_FILE="${PROJECT_DIR}/.env"
source "${PROJECT_DIR}/scripts/lib/compose_overrides.sh"
declare -a COMPOSE_FILE_ARGS=()
# Colors
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; MAGENTA='\033[0;35m'; NC='\033[0m'
@@ -125,15 +127,7 @@ if [ -f "$ENV_FILE" ]; then
set -a; source "$ENV_FILE"; set +a
fi
COMPOSE_FILE_ARGS=(-f "$DEFAULT_COMPOSE_FILE")
if [ "${MYSQL_EXPOSE_PORT:-0}" = "1" ]; then
EXTRA_COMPOSE_FILE="${PROJECT_DIR}/docker-compose.mysql-expose.yml"
if [ -f "$EXTRA_COMPOSE_FILE" ]; then
COMPOSE_FILE_ARGS+=(-f "$EXTRA_COMPOSE_FILE")
else
print_status WARNING "MYSQL_EXPOSE_PORT=1 but $EXTRA_COMPOSE_FILE missing; skipping port exposure override."
fi
fi
compose_overrides::build_compose_args "$PROJECT_DIR" "$ENV_FILE" "$DEFAULT_COMPOSE_FILE" COMPOSE_FILE_ARGS
COMPOSE_FILE_ARGS_STR=""
for arg in "${COMPOSE_FILE_ARGS[@]}"; do
COMPOSE_FILE_ARGS_STR+=" ${arg}"

View File

@@ -0,0 +1,12 @@
# override-flag: COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED
# legacy-flag: MYSQL_EXPOSE_PORT
# Optional override file that publishes MySQL to the host. Use it only when you
# intentionally need direct access (e.g., external DB clients). RealmMaster scripts
# auto-include this file whenever `COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED=1`
# (or the legacy `MYSQL_EXPOSE_PORT=1`) in `.env`. When running Compose manually, add:
# docker compose -f docker-compose.yml -f compose-overrides/mysql-expose.yml up -d
# Reset the flag to 0 to return to the secure, internal-only default.
services:
ac-mysql:
ports:
- "${MYSQL_EXTERNAL_PORT}:${MYSQL_PORT}"

View File

@@ -0,0 +1,15 @@
# override-flag: COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED
# Example override that bumps worldserver log verbosity across all profiles.
# Enable by setting COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED=1 in .env.
# Helpful while investigating live issues; remember to turn it off to reduce noise.
# Follow this pattern for any compose layers you want to implement
services:
ac-worldserver-standard:
environment:
AC_LOG_LEVEL: "3"
ac-worldserver-playerbots:
environment:
AC_LOG_LEVEL: "3"
ac-worldserver-modules:
environment:
AC_LOG_LEVEL: "3"

View File

@@ -1,28 +1,39 @@
{
"modules": [
"MODULE_ELUNA",
"MODULE_PLAYERBOTS",
"MODULE_PLAYER_BOT_LEVEL_BRACKETS",
"MODULE_SOLO_LFG",
"MODULE_TRANSMOG",
"MODULE_NPC_BUFFER",
"MODULE_LEARN_SPELLS",
"MODULE_FIREWORKS",
"MODULE_REAGENT_BANK",
"MODULE_BLACK_MARKET_AUCTION_HOUSE",
"MODULE_TRANSMOG",
"MODULE_NPC_BUFFER",
"MODULE_SOLO_LFG",
"MODULE_1V1_ARENA",
"MODULE_ACCOUNT_ACHIEVEMENTS",
"MODULE_BREAKING_NEWS",
"MODULE_BOSS_ANNOUNCER",
"MODULE_ACCOUNT_ACHIEVEMENTS",
"MODULE_AUTO_REVIVE",
"MODULE_ELUNA_TS",
"MODULE_GAIN_HONOR_GUARD",
"MODULE_ELUNA",
"MODULE_TIME_IS_TIME",
"MODULE_RANDOM_ENCHANTS",
"MODULE_SOLOCRAFT",
"MODULE_NPC_BEASTMASTER",
"MODULE_NPC_ENCHANTER",
"MODULE_RANDOM_ENCHANTS",
"MODULE_INSTANCE_RESET",
"MODULE_TIME_IS_TIME",
"MODULE_GAIN_HONOR_GUARD",
"MODULE_ARAC"
"MODULE_ARAC",
"MODULE_ASSISTANT",
"MODULE_REAGENT_BANK",
"MODULE_BLACK_MARKET_AUCTION_HOUSE",
"MODULE_STATBOOSTER",
"MODULE_ELUNA_TS",
"MODULE_AIO",
"MODULE_ELUNA_SCRIPTS",
"MODULE_EVENT_SCRIPTS",
"MODULE_ACTIVE_CHAT",
"MODULE_GUILDHOUSE",
"MODULE_NPC_FREE_PROFESSIONS",
"MODULE_MORPHSUMMON",
"MODULE_ITEM_LEVEL_UP",
"MODULE_GLOBAL_CHAT"
],
"label": "\ud83e\udde9 Sam",
"description": "Sam's playerbot-centric preset (use high bot counts)",

View File

@@ -11,6 +11,7 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_COMPOSE_FILE="$ROOT_DIR/docker-compose.yml"
ENV_PATH="$ROOT_DIR/.env"
source "$ROOT_DIR/scripts/lib/compose_overrides.sh"
TARGET_PROFILE=""
WATCH_LOGS=1
KEEP_RUNNING=0
@@ -280,17 +281,7 @@ read_env(){
}
init_compose_files(){
local expose_port
expose_port="$(read_env MYSQL_EXPOSE_PORT "0")"
COMPOSE_FILE_ARGS=(-f "$DEFAULT_COMPOSE_FILE")
if [ "$expose_port" = "1" ]; then
local extra_file="$ROOT_DIR/docker-compose.mysql-expose.yml"
if [ -f "$extra_file" ]; then
COMPOSE_FILE_ARGS+=(-f "$extra_file")
else
warn "MYSQL_EXPOSE_PORT=1 but $extra_file not found; skipping port override"
fi
fi
compose_overrides::build_compose_args "$ROOT_DIR" "$ENV_PATH" "$DEFAULT_COMPOSE_FILE" COMPOSE_FILE_ARGS
}
init_compose_files
@@ -337,7 +328,7 @@ ensure_module_state(){
local output_dir="${storage_root}/modules"
ensure_modules_dir_writable "$storage_root"
if ! python3 "$MODULE_HELPER" --env-path "$ENV_PATH" --manifest "$ROOT_DIR/config/modules.json" generate --output-dir "$output_dir"; then
if ! python3 "$MODULE_HELPER" --env-path "$ENV_PATH" --manifest "$ROOT_DIR/config/module-manifest.json" generate --output-dir "$output_dir"; then
err "Module manifest validation failed. See errors above."
fi

View File

@@ -1,5 +0,0 @@
services:
ac-mysql:
# Optional override that publishes the MySQL port when MYSQL_EXPOSE_PORT=1
ports:
- "${MYSQL_EXTERNAL_PORT}:${MYSQL_PORT}"

View File

@@ -596,7 +596,7 @@ services:
env_file:
- ./.env
environment:
MODULES_MANIFEST_PATH: /tmp/config/modules.json
MODULES_MANIFEST_PATH: /tmp/config/module-manifest.json
entrypoint: ["/bin/sh"]
command:
- -c

View File

@@ -14,9 +14,27 @@ This guide mirrors the community “Installing AzerothCore using Docker” workf
RealmMaster keeps the familiar `docker-compose.yml` at the repo root. Instead of editing the YAML directly, run `./setup.sh` (see [README → Getting Started](../README.md#getting-started)) to generate `.env`; every setting from storage paths and ports to module toggles lives there. This mirrors the upstream “use docker-compose.override.yml” advice while preserving a single declarative stack.
- **Security**: databases stay on the internal `azerothcore` bridge and never publish MySQL ports unless you explicitly set `MYSQL_EXPOSE_PORT` in `.env`. Binary logging is disabled via `MYSQL_DISABLE_BINLOG=1`, matching the upstream recommendation for playerbots.
- **Security**: databases stay on the internal `azerothcore` bridge and never publish MySQL ports unless you explicitly set `COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED=1` in `.env`. Binary logging is disabled via `MYSQL_DISABLE_BINLOG=1`, matching the upstream recommendation for playerbots.
- **Storage**: bind mounts map to `storage/` and `local-storage/`, ensuring data survives container rebuilds just like the original bind-mount instructions. `ac-volume-init` and `ac-storage-init` bootstrap ownership so you do not need to chown paths manually.
- **Networks & profiles**: all services share the `azerothcore` bridge, and Compose profiles (`services-standard`, `services-playerbots`, `services-modules`, `tools`) let you enable only what you need, similar to copying multiple override files upstream.
- **Override toggles**: drop-in files under `compose-overrides/` (like `mysql-expose.yml` for port exposure or `worldserver-debug-logging.yml` for verbose logs) can be activated by setting `COMPOSE_OVERRIDE_<NAME>_ENABLED=1` in `.env`, so you can extend the stack without editing the main compose file.
- **Module manifest**: all module metadata lives in `config/module-manifest.json`; presets surfaced in `setup.sh` come from `config/module-profiles/*.json`, so you can adapt the same workflow the upstream document used by editing those files.
### Override Examples
RealmMaster ships with two opt-in overrides to demonstrate the pattern:
- `compose-overrides/mysql-expose.yml` (`COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED=1`) publishes MySQL on `${MYSQL_EXTERNAL_PORT}` for IDEs or external tooling.
- `compose-overrides/worldserver-debug-logging.yml` (`COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED=1`) bumps `AC_LOG_LEVEL` to `3` across every worldserver profile for troubleshooting.
Add your own overrides by dropping a `.yml` file into `compose-overrides/` with a `# override-flag: ...` header and toggling the matching env flag. All project scripts automatically include enabled overrides, so the workflow mirrors the upstream “override file” approach without manual compose arguments.
### Module Layout
- **Manifest**: `config/module-manifest.json` tracks every supported module (type, repo, dependencies). Edit this if you need to add or update modules—`scripts/modules.py` and all container helpers consume it automatically.
- **Presets**: `config/module-profiles/*.json` replaces the old `profiles/*.json`. Each preset defines a `modules` list plus optional `label/description/order`, and `setup.sh` surfaces them in the module-selection menu or via `--module-config <name>`.
Because the manifest/preset locations mirror the upstream structure conceptually, experienced users can jump straight into editing those files without re-learning the workflow.
Example excerpt (trimmed for clarity):

View File

@@ -8,7 +8,7 @@ from pathlib import Path
def load_module_state(root: Path) -> dict:
env_path = root / ".env"
manifest_path = root / "config" / "modules.json"
manifest_path = root / "config" / "module-manifest.json"
modules_py = root / "scripts" / "modules.py"
try:

View File

@@ -7,6 +7,7 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
DEFAULT_COMPOSE_FILE="$ROOT_DIR/docker-compose.yml"
ENV_FILE="$ROOT_DIR/.env"
source "$ROOT_DIR/scripts/lib/compose_overrides.sh"
declare -a COMPOSE_FILE_ARGS=()
BLUE='\033[0;34m'
@@ -46,15 +47,7 @@ resolve_project_name(){
}
init_compose_files(){
COMPOSE_FILE_ARGS=(-f "$DEFAULT_COMPOSE_FILE")
if [ "$(read_env MYSQL_EXPOSE_PORT "0")" = "1" ]; then
local extra_file="$ROOT_DIR/docker-compose.mysql-expose.yml"
if [ -f "$extra_file" ]; then
COMPOSE_FILE_ARGS+=(-f "$extra_file")
else
warn "MYSQL_EXPOSE_PORT=1 but $extra_file missing; skipping port override."
fi
fi
compose_overrides::build_compose_args "$ROOT_DIR" "$ENV_FILE" "$DEFAULT_COMPOSE_FILE" COMPOSE_FILE_ARGS
}
init_compose_files

View File

@@ -1,49 +0,0 @@
# Post-Install Hooks Refactoring Summary
## What Was Accomplished
### 1. **Legacy System Issues**
- Hardcoded hooks in `manage-modules.sh` (only 2 implemented)
- 26 undefined hooks from Eluna modules causing warnings
- No extensibility or maintainability
### 2. **New Architecture Implemented**
#### **External Hook Scripts** (`scripts/hooks/`)
- `copy-standard-lua` - Generic Lua script copying for Eluna modules
- `copy-aio-lua` - AIO-specific Lua script handling
- `mod-ale-patches` - mod-ale compatibility patches
- `black-market-setup` - Black Market specific setup
- `README.md` - Complete documentation
#### **Manifest-Driven Configuration**
- All hooks now defined in `config/modules.json`
- Standardized hook names (kebab-case)
- No more undefined hooks
#### **Refactored Hook Runner** (`manage-modules.sh`)
- External script execution with environment variables
- Proper error handling (exit codes 0/1/2)
- Environment cleanup
- Removed legacy fallback code
### 3. **Hook Mapping Applied**
- **24 Eluna modules** → `copy-standard-lua`
- **2 AIO modules** → `copy-aio-lua`
- **1 mod-ale module** → `mod-ale-patches`
- **1 Black Market module** → `black-market-setup`
### 4. **Benefits Achieved**
-**Maintainable** - Hooks are separate, reusable scripts
-**Extensible** - Easy to add new hooks without code changes
-**Reliable** - No more undefined hook warnings
-**Documented** - Clear interface and usage patterns
-**Clean** - Removed legacy code and hardcoded cases
## Files Modified
- `scripts/hooks/` (new directory with 5 files)
- `scripts/manage-modules.sh` (refactored hook runner)
- `config/modules.json` (updated all 28 hook definitions)
## Testing Ready
The system is ready for testing with the modules container to ensure all Lua scripts are properly copied to `/azerothcore/lua_scripts` during module installation.

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# Helper utilities for dynamically including docker compose override files
# based on FEATURE_NAME_ENABLED style environment flags.
compose_overrides::trim() {
local value="$1"
# shellcheck disable=SC2001
value="$(echo "$value" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
printf '%s' "$value"
}
compose_overrides::derive_flag_from_name() {
local file="$1"
local base
base="$(basename "$file")"
base="${base%.*}"
base="${base//[^[:alnum:]]/_}"
base="${base^^}"
printf 'COMPOSE_OVERRIDE_%s_ENABLED' "$base"
}
compose_overrides::extract_tag() {
local file="$1" tag="$2"
local line
line="$(grep -m1 "^# *${tag}:" "$file" 2>/dev/null || true)"
if [ -z "$line" ]; then
return 1
fi
line="${line#*:}"
compose_overrides::trim "$line"
}
compose_overrides::extract_all_tags() {
local file="$1" tag="$2"
grep "^# *${tag}:" "$file" 2>/dev/null | cut -d':' -f2- | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
compose_overrides::read_env_value() {
local env_path="$1" key="$2" default="${3:-}"
local value=""
if [ -f "$env_path" ]; then
value="$(grep -E "^${key}=" "$env_path" | tail -n1 | cut -d'=' -f2- | tr -d '\r')"
fi
if [ -z "$value" ]; then
value="$default"
fi
printf '%s' "$value"
}
compose_overrides::list_enabled_files() {
local root_dir="$1" env_path="$2" result_var="$3"
local overrides_dir="${root_dir}/compose-overrides"
local -n __result="$result_var"
__result=()
[ -d "$overrides_dir" ] || return 0
local -a override_files=()
while IFS= read -r -d '' file; do
override_files+=("$file")
done < <(find "$overrides_dir" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) -print0 | sort -z)
local file flag flag_value legacy_default legacy_flags legacy_flag
for file in "${override_files[@]}"; do
flag="$(compose_overrides::extract_tag "$file" "override-flag" || true)"
if [ -z "$flag" ]; then
flag="$(compose_overrides::derive_flag_from_name "$file")"
fi
legacy_default="0"
legacy_flags="$(compose_overrides::extract_all_tags "$file" "legacy-flag" || true)"
if [ -n "$legacy_flags" ]; then
while IFS= read -r legacy_flag; do
[ -z "$legacy_flag" ] && continue
legacy_default="$(compose_overrides::read_env_value "$env_path" "$legacy_flag" "$legacy_default")"
# Stop at first legacy flag that yields a value
if [ -n "$legacy_default" ]; then
break
fi
done <<< "$legacy_flags"
fi
flag_value="$(compose_overrides::read_env_value "$env_path" "$flag" "$legacy_default")"
if [ "$flag_value" = "1" ]; then
__result+=("$file")
fi
done
}
compose_overrides::build_compose_args() {
local root_dir="$1" env_path="$2" default_compose="$3" result_var="$4"
local -n __result="$result_var"
__result=(-f "$default_compose")
local -a enabled_files=()
compose_overrides::list_enabled_files "$root_dir" "$env_path" enabled_files
for file in "${enabled_files[@]}"; do
__result+=(-f "$file")
done
}

View File

@@ -130,7 +130,7 @@ ensure_module_metadata(){
fi
done
local manifest_path="${MANIFEST_PATH:-${MODULES_MANIFEST_PATH:-/tmp/config/modules.json}}"
local manifest_path="${MANIFEST_PATH:-${MODULES_MANIFEST_PATH:-/tmp/config/module-manifest.json}}"
local env_path="${ENV_PATH:-${MODULES_ENV_PATH:-/tmp/.env}}"
local state_env_candidate="${STATE_DIR:-${MODULES_ROOT:-/modules}}/modules.env"
if [ -f "$state_env_candidate" ]; then

View File

@@ -55,22 +55,22 @@ resolve_manifest_path(){
return
fi
local candidate
candidate="$PROJECT_ROOT/config/modules.json"
candidate="$PROJECT_ROOT/config/module-manifest.json"
if [ -f "$candidate" ]; then
echo "$candidate"
return
fi
candidate="$SCRIPT_DIR/../config/modules.json"
candidate="$SCRIPT_DIR/../config/module-manifest.json"
if [ -f "$candidate" ]; then
echo "$candidate"
return
fi
candidate="/tmp/config/modules.json"
candidate="/tmp/config/module-manifest.json"
if [ -f "$candidate" ]; then
echo "$candidate"
return
fi
err "Unable to locate module manifest (set MODULES_MANIFEST_PATH or ensure config/modules.json exists)"
err "Unable to locate module manifest (set MODULES_MANIFEST_PATH or ensure config/module-manifest.json exists)"
}
setup_git_config(){

View File

@@ -2,7 +2,7 @@
"""
Module manifest helper.
Reads config/modules.json and .env to produce canonical module state that
Reads config/module-manifest.json and .env to produce canonical module state that
downstream shell scripts can consume for staging, rebuild detection, and
dependency validation.
"""
@@ -466,8 +466,8 @@ def configure_parser() -> argparse.ArgumentParser:
)
parser.add_argument(
"--manifest",
default="config/modules.json",
help="Path to module manifest (default: config/modules.json)",
default="config/module-manifest.json",
help="Path to module manifest (default: config/module-manifest.json)",
)
subparsers = parser.add_subparsers(dest="command", required=True)

View File

@@ -179,7 +179,7 @@ ensure_module_state(){
local storage_root
storage_root="$(resolve_local_storage_path)"
MODULE_STATE_DIR="${storage_root}/modules"
if ! python3 "$MODULE_HELPER" --env-path "$ENV_FILE" --manifest "$PROJECT_DIR/config/modules.json" generate --output-dir "$MODULE_STATE_DIR"; then
if ! python3 "$MODULE_HELPER" --env-path "$ENV_FILE" --manifest "$PROJECT_DIR/config/module-manifest.json" generate --output-dir "$MODULE_STATE_DIR"; then
echo "❌ Module manifest validation failed. See details above."
exit 1
fi
@@ -463,7 +463,7 @@ remove_sentinel(){
fi
if command -v docker >/dev/null 2>&1; then
local db_image
db_image="$(read_env AC_DB_IMPORT_IMAGE "acore/ac-wotlk-db-import:14.0.0-dev")"
db_image="$(read_env AC_DB_IMPORT_IMAGE "acore/ac-wotlk-db-import:master")"
if docker image inspect "$db_image" >/dev/null 2>&1; then
local mount_dir
mount_dir="$(dirname "$sentinel_path")"

View File

@@ -65,7 +65,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
ENV_FILE="$PROJECT_DIR/.env"
DEFAULT_COMPOSE_FILE="$PROJECT_DIR/docker-compose.yml"
EXTRA_COMPOSE_FILE="$PROJECT_DIR/docker-compose.mysql-expose.yml"
source "$PROJECT_DIR/scripts/lib/compose_overrides.sh"
usage(){
cat <<EOF
@@ -116,12 +116,10 @@ resolve_project_name(){
if [ -z "${COMPOSE_FILE:-}" ]; then
compose_files=("$DEFAULT_COMPOSE_FILE")
if [ "$(read_env MYSQL_EXPOSE_PORT "0")" = "1" ]; then
if [ -f "$EXTRA_COMPOSE_FILE" ]; then
compose_files+=("$EXTRA_COMPOSE_FILE")
else
echo "⚠️ MYSQL_EXPOSE_PORT=1 but ${EXTRA_COMPOSE_FILE} not found; continuing without port exposure override."
fi
declare -a enabled_overrides=()
compose_overrides::list_enabled_files "$PROJECT_DIR" "$ENV_FILE" enabled_overrides
if [ "${#enabled_overrides[@]}" -gt 0 ]; then
compose_files+=("${enabled_overrides[@]}")
fi
COMPOSE_FILE="$(IFS=:; echo "${compose_files[*]}")"
export COMPOSE_FILE

View File

@@ -15,7 +15,7 @@ from pathlib import Path
root = Path(sys.argv[1])
modules_py = root / "scripts" / "modules.py"
env_path = root / ".env"
manifest_path = root / "config" / "modules.json"
manifest_path = root / "config" / "module-manifest.json"
state = json.loads(subprocess.check_output([
sys.executable,

View File

@@ -13,6 +13,7 @@ err(){ echo -e "${RED}❌ $*${NC}"; }
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPOSE_FILE="$PROJECT_DIR/docker-compose.yml"
ENV_FILE=""
source "$PROJECT_DIR/scripts/lib/compose_overrides.sh"
PROFILES=(db services-standard client-data modules tools)
SKIP_DEPLOY=false
QUICK=false
@@ -73,15 +74,13 @@ run_compose(){
compose_args+=(--env-file "$ENV_FILE")
fi
compose_args+=(-f "$COMPOSE_FILE")
if [ "$(read_env_value MYSQL_EXPOSE_PORT "0")" = "1" ]; then
local extra_file
extra_file="$(dirname "$COMPOSE_FILE")/docker-compose.mysql-expose.yml"
if [ -f "$extra_file" ]; then
compose_args+=(-f "$extra_file")
else
warn "MYSQL_EXPOSE_PORT=1 but ${extra_file} missing; skipping port exposure override."
fi
fi
local env_path
env_path="$(env_file_path)"
declare -a enabled_overrides=()
compose_overrides::list_enabled_files "$PROJECT_DIR" "$env_path" enabled_overrides
for file in "${enabled_overrides[@]}"; do
compose_args+=(-f "$file")
done
docker compose "${compose_args[@]}" "$@"
}

View File

@@ -68,11 +68,12 @@ declare -A TEMPLATE_VALUE_MAP=(
[DEFAULT_AUTH_PORT]=AUTH_EXTERNAL_PORT
[DEFAULT_SOAP_PORT]=SOAP_EXTERNAL_PORT
[DEFAULT_MYSQL_PORT]=MYSQL_EXTERNAL_PORT
[DEFAULT_MYSQL_EXPOSE_PORT]=MYSQL_EXPOSE_PORT
[DEFAULT_PLAYERBOT_MIN]=PLAYERBOT_MIN_BOTS
[DEFAULT_PLAYERBOT_MAX]=PLAYERBOT_MAX_BOTS
[DEFAULT_LOCAL_STORAGE]=STORAGE_PATH
[DEFAULT_BACKUP_PATH]=BACKUP_PATH
[DEFAULT_COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED]=COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED
[DEFAULT_COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED]=COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED
[PERMISSION_LOCAL_USER]=DEFAULT_PERMISSION_LOCAL_USER
[PERMISSION_NFS_USER]=DEFAULT_PERMISSION_NFS_USER
[DEFAULT_CUSTOM_UID]=DEFAULT_CUSTOM_UID
@@ -351,7 +352,7 @@ EOF
# Module metadata / defaults
# ==============================
MODULE_MANIFEST_PATH="$SCRIPT_DIR/config/modules.json"
MODULE_MANIFEST_PATH="$SCRIPT_DIR/config/module-manifest.json"
MODULE_MANIFEST_HELPER="$SCRIPT_DIR/scripts/setup_manifest.py"
MODULE_PROFILES_HELPER="$SCRIPT_DIR/scripts/setup_profiles.py"
ENV_TEMPLATE_FILE="$SCRIPT_DIR/.env.template"
@@ -576,7 +577,7 @@ Options:
--backup-retention-hours N Hourly backup retention (default 6)
--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 profiles/<NAME>.json
--module-config NAME Use preset NAME from config/module-profiles/<NAME>.json
--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
@@ -935,7 +936,7 @@ fi
declare -A MODULE_PRESET_LABELS=()
declare -A MODULE_PRESET_DESCRIPTIONS=()
declare -A MODULE_PRESET_ORDER=()
local CONFIG_DIR="$SCRIPT_DIR/profiles"
local CONFIG_DIR="$SCRIPT_DIR/config/module-profiles"
if [ ! -x "$MODULE_PROFILES_HELPER" ]; then
say ERROR "Profile helper not found or not executable at $MODULE_PROFILES_HELPER"
exit 1
@@ -953,7 +954,7 @@ fi
local missing_presets=0
for required_preset in "$DEFAULT_PRESET_SUGGESTED" "$DEFAULT_PRESET_PLAYERBOTS"; do
if [ -z "${MODULE_PRESET_CONFIGS[$required_preset]:-}" ]; then
say ERROR "Missing module preset profiles/${required_preset}.json"
say ERROR "Missing module preset config/module-profiles/${required_preset}.json"
missing_presets=1
fi
done
@@ -1036,7 +1037,7 @@ fi
else
pretty_name=$(echo "$preset_name" | tr '_-' ' ' | awk '{for(i=1;i<=NF;i++){$i=toupper(substr($i,1,1)) substr($i,2)}}1')
fi
echo "${menu_index}) ${pretty_name} (profiles/${preset_name}.json)"
echo "${menu_index}) ${pretty_name} (config/module-profiles/${preset_name}.json)"
MENU_PRESET_INDEX[$menu_index]="$preset_name"
menu_index=$((menu_index + 1))
done
@@ -1454,7 +1455,8 @@ fi
HOST_ZONEINFO_PATH=${HOST_ZONEINFO_PATH:-$DEFAULT_HOST_ZONEINFO_PATH}
MYSQL_INNODB_REDO_LOG_CAPACITY=${MYSQL_INNODB_REDO_LOG_CAPACITY:-$DEFAULT_MYSQL_INNODB_REDO_LOG_CAPACITY}
MYSQL_RUNTIME_TMPFS_SIZE=${MYSQL_RUNTIME_TMPFS_SIZE:-$DEFAULT_MYSQL_RUNTIME_TMPFS_SIZE}
MYSQL_EXPOSE_PORT=${MYSQL_EXPOSE_PORT:-$DEFAULT_MYSQL_EXPOSE_PORT}
COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED=${COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED:-$DEFAULT_COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED}
COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED=${COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED:-$DEFAULT_COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED}
MYSQL_DISABLE_BINLOG=${MYSQL_DISABLE_BINLOG:-$DEFAULT_MYSQL_DISABLE_BINLOG}
MYSQL_CONFIG_DIR=${MYSQL_CONFIG_DIR:-$DEFAULT_MYSQL_CONFIG_DIR}
CLIENT_DATA_PATH=${CLIENT_DATA_PATH:-$DEFAULT_CLIENT_DATA_PATH}
@@ -1502,6 +1504,12 @@ fi
cat <<EOF
# Generated by ac-compose/setup.sh
# Compose overrides (set to 1 to include matching file under compose-overrides/)
# mysql-expose.yml -> exposes MySQL externally via COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED
# worldserver-debug-logging.yml -> raises log verbosity via COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED
COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED=$COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED
COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED=$COMPOSE_OVERRIDE_WORLDSERVER_DEBUG_LOGGING_ENABLED
COMPOSE_PROJECT_NAME=$DEFAULT_COMPOSE_PROJECT_NAME
STORAGE_PATH=$STORAGE_PATH
@@ -1514,9 +1522,8 @@ MYSQL_IMAGE=$DEFAULT_MYSQL_IMAGE
MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD
MYSQL_ROOT_HOST=$DEFAULT_MYSQL_ROOT_HOST
MYSQL_USER=$DEFAULT_MYSQL_USER
MYSQL_PORT=$DEFAULT_MYSQL_INTERNAL_PORT
MYSQL_EXTERNAL_PORT=$MYSQL_EXTERNAL_PORT
MYSQL_EXPOSE_PORT=${MYSQL_EXPOSE_PORT:-$DEFAULT_MYSQL_EXPOSE_PORT}
MYSQL_PORT=$DEFAULT_MYSQL_INTERNAL_PORT
MYSQL_EXTERNAL_PORT=$MYSQL_EXTERNAL_PORT
MYSQL_DISABLE_BINLOG=${MYSQL_DISABLE_BINLOG:-$DEFAULT_MYSQL_DISABLE_BINLOG}
MYSQL_CONFIG_DIR=${MYSQL_CONFIG_DIR:-$DEFAULT_MYSQL_CONFIG_DIR}
MYSQL_CHARACTER_SET=$DEFAULT_MYSQL_CHARACTER_SET

View File

@@ -53,7 +53,7 @@ AUTH_PORT="$(read_env AUTH_EXTERNAL_PORT)"
WORLD_PORT="$(read_env WORLD_EXTERNAL_PORT)"
SOAP_PORT="$(read_env SOAP_EXTERNAL_PORT)"
MYSQL_PORT="$(read_env MYSQL_EXTERNAL_PORT)"
MYSQL_EXPOSE_PORT="$(read_env MYSQL_EXPOSE_PORT)"
MYSQL_EXPOSE_OVERRIDE="$(read_env COMPOSE_OVERRIDE_MYSQL_EXPOSE_ENABLED "$(read_env MYSQL_EXPOSE_PORT "0")")"
PMA_PORT="$(read_env PMA_EXTERNAL_PORT)"
KEIRA_PORT="$(read_env KEIRA3_EXTERNAL_PORT)"
ELUNA_ENABLED="$(read_env AC_ELUNA_ENABLED)"
@@ -255,7 +255,7 @@ ports_summary(){
for i in "${!names[@]}"; do
local svc="${names[$i]}"
local port="${ports[$i]}"
if [ "$svc" = "MySQL" ] && [ "${MYSQL_EXPOSE_PORT}" != "1" ]; then
if [ "$svc" = "MySQL" ] && [ "${MYSQL_EXPOSE_OVERRIDE}" != "1" ]; then
printf " %-10s %-6s %b○%b not exposed\n" "$svc" "--" "$CYAN" "$NC"
continue
fi