diff --git a/.env.template b/.env.template index 19bde4a..9c8d26a 100644 --- a/.env.template +++ b/.env.template @@ -57,6 +57,12 @@ AC_WORLDSERVER_IMAGE_MODULES=acore-compose:worldserver-modules-latest # Client Data AC_CLIENT_DATA_IMAGE=acore/ac-wotlk-client-data:14.0.0-dev AC_CLIENT_DATA_IMAGE_PLAYERBOTS=uprightbass360/azerothcore-wotlk-playerbots:client-data-Playerbot +# Build artifacts +DOCKER_IMAGE_TAG=master +AC_AUTHSERVER_IMAGE_BASE=acore/ac-wotlk-authserver +AC_WORLDSERVER_IMAGE_BASE=acore/ac-wotlk-worldserver +AC_DB_IMPORT_IMAGE_BASE=acore/ac-wotlk-db-import +AC_CLIENT_DATA_IMAGE_BASE=acore/ac-wotlk-client-data # Helper images ALPINE_GIT_IMAGE=alpine/git:latest ALPINE_IMAGE=alpine:latest diff --git a/docker-compose.yml b/docker-compose.yml index f6f1957..22c720c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -591,61 +591,10 @@ services: - ${STORAGE_PATH}/config:/azerothcore/env/dist/etc - ./scripts:/tmp/scripts:ro - ./config:/tmp/config:ro + env_file: + - ./.env environment: - - MODULES_MANIFEST_PATH=/tmp/config/modules.json - - MODULE_PLAYERBOTS=${MODULE_PLAYERBOTS} - - MODULE_AOE_LOOT=${MODULE_AOE_LOOT} - - MODULE_LEARN_SPELLS=${MODULE_LEARN_SPELLS} - - MODULE_FIREWORKS=${MODULE_FIREWORKS} - - MODULE_INDIVIDUAL_PROGRESSION=${MODULE_INDIVIDUAL_PROGRESSION} - - MODULE_AHBOT=${MODULE_AHBOT} - - MODULE_AUTOBALANCE=${MODULE_AUTOBALANCE} - - MODULE_TRANSMOG=${MODULE_TRANSMOG} - - MODULE_NPC_BUFFER=${MODULE_NPC_BUFFER} - - MODULE_DYNAMIC_XP=${MODULE_DYNAMIC_XP} - - MODULE_SOLO_LFG=${MODULE_SOLO_LFG} - - MODULE_1V1_ARENA=${MODULE_1V1_ARENA} - - MODULE_PHASED_DUELS=${MODULE_PHASED_DUELS} - - MODULE_BREAKING_NEWS=${MODULE_BREAKING_NEWS} - - MODULE_BOSS_ANNOUNCER=${MODULE_BOSS_ANNOUNCER} - - MODULE_ACCOUNT_ACHIEVEMENTS=${MODULE_ACCOUNT_ACHIEVEMENTS} - - MODULE_AUTO_REVIVE=${MODULE_AUTO_REVIVE} - - MODULE_GAIN_HONOR_GUARD=${MODULE_GAIN_HONOR_GUARD} - - MODULE_ELUNA=${MODULE_ELUNA} - - MODULE_ARAC=${MODULE_ARAC} - - MODULE_TIME_IS_TIME=${MODULE_TIME_IS_TIME} - - MODULE_POCKET_PORTAL=${MODULE_POCKET_PORTAL} - - MODULE_RANDOM_ENCHANTS=${MODULE_RANDOM_ENCHANTS} - - MODULE_SOLOCRAFT=${MODULE_SOLOCRAFT} - - MODULE_PVP_TITLES=${MODULE_PVP_TITLES} - - MODULE_NPC_BEASTMASTER=${MODULE_NPC_BEASTMASTER} - - MODULE_NPC_ENCHANTER=${MODULE_NPC_ENCHANTER} - - MODULE_INSTANCE_RESET=${MODULE_INSTANCE_RESET} - - MODULE_LEVEL_GRANT=${MODULE_LEVEL_GRANT} - - MODULE_ASSISTANT=${MODULE_ASSISTANT} - - MODULE_REAGENT_BANK=${MODULE_REAGENT_BANK} - - MODULE_BLACK_MARKET_AUCTION_HOUSE=${MODULE_BLACK_MARKET_AUCTION_HOUSE} - - MODULE_CHALLENGE_MODES=${MODULE_CHALLENGE_MODES} - - MODULE_OLLAMA_CHAT=${MODULE_OLLAMA_CHAT} - - MODULE_PLAYER_BOT_LEVEL_BRACKETS=${MODULE_PLAYER_BOT_LEVEL_BRACKETS} - - MODULE_STATBOOSTER=${MODULE_STATBOOSTER} - - MODULE_DUNGEON_RESPAWN=${MODULE_DUNGEON_RESPAWN} - - MODULE_SKELETON_MODULE=${MODULE_SKELETON_MODULE} - - MODULE_BG_SLAVERYVALLEY=${MODULE_BG_SLAVERYVALLEY} - - MODULE_AZEROTHSHARD=${MODULE_AZEROTHSHARD} - - MODULE_WORGOBLIN=${MODULE_WORGOBLIN} - - MODULE_ELUNA_TS=${MODULE_ELUNA_TS} - - CONTAINER_MYSQL=${CONTAINER_MYSQL} - - MYSQL_PORT=${MYSQL_PORT} - - MYSQL_USER=${MYSQL_USER} - - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - - DB_AUTH_NAME=${DB_AUTH_NAME} - - DB_WORLD_NAME=${DB_WORLD_NAME} - - DB_CHARACTERS_NAME=${DB_CHARACTERS_NAME} - - DB_PLAYERBOTS_NAME=${DB_PLAYERBOTS_NAME} - - MYSQL_CHARACTER_SET=${MYSQL_CHARACTER_SET} - - MYSQL_COLLATION=${MYSQL_COLLATION} - - CONTAINER_USER=${CONTAINER_USER} + MODULES_MANIFEST_PATH: /tmp/config/modules.json entrypoint: ["/bin/sh"] command: - -c diff --git a/scripts/manage-modules.sh b/scripts/manage-modules.sh index 3aea8fd..d23b8f6 100755 --- a/scripts/manage-modules.sh +++ b/scripts/manage-modules.sh @@ -119,7 +119,11 @@ run_post_install_hooks(){ [ -n "$hooks_csv" ] || return 0 IFS=',' read -r -a hooks <<< "$hooks_csv" - local hooks_dir="/tmp/scripts/hooks" + local -a hook_search_paths=( + "$SCRIPT_DIR/hooks" + "/tmp/scripts/hooks" + "/scripts/hooks" + ) for hook in "${hooks[@]}"; do [ -n "$hook" ] || continue @@ -127,9 +131,16 @@ run_post_install_hooks(){ # Trim whitespace hook="$(echo "$hook" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" - local hook_script="$hooks_dir/$hook" + local hook_script="" + local candidate + for candidate in "${hook_search_paths[@]}"; do + if [ -x "$candidate/$hook" ]; then + hook_script="$candidate/$hook" + break + fi + done - if [ -x "$hook_script" ]; then + if [ -n "$hook_script" ]; then info "Running post-install hook: $hook" # Set hook environment variables @@ -153,7 +164,7 @@ run_post_install_hooks(){ # Clean up environment unset MODULE_KEY MODULE_DIR MODULE_NAME MODULES_ROOT LUA_SCRIPTS_TARGET else - err "Hook script not found: $hook_script" + err "Hook script not found for ${hook} (searched: ${hook_search_paths[*]})" fi done } diff --git a/scripts/rebuild-with-modules.sh b/scripts/rebuild-with-modules.sh index d84afb8..d9fe541 100755 --- a/scripts/rebuild-with-modules.sh +++ b/scripts/rebuild-with-modules.sh @@ -355,69 +355,97 @@ get_template_value() { fi } +strip_tag(){ + local image="$1" + if [[ "$image" == *:* ]]; then + echo "${image%:*}" + else + echo "$image" + fi +} + +tag_if_exists(){ + local source_image="$1" + local target_image="$2" + local description="$3" + if [ -z "$source_image" ] || [ -z "$target_image" ]; then + return 1 + fi + if docker image inspect "$source_image" >/dev/null 2>&1; then + if docker tag "$source_image" "$target_image"; then + echo "✅ Tagged ${description}: $target_image (from $source_image)" + return 0 + fi + fi + echo "⚠️ Warning: unable to tag ${description} from $source_image" + return 1 +} + +SOURCE_IMAGE_TAG="$(read_env DOCKER_IMAGE_TAG "$(get_template_value "DOCKER_IMAGE_TAG" "master")")" +[ -z "$SOURCE_IMAGE_TAG" ] && SOURCE_IMAGE_TAG="master" + +AUTHSERVER_BASE_REPO="$(strip_tag "$(read_env AC_AUTHSERVER_IMAGE_BASE "$(get_template_value "AC_AUTHSERVER_IMAGE_BASE" "acore/ac-wotlk-authserver")")")" +WORLDSERVER_BASE_REPO="$(strip_tag "$(read_env AC_WORLDSERVER_IMAGE_BASE "$(get_template_value "AC_WORLDSERVER_IMAGE_BASE" "acore/ac-wotlk-worldserver")")")" +DB_IMPORT_BASE_REPO="$(strip_tag "$(read_env AC_DB_IMPORT_IMAGE_BASE "$(get_template_value "AC_DB_IMPORT_IMAGE_BASE" "acore/ac-wotlk-db-import")")")" +CLIENT_DATA_BASE_REPO="$(strip_tag "$(read_env AC_CLIENT_DATA_IMAGE_BASE "$(get_template_value "AC_CLIENT_DATA_IMAGE_BASE" "acore/ac-wotlk-client-data")")")" + +BUILT_AUTHSERVER_IMAGE="$AUTHSERVER_BASE_REPO:$SOURCE_IMAGE_TAG" +BUILT_WORLDSERVER_IMAGE="$WORLDSERVER_BASE_REPO:$SOURCE_IMAGE_TAG" +BUILT_DB_IMPORT_IMAGE="$DB_IMPORT_BASE_REPO:$SOURCE_IMAGE_TAG" +BUILT_CLIENT_DATA_IMAGE="$CLIENT_DATA_BASE_REPO:$SOURCE_IMAGE_TAG" + 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")")" -[ -z "$TARGET_AUTHSERVER_IMAGE" ] && TARGET_AUTHSERVER_IMAGE="$(resolve_project_image "authserver-modules-latest")" -[ -z "$TARGET_WORLDSERVER_IMAGE" ] && TARGET_WORLDSERVER_IMAGE="$(resolve_project_image "worldserver-modules-latest")" -[ -z "$PLAYERBOTS_AUTHSERVER_IMAGE" ] && PLAYERBOTS_AUTHSERVER_IMAGE="$(resolve_project_image "authserver-playerbots")" -[ -z "$PLAYERBOTS_WORLDSERVER_IMAGE" ] && PLAYERBOTS_WORLDSERVER_IMAGE="$(resolve_project_image "worldserver-playerbots")" - -PLAYERBOTS_AUTHSERVER_IMAGE="$(ensure_project_image_tag "authserver-Playerbot" "$(resolve_project_image "authserver-playerbots")")" -if [ -z "$PLAYERBOTS_AUTHSERVER_IMAGE" ]; then - echo "⚠️ Warning: unable to ensure project tag for authserver playerbots image" -else - update_env_value "AC_AUTHSERVER_IMAGE_PLAYERBOTS" "$PLAYERBOTS_AUTHSERVER_IMAGE" +if [ -z "$TARGET_AUTHSERVER_IMAGE" ]; then + echo "❌ AC_AUTHSERVER_IMAGE_MODULES is not defined in .env" + exit 1 +fi +if [ -z "$TARGET_WORLDSERVER_IMAGE" ]; then + echo "❌ AC_WORLDSERVER_IMAGE_MODULES is not defined in .env" + exit 1 +fi +if [ -z "$PLAYERBOTS_AUTHSERVER_IMAGE" ]; then + echo "❌ AC_AUTHSERVER_IMAGE_PLAYERBOTS is not defined in .env" + exit 1 fi - -PLAYERBOTS_WORLDSERVER_IMAGE="$(ensure_project_image_tag "worldserver-Playerbot" "$(resolve_project_image "worldserver-playerbots")")" if [ -z "$PLAYERBOTS_WORLDSERVER_IMAGE" ]; then - echo "⚠️ Warning: unable to ensure project tag for worldserver playerbots image" -else - update_env_value "AC_WORLDSERVER_IMAGE_PLAYERBOTS" "$PLAYERBOTS_WORLDSERVER_IMAGE" + echo "❌ AC_WORLDSERVER_IMAGE_PLAYERBOTS is not defined in .env" + exit 1 fi echo "🔁 Tagging modules images from playerbot build artifacts" -if [ -n "$PLAYERBOTS_AUTHSERVER_IMAGE" ] && docker image inspect "$PLAYERBOTS_AUTHSERVER_IMAGE" >/dev/null 2>&1; then - if docker tag "$PLAYERBOTS_AUTHSERVER_IMAGE" "$TARGET_AUTHSERVER_IMAGE"; then - echo "✅ Tagged $TARGET_AUTHSERVER_IMAGE from $PLAYERBOTS_AUTHSERVER_IMAGE" - update_env_value "AC_AUTHSERVER_IMAGE_PLAYERBOTS" "$PLAYERBOTS_AUTHSERVER_IMAGE" - update_env_value "AC_AUTHSERVER_IMAGE_MODULES" "$TARGET_AUTHSERVER_IMAGE" - else - echo "⚠️ Failed to tag $TARGET_AUTHSERVER_IMAGE from $PLAYERBOTS_AUTHSERVER_IMAGE" - fi -else - echo "⚠️ Warning: unable to locate project-tagged authserver playerbots image" +if tag_if_exists "$BUILT_AUTHSERVER_IMAGE" "$PLAYERBOTS_AUTHSERVER_IMAGE" "playerbots authserver"; then + update_env_value "AC_AUTHSERVER_IMAGE_PLAYERBOTS" "$PLAYERBOTS_AUTHSERVER_IMAGE" +fi +if tag_if_exists "$BUILT_WORLDSERVER_IMAGE" "$PLAYERBOTS_WORLDSERVER_IMAGE" "playerbots worldserver"; then + update_env_value "AC_WORLDSERVER_IMAGE_PLAYERBOTS" "$PLAYERBOTS_WORLDSERVER_IMAGE" +fi +if tag_if_exists "$BUILT_AUTHSERVER_IMAGE" "$TARGET_AUTHSERVER_IMAGE" "modules authserver"; then + update_env_value "AC_AUTHSERVER_IMAGE_MODULES" "$TARGET_AUTHSERVER_IMAGE" +fi +if tag_if_exists "$BUILT_WORLDSERVER_IMAGE" "$TARGET_WORLDSERVER_IMAGE" "modules worldserver"; then + update_env_value "AC_WORLDSERVER_IMAGE_MODULES" "$TARGET_WORLDSERVER_IMAGE" fi -if [ -n "$PLAYERBOTS_WORLDSERVER_IMAGE" ] && docker image inspect "$PLAYERBOTS_WORLDSERVER_IMAGE" >/dev/null 2>&1; then - if docker tag "$PLAYERBOTS_WORLDSERVER_IMAGE" "$TARGET_WORLDSERVER_IMAGE"; then - echo "✅ Tagged $TARGET_WORLDSERVER_IMAGE from $PLAYERBOTS_WORLDSERVER_IMAGE" - update_env_value "AC_WORLDSERVER_IMAGE_PLAYERBOTS" "$PLAYERBOTS_WORLDSERVER_IMAGE" - update_env_value "AC_WORLDSERVER_IMAGE_MODULES" "$TARGET_WORLDSERVER_IMAGE" - else - echo "⚠️ Failed to tag $TARGET_WORLDSERVER_IMAGE from $PLAYERBOTS_WORLDSERVER_IMAGE" - fi -else - echo "⚠️ Warning: unable to locate project-tagged worldserver playerbots image" +TARGET_DB_IMPORT_IMAGE="$(read_env AC_DB_IMPORT_IMAGE "$(get_template_value "AC_DB_IMPORT_IMAGE")")" +if [ -z "$TARGET_DB_IMPORT_IMAGE" ]; then + echo "❌ AC_DB_IMPORT_IMAGE is not defined in .env" + exit 1 +fi +if tag_if_exists "$BUILT_DB_IMPORT_IMAGE" "$TARGET_DB_IMPORT_IMAGE" "playerbots db-import"; then + update_env_value "AC_DB_IMPORT_IMAGE" "$TARGET_DB_IMPORT_IMAGE" fi -TARGET_DB_IMPORT_IMAGE="$(resolve_project_image "db-import-playerbots")" -DB_IMPORT_IMAGE="$(ensure_project_image_tag "db-import-Playerbot" "$TARGET_DB_IMPORT_IMAGE")" -if [ -n "$DB_IMPORT_IMAGE" ]; then - update_env_value "AC_DB_IMPORT_IMAGE" "$DB_IMPORT_IMAGE" -else - echo "⚠️ Warning: unable to ensure project tag for db-import image" +TARGET_CLIENT_DATA_IMAGE="$(read_env AC_CLIENT_DATA_IMAGE_PLAYERBOTS "$(get_template_value "AC_CLIENT_DATA_IMAGE_PLAYERBOTS")")" +if [ -z "$TARGET_CLIENT_DATA_IMAGE" ]; then + echo "❌ AC_CLIENT_DATA_IMAGE_PLAYERBOTS is not defined in .env" + exit 1 fi - -TARGET_CLIENT_DATA_IMAGE="$(resolve_project_image "client-data-playerbots")" -CLIENT_DATA_IMAGE="$(ensure_project_image_tag "client-data-Playerbot" "$TARGET_CLIENT_DATA_IMAGE")" -if [ -n "$CLIENT_DATA_IMAGE" ]; then - update_env_value "AC_CLIENT_DATA_IMAGE_PLAYERBOTS" "$CLIENT_DATA_IMAGE" -else - echo "⚠️ Warning: unable to ensure project tag for client-data image" +if tag_if_exists "$BUILT_CLIENT_DATA_IMAGE" "$TARGET_CLIENT_DATA_IMAGE" "playerbots client-data"; then + update_env_value "AC_CLIENT_DATA_IMAGE_PLAYERBOTS" "$TARGET_CLIENT_DATA_IMAGE" fi show_rebuild_step 5 5 "Cleaning up build containers" diff --git a/setup.sh b/setup.sh index e1d0054..57a43cc 100755 --- a/setup.sh +++ b/setup.sh @@ -88,6 +88,11 @@ declare -A TEMPLATE_VALUE_MAP=( [DEFAULT_AC_AUTHSERVER_IMAGE]=AC_AUTHSERVER_IMAGE [DEFAULT_AC_WORLDSERVER_IMAGE]=AC_WORLDSERVER_IMAGE [DEFAULT_AC_CLIENT_DATA_IMAGE]=AC_CLIENT_DATA_IMAGE + [DEFAULT_DOCKER_IMAGE_TAG]=DOCKER_IMAGE_TAG + [DEFAULT_AUTHSERVER_IMAGE_BASE]=AC_AUTHSERVER_IMAGE_BASE + [DEFAULT_WORLDSERVER_IMAGE_BASE]=AC_WORLDSERVER_IMAGE_BASE + [DEFAULT_DB_IMPORT_IMAGE_BASE]=AC_DB_IMPORT_IMAGE_BASE + [DEFAULT_CLIENT_DATA_IMAGE_BASE]=AC_CLIENT_DATA_IMAGE_BASE [DEFAULT_AUTH_IMAGE_PLAYERBOTS]=AC_AUTHSERVER_IMAGE_PLAYERBOTS [DEFAULT_WORLD_IMAGE_PLAYERBOTS]=AC_WORLDSERVER_IMAGE_PLAYERBOTS [DEFAULT_CLIENT_DATA_IMAGE_PLAYERBOTS]=AC_CLIENT_DATA_IMAGE_PLAYERBOTS @@ -278,60 +283,15 @@ normalize_module_name(){ declare -A MODULE_ENABLE_SET=() -KNOWN_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_ARAC - 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_CHALLENGE_MODES - MODULE_OLLAMA_CHAT - MODULE_SKELETON_MODULE - MODULE_BG_SLAVERYVALLEY - MODULE_ELUNA_TS - MODULE_PLAYER_BOT_LEVEL_BRACKETS - MODULE_STATBOOSTER - MODULE_DUNGEON_RESPAWN - MODULE_AZEROTHSHARD - MODULE_WORGOBLIN - MODULE_ASSISTANT - MODULE_REAGENT_BANK - MODULE_BLACK_MARKET_AUCTION_HOUSE -) - -declare -A KNOWN_MODULE_LOOKUP=() -for __mod in "${KNOWN_MODULE_VARS[@]}"; do - KNOWN_MODULE_LOOKUP["$__mod"]=1 -done -unset __mod - module_default(){ local key="$1" - if [ "${MODULE_ENABLE_SET[$key]}" = "1" ]; then + if [ "${MODULE_ENABLE_SET[$key]:-0}" = "1" ]; then + echo y + return + fi + local current + eval "current=\${$key:-${MODULE_DEFAULT_VALUES[$key]:-0}}" + if [ "$current" = "1" ]; then echo y else echo n @@ -345,7 +305,7 @@ apply_module_preset(){ local mod="${item//[[:space:]]/}" [ -z "$mod" ] && continue if [ -n "${KNOWN_MODULE_LOOKUP[$mod]:-}" ]; then - eval "$mod=1" + printf -v "$mod" '%s' "1" else say WARNING "Preset references unknown module $mod" fi @@ -383,6 +343,204 @@ EOF echo -e "${NC}" } +# ============================== +# Module metadata / defaults +# ============================== + +MODULE_MANIFEST_PATH="$SCRIPT_DIR/config/modules.json" +ENV_TEMPLATE_FILE="$SCRIPT_DIR/.env.template" + +declare -a MODULE_KEYS=() +declare -a MODULE_KEYS_SORTED=() +declare -A MODULE_NAME_MAP=() +declare -A MODULE_TYPE_MAP=() +declare -A MODULE_STATUS_MAP=() +declare -A MODULE_BLOCK_REASON_MAP=() +declare -A MODULE_NEEDS_BUILD_MAP=() +declare -A MODULE_REQUIRES_MAP=() +declare -A MODULE_NOTES_MAP=() +declare -A MODULE_DEFAULT_VALUES=() +declare -A KNOWN_MODULE_LOOKUP=() +declare -A ENV_TEMPLATE_VALUES=() +MODULE_METADATA_INITIALIZED=0 + +load_env_template_values() { + local template_file="$ENV_TEMPLATE_FILE" + if [ ! -f "$template_file" ]; then + echo "ERROR: .env.template file not found at $template_file" >&2 + exit 1 + fi + + while IFS= read -r raw_line || [ -n "$raw_line" ]; do + local line="${raw_line%%#*}" + line="${line%%$'\r'}" + line="$(echo "$line" | sed 's/[[:space:]]*$//')" + [ -n "$line" ] || continue + [[ "$line" == *=* ]] || continue + local key="${line%%=*}" + local value="${line#*=}" + key="$(echo "$key" | sed 's/[[:space:]]//g')" + value="$(echo "$value" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + [ -n "$key" ] || continue + ENV_TEMPLATE_VALUES["$key"]="$value" + done < "$template_file" +} + +load_module_manifest_metadata() { + if [ ! -f "$MODULE_MANIFEST_PATH" ]; then + echo "ERROR: Module manifest not found at $MODULE_MANIFEST_PATH" >&2 + exit 1 + fi + if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 is required to read $MODULE_MANIFEST_PATH" >&2 + exit 1 + fi + + mapfile -t MODULE_KEYS < <( + python3 - "$MODULE_MANIFEST_PATH" <<'PY' +import json, sys +from pathlib import Path + +manifest_path = Path(sys.argv[1]) +manifest = json.loads(manifest_path.read_text()) +modules = manifest.get("modules", []) +for entry in modules: + key = entry.get("key") + if not key: + continue + print(key) +PY + ) + + if [ ${#MODULE_KEYS[@]} -eq 0 ]; then + echo "ERROR: No modules defined in manifest $MODULE_MANIFEST_PATH" >&2 + exit 1 + fi + + while IFS=$'\t' read -r key name needs_build module_type status block_reason requires notes; do + [ -n "$key" ] || continue + MODULE_NAME_MAP["$key"]="$name" + MODULE_NEEDS_BUILD_MAP["$key"]="$needs_build" + MODULE_TYPE_MAP["$key"]="$module_type" + MODULE_STATUS_MAP["$key"]="$status" + MODULE_BLOCK_REASON_MAP["$key"]="$block_reason" + MODULE_REQUIRES_MAP["$key"]="$requires" + MODULE_NOTES_MAP["$key"]="$notes" + KNOWN_MODULE_LOOKUP["$key"]=1 + done < <( + python3 - "$MODULE_MANIFEST_PATH" <<'PY' +import json, sys +from pathlib import Path + +manifest_path = Path(sys.argv[1]) +manifest = json.loads(manifest_path.read_text()) + +def clean(value): + if value is None: + return "" + return str(value).replace("\t", " ").replace("\n", " ").strip() + +for entry in manifest.get("modules", []): + key = entry.get("key") + if not key: + continue + name = clean(entry.get("name", key)) + needs_build = "1" if entry.get("needs_build") else "0" + module_type = clean(entry.get("type", "")) + status = clean(entry.get("status", "active")) + block_reason = clean(entry.get("block_reason", "")) + requires = entry.get("requires") or [] + depends_on = entry.get("depends_on") or [] + ordered = [] + for dep in list(requires) + list(depends_on): + if dep and dep not in ordered: + ordered.append(dep) + requires_csv = ",".join(ordered) + notes = clean(entry.get("notes", "")) + print("\t".join([key, name, needs_build, module_type, status or "active", block_reason, requires_csv, notes])) +PY + ) + + mapfile -t MODULE_KEYS_SORTED < <( + python3 - "$MODULE_MANIFEST_PATH" <<'PY' +import json, sys +from pathlib import Path + +manifest_path = Path(sys.argv[1]) +manifest = json.loads(manifest_path.read_text()) +modules = manifest.get("modules", []) +sorted_keys = sorted(modules, key=lambda m: (str(m.get("type", "")), str(m.get("name", m.get("key"))).lower())) +for entry in sorted_keys: + key = entry.get("key") + if key: + print(key) +PY + ) +} + +initialize_module_defaults() { + if [ "$MODULE_METADATA_INITIALIZED" = "1" ]; then + return + fi + load_env_template_values + load_module_manifest_metadata + + for key in "${MODULE_KEYS[@]}"; do + if [ -z "${ENV_TEMPLATE_VALUES[$key]+_}" ]; then + echo "ERROR: .env.template missing default value for ${key}" >&2 + exit 1 + fi + local default="${ENV_TEMPLATE_VALUES[$key]}" + MODULE_DEFAULT_VALUES["$key"]="$default" + printf -v "$key" '%s' "$default" + done + MODULE_METADATA_INITIALIZED=1 +} + +reset_modules_to_defaults() { + for key in "${MODULE_KEYS[@]}"; do + printf -v "$key" '%s' "${MODULE_DEFAULT_VALUES[$key]}" + done +} + +module_display_name() { + local key="$1" + local name="${MODULE_NAME_MAP[$key]:-$key}" + local note="${MODULE_NOTES_MAP[$key]}" + if [ -n "$note" ]; then + echo "${name} - ${note}" + else + echo "$name" + fi +} + +auto_enable_module_dependencies() { + local changed=1 + while [ "$changed" -eq 1 ]; do + changed=0 + for key in "${MODULE_KEYS[@]}"; do + local enabled + eval "enabled=\${$key:-0}" + [ "$enabled" = "1" ] || continue + local requires_csv="${MODULE_REQUIRES_MAP[$key]}" + IFS=',' read -r -a deps <<< "${requires_csv}" + for dep in "${deps[@]}"; do + dep="${dep//[[:space:]]/}" + [ -n "$dep" ] || continue + [ -n "${KNOWN_MODULE_LOOKUP[$dep]:-}" ] || continue + local dep_value + eval "dep_value=\${$dep:-0}" + if [ "$dep_value" != "1" ]; then + say INFO "Automatically enabling ${dep#MODULE_} (required by ${key#MODULE_})." + printf -v "$dep" '%s' "1" + MODULE_ENABLE_SET["$dep"]=1 + changed=1 + fi + done + done + done +} + show_realm_configured(){ echo -e "\n${GREEN}⚔️ Your realm configuration has been forged! ⚔️${NC}" @@ -414,6 +572,9 @@ main(){ local FORCE_OVERWRITE=0 local CLI_ENABLE_MODULES_RAW=() + initialize_module_defaults + reset_modules_to_defaults + while [[ $# -gt 0 ]]; do case "$1" in -h|--help) @@ -707,8 +868,14 @@ fi # Permission scheme say HEADER "PERMISSION SCHEME" + local CURRENT_UID CURRENT_GID CURRENT_USER_PAIR CURRENT_USER_NAME CURRENT_GROUP_NAME + CURRENT_UID="$(id -u 2>/dev/null || echo 1000)" + CURRENT_GID="$(id -g 2>/dev/null || echo 1000)" + CURRENT_USER_NAME="$(id -un 2>/dev/null || echo user)" + CURRENT_GROUP_NAME="$(id -gn 2>/dev/null || echo users)" + CURRENT_USER_PAIR="${CURRENT_UID}:${CURRENT_GID}" echo "1) 🏠 Local Root (0:0)" - echo "2) 🗂️ User (1001:1000)" + echo "2) 🗂️ Current User (${CURRENT_USER_NAME}:${CURRENT_GROUP_NAME} → ${CURRENT_USER_PAIR})" echo "3) ⚙️ Custom" local PERMISSION_SCHEME_INPUT="${CLI_PERMISSION_SCHEME}" local PERMISSION_SCHEME_NAME="" @@ -725,9 +892,9 @@ fi CONTAINER_USER="$PERMISSION_LOCAL_USER" PERMISSION_SCHEME_NAME="local" ;; - 2|nfs) - CONTAINER_USER="$PERMISSION_NFS_USER" - PERMISSION_SCHEME_NAME="nfs" + 2|nfs|user) + CONTAINER_USER="$CURRENT_USER_PAIR" + PERMISSION_SCHEME_NAME="user" ;; 3|custom) local uid gid @@ -911,15 +1078,6 @@ fi fi fi - # Initialize toggles - local MODULE_PLAYERBOTS=0 MODULE_AOE_LOOT=0 MODULE_LEARN_SPELLS=0 MODULE_FIREWORKS=0 MODULE_INDIVIDUAL_PROGRESSION=0 \ - MODULE_AHBOT=0 MODULE_AUTOBALANCE=0 MODULE_TRANSMOG=0 MODULE_NPC_BUFFER=0 MODULE_DYNAMIC_XP=0 MODULE_SOLO_LFG=0 \ - MODULE_1V1_ARENA=0 MODULE_PHASED_DUELS=0 MODULE_BREAKING_NEWS=0 MODULE_BOSS_ANNOUNCER=0 MODULE_ACCOUNT_ACHIEVEMENTS=0 \ - MODULE_AUTO_REVIVE=0 MODULE_GAIN_HONOR_GUARD=0 MODULE_TIME_IS_TIME=0 MODULE_POCKET_PORTAL=0 \ - MODULE_RANDOM_ENCHANTS=0 MODULE_SOLOCRAFT=0 MODULE_PVP_TITLES=0 MODULE_NPC_BEASTMASTER=0 MODULE_NPC_ENCHANTER=0 \ - 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_ELUNA=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 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" @@ -929,17 +1087,17 @@ fi local mod_var for mod_var in "${!MODULE_ENABLE_SET[@]}"; do - if [ -n "${KNOWN_MODULE_LOOKUP[$mod_var]}" ]; then - eval "$mod_var=1" + if [ -n "${KNOWN_MODULE_LOOKUP[$mod_var]:-}" ]; then + printf -v "$mod_var" '%s' "1" fi done - if { [ "${MODULE_PLAYER_BOT_LEVEL_BRACKETS}" = "1" ] || [ "${MODULE_OLLAMA_CHAT}" = "1" ]; } && [ "$MODULE_PLAYERBOTS" != "1" ]; then + auto_enable_module_dependencies + + if [ "${MODULE_OLLAMA_CHAT:-0}" = "1" ] && [ "${MODULE_PLAYERBOTS:-0}" != "1" ]; then + say INFO "Automatically enabling MODULE_PLAYERBOTS for MODULE_OLLAMA_CHAT." MODULE_PLAYERBOTS=1 MODULE_ENABLE_SET["MODULE_PLAYERBOTS"]=1 - if [ ${#MODULE_ENABLE_SET[@]} -gt 0 ]; then - say INFO "Automatically enabling MODULE_PLAYERBOTS to satisfy playerbot-dependent modules." - fi fi declare -A DISABLED_MODULE_REASONS=( @@ -968,55 +1126,40 @@ fi for key in "${!DISABLED_MODULE_REASONS[@]}"; do say WARNING "${key#MODULE_}: ${DISABLED_MODULE_REASONS[$key]}" done - # Core Gameplay - MODULE_PLAYERBOTS=$(ask_yn "Playerbots - AI companions" "$(module_default MODULE_PLAYERBOTS)") - MODULE_PLAYER_BOT_LEVEL_BRACKETS=$(ask_yn "Playerbot Level Brackets - Evenly distribute bot levels" "$(module_default MODULE_PLAYER_BOT_LEVEL_BRACKETS)") - MODULE_OLLAMA_CHAT=$(ask_yn "Ollama Chat - LLM dialogue for playerbots (requires external Ollama API)" "$(module_default MODULE_OLLAMA_CHAT)") - MODULE_SOLO_LFG=$(ask_yn "Solo LFG - Solo dungeon finder" "$(module_default MODULE_SOLO_LFG)") - MODULE_SOLOCRAFT=$(ask_yn "Solocraft - Scale dungeons/raids for solo" "$(module_default MODULE_SOLOCRAFT)") - MODULE_CHALLENGE_MODES=$(ask_yn "Challenge Modes - Timed dungeon keystones" "$(module_default MODULE_CHALLENGE_MODES)") - MODULE_AUTOBALANCE=$(ask_yn "Autobalance - Dynamic difficulty" "$(module_default MODULE_AUTOBALANCE)") - # QoL - MODULE_TRANSMOG=$(ask_yn "Transmog - Appearance changes" "$(module_default MODULE_TRANSMOG)") - MODULE_NPC_BUFFER=$(ask_yn "NPC Buffer - Buff NPCs" "$(module_default MODULE_NPC_BUFFER)") - MODULE_LEARN_SPELLS=$(ask_yn "Learn Spells - Auto-learn" "$(module_default MODULE_LEARN_SPELLS)") - MODULE_AOE_LOOT=$(ask_yn "AOE Loot - Multi-corpse loot" "$(module_default MODULE_AOE_LOOT)") - MODULE_FIREWORKS=$(ask_yn "Fireworks - Level-up FX" "$(module_default MODULE_FIREWORKS)") - MODULE_ASSISTANT=$(ask_yn "Assistant - Multi-service NPC" "$(module_default MODULE_ASSISTANT)") - MODULE_STATBOOSTER=$(ask_yn "Stat Booster - Random enchant upgrades" "$(module_default MODULE_STATBOOSTER)") - MODULE_DUNGEON_RESPAWN=$(ask_yn "Dungeon Respawn - Return to entrance on death" "$(module_default MODULE_DUNGEON_RESPAWN)") - MODULE_SKELETON_MODULE=$(ask_yn "Skeleton Module - Blank module template" "$(module_default MODULE_SKELETON_MODULE)") - # Economy - MODULE_AHBOT=$(ask_yn "AH Bot - Auction automation" "$(module_default MODULE_AHBOT)") - MODULE_REAGENT_BANK=$(ask_yn "Reagent Bank - Materials storage" "$(module_default MODULE_REAGENT_BANK)") - MODULE_BLACK_MARKET_AUCTION_HOUSE=$(ask_yn "Black Market - MoP-style" "$(module_default MODULE_BLACK_MARKET_AUCTION_HOUSE)") - # PvP - MODULE_1V1_ARENA=$(ask_yn "1v1 Arena - Solo arena queue system" "$(module_default MODULE_1V1_ARENA)") - MODULE_PHASED_DUELS=$(ask_yn "Phased Duels - Isolated duel instances" "$(module_default MODULE_PHASED_DUELS)") - MODULE_PVP_TITLES=$(ask_yn "PvP Titles - Classic honor rank titles" "$(module_default MODULE_PVP_TITLES)") - MODULE_BG_SLAVERYVALLEY=$(ask_yn "Slavery Valley - Custom battleground" "$(module_default MODULE_BG_SLAVERYVALLEY)") - # Progression - MODULE_INDIVIDUAL_PROGRESSION=$(ask_yn "Individual Progression (Vanilla→TBC→WotLK)" "$(module_default MODULE_INDIVIDUAL_PROGRESSION)") - MODULE_DYNAMIC_XP=$(ask_yn "Dynamic XP - Adaptive experience rates" "$(module_default MODULE_DYNAMIC_XP)") - MODULE_ACCOUNT_ACHIEVEMENTS=$(ask_yn "Account Achievements - Share progress across characters" "$(module_default MODULE_ACCOUNT_ACHIEVEMENTS)") - MODULE_AZEROTHSHARD=$(ask_yn "AzerothShard - Blended custom features" "$(module_default MODULE_AZEROTHSHARD)") - # Server Features - MODULE_BREAKING_NEWS=$(ask_yn "Breaking News - Server announcement system" "$(module_default MODULE_BREAKING_NEWS)") - MODULE_BOSS_ANNOUNCER=$(ask_yn "Boss Announcer - Broadcast boss kills" "$(module_default MODULE_BOSS_ANNOUNCER)") - MODULE_AUTO_REVIVE=$(ask_yn "Auto Revive - Automatic resurrection system" "$(module_default MODULE_AUTO_REVIVE)") - MODULE_ELUNA_TS=$(ask_yn "Eluna TS - TypeScript toolchain for Lua" "$(module_default MODULE_ELUNA_TS)") - # Utility - MODULE_NPC_BEASTMASTER=$(ask_yn "NPC Beastmaster - Rare pet vendor" "$(module_default MODULE_NPC_BEASTMASTER)") - MODULE_NPC_ENCHANTER=$(ask_yn "NPC Enchanter - Gear enchanting service" "$(module_default MODULE_NPC_ENCHANTER)") - MODULE_RANDOM_ENCHANTS=$(ask_yn "Random Enchants - Suffix property system" "$(module_default MODULE_RANDOM_ENCHANTS)") - MODULE_POCKET_PORTAL=$(ask_yn "Pocket Portal - Personal teleportation device" "$(module_default MODULE_POCKET_PORTAL)") - MODULE_INSTANCE_RESET=$(ask_yn "Instance Reset - Dungeon lockout management" "$(module_default MODULE_INSTANCE_RESET)") - MODULE_TIME_IS_TIME=$(ask_yn "Time is Time - Real-time clock system" "$(module_default MODULE_TIME_IS_TIME)") - 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)") + local -a selection_keys=("${MODULE_KEYS_SORTED[@]}") + if [ ${#selection_keys[@]} -eq 0 ]; then + selection_keys=("${MODULE_KEYS[@]}") + fi + local key + for key in "${selection_keys[@]}"; do + [ -n "${KNOWN_MODULE_LOOKUP[$key]:-}" ] || continue + local status_lc="${MODULE_STATUS_MAP[$key],,}" + if [ -n "$status_lc" ] && [ "$status_lc" != "active" ]; then + local reason="${MODULE_BLOCK_REASON_MAP[$key]:-Blocked in manifest}" + say WARNING "${key#MODULE_} is blocked: ${reason}" + printf -v "$key" '%s' "0" + continue + fi + local prompt_label + prompt_label="$(module_display_name "$key")" + if [ "${MODULE_NEEDS_BUILD_MAP[$key]}" = "1" ]; then + prompt_label="${prompt_label} (requires build)" + fi + local default_answer + default_answer="$(module_default "$key")" + local response + response=$(ask_yn "$prompt_label" "$default_answer") + if [ "$response" = "1" ]; then + printf -v "$key" '%s' "1" + else + printf -v "$key" '%s' "0" + fi + done module_mode_label="preset 3 (Manual)" elif [ "$MODE_SELECTION" = "4" ]; then + for key in "${MODULE_KEYS[@]}"; do + printf -v "$key" '%s' "0" + done module_mode_label="preset 4 (No modules)" elif [ "$MODE_SELECTION" = "preset" ]; then local preset_modules="${MODULE_PRESET_CONFIGS[$MODE_PRESET_NAME]}" @@ -1029,6 +1172,8 @@ fi module_mode_label="preset (${MODE_PRESET_NAME})" fi + auto_enable_module_dependencies + if [ -n "$CLI_PLAYERBOT_ENABLED" ]; then if [[ "$CLI_PLAYERBOT_ENABLED" != "0" && "$CLI_PLAYERBOT_ENABLED" != "1" ]]; then say ERROR "--playerbot-enabled must be 0 or 1" @@ -1051,11 +1196,13 @@ fi 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 - eval "value=\$$mod_var" - if [ "$value" = "1" ]; then - NEEDS_CXX_REBUILD=1 - break + for mod_var in "${MODULE_KEYS[@]}"; do + if [ "${MODULE_NEEDS_BUILD_MAP[$mod_var]}" = "1" ]; then + eval "value=\${$mod_var:-0}" + if [ "$value" = "1" ]; then + NEEDS_CXX_REBUILD=1 + break + fi fi done @@ -1080,8 +1227,8 @@ fi printf " %-18s %s\n" "Playerbot Max Bots:" "$PLAYERBOT_MAX_BOTS" printf " %-18s" "Enabled Modules:" local enabled_modules=() - for module_var in "${KNOWN_MODULE_VARS[@]}"; do - eval "value=\$$module_var" + for module_var in "${MODULE_KEYS[@]}"; do + eval "value=\${$module_var:-0}" if [ "$value" = "1" ]; then enabled_modules+=("${module_var#MODULE_}") fi @@ -1111,7 +1258,7 @@ fi export STORAGE_PATH STORAGE_PATH_LOCAL local module_export_var - for module_export_var in "${KNOWN_MODULE_VARS[@]}"; do + for module_export_var in "${MODULE_KEYS[@]}"; do export "$module_export_var" done @@ -1227,11 +1374,10 @@ COMPOSE_PROJECT_NAME=$DEFAULT_COMPOSE_PROJECT_NAME STORAGE_PATH=$STORAGE_PATH STORAGE_PATH_LOCAL=$LOCAL_STORAGE_ROOT BACKUP_PATH=$BACKUP_PATH -HOST_ZONEINFO_PATH=${HOST_ZONEINFO_PATH:-$DEFAULT_HOST_ZONEINFO_PATH} TZ=$DEFAULT_TZ + # Database MYSQL_IMAGE=$DEFAULT_MYSQL_IMAGE -CONTAINER_MYSQL=$DEFAULT_CONTAINER_MYSQL MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD MYSQL_ROOT_HOST=$DEFAULT_MYSQL_ROOT_HOST MYSQL_USER=$DEFAULT_MYSQL_USER @@ -1267,7 +1413,18 @@ AC_CLIENT_DATA_IMAGE_PLAYERBOTS=$AC_CLIENT_DATA_IMAGE_PLAYERBOTS_VALUE CLIENT_DATA_CACHE_PATH=$DEFAULT_CLIENT_DATA_CACHE_PATH CLIENT_DATA_VOLUME=${CLIENT_DATA_VOLUME:-$DEFAULT_CLIENT_DATA_VOLUME} +# Build artifacts +DOCKER_IMAGE_TAG=$DEFAULT_DOCKER_IMAGE_TAG +AC_AUTHSERVER_IMAGE_BASE=$DEFAULT_AUTHSERVER_IMAGE_BASE +AC_WORLDSERVER_IMAGE_BASE=$DEFAULT_WORLDSERVER_IMAGE_BASE +AC_DB_IMPORT_IMAGE_BASE=$DEFAULT_DB_IMPORT_IMAGE_BASE +AC_CLIENT_DATA_IMAGE_BASE=$DEFAULT_CLIENT_DATA_IMAGE_BASE + +# Container user +CONTAINER_USER=$CONTAINER_USER + # Containers +CONTAINER_MYSQL=$DEFAULT_CONTAINER_MYSQL CONTAINER_DB_IMPORT=$DEFAULT_CONTAINER_DB_IMPORT CONTAINER_DB_INIT=$DEFAULT_CONTAINER_DB_INIT CONTAINER_BACKUP=$DEFAULT_CONTAINER_BACKUP @@ -1293,52 +1450,13 @@ BACKUP_DAILY_TIME=$BACKUP_DAILY_TIME BACKUP_HEALTHCHECK_MAX_MINUTES=$BACKUP_HEALTHCHECK_MAX_MINUTES BACKUP_HEALTHCHECK_GRACE_SECONDS=$BACKUP_HEALTHCHECK_GRACE_SECONDS -# Container user -CONTAINER_USER=$CONTAINER_USER - -# Modules -MODULE_PLAYERBOTS=$MODULE_PLAYERBOTS -MODULE_AOE_LOOT=$MODULE_AOE_LOOT -MODULE_LEARN_SPELLS=$MODULE_LEARN_SPELLS -MODULE_FIREWORKS=$MODULE_FIREWORKS -MODULE_INDIVIDUAL_PROGRESSION=$MODULE_INDIVIDUAL_PROGRESSION -MODULE_AHBOT=$MODULE_AHBOT -MODULE_AUTOBALANCE=$MODULE_AUTOBALANCE -MODULE_TRANSMOG=$MODULE_TRANSMOG -MODULE_NPC_BUFFER=$MODULE_NPC_BUFFER -MODULE_DYNAMIC_XP=$MODULE_DYNAMIC_XP -MODULE_SOLO_LFG=$MODULE_SOLO_LFG -MODULE_1V1_ARENA=$MODULE_1V1_ARENA -MODULE_PHASED_DUELS=$MODULE_PHASED_DUELS -MODULE_BREAKING_NEWS=$MODULE_BREAKING_NEWS -MODULE_BOSS_ANNOUNCER=$MODULE_BOSS_ANNOUNCER -MODULE_ACCOUNT_ACHIEVEMENTS=$MODULE_ACCOUNT_ACHIEVEMENTS -MODULE_AUTO_REVIVE=$MODULE_AUTO_REVIVE -MODULE_GAIN_HONOR_GUARD=$MODULE_GAIN_HONOR_GUARD -MODULE_ARAC=$MODULE_ARAC -MODULE_ELUNA=$MODULE_ELUNA -MODULE_TIME_IS_TIME=$MODULE_TIME_IS_TIME -MODULE_POCKET_PORTAL=$MODULE_POCKET_PORTAL -MODULE_RANDOM_ENCHANTS=$MODULE_RANDOM_ENCHANTS -MODULE_SOLOCRAFT=$MODULE_SOLOCRAFT -MODULE_PVP_TITLES=$MODULE_PVP_TITLES -MODULE_NPC_BEASTMASTER=$MODULE_NPC_BEASTMASTER -MODULE_NPC_ENCHANTER=$MODULE_NPC_ENCHANTER -MODULE_INSTANCE_RESET=$MODULE_INSTANCE_RESET -MODULE_LEVEL_GRANT=$MODULE_LEVEL_GRANT -MODULE_CHALLENGE_MODES=$MODULE_CHALLENGE_MODES -MODULE_OLLAMA_CHAT=$MODULE_OLLAMA_CHAT -MODULE_SKELETON_MODULE=$MODULE_SKELETON_MODULE -MODULE_BG_SLAVERYVALLEY=$MODULE_BG_SLAVERYVALLEY -MODULE_ELUNA_TS=$MODULE_ELUNA_TS -MODULE_PLAYER_BOT_LEVEL_BRACKETS=$MODULE_PLAYER_BOT_LEVEL_BRACKETS -MODULE_STATBOOSTER=$MODULE_STATBOOSTER -MODULE_DUNGEON_RESPAWN=$MODULE_DUNGEON_RESPAWN -MODULE_AZEROTHSHARD=$MODULE_AZEROTHSHARD -MODULE_WORGOBLIN=$MODULE_WORGOBLIN -MODULE_ASSISTANT=$MODULE_ASSISTANT -MODULE_REAGENT_BANK=$MODULE_REAGENT_BANK -MODULE_BLACK_MARKET_AUCTION_HOUSE=$MODULE_BLACK_MARKET_AUCTION_HOUSE +EOF + echo + echo "# Modules" + for module_key in "${MODULE_KEYS[@]}"; do + printf "%s=%s\n" "$module_key" "${!module_key:-0}" + done + cat <