mirror of
https://github.com/uprightbass360/AzerothCore-RealmMaster.git
synced 2026-01-13 00:58:34 +00:00
adding automatic module inclusion features and bugfixes
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,7 +12,10 @@ local-storage/
|
|||||||
images/
|
images/
|
||||||
node_modules/
|
node_modules/
|
||||||
.mcp*/
|
.mcp*/
|
||||||
|
scripts/__pycache__/
|
||||||
.env
|
.env
|
||||||
package-lock.json
|
package-lock.json
|
||||||
package.json
|
package.json
|
||||||
.modules_state
|
.modules_state
|
||||||
|
.modules-meta
|
||||||
|
todo.md
|
||||||
@@ -548,6 +548,11 @@ Initializes or updates AzerothCore source repositories for compilation.
|
|||||||
#### `scripts/manage-modules.sh` - Module Management Container
|
#### `scripts/manage-modules.sh` - Module Management Container
|
||||||
Internal script that manages module lifecycle within the ac-modules container.
|
Internal script that manages module lifecycle within the ac-modules container.
|
||||||
|
|
||||||
|
#### `config/modules.json` & `scripts/modules.py`
|
||||||
|
- Declarative manifest describing every supported module (repo, type, hooks, dependencies).
|
||||||
|
- `scripts/modules.py` reads the manifest and `.env`, generating `modules.env`, rebuild metadata, and shell-ready module maps.
|
||||||
|
- Build and deploy scripts source `modules.env`, while `manage-modules.sh` consumes the manifest at runtime—no more duplicated module lists.
|
||||||
|
|
||||||
#### `scripts/manage-modules-sql.sh` - Module Database Integration
|
#### `scripts/manage-modules-sql.sh` - Module Database Integration
|
||||||
Executes module-specific SQL scripts for database schema updates.
|
Executes module-specific SQL scripts for database schema updates.
|
||||||
|
|
||||||
|
|||||||
126
build.sh
126
build.sh
@@ -67,6 +67,7 @@ require_cmd(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
require_cmd docker
|
require_cmd docker
|
||||||
|
require_cmd python3
|
||||||
|
|
||||||
read_env(){
|
read_env(){
|
||||||
local key="$1" default="${2:-}"
|
local key="$1" default="${2:-}"
|
||||||
@@ -80,28 +81,47 @@ read_env(){
|
|||||||
echo "$value"
|
echo "$value"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Module detection logic (extracted from deploy.sh)
|
MODULE_HELPER="$ROOT_DIR/scripts/modules.py"
|
||||||
COMPILE_MODULE_VARS=(
|
MODULE_STATE_INITIALIZED=0
|
||||||
MODULE_AOE_LOOT MODULE_LEARN_SPELLS MODULE_FIREWORKS MODULE_INDIVIDUAL_PROGRESSION MODULE_AHBOT MODULE_AUTOBALANCE
|
declare -a MODULES_COMPILE_LIST=()
|
||||||
MODULE_TRANSMOG MODULE_NPC_BUFFER MODULE_DYNAMIC_XP MODULE_SOLO_LFG MODULE_1V1_ARENA MODULE_PHASED_DUELS
|
|
||||||
MODULE_BREAKING_NEWS MODULE_BOSS_ANNOUNCER MODULE_ACCOUNT_ACHIEVEMENTS MODULE_AUTO_REVIVE MODULE_GAIN_HONOR_GUARD
|
resolve_local_storage_path(){
|
||||||
MODULE_ELUNA MODULE_TIME_IS_TIME MODULE_POCKET_PORTAL MODULE_RANDOM_ENCHANTS MODULE_SOLOCRAFT MODULE_PVP_TITLES MODULE_NPC_BEASTMASTER
|
local local_root
|
||||||
MODULE_NPC_ENCHANTER MODULE_INSTANCE_RESET MODULE_LEVEL_GRANT MODULE_ARAC MODULE_ASSISTANT MODULE_REAGENT_BANK
|
local_root="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
||||||
MODULE_BLACK_MARKET_AUCTION_HOUSE MODULE_CHALLENGE_MODES MODULE_OLLAMA_CHAT MODULE_PLAYER_BOT_LEVEL_BRACKETS MODULE_STATBOOSTER MODULE_DUNGEON_RESPAWN
|
if [[ "$local_root" != /* ]]; then
|
||||||
MODULE_SKELETON_MODULE MODULE_BG_SLAVERYVALLEY MODULE_AZEROTHSHARD MODULE_WORGOBLIN MODULE_ELUNA_TS
|
local_root="${local_root#./}"
|
||||||
)
|
local_root="$ROOT_DIR/$local_root"
|
||||||
|
fi
|
||||||
|
echo "${local_root%/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_module_state(){
|
||||||
|
local storage_root
|
||||||
|
storage_root="$(resolve_local_storage_path)"
|
||||||
|
local output_dir="${storage_root}/modules"
|
||||||
|
if ! python3 "$MODULE_HELPER" --env-path "$ENV_PATH" --manifest "$ROOT_DIR/config/modules.json" generate --output-dir "$output_dir"; then
|
||||||
|
err "Module manifest validation failed. See errors above."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "${output_dir}/modules.env" ]; then
|
||||||
|
err "modules.env not produced by helper at ${output_dir}/modules.env"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "${output_dir}/modules.env"
|
||||||
|
MODULE_STATE_INITIALIZED=1
|
||||||
|
MODULES_COMPILE_LIST=()
|
||||||
|
IFS=' ' read -r -a MODULES_COMPILE_LIST <<< "${MODULES_COMPILE:-}"
|
||||||
|
if [ "${#MODULES_COMPILE_LIST[@]}" -eq 1 ] && [ -z "${MODULES_COMPILE_LIST[0]}" ]; then
|
||||||
|
MODULES_COMPILE_LIST=()
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
requires_playerbot_source(){
|
requires_playerbot_source(){
|
||||||
if [ "$(read_env MODULE_PLAYERBOTS "0")" = "1" ]; then
|
if [ "$MODULE_STATE_INITIALIZED" -ne 1 ]; then
|
||||||
return 0
|
generate_module_state
|
||||||
fi
|
fi
|
||||||
local var
|
[ "${MODULES_REQUIRES_PLAYERBOT_SOURCE:-0}" = "1" ]
|
||||||
for var in "${COMPILE_MODULE_VARS[@]}"; do
|
|
||||||
if [ "$(read_env "$var" "0")" = "1" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_source_repo(){
|
ensure_source_repo(){
|
||||||
@@ -197,14 +217,14 @@ detect_rebuild_reasons(){
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if any C++ modules are enabled but modules-latest images don't exist
|
# Check if any C++ modules are enabled but modules-latest images don't exist
|
||||||
local any_cxx_modules=0
|
if [ "$MODULE_STATE_INITIALIZED" -ne 1 ]; then
|
||||||
local var
|
generate_module_state
|
||||||
for var in "${COMPILE_MODULE_VARS[@]}"; do
|
fi
|
||||||
if [ "$(read_env "$var" "0")" = "1" ]; then
|
|
||||||
any_cxx_modules=1
|
local any_cxx_modules=0
|
||||||
break
|
if [ "${#MODULES_COMPILE_LIST[@]}" -gt 0 ]; then
|
||||||
|
any_cxx_modules=1
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$any_cxx_modules" = "1" ]; then
|
if [ "$any_cxx_modules" = "1" ]; then
|
||||||
local authserver_modules_image
|
local authserver_modules_image
|
||||||
@@ -296,11 +316,7 @@ confirm_build(){
|
|||||||
# Module staging logic (extracted from setup.sh)
|
# Module staging logic (extracted from setup.sh)
|
||||||
sync_modules(){
|
sync_modules(){
|
||||||
local storage_path
|
local storage_path
|
||||||
storage_path="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
storage_path="$(resolve_local_storage_path)"
|
||||||
if [[ "$storage_path" != /* ]]; then
|
|
||||||
storage_path="${storage_path#./}"
|
|
||||||
storage_path="$ROOT_DIR/$storage_path"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$storage_path/modules"
|
mkdir -p "$storage_path/modules"
|
||||||
info "Using local module staging at $storage_path/modules"
|
info "Using local module staging at $storage_path/modules"
|
||||||
@@ -323,10 +339,10 @@ resolve_project_name(){
|
|||||||
stage_modules(){
|
stage_modules(){
|
||||||
local src_path="$1"
|
local src_path="$1"
|
||||||
local storage_path
|
local storage_path
|
||||||
storage_path="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
storage_path="$(resolve_local_storage_path)"
|
||||||
if [[ "$storage_path" != /* ]]; then
|
|
||||||
storage_path="${storage_path#./}"
|
if [ -z "${MODULES_ENABLED:-}" ]; then
|
||||||
storage_path="$ROOT_DIR/$storage_path"
|
generate_module_state
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Staging modules to source directory: $src_path/modules"
|
info "Staging modules to source directory: $src_path/modules"
|
||||||
@@ -340,28 +356,17 @@ stage_modules(){
|
|||||||
local local_modules_dir="${src_path}/modules"
|
local local_modules_dir="${src_path}/modules"
|
||||||
mkdir -p "$local_modules_dir"
|
mkdir -p "$local_modules_dir"
|
||||||
|
|
||||||
# Export module variables for the script
|
|
||||||
local module_vars=(
|
|
||||||
MODULE_PLAYERBOTS MODULE_AOE_LOOT MODULE_LEARN_SPELLS MODULE_FIREWORKS MODULE_INDIVIDUAL_PROGRESSION MODULE_AHBOT MODULE_AUTOBALANCE
|
|
||||||
MODULE_TRANSMOG MODULE_NPC_BUFFER MODULE_DYNAMIC_XP MODULE_SOLO_LFG MODULE_1V1_ARENA MODULE_PHASED_DUELS
|
|
||||||
MODULE_BREAKING_NEWS MODULE_BOSS_ANNOUNCER MODULE_ACCOUNT_ACHIEVEMENTS MODULE_AUTO_REVIVE MODULE_GAIN_HONOR_GUARD
|
|
||||||
MODULE_ELUNA MODULE_TIME_IS_TIME MODULE_POCKET_PORTAL MODULE_RANDOM_ENCHANTS MODULE_SOLOCRAFT MODULE_PVP_TITLES
|
|
||||||
MODULE_NPC_BEASTMASTER MODULE_NPC_ENCHANTER MODULE_INSTANCE_RESET MODULE_LEVEL_GRANT MODULE_ARAC MODULE_ASSISTANT
|
|
||||||
MODULE_REAGENT_BANK MODULE_BLACK_MARKET_AUCTION_HOUSE MODULE_CHALLENGE_MODES MODULE_OLLAMA_CHAT
|
|
||||||
MODULE_PLAYER_BOT_LEVEL_BRACKETS MODULE_STATBOOSTER MODULE_DUNGEON_RESPAWN MODULE_SKELETON_MODULE
|
|
||||||
MODULE_BG_SLAVERYVALLEY MODULE_AZEROTHSHARD MODULE_WORGOBLIN MODULE_ELUNA_TS
|
|
||||||
)
|
|
||||||
|
|
||||||
local module_export_var
|
|
||||||
for module_export_var in "${module_vars[@]}"; do
|
|
||||||
local module_value
|
|
||||||
module_value="$(read_env "$module_export_var" "0")"
|
|
||||||
export "${module_export_var}=${module_value:-0}"
|
|
||||||
done
|
|
||||||
|
|
||||||
local staging_modules_dir="${storage_path}/modules"
|
local staging_modules_dir="${storage_path}/modules"
|
||||||
export MODULES_HOST_DIR="$staging_modules_dir"
|
export MODULES_HOST_DIR="$staging_modules_dir"
|
||||||
|
|
||||||
|
local env_target_dir="$src_path/env/dist/etc"
|
||||||
|
mkdir -p "$env_target_dir"
|
||||||
|
export MODULES_ENV_TARGET_DIR="$env_target_dir"
|
||||||
|
|
||||||
|
local lua_target_dir="$src_path/lua_scripts"
|
||||||
|
mkdir -p "$lua_target_dir"
|
||||||
|
export MODULES_LUA_TARGET_DIR="$lua_target_dir"
|
||||||
|
|
||||||
# Set up local storage path for build sentinel tracking
|
# Set up local storage path for build sentinel tracking
|
||||||
local local_storage_path
|
local local_storage_path
|
||||||
local_storage_path="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
local_storage_path="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
||||||
@@ -388,6 +393,7 @@ stage_modules(){
|
|||||||
|
|
||||||
# Run module staging script in local modules directory
|
# Run module staging script in local modules directory
|
||||||
export MODULES_LOCAL_RUN=1
|
export MODULES_LOCAL_RUN=1
|
||||||
|
export MODULES_SKIP_SQL=1
|
||||||
if [ -n "$staging_modules_dir" ]; then
|
if [ -n "$staging_modules_dir" ]; then
|
||||||
mkdir -p "$staging_modules_dir"
|
mkdir -p "$staging_modules_dir"
|
||||||
rm -f "$staging_modules_dir/.modules_state" "$staging_modules_dir/.requires_rebuild" 2>/dev/null || true
|
rm -f "$staging_modules_dir/.modules_state" "$staging_modules_dir/.requires_rebuild" 2>/dev/null || true
|
||||||
@@ -404,11 +410,19 @@ stage_modules(){
|
|||||||
rsync -a --delete \
|
rsync -a --delete \
|
||||||
--exclude '.modules_state' \
|
--exclude '.modules_state' \
|
||||||
--exclude '.requires_rebuild' \
|
--exclude '.requires_rebuild' \
|
||||||
|
--exclude 'modules.env' \
|
||||||
|
--exclude 'modules-state.json' \
|
||||||
|
--exclude 'modules-compile.txt' \
|
||||||
|
--exclude 'modules-enabled.txt' \
|
||||||
"$local_modules_dir"/ "$staging_modules_dir"/
|
"$local_modules_dir"/ "$staging_modules_dir"/
|
||||||
else
|
else
|
||||||
find "$staging_modules_dir" -mindepth 1 -maxdepth 1 \
|
find "$staging_modules_dir" -mindepth 1 -maxdepth 1 \
|
||||||
! -name '.modules_state' \
|
! -name '.modules_state' \
|
||||||
! -name '.requires_rebuild' \
|
! -name '.requires_rebuild' \
|
||||||
|
! -name 'modules.env' \
|
||||||
|
! -name 'modules-state.json' \
|
||||||
|
! -name 'modules-compile.txt' \
|
||||||
|
! -name 'modules-enabled.txt' \
|
||||||
-exec rm -rf {} + 2>/dev/null || true
|
-exec rm -rf {} + 2>/dev/null || true
|
||||||
(cd "$local_modules_dir" && tar cf - --exclude='.modules_state' --exclude='.requires_rebuild' .) | (cd "$staging_modules_dir" && tar xf -)
|
(cd "$local_modules_dir" && tar cf - --exclude='.modules_state' --exclude='.requires_rebuild' .) | (cd "$staging_modules_dir" && tar xf -)
|
||||||
fi
|
fi
|
||||||
@@ -420,6 +434,7 @@ stage_modules(){
|
|||||||
# Cleanup
|
# Cleanup
|
||||||
export GIT_CONFIG_GLOBAL="$prev_git_config_global"
|
export GIT_CONFIG_GLOBAL="$prev_git_config_global"
|
||||||
unset MODULES_LOCAL_RUN
|
unset MODULES_LOCAL_RUN
|
||||||
|
unset MODULES_SKIP_SQL
|
||||||
unset MODULES_HOST_DIR
|
unset MODULES_HOST_DIR
|
||||||
[ -n "$git_temp_config" ] && [ -f "$git_temp_config" ] && rm -f "$git_temp_config"
|
[ -n "$git_temp_config" ] && [ -f "$git_temp_config" ] && rm -f "$git_temp_config"
|
||||||
}
|
}
|
||||||
@@ -492,6 +507,9 @@ main(){
|
|||||||
local src_dir
|
local src_dir
|
||||||
local rebuild_reasons
|
local rebuild_reasons
|
||||||
|
|
||||||
|
info "Preparing module manifest metadata"
|
||||||
|
generate_module_state
|
||||||
|
|
||||||
info "Step 1/6: Setting up source repository"
|
info "Step 1/6: Setting up source repository"
|
||||||
src_dir="$(ensure_source_repo)"
|
src_dir="$(ensure_source_repo)"
|
||||||
|
|
||||||
|
|||||||
497
config/modules.json
Normal file
497
config/modules.json
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
{
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"key": "MODULE_PLAYERBOTS",
|
||||||
|
"name": "mod-playerbots",
|
||||||
|
"repo": "https://github.com/mod-playerbots/mod-playerbots.git",
|
||||||
|
"needs_build": false,
|
||||||
|
"type": "data",
|
||||||
|
"notes": "Installs SQL/config assets; core functionality is built into playerbot images",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"playerbots.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_AOE_LOOT",
|
||||||
|
"name": "mod-aoe-loot",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-aoe-loot.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"mod_aoe_loot.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_LEARN_SPELLS",
|
||||||
|
"name": "mod-learn-spells",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-learn-spells.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"mod_learnspells.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_FIREWORKS",
|
||||||
|
"name": "mod-fireworks-on-level",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-fireworks-on-level.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"mod_fireworks.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_INDIVIDUAL_PROGRESSION",
|
||||||
|
"name": "mod-individual-progression",
|
||||||
|
"repo": "https://github.com/ZhengPeiRu21/mod-individual-progression.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"individual_progression.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_AHBOT",
|
||||||
|
"name": "mod-ahbot",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-ahbot.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"mod_ahbot.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_AUTOBALANCE",
|
||||||
|
"name": "mod-autobalance",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-autobalance.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"AutoBalance.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_TRANSMOG",
|
||||||
|
"name": "mod-transmog",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-transmog.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"transmog.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_NPC_BUFFER",
|
||||||
|
"name": "mod-npc-buffer",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-npc-buffer.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"npc_buffer.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_DYNAMIC_XP",
|
||||||
|
"name": "mod-dynamic-xp",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-dynamic-xp.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"Individual-XP.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_SOLO_LFG",
|
||||||
|
"name": "mod-solo-lfg",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-solo-lfg.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"SoloLfg.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_1V1_ARENA",
|
||||||
|
"name": "mod-1v1-arena",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-1v1-arena.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"1v1arena.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_PHASED_DUELS",
|
||||||
|
"name": "mod-phased-duels",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-phased-duels.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"phasedduels.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_BREAKING_NEWS",
|
||||||
|
"name": "mod-breaking-news-override",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-breaking-news-override.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"breaking_news.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_BOSS_ANNOUNCER",
|
||||||
|
"name": "mod-boss-announcer",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-boss-announcer.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"boss_announcer.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_ACCOUNT_ACHIEVEMENTS",
|
||||||
|
"name": "mod-account-achievements",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-account-achievements.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"account_achievements.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_AUTO_REVIVE",
|
||||||
|
"name": "mod-auto-revive",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-auto-revive.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"AutoRevive.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_GAIN_HONOR_GUARD",
|
||||||
|
"name": "mod-gain-honor-guard",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-gain-honor-guard.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"GainHonorGuard.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_ELUNA",
|
||||||
|
"name": "mod-ale",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-ale.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [
|
||||||
|
"mod_ale_move_path_patch"
|
||||||
|
],
|
||||||
|
"config_cleanup": [
|
||||||
|
"mod_eluna.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_TIME_IS_TIME",
|
||||||
|
"name": "mod-TimeIsTime",
|
||||||
|
"repo": "https://github.com/dunjeon/mod-TimeIsTime.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"mod-time_is_time.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_POCKET_PORTAL",
|
||||||
|
"name": "mod-pocket-portal",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-pocket-portal.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"status": "blocked",
|
||||||
|
"block_reason": "Requires C++20 std::format support patch before enabling",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"pocketportal.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_RANDOM_ENCHANTS",
|
||||||
|
"name": "mod-random-enchants",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-random-enchants.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"RandomEnchants.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_SOLOCRAFT",
|
||||||
|
"name": "mod-solocraft",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-solocraft.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"Solocraft.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_PVP_TITLES",
|
||||||
|
"name": "mod-pvp-titles",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-pvp-titles.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": [
|
||||||
|
"MODULE_ELUNA"
|
||||||
|
],
|
||||||
|
"config_cleanup": [
|
||||||
|
"mod_pvptitles.conf*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_NPC_BEASTMASTER",
|
||||||
|
"name": "mod-npc-beastmaster",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-npc-beastmaster.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"npc_beastmaster.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_NPC_ENCHANTER",
|
||||||
|
"name": "mod-npc-enchanter",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-npc-enchanter.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"npc_enchanter.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_INSTANCE_RESET",
|
||||||
|
"name": "mod-instance-reset",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-instance-reset.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"instance-reset.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_LEVEL_GRANT",
|
||||||
|
"name": "mod-quest-count-level",
|
||||||
|
"repo": "https://github.com/michaeldelago/mod-quest-count-level.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"levelGrant.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_ARAC",
|
||||||
|
"name": "mod-arac",
|
||||||
|
"repo": "https://github.com/heyitsbench/mod-arac.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"config_cleanup": [
|
||||||
|
"arac.conf*"
|
||||||
|
],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_ASSISTANT",
|
||||||
|
"name": "mod-assistant",
|
||||||
|
"repo": "https://github.com/noisiver/mod-assistant.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_REAGENT_BANK",
|
||||||
|
"name": "mod-reagent-bank",
|
||||||
|
"repo": "https://github.com/ZhengPeiRu21/mod-reagent-bank.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_BLACK_MARKET_AUCTION_HOUSE",
|
||||||
|
"name": "mod-black-market",
|
||||||
|
"repo": "https://github.com/Youpeoples/Black-Market-Auction-House.git",
|
||||||
|
"needs_build": false,
|
||||||
|
"type": "lua",
|
||||||
|
"requires": [
|
||||||
|
"MODULE_ELUNA"
|
||||||
|
],
|
||||||
|
"post_install_hooks": [
|
||||||
|
"black_market_copy_lua"
|
||||||
|
],
|
||||||
|
"depends_on": [
|
||||||
|
"MODULE_ELUNA"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_CHALLENGE_MODES",
|
||||||
|
"name": "mod-challenge-modes",
|
||||||
|
"repo": "https://github.com/ZhengPeiRu21/mod-challenge-modes.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_OLLAMA_CHAT",
|
||||||
|
"name": "mod-ollama-chat",
|
||||||
|
"repo": "https://github.com/DustinHendrickson/mod-ollama-chat.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_PLAYER_BOT_LEVEL_BRACKETS",
|
||||||
|
"name": "mod-player-bot-level-brackets",
|
||||||
|
"repo": "https://github.com/DustinHendrickson/mod-player-bot-level-brackets.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"requires": [
|
||||||
|
"MODULE_PLAYERBOTS"
|
||||||
|
],
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_STATBOOSTER",
|
||||||
|
"name": "StatBooster",
|
||||||
|
"repo": "https://github.com/AnchyDev/StatBooster.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_DUNGEON_RESPAWN",
|
||||||
|
"name": "DungeonRespawn",
|
||||||
|
"repo": "https://github.com/AnchyDev/DungeonRespawn.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"status": "blocked",
|
||||||
|
"block_reason": "Upstream override signature mismatch (OnBeforeTeleport); awaiting fix",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_SKELETON_MODULE",
|
||||||
|
"name": "skeleton-module",
|
||||||
|
"repo": "https://github.com/azerothcore/skeleton-module.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_BG_SLAVERYVALLEY",
|
||||||
|
"name": "mod-bg-slaveryvalley",
|
||||||
|
"repo": "https://github.com/Helias/mod-bg-slaveryvalley.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"depends_on": [
|
||||||
|
"MODULE_ELUNA"
|
||||||
|
],
|
||||||
|
"post_install_hooks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_AZEROTHSHARD",
|
||||||
|
"name": "mod-azerothshard",
|
||||||
|
"repo": "https://github.com/azerothcore/mod-azerothshard.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_WORGOBLIN",
|
||||||
|
"name": "mod-worgoblin",
|
||||||
|
"repo": "https://github.com/heyitsbench/mod-worgoblin.git",
|
||||||
|
"needs_build": true,
|
||||||
|
"type": "cpp",
|
||||||
|
"depends_on": [
|
||||||
|
"MODULE_ELUNA"
|
||||||
|
],
|
||||||
|
"post_install_hooks": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MODULE_ELUNA_TS",
|
||||||
|
"name": "eluna-ts",
|
||||||
|
"repo": "https://github.com/azerothcore/eluna-ts.git",
|
||||||
|
"needs_build": false,
|
||||||
|
"type": "tool",
|
||||||
|
"requires": [
|
||||||
|
"MODULE_ELUNA"
|
||||||
|
],
|
||||||
|
"post_install_hooks": [],
|
||||||
|
"depends_on": [
|
||||||
|
"MODULE_ELUNA"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
64
deploy.sh
64
deploy.sh
@@ -26,15 +26,9 @@ REMOTE_PROJECT_DIR=""
|
|||||||
REMOTE_SKIP_STORAGE=0
|
REMOTE_SKIP_STORAGE=0
|
||||||
REMOTE_ARGS_PROVIDED=0
|
REMOTE_ARGS_PROVIDED=0
|
||||||
|
|
||||||
COMPILE_MODULE_VARS=(
|
MODULE_HELPER="$ROOT_DIR/scripts/modules.py"
|
||||||
MODULE_AOE_LOOT MODULE_LEARN_SPELLS MODULE_FIREWORKS MODULE_INDIVIDUAL_PROGRESSION MODULE_AHBOT MODULE_AUTOBALANCE
|
MODULE_STATE_INITIALIZED=0
|
||||||
MODULE_TRANSMOG MODULE_NPC_BUFFER MODULE_DYNAMIC_XP MODULE_SOLO_LFG MODULE_1V1_ARENA MODULE_PHASED_DUELS
|
declare -a MODULES_COMPILE_LIST=()
|
||||||
MODULE_BREAKING_NEWS MODULE_BOSS_ANNOUNCER MODULE_ACCOUNT_ACHIEVEMENTS MODULE_AUTO_REVIVE MODULE_GAIN_HONOR_GUARD
|
|
||||||
MODULE_TIME_IS_TIME MODULE_POCKET_PORTAL MODULE_RANDOM_ENCHANTS MODULE_SOLOCRAFT MODULE_PVP_TITLES MODULE_NPC_BEASTMASTER
|
|
||||||
MODULE_NPC_ENCHANTER MODULE_INSTANCE_RESET MODULE_LEVEL_GRANT MODULE_ARAC MODULE_ASSISTANT MODULE_REAGENT_BANK
|
|
||||||
MODULE_CHALLENGE_MODES MODULE_OLLAMA_CHAT MODULE_PLAYER_BOT_LEVEL_BRACKETS MODULE_STATBOOSTER MODULE_DUNGEON_RESPAWN
|
|
||||||
MODULE_SKELETON_MODULE MODULE_BG_SLAVERYVALLEY MODULE_AZEROTHSHARD MODULE_WORGOBLIN
|
|
||||||
)
|
|
||||||
|
|
||||||
BLUE='\033[0;34m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
|
BLUE='\033[0;34m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
|
||||||
info(){ printf '%b\n' "${BLUE}ℹ️ $*${NC}"; }
|
info(){ printf '%b\n' "${BLUE}ℹ️ $*${NC}"; }
|
||||||
@@ -248,6 +242,7 @@ require_cmd(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
require_cmd docker
|
require_cmd docker
|
||||||
|
require_cmd python3
|
||||||
|
|
||||||
if [ "$REMOTE_MODE" -eq 1 ]; then
|
if [ "$REMOTE_MODE" -eq 1 ]; then
|
||||||
if [ -z "$REMOTE_HOST" ]; then
|
if [ -z "$REMOTE_HOST" ]; then
|
||||||
@@ -283,6 +278,43 @@ read_env(){
|
|||||||
echo "$value"
|
echo "$value"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve_local_storage_path(){
|
||||||
|
local path
|
||||||
|
path="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
||||||
|
if [[ "$path" != /* ]]; then
|
||||||
|
path="${path#./}"
|
||||||
|
path="$ROOT_DIR/$path"
|
||||||
|
fi
|
||||||
|
echo "${path%/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_module_state(){
|
||||||
|
if [ "$MODULE_STATE_INITIALIZED" -eq 1 ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local storage_root
|
||||||
|
storage_root="$(resolve_local_storage_path)"
|
||||||
|
local output_dir="${storage_root}/modules"
|
||||||
|
|
||||||
|
if ! python3 "$MODULE_HELPER" --env-path "$ENV_PATH" --manifest "$ROOT_DIR/config/modules.json" generate --output-dir "$output_dir"; then
|
||||||
|
err "Module manifest validation failed. See errors above."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$output_dir/modules.env" ]; then
|
||||||
|
err "modules.env not produced at $output_dir/modules.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$output_dir/modules.env"
|
||||||
|
MODULE_STATE_INITIALIZED=1
|
||||||
|
MODULES_COMPILE_LIST=()
|
||||||
|
IFS=' ' read -r -a MODULES_COMPILE_LIST <<< "${MODULES_COMPILE:-}"
|
||||||
|
if [ "${#MODULES_COMPILE_LIST[@]}" -eq 1 ] && [ -z "${MODULES_COMPILE_LIST[0]}" ]; then
|
||||||
|
MODULES_COMPILE_LIST=()
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
resolve_project_name(){
|
resolve_project_name(){
|
||||||
local raw_name="$(read_env COMPOSE_PROJECT_NAME "acore-compose")"
|
local raw_name="$(read_env COMPOSE_PROJECT_NAME "acore-compose")"
|
||||||
local sanitized
|
local sanitized
|
||||||
@@ -327,14 +359,12 @@ detect_build_needed(){
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if any C++ modules are enabled but modules-latest images don't exist
|
# Check if any C++ modules are enabled but modules-latest images don't exist
|
||||||
|
ensure_module_state
|
||||||
|
|
||||||
local any_cxx_modules=0
|
local any_cxx_modules=0
|
||||||
local var
|
if [ "${#MODULES_COMPILE_LIST[@]}" -gt 0 ]; then
|
||||||
for var in "${COMPILE_MODULE_VARS[@]}"; do
|
|
||||||
if [ "$(read_env "$var" "0")" = "1" ]; then
|
|
||||||
any_cxx_modules=1
|
any_cxx_modules=1
|
||||||
break
|
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$any_cxx_modules" = "1" ]; then
|
if [ "$any_cxx_modules" = "1" ]; then
|
||||||
local authserver_modules_image
|
local authserver_modules_image
|
||||||
@@ -473,13 +503,11 @@ determine_profile(){
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local var
|
ensure_module_state
|
||||||
for var in "${COMPILE_MODULE_VARS[@]}"; do
|
if [ "${#MODULES_COMPILE_LIST[@]}" -gt 0 ]; then
|
||||||
if [ "$(read_env "$var" "0")" = "1" ]; then
|
|
||||||
echo "modules"
|
echo "modules"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
echo "standard"
|
echo "standard"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -590,7 +590,9 @@ services:
|
|||||||
- ${STORAGE_PATH}/modules:/modules
|
- ${STORAGE_PATH}/modules:/modules
|
||||||
- ${STORAGE_PATH}/config:/azerothcore/env/dist/etc
|
- ${STORAGE_PATH}/config:/azerothcore/env/dist/etc
|
||||||
- ./scripts:/tmp/scripts:ro
|
- ./scripts:/tmp/scripts:ro
|
||||||
|
- ./config:/tmp/config:ro
|
||||||
environment:
|
environment:
|
||||||
|
- MODULES_MANIFEST_PATH=/tmp/config/modules.json
|
||||||
- MODULE_PLAYERBOTS=${MODULE_PLAYERBOTS}
|
- MODULE_PLAYERBOTS=${MODULE_PLAYERBOTS}
|
||||||
- MODULE_AOE_LOOT=${MODULE_AOE_LOOT}
|
- MODULE_AOE_LOOT=${MODULE_AOE_LOOT}
|
||||||
- MODULE_LEARN_SPELLS=${MODULE_LEARN_SPELLS}
|
- MODULE_LEARN_SPELLS=${MODULE_LEARN_SPELLS}
|
||||||
@@ -634,6 +636,8 @@ services:
|
|||||||
- MODULE_WORGOBLIN=${MODULE_WORGOBLIN}
|
- MODULE_WORGOBLIN=${MODULE_WORGOBLIN}
|
||||||
- MODULE_ELUNA_TS=${MODULE_ELUNA_TS}
|
- MODULE_ELUNA_TS=${MODULE_ELUNA_TS}
|
||||||
- CONTAINER_MYSQL=${CONTAINER_MYSQL}
|
- CONTAINER_MYSQL=${CONTAINER_MYSQL}
|
||||||
|
- MYSQL_PORT=${MYSQL_PORT}
|
||||||
|
- MYSQL_USER=${MYSQL_USER}
|
||||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||||
- DB_AUTH_NAME=${DB_AUTH_NAME}
|
- DB_AUTH_NAME=${DB_AUTH_NAME}
|
||||||
- DB_WORLD_NAME=${DB_WORLD_NAME}
|
- DB_WORLD_NAME=${DB_WORLD_NAME}
|
||||||
@@ -646,7 +650,7 @@ services:
|
|||||||
command:
|
command:
|
||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
apk add --no-cache curl bash git
|
apk add --no-cache curl bash git python3
|
||||||
(chmod +x /tmp/scripts/manage-modules.sh /tmp/scripts/manage-modules-sql.sh 2>/dev/null || true) && /tmp/scripts/manage-modules.sh
|
(chmod +x /tmp/scripts/manage-modules.sh /tmp/scripts/manage-modules-sql.sh 2>/dev/null || true) && /tmp/scripts/manage-modules.sh
|
||||||
# Fix permissions after module operations
|
# Fix permissions after module operations
|
||||||
chown -R ${CONTAINER_USER} /modules /azerothcore/env/dist/etc 2>/dev/null || true
|
chown -R ${CONTAINER_USER} /modules /azerothcore/env/dist/etc 2>/dev/null || true
|
||||||
|
|||||||
1085
modules-state.json
Normal file
1085
modules-state.json
Normal file
File diff suppressed because it is too large
Load Diff
51
modules.env
Normal file
51
modules.env
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Autogenerated by scripts/modules.py
|
||||||
|
# Generated at 2025-10-31T20:30:32.837900+00:00
|
||||||
|
export MODULES_MANIFEST="/home/upb/src/acore-compose/config/modules.json"
|
||||||
|
export MODULES_ENV_PATH="/home/upb/src/acore-compose/.env"
|
||||||
|
export MODULE_PLAYERBOTS=1
|
||||||
|
export MODULE_AOE_LOOT=1
|
||||||
|
export MODULE_LEARN_SPELLS=1
|
||||||
|
export MODULE_FIREWORKS=1
|
||||||
|
export MODULE_INDIVIDUAL_PROGRESSION=1
|
||||||
|
export MODULE_AHBOT=1
|
||||||
|
export MODULE_AUTOBALANCE=1
|
||||||
|
export MODULE_TRANSMOG=1
|
||||||
|
export MODULE_NPC_BUFFER=1
|
||||||
|
export MODULE_DYNAMIC_XP=1
|
||||||
|
export MODULE_SOLO_LFG=1
|
||||||
|
export MODULE_1V1_ARENA=1
|
||||||
|
export MODULE_PHASED_DUELS=1
|
||||||
|
export MODULE_BREAKING_NEWS=1
|
||||||
|
export MODULE_BOSS_ANNOUNCER=1
|
||||||
|
export MODULE_ACCOUNT_ACHIEVEMENTS=1
|
||||||
|
export MODULE_AUTO_REVIVE=1
|
||||||
|
export MODULE_GAIN_HONOR_GUARD=1
|
||||||
|
export MODULE_ELUNA=1
|
||||||
|
export MODULE_TIME_IS_TIME=1
|
||||||
|
export MODULE_POCKET_PORTAL=0
|
||||||
|
export MODULE_RANDOM_ENCHANTS=1
|
||||||
|
export MODULE_SOLOCRAFT=1
|
||||||
|
export MODULE_PVP_TITLES=1
|
||||||
|
export MODULE_NPC_BEASTMASTER=1
|
||||||
|
export MODULE_NPC_ENCHANTER=1
|
||||||
|
export MODULE_INSTANCE_RESET=1
|
||||||
|
export MODULE_LEVEL_GRANT=1
|
||||||
|
export MODULE_ARAC=1
|
||||||
|
export MODULE_ASSISTANT=1
|
||||||
|
export MODULE_REAGENT_BANK=1
|
||||||
|
export MODULE_BLACK_MARKET_AUCTION_HOUSE=1
|
||||||
|
export MODULE_CHALLENGE_MODES=1
|
||||||
|
export MODULE_OLLAMA_CHAT=1
|
||||||
|
export MODULE_PLAYER_BOT_LEVEL_BRACKETS=1
|
||||||
|
export MODULE_STATBOOSTER=1
|
||||||
|
export MODULE_DUNGEON_RESPAWN=0
|
||||||
|
export MODULE_SKELETON_MODULE=1
|
||||||
|
export MODULE_BG_SLAVERYVALLEY=1
|
||||||
|
export MODULE_AZEROTHSHARD=1
|
||||||
|
export MODULE_WORGOBLIN=1
|
||||||
|
export MODULE_ELUNA_TS=1
|
||||||
|
export MODULES_ENABLED="mod-playerbots mod-aoe-loot mod-learn-spells mod-fireworks-on-level mod-individual-progression mod-ahbot mod-autobalance mod-transmog mod-npc-buffer mod-dynamic-xp mod-solo-lfg mod-1v1-arena mod-phased-duels mod-breaking-news-override mod-boss-announcer mod-account-achievements mod-auto-revive mod-gain-honor-guard mod-ale mod-TimeIsTime mod-random-enchants mod-solocraft mod-pvp-titles mod-npc-beastmaster mod-npc-enchanter mod-instance-reset mod-quest-count-level mod-arac mod-assistant mod-reagent-bank mod-black-market mod-challenge-modes mod-ollama-chat mod-player-bot-level-brackets StatBooster skeleton-module mod-bg-slaveryvalley mod-azerothshard mod-worgoblin eluna-ts"
|
||||||
|
export MODULES_COMPILE="mod-aoe-loot mod-learn-spells mod-fireworks-on-level mod-individual-progression mod-ahbot mod-autobalance mod-transmog mod-npc-buffer mod-dynamic-xp mod-solo-lfg mod-1v1-arena mod-phased-duels mod-breaking-news-override mod-boss-announcer mod-account-achievements mod-auto-revive mod-gain-honor-guard mod-ale mod-TimeIsTime mod-random-enchants mod-solocraft mod-pvp-titles mod-npc-beastmaster mod-npc-enchanter mod-instance-reset mod-quest-count-level mod-arac mod-assistant mod-reagent-bank mod-challenge-modes mod-ollama-chat mod-player-bot-level-brackets StatBooster skeleton-module mod-bg-slaveryvalley mod-azerothshard mod-worgoblin"
|
||||||
|
export MODULES_REQUIRES_PLAYERBOT_SOURCE=1
|
||||||
|
export MODULES_WARNING_COUNT=0
|
||||||
|
export MODULES_ERROR_COUNT=0
|
||||||
76
scripts/check_module_staging.py
Executable file
76
scripts/check_module_staging.py
Executable file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def load_module_state(root: Path) -> dict:
|
||||||
|
env_path = root / ".env"
|
||||||
|
manifest_path = root / "config" / "modules.json"
|
||||||
|
modules_py = root / "scripts" / "modules.py"
|
||||||
|
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
str(modules_py),
|
||||||
|
"--env-path",
|
||||||
|
str(env_path),
|
||||||
|
"--manifest",
|
||||||
|
str(manifest_path),
|
||||||
|
"dump",
|
||||||
|
"--format",
|
||||||
|
"json",
|
||||||
|
],
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as exc:
|
||||||
|
print("Unable to load module state:", exc, file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
return json.loads(output)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
data = load_module_state(root)
|
||||||
|
|
||||||
|
enabled_modules = [m for m in data["modules"] if m["enabled"]]
|
||||||
|
storage_dir = root / "storage" / "modules"
|
||||||
|
|
||||||
|
local_root = Path(os.environ.get("STORAGE_PATH_LOCAL", "./local-storage"))
|
||||||
|
local_root = (root / local_root).resolve()
|
||||||
|
requires_playerbots = any(m["key"] == "MODULE_PLAYERBOTS" and m["enabled"] for m in enabled_modules)
|
||||||
|
source_dir = local_root / "source"
|
||||||
|
source_dir = source_dir / ("azerothcore-playerbots" if requires_playerbots else "azerothcore") / "modules"
|
||||||
|
|
||||||
|
print(f"📦 Checking module staging in {storage_dir} and {source_dir}")
|
||||||
|
print("Enabled modules:", ", ".join(m["name"] for m in enabled_modules))
|
||||||
|
|
||||||
|
status = 0
|
||||||
|
for module in enabled_modules:
|
||||||
|
dir_name = module["name"]
|
||||||
|
storage_path = storage_dir / dir_name
|
||||||
|
source_path = source_dir / dir_name
|
||||||
|
|
||||||
|
def state(path: Path) -> str:
|
||||||
|
if (path / ".git").is_dir():
|
||||||
|
return "git"
|
||||||
|
if path.is_dir():
|
||||||
|
return "present"
|
||||||
|
return "missing"
|
||||||
|
|
||||||
|
storage_state = state(storage_path)
|
||||||
|
source_state = state(source_path)
|
||||||
|
print(f" - {dir_name} ({module['key']}): storage={storage_state}, source={source_state}")
|
||||||
|
|
||||||
|
if storage_state == "missing" or source_state == "missing":
|
||||||
|
status = 1
|
||||||
|
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -6,6 +6,75 @@ trap 'echo " ❌ SQL helper error (line ${LINENO}): ${BASH_COMMAND}" >&2' ERR
|
|||||||
CUSTOM_SQL_ROOT="/tmp/scripts/sql/custom"
|
CUSTOM_SQL_ROOT="/tmp/scripts/sql/custom"
|
||||||
ALT_CUSTOM_SQL_ROOT="/scripts/sql/custom"
|
ALT_CUSTOM_SQL_ROOT="/scripts/sql/custom"
|
||||||
|
|
||||||
|
SQL_SUCCESS_LOG=()
|
||||||
|
SQL_FAILURE_LOG=()
|
||||||
|
TEMP_SQL_FILES=()
|
||||||
|
|
||||||
|
render_sql_file_for_execution(){
|
||||||
|
local src="$1"
|
||||||
|
local pb_db="${DB_PLAYERBOTS_NAME:-acore_playerbots}"
|
||||||
|
local rendered="$src"
|
||||||
|
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
local temp
|
||||||
|
temp="$(mktemp)"
|
||||||
|
local result
|
||||||
|
result="$(python3 - "$src" "$temp" "$pb_db" <<'PY'
|
||||||
|
import sys, pathlib, re
|
||||||
|
src, dest, pb_db = sys.argv[1:]
|
||||||
|
text = pathlib.Path(src).read_text()
|
||||||
|
original = text
|
||||||
|
text = text.replace("{{PLAYERBOTS_DB}}", pb_db)
|
||||||
|
pattern = re.compile(r'(?<![.`])\bplayerbots\b')
|
||||||
|
text = pattern.sub(f'`{pb_db}`.playerbots', text)
|
||||||
|
pathlib.Path(dest).write_text(text)
|
||||||
|
print("changed" if text != original else "unchanged", end="")
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
if [ "$result" = "changed" ]; then
|
||||||
|
rendered="$temp"
|
||||||
|
TEMP_SQL_FILES+=("$temp")
|
||||||
|
else
|
||||||
|
rm -f "$temp"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$rendered"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_sql_success(){
|
||||||
|
local target_db="$1"
|
||||||
|
local sql_file="$2"
|
||||||
|
SQL_SUCCESS_LOG+=("${target_db}::${sql_file}")
|
||||||
|
}
|
||||||
|
|
||||||
|
log_sql_failure(){
|
||||||
|
local target_db="$1"
|
||||||
|
local sql_file="$2"
|
||||||
|
SQL_FAILURE_LOG+=("${target_db}::${sql_file}")
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql_exec(){
|
||||||
|
local mysql_port="${MYSQL_PORT:-3306}"
|
||||||
|
if command -v mariadb >/dev/null 2>&1; then
|
||||||
|
mariadb --ssl=false -h "${CONTAINER_MYSQL}" -P "$mysql_port" -u root -p"${MYSQL_ROOT_PASSWORD}" "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if command -v mysql >/dev/null 2>&1; then
|
||||||
|
mysql --ssl-mode=DISABLED -h "${CONTAINER_MYSQL}" -P "$mysql_port" -u root -p"${MYSQL_ROOT_PASSWORD}" "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo " ❌ Neither mariadb nor mysql client is available for SQL execution" >&2
|
||||||
|
return 127
|
||||||
|
}
|
||||||
|
|
||||||
|
playerbots_table_exists(){
|
||||||
|
local pb_db="${DB_PLAYERBOTS_NAME:-acore_playerbots}"
|
||||||
|
local count
|
||||||
|
count="$(mysql_exec -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${pb_db}' AND table_name='playerbots';" 2>/dev/null || echo 0)"
|
||||||
|
[ "${count}" != "0" ]
|
||||||
|
}
|
||||||
|
|
||||||
run_custom_sql_group(){
|
run_custom_sql_group(){
|
||||||
local subdir="$1" target_db="$2" label="$3"
|
local subdir="$1" target_db="$2" label="$3"
|
||||||
local dir="${CUSTOM_SQL_ROOT}/${subdir}"
|
local dir="${CUSTOM_SQL_ROOT}/${subdir}"
|
||||||
@@ -13,25 +82,76 @@ run_custom_sql_group(){
|
|||||||
dir="${ALT_CUSTOM_SQL_ROOT}/${subdir}"
|
dir="${ALT_CUSTOM_SQL_ROOT}/${subdir}"
|
||||||
fi
|
fi
|
||||||
[ -d "$dir" ] || return 0
|
[ -d "$dir" ] || return 0
|
||||||
LC_ALL=C find "$dir" -type f -name "*.sql" | sort | while read -r sql_file; do
|
while IFS= read -r sql_file; do
|
||||||
local base_name
|
local base_name
|
||||||
base_name="$(basename "$sql_file")"
|
base_name="$(basename "$sql_file")"
|
||||||
|
local rendered
|
||||||
|
rendered="$(render_sql_file_for_execution "$sql_file")"
|
||||||
|
if grep -q '\bplayerbots\b' "$rendered"; then
|
||||||
|
if ! playerbots_table_exists; then
|
||||||
|
echo " Skipping ${label}: ${base_name} (playerbots table missing)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
echo " Executing ${label}: ${base_name}"
|
echo " Executing ${label}: ${base_name}"
|
||||||
if mariadb --ssl=false -h "${CONTAINER_MYSQL}" -P 3306 -u root -p"${MYSQL_ROOT_PASSWORD}" "${target_db}" < "$sql_file" >/dev/null 2>&1; then
|
local sql_output
|
||||||
|
sql_output="$(mktemp)"
|
||||||
|
if mysql_exec "${target_db}" < "$rendered" >"$sql_output" 2>&1; then
|
||||||
echo " ✅ Successfully executed ${base_name}"
|
echo " ✅ Successfully executed ${base_name}"
|
||||||
|
log_sql_success "$target_db" "$sql_file"
|
||||||
else
|
else
|
||||||
echo " ❌ Failed to execute $sql_file"
|
echo " ❌ Failed to execute $sql_file"
|
||||||
|
sed 's/^/ /' "$sql_output"
|
||||||
|
log_sql_failure "$target_db" "$sql_file"
|
||||||
fi
|
fi
|
||||||
done || true
|
rm -f "$sql_output"
|
||||||
|
done < <(LC_ALL=C find "$dir" -type f -name "*.sql" | sort) || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to execute SQL files for a module
|
# Function to execute SQL files for a module
|
||||||
execute_module_sql() {
|
module_sql_run_module(){
|
||||||
local module_dir="$1"
|
local module_key="$1"
|
||||||
local module_name="$2"
|
local module_dir="$2"
|
||||||
|
local module_name="${MODULE_NAME[$module_key]:-}"
|
||||||
|
if [ -z "$module_name" ]; then
|
||||||
|
module_name="$module_dir"
|
||||||
|
fi
|
||||||
|
local world_db="${DB_WORLD_NAME:-acore_world}"
|
||||||
|
local auth_db="${DB_AUTH_NAME:-acore_auth}"
|
||||||
|
local characters_db="${DB_CHARACTERS_NAME:-acore_characters}"
|
||||||
local playerbots_db="${DB_PLAYERBOTS_NAME:-acore_playerbots}"
|
local playerbots_db="${DB_PLAYERBOTS_NAME:-acore_playerbots}"
|
||||||
local character_set="${MYSQL_CHARACTER_SET:-utf8mb4}"
|
local character_set="${MYSQL_CHARACTER_SET:-utf8mb4}"
|
||||||
local collation="${MYSQL_COLLATION:-utf8mb4_unicode_ci}"
|
local collation="${MYSQL_COLLATION:-utf8mb4_unicode_ci}"
|
||||||
|
execute_sql_file_in_db(){
|
||||||
|
local target_db="$1"
|
||||||
|
local sql_file="$2"
|
||||||
|
local label="$3"
|
||||||
|
local rendered
|
||||||
|
rendered="$(render_sql_file_for_execution "$sql_file")"
|
||||||
|
|
||||||
|
if grep -q '\bplayerbots\b' "$rendered"; then
|
||||||
|
if ! playerbots_table_exists; then
|
||||||
|
echo " Skipping ${label}: ${base_name} (playerbots table missing)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local base_name
|
||||||
|
base_name="$(basename "$sql_file")"
|
||||||
|
echo " Executing ${label}: ${base_name}"
|
||||||
|
local sql_output
|
||||||
|
sql_output="$(mktemp)"
|
||||||
|
if mysql_exec "${target_db}" < "$rendered" >"$sql_output" 2>&1; then
|
||||||
|
echo " ✅ Successfully executed ${base_name}"
|
||||||
|
log_sql_success "$target_db" "$sql_file"
|
||||||
|
else
|
||||||
|
echo " ❌ Failed to execute $sql_file"
|
||||||
|
sed 's/^/ /' "$sql_output"
|
||||||
|
log_sql_failure "$target_db" "$sql_file"
|
||||||
|
fi
|
||||||
|
rm -f "$sql_output"
|
||||||
|
}
|
||||||
|
|
||||||
local run_sorted_sql
|
local run_sorted_sql
|
||||||
|
|
||||||
run_sorted_sql() {
|
run_sorted_sql() {
|
||||||
@@ -40,27 +160,22 @@ execute_module_sql() {
|
|||||||
local label="$3"
|
local label="$3"
|
||||||
local skip_regex="${4:-}"
|
local skip_regex="${4:-}"
|
||||||
[ -d "$dir" ] || return
|
[ -d "$dir" ] || return
|
||||||
LC_ALL=C find "$dir" -type f -name "*.sql" | sort | while read -r sql_file; do
|
while IFS= read -r sql_file; do
|
||||||
local base_name
|
local base_name
|
||||||
base_name="$(basename "$sql_file")"
|
base_name="$(basename "$sql_file")"
|
||||||
if [ -n "$skip_regex" ] && [[ "$base_name" =~ $skip_regex ]]; then
|
if [ -n "$skip_regex" ] && [[ "$base_name" =~ $skip_regex ]]; then
|
||||||
echo " Skipping ${label}: ${base_name}"
|
echo " Skipping ${label}: ${base_name}"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
echo " Executing ${label}: ${base_name}"
|
execute_sql_file_in_db "$target_db" "$sql_file" "$label"
|
||||||
if mariadb --ssl=false -h "${CONTAINER_MYSQL}" -P 3306 -u root -p"${MYSQL_ROOT_PASSWORD}" "${target_db}" < "$sql_file" >/dev/null 2>&1; then
|
done < <(LC_ALL=C find "$dir" -type f -name "*.sql" | sort) || true
|
||||||
echo " ✅ Successfully executed ${base_name}"
|
|
||||||
else
|
|
||||||
echo " ❌ Failed to execute $sql_file"
|
|
||||||
fi
|
|
||||||
done || true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "Processing SQL scripts for $module_name..."
|
echo "Processing SQL scripts for $module_name..."
|
||||||
|
|
||||||
if [ "$module_name" = "Playerbots" ]; then
|
if [ "$module_key" = "MODULE_PLAYERBOTS" ]; then
|
||||||
echo " Ensuring database ${playerbots_db} exists..."
|
echo " Ensuring database ${playerbots_db} exists..."
|
||||||
if mariadb --ssl=false -h "${CONTAINER_MYSQL}" -P 3306 -u root -p"${MYSQL_ROOT_PASSWORD}" -e "CREATE DATABASE IF NOT EXISTS \`${playerbots_db}\` CHARACTER SET ${character_set} COLLATE ${collation};" >/dev/null 2>&1; then
|
if mysql_exec -e "CREATE DATABASE IF NOT EXISTS \`${playerbots_db}\` CHARACTER SET ${character_set} COLLATE ${collation};" >/dev/null 2>&1; then
|
||||||
echo " ✅ Playerbots database ready"
|
echo " ✅ Playerbots database ready"
|
||||||
else
|
else
|
||||||
echo " ❌ Failed to ensure playerbots database"
|
echo " ❌ Failed to ensure playerbots database"
|
||||||
@@ -71,45 +186,30 @@ execute_module_sql() {
|
|||||||
if [ -d "$module_dir/data/sql" ]; then
|
if [ -d "$module_dir/data/sql" ]; then
|
||||||
# Execute world database scripts
|
# Execute world database scripts
|
||||||
if [ -d "$module_dir/data/sql/world" ]; then
|
if [ -d "$module_dir/data/sql/world" ]; then
|
||||||
find "$module_dir/data/sql/world" -name "*.sql" -type f | while read sql_file; do
|
while IFS= read -r sql_file; do
|
||||||
echo " Executing world SQL: $(basename "$sql_file")"
|
execute_sql_file_in_db "$world_db" "$sql_file" "world SQL"
|
||||||
if mariadb --ssl=false -h "${CONTAINER_MYSQL}" -P 3306 -u root -p"${MYSQL_ROOT_PASSWORD}" "${DB_WORLD_NAME}" < "$sql_file" >/dev/null 2>&1; then
|
done < <(find "$module_dir/data/sql/world" -type f -name "*.sql") || true
|
||||||
echo " ✅ Successfully executed $(basename "$sql_file")"
|
|
||||||
else
|
|
||||||
echo " ❌ Failed to execute $sql_file"
|
|
||||||
fi
|
fi
|
||||||
done
|
run_sorted_sql "$module_dir/data/sql/db-world" "${world_db}" "world SQL"
|
||||||
fi
|
|
||||||
run_sorted_sql "$module_dir/data/sql/db-world" "${DB_WORLD_NAME}" "world SQL"
|
|
||||||
|
|
||||||
# Execute auth database scripts
|
# Execute auth database scripts
|
||||||
if [ -d "$module_dir/data/sql/auth" ]; then
|
if [ -d "$module_dir/data/sql/auth" ]; then
|
||||||
find "$module_dir/data/sql/auth" -name "*.sql" -type f | while read sql_file; do
|
while IFS= read -r sql_file; do
|
||||||
echo " Executing auth SQL: $(basename "$sql_file")"
|
execute_sql_file_in_db "$auth_db" "$sql_file" "auth SQL"
|
||||||
if mariadb --ssl=false -h "${CONTAINER_MYSQL}" -P 3306 -u root -p"${MYSQL_ROOT_PASSWORD}" "${DB_AUTH_NAME}" < "$sql_file" >/dev/null 2>&1; then
|
done < <(find "$module_dir/data/sql/auth" -type f -name "*.sql") || true
|
||||||
echo " ✅ Successfully executed $(basename "$sql_file")"
|
|
||||||
else
|
|
||||||
echo " ❌ Failed to execute $sql_file"
|
|
||||||
fi
|
fi
|
||||||
done
|
run_sorted_sql "$module_dir/data/sql/db-auth" "${auth_db}" "auth SQL"
|
||||||
fi
|
|
||||||
run_sorted_sql "$module_dir/data/sql/db-auth" "${DB_AUTH_NAME}" "auth SQL"
|
|
||||||
|
|
||||||
# Execute character database scripts
|
# Execute character database scripts
|
||||||
if [ -d "$module_dir/data/sql/characters" ]; then
|
if [ -d "$module_dir/data/sql/characters" ]; then
|
||||||
find "$module_dir/data/sql/characters" -name "*.sql" -type f | while read sql_file; do
|
while IFS= read -r sql_file; do
|
||||||
echo " Executing characters SQL: $(basename "$sql_file")"
|
execute_sql_file_in_db "$characters_db" "$sql_file" "characters SQL"
|
||||||
if mariadb --ssl=false -h "${CONTAINER_MYSQL}" -P 3306 -u root -p"${MYSQL_ROOT_PASSWORD}" "${DB_CHARACTERS_NAME}" < "$sql_file" >/dev/null 2>&1; then
|
done < <(find "$module_dir/data/sql/characters" -type f -name "*.sql") || true
|
||||||
echo " ✅ Successfully executed $(basename "$sql_file")"
|
|
||||||
else
|
|
||||||
echo " ❌ Failed to execute $sql_file"
|
|
||||||
fi
|
fi
|
||||||
done
|
run_sorted_sql "$module_dir/data/sql/db-characters" "${characters_db}" "characters SQL"
|
||||||
fi
|
|
||||||
run_sorted_sql "$module_dir/data/sql/db-characters" "${DB_CHARACTERS_NAME}" "characters SQL"
|
|
||||||
|
|
||||||
# Execute playerbots database scripts
|
# Execute playerbots database scripts
|
||||||
if [ "$module_name" = "Playerbots" ] && [ -d "$module_dir/data/sql/playerbots" ]; then
|
if [ "$module_key" = "MODULE_PLAYERBOTS" ] && [ -d "$module_dir/data/sql/playerbots" ]; then
|
||||||
local pb_root="$module_dir/data/sql/playerbots"
|
local pb_root="$module_dir/data/sql/playerbots"
|
||||||
run_sorted_sql "$pb_root/base" "$playerbots_db" "playerbots SQL"
|
run_sorted_sql "$pb_root/base" "$playerbots_db" "playerbots SQL"
|
||||||
run_sorted_sql "$pb_root/custom" "$playerbots_db" "playerbots SQL"
|
run_sorted_sql "$pb_root/custom" "$playerbots_db" "playerbots SQL"
|
||||||
@@ -119,18 +219,16 @@ execute_module_sql() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Execute base SQL files (common pattern)
|
# Execute base SQL files (common pattern)
|
||||||
find "$module_dir/data/sql" -maxdepth 1 -name "*.sql" -type f | while read sql_file; do
|
while IFS= read -r sql_file; do
|
||||||
echo " Executing base SQL: $(basename "$sql_file")"
|
execute_sql_file_in_db "$world_db" "$sql_file" "base SQL"
|
||||||
mysql -h "${CONTAINER_MYSQL}" -P 3306 -u root -p"${MYSQL_ROOT_PASSWORD}" "${DB_WORLD_NAME}" < "$sql_file" 2>/dev/null || echo " Warning: Failed to execute $sql_file"
|
done < <(find "$module_dir/data/sql" -maxdepth 1 -type f -name "*.sql") || true
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Look for SQL files in other common locations
|
# Look for SQL files in other common locations
|
||||||
if [ -d "$module_dir/sql" ]; then
|
if [ -d "$module_dir/sql" ]; then
|
||||||
find "$module_dir/sql" -name "*.sql" -type f | while read sql_file; do
|
while IFS= read -r sql_file; do
|
||||||
echo " Executing SQL: $(basename "$sql_file")"
|
execute_sql_file_in_db "$world_db" "$sql_file" "module SQL"
|
||||||
mysql -h "${CONTAINER_MYSQL}" -P 3306 -u root -p"${MYSQL_ROOT_PASSWORD}" "${DB_WORLD_NAME}" < "$sql_file" 2>/dev/null || echo " Warning: Failed to execute $sql_file"
|
done < <(find "$module_dir/sql" -type f -name "*.sql") || true
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -144,76 +242,60 @@ execute_module_sql_scripts() {
|
|||||||
apk add --no-cache mariadb-client >/dev/null 2>&1 || echo "Warning: Could not install MariaDB client"
|
apk add --no-cache mariadb-client >/dev/null 2>&1 || echo "Warning: Could not install MariaDB client"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Iterate modules from staging directory to catch new modules automatically
|
SQL_SUCCESS_LOG=()
|
||||||
for module_dir in */; do
|
SQL_FAILURE_LOG=()
|
||||||
[[ -d "$module_dir" ]] || continue
|
|
||||||
[[ "$module_dir" == "." || "$module_dir" == ".." ]] && continue
|
# Iterate modules from manifest metadata
|
||||||
module_dir="${module_dir%/}"
|
local key module_dir enabled
|
||||||
# Only process directories that follow mod-* convention or known module names
|
local world_db="${DB_WORLD_NAME:-acore_world}"
|
||||||
if [[ "$module_dir" != mod-* && "$module_dir" != StatBooster && "$module_dir" != DungeonRespawn && "$module_dir" != eluna-ts ]]; then
|
local auth_db="${DB_AUTH_NAME:-acore_auth}"
|
||||||
|
local characters_db="${DB_CHARACTERS_NAME:-acore_characters}"
|
||||||
|
for key in "${MODULE_KEYS[@]}"; do
|
||||||
|
module_dir="${MODULE_NAME[$key]:-}"
|
||||||
|
[ -n "$module_dir" ] || continue
|
||||||
|
[ -d "$module_dir" ] || continue
|
||||||
|
|
||||||
|
enabled="${MODULE_ENABLED[$key]:-0}"
|
||||||
|
if [ "$enabled" != "1" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local enabled=0
|
|
||||||
case "$module_dir" in
|
|
||||||
mod-playerbots) enabled="$MODULE_PLAYERBOTS" ;;
|
|
||||||
mod-aoe-loot) enabled="$MODULE_AOE_LOOT" ;;
|
|
||||||
mod-learn-spells) enabled="$MODULE_LEARN_SPELLS" ;;
|
|
||||||
mod-fireworks-on-level) enabled="$MODULE_FIREWORKS" ;;
|
|
||||||
mod-individual-progression) enabled="$MODULE_INDIVIDUAL_PROGRESSION" ;;
|
|
||||||
mod-ahbot) enabled="$MODULE_AHBOT" ;;
|
|
||||||
mod-autobalance) enabled="$MODULE_AUTOBALANCE" ;;
|
|
||||||
mod-transmog) enabled="$MODULE_TRANSMOG" ;;
|
|
||||||
mod-npc-buffer) enabled="$MODULE_NPC_BUFFER" ;;
|
|
||||||
mod-dynamic-xp) enabled="$MODULE_DYNAMIC_XP" ;;
|
|
||||||
mod-solo-lfg) enabled="$MODULE_SOLO_LFG" ;;
|
|
||||||
mod-1v1-arena) enabled="$MODULE_1V1_ARENA" ;;
|
|
||||||
mod-phased-duels) enabled="$MODULE_PHASED_DUELS" ;;
|
|
||||||
mod-breaking-news-override) enabled="$MODULE_BREAKING_NEWS" ;;
|
|
||||||
mod-boss-announcer) enabled="$MODULE_BOSS_ANNOUNCER" ;;
|
|
||||||
mod-account-achievements) enabled="$MODULE_ACCOUNT_ACHIEVEMENTS" ;;
|
|
||||||
mod-auto-revive) enabled="$MODULE_AUTO_REVIVE" ;;
|
|
||||||
mod-gain-honor-guard) enabled="$MODULE_GAIN_HONOR_GUARD" ;;
|
|
||||||
mod-ale) enabled="$MODULE_ELUNA" ;;
|
|
||||||
mod-TimeIsTime) enabled="$MODULE_TIME_IS_TIME" ;;
|
|
||||||
mod-pocket-portal) enabled="$MODULE_POCKET_PORTAL" ;;
|
|
||||||
mod-random-enchants) enabled="$MODULE_RANDOM_ENCHANTS" ;;
|
|
||||||
mod-solocraft) enabled="$MODULE_SOLOCRAFT" ;;
|
|
||||||
mod-pvp-titles) enabled="$MODULE_PVP_TITLES" ;;
|
|
||||||
mod-npc-beastmaster) enabled="$MODULE_NPC_BEASTMASTER" ;;
|
|
||||||
mod-npc-enchanter) enabled="$MODULE_NPC_ENCHANTER" ;;
|
|
||||||
mod-instance-reset) enabled="$MODULE_INSTANCE_RESET" ;;
|
|
||||||
mod-quest-count-level) enabled="$MODULE_LEVEL_GRANT" ;;
|
|
||||||
mod-arac) enabled="$MODULE_ARAC" ;;
|
|
||||||
mod-assistant) enabled="$MODULE_ASSISTANT" ;;
|
|
||||||
mod-reagent-bank) enabled="$MODULE_REAGENT_BANK" ;;
|
|
||||||
mod-black-market) enabled="$MODULE_BLACK_MARKET_AUCTION_HOUSE" ;;
|
|
||||||
mod-challenge-modes) enabled="$MODULE_CHALLENGE_MODES" ;;
|
|
||||||
mod-ollama-chat) enabled="$MODULE_OLLAMA_CHAT" ;;
|
|
||||||
mod-player-bot-level-brackets) enabled="$MODULE_PLAYER_BOT_LEVEL_BRACKETS" ;;
|
|
||||||
StatBooster) enabled="$MODULE_STATBOOSTER" ;;
|
|
||||||
DungeonRespawn) enabled="$MODULE_DUNGEON_RESPAWN" ;;
|
|
||||||
skeleton-module) enabled="$MODULE_SKELETON_MODULE" ;;
|
|
||||||
mod-bg-slaveryvalley) enabled="$MODULE_BG_SLAVERYVALLEY" ;;
|
|
||||||
mod-azerothshard) enabled="$MODULE_AZEROTHSHARD" ;;
|
|
||||||
mod-worgoblin) enabled="$MODULE_WORGOBLIN" ;;
|
|
||||||
eluna-ts) enabled="$MODULE_ELUNA_TS" ;;
|
|
||||||
*) enabled=1 ;; # Default to enabled for unknown module directories
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ "${enabled:-0}" = "1" ]; then
|
|
||||||
# Skip modules explicitly disabled for SQL
|
|
||||||
if [ "$module_dir" = "mod-pocket-portal" ]; then
|
if [ "$module_dir" = "mod-pocket-portal" ]; then
|
||||||
echo '⚠️ Skipping mod-pocket-portal SQL: module disabled until C++20 patch is applied.'
|
echo '⚠️ Skipping mod-pocket-portal SQL: module disabled until C++20 patch is applied.'
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
execute_module_sql "$module_dir" "$module_dir"
|
|
||||||
fi
|
module_sql_run_module "$key" "$module_dir"
|
||||||
done
|
done
|
||||||
|
|
||||||
run_custom_sql_group world "${DB_WORLD_NAME}" "custom world SQL"
|
run_custom_sql_group world "${world_db}" "custom world SQL"
|
||||||
run_custom_sql_group auth "${DB_AUTH_NAME}" "custom auth SQL"
|
run_custom_sql_group auth "${auth_db}" "custom auth SQL"
|
||||||
run_custom_sql_group characters "${DB_CHARACTERS_NAME}" "custom characters SQL"
|
run_custom_sql_group characters "${characters_db}" "custom characters SQL"
|
||||||
|
|
||||||
|
echo "SQL execution summary:"
|
||||||
|
if [ ${#SQL_SUCCESS_LOG[@]} -gt 0 ]; then
|
||||||
|
echo " ✅ Applied:"
|
||||||
|
for entry in "${SQL_SUCCESS_LOG[@]}"; do
|
||||||
|
IFS='::' read -r db file <<< "$entry"
|
||||||
|
echo " • [$db] $file"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo " ✅ Applied: none"
|
||||||
|
fi
|
||||||
|
if [ ${#SQL_FAILURE_LOG[@]} -gt 0 ]; then
|
||||||
|
echo " ❌ Failed:"
|
||||||
|
for entry in "${SQL_FAILURE_LOG[@]}"; do
|
||||||
|
IFS='::' read -r db file <<< "$entry"
|
||||||
|
echo " • [$db] $file"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo " ❌ Failed: none"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#TEMP_SQL_FILES[@]} -gt 0 ]; then
|
||||||
|
rm -f "${TEMP_SQL_FILES[@]}" 2>/dev/null || true
|
||||||
|
TEMP_SQL_FILES=()
|
||||||
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
519
scripts/modules.py
Executable file
519
scripts/modules.py
Executable file
@@ -0,0 +1,519 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Module manifest helper.
|
||||||
|
|
||||||
|
Reads config/modules.json and .env to produce canonical module state that
|
||||||
|
downstream shell scripts can consume for staging, rebuild detection, and
|
||||||
|
dependency validation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
from dataclasses import dataclass, asdict, field
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Iterable, List, Optional, Tuple
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
|
||||||
|
STRICT_TRUE = {"1", "true", "yes", "on"}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_bool(value: str) -> bool:
|
||||||
|
if value is None:
|
||||||
|
return False
|
||||||
|
return str(value).strip().lower() in STRICT_TRUE
|
||||||
|
|
||||||
|
|
||||||
|
def load_env_file(env_path: Path) -> Dict[str, str]:
|
||||||
|
if not env_path.exists():
|
||||||
|
return {}
|
||||||
|
env: Dict[str, str] = {}
|
||||||
|
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if line.startswith("export "):
|
||||||
|
line = line[len("export ") :].strip()
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip()
|
||||||
|
if value.startswith('"') and value.endswith('"'):
|
||||||
|
value = value[1:-1]
|
||||||
|
elif value.startswith("'") and value.endswith("'"):
|
||||||
|
value = value[1:-1]
|
||||||
|
env[key] = value
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
def load_manifest(manifest_path: Path) -> List[Dict[str, object]]:
|
||||||
|
if not manifest_path.exists():
|
||||||
|
raise FileNotFoundError(f"Manifest file not found: {manifest_path}")
|
||||||
|
with manifest_path.open("r", encoding="utf-8") as fh:
|
||||||
|
manifest = json.load(fh)
|
||||||
|
modules = manifest.get("modules")
|
||||||
|
if not isinstance(modules, list):
|
||||||
|
raise ValueError("Manifest must define a top-level 'modules' array")
|
||||||
|
validated: List[Dict[str, object]] = []
|
||||||
|
seen_keys: set[str] = set()
|
||||||
|
for entry in modules:
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
raise ValueError("Each manifest entry must be an object")
|
||||||
|
key = entry.get("key")
|
||||||
|
name = entry.get("name")
|
||||||
|
repo = entry.get("repo")
|
||||||
|
if not key or not isinstance(key, str):
|
||||||
|
raise ValueError("Manifest entry missing 'key'")
|
||||||
|
if key in seen_keys:
|
||||||
|
raise ValueError(f"Duplicate manifest key detected: {key}")
|
||||||
|
seen_keys.add(key)
|
||||||
|
if not name or not isinstance(name, str):
|
||||||
|
raise ValueError(f"Manifest entry {key} missing 'name'")
|
||||||
|
if not repo or not isinstance(repo, str):
|
||||||
|
raise ValueError(f"Manifest entry {key} missing 'repo'")
|
||||||
|
validated.append(entry)
|
||||||
|
return validated
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ModuleState:
|
||||||
|
key: str
|
||||||
|
name: str
|
||||||
|
repo: str
|
||||||
|
needs_build: bool
|
||||||
|
module_type: str
|
||||||
|
requires: List[str] = field(default_factory=list)
|
||||||
|
ref: Optional[str] = None
|
||||||
|
status: str = "active"
|
||||||
|
block_reason: Optional[str] = None
|
||||||
|
post_install_hooks: List[str] = field(default_factory=list)
|
||||||
|
config_cleanup: List[str] = field(default_factory=list)
|
||||||
|
sql: Optional[object] = None
|
||||||
|
notes: Optional[str] = None
|
||||||
|
enabled_raw: bool = False
|
||||||
|
enabled_effective: bool = False
|
||||||
|
value: str = "0"
|
||||||
|
dependency_issues: List[str] = field(default_factory=list)
|
||||||
|
warnings: List[str] = field(default_factory=list)
|
||||||
|
errors: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def blocked(self) -> bool:
|
||||||
|
return self.status.lower() == "blocked"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ModuleCollectionState:
|
||||||
|
manifest_path: Path
|
||||||
|
env_path: Path
|
||||||
|
modules: List[ModuleState]
|
||||||
|
generated_at: datetime
|
||||||
|
warnings: List[str]
|
||||||
|
errors: List[str]
|
||||||
|
|
||||||
|
def enabled_modules(self) -> List[ModuleState]:
|
||||||
|
return [module for module in self.modules if module.enabled_effective]
|
||||||
|
|
||||||
|
def compile_modules(self) -> List[ModuleState]:
|
||||||
|
return [
|
||||||
|
module
|
||||||
|
for module in self.modules
|
||||||
|
if module.enabled_effective and module.needs_build
|
||||||
|
]
|
||||||
|
|
||||||
|
def requires_playerbot_source(self) -> bool:
|
||||||
|
module_map = {m.key: m for m in self.modules}
|
||||||
|
playerbots_enabled = module_map.get("MODULE_PLAYERBOTS")
|
||||||
|
playerbots = bool(playerbots_enabled and playerbots_enabled.enabled_effective)
|
||||||
|
needs_cpp = any(module.needs_build and module.enabled_effective for module in self.modules)
|
||||||
|
return playerbots or needs_cpp
|
||||||
|
|
||||||
|
|
||||||
|
def build_state(env_path: Path, manifest_path: Path) -> ModuleCollectionState:
|
||||||
|
env_map = load_env_file(env_path)
|
||||||
|
manifest_entries = load_manifest(manifest_path)
|
||||||
|
modules: List[ModuleState] = []
|
||||||
|
errors: List[str] = []
|
||||||
|
warnings: List[str] = []
|
||||||
|
|
||||||
|
# Track which manifest keys appear in .env for coverage validation
|
||||||
|
env_keys_in_manifest: set[str] = set()
|
||||||
|
|
||||||
|
for entry in manifest_entries:
|
||||||
|
key = entry["key"]
|
||||||
|
name = entry["name"]
|
||||||
|
repo = entry["repo"]
|
||||||
|
needs_build = bool(entry.get("needs_build", False))
|
||||||
|
module_type = str(entry.get("type", "cpp"))
|
||||||
|
requires = entry.get("requires") or []
|
||||||
|
if not isinstance(requires, list):
|
||||||
|
raise ValueError(f"Manifest entry {key} has non-list 'requires'")
|
||||||
|
requires = [str(dep) for dep in requires]
|
||||||
|
|
||||||
|
depends_on = entry.get("depends_on") or []
|
||||||
|
if not isinstance(depends_on, list):
|
||||||
|
raise ValueError(f"Manifest entry {key} has non-list 'depends_on'")
|
||||||
|
depends_on = [str(dep) for dep in depends_on]
|
||||||
|
if depends_on:
|
||||||
|
requires = list(dict.fromkeys(requires + depends_on))
|
||||||
|
status = entry.get("status", "active")
|
||||||
|
block_reason = entry.get("block_reason")
|
||||||
|
post_install_hooks = entry.get("post_install_hooks") or []
|
||||||
|
if not isinstance(post_install_hooks, list):
|
||||||
|
raise ValueError(f"Manifest entry {key} has non-list 'post_install_hooks'")
|
||||||
|
post_install_hooks = [str(hook) for hook in post_install_hooks]
|
||||||
|
config_cleanup = entry.get("config_cleanup") or []
|
||||||
|
if not isinstance(config_cleanup, list):
|
||||||
|
raise ValueError(f"Manifest entry {key} has non-list 'config_cleanup'")
|
||||||
|
config_cleanup = [str(pattern) for pattern in config_cleanup]
|
||||||
|
sql = entry.get("sql")
|
||||||
|
ref = entry.get("ref")
|
||||||
|
notes = entry.get("notes")
|
||||||
|
|
||||||
|
raw_value = env_map.get(key, os.environ.get(key, "0"))
|
||||||
|
env_keys_in_manifest.add(key)
|
||||||
|
enabled_raw = parse_bool(raw_value)
|
||||||
|
|
||||||
|
module = ModuleState(
|
||||||
|
key=key,
|
||||||
|
name=name,
|
||||||
|
repo=repo,
|
||||||
|
needs_build=needs_build,
|
||||||
|
module_type=module_type,
|
||||||
|
requires=requires,
|
||||||
|
ref=ref,
|
||||||
|
status=status,
|
||||||
|
block_reason=block_reason,
|
||||||
|
post_install_hooks=post_install_hooks,
|
||||||
|
config_cleanup=config_cleanup,
|
||||||
|
sql=sql,
|
||||||
|
notes=notes,
|
||||||
|
enabled_raw=enabled_raw,
|
||||||
|
)
|
||||||
|
|
||||||
|
if module.blocked and enabled_raw:
|
||||||
|
module.errors.append(
|
||||||
|
f"{module.key} is blocked: {module.block_reason or 'blocked in manifest'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Effective enablement respects block status
|
||||||
|
module.enabled_effective = enabled_raw and not module.blocked
|
||||||
|
module.value = "1" if module.enabled_effective else "0"
|
||||||
|
|
||||||
|
modules.append(module)
|
||||||
|
|
||||||
|
module_map: Dict[str, ModuleState] = {module.key: module for module in modules}
|
||||||
|
|
||||||
|
# Dependency validation
|
||||||
|
for module in modules:
|
||||||
|
if not module.enabled_effective:
|
||||||
|
continue
|
||||||
|
missing: List[str] = []
|
||||||
|
for dependency in module.requires:
|
||||||
|
dep_state = module_map.get(dependency)
|
||||||
|
if not dep_state or not dep_state.enabled_effective:
|
||||||
|
missing.append(dependency)
|
||||||
|
if missing:
|
||||||
|
plural = "modules" if len(missing) > 1 else "module"
|
||||||
|
list_str = ", ".join(missing)
|
||||||
|
message = f"{module.key} requires {plural}: {list_str}"
|
||||||
|
module.errors.append(message)
|
||||||
|
|
||||||
|
# Collect warnings/errors
|
||||||
|
for module in modules:
|
||||||
|
if module.errors:
|
||||||
|
errors.extend(module.errors)
|
||||||
|
if module.warnings:
|
||||||
|
warnings.extend(module.warnings)
|
||||||
|
|
||||||
|
# Warn if .env defines modules not in manifest
|
||||||
|
extra_env_modules = [
|
||||||
|
key for key in env_map.keys() if key.startswith("MODULE_") and key not in module_map
|
||||||
|
]
|
||||||
|
for unknown_key in extra_env_modules:
|
||||||
|
warnings.append(f".env defines {unknown_key} but it is missing from the manifest")
|
||||||
|
|
||||||
|
# Warn if manifest entry lacks .env toggle
|
||||||
|
for module in modules:
|
||||||
|
if module.key not in env_map and module.key not in os.environ:
|
||||||
|
warnings.append(
|
||||||
|
f"Manifest includes {module.key} but .env does not define it (defaulting to 0)"
|
||||||
|
)
|
||||||
|
|
||||||
|
return ModuleCollectionState(
|
||||||
|
manifest_path=manifest_path,
|
||||||
|
env_path=env_path,
|
||||||
|
modules=modules,
|
||||||
|
generated_at=datetime.now(timezone.utc),
|
||||||
|
warnings=warnings,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_outputs(state: ModuleCollectionState, output_dir: Path) -> None:
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
env_lines: List[str] = [
|
||||||
|
"# Autogenerated by scripts/modules.py",
|
||||||
|
f"# Generated at {state.generated_at.isoformat()}",
|
||||||
|
f'export MODULES_MANIFEST="{state.manifest_path}"',
|
||||||
|
f'export MODULES_ENV_PATH="{state.env_path}"',
|
||||||
|
]
|
||||||
|
|
||||||
|
enabled_names: List[str] = []
|
||||||
|
compile_names: List[str] = []
|
||||||
|
|
||||||
|
for module in state.modules:
|
||||||
|
env_lines.append(f"export {module.key}={module.value}")
|
||||||
|
if module.enabled_effective:
|
||||||
|
enabled_names.append(module.name)
|
||||||
|
if module.enabled_effective and module.needs_build:
|
||||||
|
compile_names.append(module.name)
|
||||||
|
|
||||||
|
env_lines.append(f'export MODULES_ENABLED="{ " ".join(enabled_names) }"'.rstrip())
|
||||||
|
env_lines.append(f'export MODULES_COMPILE="{ " ".join(compile_names) }"'.rstrip())
|
||||||
|
env_lines.append(
|
||||||
|
f"export MODULES_REQUIRES_PLAYERBOT_SOURCE="
|
||||||
|
f'{"1" if state.requires_playerbot_source() else "0"}'
|
||||||
|
)
|
||||||
|
env_lines.append(f"export MODULES_WARNING_COUNT={len(state.warnings)}")
|
||||||
|
env_lines.append(f"export MODULES_ERROR_COUNT={len(state.errors)}")
|
||||||
|
|
||||||
|
modules_env_path = output_dir / "modules.env"
|
||||||
|
modules_env_path.write_text("\n".join(env_lines) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
state_payload = {
|
||||||
|
"generated_at": state.generated_at.isoformat(),
|
||||||
|
"manifest_path": str(state.manifest_path),
|
||||||
|
"env_path": str(state.env_path),
|
||||||
|
"warnings": state.warnings,
|
||||||
|
"errors": state.errors,
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
**asdict(module),
|
||||||
|
"enabled_raw": module.enabled_raw,
|
||||||
|
"enabled_effective": module.enabled_effective,
|
||||||
|
"blocked": module.blocked,
|
||||||
|
}
|
||||||
|
for module in state.modules
|
||||||
|
],
|
||||||
|
"enabled_modules": [module.name for module in state.enabled_modules()],
|
||||||
|
"compile_modules": [module.name for module in state.compile_modules()],
|
||||||
|
"requires_playerbot_source": state.requires_playerbot_source(),
|
||||||
|
}
|
||||||
|
|
||||||
|
modules_state_path = output_dir / "modules-state.json"
|
||||||
|
modules_state_path.write_text(
|
||||||
|
json.dumps(state_payload, indent=2, sort_keys=True) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
meta_dir = output_dir / ".modules-meta"
|
||||||
|
meta_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
compile_list_path = meta_dir / "modules-compile.txt"
|
||||||
|
compile_list_path.write_text(
|
||||||
|
"\n".join(state_payload["compile_modules"]) + ("\n" if compile_names else ""),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
enabled_list_path = meta_dir / "modules-enabled.txt"
|
||||||
|
enabled_list_path.write_text(
|
||||||
|
"\n".join(state_payload["enabled_modules"]) + ("\n" if enabled_names else ""),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def print_list(state: ModuleCollectionState, selector: str) -> None:
|
||||||
|
if selector == "compile":
|
||||||
|
items = [module.name for module in state.compile_modules()]
|
||||||
|
elif selector == "enabled":
|
||||||
|
items = [module.name for module in state.enabled_modules()]
|
||||||
|
elif selector == "keys":
|
||||||
|
items = [module.key for module in state.enabled_modules()]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown list selector: {selector}")
|
||||||
|
for item in items:
|
||||||
|
print(item)
|
||||||
|
|
||||||
|
|
||||||
|
def print_requires_playerbot(state: ModuleCollectionState) -> None:
|
||||||
|
print("1" if state.requires_playerbot_source() else "0")
|
||||||
|
|
||||||
|
|
||||||
|
def print_state(state: ModuleCollectionState, fmt: str) -> None:
|
||||||
|
payload = {
|
||||||
|
"generated_at": state.generated_at.isoformat(),
|
||||||
|
"warnings": state.warnings,
|
||||||
|
"errors": state.errors,
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"key": module.key,
|
||||||
|
"name": module.name,
|
||||||
|
"enabled": module.enabled_effective,
|
||||||
|
"needs_build": module.needs_build,
|
||||||
|
"requires": module.requires,
|
||||||
|
"blocked": module.blocked,
|
||||||
|
"dependency_issues": module.dependency_issues,
|
||||||
|
"post_install_hooks": module.post_install_hooks,
|
||||||
|
"config_cleanup": module.config_cleanup,
|
||||||
|
}
|
||||||
|
for module in state.modules
|
||||||
|
],
|
||||||
|
"enabled_modules": [module.name for module in state.enabled_modules()],
|
||||||
|
"compile_modules": [module.name for module in state.compile_modules()],
|
||||||
|
"requires_playerbot_source": state.requires_playerbot_source(),
|
||||||
|
}
|
||||||
|
if fmt == "json":
|
||||||
|
json.dump(payload, sys.stdout, indent=2, sort_keys=True)
|
||||||
|
sys.stdout.write("\n")
|
||||||
|
elif fmt == "shell":
|
||||||
|
keys = [module.key for module in state.modules]
|
||||||
|
quoted_keys = " ".join(shlex.quote(key) for key in keys)
|
||||||
|
print(f"MODULE_KEYS=({quoted_keys})")
|
||||||
|
print(
|
||||||
|
"declare -A MODULE_NAME MODULE_REPO MODULE_REF MODULE_TYPE MODULE_ENABLED "
|
||||||
|
"MODULE_NEEDS_BUILD MODULE_BLOCKED MODULE_POST_INSTALL MODULE_REQUIRES "
|
||||||
|
"MODULE_CONFIG_CLEANUP "
|
||||||
|
"MODULE_NOTES MODULE_STATUS MODULE_BLOCK_REASON"
|
||||||
|
)
|
||||||
|
for module in state.modules:
|
||||||
|
key = module.key
|
||||||
|
post_install = ",".join(module.post_install_hooks)
|
||||||
|
dependencies = ",".join(module.requires)
|
||||||
|
block_reason = module.block_reason or ""
|
||||||
|
ref = module.ref or ""
|
||||||
|
notes = module.notes or ""
|
||||||
|
config_cleanup = ",".join(module.config_cleanup)
|
||||||
|
print(f"MODULE_NAME[{key}]={shlex.quote(module.name)}")
|
||||||
|
print(f"MODULE_REPO[{key}]={shlex.quote(module.repo)}")
|
||||||
|
print(f"MODULE_REF[{key}]={shlex.quote(ref)}")
|
||||||
|
print(f"MODULE_TYPE[{key}]={shlex.quote(module.module_type)}")
|
||||||
|
print(f"MODULE_ENABLED[{key}]={1 if module.enabled_effective else 0}")
|
||||||
|
print(f"MODULE_NEEDS_BUILD[{key}]={1 if module.needs_build else 0}")
|
||||||
|
print(f"MODULE_BLOCKED[{key}]={1 if module.blocked else 0}")
|
||||||
|
print(f"MODULE_POST_INSTALL[{key}]={shlex.quote(post_install)}")
|
||||||
|
print(f"MODULE_REQUIRES[{key}]={shlex.quote(dependencies)}")
|
||||||
|
print(f"MODULE_CONFIG_CLEANUP[{key}]={shlex.quote(config_cleanup)}")
|
||||||
|
print(f"MODULE_NOTES[{key}]={shlex.quote(notes)}")
|
||||||
|
print(f"MODULE_STATUS[{key}]={shlex.quote(module.status)}")
|
||||||
|
print(f"MODULE_BLOCK_REASON[{key}]={shlex.quote(block_reason)}")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported format: {fmt}")
|
||||||
|
|
||||||
|
|
||||||
|
def handle_generate(args: argparse.Namespace) -> int:
|
||||||
|
env_path = Path(args.env_path).resolve()
|
||||||
|
manifest_path = Path(args.manifest).resolve()
|
||||||
|
output_dir = Path(args.output_dir).resolve()
|
||||||
|
state = build_state(env_path, manifest_path)
|
||||||
|
write_outputs(state, output_dir)
|
||||||
|
|
||||||
|
if state.warnings:
|
||||||
|
warning_block = "\n".join(f"- {warning}" for warning in state.warnings)
|
||||||
|
print(
|
||||||
|
textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
⚠️ Module manifest warnings detected:
|
||||||
|
{warning_block}
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
if state.errors:
|
||||||
|
error_block = "\n".join(f"- {error}" for error in state.errors)
|
||||||
|
print(
|
||||||
|
textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
❌ Module manifest errors detected:
|
||||||
|
{error_block}
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def configure_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(description="Module manifest helper")
|
||||||
|
parser.add_argument(
|
||||||
|
"--env-path",
|
||||||
|
default=".env",
|
||||||
|
help="Path to .env file (default: .env)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--manifest",
|
||||||
|
default="config/modules.json",
|
||||||
|
help="Path to module manifest (default: config/modules.json)",
|
||||||
|
)
|
||||||
|
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
generate_parser = subparsers.add_parser("generate", help="Generate module state files")
|
||||||
|
generate_parser.add_argument(
|
||||||
|
"--output-dir",
|
||||||
|
default="local-storage/modules",
|
||||||
|
help="Directory for generated module artifacts (default: local-storage/modules)",
|
||||||
|
)
|
||||||
|
generate_parser.set_defaults(func=handle_generate)
|
||||||
|
|
||||||
|
list_parser = subparsers.add_parser("list", help="Print module lists")
|
||||||
|
list_parser.add_argument(
|
||||||
|
"--type",
|
||||||
|
choices=["compile", "enabled", "keys"],
|
||||||
|
default="compile",
|
||||||
|
help="List selector (default: compile)",
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_list(args: argparse.Namespace) -> int:
|
||||||
|
state = build_state(Path(args.env_path).resolve(), Path(args.manifest).resolve())
|
||||||
|
print_list(state, args.type)
|
||||||
|
return 1 if state.errors else 0
|
||||||
|
|
||||||
|
list_parser.set_defaults(func=handle_list)
|
||||||
|
|
||||||
|
rps_parser = subparsers.add_parser(
|
||||||
|
"requires-playerbot", help="Print 1 if playerbot source is required else 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_requires_playerbot(args: argparse.Namespace) -> int:
|
||||||
|
state = build_state(Path(args.env_path).resolve(), Path(args.manifest).resolve())
|
||||||
|
print_requires_playerbot(state)
|
||||||
|
return 1 if state.errors else 0
|
||||||
|
|
||||||
|
rps_parser.set_defaults(func=handle_requires_playerbot)
|
||||||
|
|
||||||
|
dump_parser = subparsers.add_parser("dump", help="Dump module state (JSON format)")
|
||||||
|
dump_parser.add_argument(
|
||||||
|
"--format",
|
||||||
|
choices=["json", "shell"],
|
||||||
|
default="json",
|
||||||
|
help="Output format (default: json)",
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_dump(args: argparse.Namespace) -> int:
|
||||||
|
state = build_state(Path(args.env_path).resolve(), Path(args.manifest).resolve())
|
||||||
|
print_state(state, args.format)
|
||||||
|
return 1 if state.errors else 0
|
||||||
|
|
||||||
|
dump_parser.set_defaults(func=handle_dump)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: Optional[Iterable[str]] = None) -> int:
|
||||||
|
parser = configure_parser()
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
return args.func(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -84,29 +84,50 @@ ASSUME_YES=0
|
|||||||
SOURCE_OVERRIDE=""
|
SOURCE_OVERRIDE=""
|
||||||
SKIP_STOP=0
|
SKIP_STOP=0
|
||||||
|
|
||||||
COMPILE_MODULE_KEYS=(
|
MODULE_HELPER="$PROJECT_DIR/scripts/modules.py"
|
||||||
MODULE_AOE_LOOT MODULE_LEARN_SPELLS MODULE_FIREWORKS MODULE_INDIVIDUAL_PROGRESSION MODULE_AHBOT MODULE_AUTOBALANCE
|
MODULE_STATE_DIR=""
|
||||||
MODULE_TRANSMOG MODULE_NPC_BUFFER MODULE_DYNAMIC_XP MODULE_SOLO_LFG MODULE_1V1_ARENA MODULE_PHASED_DUELS
|
declare -a MODULES_COMPILE_LIST=()
|
||||||
MODULE_BREAKING_NEWS MODULE_BOSS_ANNOUNCER MODULE_ACCOUNT_ACHIEVEMENTS MODULE_AUTO_REVIVE MODULE_GAIN_HONOR_GUARD
|
|
||||||
MODULE_ELUNA MODULE_TIME_IS_TIME MODULE_POCKET_PORTAL MODULE_RANDOM_ENCHANTS MODULE_SOLOCRAFT MODULE_PVP_TITLES MODULE_NPC_BEASTMASTER
|
resolve_local_storage_path(){
|
||||||
MODULE_NPC_ENCHANTER MODULE_INSTANCE_RESET MODULE_LEVEL_GRANT MODULE_ARAC MODULE_ASSISTANT MODULE_REAGENT_BANK
|
local path
|
||||||
MODULE_BLACK_MARKET_AUCTION_HOUSE MODULE_CHALLENGE_MODES MODULE_OLLAMA_CHAT MODULE_PLAYER_BOT_LEVEL_BRACKETS MODULE_STATBOOSTER MODULE_DUNGEON_RESPAWN
|
path="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
||||||
MODULE_SKELETON_MODULE MODULE_BG_SLAVERYVALLEY MODULE_AZEROTHSHARD MODULE_WORGOBLIN MODULE_ELUNA_TS
|
if [[ "$path" != /* ]]; then
|
||||||
)
|
path="${path#./}"
|
||||||
|
path="$PROJECT_DIR/$path"
|
||||||
|
fi
|
||||||
|
echo "${path%/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_module_state(){
|
||||||
|
if [ -n "$MODULE_STATE_DIR" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
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
|
||||||
|
echo "❌ Module manifest validation failed. See details above."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$MODULE_STATE_DIR/modules.env" ]; then
|
||||||
|
echo "❌ modules.env not produced at $MODULE_STATE_DIR/modules.env"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$MODULE_STATE_DIR/modules.env"
|
||||||
|
IFS=' ' read -r -a MODULES_COMPILE_LIST <<< "${MODULES_COMPILE:-}"
|
||||||
|
if [ "${#MODULES_COMPILE_LIST[@]}" -eq 1 ] && [ -z "${MODULES_COMPILE_LIST[0]}" ]; then
|
||||||
|
MODULES_COMPILE_LIST=()
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
modules_require_playerbot_source(){
|
modules_require_playerbot_source(){
|
||||||
if [ "$(read_env MODULE_PLAYERBOTS "0")" = "1" ]; then
|
ensure_module_state
|
||||||
|
if [ "${MODULES_REQUIRES_PLAYERBOT_SOURCE:-0}" = "1" ]; then
|
||||||
echo 1
|
echo 1
|
||||||
return
|
else
|
||||||
fi
|
|
||||||
local key
|
|
||||||
for key in "${COMPILE_MODULE_KEYS[@]}"; do
|
|
||||||
if [ "$(read_env "$key" "0")" = "1" ]; then
|
|
||||||
echo 1
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo 0
|
echo 0
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
@@ -124,6 +145,11 @@ if ! command -v docker >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
echo "❌ python3 not found in PATH."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
STORAGE_PATH="$(read_env STORAGE_PATH "./storage")"
|
STORAGE_PATH="$(read_env STORAGE_PATH "./storage")"
|
||||||
if [[ "$STORAGE_PATH" != /* ]]; then
|
if [[ "$STORAGE_PATH" != /* ]]; then
|
||||||
STORAGE_PATH="$PROJECT_DIR/${STORAGE_PATH#./}"
|
STORAGE_PATH="$PROJECT_DIR/${STORAGE_PATH#./}"
|
||||||
@@ -191,65 +217,16 @@ if [ ! -f "$SOURCE_COMPOSE" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
declare -A MODULE_REPO_MAP=(
|
ensure_module_state
|
||||||
[MODULE_AOE_LOOT]=mod-aoe-loot
|
|
||||||
[MODULE_LEARN_SPELLS]=mod-learn-spells
|
|
||||||
[MODULE_FIREWORKS]=mod-fireworks-on-level
|
|
||||||
[MODULE_INDIVIDUAL_PROGRESSION]=mod-individual-progression
|
|
||||||
[MODULE_AHBOT]=mod-ahbot
|
|
||||||
[MODULE_AUTOBALANCE]=mod-autobalance
|
|
||||||
[MODULE_TRANSMOG]=mod-transmog
|
|
||||||
[MODULE_NPC_BUFFER]=mod-npc-buffer
|
|
||||||
[MODULE_DYNAMIC_XP]=mod-dynamic-xp
|
|
||||||
[MODULE_SOLO_LFG]=mod-solo-lfg
|
|
||||||
[MODULE_1V1_ARENA]=mod-1v1-arena
|
|
||||||
[MODULE_PHASED_DUELS]=mod-phased-duels
|
|
||||||
[MODULE_BREAKING_NEWS]=mod-breaking-news-override
|
|
||||||
[MODULE_BOSS_ANNOUNCER]=mod-boss-announcer
|
|
||||||
[MODULE_ACCOUNT_ACHIEVEMENTS]=mod-account-achievements
|
|
||||||
[MODULE_AUTO_REVIVE]=mod-auto-revive
|
|
||||||
[MODULE_GAIN_HONOR_GUARD]=mod-gain-honor-guard
|
|
||||||
[MODULE_ELUNA]=mod-ale
|
|
||||||
[MODULE_TIME_IS_TIME]=mod-TimeIsTime
|
|
||||||
[MODULE_POCKET_PORTAL]=mod-pocket-portal
|
|
||||||
[MODULE_RANDOM_ENCHANTS]=mod-random-enchants
|
|
||||||
[MODULE_SOLOCRAFT]=mod-solocraft
|
|
||||||
[MODULE_PVP_TITLES]=mod-pvp-titles
|
|
||||||
[MODULE_NPC_BEASTMASTER]=mod-npc-beastmaster
|
|
||||||
[MODULE_NPC_ENCHANTER]=mod-npc-enchanter
|
|
||||||
[MODULE_INSTANCE_RESET]=mod-instance-reset
|
|
||||||
[MODULE_LEVEL_GRANT]=mod-quest-count-level
|
|
||||||
[MODULE_ARAC]=mod-arac
|
|
||||||
[MODULE_ASSISTANT]=mod-assistant
|
|
||||||
[MODULE_REAGENT_BANK]=mod-reagent-bank
|
|
||||||
[MODULE_BLACK_MARKET_AUCTION_HOUSE]=mod-black-market
|
|
||||||
[MODULE_CHALLENGE_MODES]=mod-challenge-modes
|
|
||||||
[MODULE_OLLAMA_CHAT]=mod-ollama-chat
|
|
||||||
[MODULE_PLAYER_BOT_LEVEL_BRACKETS]=mod-player-bot-level-brackets
|
|
||||||
[MODULE_STATBOOSTER]=StatBooster
|
|
||||||
[MODULE_DUNGEON_RESPAWN]=DungeonRespawn
|
|
||||||
[MODULE_SKELETON_MODULE]=skeleton-module
|
|
||||||
[MODULE_BG_SLAVERYVALLEY]=mod-bg-slaveryvalley
|
|
||||||
[MODULE_AZEROTHSHARD]=mod-azerothshard
|
|
||||||
[MODULE_WORGOBLIN]=mod-worgoblin
|
|
||||||
[MODULE_ELUNA_TS]=eluna-ts
|
|
||||||
)
|
|
||||||
|
|
||||||
compile_modules=()
|
if [ ${#MODULES_COMPILE_LIST[@]} -eq 0 ]; then
|
||||||
for key in "${!MODULE_REPO_MAP[@]}"; do
|
|
||||||
if [ "$(read_env "$key" "0")" = "1" ]; then
|
|
||||||
compile_modules+=("${MODULE_REPO_MAP[$key]}")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ${#compile_modules[@]} -eq 0 ]; then
|
|
||||||
echo "✅ No C++ modules enabled that require a source rebuild."
|
echo "✅ No C++ modules enabled that require a source rebuild."
|
||||||
rm -f "$SENTINEL_FILE" 2>/dev/null || true
|
rm -f "$SENTINEL_FILE" 2>/dev/null || true
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "🔧 Modules requiring compilation:"
|
echo "🔧 Modules requiring compilation:"
|
||||||
for mod in "${compile_modules[@]}"; do
|
for mod in "${MODULES_COMPILE_LIST[@]}"; do
|
||||||
echo " • $mod"
|
echo " • $mod"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ WHERE guid IN (
|
|||||||
AND c.deleteInfos_Account IS NULL
|
AND c.deleteInfos_Account IS NULL
|
||||||
AND c.name IN (
|
AND c.name IN (
|
||||||
SELECT p.name
|
SELECT p.name
|
||||||
FROM playerbots p
|
FROM `{{PLAYERBOTS_DB}}`.playerbots p
|
||||||
WHERE p.bot = 1
|
WHERE p.bot = 1
|
||||||
)
|
)
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
|
|||||||
@@ -97,6 +97,37 @@ read_env(){
|
|||||||
echo "$value"
|
echo "$value"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canonical_path(){
|
||||||
|
local path="$1"
|
||||||
|
if command -v realpath >/dev/null 2>&1; then
|
||||||
|
realpath -m "$path"
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 - "$path" <<'PY'
|
||||||
|
import os, sys
|
||||||
|
print(os.path.normpath(sys.argv[1]))
|
||||||
|
PY
|
||||||
|
else
|
||||||
|
local normalized="$path"
|
||||||
|
# Strip leading "./" portions so relative paths are clean
|
||||||
|
while [[ "$normalized" == ./* ]]; do
|
||||||
|
normalized="${normalized:2}"
|
||||||
|
done
|
||||||
|
# Collapse any embedded "/./" segments that appear in absolute paths
|
||||||
|
while [[ "$normalized" == *"/./"* ]]; do
|
||||||
|
normalized="${normalized//\/\.\//\/}"
|
||||||
|
done
|
||||||
|
# Replace duplicate slashes with a single slash for readability
|
||||||
|
while [[ "$normalized" == *"//"* ]]; do
|
||||||
|
normalized="${normalized//\/\//\/}"
|
||||||
|
done
|
||||||
|
# Preserve absolute path prefix if original started with '/'
|
||||||
|
if [[ "$path" == /* && "$normalized" != /* ]]; then
|
||||||
|
normalized="/${normalized}"
|
||||||
|
fi
|
||||||
|
echo "$normalized"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
confirm(){
|
confirm(){
|
||||||
local prompt="$1" default="$2" reply
|
local prompt="$1" default="$2" reply
|
||||||
if [ "$ASSUME_YES" = "1" ]; then
|
if [ "$ASSUME_YES" = "1" ]; then
|
||||||
@@ -141,15 +172,15 @@ STORAGE_PATH="$(read_env STORAGE_PATH "./storage")"
|
|||||||
if [[ "$STORAGE_PATH" != /* ]]; then
|
if [[ "$STORAGE_PATH" != /* ]]; then
|
||||||
STORAGE_PATH="$PROJECT_DIR/$STORAGE_PATH"
|
STORAGE_PATH="$PROJECT_DIR/$STORAGE_PATH"
|
||||||
fi
|
fi
|
||||||
|
STORAGE_PATH="$(canonical_path "$STORAGE_PATH")"
|
||||||
MODULES_DIR="$STORAGE_PATH/modules"
|
MODULES_DIR="$STORAGE_PATH/modules"
|
||||||
|
|
||||||
# Build sentinel is in local storage, deployment modules are in shared storage
|
# Build sentinel is in local storage, deployment modules are in shared storage
|
||||||
LOCAL_STORAGE_PATH="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
LOCAL_STORAGE_PATH="$(read_env STORAGE_PATH_LOCAL "./local-storage")"
|
||||||
if [[ "$LOCAL_STORAGE_PATH" != /* ]]; then
|
if [[ "$LOCAL_STORAGE_PATH" != /* ]]; then
|
||||||
# Remove leading ./ if present
|
|
||||||
LOCAL_STORAGE_PATH="${LOCAL_STORAGE_PATH#./}"
|
|
||||||
LOCAL_STORAGE_PATH="$PROJECT_DIR/$LOCAL_STORAGE_PATH"
|
LOCAL_STORAGE_PATH="$PROJECT_DIR/$LOCAL_STORAGE_PATH"
|
||||||
fi
|
fi
|
||||||
|
LOCAL_STORAGE_PATH="$(canonical_path "$LOCAL_STORAGE_PATH")"
|
||||||
SENTINEL_FILE="$LOCAL_STORAGE_PATH/modules/.requires_rebuild"
|
SENTINEL_FILE="$LOCAL_STORAGE_PATH/modules/.requires_rebuild"
|
||||||
|
|
||||||
# Define module mappings (from rebuild-with-modules.sh)
|
# Define module mappings (from rebuild-with-modules.sh)
|
||||||
|
|||||||
@@ -1,10 +1,39 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Thin wrapper to bring the AzerothCore stack online without triggering rebuilds.
|
# Thin wrapper to bring the AzerothCore stack online without triggering rebuilds.
|
||||||
# Reuses deploy.sh so all profile detection and tagging logic stay consistent.
|
# Picks the right profile automatically (standard/playerbots/modules) and delegates
|
||||||
|
# to deploy.sh so all staging/health logic stays consistent.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
exec "${SCRIPT_DIR}/deploy.sh" --skip-rebuild --yes --no-watch
|
PROFILE="$(python3 - <<'PY' "$ROOT_DIR"
|
||||||
|
import json, subprocess, sys
|
||||||
|
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"
|
||||||
|
|
||||||
|
state = json.loads(subprocess.check_output([
|
||||||
|
sys.executable,
|
||||||
|
str(modules_py),
|
||||||
|
"--env-path", str(env_path),
|
||||||
|
"--manifest", str(manifest_path),
|
||||||
|
"dump", "--format", "json",
|
||||||
|
]))
|
||||||
|
|
||||||
|
enabled = [m for m in state["modules"] if m["enabled"]]
|
||||||
|
profile = "standard"
|
||||||
|
if any(m["key"] == "MODULE_PLAYERBOTS" and m["enabled"] for m in enabled):
|
||||||
|
profile = "playerbots"
|
||||||
|
elif any(m["needs_build"] and m["enabled"] for m in enabled):
|
||||||
|
profile = "modules"
|
||||||
|
|
||||||
|
print(profile)
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
exec "${ROOT_DIR}/deploy.sh" --profile "$PROFILE" --yes --no-watch
|
||||||
|
|||||||
Reference in New Issue
Block a user