diff --git a/.env.template b/.env.template index d51a498..d98e1d8 100644 --- a/.env.template +++ b/.env.template @@ -52,6 +52,13 @@ AC_WORLDSERVER_IMAGE=acore/ac-wotlk-worldserver:14.0.0-dev AC_AUTHSERVER_IMAGE_PLAYERBOTS=uprightbass360/azerothcore-wotlk-playerbots:authserver-Playerbot AC_WORLDSERVER_IMAGE_PLAYERBOTS=uprightbass360/azerothcore-wotlk-playerbots:worldserver-Playerbot +# ===================== +# Services (Module Build Tags) +# ===================== +# Images used during module compilation and tagging +AC_AUTHSERVER_IMAGE_MODULES=uprightbass360/azerothcore-wotlk-playerbots:authserver-modules-latest +AC_WORLDSERVER_IMAGE_MODULES=uprightbass360/azerothcore-wotlk-playerbots:worldserver-modules-latest + # ===================== # Client Data # ===================== diff --git a/README.md b/README.md index 01fdb98..c5e58bb 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ cd acore-compose ./deploy.sh ``` +> ℹ️ **Image Sources:** Vanilla/standard profiles run the upstream `acore/*` images. As soon as you enable playerbots or any C++ module, the toolchain switches to the `uprightbass360/azerothcore-wotlk-playerbots` fork, rebuilds it locally when needed, and produces fresh `uprightbass360/...:modules-latest` tags. + **4. Create Admin Account** Once the worldserver is running: @@ -81,7 +83,7 @@ set realmlist 203.0.113.100 8215 ### ✅ Core Server Components - **AzerothCore 3.3.5a** - WotLK server application - **MySQL 8.0** - Database with intelligent initialization and restoration -- **Smart Module System** - Automated module management and source builds +- **Smart Module System** - Automated module management and source builds (compiles the uprightbass360 playerbot fork whenever modules need C++ changes) - **phpMyAdmin** - Web-based database administration - **Keira3** - Game content editor and developer tools @@ -207,7 +209,7 @@ Use this workflow to build locally, then push the same stack to a remote host: --host docker-server \ --project-dir /home/sam/src/acore-compose ``` - Adjust `--project-dir` (and `--identity`) to match your environment. The script copies the repo, `storage/`, and the `modules-latest` images to the remote machine. + Adjust `--project-dir` (and `--identity`) to match your environment. The script copies the repo, `storage/`, and the `uprightbass360/...:modules-latest` images to the remote machine. 3. **Deploy Remotely** ```bash @@ -216,7 +218,7 @@ Use this workflow to build locally, then push the same stack to a remote host: ./deploy.sh --skip-rebuild --no-watch ' ``` - Because the `.env` now points the playerbot services at the `modules-latest` tags, the remote compose run uses the build you just migrated—no additional rebuild required. + Because the `.env` now points the modules profile at the `uprightbass360/...:modules-latest` tags, the remote compose run uses the build you just migrated—no additional rebuild required. 4. **Verify** ```bash @@ -237,7 +239,7 @@ Use this workflow to build locally, then push the same stack to a remote host: --user sam \ --project-dir /home/sam/src/acore-compose ``` - (Exports rebuilt images to `local-storage/images/acore-modules-images.tar`, including both `acore/...:modules-latest` and `uprightbass360/...:Playerbot` tags, then syncs `storage/` unless `--skip-storage` is provided.) + (Exports rebuilt images to `local-storage/images/acore-modules-images.tar`, bundling the `uprightbass360/...:modules-latest` and `uprightbass360/...:Playerbot` tags, then syncs `storage/` unless `--skip-storage` is provided.) 3. **Deploy on Remote Host** ```bash ssh docker-server ' @@ -378,9 +380,9 @@ http://YOUR_SERVER_IP:4201 ./deploy.sh --profile modules # Custom modules build # Module staging and compilation -./scripts/stage-modules.sh # Download and stage enabled modules -./scripts/rebuild-with-modules.sh --yes # Force source rebuild with modules -./scripts/setup-source.sh # Initialize/update source repositories +./scripts/stage-modules.sh # Download and stage enabled modules (preps upright playerbot builds) +./scripts/rebuild-with-modules.sh --yes # Rebuild uprightbass360/playerbot images with your modules +./scripts/setup-source.sh # Initialize/update source repositories (auto-switches to playerbot fork for modules) # Module configuration management ./scripts/copy-module-configs.sh # Create module .conf files diff --git a/deploy.sh b/deploy.sh index 0ca6647..7c84d90 100755 --- a/deploy.sh +++ b/deploy.sh @@ -18,6 +18,16 @@ SKIP_REBUILD=0 WORLD_LOG_SINCE="" ASSUME_YES=0 +COMPILE_MODULE_VARS=( + 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_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' info(){ printf '%b\n' "${BLUE}ℹ️ $*${NC}"; } ok(){ printf '%b\n' "${GREEN}✅ $*${NC}"; } @@ -50,11 +60,18 @@ Options: --no-watch Do not tail worldserver logs after staging --keep-running Do not pre-stop runtime stack before rebuild --skip-rebuild Skip source rebuild even if modules require it - --yes, -y Auto-confirm the deployment prompt + --yes, -y Auto-confirm deployment and rebuild prompts -h, --help Show this help -This command automates the module workflow (sync modules, rebuild source if needed, -stage the correct compose profile, and optionally watch worldserver logs). +This command automates the module workflow: sync modules, rebuild source if needed, +stage the correct compose profile, and optionally watch worldserver logs. + +Rebuild Detection: +The script automatically detects when a module rebuild is required by checking: +• Module changes (sentinel file .requires_rebuild) +• C++ modules enabled but modules-latest Docker images missing + +Set AUTO_REBUILD_ON_DEPLOY=1 in .env to skip rebuild prompts and auto-rebuild. EOF } @@ -109,14 +126,16 @@ compose(){ } ensure_source_repo(){ - local module_playerbots - module_playerbots="$(read_env MODULE_PLAYERBOTS "0")" + local use_playerbot_source=0 + if requires_playerbot_source; then + use_playerbot_source=1 + fi local local_root local_root="$(read_env STORAGE_PATH_LOCAL "./local-storage")" local_root="${local_root%/}" [ -z "$local_root" ] && local_root="." local default_source="${local_root}/source/azerothcore" - if [ "$module_playerbots" = "1" ]; then + if [ "$use_playerbot_source" = "1" ]; then default_source="${local_root}/source/azerothcore-playerbots" fi @@ -163,6 +182,114 @@ modules_need_rebuild(){ [[ -f "$sentinel" ]] } +check_auto_rebuild_setting(){ + local auto_rebuild + auto_rebuild="$(read_env AUTO_REBUILD_ON_DEPLOY "0")" + [[ "$auto_rebuild" = "1" ]] +} + +detect_rebuild_reasons(){ + local reasons=() + + # Check sentinel file + if modules_need_rebuild; then + reasons+=("Module changes detected (sentinel file present)") + fi + + # Check if any C++ modules are enabled but modules-latest images don't exist + local any_cxx_modules=0 + local var + for var in "${COMPILE_MODULE_VARS[@]}"; do + if [ "$(read_env "$var" "0")" = "1" ]; then + any_cxx_modules=1 + break + fi + done + + if [ "$any_cxx_modules" = "1" ]; then + local authserver_modules_image + local worldserver_modules_image + authserver_modules_image="$(read_env AC_AUTHSERVER_IMAGE_MODULES "uprightbass360/azerothcore-wotlk-playerbots:authserver-modules-latest")" + worldserver_modules_image="$(read_env AC_WORLDSERVER_IMAGE_MODULES "uprightbass360/azerothcore-wotlk-playerbots:worldserver-modules-latest")" + + if ! docker image inspect "$authserver_modules_image" >/dev/null 2>&1; then + reasons+=("C++ modules enabled but authserver modules image $authserver_modules_image is missing") + fi + if ! docker image inspect "$worldserver_modules_image" >/dev/null 2>&1; then + reasons+=("C++ modules enabled but worldserver modules image $worldserver_modules_image is missing") + fi + fi + + printf '%s\n' "${reasons[@]}" +} + +requires_playerbot_source(){ + if [ "$(read_env MODULE_PLAYERBOTS "0")" = "1" ]; then + return 0 + fi + local var + for var in "${COMPILE_MODULE_VARS[@]}"; do + if [ "$(read_env "$var" "0")" = "1" ]; then + return 0 + fi + done + return 1 +} + +confirm_rebuild(){ + local reasons=("$@") + + if [ ${#reasons[@]} -eq 0 ]; then + return 1 # No rebuild needed + fi + + echo + warn "Module rebuild appears to be required:" + local reason + for reason in "${reasons[@]}"; do + warn " • $reason" + done + echo + + # Check auto-rebuild setting + if check_auto_rebuild_setting; then + info "AUTO_REBUILD_ON_DEPLOY is enabled; proceeding with automatic rebuild." + return 0 + fi + + # Skip prompt if --yes flag is provided + if [ "$ASSUME_YES" -eq 1 ]; then + info "Auto-confirming rebuild (--yes supplied)." + return 0 + fi + + # Interactive prompt + info "This will rebuild AzerothCore from source with your enabled modules." + warn "⏱️ This process typically takes 15-45 minutes depending on your system." + echo + if [ -t 0 ]; then + local reply + read -r -p "Proceed with module rebuild? [y/N]: " reply + reply="${reply:-n}" + case "$reply" in + [Yy]*) + info "Rebuild confirmed." + return 0 + ;; + *) + warn "Rebuild declined. You can:" + warn " • Run with --skip-rebuild to deploy without rebuilding" + warn " • Set AUTO_REBUILD_ON_DEPLOY=1 in .env for automatic rebuilds" + warn " • Run './scripts/rebuild-with-modules.sh' manually later" + return 1 + ;; + esac + else + warn "Standard input is not interactive; use --yes to auto-confirm or --skip-rebuild to skip." + return 1 + fi +} + determine_profile(){ if [ -n "$TARGET_PROFILE" ]; then echo "$TARGET_PROFILE" @@ -178,49 +305,8 @@ determine_profile(){ return fi - local compile_vars=( - 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_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 - ) - local var - for var in "${compile_vars[@]}"; do + for var in "${COMPILE_MODULE_VARS[@]}"; do if [ "$(read_env "$var" "0")" = "1" ]; then echo "modules" return @@ -253,53 +339,29 @@ rebuild_source(){ } tag_module_images(){ - local source_world="acore/ac-wotlk-worldserver:master" - local source_auth="acore/ac-wotlk-authserver:master" + local source_auth + local source_world + local target_auth + local target_world - local targets_world=() - local targets_auth=() + source_auth="$(read_env AC_AUTHSERVER_IMAGE_PLAYERBOTS "uprightbass360/azerothcore-wotlk-playerbots:authserver-Playerbot")" + source_world="$(read_env AC_WORLDSERVER_IMAGE_PLAYERBOTS "uprightbass360/azerothcore-wotlk-playerbots:worldserver-Playerbot")" + target_auth="$(read_env AC_AUTHSERVER_IMAGE_MODULES "uprightbass360/azerothcore-wotlk-playerbots:authserver-modules-latest")" + target_world="$(read_env AC_WORLDSERVER_IMAGE_MODULES "uprightbass360/azerothcore-wotlk-playerbots:worldserver-modules-latest")" - targets_world+=("$(read_env AC_WORLDSERVER_IMAGE_MODULES "acore/ac-wotlk-worldserver:modules-latest")") - targets_world+=("$(read_env AC_WORLDSERVER_IMAGE_PLAYERBOTS "uprightbass360/azerothcore-wotlk-playerbots:worldserver-Playerbot")") - targets_auth+=("$(read_env AC_AUTHSERVER_IMAGE_MODULES "acore/ac-wotlk-authserver:modules-latest")") - targets_auth+=("$(read_env AC_AUTHSERVER_IMAGE_PLAYERBOTS "uprightbass360/azerothcore-wotlk-playerbots:authserver-Playerbot")") + if docker image inspect "$source_auth" >/dev/null 2>&1; then + docker tag "$source_auth" "$target_auth" + ok "Tagged $target_auth from $source_auth" + else + warn "Source authserver image $source_auth not found; skipping modules tag" + fi - local tagged_world=() - local tagged_auth=() - - tag_image(){ - local src="$1" target="$2" label="$3" - [[ -n "$target" ]] || return 0 - case "$label" in - world) for image in "${tagged_world[@]}"; do [[ "$image" == "$target" ]] && return 0; done ;; - auth) for image in "${tagged_auth[@]}"; do [[ "$image" == "$target" ]] && return 0; done ;; - esac - case "$target" in - *modules-latest*) - if docker image inspect "$src" >/dev/null 2>&1; then - docker tag "$src" "$target" - ok "Tagged $target from $src ($label)" - else - warn "Source image $src not found; skipping tag for $label" - fi - ;; - *) - info "Skipping tag for $label image $target (non modules-latest)" - return 0 - ;; - esac - case "$label" in - world) tagged_world+=("$target") ;; - auth) tagged_auth+=("$target") ;; - esac - } - - for target in "${targets_world[@]}"; do - tag_image "$source_world" "$target" world - done - for target in "${targets_auth[@]}"; do - tag_image "$source_auth" "$target" auth - done + if docker image inspect "$source_world" >/dev/null 2>&1; then + docker tag "$source_world" "$target_world" + ok "Tagged $target_world from $source_world" + else + warn "Source worldserver image $source_world not found; skipping modules tag" + fi } stage_runtime(){ @@ -396,13 +458,26 @@ main(){ sync_modules local did_rebuild=0 - if modules_need_rebuild; then + local rebuild_reasons + readarray -t rebuild_reasons < <(detect_rebuild_reasons) + + if [ ${#rebuild_reasons[@]} -gt 0 ]; then if [ "$SKIP_REBUILD" -eq 1 ]; then - warn "Modules require rebuild, but --skip-rebuild was provided." + warn "Modules require rebuild, but --skip-rebuild was provided:" + local reason + for reason in "${rebuild_reasons[@]}"; do + warn " • $reason" + done + warn "Proceeding without rebuild; deployment may fail if modules-latest images are missing." else - show_step 4 5 "Building realm with modules (this may take 15-45 minutes)" - rebuild_source "$src_dir" - did_rebuild=1 + if confirm_rebuild "${rebuild_reasons[@]}"; then + show_step 4 5 "Building realm with modules (this may take 15-45 minutes)" + rebuild_source "$src_dir" + did_rebuild=1 + else + err "Rebuild required but declined. Use --skip-rebuild to force deployment without rebuild." + exit 1 + fi fi else info "No module rebuild required." diff --git a/scripts/rebuild-with-modules.sh b/scripts/rebuild-with-modules.sh index 8219310..90e8c95 100755 --- a/scripts/rebuild-with-modules.sh +++ b/scripts/rebuild-with-modules.sh @@ -45,15 +45,15 @@ read_env(){ } default_source_path(){ - local module_playerbots - module_playerbots="$(read_env MODULE_PLAYERBOTS "0")" + local require_playerbot + require_playerbot="$(modules_require_playerbot_source)" local local_root local_root="$(read_env STORAGE_PATH_LOCAL "./local-storage")" local_root="${local_root%/}" if [[ -z "$local_root" ]]; then local_root="." fi - if [ "$module_playerbots" = "1" ]; then + if [ "$require_playerbot" = "1" ]; then echo "${local_root}/source/azerothcore-playerbots" else echo "${local_root}/source/azerothcore" @@ -84,6 +84,31 @@ ASSUME_YES=0 SOURCE_OVERRIDE="" SKIP_STOP=0 +COMPILE_MODULE_KEYS=( + 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_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 +) + +modules_require_playerbot_source(){ + if [ "$(read_env MODULE_PLAYERBOTS "0")" = "1" ]; then + echo 1 + return + fi + local key + for key in "${COMPILE_MODULE_KEYS[@]}"; do + if [ "$(read_env "$key" "0")" = "1" ]; then + echo 1 + return + fi + done + echo 0 +} + while [[ $# -gt 0 ]]; do case "$1" in --yes|-y) ASSUME_YES=1; shift;; @@ -246,13 +271,41 @@ echo "🚀 Building AzerothCore with modules..." docker compose build --no-cache echo "🔖 Tagging modules-latest images" -docker tag acore/ac-wotlk-worldserver:master acore/ac-wotlk-worldserver:modules-latest -docker tag acore/ac-wotlk-authserver:master acore/ac-wotlk-authserver:modules-latest -if [ "$(read_env MODULE_PLAYERBOTS "0")" = "1" ]; then - echo "🔁 Tagging playerbot images uprightbass360/azerothcore-wotlk-playerbots:*" - docker tag acore/ac-wotlk-worldserver:modules-latest uprightbass360/azerothcore-wotlk-playerbots:worldserver-Playerbot - docker tag acore/ac-wotlk-authserver:modules-latest uprightbass360/azerothcore-wotlk-playerbots:authserver-Playerbot +# Get image names and tags from .env.template +TEMPLATE_FILE="$PROJECT_DIR/.env.template" +get_template_value() { + local key="$1" + local fallback="$2" + if [ -f "$TEMPLATE_FILE" ]; then + local value + value=$(grep "^${key}=" "$TEMPLATE_FILE" | head -1 | cut -d'=' -f2- | sed 's/^"\(.*\)"$/\1/') + if [[ "$value" =~ ^\$\{[^}]*:-([^}]*)\}$ ]]; then + value="${BASH_REMATCH[1]}" + fi + [ -n "$value" ] && echo "$value" || echo "$fallback" + else + echo "$fallback" + fi +} + +TARGET_AUTHSERVER_IMAGE="$(read_env AC_AUTHSERVER_IMAGE_MODULES "$(get_template_value "AC_AUTHSERVER_IMAGE_MODULES")")" +TARGET_WORLDSERVER_IMAGE="$(read_env AC_WORLDSERVER_IMAGE_MODULES "$(get_template_value "AC_WORLDSERVER_IMAGE_MODULES")")" + +PLAYERBOTS_AUTHSERVER_IMAGE="$(read_env AC_AUTHSERVER_IMAGE_PLAYERBOTS "$(get_template_value "AC_AUTHSERVER_IMAGE_PLAYERBOTS")")" +PLAYERBOTS_WORLDSERVER_IMAGE="$(read_env AC_WORLDSERVER_IMAGE_PLAYERBOTS "$(get_template_value "AC_WORLDSERVER_IMAGE_PLAYERBOTS")")" + +echo "🔁 Tagging modules images from playerbot build artifacts" +if docker image inspect "$PLAYERBOTS_AUTHSERVER_IMAGE" >/dev/null 2>&1; then + docker tag "$PLAYERBOTS_AUTHSERVER_IMAGE" "$TARGET_AUTHSERVER_IMAGE" +else + echo "⚠️ Warning: $PLAYERBOTS_AUTHSERVER_IMAGE not found, skipping authserver tag" +fi + +if docker image inspect "$PLAYERBOTS_WORLDSERVER_IMAGE" >/dev/null 2>&1; then + docker tag "$PLAYERBOTS_WORLDSERVER_IMAGE" "$TARGET_WORLDSERVER_IMAGE" +else + echo "⚠️ Warning: $PLAYERBOTS_WORLDSERVER_IMAGE not found, skipping worldserver tag" fi show_rebuild_step 5 5 "Cleaning up build containers" diff --git a/scripts/setup-source.sh b/scripts/setup-source.sh index 6064a2e..eabf4e6 100755 --- a/scripts/setup-source.sh +++ b/scripts/setup-source.sh @@ -14,12 +14,32 @@ PROJECT_ROOT="$(pwd)" # Default values MODULE_PLAYERBOTS="${MODULE_PLAYERBOTS:-0}" +NEEDS_CXX_REBUILD="${NEEDS_CXX_REBUILD:-0}" + +COMPILE_MODULE_KEYS=( + 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_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 +) + +if [ "$NEEDS_CXX_REBUILD" != "1" ]; then + for key in "${COMPILE_MODULE_KEYS[@]}"; do + if [ "${!key:-0}" = "1" ]; then + NEEDS_CXX_REBUILD=1 + break + fi + done +fi LOCAL_STORAGE_ROOT="${STORAGE_PATH_LOCAL:-./local-storage}" DEFAULT_STANDARD_PATH="${LOCAL_STORAGE_ROOT%/}/source/azerothcore" DEFAULT_PLAYERBOTS_PATH="${LOCAL_STORAGE_ROOT%/}/source/azerothcore-playerbots" SOURCE_PATH_DEFAULT="$DEFAULT_STANDARD_PATH" -if [ "$MODULE_PLAYERBOTS" = "1" ]; then +if [ "$MODULE_PLAYERBOTS" = "1" ] || [ "$NEEDS_CXX_REBUILD" = "1" ]; then SOURCE_PATH_DEFAULT="$DEFAULT_PLAYERBOTS_PATH" fi SOURCE_PATH="${MODULES_REBUILD_SOURCE_PATH:-$SOURCE_PATH_DEFAULT}" @@ -53,7 +73,7 @@ ACORE_REPO_PLAYERBOTS="${ACORE_REPO_PLAYERBOTS:-https://github.com/uprightbass36 ACORE_BRANCH_PLAYERBOTS="${ACORE_BRANCH_PLAYERBOTS:-Playerbot}" # Repository and branch selection based on playerbots mode -if [ "$MODULE_PLAYERBOTS" = "1" ]; then +if [ "$MODULE_PLAYERBOTS" = "1" ] || [ "$NEEDS_CXX_REBUILD" = "1" ]; then REPO_URL="$ACORE_REPO_PLAYERBOTS" BRANCH="$ACORE_BRANCH_PLAYERBOTS" echo "📌 Playerbots mode: Using $REPO_URL, branch $BRANCH" diff --git a/scripts/stage-modules.sh b/scripts/stage-modules.sh index 98ae568..4265bfb 100755 --- a/scripts/stage-modules.sh +++ b/scripts/stage-modules.sh @@ -182,10 +182,11 @@ echo "🎯 Target profile: services-$TARGET_PROFILE" # Check if source rebuild is needed for modules profile REBUILD_NEEDED=0 +TARGET_WORLDSERVER_IMAGE_MODULES="$(read_env AC_WORLDSERVER_IMAGE_MODULES "uprightbass360/azerothcore-wotlk-playerbots:worldserver-modules-latest")" if [ "$TARGET_PROFILE" = "modules" ]; then # Check if source image exists - if ! docker image inspect "acore/ac-wotlk-worldserver:modules-latest" >/dev/null 2>&1; then - echo "📦 Custom worldserver image not found - rebuild needed" + if ! docker image inspect "$TARGET_WORLDSERVER_IMAGE_MODULES" >/dev/null 2>&1; then + echo "📦 Modules image $TARGET_WORLDSERVER_IMAGE_MODULES not found - rebuild needed" REBUILD_NEEDED=1 elif [ -f "$SENTINEL_FILE" ]; then echo "🔄 Modules changed since last build - rebuild needed" diff --git a/setup.sh b/setup.sh index 45f7f4f..e273d55 100755 --- a/setup.sh +++ b/setup.sh @@ -6,6 +6,147 @@ set -e # ============================================== # Mirrors options from scripts/setup-server.sh but targets ac-compose/.env +# Get script directory for template reading +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# ============================================== +# Constants (auto-loaded from .env.template) +# ============================================== + +# Function to read value from .env.template (required) +get_template_value() { + local key="$1" + local template_file="$SCRIPT_DIR/.env.template" + + if [ ! -f "$template_file" ]; then + echo "ERROR: .env.template file not found at $template_file" >&2 + echo "This file is required for setup.sh to function properly." >&2 + exit 1 + fi + + # Extract value, handling variable expansion syntax like ${VAR:-default} + local value + value=$(grep "^${key}=" "$template_file" | head -1 | cut -d'=' -f2- | sed 's/^"\(.*\)"$/\1/') + + # Handle ${VAR:-default} syntax by extracting the default value + if [[ "$value" =~ ^\$\{[^}]*:-([^}]*)\}$ ]]; then + value="${BASH_REMATCH[1]}" + fi + + if [ -z "$value" ]; then + echo "ERROR: Required key '$key' not found in .env.template" >&2 + exit 1 + fi + + echo "$value" +} + +# Load constants from .env.template (required) +readonly DEFAULT_MYSQL_PASSWORD="$(get_template_value "MYSQL_ROOT_PASSWORD")" +readonly DEFAULT_REALM_PORT="$(get_template_value "WORLD_EXTERNAL_PORT")" +readonly DEFAULT_AUTH_PORT="$(get_template_value "AUTH_EXTERNAL_PORT")" +readonly DEFAULT_SOAP_PORT="$(get_template_value "SOAP_EXTERNAL_PORT")" +readonly DEFAULT_MYSQL_PORT="$(get_template_value "MYSQL_EXTERNAL_PORT")" +readonly DEFAULT_PLAYERBOT_MAX="$(get_template_value "PLAYERBOT_MAX_BOTS")" +readonly DEFAULT_LOCAL_STORAGE="$(get_template_value "STORAGE_PATH")" + +# Permission schemes (hardcoded as not in template) +readonly PERMISSION_LOCAL_USER="0:0" +readonly PERMISSION_NFS_USER="1001:1000" +readonly DEFAULT_CUSTOM_UID="1000" +readonly DEFAULT_CUSTOM_GID="1000" + +# Static values +readonly DEFAULT_LOCAL_ADDRESS="127.0.0.1" +readonly DEFAULT_FALLBACK_LAN_IP="192.168.1.100" +readonly DEFAULT_DOMAIN_PLACEHOLDER="your-domain.com" +readonly DEFAULT_BACKUP_DAYS="3" +readonly DEFAULT_BACKUP_HOURS="6" +readonly DEFAULT_BACKUP_TIME="09" +readonly DEFAULT_NFS_STORAGE="/nfs/azerothcore" +readonly DEFAULT_MOUNT_STORAGE="/mnt/azerothcore-data" + +# Docker images (from .env.template) +readonly DEFAULT_MYSQL_IMAGE="$(get_template_value "MYSQL_IMAGE")" +readonly DEFAULT_AC_DB_IMPORT_IMAGE="$(get_template_value "AC_DB_IMPORT_IMAGE")" +readonly DEFAULT_AC_AUTHSERVER_IMAGE="$(get_template_value "AC_AUTHSERVER_IMAGE")" +readonly DEFAULT_AC_WORLDSERVER_IMAGE="$(get_template_value "AC_WORLDSERVER_IMAGE")" +readonly DEFAULT_AC_CLIENT_DATA_IMAGE="$(get_template_value "AC_CLIENT_DATA_IMAGE")" +readonly DEFAULT_AUTH_IMAGE_PLAYERBOTS="$(get_template_value "AC_AUTHSERVER_IMAGE_PLAYERBOTS")" +readonly DEFAULT_WORLD_IMAGE_PLAYERBOTS="$(get_template_value "AC_WORLDSERVER_IMAGE_PLAYERBOTS")" +readonly DEFAULT_CLIENT_DATA_IMAGE_PLAYERBOTS="$(get_template_value "AC_CLIENT_DATA_IMAGE_PLAYERBOTS")" +readonly DEFAULT_AUTH_IMAGE_MODULES="$(get_template_value "AC_AUTHSERVER_IMAGE_MODULES")" +readonly DEFAULT_WORLD_IMAGE_MODULES="$(get_template_value "AC_WORLDSERVER_IMAGE_MODULES")" + +# Database names +readonly DEFAULT_DB_AUTH_NAME="$(get_template_value "DB_AUTH_NAME")" +readonly DEFAULT_DB_WORLD_NAME="$(get_template_value "DB_WORLD_NAME")" +readonly DEFAULT_DB_CHARACTERS_NAME="$(get_template_value "DB_CHARACTERS_NAME")" +readonly DEFAULT_DB_PLAYERBOTS_NAME="$(get_template_value "DB_PLAYERBOTS_NAME")" + +# Container names +readonly DEFAULT_CONTAINER_MYSQL="$(get_template_value "CONTAINER_MYSQL")" +readonly DEFAULT_COMPOSE_PROJECT_NAME="$(get_template_value "COMPOSE_PROJECT_NAME")" +readonly DEFAULT_CLIENT_DATA_VOLUME="$(get_template_value "CLIENT_DATA_VOLUME")" + +# Version constants +readonly DEFAULT_CLIENT_DATA_VERSION="$(get_template_value "CLIENT_DATA_VERSION")" + +# Network configuration +readonly DEFAULT_NETWORK_NAME="$(get_template_value "NETWORK_NAME")" +readonly DEFAULT_NETWORK_SUBNET="$(get_template_value "NETWORK_SUBNET")" +readonly DEFAULT_NETWORK_GATEWAY="$(get_template_value "NETWORK_GATEWAY")" + +# MySQL configuration +readonly DEFAULT_MYSQL_CHARACTER_SET="$(get_template_value "MYSQL_CHARACTER_SET")" +readonly DEFAULT_MYSQL_COLLATION="$(get_template_value "MYSQL_COLLATION")" +readonly DEFAULT_MYSQL_MAX_CONNECTIONS="$(get_template_value "MYSQL_MAX_CONNECTIONS")" +readonly DEFAULT_MYSQL_INNODB_BUFFER_POOL_SIZE="$(get_template_value "MYSQL_INNODB_BUFFER_POOL_SIZE")" +readonly DEFAULT_MYSQL_INNODB_LOG_FILE_SIZE="$(get_template_value "MYSQL_INNODB_LOG_FILE_SIZE")" +readonly DEFAULT_MYSQL_INNODB_REDO_LOG_CAPACITY="$(get_template_value "MYSQL_INNODB_REDO_LOG_CAPACITY")" +readonly DEFAULT_MYSQL_RUNTIME_TMPFS_SIZE="$(get_template_value "MYSQL_RUNTIME_TMPFS_SIZE")" + +# Paths +readonly DEFAULT_HOST_ZONEINFO_PATH="$(get_template_value "HOST_ZONEINFO_PATH")" +readonly DEFAULT_ELUNA_SCRIPT_PATH="$(get_template_value "AC_ELUNA_SCRIPT_PATH")" + +# Tool configuration +readonly DEFAULT_PMA_EXTERNAL_PORT="$(get_template_value "PMA_EXTERNAL_PORT")" +readonly DEFAULT_PMA_UPLOAD_LIMIT="$(get_template_value "PMA_UPLOAD_LIMIT")" +readonly DEFAULT_PMA_MEMORY_LIMIT="$(get_template_value "PMA_MEMORY_LIMIT")" +readonly DEFAULT_PMA_MAX_EXECUTION_TIME="$(get_template_value "PMA_MAX_EXECUTION_TIME")" +readonly DEFAULT_KEIRA3_EXTERNAL_PORT="$(get_template_value "KEIRA3_EXTERNAL_PORT")" +readonly DEFAULT_PMA_USER="$(get_template_value "PMA_USER")" +readonly DEFAULT_PMA_ARBITRARY="$(get_template_value "PMA_ARBITRARY")" +readonly DEFAULT_PMA_ABSOLUTE_URI="$(get_template_value "PMA_ABSOLUTE_URI")" + +# Module preset names (not in template) +readonly DEFAULT_PRESET_SUGGESTED="suggested-modules" +readonly DEFAULT_PRESET_PLAYERBOTS="playerbots-suggested-modules" + +# Internal ports +readonly DEFAULT_AUTH_INTERNAL_PORT="$(get_template_value "AUTH_PORT")" +readonly DEFAULT_WORLD_INTERNAL_PORT="$(get_template_value "WORLD_PORT")" +readonly DEFAULT_SOAP_INTERNAL_PORT="$(get_template_value "SOAP_PORT")" +readonly DEFAULT_MYSQL_INTERNAL_PORT="$(get_template_value "MYSQL_PORT")" + +# System configuration +readonly DEFAULT_TZ="$(get_template_value "TZ")" +readonly DEFAULT_MYSQL_ROOT_HOST="$(get_template_value "MYSQL_ROOT_HOST")" +readonly DEFAULT_MYSQL_USER="$(get_template_value "MYSQL_USER")" + +# Eluna configuration +readonly DEFAULT_ELUNA_ENABLED="$(get_template_value "AC_ELUNA_ENABLED")" +readonly DEFAULT_ELUNA_TRACE_BACK="$(get_template_value "AC_ELUNA_TRACE_BACK")" +readonly DEFAULT_ELUNA_AUTO_RELOAD="$(get_template_value "AC_ELUNA_AUTO_RELOAD")" +readonly DEFAULT_ELUNA_BYTECODE_CACHE="$(get_template_value "AC_ELUNA_BYTECODE_CACHE")" +readonly DEFAULT_ELUNA_AUTO_RELOAD_INTERVAL="$(get_template_value "AC_ELUNA_AUTO_RELOAD_INTERVAL")" +readonly DEFAULT_ELUNA_REQUIRE_PATHS="$(get_template_value "AC_ELUNA_REQUIRE_PATHS")" +readonly DEFAULT_ELUNA_REQUIRE_CPATHS="$(get_template_value "AC_ELUNA_REQUIRE_CPATHS")" + +# Route detection IP (not in template) +readonly ROUTE_DETECTION_IP="1.1.1.1" + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; MAGENTA='\033[0;35m'; NC='\033[0m' say(){ local t=$1; shift; case "$t" in INFO) echo -e "${BLUE}ℹ️ $*${NC}";; @@ -19,8 +160,6 @@ validate_ip(){ [[ $1 =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; } validate_port(){ [[ $1 =~ ^[0-9]+$ ]] && [ $1 -ge 1 ] && [ $1 -le 65535 ]; } validate_number(){ [[ $1 =~ ^[0-9]+$ ]]; } -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - NON_INTERACTIVE=0 ask(){ @@ -92,62 +231,6 @@ normalize_module_name(){ declare -A MODULE_ENABLE_SET=() -declare -a COMPOSE_CMD=() - -resolve_compose_command(){ - if [ ${#COMPOSE_CMD[@]} -gt 0 ]; then - return 0 - fi - if command -v docker >/dev/null 2>&1; then - if docker compose version >/dev/null 2>&1; then - COMPOSE_CMD=(docker compose) - return 0 - fi - fi - if command -v docker-compose >/dev/null 2>&1; then - COMPOSE_CMD=(docker-compose) - return 0 - fi - COMPOSE_CMD=() - return 1 -} - -modules_directory_has_content(){ - local dir="$1" - [ -d "$dir" ] || return 1 - local first_entry - first_entry="$(find "$dir" -mindepth 1 -maxdepth 1 -type d -print -quit 2>/dev/null)" - [ -n "$first_entry" ] -} - -ensure_modules_staged(){ - local storage_abs="$1" needs_cxx="$2" run_now="$3" - if [ "$needs_cxx" != "1" ] || [ "$run_now" != "1" ]; then - return 0 - fi - - local modules_dir="${storage_abs}/modules" - local config_dir="${storage_abs}/config" - mkdir -p "$modules_dir" "$config_dir" - - if modules_directory_has_content "$modules_dir"; then - return 0 - fi - - if ! resolve_compose_command; then - say WARNING "Docker Compose not detected; skipping automatic module staging." - return 1 - fi - - say INFO "Staging module repositories via ${COMPOSE_CMD[*]} run --rm --no-deps ac-modules" - if ! "${COMPOSE_CMD[@]}" --profile modules run --rm --no-deps ac-modules; then - say WARNING "Module staging failed; repositories may be incomplete." - return 1 - fi - - return 0 -} - KNOWN_MODULE_VARS=( MODULE_PLAYERBOTS MODULE_AOE_LOOT @@ -221,11 +304,11 @@ apply_module_preset(){ done } -DEFAULT_PRESET_SUGGESTED="suggested-modules" -DEFAULT_PRESET_PLAYERBOTS="playerbots-suggested-modules" show_wow_header() { - clear + if [ -t 1 ] && command -v clear >/dev/null 2>&1; then + clear >/dev/null 2>&1 || true + fi echo -e "${RED}" cat <<'EOF' @@ -562,20 +645,20 @@ fi if [ -n "$CLI_SERVER_ADDRESS" ]; then SERVER_ADDRESS="$CLI_SERVER_ADDRESS" elif [ "$DEPLOYMENT_TYPE" = "local" ]; then - SERVER_ADDRESS=127.0.0.1 + SERVER_ADDRESS=$DEFAULT_LOCAL_ADDRESS elif [ "$DEPLOYMENT_TYPE" = "lan" ]; then local LAN_IP - LAN_IP=$(ip route get 1.1.1.1 2>/dev/null | awk 'NR==1{print $7}') - SERVER_ADDRESS=$(ask "Enter server IP address" "${CLI_SERVER_ADDRESS:-${LAN_IP:-192.168.1.100}}" validate_ip) + LAN_IP=$(ip route get $ROUTE_DETECTION_IP 2>/dev/null | awk 'NR==1{print $7}') + SERVER_ADDRESS=$(ask "Enter server IP address" "${CLI_SERVER_ADDRESS:-${LAN_IP:-$DEFAULT_FALLBACK_LAN_IP}}" validate_ip) else - SERVER_ADDRESS=$(ask "Enter server address (IP or domain)" "${CLI_SERVER_ADDRESS:-your-domain.com}" ) + SERVER_ADDRESS=$(ask "Enter server address (IP or domain)" "${CLI_SERVER_ADDRESS:-$DEFAULT_DOMAIN_PLACEHOLDER}" ) fi local REALM_PORT AUTH_EXTERNAL_PORT SOAP_EXTERNAL_PORT MYSQL_EXTERNAL_PORT - REALM_PORT=$(ask "Enter client connection port" "${CLI_REALM_PORT:-8215}" validate_port) - AUTH_EXTERNAL_PORT=$(ask "Enter auth server port" "${CLI_AUTH_PORT:-3784}" validate_port) - SOAP_EXTERNAL_PORT=$(ask "Enter SOAP API port" "${CLI_SOAP_PORT:-7778}" validate_port) - MYSQL_EXTERNAL_PORT=$(ask "Enter MySQL external port" "${CLI_MYSQL_PORT:-64306}" validate_port) + REALM_PORT=$(ask "Enter client connection port" "${CLI_REALM_PORT:-$DEFAULT_REALM_PORT}" validate_port) + AUTH_EXTERNAL_PORT=$(ask "Enter auth server port" "${CLI_AUTH_PORT:-$DEFAULT_AUTH_PORT}" validate_port) + SOAP_EXTERNAL_PORT=$(ask "Enter SOAP API port" "${CLI_SOAP_PORT:-$DEFAULT_SOAP_PORT}" validate_port) + MYSQL_EXTERNAL_PORT=$(ask "Enter MySQL external port" "${CLI_MYSQL_PORT:-$DEFAULT_MYSQL_PORT}" validate_port) # Permission scheme say HEADER "PERMISSION SCHEME" @@ -594,17 +677,17 @@ fi fi case "${PERMISSION_SCHEME_INPUT,,}" in 1|local) - CONTAINER_USER="0:0" + CONTAINER_USER="$PERMISSION_LOCAL_USER" PERMISSION_SCHEME_NAME="local" ;; 2|nfs) - CONTAINER_USER="1001:1000" + CONTAINER_USER="$PERMISSION_NFS_USER" PERMISSION_SCHEME_NAME="nfs" ;; 3|custom) local uid gid - uid="${CLI_CUSTOM_UID:-$(ask "Enter PUID (user id)" 1000 validate_number)}" - gid="${CLI_CUSTOM_GID:-$(ask "Enter PGID (group id)" 1000 validate_number)}" + uid="${CLI_CUSTOM_UID:-$(ask "Enter PUID (user id)" $DEFAULT_CUSTOM_UID validate_number)}" + gid="${CLI_CUSTOM_GID:-$(ask "Enter PGID (group id)" $DEFAULT_CUSTOM_GID validate_number)}" CONTAINER_USER="${uid}:${gid}" PERMISSION_SCHEME_NAME="custom" ;; @@ -625,7 +708,7 @@ fi fi # DB config say HEADER "DATABASE CONFIGURATION" - local MYSQL_ROOT_PASSWORD; MYSQL_ROOT_PASSWORD=$(ask "Enter MySQL root password" "${CLI_MYSQL_PASSWORD:-azerothcore123}") + local MYSQL_ROOT_PASSWORD; MYSQL_ROOT_PASSWORD=$(ask "Enter MySQL root password" "${CLI_MYSQL_PASSWORD:-$DEFAULT_MYSQL_PASSWORD}") # Storage say HEADER "STORAGE CONFIGURATION" @@ -633,10 +716,10 @@ fi if [ -n "$CLI_STORAGE_PATH" ]; then STORAGE_PATH="$CLI_STORAGE_PATH" elif [ "$DEPLOYMENT_TYPE" = "local" ]; then - STORAGE_PATH=./storage + STORAGE_PATH=$DEFAULT_LOCAL_STORAGE else if [ "$NON_INTERACTIVE" = "1" ]; then - STORAGE_PATH=/mnt/azerothcore-data + STORAGE_PATH=$DEFAULT_MOUNT_STORAGE else echo "1) 💾 ./storage (local)" echo "2) 🌐 /nfs/azerothcore (NFS)" @@ -644,9 +727,9 @@ fi while true; do read -p "$(echo -e "${YELLOW}🔧 Select storage option [1-3]: ${NC}")" s case "$s" in - 1) STORAGE_PATH=./storage; break;; - 2) STORAGE_PATH=/nfs/azerothcore; break;; - 3) STORAGE_PATH=$(ask "Enter custom storage path" "/mnt/azerothcore-data"); break;; + 1) STORAGE_PATH=$DEFAULT_LOCAL_STORAGE; break;; + 2) STORAGE_PATH=$DEFAULT_NFS_STORAGE; break;; + 3) STORAGE_PATH=$(ask "Enter custom storage path" "$DEFAULT_MOUNT_STORAGE"); break;; *) say ERROR "Please select 1, 2, or 3";; esac done @@ -656,11 +739,11 @@ fi # Backup say HEADER "BACKUP CONFIGURATION" local BACKUP_RETENTION_DAYS BACKUP_RETENTION_HOURS BACKUP_DAILY_TIME - BACKUP_RETENTION_DAYS=$(ask "Daily backups retention (days)" "${CLI_BACKUP_DAYS:-3}" validate_number) - BACKUP_RETENTION_HOURS=$(ask "Hourly backups retention (hours)" "${CLI_BACKUP_HOURS:-6}" validate_number) - BACKUP_DAILY_TIME=$(ask "Daily backup hour (00-23, UTC)" "${CLI_BACKUP_TIME:-09}" validate_number) + BACKUP_RETENTION_DAYS=$(ask "Daily backups retention (days)" "${CLI_BACKUP_DAYS:-$DEFAULT_BACKUP_DAYS}" validate_number) + BACKUP_RETENTION_HOURS=$(ask "Hourly backups retention (hours)" "${CLI_BACKUP_HOURS:-$DEFAULT_BACKUP_HOURS}" validate_number) + BACKUP_DAILY_TIME=$(ask "Daily backup hour (00-23, UTC)" "${CLI_BACKUP_TIME:-$DEFAULT_BACKUP_TIME}" validate_number) - local MODE="" + local MODE_SELECTION="" local MODE_PRESET_NAME="" declare -A MODULE_PRESET_CONFIGS=() declare -a MODULE_PRESET_ORDER=() @@ -690,7 +773,7 @@ fi if [ -n "$CLI_MODULE_PRESET" ]; then if [ -n "${MODULE_PRESET_CONFIGS[$CLI_MODULE_PRESET]:-}" ]; then - MODE="preset" + MODE_SELECTION="preset" MODE_PRESET_NAME="$CLI_MODULE_PRESET" else say ERROR "Unknown module preset: $CLI_MODULE_PRESET" @@ -698,39 +781,38 @@ fi fi fi - if [ -n "$MODE" ] && [ "$MODE" != "preset" ]; then + if [ -n "$MODE_SELECTION" ] && [ "$MODE_SELECTION" != "preset" ]; then MODE_PRESET_NAME="" fi if [ -n "$CLI_MODULE_MODE" ]; then case "${CLI_MODULE_MODE,,}" in - 1|suggested) MODE=1 ;; - 2|playerbots) MODE=2 ;; - 3|manual) MODE=3 ;; - 4|none) MODE=4 ;; + 1|suggested) MODE_SELECTION=1 ;; + 2|playerbots) MODE_SELECTION=2 ;; + 3|manual) MODE_SELECTION=3 ;; + 4|none) MODE_SELECTION=4 ;; *) say ERROR "Invalid module mode: ${CLI_MODULE_MODE}"; exit 1 ;; esac - if [ "$MODE" = "1" ]; then + if [ "$MODE_SELECTION" = "1" ]; then MODE_PRESET_NAME="$DEFAULT_PRESET_SUGGESTED" - elif [ "$MODE" = "2" ]; then + elif [ "$MODE_SELECTION" = "2" ]; then MODE_PRESET_NAME="$DEFAULT_PRESET_PLAYERBOTS" fi fi - if [ -z "$MODE" ] && [ ${#MODULE_ENABLE_SET[@]} -gt 0 ]; then - MODE=3 + if [ -z "$MODE_SELECTION" ] && [ ${#MODULE_ENABLE_SET[@]} -gt 0 ]; then + MODE_SELECTION=3 fi - if [ ${#MODULE_ENABLE_SET[@]} -gt 0 ] && [ -n "$MODE" ] && [ "$MODE" != "3" ] && [ "$MODE" != "4" ]; then + if [ ${#MODULE_ENABLE_SET[@]} -gt 0 ] && [ -n "$MODE_SELECTION" ] && [ "$MODE_SELECTION" != "3" ] && [ "$MODE_SELECTION" != "4" ]; then say INFO "Switching module preset to manual to honor --enable-modules list." - MODE=3 + MODE_SELECTION=3 fi - if [ "$MODE" = "4" ] && [ ${#MODULE_ENABLE_SET[@]} -gt 0 ]; then + if [ "$MODE_SELECTION" = "4" ] && [ ${#MODULE_ENABLE_SET[@]} -gt 0 ]; then say ERROR "--enable-modules cannot be used together with module-mode=none." exit 1 fi - local MODE_PRESET_NAME="" - if [ "$MODE" = "preset" ] && [ -n "$CLI_MODULE_PRESET" ]; then + if [ "$MODE_SELECTION" = "preset" ] && [ -n "$CLI_MODULE_PRESET" ]; then MODE_PRESET_NAME="$CLI_MODULE_PRESET" fi @@ -757,30 +839,30 @@ fi fi local max_option=$((menu_index - 1)) - if [ "$NON_INTERACTIVE" = "1" ] && [ -z "$MODE" ]; then - MODE=1 + if [ "$NON_INTERACTIVE" = "1" ] && [ -z "$MODE_SELECTION" ]; then + MODE_SELECTION=1 fi - if [ -z "$MODE" ]; then - local MODE_SELECTION + if [ -z "$MODE_SELECTION" ]; then + local selection_input while true; do - read -p "$(echo -e "${YELLOW}🔧 Select module configuration [1-${max_option}]: ${NC}")" MODE_SELECTION - if [[ "$MODE_SELECTION" =~ ^[0-9]+$ ]] && [ "$MODE_SELECTION" -ge 1 ] && [ "$MODE_SELECTION" -le "$max_option" ]; then - if [ -n "${MENU_PRESET_INDEX[$MODE_SELECTION]:-}" ]; then - MODE="preset" - MODE_PRESET_NAME="${MENU_PRESET_INDEX[$MODE_SELECTION]}" + read -p "$(echo -e "${YELLOW}🔧 Select module configuration [1-${max_option}]: ${NC}")" selection_input + if [[ "$selection_input" =~ ^[0-9]+$ ]] && [ "$selection_input" -ge 1 ] && [ "$selection_input" -le "$max_option" ]; then + if [ -n "${MENU_PRESET_INDEX[$selection_input]:-}" ]; then + MODE_SELECTION="preset" + MODE_PRESET_NAME="${MENU_PRESET_INDEX[$selection_input]}" else - MODE="$MODE_SELECTION" + MODE_SELECTION="$selection_input" fi break fi say ERROR "Please select a number between 1 and ${max_option}" done else - if [ "$MODE" = "preset" ]; then + if [ "$MODE_SELECTION" = "preset" ]; then say INFO "Module preset set to ${MODE_PRESET_NAME}." else - say INFO "Module preset set to ${MODE}." + say INFO "Module preset set to ${MODE_SELECTION}." fi fi @@ -793,10 +875,10 @@ fi MODULE_INSTANCE_RESET=0 MODULE_LEVEL_GRANT=0 MODULE_ASSISTANT=0 MODULE_REAGENT_BANK=0 MODULE_BLACK_MARKET_AUCTION_HOUSE=0 MODULE_ARAC=0 \ MODULE_CHALLENGE_MODES=0 MODULE_OLLAMA_CHAT=0 MODULE_SKELETON_MODULE=0 MODULE_BG_SLAVERYVALLEY=0 MODULE_ELUNA_TS=0 \ MODULE_PLAYER_BOT_LEVEL_BRACKETS=0 MODULE_STATBOOSTER=0 MODULE_DUNGEON_RESPAWN=0 MODULE_AZEROTHSHARD=0 MODULE_WORGOBLIN=0 - local DEFAULT_AUTH_IMAGE_PLAYERBOTS="uprightbass360/azerothcore-wotlk-playerbots:authserver-Playerbot" - local DEFAULT_WORLD_IMAGE_PLAYERBOTS="uprightbass360/azerothcore-wotlk-playerbots:worldserver-Playerbot" local AC_AUTHSERVER_IMAGE_PLAYERBOTS_VALUE="$DEFAULT_AUTH_IMAGE_PLAYERBOTS" local AC_WORLDSERVER_IMAGE_PLAYERBOTS_VALUE="$DEFAULT_WORLD_IMAGE_PLAYERBOTS" + local AC_AUTHSERVER_IMAGE_MODULES_VALUE="$DEFAULT_AUTH_IMAGE_MODULES" + local AC_WORLDSERVER_IMAGE_MODULES_VALUE="$DEFAULT_WORLD_IMAGE_MODULES" local mod_var for mod_var in "${!MODULE_ENABLE_SET[@]}"; do @@ -825,13 +907,16 @@ fi local RUN_REBUILD_NOW=$CLI_RUN_REBUILD local NEEDS_CXX_REBUILD=0 - if [ "$MODE" = "1" ]; then + local module_mode_label="" + if [ "$MODE_SELECTION" = "1" ]; then MODE_PRESET_NAME="$DEFAULT_PRESET_SUGGESTED" apply_module_preset "${MODULE_PRESET_CONFIGS[$DEFAULT_PRESET_SUGGESTED]}" - elif [ "$MODE" = "2" ]; then + module_mode_label="preset 1 (Suggested)" + elif [ "$MODE_SELECTION" = "2" ]; then MODE_PRESET_NAME="$DEFAULT_PRESET_PLAYERBOTS" apply_module_preset "${MODULE_PRESET_CONFIGS[$DEFAULT_PRESET_PLAYERBOTS]}" - elif [ "$MODE" = "3" ]; then + module_mode_label="preset 2 (Playerbots + Suggested)" + elif [ "$MODE_SELECTION" = "3" ]; then MODE_PRESET_NAME="" say INFO "Answer y/n for each module" for key in "${!DISABLED_MODULE_REASONS[@]}"; do @@ -884,7 +969,10 @@ fi MODULE_GAIN_HONOR_GUARD=$(ask_yn "Gain Honor Guard - Honor from guard kills" "$(module_default MODULE_GAIN_HONOR_GUARD)") MODULE_ARAC=$(ask_yn "All Races All Classes (requires client patch)" "$(module_default MODULE_ARAC)") MODULE_WORGOBLIN=$(ask_yn "Worgoblin - Worgen & Goblin races (client patch required)" "$(module_default MODULE_WORGOBLIN)") - elif [ "$MODE" = "preset" ]; then + module_mode_label="preset 3 (Manual)" + elif [ "$MODE_SELECTION" = "4" ]; then + module_mode_label="preset 4 (No modules)" + elif [ "$MODE_SELECTION" = "preset" ]; then local preset_modules="${MODULE_PRESET_CONFIGS[$MODE_PRESET_NAME]}" if [ -n "$preset_modules" ]; then apply_module_preset "$preset_modules" @@ -892,6 +980,7 @@ fi else say WARNING "Preset '${MODE_PRESET_NAME}' did not contain any module selections." fi + module_mode_label="preset (${MODE_PRESET_NAME})" fi if [ -n "$CLI_PLAYERBOT_ENABLED" ]; then @@ -913,7 +1002,7 @@ fi if [ -z "$CLI_PLAYERBOT_ENABLED" ]; then PLAYERBOT_ENABLED=1 fi - PLAYERBOT_MAX_BOTS=$(ask "Maximum concurrent playerbots" "${CLI_PLAYERBOT_MAX:-40}" validate_number) + PLAYERBOT_MAX_BOTS=$(ask "Maximum concurrent playerbots" "${CLI_PLAYERBOT_MAX:-$DEFAULT_PLAYERBOT_MAX}" validate_number) fi for mod_var in 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_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_PLAYER_BOT_LEVEL_BRACKETS MODULE_OLLAMA_CHAT MODULE_CHALLENGE_MODES MODULE_STATBOOSTER MODULE_DUNGEON_RESPAWN MODULE_SKELETON_MODULE MODULE_BG_SLAVERYVALLEY MODULE_AZEROTHSHARD MODULE_WORGOBLIN; do @@ -924,20 +1013,17 @@ fi fi done + export NEEDS_CXX_REBUILD + if [ "$MODULE_PLAYERBOTS" = "1" ]; then AC_AUTHSERVER_IMAGE_PLAYERBOTS_VALUE="$DEFAULT_AUTH_IMAGE_PLAYERBOTS" AC_WORLDSERVER_IMAGE_PLAYERBOTS_VALUE="$DEFAULT_WORLD_IMAGE_PLAYERBOTS" fi - local SUMMARY_MODE_TEXT - case "$MODE" in - 1) SUMMARY_MODE_TEXT="preset 1 (Suggested)" ;; - 2) SUMMARY_MODE_TEXT="preset 2 (Playerbots + Suggested)" ;; - 3) SUMMARY_MODE_TEXT="preset 3 (Manual)" ;; - 4) SUMMARY_MODE_TEXT="preset 4 (No modules)" ;; - preset) SUMMARY_MODE_TEXT="preset (${MODE_PRESET_NAME})" ;; - *) SUMMARY_MODE_TEXT="$MODE" ;; - esac + local SUMMARY_MODE_TEXT="$module_mode_label" + if [ -z "$SUMMARY_MODE_TEXT" ]; then + SUMMARY_MODE_TEXT="$MODE_SELECTION" + fi # Summary say HEADER "SUMMARY" @@ -946,6 +1032,8 @@ fi printf " %-18s %s\n" "Storage Path:" "$STORAGE_PATH" printf " %-18s %s\n" "Container User:" "$CONTAINER_USER" printf " %-18s Daily %s:00 UTC, keep %sd/%sh\n" "Backups:" "$BACKUP_DAILY_TIME" "$BACKUP_RETENTION_DAYS" "$BACKUP_RETENTION_HOURS" + printf " %-18s %s\n" "Source checkout:" "$default_source_rel" + printf " %-18s %s\n" "Modules images:" "$AC_AUTHSERVER_IMAGE_MODULES_VALUE | $AC_WORLDSERVER_IMAGE_MODULES_VALUE" printf " %-18s %s\n" "Modules preset:" "$SUMMARY_MODE_TEXT" printf " %-18s %s\n" "Playerbot Max Bots:" "$PLAYERBOT_MAX_BOTS" @@ -1007,7 +1095,7 @@ fi fi local default_source_rel="${LOCAL_STORAGE_ROOT}/source/azerothcore" - if [ "$MODULE_PLAYERBOTS" = "1" ]; then + if [ "$NEEDS_CXX_REBUILD" = "1" ] || [ "$MODULE_PLAYERBOTS" = "1" ]; then default_source_rel="${LOCAL_STORAGE_ROOT}/source/azerothcore-playerbots" fi @@ -1034,8 +1122,10 @@ fi MODULES_REBUILD_SOURCE_PATH_VALUE="$rebuild_source_path" export MODULES_REBUILD_SOURCE_PATH="$MODULES_REBUILD_SOURCE_PATH_VALUE" if [ ! -f "$rebuild_source_path/docker-compose.yml" ]; then - say INFO "Preparing source repository via scripts/setup-source.sh (git clone/fetch can take a few minutes)" - if ! ./scripts/setup-source.sh >/dev/null 2>&1; then + say INFO "Preparing source repository via scripts/setup-source.sh (progress will stream below)" + if ! ( set -o pipefail; ./scripts/setup-source.sh 2>&1 | while IFS= read -r line; do + say INFO "[setup-source] $line" + done ); then say WARNING "Source setup encountered issues; running interactively." if ! ./scripts/setup-source.sh; then say WARNING "Source setup failed; skipping automatic rebuild." @@ -1056,9 +1146,19 @@ fi export "$module_export_var" done - # Set git config for module script - git config --global user.name "${GIT_USERNAME:-ac-compose}" 2>/dev/null || true - git config --global user.email "${GIT_EMAIL:-noreply@azerothcore.org}" 2>/dev/null || true + # Prepare isolated git config for the module script so we do not mutate user-level settings + local prev_git_config_global="${GIT_CONFIG_GLOBAL:-}" + local git_temp_config="" + if command -v mktemp >/dev/null 2>&1; then + if ! git_temp_config="$(mktemp)"; then + git_temp_config="" + fi + fi + if [ -z "$git_temp_config" ]; then + git_temp_config="$local_modules_dir/.gitconfig.tmp" + : > "$git_temp_config" + fi + export GIT_CONFIG_GLOBAL="$git_temp_config" # Run module staging script in local modules directory # Set environment variable to indicate we're running locally @@ -1069,12 +1169,15 @@ fi say WARNING "Module staging encountered issues, but continuing with rebuild" fi unset MODULES_LOCAL_RUN - fi - fi - if [ "$RUN_REBUILD_NOW" = "1" ]; then - if ! ./scripts/rebuild-with-modules.sh --yes --skip-stop; then - say WARNING "Module rebuild failed; run ./scripts/rebuild-with-modules.sh manually." + if [ -n "$git_temp_config" ]; then + rm -f "$git_temp_config" + fi + if [ -n "$prev_git_config_global" ]; then + export GIT_CONFIG_GLOBAL="$prev_git_config_global" + else + unset GIT_CONFIG_GLOBAL + fi fi fi @@ -1096,63 +1199,65 @@ fi MODULES_REBUILD_SOURCE_PATH_VALUE="$default_source_rel" fi - DB_PLAYERBOTS_NAME=${DB_PLAYERBOTS_NAME:-acore_playerbots} + DB_PLAYERBOTS_NAME=${DB_PLAYERBOTS_NAME:-$DEFAULT_DB_PLAYERBOTS_NAME} local CLIENT_DATA_CACHE_PATH_VALUE="${LOCAL_STORAGE_ROOT}/client-data-cache" - HOST_ZONEINFO_PATH=${HOST_ZONEINFO_PATH:-/usr/share/zoneinfo} - MYSQL_INNODB_REDO_LOG_CAPACITY=${MYSQL_INNODB_REDO_LOG_CAPACITY:-512M} - MYSQL_RUNTIME_TMPFS_SIZE=${MYSQL_RUNTIME_TMPFS_SIZE:-8G} - CLIENT_DATA_VOLUME=${CLIENT_DATA_VOLUME:-ac-client-data} + HOST_ZONEINFO_PATH=${HOST_ZONEINFO_PATH:-$DEFAULT_HOST_ZONEINFO_PATH} + MYSQL_INNODB_REDO_LOG_CAPACITY=${MYSQL_INNODB_REDO_LOG_CAPACITY:-$DEFAULT_MYSQL_INNODB_REDO_LOG_CAPACITY} + MYSQL_RUNTIME_TMPFS_SIZE=${MYSQL_RUNTIME_TMPFS_SIZE:-$DEFAULT_MYSQL_RUNTIME_TMPFS_SIZE} + CLIENT_DATA_VOLUME=${CLIENT_DATA_VOLUME:-$DEFAULT_CLIENT_DATA_VOLUME} cat > "$ENV_OUT" <