From b8bebb28c05fe41c08c829c2785140f8d81540e9 Mon Sep 17 00:00:00 2001 From: uprightbass360 Date: Tue, 4 Nov 2025 22:48:29 -0500 Subject: [PATCH 1/2] consolidate module configs --- scripts/auto-post-install.sh | 2 - scripts/manage-modules.sh | 145 +++++++++++++++++++++++++++-------- 2 files changed, 112 insertions(+), 35 deletions(-) diff --git a/scripts/auto-post-install.sh b/scripts/auto-post-install.sh index 8e6e1f3..cdd88b8 100755 --- a/scripts/auto-post-install.sh +++ b/scripts/auto-post-install.sh @@ -140,8 +140,6 @@ else sed -i "s|^LoginDatabaseInfo *=.*|LoginDatabaseInfo = \"${MYSQL_HOST};${MYSQL_PORT};${MYSQL_USER};${MYSQL_ROOT_PASSWORD};${DB_AUTH_NAME}\"|" /azerothcore/config/worldserver.conf || true sed -i "s|^WorldDatabaseInfo *=.*|WorldDatabaseInfo = \"${MYSQL_HOST};${MYSQL_PORT};${MYSQL_USER};${MYSQL_ROOT_PASSWORD};${DB_WORLD_NAME}\"|" /azerothcore/config/worldserver.conf || true sed -i "s|^CharacterDatabaseInfo *=.*|CharacterDatabaseInfo = \"${MYSQL_HOST};${MYSQL_PORT};${MYSQL_USER};${MYSQL_ROOT_PASSWORD};${DB_CHARACTERS_NAME}\"|" /azerothcore/config/worldserver.conf || true - update_playerbots_conf /azerothcore/config/playerbots.conf - update_playerbots_conf /azerothcore/config/playerbots.conf.dist update_playerbots_conf /azerothcore/config/modules/playerbots.conf update_playerbots_conf /azerothcore/config/modules/playerbots.conf.dist diff --git a/scripts/manage-modules.sh b/scripts/manage-modules.sh index f2b492c..1ed9407 100755 --- a/scripts/manage-modules.sh +++ b/scripts/manage-modules.sh @@ -209,35 +209,103 @@ install_enabled_modules(){ update_playerbots_db_info(){ local target="$1" - if [ ! -f "$target" ]; then + if [ ! -f "$target" ] && [ ! -L "$target" ]; then return 0 fi - local host - host="$(read_env_value CONTAINER_MYSQL)" - if [ -z "$host" ]; then - host="$(read_env_value MYSQL_HOST)" - fi - host="${host:-ac-mysql}" + local env_file="${ENV_PATH:-}" + local resolved - local port - port="$(read_env_value MYSQL_PORT "3306")" + resolved="$( + python3 - "$target" "${env_file}" <<'PY' +import os +import pathlib +import sys - local user - user="$(read_env_value MYSQL_USER "root")" +def load_env_file(path): + data = {} + if not path: + return data + candidate = pathlib.Path(path) + if not candidate.is_file(): + return data + for raw in candidate.read_text(encoding="utf-8", errors="ignore").splitlines(): + if not raw or raw.lstrip().startswith("#"): + continue + if "=" not in raw: + continue + key, val = raw.split("=", 1) + key = key.strip() + val = val.strip() + if not key: + continue + if val and val[0] == val[-1] and val[0] in {"'", '"'}: + val = val[1:-1] + if "#" in val: + # Strip inline comments + val = val.split("#", 1)[0].rstrip() + data[key] = val + return data - local pass - pass="$(read_env_value MYSQL_ROOT_PASSWORD)" +def resolve_key(env_map, key, default=""): + value = os.environ.get(key) + if value: + return value + return env_map.get(key, default) - local db - db="$(read_env_value DB_PLAYERBOTS_NAME "acore_playerbots")" - local value="${host};${port};${user};${pass};${db}" +def update_config(path_in, replacement): + if not (os.path.exists(path_in) or os.path.islink(path_in)): + return False + path = os.path.realpath(path_in) + try: + with open(path, "r", encoding="utf-8", errors="ignore") as fh: + lines = fh.read().splitlines() + except FileNotFoundError: + lines = [] - if grep -qE '^[[:space:]]*PlayerbotsDatabaseInfo[[:space:]]*=' "$target"; then - sed -i "s|^[[:space:]]*PlayerbotsDatabaseInfo[[:space:]]*=.*|PlayerbotsDatabaseInfo = \"${value}\"|" "$target" || return - else - printf '\nPlayerbotsDatabaseInfo = "%s"\n' "$value" >> "$target" || return - fi + key = "PlayerbotsDatabaseInfo" + replacement_line = f'{key} = "{replacement}"' + changed = False + + for idx, raw in enumerate(lines): + if raw.strip().startswith(key): + if raw.strip() != replacement_line: + lines[idx] = replacement_line + changed = True + break + else: + lines.append(replacement_line) + changed = True + + if changed: + output = "\n".join(lines) + if output and not output.endswith("\n"): + output += "\n" + with open(path, "w", encoding="utf-8") as fh: + fh.write(output) + + return True + +target_path, env_path = sys.argv[1:3] +env_map = load_env_file(env_path) + +host = resolve_key(env_map, "CONTAINER_MYSQL") or resolve_key(env_map, "MYSQL_HOST", "ac-mysql") or "ac-mysql" +port = resolve_key(env_map, "MYSQL_PORT", "3306") or "3306" +user = resolve_key(env_map, "MYSQL_USER", "root") or "root" +password = resolve_key(env_map, "MYSQL_ROOT_PASSWORD", "") +database = resolve_key(env_map, "DB_PLAYERBOTS_NAME", "acore_playerbots") or "acore_playerbots" + +value = ";".join([host, port, user, password, database]) +update_config(target_path, value) + +print(value) +PY + )" || return 0 + + local host port + host="${resolved%%;*}" + port="${resolved#*;}" + port="${port%%;*}" if [ "$PLAYERBOTS_DB_UPDATE_LOGGED" = "0" ]; then info "Updated PlayerbotsDatabaseInfo to use host ${host}:${port}" @@ -279,17 +347,12 @@ manage_configuration_files(){ unset patterns done - local module_dir - for key in "${MODULE_KEYS[@]}"; do - module_dir="${MODULE_NAME[$key]:-}" - [ -n "$module_dir" ] || continue - [ -d "$module_dir" ] || continue - find "$module_dir" -name "*.conf.dist" -exec cp {} "$env_target"/ \; 2>/dev/null || true - done - local modules_conf_dir="${env_target%/}/modules" mkdir -p "$modules_conf_dir" + rm -rf "${modules_conf_dir}.backup" rm -f "$modules_conf_dir"/*.conf "$modules_conf_dir"/*.conf.dist 2>/dev/null || true + + local module_dir for key in "${MODULE_KEYS[@]}"; do module_dir="${MODULE_NAME[$key]:-}" [ -n "$module_dir" ] || continue @@ -297,8 +360,26 @@ manage_configuration_files(){ while IFS= read -r conf_file; do [ -n "$conf_file" ] || continue base_name="$(basename "$conf_file")" - dest_name="${base_name%.dist}" - cp "$conf_file" "$modules_conf_dir/$dest_name" + # Ensure previous copies in root config are removed to keep modules/ canonical + main_conf_path="${env_target}/${base_name}" + if [ -f "$main_conf_path" ]; then + rm -f "$main_conf_path" + fi + if [[ "$base_name" == *.conf.dist ]]; then + root_conf="${env_target}/${base_name%.dist}" + if [ -f "$root_conf" ]; then + rm -f "$root_conf" + fi + fi + + dest_path="${modules_conf_dir}/${base_name}" + cp "$conf_file" "$dest_path" + if [[ "$base_name" == *.conf.dist ]]; then + dest_conf="${modules_conf_dir}/${base_name%.dist}" + if [ ! -f "$dest_conf" ]; then + cp "$conf_file" "$dest_conf" + fi + fi done < <(find "$module_dir" -path "*/conf/*" -type f \( -name "*.conf" -o -name "*.conf.dist" \) 2>/dev/null) done @@ -308,8 +389,6 @@ manage_configuration_files(){ fi if [ "$playerbots_enabled" = "1" ]; then - update_playerbots_db_info "$env_target/playerbots.conf" - update_playerbots_db_info "$env_target/playerbots.conf.dist" update_playerbots_db_info "$modules_conf_dir/playerbots.conf" update_playerbots_db_info "$modules_conf_dir/playerbots.conf.dist" fi From 389952e4690cde2ddeb09232a8e240bbec64b301 Mon Sep 17 00:00:00 2001 From: uprightbass360 Date: Tue, 4 Nov 2025 23:14:57 -0500 Subject: [PATCH 2/2] add min/max playerbots env settings --- .env.template | 1 + scripts/manage-modules.sh | 77 +++++++++++++++++++++++++++++++++------ setup.sh | 32 +++++++++++++++- 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/.env.template b/.env.template index 9c8d26a..71e3c33 100644 --- a/.env.template +++ b/.env.template @@ -135,6 +135,7 @@ BACKUP_HEALTHCHECK_START_PERIOD=120s # Playerbots runtime flags (used by worldserver env) # ===================== PLAYERBOT_ENABLED=0 +PLAYERBOT_MIN_BOTS=40 PLAYERBOT_MAX_BOTS=40 # ===================== diff --git a/scripts/manage-modules.sh b/scripts/manage-modules.sh index 1ed9407..c13ea88 100755 --- a/scripts/manage-modules.sh +++ b/scripts/manage-modules.sh @@ -221,6 +221,7 @@ update_playerbots_db_info(){ import os import pathlib import sys +import re def load_env_file(path): data = {} @@ -253,7 +254,29 @@ def resolve_key(env_map, key, default=""): return value return env_map.get(key, default) -def update_config(path_in, replacement): +def parse_bool(value): + if value is None: + return None + value = value.strip().lower() + if value == "": + return None + if value in {"1", "true", "yes", "on"}: + return True + if value in {"0", "false", "no", "off"}: + return False + return None + +def parse_int(value): + if value is None: + return None + value = value.strip() + if not value: + return None + if re.fullmatch(r"[+-]?\d+", value): + return str(int(value)) + return None + +def update_config(path_in, settings): if not (os.path.exists(path_in) or os.path.islink(path_in)): return False path = os.path.realpath(path_in) @@ -263,18 +286,33 @@ def update_config(path_in, replacement): except FileNotFoundError: lines = [] - key = "PlayerbotsDatabaseInfo" - replacement_line = f'{key} = "{replacement}"' changed = False + pending = dict(settings) for idx, raw in enumerate(lines): - if raw.strip().startswith(key): - if raw.strip() != replacement_line: - lines[idx] = replacement_line - changed = True - break - else: - lines.append(replacement_line) + stripped = raw.strip() + for key, value in list(pending.items()): + if re.match(rf"^\s*{re.escape(key)}\s*=", stripped): + desired = f"{key} = {value}" + if stripped != desired: + leading = raw[: len(raw) - len(raw.lstrip())] + trailing = "" + if "#" in raw: + before, comment = raw.split("#", 1) + if before.strip(): + trailing = f" # {comment.strip()}" + lines[idx] = f"{leading}{desired}{trailing}" + changed = True + pending.pop(key, None) + break + + if pending: + if lines and lines[-1] and not lines[-1].endswith("\n"): + lines[-1] = lines[-1] + "\n" + if lines and lines[-1].strip(): + lines.append("\n") + for key, value in pending.items(): + lines.append(f"{key} = {value}\n") changed = True if changed: @@ -296,7 +334,24 @@ password = resolve_key(env_map, "MYSQL_ROOT_PASSWORD", "") database = resolve_key(env_map, "DB_PLAYERBOTS_NAME", "acore_playerbots") or "acore_playerbots" value = ";".join([host, port, user, password, database]) -update_config(target_path, value) +settings = {"PlayerbotsDatabaseInfo": f'"{value}"'} + +enabled_setting = parse_bool(resolve_key(env_map, "PLAYERBOT_ENABLED")) +if enabled_setting is not None: + settings["AiPlayerbot.Enabled"] = "1" if enabled_setting else "0" + +max_bots = parse_int(resolve_key(env_map, "PLAYERBOT_MAX_BOTS")) +min_bots = parse_int(resolve_key(env_map, "PLAYERBOT_MIN_BOTS")) + +if max_bots and not min_bots: + min_bots = max_bots + +if min_bots: + settings["AiPlayerbot.MinRandomBots"] = min_bots +if max_bots: + settings["AiPlayerbot.MaxRandomBots"] = max_bots + +update_config(target_path, settings) print(value) PY diff --git a/setup.sh b/setup.sh index 9a325f8..c959c2d 100755 --- a/setup.sh +++ b/setup.sh @@ -68,6 +68,7 @@ declare -A TEMPLATE_VALUE_MAP=( [DEFAULT_AUTH_PORT]=AUTH_EXTERNAL_PORT [DEFAULT_SOAP_PORT]=SOAP_EXTERNAL_PORT [DEFAULT_MYSQL_PORT]=MYSQL_EXTERNAL_PORT + [DEFAULT_PLAYERBOT_MIN]=PLAYERBOT_MIN_BOTS [DEFAULT_PLAYERBOT_MAX]=PLAYERBOT_MAX_BOTS [DEFAULT_LOCAL_STORAGE]=STORAGE_PATH [DEFAULT_BACKUP_PATH]=BACKUP_PATH @@ -573,6 +574,7 @@ main(){ local CLI_MODULE_MODE="" local CLI_MODULE_PRESET="" local CLI_PLAYERBOT_ENABLED="" + local CLI_PLAYERBOT_MIN="" local CLI_PLAYERBOT_MAX="" local CLI_AUTO_REBUILD=0 local CLI_MODULES_SOURCE="" @@ -615,7 +617,8 @@ Options: --module-config NAME Use preset NAME from profiles/.conf --enable-modules LIST Comma-separated module list (MODULE_* or shorthand) --playerbot-enabled 0|1 Override PLAYERBOT_ENABLED flag - --playerbot-max-bots N Override PLAYERBOT_MAX_BOTS value + --playerbot-min-bots N Override PLAYERBOT_MIN_BOTS value + --playerbot-max-bots N Override PLAYERBOT_MAX_BOTS value --auto-rebuild-on-deploy Enable automatic rebuild during deploys --modules-rebuild-source PATH Source checkout used for module rebuilds --deploy-after Run ./deploy.sh automatically after setup completes @@ -755,6 +758,12 @@ EOF ;; --playerbot-max-bots) [[ $# -ge 2 ]] || { say ERROR "--playerbot-max-bots requires a value"; exit 1; } + CLI_PLAYERBOT_MIN="$2"; shift 2 + ;; + --playerbot-min-bots=*) + CLI_PLAYERBOT_MIN="${1#*=}"; shift + ;; + --playerbot-max-bots) CLI_PLAYERBOT_MAX="$2"; shift 2 ;; --playerbot-max-bots=*) @@ -1112,7 +1121,9 @@ fi [MODULE_LEVEL_GRANT]="QuestCountLevel module relies on removed ConfigMgr APIs and fails to build" ) - local PLAYERBOT_ENABLED=0 PLAYERBOT_MAX_BOTS=40 + local PLAYERBOT_ENABLED=0 + local PLAYERBOT_MIN_BOTS="${DEFAULT_PLAYERBOT_MIN:-40}" + local PLAYERBOT_MAX_BOTS="${DEFAULT_PLAYERBOT_MAX:-40}" local AUTO_REBUILD_ON_DEPLOY=$CLI_AUTO_REBUILD local MODULES_REBUILD_SOURCE_PATH_VALUE="${CLI_MODULES_SOURCE}" @@ -1192,6 +1203,13 @@ fi fi PLAYERBOT_ENABLED="$CLI_PLAYERBOT_ENABLED" fi + if [ -n "$CLI_PLAYERBOT_MIN" ]; then + if ! [[ "$CLI_PLAYERBOT_MIN" =~ ^[0-9]+$ ]]; then + say ERROR "--playerbot-min-bots must be numeric" + exit 1 + fi + PLAYERBOT_MIN_BOTS="$CLI_PLAYERBOT_MIN" + fi if [ -n "$CLI_PLAYERBOT_MAX" ]; then if ! [[ "$CLI_PLAYERBOT_MAX" =~ ^[0-9]+$ ]]; then say ERROR "--playerbot-max-bots must be numeric" @@ -1204,9 +1222,17 @@ fi if [ -z "$CLI_PLAYERBOT_ENABLED" ]; then PLAYERBOT_ENABLED=1 fi + PLAYERBOT_MIN_BOTS=$(ask "Minimum concurrent playerbots" "${CLI_PLAYERBOT_MIN:-$DEFAULT_PLAYERBOT_MIN}" validate_number) PLAYERBOT_MAX_BOTS=$(ask "Maximum concurrent playerbots" "${CLI_PLAYERBOT_MAX:-$DEFAULT_PLAYERBOT_MAX}" validate_number) fi + if [ -n "$PLAYERBOT_MIN_BOTS" ] && [ -n "$PLAYERBOT_MAX_BOTS" ]; then + if [ "$PLAYERBOT_MAX_BOTS" -lt "$PLAYERBOT_MIN_BOTS" ]; then + say WARNING "Playerbot max bots ($PLAYERBOT_MAX_BOTS) lower than min ($PLAYERBOT_MIN_BOTS); adjusting max to match min." + PLAYERBOT_MAX_BOTS="$PLAYERBOT_MIN_BOTS" + fi + fi + for mod_var in "${MODULE_KEYS[@]}"; do if [ "${MODULE_NEEDS_BUILD_MAP[$mod_var]}" = "1" ]; then eval "value=\${$mod_var:-0}" @@ -1235,6 +1261,7 @@ fi 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 Min Bots:" "$PLAYERBOT_MIN_BOTS" printf " %-18s %s\n" "Playerbot Max Bots:" "$PLAYERBOT_MAX_BOTS" printf " %-18s" "Enabled Modules:" local enabled_modules=() @@ -1474,6 +1501,7 @@ CLIENT_DATA_VERSION=${CLIENT_DATA_VERSION:-$DEFAULT_CLIENT_DATA_VERSION} # Playerbot runtime PLAYERBOT_ENABLED=$PLAYERBOT_ENABLED +PLAYERBOT_MIN_BOTS=$PLAYERBOT_MIN_BOTS PLAYERBOT_MAX_BOTS=$PLAYERBOT_MAX_BOTS # Rebuild automation