cleanup: validation and integrations for importing data

This commit is contained in:
uprightbass360
2025-11-22 16:49:01 -05:00
committed by Deckard
parent e6231bb4a4
commit 6ddfe9b2c7
17 changed files with 6797 additions and 369 deletions

View File

@@ -441,3 +441,260 @@ PMA_MAX_EXECUTION_TIME=600
KEIRA3_EXTERNAL_PORT=4201
KEIRA_DATABASE_HOST=ac-mysql
KEIRA_DATABASE_PORT=3306
# Auto-generated defaults for new modules
MODULE_1V1_PVP_SYSTEM=0
MODULE_ACI=0
MODULE_ACORE_API=0
MODULE_ACORE_BG_END_ANNOUNCER=0
MODULE_ACORE_BOX=0
MODULE_ACORE_CLIENT=0
MODULE_ACORE_CMS=0
MODULE_ACORE_ELUNATEST=0
MODULE_ACORE_LINUX_RESTARTER=0
MODULE_ACORE_LUA_UNLIMITED_AMMO=0
MODULE_ACORE_LXD_IMAGE=0
MODULE_ACORE_MALL=0
MODULE_ACORE_MINI_REG_PAGE=0
MODULE_ACORE_NODE_SERVER=0
MODULE_ACORE_PWA=0
MODULE_ACORE_SOD=0
MODULE_ACORE_SUMMONALL=0
MODULE_ACORE_TILEMAP=0
MODULE_ACORE_ZONEDEBUFF=0
MODULE_ACREBUILD=0
MODULE_ADDON_FACTION_FREE_UNIT_POPUP=0
MODULE_AOE_LOOT_MERGE=0
MODULE_APAW=0
MODULE_ARENA_SPECTATOR=0
MODULE_ARENA_STATS=0
MODULE_ATTRIBOOST=0
MODULE_AUTO_CHECK_RESTART=0
MODULE_AZEROTHCOREADMIN=0
MODULE_AZEROTHCOREDISCORDBOT=0
MODULE_AZEROTHCORE_ADDITIONS=0
MODULE_AZEROTHCORE_ALL_STACKABLES_200=0
MODULE_AZEROTHCORE_ANSIBLE=0
MODULE_AZEROTHCORE_ARMORY=0
MODULE_AZEROTHCORE_LUA_ARENA_MASTER_COMMAND=0
MODULE_AZEROTHCORE_LUA_DEMON_MORPHER=0
MODULE_AZEROTHCORE_PASSRESET=0
MODULE_AZEROTHCORE_REGISTRATION_PAGE=0
MODULE_AZEROTHCORE_SERVER_MANAGER=0
MODULE_AZEROTHCORE_TRIVIA_SYSTEM=0
MODULE_AZEROTHCORE_WEBSITE=0
MODULE_AZEROTHCORE_WOWHEAD_MOD_LUA=0
MODULE_AZTRAL_AIRLINES=0
MODULE_BGQUEUECHECKER=0
MODULE_BG_QUEUE_ABUSER_VIEWER=0
MODULE_BLIZZLIKE_TELES=0
MODULE_BREAKINGNEWSOVERRIDE=0
MODULE_CLASSIC_MODE=0
MODULE_CODEBASE=0
MODULE_CONFIG_RATES=0
MODULE_DEVJOESTAR=0
MODULE_ELUNA_WOW_SCRIPTS=0
MODULE_EXTENDEDXP=0
MODULE_EXTENDED_HOLIDAYS_LUA=0
MODULE_FFAFIX=0
MODULE_FLAG_CHECKER=0
MODULE_GUILDBANKTABFEEFIXER=0
MODULE_HARDMODE=0
MODULE_HEARTHSTONE_COOLDOWNS=0
MODULE_ITEMBROADCASTGUILDCHAT=0
MODULE_KARGATUM_SYSTEM=0
MODULE_KEIRA3=0
MODULE_LOTTERY_CHANCE_INSTANT=0
MODULE_LUA_AIO_MODRATE_EXP=0
MODULE_LUA_COMMAND_PLUS=0
MODULE_LUA_ITEMUPGRADER_TEMPLATE=0
MODULE_LUA_NOTONLY_RANDOMMORPHER=0
MODULE_LUA_PARAGON_ANNIVERSARY=0
MODULE_LUA_PVP_TITLES_RANKING_SYSTEM=0
MODULE_LUA_SCRIPTS=0
MODULE_LUA_SUPER_BUFFERNPC=0
MODULE_LUA_VIP=0
MODULE_MOD_ACCOUNTBOUND=0
MODULE_MOD_ACCOUNT_VANITY_PETS=0
MODULE_MOD_ACTIVATEZONES=0
MODULE_MOD_AH_BOT_PLUS=0
MODULE_MOD_ALPHA_REWARDS=0
MODULE_MOD_AOE_LOOT=0
MODULE_MOD_APPRECIATION=0
MODULE_MOD_ARENA_TIGERSPEAK=0
MODULE_MOD_ARENA_TOLVIRON=0
MODULE_MOD_AUTOFISH=0
MODULE_MOD_AUTO_RESURRECT=0
MODULE_MOD_BG_BATTLE_FOR_GILNEAS=0
MODULE_MOD_BG_ITEM_REWARD=0
MODULE_MOD_BG_REWARD=0
MODULE_MOD_BG_TWINPEAKS=0
MODULE_MOD_BIENVENIDA=0
MODULE_MOD_BLACK_MARKET=0
MODULE_MOD_BRAWLERS_GUILD=0
MODULE_MOD_BUFF_COMMAND=0
MODULE_MOD_CFPVE=0
MODULE_MOD_CHANGEABLESPAWNRATES=0
MODULE_MOD_CHARACTER_SERVICES=0
MODULE_MOD_CHARACTER_TOOLS=0
MODULE_MOD_CHAT_TRANSMITTER=0
MODULE_MOD_CHROMIE_XP=0
MODULE_MOD_CONGRATS_ON_LEVEL=0
MODULE_MOD_COSTUMES=0
MODULE_MOD_CRAFTSPEED=0
MODULE_MOD_CTA_SWITCH=0
MODULE_MOD_DEAD_MEANS_DEAD=0
MODULE_MOD_DEATHROLL_AIO=0
MODULE_MOD_DEMONIC_PACT_CLASSIC=0
MODULE_MOD_DESERTION_WARNINGS=0
MODULE_MOD_DISCORD_ANNOUNCE=0
MODULE_MOD_DISCORD_WEBHOOK=0
MODULE_MOD_DMF_SWITCH=0
MODULE_MOD_DUNGEONMASTER=0
MODULE_MOD_DUNGEON_SCALE=0
MODULE_MOD_DYNAMIC_LOOT_RATES=0
MODULE_MOD_DYNAMIC_RESURRECTIONS=0
MODULE_MOD_ENCOUNTER_LOGS=0
MODULE_MOD_FACTION_FREE=0
MODULE_MOD_FIRSTLOGIN_AIO=0
MODULE_MOD_FLIGHTMASTER_WHISTLE=0
MODULE_MOD_FORTIS_AUTOBALANCE=0
MODULE_MOD_GAME_STATE_API=0
MODULE_MOD_GEDDON_BINDING_SHARD=0
MODULE_MOD_GHOST_SPEED=0
MODULE_MOD_GLOBALCHAT=0
MODULE_MOD_GM_COMMANDS=0
MODULE_MOD_GOMOVE=0
MODULE_MOD_GROWNUP=0
MODULE_MOD_GUILDFUNDS=0
MODULE_MOD_GUILD_VILLAGE=0
MODULE_MOD_GUILD_ZONE_SYSTEM=0
MODULE_MOD_HARDCORE=0
MODULE_MOD_HARDCORE_MAKGORA=0
MODULE_MOD_HARD_MODES=0
MODULE_MOD_HIGH_RISK_SYSTEM=0
MODULE_MOD_HUNTER_PET_STORAGE=0
MODULE_MOD_IMPROVED_BANK=0
MODULE_MOD_INCREMENT_CACHE_VERSION=0
MODULE_MOD_INDIVIDUAL_XP=0
MODULE_MOD_INFLUXDB=0
MODULE_MOD_INSTANCE_TOOLS=0
MODULE_MOD_IP2NATION=0
MODULE_MOD_IP_TRACKER=0
MODULE_MOD_ITEMLEVEL=0
MODULE_MOD_ITEM_UPGRADE=0
MODULE_MOD_JUNK_TO_GOLD=0
MODULE_MOD_LEARNSPELLS=0
MODULE_MOD_LEECH=0
MODULE_MOD_LEVEL_15_BOOST=0
MODULE_MOD_LEVEL_ONE_MOUNTS=0
MODULE_MOD_LEVEL_REWARDS=0
MODULE_MOD_LOGIN_REWARDS=0
MODULE_MOD_LOW_LEVEL_ARENA=0
MODULE_MOD_LOW_LEVEL_RBG=0
MODULE_MOD_MISSING_OBJECTIVES=0
MODULE_MOD_MONEY_FOR_KILLS=0
MODULE_MOD_MOUNTS_ON_ACCOUNT=0
MODULE_MOD_MOUNT_REQUIREMENTS=0
MODULE_MOD_MULTI_VENDOR=0
MODULE_MOD_MYTHIC_PLUS=0
MODULE_MOD_NOCLIP=0
MODULE_MOD_NORDF=0
MODULE_MOD_NOTIFY_MUTED=0
MODULE_MOD_NO_FARMING=0
MODULE_MOD_NO_HEARTHSTONE_COOLDOWN=0
MODULE_MOD_NPC_ALL_MOUNTS=0
MODULE_MOD_NPC_CODEBOX=0
MODULE_MOD_NPC_GAMBLER=0
MODULE_MOD_NPC_MORPH=0
MODULE_MOD_NPC_PROMOTION=0
MODULE_MOD_NPC_SERVICES=0
MODULE_MOD_NPC_SPECTATOR=0
MODULE_MOD_NPC_SUBCLASS=0
MODULE_MOD_OBJSCALE=0
MODULE_MOD_OLLAMA_BOT_BUDDY=0
MODULE_MOD_ONY_NAXX_LOGOUT_TELEPORT=0
MODULE_MOD_PEACEKEEPER=0
MODULE_MOD_PETEQUIP=0
MODULE_MOD_PREMIUM=0
MODULE_MOD_PREMIUM_LIB=0
MODULE_MOD_PROFESSION_EXPERIENCE=0
MODULE_MOD_PROFSPECS=0
MODULE_MOD_PTR_TEMPLATE=0
MODULE_MOD_PVPSCRIPT=0
MODULE_MOD_PVPSTATS_ANNOUNCER=0
MODULE_MOD_PVP_ZONES=0
MODULE_MOD_QUEST_LOOT_PARTY=0
MODULE_MOD_QUEST_STATUS=0
MODULE_MOD_QUEUE_LIST_CACHE=0
MODULE_MOD_QUICKBALANCE=0
MODULE_MOD_QUICK_RESPAWN=0
MODULE_MOD_RACIAL_TRAIT_SWAP=0
MODULE_MOD_RARE_DROPS=0
MODULE_MOD_RDF_EXPANSION=0
MODULE_MOD_REAL_ONLINE=0
MODULE_MOD_RECRUIT_FRIEND=0
MODULE_MOD_REFORGING=0
MODULE_MOD_RESET_RAID_COOLDOWNS=0
MODULE_MOD_REWARD_PLAYED_TIME_IMPROVED=0
MODULE_MOD_REWARD_SHOP=0
MODULE_MOD_SELL_ITEMS=0
MODULE_MOD_SETXPBAR=0
MODULE_MOD_SHARE_MOUNTS=0
MODULE_MOD_SPAWNPOINTS=0
MODULE_MOD_SPEC_REWARD=0
MODULE_MOD_SPELLREGULATOR=0
MODULE_MOD_SPONSORSHIP=0
MODULE_MOD_STARTER_GUILD=0
MODULE_MOD_STARTER_WANDS=0
MODULE_MOD_STARTING_PET=0
MODULE_MOD_STREAMS=0
MODULE_MOD_SWIFT_TRAVEL_FORM=0
MODULE_MOD_TALENTBUTTON=0
MODULE_MOD_TRADE_ITEMS_FILTER=0
MODULE_MOD_TREASURE=0
MODULE_MOD_TRIAL_OF_FINALITY=0
MODULE_MOD_VANILLA_NAXXRAMAS=0
MODULE_MOD_WARLOCK_PET_RENAME=0
MODULE_MOD_WEAPON_VISUAL=0
MODULE_MOD_WEEKENDBONUS=0
MODULE_MOD_WEEKEND_XP=0
MODULE_MOD_WHOLOGGED=0
MODULE_MORZA_ISLAND_ARAXIA_SERVER=0
MODULE_MPQ_TOOLS_OSX=0
MODULE_MYSQL_TOOLS=0
MODULE_NODEROUTER=0
MODULE_OPENPROJECTS=0
MODULE_PLAYERTELEPORT=0
MODULE_PORTALS_IN_ALL_CAPITALS=0
MODULE_PRESTIGE=0
MODULE_PRESTIGIOUS=0
MODULE_PVPSTATS=0
MODULE_RAIDTELEPORTER=0
MODULE_RECACHE=0
MODULE_RECYCLEDITEMS=0
MODULE_REWARD_SYSTEM=0
MODULE_SAHTOUTCMS=0
MODULE_SERVER_STATUS=0
MODULE_SETXPBAR=0
MODULE_SPELLSCRIPT_REFACTOR_TOOL=0
MODULE_SQL_NPC_TELEPORTER=0
MODULE_STATBOOSTERREROLLER=0
MODULE_STRAPI_AZEROTHCORE=0
MODULE_TBC_RAID_HP_RESTORATION=0
MODULE_TELEGRAM_AUTOMATED_DB_BACKUP=0
MODULE_TOOL_TC_MIGRATION=0
MODULE_TRANSMOG_ADDONS=0
MODULE_UPDATE_MOB_LEVEL_TO_PLAYER_AND_RANDOM_ITEM_STATS=0
MODULE_UPDATE_MODULE_CONFS=0
MODULE_WEB_CHARACTER_MIGRATION_TOOL=0
MODULE_WEEKLY_ARMOR_VENDOR_BLACK_MARKET=0
MODULE_WORLD_BOSS_RANK=0
MODULE_WOWDATABASEEDITOR=0
MODULE_WOWLAUNCHER_DELPHI=0
MODULE_WOWSIMS_TO_COMMANDS=0
MODULE_WOW_CLIENT_PATCHER=0
MODULE_WOW_ELUNA_TS_MODULE=0
MODULE_WOW_SERVER_RELAY=0
MODULE_WOW_STATISTICS=0
MODULE_WRATH_OF_THE_VANILLA=0

4
.gitignore vendored
View File

@@ -11,10 +11,12 @@ local-storage/
images/
node_modules/
.mcp*/
scripts/__pycache__/
scripts/__pycache__/*
scripts/python/__pycache__/*
.env
package-lock.json
package.json
todo.md
.gocache/
.module-ledger/
deploy.log

File diff suppressed because it is too large Load Diff

View File

@@ -1,95 +1,345 @@
{
"modules": [
"MODULE_1V1_ARENA",
"MODULE_ACCOUNTWIDE_SYSTEMS",
"MODULE_ACCOUNT_ACHIEVEMENTS",
"MODULE_ACCOUNT_MOUNTS",
"MODULE_ACORE_SUBSCRIPTIONS",
"MODULE_ACTIVE_CHAT",
"MODULE_AHBOT",
"MODULE_AIO",
"MODULE_AIO_BLACKJACK",
"MODULE_ANTIFARMING",
"MODULE_PLAYERBOTS",
"MODULE_AOE_LOOT",
"MODULE_ARAC",
"MODULE_ARENA_REPLAY",
"MODULE_ASSISTANT",
"MODULE_AUTOBALANCE",
"MODULE_AUTO_REVIVE",
"MODULE_AZEROTHSHARD",
"MODULE_BG_SLAVERYVALLEY",
"MODULE_BLACK_MARKET_AUCTION_HOUSE",
"MODULE_BOSS_ANNOUNCER",
"MODULE_BREAKING_NEWS",
"MODULE_CARBON_COPY",
"MODULE_CHALLENGE_MODES",
"MODULE_DISCORD_NOTIFIER",
"MODULE_DUEL_RESET",
"MODULE_DUNGEON_RESPAWN",
"MODULE_DYNAMIC_TRADER",
"MODULE_DYNAMIC_XP",
"MODULE_ELUNA",
"MODULE_ELUNA_SCRIPTS",
"MODULE_ELUNA_TS",
"MODULE_EVENT_SCRIPTS",
"MODULE_EXCHANGE_NPC",
"MODULE_LEARN_SPELLS",
"MODULE_FIREWORKS",
"MODULE_INDIVIDUAL_PROGRESSION",
"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_GLOBAL_CHAT",
"MODULE_ELUNA",
"MODULE_TIME_IS_TIME",
"MODULE_RANDOM_ENCHANTS",
"MODULE_SOLOCRAFT",
"MODULE_PVP_TITLES",
"MODULE_NPC_BEASTMASTER",
"MODULE_NPC_ENCHANTER",
"MODULE_INSTANCE_RESET",
"MODULE_ARAC",
"MODULE_ASSISTANT",
"MODULE_REAGENT_BANK",
"MODULE_BLACK_MARKET_AUCTION_HOUSE",
"MODULE_OLLAMA_CHAT",
"MODULE_PLAYER_BOT_LEVEL_BRACKETS",
"MODULE_SKELETON_MODULE",
"MODULE_BG_SLAVERYVALLEY",
"MODULE_WORGOBLIN",
"MODULE_ELUNA_TS",
"MODULE_AIO",
"MODULE_ELUNA_SCRIPTS",
"MODULE_TRANSMOG_AIO",
"MODULE_EVENT_SCRIPTS",
"MODULE_LEVEL_UP_REWARD",
"MODULE_ACCOUNTWIDE_SYSTEMS",
"MODULE_EXCHANGE_NPC",
"MODULE_RECRUIT_A_FRIEND",
"MODULE_PRESTIGE_DRAFT_MODE",
"MODULE_LUA_AH_BOT",
"MODULE_HARDCORE_MODE",
"MODULE_NPCBOT_EXTENDED_COMMANDS",
"MODULE_TREASURE_CHEST_SYSTEM",
"MODULE_ACTIVE_CHAT",
"MODULE_ULTIMATE_FULL_LOOT_PVP",
"MODULE_HORADRIC_CUBE",
"MODULE_CARBON_COPY",
"MODULE_TEMP_ANNOUNCEMENTS",
"MODULE_ZONE_CHECK",
"MODULE_AIO_BLACKJACK",
"MODULE_SEND_AND_BIND",
"MODULE_DYNAMIC_TRADER",
"MODULE_LOTTERY_LUA",
"MODULE_DISCORD_NOTIFIER",
"MODULE_GLOBAL_MAIL_BANKING_AUCTIONS",
"MODULE_GUILDHOUSE",
"MODULE_HARDCORE_MODE",
"MODULE_HORADRIC_CUBE",
"MODULE_INDIVIDUAL_PROGRESSION",
"MODULE_INSTANCE_RESET",
"MODULE_ITEM_LEVEL_UP",
"MODULE_KEEP_OUT",
"MODULE_LEARN_SPELLS",
"MODULE_LEVEL_GRANT",
"MODULE_LEVEL_UP_REWARD",
"MODULE_LOTTERY_LUA",
"MODULE_LUA_AH_BOT",
"MODULE_MORPHSUMMON",
"MODULE_MULTIVENDOR",
"MODULE_NPCBOT_EXTENDED_COMMANDS",
"MODULE_NPC_BEASTMASTER",
"MODULE_NPC_BUFFER",
"MODULE_NPC_ENCHANTER",
"MODULE_NPC_FREE_PROFESSIONS",
"MODULE_NPC_TALENT_TEMPLATE",
"MODULE_OLLAMA_CHAT",
"MODULE_PHASED_DUELS",
"MODULE_PLAYERBOTS",
"MODULE_PLAYER_BOT_LEVEL_BRACKETS",
"MODULE_POCKET_PORTAL",
"MODULE_PREMIUM",
"MODULE_PRESTIGE_DRAFT_MODE",
"MODULE_PROGRESSION_SYSTEM",
"MODULE_PROMOTION_AZEROTHCORE",
"MODULE_PVP_TITLES",
"MODULE_RANDOM_ENCHANTS",
"MODULE_REAGENT_BANK",
"MODULE_RECRUIT_A_FRIEND",
"MODULE_RESURRECTION_SCROLL",
"MODULE_REWARD_PLAYED_TIME",
"MODULE_SEND_AND_BIND",
"MODULE_SERVER_AUTO_SHUTDOWN",
"MODULE_SOLOCRAFT",
"MODULE_SOLO_LFG",
"MODULE_SYSTEM_VIP",
"MODULE_TEMP_ANNOUNCEMENTS",
"MODULE_TIC_TAC_TOE",
"MODULE_TIME_IS_TIME",
"MODULE_TRANSMOG",
"MODULE_TRANSMOG_AIO",
"MODULE_TREASURE_CHEST_SYSTEM",
"MODULE_ULTIMATE_FULL_LOOT_PVP",
"MODULE_WAR_EFFORT",
"MODULE_NPC_FREE_PROFESSIONS",
"MODULE_DUEL_RESET",
"MODULE_ZONE_DIFFICULTY",
"MODULE_MORPHSUMMON",
"MODULE_SPELL_REGULATOR",
"MODULE_WEEKEND_XP",
"MODULE_REWARD_PLAYED_TIME",
"MODULE_RESURRECTION_SCROLL",
"MODULE_ITEM_LEVEL_UP",
"MODULE_NPC_TALENT_TEMPLATE",
"MODULE_GLOBAL_CHAT",
"MODULE_PREMIUM",
"MODULE_SYSTEM_VIP",
"MODULE_ACORE_SUBSCRIPTIONS",
"MODULE_KEEP_OUT",
"MODULE_SERVER_AUTO_SHUTDOWN",
"MODULE_WHO_LOGGED",
"MODULE_WORGOBLIN",
"MODULE_ZONE_CHECK",
"MODULE_ZONE_DIFFICULTY"
"MODULE_ACCOUNT_MOUNTS",
"MODULE_ANTIFARMING",
"MODULE_ARENA_REPLAY",
"MODULE_TIC_TAC_TOE",
"MODULE_WAR_EFFORT",
"MODULE_PROMOTION_AZEROTHCORE",
"MODULE_MOD_GUILD_VILLAGE",
"MODULE_MOD_CRAFTSPEED",
"MODULE_MOD_AUTOFISH",
"MODULE_MOD_VANILLA_NAXXRAMAS",
"MODULE_MOD_TREASURE",
"MODULE_MOD_REAL_ONLINE",
"MODULE_MOD_INSTANCE_TOOLS",
"MODULE_MOD_LEARNSPELLS",
"MODULE_MOD_SWIFT_TRAVEL_FORM",
"MODULE_MOD_CHAT_TRANSMITTER",
"MODULE_MOD_NOTIFY_MUTED",
"MODULE_MOD_AH_BOT_PLUS",
"MODULE_OPENPROJECTS",
"MODULE_MOD_DUNGEON_SCALE",
"MODULE_AZEROTHCORE_LUA_ARENA_MASTER_COMMAND",
"MODULE_MOD_HARDCORE_MAKGORA",
"MODULE_MOD_GEDDON_BINDING_SHARD",
"MODULE_MOD_GM_COMMANDS",
"MODULE_MOD_GOMOVE",
"MODULE_MOD_FORTIS_AUTOBALANCE",
"MODULE_MOD_MISSING_OBJECTIVES",
"MODULE_MOD_TRIAL_OF_FINALITY",
"MODULE_MOD_HUNTER_PET_STORAGE",
"MODULE_MOD_CHARACTER_SERVICES",
"MODULE_MOD_MOUNT_REQUIREMENTS",
"MODULE_SETXPBAR",
"MODULE_MOD_REWARD_PLAYED_TIME_IMPROVED",
"MODULE_MOD_GROWNUP",
"MODULE_MOD_MYTHIC_PLUS",
"MODULE_MOD_FACTION_FREE",
"MODULE_MOD_FLIGHTMASTER_WHISTLE",
"MODULE_MOD_STARTER_WANDS",
"MODULE_MOD_MOUNTS_ON_ACCOUNT",
"MODULE_MOD_OLLAMA_BOT_BUDDY",
"MODULE_MOD_AOE_LOOT",
"MODULE_MOD_PROFESSION_EXPERIENCE",
"MODULE_MOD_ACCOUNT_VANITY_PETS",
"MODULE_MOD_GAME_STATE_API",
"MODULE_MOD_WEEKEND_XP",
"MODULE_MOD_PEACEKEEPER",
"MODULE_MOD_QUEST_LOOT_PARTY",
"MODULE_MOD_NORDF",
"MODULE_MOD_DISCORD_ANNOUNCE",
"MODULE_MOD_BRAWLERS_GUILD",
"MODULE_MOD_HARDCORE",
"MODULE_MOD_STREAMS",
"MODULE_MOD_BLACK_MARKET",
"MODULE_MOD_TALENTBUTTON",
"MODULE_MOD_SETXPBAR",
"MODULE_MOD_ITEM_UPGRADE",
"MODULE_MOD_LEVEL_REWARDS",
"MODULE_MOD_REFORGING",
"MODULE_MOD_ONY_NAXX_LOGOUT_TELEPORT",
"MODULE_MOD_QUICK_RESPAWN",
"MODULE_MOD_AUTO_RESURRECT",
"MODULE_MOD_IMPROVED_BANK",
"MODULE_MOD_BIENVENIDA",
"MODULE_MOD_NO_HEARTHSTONE_COOLDOWN",
"MODULE_MOD_PTR_TEMPLATE",
"MODULE_MOD_STARTER_GUILD",
"MODULE_MOD_BG_REWARD",
"MODULE_MOD_NPC_MORPH",
"MODULE_MOD_BG_ITEM_REWARD",
"MODULE_MOD_IP_TRACKER",
"MODULE_MOD_DMF_SWITCH",
"MODULE_MOD_BUFF_COMMAND",
"MODULE_MOD_NPC_CODEBOX",
"MODULE_MOD_CHROMIE_XP",
"MODULE_MOD_SELL_ITEMS",
"MODULE_MOD_PVP_ZONES",
"MODULE_MOD_CONGRATS_ON_LEVEL",
"MODULE_MOD_GUILD_ZONE_SYSTEM",
"MODULE_MOD_CTA_SWITCH",
"MODULE_MOD_NPC_SPECTATOR",
"MODULE_MOD_NPC_GAMBLER",
"MODULE_MOD_WEAPON_VISUAL",
"MODULE_MOD_NPC_ALL_MOUNTS",
"MODULE_MOD_RACIAL_TRAIT_SWAP",
"MODULE_MOD_MONEY_FOR_KILLS",
"MODULE_MOD_APPRECIATION",
"MODULE_MOD_HARD_MODES",
"MODULE_MOD_QUEUE_LIST_CACHE",
"MODULE_MOD_PVPSTATS_ANNOUNCER",
"MODULE_MOD_RDF_EXPANSION",
"MODULE_MOD_COSTUMES",
"MODULE_MOD_WEEKENDBONUS",
"MODULE_MOD_JUNK_TO_GOLD",
"MODULE_MOD_DESERTION_WARNINGS",
"MODULE_MOD_LOW_LEVEL_RBG",
"MODULE_PRESTIGE",
"MODULE_HARDMODE",
"MODULE_MOD_LOW_LEVEL_ARENA",
"MODULE_MOD_CFPVE",
"MODULE_MOD_ACCOUNTBOUND",
"MODULE_MOD_DISCORD_WEBHOOK",
"MODULE_MOD_DUNGEONMASTER",
"MODULE_MOD_RESET_RAID_COOLDOWNS",
"MODULE_MOD_INCREMENT_CACHE_VERSION",
"MODULE_MOD_RECRUIT_FRIEND",
"MODULE_MOD_PETEQUIP",
"MODULE_MOD_LOGIN_REWARDS",
"MODULE_MOD_HIGH_RISK_SYSTEM",
"MODULE_MOD_STARTING_PET",
"MODULE_MOD_BG_TWINPEAKS",
"MODULE_MOD_BG_BATTLE_FOR_GILNEAS",
"MODULE_MOD_ARENA_TIGERSPEAK",
"MODULE_MOD_ARENA_TOLVIRON",
"MODULE_MOD_GHOST_SPEED",
"MODULE_MOD_GUILDFUNDS",
"MODULE_BREAKINGNEWSOVERRIDE",
"MODULE_AOE_LOOT_MERGE",
"MODULE_MOD_CHANGEABLESPAWNRATES",
"MODULE_MOD_NOCLIP",
"MODULE_MOD_NPC_SERVICES",
"MODULE_MOD_NPC_PROMOTION",
"MODULE_DEVJOESTAR",
"MODULE_MOD_OBJSCALE",
"MODULE_MOD_WARLOCK_PET_RENAME",
"MODULE_MOD_MULTI_VENDOR",
"MODULE_MOD_DEMONIC_PACT_CLASSIC",
"MODULE_RECYCLEDITEMS",
"MODULE_MOD_NPC_SUBCLASS",
"MODULE_ATTRIBOOST",
"MODULE_PRESTIGIOUS",
"MODULE_RECACHE",
"MODULE_MOD_REWARD_SHOP",
"MODULE_EXTENDEDXP",
"MODULE_MOD_LEVEL_15_BOOST",
"MODULE_BGQUEUECHECKER",
"MODULE_ADDON_FACTION_FREE_UNIT_POPUP",
"MODULE_MOD_ENCOUNTER_LOGS",
"MODULE_MOD_TRADE_ITEMS_FILTER",
"MODULE_MOD_QUEST_STATUS",
"MODULE_MOD_PVPSCRIPT",
"MODULE_ITEMBROADCASTGUILDCHAT",
"MODULE_FFAFIX",
"MODULE_ACI",
"MODULE_RAIDTELEPORTER",
"MODULE_MOD_QUICKBALANCE",
"MODULE_MOD_DEAD_MEANS_DEAD",
"MODULE_MOD_DYNAMIC_LOOT_RATES",
"MODULE_MOD_SHARE_MOUNTS",
"MODULE_MOD_PREMIUM",
"MODULE_MOD_GLOBALCHAT",
"MODULE_MOD_LEECH",
"MODULE_PLAYERTELEPORT",
"MODULE_WRATH_OF_THE_VANILLA",
"MODULE_STATBOOSTERREROLLER",
"MODULE_MOD_SPONSORSHIP",
"MODULE_MOD_PROFSPECS",
"MODULE_UPDATE_MOB_LEVEL_TO_PLAYER_AND_RANDOM_ITEM_STATS",
"MODULE_MOD_PREMIUM_LIB",
"MODULE_MOD_SPAWNPOINTS",
"MODULE_MOD_FIRSTLOGIN_AIO",
"MODULE_KARGATUM_SYSTEM",
"MODULE_MOD_INDIVIDUAL_XP",
"MODULE_MOD_SPEC_REWARD",
"MODULE_MOD_ACTIVATEZONES",
"MODULE_MOD_INFLUXDB",
"MODULE_MOD_SPELLREGULATOR",
"MODULE_MOD_ITEMLEVEL",
"MODULE_MOD_DYNAMIC_RESURRECTIONS",
"MODULE_MOD_ALPHA_REWARDS",
"MODULE_MOD_WHOLOGGED",
"MODULE_REWARD_SYSTEM",
"MODULE_MOD_CHARACTER_TOOLS",
"MODULE_MOD_NO_FARMING",
"MODULE_CODEBASE",
"MODULE_KEIRA3",
"MODULE_ACORE_LXD_IMAGE",
"MODULE_SAHTOUTCMS",
"MODULE_WOWDATABASEEDITOR",
"MODULE_ACREBUILD",
"MODULE_ACORE_CMS",
"MODULE_SERVER_STATUS",
"MODULE_AZEROTHCORE_ARMORY",
"MODULE_ARENA_STATS",
"MODULE_AZEROTHCORE_SERVER_MANAGER",
"MODULE_WOWSIMS_TO_COMMANDS",
"MODULE_UPDATE_MODULE_CONFS",
"MODULE_ACORE_API",
"MODULE_AZEROTHCORE_REGISTRATION_PAGE",
"MODULE_MPQ_TOOLS_OSX",
"MODULE_ACORE_TILEMAP",
"MODULE_AZEROTHCORE_PASSRESET",
"MODULE_FLAG_CHECKER",
"MODULE_WOW_SERVER_RELAY",
"MODULE_ACORE_CLIENT",
"MODULE_AZEROTHCORE_WEBSITE",
"MODULE_PVPSTATS",
"MODULE_SPELLSCRIPT_REFACTOR_TOOL",
"MODULE_STRAPI_AZEROTHCORE",
"MODULE_WOW_ELUNA_TS_MODULE",
"MODULE_WOW_STATISTICS",
"MODULE_AZEROTHCORE_ANSIBLE",
"MODULE_GUILDBANKTABFEEFIXER",
"MODULE_TELEGRAM_AUTOMATED_DB_BACKUP",
"MODULE_AZEROTHCOREDISCORDBOT",
"MODULE_WOW_CLIENT_PATCHER",
"MODULE_BG_QUEUE_ABUSER_VIEWER",
"MODULE_ARENA_SPECTATOR",
"MODULE_TOOL_TC_MIGRATION",
"MODULE_AZEROTHCOREADMIN",
"MODULE_AUTO_CHECK_RESTART",
"MODULE_TRANSMOG_ADDONS",
"MODULE_ACORE_BOX",
"MODULE_NODEROUTER",
"MODULE_WORLD_BOSS_RANK",
"MODULE_APAW",
"MODULE_ACORE_MINI_REG_PAGE",
"MODULE_ACORE_PWA",
"MODULE_MYSQL_TOOLS",
"MODULE_ACORE_LINUX_RESTARTER",
"MODULE_ACORE_NODE_SERVER",
"MODULE_WEB_CHARACTER_MIGRATION_TOOL",
"MODULE_WOWLAUNCHER_DELPHI",
"MODULE_LUA_PARAGON_ANNIVERSARY",
"MODULE_AZEROTHCORE_ADDITIONS",
"MODULE_AZEROTHCORE_LUA_DEMON_MORPHER",
"MODULE_1V1_PVP_SYSTEM",
"MODULE_LUA_PVP_TITLES_RANKING_SYSTEM",
"MODULE_LUA_SCRIPTS",
"MODULE_ACORE_SOD",
"MODULE_CONFIG_RATES",
"MODULE_AZEROTHCORE_TRIVIA_SYSTEM",
"MODULE_LOTTERY_CHANCE_INSTANT",
"MODULE_WEEKLY_ARMOR_VENDOR_BLACK_MARKET",
"MODULE_MORZA_ISLAND_ARAXIA_SERVER",
"MODULE_MOD_DEATHROLL_AIO",
"MODULE_EXTENDED_HOLIDAYS_LUA",
"MODULE_ACORE_LUA_UNLIMITED_AMMO",
"MODULE_LUA_VIP",
"MODULE_AZEROTHCORE_WOWHEAD_MOD_LUA",
"MODULE_ELUNA_WOW_SCRIPTS",
"MODULE_LUA_NOTONLY_RANDOMMORPHER",
"MODULE_LUA_ITEMUPGRADER_TEMPLATE",
"MODULE_LUA_SUPER_BUFFERNPC",
"MODULE_ACORE_ZONEDEBUFF",
"MODULE_ACORE_ELUNATEST",
"MODULE_ACORE_SUMMONALL",
"MODULE_ACORE_BG_END_ANNOUNCER",
"MODULE_LUA_AIO_MODRATE_EXP",
"MODULE_LUA_COMMAND_PLUS",
"MODULE_TBC_RAID_HP_RESTORATION",
"MODULE_MOD_RARE_DROPS",
"MODULE_MOD_LEVEL_ONE_MOUNTS",
"MODULE_BLIZZLIKE_TELES",
"MODULE_SQL_NPC_TELEPORTER",
"MODULE_ACORE_MALL",
"MODULE_HEARTHSTONE_COOLDOWNS",
"MODULE_AZEROTHCORE_ALL_STACKABLES_200",
"MODULE_PORTALS_IN_ALL_CAPITALS",
"MODULE_AZTRAL_AIRLINES",
"MODULE_MOD_IP2NATION",
"MODULE_CLASSIC_MODE"
],
"label": "\ud83e\udde9 All Modules",
"description": "Enable every optional module in the repository",

View File

@@ -40,6 +40,7 @@ services:
- --innodb-log-file-size=${MYSQL_INNODB_LOG_FILE_SIZE}
- --innodb-redo-log-capacity=${MYSQL_INNODB_REDO_LOG_CAPACITY}
restart: unless-stopped
logging:
healthcheck:
test: ["CMD", "sh", "-c", "mysqladmin ping -h localhost -u ${MYSQL_USER} -p${MYSQL_ROOT_PASSWORD} --silent || exit 1"]
interval: ${MYSQL_HEALTHCHECK_INTERVAL}
@@ -477,6 +478,7 @@ services:
ports:
- "${AUTH_EXTERNAL_PORT}:${AUTH_PORT}"
restart: unless-stopped
logging:
networks:
- azerothcore
volumes:
@@ -531,6 +533,7 @@ services:
- ${STORAGE_PATH}/modules:/azerothcore/modules
- ${STORAGE_PATH}/lua_scripts:/azerothcore/lua_scripts
restart: unless-stopped
logging:
networks:
- azerothcore
cap_add: ["SYS_NICE"]
@@ -568,6 +571,11 @@ services:
ports:
- "${AUTH_EXTERNAL_PORT}:${AUTH_PORT}"
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks:
- azerothcore
volumes:
@@ -603,6 +611,7 @@ services:
ports:
- "${AUTH_EXTERNAL_PORT}:${AUTH_PORT}"
restart: unless-stopped
logging:
networks:
- azerothcore
volumes:
@@ -660,6 +669,7 @@ services:
- ${STORAGE_PATH}/modules:/azerothcore/modules
- ${STORAGE_PATH}/lua_scripts:/azerothcore/lua_scripts
restart: unless-stopped
logging:
networks:
- azerothcore
cap_add: ["SYS_NICE"]
@@ -716,6 +726,11 @@ services:
- "${WORLD_EXTERNAL_PORT}:${WORLD_PORT}"
- "${SOAP_EXTERNAL_PORT}:${SOAP_PORT}"
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
cap_add: ["SYS_NICE"]
healthcheck:
test: ["CMD", "sh", "-c", "ps aux | grep '[w]orldserver' | grep -v grep || exit 1"]
@@ -863,10 +878,6 @@ services:
retries: 3
start_period: 40s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
security_opt:
- no-new-privileges:true
networks:

251
docs/MODULE_FAILURES.md Normal file
View File

@@ -0,0 +1,251 @@
# Module Compilation Failures
This document tracks all modules that have been disabled due to compilation failures or other issues during the validation process.
**Last Updated:** 2025-11-22
**Total Blocked Modules:** 93
---
## Compilation Errors
### Virtual Function Override Errors
These modules incorrectly mark non-virtual functions with 'override':
- **MODULE_MOD_ACCOUNTBOUND** - only virtual member functions can be marked 'override'
- **MODULE_MOD_RECYCLEDITEMS** - only virtual member functions can be marked 'override'
- **MODULE_PRESTIGE** - 'OnLogin' marked 'override' but does not override
- **MODULE_PLAYERTELEPORT** - only virtual member functions can be marked 'override'
- **MODULE_ITEMBROADCASTGUILDCHAT** - only virtual member functions can be marked 'override'
- **MODULE_MOD_LOGIN_REWARDS** - only virtual member functions can be marked 'override'
- **MODULE_MOD_NOCLIP** - only virtual member functions can be marked 'override'
- **MODULE_MOD_OBJSCALE** - only virtual member functions can be marked 'override'
- **MODULE_MOD_QUEST_STATUS** - only virtual member functions can be marked 'override'
- **MODULE_MOD_RARE_DROPS** - only virtual member functions can be marked 'override'
- **MODULE_MOD_TRADE_ITEMS_FILTER** - only virtual member functions can be marked 'override'
- **MODULE_MOD_STARTING_PET** - `OnFirstLogin` marked `override` but base method is not virtual
### Missing Member Errors
These modules reference class members that don't exist:
- **MODULE_MOD_FIRSTLOGIN_AIO** - no member named 'getLevel'; did you mean 'GetLevel'?
- **MODULE_MOD_PVPSCRIPT** - no member named 'SendNotification' in 'WorldSession'
- **MODULE_MOD_KARGATUM_SYSTEM** - no member named 'PQuery' / 'outString' in Log
- **MODULE_MOD_ENCOUNTER_LOGS** - no member named 'IsWorldObject' in 'Unit'
- **MODULE_MOD_GOMOVE** - no member named 'DestroyForNearbyPlayers' in 'GameObject'
- **MODULE_MOD_LEVEL_15_BOOST** - no member named 'getLevel' in 'Player'
- **MODULE_MOD_LEVEL_REWARDS** - no member named 'SetStationary' in 'MailDraft'
- **MODULE_MOD_MULTI_VENDOR** - no member named 'SendNotification' in 'WorldSession'
- **MODULE_MOD_OBJSCALE** - no member named 'DestroyForNearbyPlayers' in 'GameObject'
- **MODULE_MOD_TRIAL_OF_FINALITY** - no member named 'isEmpty' in 'MapRefMgr'
- **MODULE_MOD_ALPHA_REWARDS** - no member named 'GetIntDefault' in 'ConfigMgr'
### Incomplete Type Errors
- **MODULE_MOD_ITEMLEVEL** - 'ChatHandler' is an incomplete type
### Undeclared Identifier Errors
- **MODULE_PRESTIGIOUS** - use of undeclared identifier 'sSpellMgr'
### Missing Header/Dependency Errors
- **MODULE_STATBOOSTERREROLLER** - 'StatBoostMgr.h' file not found
---
## Configuration/Build Errors
### CMake/Library Errors
- **MODULE_MOD_INFLUXDB** - CMake Error: Could NOT find CURL
- **MODULE_MOD_DUNGEON_SCALE** - Duplicate symbol definitions for AutoBalance utilities (GetCurrentConfigTime, LoadMapSettings, etc.) when linked with mod-autobalance
- **MODULE_MOD_GAME_STATE_API** - TLS symbol mismatch in cpp-httplib (`HttpGameStateServer.cpp` vs `mod_discord_announce.cpp`) causes linker failure (`error adding symbols: bad value`)
- **MODULE_WOW_STATISTICS** - Missing script loader; `Addwow_statisticsScripts()` referenced by ModulesLoader but not defined
- **MODULE_WOW_CLIENT_PATCHER** - Missing script loader; `Addwow_client_patcherScripts()` referenced by ModulesLoader but not defined
### Missing Script Loader / Non-C++ Modules
These repositories are Lua scripts or external web tools without a worldserver loader. When they are flagged as C++ modules the build fails with undefined references during linking:
- **MODULE_MOD_DISCORD_WEBHOOK** - No `Addmod_discord_webhookScripts()` implementation
- **MODULE_BG_QUEUE_ABUSER_VIEWER** - No `AddBG_Queue_Abuser_ViewerScripts()` implementation
- **MODULE_ACORE_API** - No `Addacore_apiScripts()` implementation
- **MODULE_ACORE_CLIENT** - No `Addacore_clientScripts()` implementation
- **MODULE_ACORE_CMS** - No `Addacore_cmsScripts()` implementation
- **MODULE_ACORE_NODE_SERVER** - No `Addacore_node_serverScripts()` implementation
- **MODULE_ACORE_PWA** - No `Addacore_pwaScripts()` implementation
- **MODULE_ACORE_TILEMAP** - No `Addacore_tilemapScripts()` implementation
- **MODULE_APAW** - No `AddapawScripts()` implementation
- **MODULE_ARENA_STATS** - No `Addarena_statsScripts()` implementation
- **MODULE_AZEROTHCORE_ARMORY** - No `Addazerothcore_armoryScripts()` implementation
- **MODULE_LUA_ITEMUPGRADER_TEMPLATE** - Lua-only script; no `Addlua_ItemUpgrader_TemplateScripts()`
- **MODULE_LUA_NOTONLY_RANDOMMORPHER** - Lua-only script; no `Addlua_NotOnly_RandomMorpherScripts()`
- **MODULE_LUA_SUPER_BUFFERNPC** - Lua-only script; no `Addlua_Super_BufferNPCScripts()`
- **MODULE_LUA_PARAGON_ANNIVERSARY** - Lua-only script; no `Addlua_paragon_anniversaryScripts()`
### SQL Import Errors (Runtime)
- **MODULE_MOD_REWARD_SHOP** - `npc.sql` references obsolete `modelid1` column during db-import
- **MODULE_BLACK_MARKET_AUCTION_HOUSE** - `MODULE_mod-black-market_creature.sql` references removed `StatsCount` column (ERROR 1054 at line 14, causes worldserver crash-loop)
- **MODULE_MOD_GUILD_VILLAGE** - `MODULE_mod-guild-village_001_creature_template.sql` tries to insert duplicate creature ID 987400 (ERROR 1062: Duplicate entry for key 'creature_template.PRIMARY')
- **MODULE_MOD_INSTANCE_TOOLS** - `MODULE_mod-instance-tools_Creature.sql` tries to insert duplicate creature ID 987456-0 (ERROR 1062: Duplicate entry for key 'creature_template_model.PRIMARY')
- **MODULE_ACORE_SUBSCRIPTIONS** - C++ code queries missing table `acore_auth.acore_cms_subscriptions` (ERROR 1146: Table doesn't exist, causes server ABORT)
- **Resolution Required:** Module directory at `local-storage/modules/mod-acore-subscriptions` must be removed and worldserver rebuilt. Disabling in .env alone is insufficient because the code is already compiled into the binary.
- **Process:** Either (1) remove module directory + rebuild, OR (2) create the missing database table/schema
- **MODULE_NODEROUTER** - No `AddnoderouterScripts()` implementation
- **MODULE_SERVER_STATUS** - No `Addserver_statusScripts()` implementation
- **MODULE_WORLD_BOSS_RANK** - No `Addworld_boss_rankScripts()` implementation
---
## Auto-Disabled Modules (Outdated)
These modules have not been updated in over 2 years and were automatically disabled:
- **MODULE_MOD_DYNAMIC_RESURRECTIONS** - Last updated: 2019-07-16
- **MODULE_MOD_WHOLOGGED** - Last updated: 2018-07-03
- **MODULE_REWARD_SYSTEM** - Last updated: 2018-07-02
- **MODULE_MOD_CHARACTER_TOOLS** - Last updated: 2018-07-02
- **MODULE_MOD_NO_FARMING** - Last updated: 2018-05-15
---
## Git/Clone Errors
- **MODULE_ELUNA_WOW_SCRIPTS** - Git clone error: unknown switch 'E'
---
## Summary by Error Type
| Error Type | Count | Common Cause |
|------------|-------|--------------|
| Virtual function override | 11 | API changes in AzerothCore hooks |
| Missing members | 11 | API changes - methods renamed/removed |
| Incomplete type | 1 | Missing include or forward declaration |
| Undeclared identifier | 1 | Missing include or API change |
| Missing headers | 1 | Module dependency missing |
| CMake/Library | 1 | External dependency not available |
| Outdated (>2yr) | 5 | Module unmaintained |
| Git errors | 1 | Repository/clone issues |
**Total:** 66 blocked modules
---
## Resolution Status
All blocked modules have been:
- ✅ Disabled in `.env` file
- ✅ Marked as 'blocked' in `config/module-manifest.json`
- ✅ Block reason documented in manifest
- ✅ Notes added to manifest with error details
---
## Runtime Validation Process
When worldserver crashes or fails to start due to modules:
1. **Check for crash-loops**: Use `docker inspect ac-worldserver --format='RestartCount: {{.RestartCount}}'`
- RestartCount > 0 indicates crash-loop, not a healthy running state
2. **Examine logs**: `docker logs ac-worldserver --tail 200 | grep -B 10 "ABORT"`
- Look for ERROR messages, ABORT signals, and stack traces
- Identify the failing module from error context
3. **Categorize the error**:
- **SQL Import Errors**: Table/column doesn't exist, duplicate keys
- **Missing Database Tables**: C++ code queries tables that don't exist
- **Configuration Issues**: Missing required config files or settings
4. **For modules with compiled C++ code querying missing DB tables**:
- **Important**: Disabling in `.env` is NOT sufficient - code is already compiled
- **Resolution Options**:
a. Remove module directory from `local-storage/modules/` + rebuild (preferred for broken modules)
b. Create the missing database table/schema (if you want to keep the module)
- Never use `sudo rm -rf` on module directories without explicit user approval
- Document the issue clearly before taking action
5. **For SQL import errors**:
- Disable module in `.env`
- Remove problematic SQL files from container: `docker exec ac-worldserver rm -f /path/to/sql/file.sql`
- Restart worldserver (no rebuild needed for SQL-only issues)
6. **For Lua-only modules** (scripts without C++ components):
- **Important**: Disabling Lua modules may leave behind database artifacts
- Lua modules often create:
- Custom database tables (in acore_world, acore_characters, or acore_auth)
- Stored procedures, triggers, or events
- NPC/creature/gameobject entries in world tables
- **SQL Cleanup Required**: When disabling Lua modules, you may need to:
a. Identify tables/data created by the module (check module's SQL files)
b. Manually DROP tables or DELETE entries if the module doesn't provide cleanup scripts
c. Check for orphaned NPCs/creatures that reference the module's functionality
- **Best Practice**: Before disabling, review the module's `data/sql/` directory to understand what was installed
6. **Update documentation**:
- Add entry to MODULE_FAILURES.md
- Update module-manifest.json with block_reason
- Increment total blocked modules count
7. **Verify fix**: Restart worldserver and confirm RestartCount stays at 0
---
## SQL Update System & Database Maintenance
### Our Implementation
This deployment uses AzerothCore's built-in SQL update system with the following structure:
- **Module SQL Location**: Each module places SQL files in `/azerothcore/data/sql/updates/db-world/`, `db-auth/`, or `db-characters/`
- **Automatic Import**: On worldserver startup, AzerothCore scans these directories and applies any SQL files not yet in the `updates` tracking table
- **One-Time Execution**: SQL files are tracked in the `updates` table to prevent re-execution
- **Persistent Storage**: SQL files are mounted from `local-storage/modules/*/data/sql/` into the container
### AzerothCore Wiki Reference
Per the [AzerothCore Keeping the Server Up to Date](https://www.azerothcore.org/wiki/keeping-the-server-up-to-date) documentation:
- Core updates include SQL changes that must be applied to databases
- The server automatically imports SQL files from `data/sql/updates/` directories
- Failed SQL imports cause the server to ABORT (as seen with our module validation)
- Database structure must match what the C++ code expects
### Module SQL Lifecycle
1. **Installation**: Module's SQL files copied to container's `/azerothcore/data/sql/updates/` during build
2. **First Startup**: Files executed and tracked in `updates` table
3. **Subsequent Startups**: Files skipped (already in `updates` table)
4. **Module Disabled**: SQL files may persist in container unless manually removed
5. **Database Artifacts**: Tables/data created by SQL remain until manually cleaned up
### Critical Notes
- **Disabling a module does NOT remove its SQL files** from the container
- **Disabling a module does NOT drop its database tables** or remove its data
- **Problematic SQL files must be manually removed** from the container after disabling the module
- **Database cleanup is manual** - no automatic rollback when modules are disabled
- **Lua modules** especially prone to leaving orphaned database artifacts (tables, NPCs, gameobjects)
### Troubleshooting SQL Issues
When a module's SQL import fails:
1. **Error in logs**: Server logs show which SQL file failed and the MySQL error
2. **Server ABORTs**: Failed imports cause server to abort startup
3. **Resolution**:
- Disable module in `.env`
- Remove problematic SQL file from container: `docker exec ac-worldserver rm -f /path/to/file.sql`
- Restart server (file won't be re-imported since it's deleted)
- **OR** if you want to keep the module: Fix the SQL file in `local-storage/modules/*/data/sql/` and rebuild
## Next Steps
1. Continue build/deploy cycle until all compilation errors resolved
2. Monitor for additional module failures
3. Document any new failures as they occur
4. Consider creating GitHub issues for maintainable modules with API incompatibilities

View File

@@ -0,0 +1,265 @@
#!/usr/bin/env bash
#
# cleanup-orphaned-sql.sh
#
# Cleans up orphaned SQL update entries from the database.
# These are entries in the 'updates' table that reference files no longer on disk.
#
# This happens when:
# - Modules are removed/uninstalled
# - Modules are updated and old SQL files are deleted
# - Manual SQL cleanup occurs
#
# NOTE: These warnings are informational and don't affect server operation.
# This script is optional - it just cleans up the logs.
#
set -euo pipefail
# Configuration
MYSQL_CONTAINER="${MYSQL_CONTAINER:-ac-mysql}"
WORLDSERVER_CONTAINER="${WORLDSERVER_CONTAINER:-ac-worldserver}"
MYSQL_USER="${MYSQL_USER:-root}"
MYSQL_PASSWORD="${MYSQL_ROOT_PASSWORD:-}"
DRY_RUN=false
VERBOSE=false
DATABASES=("acore_world" "acore_characters" "acore_auth")
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Usage
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Clean up orphaned SQL update entries from AzerothCore databases.
OPTIONS:
-p, --password PASSWORD MySQL root password (or use MYSQL_ROOT_PASSWORD env var)
-c, --container NAME MySQL container name (default: ac-mysql)
-w, --worldserver NAME Worldserver container name (default: ac-worldserver)
-d, --database DB Clean only specific database (world, characters, auth)
-n, --dry-run Show what would be cleaned without making changes
-v, --verbose Show detailed output
-h, --help Show this help message
EXAMPLES:
# Dry run to see what would be cleaned
$0 --dry-run
# Clean all databases
$0 --password yourpassword
# Clean only world database
$0 --password yourpassword --database world
# Verbose output
$0 --password yourpassword --verbose
NOTES:
- This script only removes entries from the 'updates' table
- It does NOT remove any actual data or tables
- It does NOT reverse any SQL that was applied
- This is safe to run and only cleans up tracking metadata
- Orphaned entries occur when modules are removed/updated
EOF
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-p|--password)
MYSQL_PASSWORD="$2"
shift 2
;;
-c|--container)
MYSQL_CONTAINER="$2"
shift 2
;;
-w|--worldserver)
WORLDSERVER_CONTAINER="$2"
shift 2
;;
-d|--database)
case $2 in
world) DATABASES=("acore_world") ;;
characters) DATABASES=("acore_characters") ;;
auth) DATABASES=("acore_auth") ;;
*) echo -e "${RED}Error: Invalid database '$2'${NC}"; exit 1 ;;
esac
shift 2
;;
-n|--dry-run)
DRY_RUN=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
usage
;;
*)
echo -e "${RED}Error: Unknown option '$1'${NC}"
usage
;;
esac
done
# Check password
if [[ -z "$MYSQL_PASSWORD" ]]; then
echo -e "${RED}Error: MySQL password required${NC}"
echo "Use --password or set MYSQL_ROOT_PASSWORD environment variable"
exit 1
fi
# Check containers exist
if ! docker ps --format '{{.Names}}' | grep -q "^${MYSQL_CONTAINER}$"; then
echo -e "${RED}Error: MySQL container '$MYSQL_CONTAINER' not found or not running${NC}"
exit 1
fi
if ! docker ps --format '{{.Names}}' | grep -q "^${WORLDSERVER_CONTAINER}$"; then
echo -e "${RED}Error: Worldserver container '$WORLDSERVER_CONTAINER' not found or not running${NC}"
exit 1
fi
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ AzerothCore Orphaned SQL Cleanup ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
echo
if [[ "$DRY_RUN" == true ]]; then
echo -e "${YELLOW}DRY RUN MODE - No changes will be made${NC}"
echo
fi
# Function to get SQL files from worldserver container
get_sql_files() {
local db_type=$1
docker exec "$WORLDSERVER_CONTAINER" find "/azerothcore/data/sql/updates/${db_type}/" -name "*.sql" -type f 2>/dev/null | \
xargs -I {} basename {} 2>/dev/null || true
}
# Function to clean orphaned entries
clean_orphaned_entries() {
local database=$1
local db_type=$2
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}Processing: $database${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# Get list of SQL files on disk
local sql_files
sql_files=$(get_sql_files "$db_type")
if [[ -z "$sql_files" ]]; then
echo -e "${YELLOW}⚠ No SQL files found in /azerothcore/data/sql/updates/${db_type}/${NC}"
echo
return
fi
local file_count
file_count=$(echo "$sql_files" | wc -l)
echo -e "📁 Found ${file_count} SQL files on disk"
# Get entries from updates table
local total_updates
total_updates=$(docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$database" -sN \
-e "SELECT COUNT(*) FROM updates" 2>/dev/null || echo "0")
echo -e "📊 Total updates in database: ${total_updates}"
if [[ "$total_updates" == "0" ]]; then
echo -e "${YELLOW}⚠ No updates found in database${NC}"
echo
return
fi
# Find orphaned entries (in DB but not on disk)
# We'll create a temp table with file names and do a LEFT JOIN
local orphaned_count=0
local orphaned_list=""
# Get all update names from DB
local db_updates
db_updates=$(docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$database" -sN \
-e "SELECT name FROM updates ORDER BY name" 2>/dev/null || true)
if [[ -n "$db_updates" ]]; then
# Check each DB entry against disk files
while IFS= read -r update_name; do
if ! echo "$sql_files" | grep -qF "$update_name"; then
((orphaned_count++))
if [[ "$VERBOSE" == true ]] || [[ "$DRY_RUN" == true ]]; then
orphaned_list="${orphaned_list}${update_name}\n"
fi
# Delete if not dry run
if [[ "$DRY_RUN" == false ]]; then
docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$database" -e \
"DELETE FROM updates WHERE name='${update_name}'" 2>/dev/null
fi
fi
done <<< "$db_updates"
fi
# Report results
if [[ $orphaned_count -gt 0 ]]; then
echo -e "${YELLOW}🗑️ Orphaned entries: ${orphaned_count}${NC}"
if [[ "$VERBOSE" == true ]] || [[ "$DRY_RUN" == true ]]; then
echo
echo -e "${YELLOW}Orphaned files:${NC}"
echo -e "$orphaned_list" | head -20
if [[ $orphaned_count -gt 20 ]]; then
echo -e "${YELLOW}... and $((orphaned_count - 20)) more${NC}"
fi
fi
if [[ "$DRY_RUN" == false ]]; then
echo -e "${GREEN}✅ Cleaned ${orphaned_count} orphaned entries${NC}"
else
echo -e "${YELLOW}Would clean ${orphaned_count} orphaned entries${NC}"
fi
else
echo -e "${GREEN}✅ No orphaned entries found${NC}"
fi
echo
}
# Process each database
for db in "${DATABASES[@]}"; do
case $db in
acore_world)
clean_orphaned_entries "$db" "db_world"
;;
acore_characters)
clean_orphaned_entries "$db" "db_characters"
;;
acore_auth)
clean_orphaned_entries "$db" "db_auth"
;;
esac
done
# Summary
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}Cleanup Complete${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
if [[ "$DRY_RUN" == true ]]; then
echo
echo -e "${YELLOW}This was a dry run. To actually clean orphaned entries, run:${NC}"
echo -e "${YELLOW} $0 --password yourpassword${NC}"
fi

View File

@@ -1,57 +1,167 @@
#!/bin/bash
# Fix item import for backup-merged characters
#
# Usage:
# fix-item-import.sh [OPTIONS]
#
# Options:
# --backup-dir DIR Path to backup directory (required)
# --account-ids IDS Comma-separated account IDs (e.g., "451,452")
# --char-guids GUIDS Comma-separated character GUIDs (e.g., "4501,4502,4503")
# --mysql-password PW MySQL root password (or use MYSQL_ROOT_PASSWORD env var)
# --mysql-container NAME MySQL container name (default: ac-mysql)
# --auth-db NAME Auth database name (default: acore_auth)
# --characters-db NAME Characters database name (default: acore_characters)
# -h, --help Show this help message
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
COLOR_RED='\033[0;31m'
COLOR_GREEN='\033[0;32m'
COLOR_YELLOW='\033[1;33m'
COLOR_BLUE='\033[0;34m'
COLOR_CYAN='\033[0;36m'
COLOR_RESET='\033[0m'
# Source common library
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
source "$SCRIPT_DIR/lib/common.sh"
else
echo "ERROR: Common library not found at $SCRIPT_DIR/lib/common.sh" >&2
exit 1
fi
log(){ printf '%b\n' "${COLOR_GREEN}$*${COLOR_RESET}"; }
info(){ printf '%b\n' "${COLOR_CYAN}$*${COLOR_RESET}"; }
warn(){ printf '%b\n' "${COLOR_YELLOW}$*${COLOR_RESET}"; }
err(){ printf '%b\n' "${COLOR_RED}$*${COLOR_RESET}"; }
fatal(){ err "$*"; exit 1; }
# Default values (can be overridden by environment or command line)
BACKUP_DIR="${BACKUP_DIR:-}"
ACCOUNT_IDS="${ACCOUNT_IDS:-}"
CHAR_GUIDS="${CHAR_GUIDS:-}"
MYSQL_PW="${MYSQL_ROOT_PASSWORD:-}"
MYSQL_CONTAINER="${MYSQL_CONTAINER:-ac-mysql}"
AUTH_DB="${AUTH_DB:-acore_auth}"
CHARACTERS_DB="${CHARACTERS_DB:-acore_characters}"
MYSQL_PW="azerothcore123"
BACKUP_DIR="/nfs/containers/ac-backup"
AUTH_DB="acore_auth"
CHARACTERS_DB="acore_characters"
# Show help message
show_help() {
cat << EOF
Fix item import for backup-merged characters
# Verify parameters
[[ -d "$BACKUP_DIR" ]] || fatal "Backup directory not found: $BACKUP_DIR"
Usage:
fix-item-import.sh [OPTIONS]
Options:
--backup-dir DIR Path to backup directory (required)
--account-ids IDS Comma-separated account IDs (e.g., "451,452")
--char-guids GUIDS Comma-separated character GUIDs (e.g., "4501,4502,4503")
--mysql-password PW MySQL root password (or use MYSQL_ROOT_PASSWORD env var)
--mysql-container NAME MySQL container name (default: ac-mysql)
--auth-db NAME Auth database name (default: acore_auth)
--characters-db NAME Characters database name (default: acore_characters)
-h, --help Show this help message
Environment Variables:
BACKUP_DIR Alternative to --backup-dir
ACCOUNT_IDS Alternative to --account-ids
CHAR_GUIDS Alternative to --char-guids
MYSQL_ROOT_PASSWORD Alternative to --mysql-password
MYSQL_CONTAINER Alternative to --mysql-container
AUTH_DB Alternative to --auth-db
CHARACTERS_DB Alternative to --characters-db
Example:
fix-item-import.sh \\
--backup-dir /path/to/backup \\
--account-ids "451,452" \\
--char-guids "4501,4502,4503" \\
--mysql-password "azerothcore123"
EOF
exit 0
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--backup-dir)
BACKUP_DIR="$2"
shift 2
;;
--account-ids)
ACCOUNT_IDS="$2"
shift 2
;;
--char-guids)
CHAR_GUIDS="$2"
shift 2
;;
--mysql-password)
MYSQL_PW="$2"
shift 2
;;
--mysql-container)
MYSQL_CONTAINER="$2"
shift 2
;;
--auth-db)
AUTH_DB="$2"
shift 2
;;
--characters-db)
CHARACTERS_DB="$2"
shift 2
;;
-h|--help)
show_help
;;
*)
fatal "Unknown option: $1\nUse --help for usage information"
;;
esac
done
# Validate required parameters
if [ -z "$BACKUP_DIR" ]; then
fatal "Backup directory not specified. Use --backup-dir or set BACKUP_DIR environment variable."
fi
if [ ! -d "$BACKUP_DIR" ]; then
fatal "Backup directory not found: $BACKUP_DIR"
fi
if [ -z "$ACCOUNT_IDS" ]; then
fatal "Account IDs not specified. Use --account-ids or set ACCOUNT_IDS environment variable."
fi
if [ -z "$CHAR_GUIDS" ]; then
fatal "Character GUIDs not specified. Use --char-guids or set CHAR_GUIDS environment variable."
fi
if [ -z "$MYSQL_PW" ]; then
fatal "MySQL password not specified. Use --mysql-password or set MYSQL_ROOT_PASSWORD environment variable."
fi
# Setup temp directory
TEMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TEMP_DIR"' EXIT
# MySQL connection helper
mysql_exec(){
# MySQL connection helpers (override common.sh defaults with script-specific values)
mysql_exec_local(){
local db="$1"
docker exec -i ac-mysql mysql -uroot -p"$MYSQL_PW" "$db" 2>/dev/null
docker exec -i "$MYSQL_CONTAINER" mysql -uroot -p"$MYSQL_PW" "$db" 2>/dev/null
}
mysql_query(){
mysql_query_local(){
local db="$1"
local query="$2"
docker exec ac-mysql mysql -uroot -p"$MYSQL_PW" -N -B "$db" -e "$query" 2>/dev/null
docker exec "$MYSQL_CONTAINER" mysql -uroot -p"$MYSQL_PW" -N -B "$db" -e "$query" 2>/dev/null
}
log "═══════════════════════════════════════════════════════════"
log " FIXING ITEM IMPORT FOR BACKUP-MERGED CHARACTERS"
log "═══════════════════════════════════════════════════════════"
# Find characters that were imported from the backup (accounts 451, 452)
# Find characters that were imported from the backup
log "Finding characters that need item restoration..."
IMPORTED_CHARS=$(mysql_query "$CHARACTERS_DB" "SELECT name, guid FROM characters WHERE account IN (451, 452);")
info "Looking for characters with account IDs: $ACCOUNT_IDS"
IMPORTED_CHARS=$(mysql_query_local "$CHARACTERS_DB" "SELECT name, guid FROM characters WHERE account IN ($ACCOUNT_IDS);")
if [[ -z "$IMPORTED_CHARS" ]]; then
fatal "No imported characters found (accounts 451, 452)"
fatal "No imported characters found with account IDs: $ACCOUNT_IDS"
fi
info "Found imported characters:"
@@ -60,7 +170,8 @@ echo "$IMPORTED_CHARS" | while read -r char_name char_guid; do
done
# Check current item count for these characters
CURRENT_ITEM_COUNT=$(mysql_query "$CHARACTERS_DB" "SELECT COUNT(*) FROM item_instance WHERE owner_guid IN (4501, 4502, 4503);")
info "Checking existing items for character GUIDs: $CHAR_GUIDS"
CURRENT_ITEM_COUNT=$(mysql_query_local "$CHARACTERS_DB" "SELECT COUNT(*) FROM item_instance WHERE owner_guid IN ($CHAR_GUIDS);")
info "Current items for imported characters: $CURRENT_ITEM_COUNT"
if [[ "$CURRENT_ITEM_COUNT" != "0" ]]; then
@@ -94,26 +205,26 @@ log "Creating staging database..."
STAGE_CHARS_DB="fix_stage_chars_$$"
# Drop any existing staging database
docker exec ac-mysql mysql -uroot -p"$MYSQL_PW" -e "DROP DATABASE IF EXISTS $STAGE_CHARS_DB;" 2>/dev/null || true
docker exec "$MYSQL_CONTAINER" mysql -uroot -p"$MYSQL_PW" -e "DROP DATABASE IF EXISTS $STAGE_CHARS_DB;" 2>/dev/null || true
# Create staging database
docker exec ac-mysql mysql -uroot -p"$MYSQL_PW" -e "CREATE DATABASE $STAGE_CHARS_DB;" 2>/dev/null
docker exec "$MYSQL_CONTAINER" mysql -uroot -p"$MYSQL_PW" -e "CREATE DATABASE $STAGE_CHARS_DB;" 2>/dev/null
# Cleanup staging database on exit
cleanup_staging(){
if [[ -n "${STAGE_CHARS_DB:-}" ]]; then
docker exec ac-mysql mysql -uroot -p"$MYSQL_PW" -e "DROP DATABASE IF EXISTS $STAGE_CHARS_DB;" 2>/dev/null || true
docker exec "$MYSQL_CONTAINER" mysql -uroot -p"$MYSQL_PW" -e "DROP DATABASE IF EXISTS $STAGE_CHARS_DB;" 2>/dev/null || true
fi
}
trap 'cleanup_staging; rm -rf "$TEMP_DIR"' EXIT
# Load backup into staging database
info "Loading backup into staging database..."
sed "s/\`acore_characters\`/\`$STAGE_CHARS_DB\`/g; s/USE \`acore_characters\`;/USE \`$STAGE_CHARS_DB\`;/g" "$TEMP_DIR/characters.sql" | \
docker exec -i ac-mysql mysql -uroot -p"$MYSQL_PW" 2>/dev/null
sed "s/\`$CHARACTERS_DB\`/\`$STAGE_CHARS_DB\`/g; s/USE \`$CHARACTERS_DB\`;/USE \`$STAGE_CHARS_DB\`;/g" "$TEMP_DIR/characters.sql" | \
docker exec -i "$MYSQL_CONTAINER" mysql -uroot -p"$MYSQL_PW" 2>/dev/null
# Get current database state
CURRENT_MAX_ITEM_GUID=$(mysql_query "$CHARACTERS_DB" "SELECT COALESCE(MAX(guid), 0) FROM item_instance;")
CURRENT_MAX_ITEM_GUID=$(mysql_query_local "$CHARACTERS_DB" "SELECT COALESCE(MAX(guid), 0) FROM item_instance;")
ITEM_OFFSET=$((CURRENT_MAX_ITEM_GUID + 10000))
info "Current max item GUID: $CURRENT_MAX_ITEM_GUID"
@@ -121,22 +232,32 @@ info "Item GUID offset: +$ITEM_OFFSET"
# Create character mapping for the imported characters
log "Creating character mapping..."
mysql_exec "$STAGE_CHARS_DB" <<EOF
info "Building character GUID mapping from staging database..."
# Create mapping table dynamically based on imported characters
mysql_exec_local "$STAGE_CHARS_DB" <<EOF
CREATE TABLE character_guid_map (
old_guid INT UNSIGNED PRIMARY KEY,
new_guid INT UNSIGNED,
name VARCHAR(12)
);
EOF
# Populate mapping by matching character names from staging to current database
# This assumes character names are unique identifiers
mysql_exec_local "$STAGE_CHARS_DB" <<EOF
INSERT INTO character_guid_map (old_guid, new_guid, name)
VALUES
(1, 4501, 'Artimage'),
(2, 4502, 'Flombey'),
(3, 4503, 'Hammertime');
SELECT
s.guid as old_guid,
c.guid as new_guid,
c.name
FROM $STAGE_CHARS_DB.characters s
JOIN $CHARACTERS_DB.characters c ON s.name = c.name
WHERE c.account IN ($ACCOUNT_IDS);
EOF
# Create item GUID mapping
mysql_exec "$STAGE_CHARS_DB" <<EOF
mysql_exec_local "$STAGE_CHARS_DB" <<EOF
CREATE TABLE item_guid_map (
old_guid INT UNSIGNED PRIMARY KEY,
new_guid INT UNSIGNED,
@@ -153,7 +274,7 @@ INNER JOIN character_guid_map cm ON i.owner_guid = cm.old_guid;
EOF
# Check how many items will be imported
ITEMS_TO_IMPORT=$(mysql_query "$STAGE_CHARS_DB" "SELECT COUNT(*) FROM item_guid_map;")
ITEMS_TO_IMPORT=$(mysql_query_local "$STAGE_CHARS_DB" "SELECT COUNT(*) FROM item_guid_map;")
info "Items to import: $ITEMS_TO_IMPORT"
if [[ "$ITEMS_TO_IMPORT" == "0" ]]; then
@@ -195,7 +316,7 @@ EOSQL
)
ITEM_SQL_EXPANDED=$(echo "$ITEM_SQL" | sed "s/STAGE_CHARS_DB/$STAGE_CHARS_DB/g")
ITEM_RESULT=$(echo "$ITEM_SQL_EXPANDED" | docker exec -i ac-mysql mysql -uroot -p"$MYSQL_PW" "$CHARACTERS_DB" 2>&1)
ITEM_RESULT=$(echo "$ITEM_SQL_EXPANDED" | docker exec -i "$MYSQL_CONTAINER" mysql -uroot -p"$MYSQL_PW" "$CHARACTERS_DB" 2>&1)
if echo "$ITEM_RESULT" | grep -q "ERROR"; then
err "Item import failed:"
echo "$ITEM_RESULT" | grep "ERROR" >&2
@@ -217,7 +338,7 @@ EOSQL
)
INV_SQL_EXPANDED=$(echo "$INV_SQL" | sed "s/STAGE_CHARS_DB/$STAGE_CHARS_DB/g")
INV_RESULT=$(echo "$INV_SQL_EXPANDED" | docker exec -i ac-mysql mysql -uroot -p"$MYSQL_PW" "$CHARACTERS_DB" 2>&1)
INV_RESULT=$(echo "$INV_SQL_EXPANDED" | docker exec -i "$MYSQL_CONTAINER" mysql -uroot -p"$MYSQL_PW" "$CHARACTERS_DB" 2>&1)
if echo "$INV_RESULT" | grep -q "ERROR"; then
err "Inventory import failed:"
echo "$INV_RESULT" | grep "ERROR" >&2
@@ -225,8 +346,8 @@ if echo "$INV_RESULT" | grep -q "ERROR"; then
fi
# Report counts
ITEMS_IMPORTED=$(mysql_query "$CHARACTERS_DB" "SELECT COUNT(*) FROM item_instance WHERE owner_guid IN (4501, 4502, 4503);")
INV_IMPORTED=$(mysql_query "$CHARACTERS_DB" "SELECT COUNT(*) FROM character_inventory WHERE guid IN (4501, 4502, 4503);")
ITEMS_IMPORTED=$(mysql_query_local "$CHARACTERS_DB" "SELECT COUNT(*) FROM item_instance WHERE owner_guid IN ($CHAR_GUIDS);")
INV_IMPORTED=$(mysql_query_local "$CHARACTERS_DB" "SELECT COUNT(*) FROM character_inventory WHERE guid IN ($CHAR_GUIDS);")
info "Items imported: $ITEMS_IMPORTED"
info "Inventory slots imported: $INV_IMPORTED"

423
scripts/bash/lib/common.sh Normal file
View File

@@ -0,0 +1,423 @@
#!/bin/bash
#
# Common utilities library for AzerothCore RealmMaster scripts
# This library provides shared functions for environment variable reading,
# logging, error handling, and other common operations.
#
# Usage: source /path/to/scripts/bash/lib/common.sh
# Prevent multiple sourcing
if [ -n "${_COMMON_LIB_LOADED:-}" ]; then
return 0
fi
_COMMON_LIB_LOADED=1
# =============================================================================
# COLOR DEFINITIONS (Standardized across all scripts)
# =============================================================================
BLUE='\033[0;34m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Legacy color names for backward compatibility
COLOR_BLUE="$BLUE"
COLOR_GREEN="$GREEN"
COLOR_YELLOW="$YELLOW"
COLOR_RED="$RED"
COLOR_CYAN="$CYAN"
COLOR_RESET="$NC"
# =============================================================================
# LOGGING FUNCTIONS (Standardized with emoji)
# =============================================================================
# Log informational messages (blue with info icon)
info() {
printf '%b\n' "${BLUE} $*${NC}"
}
# Log success messages (green with checkmark)
ok() {
printf '%b\n' "${GREEN}$*${NC}"
}
# Log general messages (green, no icon - for clean output)
log() {
printf '%b\n' "${GREEN}$*${NC}"
}
# Log warning messages (yellow with warning icon)
warn() {
printf '%b\n' "${YELLOW}⚠️ $*${NC}"
}
# Log error messages (red with error icon, continues execution)
err() {
printf '%b\n' "${RED}$*${NC}" >&2
}
# Log fatal error and exit (red with error icon, exits with code 1)
fatal() {
printf '%b\n' "${RED}$*${NC}" >&2
exit 1
}
# =============================================================================
# ENVIRONMENT VARIABLE READING
# =============================================================================
# Read environment variable from .env file with fallback to default
# Handles various quote styles, comments, and whitespace
#
# Usage:
# read_env KEY [DEFAULT_VALUE]
# value=$(read_env "MYSQL_PASSWORD" "default_password")
#
# Features:
# - Reads from file specified by $ENV_PATH (or $DEFAULT_ENV_PATH)
# - Strips leading/trailing whitespace
# - Removes inline comments (everything after #)
# - Handles double quotes, single quotes, and unquoted values
# - Returns default value if key not found
# - Returns value from environment variable if already set
#
read_env() {
local key="$1"
local default="${2:-}"
local value=""
# Check if variable is already set in environment (takes precedence)
if [ -n "${!key:-}" ]; then
echo "${!key}"
return 0
fi
# Determine which .env file to use
local env_file="${ENV_PATH:-${DEFAULT_ENV_PATH:-}}"
# Read from .env file if it exists
if [ -f "$env_file" ]; then
# Extract value using grep and cut, handling various formats
value="$(grep -E "^${key}=" "$env_file" 2>/dev/null | tail -n1 | cut -d'=' -f2- | tr -d '\r')"
# Remove inline comments (everything after # that's not inside quotes)
# This is a simplified approach - doesn't handle quotes perfectly but works for most cases
value="$(echo "$value" | sed 's/[[:space:]]*#.*//' | sed 's/[[:space:]]*$//')"
# Strip quotes if present
if [[ "$value" == \"*\" && "$value" == *\" ]]; then
# Double quotes
value="${value:1:-1}"
elif [[ "$value" == \'*\' && "$value" == *\' ]]; then
# Single quotes
value="${value:1:-1}"
fi
fi
# Use default if still empty
if [ -z "${value:-}" ]; then
value="$default"
fi
printf '%s\n' "${value}"
}
# Read value from .env.template file (used during setup)
# This is similar to read_env but specifically for template files
#
# Usage:
# get_template_value KEY [TEMPLATE_FILE]
# value=$(get_template_value "MYSQL_PASSWORD")
#
get_template_value() {
local key="$1"
local template_file="${2:-${TEMPLATE_FILE:-${TEMPLATE_PATH:-.env.template}}}"
if [ ! -f "$template_file" ]; then
fatal "Template file not found: $template_file"
fi
# Extract value, handling variable expansion syntax like ${VAR:-default}
local value
local raw_line
raw_line=$(grep "^${key}=" "$template_file" 2>/dev/null | head -1)
if [ -z "$raw_line" ]; then
err "Key '$key' not found in template: $template_file"
return 1
fi
value="${raw_line#*=}"
value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/')
# Handle ${VAR:-default} syntax by extracting the default value
if [[ "$value" =~ ^\$\{[^}]*:-([^}]*)\}$ ]]; then
value="${BASH_REMATCH[1]}"
fi
echo "$value"
}
# Update or add environment variable in .env file
# Creates file if it doesn't exist
#
# Usage:
# update_env_value KEY VALUE [ENV_FILE]
# update_env_value "MYSQL_PASSWORD" "new_password"
#
update_env_value() {
local key="$1"
local value="$2"
local env_file="${3:-${ENV_PATH:-${DEFAULT_ENV_PATH:-.env}}}"
[ -n "$env_file" ] || return 0
# Create file if it doesn't exist
if [ ! -f "$env_file" ]; then
printf '%s=%s\n' "$key" "$value" >> "$env_file"
return 0
fi
# Update existing or append new
if grep -q "^${key}=" "$env_file"; then
# Use platform-appropriate sed in-place editing
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s|^${key}=.*|${key}=${value}|" "$env_file"
else
sed -i "s|^${key}=.*|${key}=${value}|" "$env_file"
fi
else
printf '\n%s=%s\n' "$key" "$value" >> "$env_file"
fi
}
# =============================================================================
# VALIDATION & REQUIREMENTS
# =============================================================================
# Require command to be available in PATH, exit with error if not found
#
# Usage:
# require_cmd docker
# require_cmd python3 jq git
#
require_cmd() {
for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
fatal "Missing required command: $cmd"
fi
done
}
# Check if command exists (returns 0 if exists, 1 if not)
#
# Usage:
# if has_cmd docker; then
# echo "Docker is available"
# fi
#
has_cmd() {
command -v "$1" >/dev/null 2>&1
}
# =============================================================================
# MYSQL/DATABASE HELPERS
# =============================================================================
# Execute MySQL command in Docker container
# Reads MYSQL_PW and container name from environment
#
# Usage:
# mysql_exec DATABASE_NAME < script.sql
# echo "SELECT 1;" | mysql_exec acore_auth
#
mysql_exec() {
local db="$1"
local mysql_pw="${MYSQL_ROOT_PASSWORD:-${MYSQL_PW:-azerothcore}}"
local container="${MYSQL_CONTAINER:-ac-mysql}"
docker exec -i "$container" mysql -uroot -p"$mysql_pw" "$db"
}
# Execute MySQL query and return result
# Outputs in non-tabular format suitable for parsing
#
# Usage:
# count=$(mysql_query "acore_characters" "SELECT COUNT(*) FROM characters")
#
mysql_query() {
local db="$1"
local query="$2"
local mysql_pw="${MYSQL_ROOT_PASSWORD:-${MYSQL_PW:-azerothcore}}"
local container="${MYSQL_CONTAINER:-ac-mysql}"
docker exec "$container" mysql -uroot -p"$mysql_pw" -N -B "$db" -e "$query" 2>/dev/null
}
# Check if MySQL container is healthy and accepting connections
#
# Usage:
# if mysql_is_ready; then
# echo "MySQL is ready"
# fi
#
mysql_is_ready() {
local container="${MYSQL_CONTAINER:-ac-mysql}"
local mysql_pw="${MYSQL_ROOT_PASSWORD:-${MYSQL_PW:-azerothcore}}"
docker exec "$container" mysqladmin ping -uroot -p"$mysql_pw" >/dev/null 2>&1
}
# Wait for MySQL to be ready with timeout
#
# Usage:
# mysql_wait_ready 60 # Wait up to 60 seconds
#
mysql_wait_ready() {
local timeout="${1:-30}"
local elapsed=0
info "Waiting for MySQL to be ready..."
while [ $elapsed -lt $timeout ]; do
if mysql_is_ready; then
ok "MySQL is ready"
return 0
fi
sleep 2
elapsed=$((elapsed + 2))
done
err "MySQL did not become ready within ${timeout}s"
return 1
}
# =============================================================================
# FILE & DIRECTORY HELPERS
# =============================================================================
# Ensure directory exists and is writable
# Creates directory if needed and sets permissions
#
# Usage:
# ensure_writable_dir /path/to/directory
#
ensure_writable_dir() {
local dir="$1"
if [ ! -d "$dir" ]; then
mkdir -p "$dir" 2>/dev/null || {
err "Failed to create directory: $dir"
return 1
}
fi
if [ ! -w "$dir" ]; then
chmod u+w "$dir" 2>/dev/null || {
err "Directory not writable: $dir"
return 1
}
fi
return 0
}
# Create backup of file before modification
#
# Usage:
# backup_file /path/to/important.conf
# # Creates /path/to/important.conf.backup.TIMESTAMP
#
backup_file() {
local file="$1"
if [ ! -f "$file" ]; then
warn "File does not exist, skipping backup: $file"
return 0
fi
local backup="${file}.backup.$(date +%Y%m%d_%H%M%S)"
cp "$file" "$backup" || {
err "Failed to create backup: $backup"
return 1
}
info "Created backup: $backup"
return 0
}
# =============================================================================
# GIT HELPERS
# =============================================================================
# Configure git identity if not already set
#
# Usage:
# setup_git_config [USERNAME] [EMAIL]
#
setup_git_config() {
local git_user="${1:-${GIT_USERNAME:-AzerothCore RealmMaster}}"
local git_email="${2:-${GIT_EMAIL:-noreply@azerothcore.org}}"
if ! git config --global user.name >/dev/null 2>&1; then
info "Configuring git identity: $git_user <$git_email>"
git config --global user.name "$git_user" || true
git config --global user.email "$git_email" || true
fi
}
# =============================================================================
# ERROR HANDLING UTILITIES
# =============================================================================
# Retry command with exponential backoff
#
# Usage:
# retry 5 docker pull myimage:latest
# retry 3 2 mysql_query "acore_auth" "SELECT 1" # 3 retries with 2s initial delay
#
retry() {
local max_attempts="$1"
shift
local delay="${1:-1}"
# Check if delay is a number, if not treat it as part of the command
if ! [[ "$delay" =~ ^[0-9]+$ ]]; then
delay=1
else
shift
fi
local attempt=1
local exit_code=0
while [ $attempt -le "$max_attempts" ]; do
if "$@"; then
return 0
fi
exit_code=$?
if [ $attempt -lt "$max_attempts" ]; then
warn "Command failed (attempt $attempt/$max_attempts), retrying in ${delay}s..."
sleep "$delay"
delay=$((delay * 2)) # Exponential backoff
fi
attempt=$((attempt + 1))
done
err "Command failed after $max_attempts attempts"
return $exit_code
}
# =============================================================================
# INITIALIZATION
# =============================================================================
# Library loaded successfully
# Scripts can check for $_COMMON_LIB_LOADED to verify library is loaded

View File

@@ -7,52 +7,36 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Source common library for shared functions
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
source "$SCRIPT_DIR/lib/common.sh"
else
echo "ERROR: Common library not found at $SCRIPT_DIR/lib/common.sh" >&2
exit 1
fi
# Source project name helper
source "$PROJECT_ROOT/scripts/bash/project_name.sh"
# Module-specific configuration
MODULE_HELPER="$PROJECT_ROOT/scripts/python/modules.py"
DEFAULT_ENV_PATH="$PROJECT_ROOT/.env"
ENV_PATH="${MODULES_ENV_PATH:-$DEFAULT_ENV_PATH}"
TEMPLATE_FILE="$PROJECT_ROOT/.env.template"
source "$PROJECT_ROOT/scripts/bash/project_name.sh"
# Default project name (read from .env or template)
DEFAULT_PROJECT_NAME="$(project_name::resolve "$ENV_PATH" "$TEMPLATE_FILE")"
BLUE='\033[0;34m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
# Module-specific state
PLAYERBOTS_DB_UPDATE_LOGGED=0
info(){ printf '%b\n' "${BLUE} $*${NC}"; }
ok(){ printf '%b\n' "${GREEN}$*${NC}"; }
warn(){ printf '%b\n' "${YELLOW}⚠️ $*${NC}"; }
err(){ printf '%b\n' "${RED}$*${NC}"; exit 1; }
# Declare module metadata arrays globally at script level
declare -A MODULE_NAME MODULE_REPO MODULE_REF MODULE_TYPE MODULE_ENABLED MODULE_NEEDS_BUILD MODULE_BLOCKED MODULE_POST_INSTALL MODULE_REQUIRES MODULE_CONFIG_CLEANUP MODULE_NOTES MODULE_STATUS MODULE_BLOCK_REASON
declare -a MODULE_KEYS
read_env_value(){
local key="$1" default="${2:-}" value="${!key:-}"
if [ -n "$value" ]; then
echo "$value"
return
fi
if [ -f "$ENV_PATH" ]; then
value="$(grep -E "^${key}=" "$ENV_PATH" 2>/dev/null | tail -n1 | cut -d'=' -f2- | tr -d '\r')"
value="$(echo "$value" | sed 's/[[:space:]]*#.*//' | sed 's/[[:space:]]*$//')"
if [[ "$value" == \"*\" && "$value" == *\" ]]; then
value="${value:1:-1}"
elif [[ "$value" == \'*\' && "$value" == *\' ]]; then
value="${value:1:-1}"
fi
fi
if [ -z "${value:-}" ]; then
value="$default"
fi
printf '%s\n' "${value}"
}
ensure_python(){
if ! command -v python3 >/dev/null 2>&1; then
err "python3 is required but not installed in PATH"
fi
}
# Ensure Python is available
require_cmd python3
resolve_manifest_path(){
if [ -n "${MODULES_MANIFEST_PATH:-}" ] && [ -f "${MODULES_MANIFEST_PATH}" ]; then
@@ -567,10 +551,10 @@ track_module_state(){
}
main(){
ensure_python
# Python is already checked at script start via require_cmd
if [ "${MODULES_LOCAL_RUN:-0}" != "1" ]; then
cd /modules || err "Modules directory /modules not found"
cd /modules || fatal "Modules directory /modules not found"
fi
MODULES_ROOT="$(pwd)"

View File

@@ -39,10 +39,6 @@ ensure_host_writable(){
fi
}
seed_sql_ledger_if_needed(){
: # No-op; ledger removed
}
sync_local_staging(){
local src_root="$LOCAL_STORAGE_PATH"
local dest_root="$STORAGE_PATH"

View File

@@ -31,54 +31,127 @@ def parse_bool(value: str) -> bool:
def load_env_file(env_path: Path) -> Dict[str, str]:
"""
Load environment variables from .env file.
Args:
env_path: Path to .env file
Returns:
Dictionary of environment variable key-value pairs
Note:
Returns empty dict if file doesn't exist (not an error).
Handles quotes, comments, and export statements.
"""
if not env_path.exists():
return {}
env: Dict[str, str] = {}
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
try:
content = env_path.read_text(encoding="utf-8")
except Exception as e:
print(f"Warning: Failed to read environment file {env_path}: {e}", file=sys.stderr)
return {}
for line_num, raw_line in enumerate(content.splitlines(), start=1):
line = raw_line.strip()
# Skip empty lines and comments
if not line or line.startswith("#"):
continue
# Remove 'export' prefix if present
if line.startswith("export "):
line = line[len("export ") :].strip()
# Skip lines without '='
if "=" not in line:
continue
try:
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
# Strip quotes
if value.startswith('"') and value.endswith('"'):
value = value[1:-1]
elif value.startswith("'") and value.endswith("'"):
value = value[1:-1]
env[key] = value
except Exception as e:
print(
f"Warning: Failed to parse line {line_num} in {env_path}: {raw_line}\n"
f" Error: {e}",
file=sys.stderr
)
continue
return env
def load_manifest(manifest_path: Path) -> List[Dict[str, object]]:
"""
Load and validate module manifest from JSON file.
Args:
manifest_path: Path to module-manifest.json file
Returns:
List of validated module dictionaries
Raises:
FileNotFoundError: If manifest file doesn't exist
json.JSONDecodeError: If manifest is not valid JSON
ValueError: If manifest structure is invalid
"""
if not manifest_path.exists():
raise FileNotFoundError(f"Manifest file not found: {manifest_path}")
try:
with manifest_path.open("r", encoding="utf-8") as fh:
manifest = json.load(fh)
except json.JSONDecodeError as e:
raise ValueError(
f"Invalid JSON in manifest file {manifest_path}:\n"
f" Line {e.lineno}, Column {e.colno}: {e.msg}"
) from e
except Exception as e:
raise ValueError(f"Failed to read manifest file {manifest_path}: {e}") from e
modules = manifest.get("modules")
if not isinstance(modules, list):
raise ValueError("Manifest must define a top-level 'modules' array")
validated: List[Dict[str, object]] = []
seen_keys: set[str] = set()
for entry in modules:
for idx, entry in enumerate(modules):
if not isinstance(entry, dict):
raise ValueError("Each manifest entry must be an object")
raise ValueError(f"Manifest entry at index {idx} must be an object")
key = entry.get("key")
name = entry.get("name")
repo = entry.get("repo")
if not key or not isinstance(key, str):
raise ValueError("Manifest entry missing 'key'")
raise ValueError(f"Manifest entry at index {idx} missing 'key'")
if key in seen_keys:
raise ValueError(f"Duplicate manifest key detected: {key}")
raise ValueError(f"Duplicate manifest key detected: '{key}' (at index {idx})")
seen_keys.add(key)
if not name or not isinstance(name, str):
raise ValueError(f"Manifest entry {key} missing 'name'")
raise ValueError(f"Manifest entry '{key}' missing 'name' field")
if not repo or not isinstance(repo, str):
raise ValueError(f"Manifest entry {key} missing 'repo'")
raise ValueError(f"Manifest entry '{key}' missing 'repo' field")
validated.append(entry)
return validated

View File

@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Generate a categorized list of GitHub modules missing from the manifest.
The script reuses the discovery logic from ``update_module_manifest.py`` to
fetch repositories by topic, filters out entries already tracked in
``config/module-manifest.json`` and writes the remainder (including type,
category, and inferred dependency hints) to a JSON file.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Dict, Iterable, List, Sequence, Tuple
from update_module_manifest import ( # type: ignore
CATEGORY_BY_TYPE,
DEFAULT_TOPICS,
GitHubClient,
collect_repositories,
load_manifest,
normalize_repo_url,
repo_name_to_key,
)
# heuristics used to surface potential dependency hints
DEPENDENCY_KEYWORDS: Tuple[Tuple[str, str], ...] = (
("playerbot", "MODULE_PLAYERBOTS"),
("ah-bot", "MODULE_PLAYERBOTS"),
("eluna", "MODULE_ELUNA"),
)
# keywords that help categorize entries that should probably stay hidden by default
SUPPRESSION_KEYWORDS: Tuple[Tuple[str, str], ...] = (
("virtual machine", "vm"),
(" vm ", "vm"),
(" docker", "docker"),
("container", "docker"),
("vagrant", "vagrant"),
("ansible", "automation"),
("terraform", "automation"),
("client", "client-distribution"),
("launcher", "client-distribution"),
)
def parse_args(argv: Sequence[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--manifest",
default="config/module-manifest.json",
help="Path to module manifest JSON (default: %(default)s)",
)
parser.add_argument(
"--output",
default="missing-modules.json",
help="Path to write the missing-module report JSON (default: %(default)s)",
)
parser.add_argument(
"--topic",
action="append",
default=[],
dest="topics",
help="GitHub topic (or '+' expression) to scan (defaults to built-in list).",
)
parser.add_argument(
"--max-pages",
type=int,
default=10,
help="Maximum pages (x100 results) to fetch per topic (default: %(default)s)",
)
parser.add_argument(
"--token",
help="GitHub API token (defaults to $GITHUB_TOKEN or $GITHUB_API_TOKEN)",
)
parser.add_argument(
"--log",
action="store_true",
help="Print verbose progress information",
)
return parser.parse_args(argv)
def implied_dependencies(module_type: str, text: str) -> List[str]:
deps: List[str] = []
if module_type == "lua":
deps.append("MODULE_ELUNA")
normalized = text.lower()
for keyword, dep in DEPENDENCY_KEYWORDS:
if keyword in normalized and dep not in deps:
deps.append(dep)
return deps
def suppression_flags(category: str, text: str) -> List[str]:
flags: List[str] = []
if category == "tooling":
flags.append("tooling")
normalized = text.lower()
for keyword, flag in SUPPRESSION_KEYWORDS:
if keyword in normalized and flag not in flags:
flags.append(flag)
return flags
def make_missing_entries(
manifest_modules: List[dict],
repos: Iterable,
) -> List[dict]:
by_key: Dict[str, dict] = {module.get("key"): module for module in manifest_modules if module.get("key")}
by_repo: Dict[str, dict] = {
normalize_repo_url(str(module.get("repo", ""))): module
for module in manifest_modules
if module.get("repo")
}
missing: List[dict] = []
for record in repos:
repo = record.data
repo_url = normalize_repo_url(repo.get("clone_url") or repo.get("html_url") or "")
existing = by_repo.get(repo_url)
key = repo_name_to_key(repo.get("name", ""))
if not existing:
existing = by_key.get(key)
if existing:
continue
module_type = record.module_type
category = CATEGORY_BY_TYPE.get(module_type, "uncategorized")
description = repo.get("description") or ""
combined_text = " ".join(
filter(
None,
[
repo.get("full_name"),
description,
" ".join(repo.get("topics") or []),
],
)
)
entry = {
"key": key,
"repo_name": repo.get("full_name"),
"topic": record.topic_expr,
"repo_url": repo.get("html_url") or repo.get("clone_url"),
"description": description,
"topics": repo.get("topics") or [],
"type": module_type,
"category": category,
"implied_dependencies": implied_dependencies(module_type, combined_text),
"flags": suppression_flags(category, combined_text),
}
missing.append(entry)
missing.sort(key=lambda item: item["key"])
return missing
def main(argv: Sequence[str]) -> int:
args = parse_args(argv)
topics = args.topics or DEFAULT_TOPICS
token = args.token or os.environ.get("GITHUB_TOKEN") or os.environ.get("GITHUB_API_TOKEN")
if not token:
print(
"Warning: no GitHub token provided, falling back to anonymous rate limit",
file=sys.stderr,
)
client = GitHubClient(token, verbose=args.log)
manifest = load_manifest(args.manifest)
repos = collect_repositories(client, topics, args.max_pages)
missing = make_missing_entries(manifest.get("modules", []), repos)
output_path = Path(args.output)
output_path.write_text(json.dumps(missing, indent=2))
print(f"Wrote {len(missing)} entries to {output_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))

View File

@@ -518,6 +518,39 @@ auto_enable_module_dependencies() {
done
}
ensure_module_platforms() {
local needs_platform=0
local key
for key in "${MODULE_KEYS[@]}"; do
case "$key" in
MODULE_ELUNA|MODULE_AIO) continue ;;
esac
local value
eval "value=\${$key:-0}"
if [ "$value" = "1" ]; then
needs_platform=1
break
fi
done
if [ "$needs_platform" != "1" ]; then
return 0
fi
local platform
for platform in MODULE_ELUNA MODULE_AIO; do
[ -n "${KNOWN_MODULE_LOOKUP[$platform]:-}" ] || continue
local platform_value
eval "platform_value=\${$platform:-0}"
if [ "$platform_value" != "1" ]; then
local platform_name="${MODULE_NAME_MAP[$platform]:-${platform#MODULE_}}"
say INFO "Automatically enabling ${platform_name} to support selected modules."
printf -v "$platform" '%s' "1"
MODULE_ENABLE_SET["$platform"]=1
fi
done
return 0
}
show_realm_configured(){
echo -e "\n${GREEN}⚔️ Your realm configuration has been forged! ⚔️${NC}"
@@ -1160,6 +1193,7 @@ fi
done
auto_enable_module_dependencies
ensure_module_platforms
if [ "${MODULE_OLLAMA_CHAT:-0}" = "1" ] && [ "${MODULE_PLAYERBOTS:-0}" != "1" ]; then
say INFO "Automatically enabling MODULE_PLAYERBOTS for MODULE_OLLAMA_CHAT."
@@ -1326,6 +1360,7 @@ fi
fi
auto_enable_module_dependencies
ensure_module_platforms
if [ -n "$CLI_PLAYERBOT_ENABLED" ]; then
if [[ "$CLI_PLAYERBOT_ENABLED" != "0" && "$CLI_PLAYERBOT_ENABLED" != "1" ]]; then

350
updates-dry-run.json Normal file
View File

@@ -0,0 +1,350 @@
[
{
"key": "MODULE_INDIVIDUAL_PROGRESSION",
"repo_name": "ZhengPeiRu21/mod-individual-progression",
"topic": "azerothcore-module",
"repo_url": "https://github.com/ZhengPeiRu21/mod-individual-progression"
},
{
"key": "MODULE_PLAYERBOTS",
"repo_name": "mod-playerbots/mod-playerbots",
"topic": "azerothcore-module",
"repo_url": "https://github.com/mod-playerbots/mod-playerbots"
},
{
"key": "MODULE_OLLAMA_CHAT",
"repo_name": "DustinHendrickson/mod-ollama-chat",
"topic": "azerothcore-module",
"repo_url": "https://github.com/DustinHendrickson/mod-ollama-chat"
},
{
"key": "MODULE_PLAYER_BOT_LEVEL_BRACKETS",
"repo_name": "DustinHendrickson/mod-player-bot-level-brackets",
"topic": "azerothcore-module",
"repo_url": "https://github.com/DustinHendrickson/mod-player-bot-level-brackets"
},
{
"key": "MODULE_DUEL_RESET",
"repo_name": "azerothcore/mod-duel-reset",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-duel-reset"
},
{
"key": "MODULE_AOE_LOOT",
"repo_name": "azerothcore/mod-aoe-loot",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-aoe-loot"
},
{
"key": "MODULE_TIC_TAC_TOE",
"repo_name": "azerothcore/mod-tic-tac-toe",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-tic-tac-toe"
},
{
"key": "MODULE_NPC_BEASTMASTER",
"repo_name": "azerothcore/mod-npc-beastmaster",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-npc-beastmaster"
},
{
"key": "MODULE_MORPHSUMMON",
"repo_name": "azerothcore/mod-morphsummon",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-morphsummon"
},
{
"key": "MODULE_WORGOBLIN",
"repo_name": "heyitsbench/mod-worgoblin",
"topic": "azerothcore-module",
"repo_url": "https://github.com/heyitsbench/mod-worgoblin"
},
{
"key": "MODULE_SKELETON_MODULE",
"repo_name": "azerothcore/skeleton-module",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/skeleton-module"
},
{
"key": "MODULE_AUTOBALANCE",
"repo_name": "azerothcore/mod-autobalance",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-autobalance"
},
{
"key": "MODULE_TRANSMOG",
"repo_name": "azerothcore/mod-transmog",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-transmog"
},
{
"key": "MODULE_ARAC",
"repo_name": "heyitsbench/mod-arac",
"topic": "azerothcore-module",
"repo_url": "https://github.com/heyitsbench/mod-arac"
},
{
"key": "MODULE_GLOBAL_CHAT",
"repo_name": "azerothcore/mod-global-chat",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-global-chat"
},
{
"key": "MODULE_PRESTIGE_DRAFT_MODE",
"repo_name": "Youpeoples/Prestige-and-Draft-Mode",
"topic": "azerothcore-module",
"repo_url": "https://github.com/Youpeoples/Prestige-and-Draft-Mode"
},
{
"key": "MODULE_BLACK_MARKET_AUCTION_HOUSE",
"repo_name": "Youpeoples/Black-Market-Auction-House",
"topic": "azerothcore-module",
"repo_url": "https://github.com/Youpeoples/Black-Market-Auction-House"
},
{
"key": "MODULE_ULTIMATE_FULL_LOOT_PVP",
"repo_name": "Youpeoples/Ultimate-Full-Loot-Pvp",
"topic": "azerothcore-module",
"repo_url": "https://github.com/Youpeoples/Ultimate-Full-Loot-Pvp"
},
{
"key": "MODULE_SERVER_AUTO_SHUTDOWN",
"repo_name": "azerothcore/mod-server-auto-shutdown",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-server-auto-shutdown"
},
{
"key": "MODULE_TIME_IS_TIME",
"repo_name": "dunjeon/mod-TimeIsTime",
"topic": "azerothcore-module",
"repo_url": "https://github.com/dunjeon/mod-TimeIsTime"
},
{
"key": "MODULE_WAR_EFFORT",
"repo_name": "azerothcore/mod-war-effort",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-war-effort"
},
{
"key": "MODULE_FIREWORKS",
"repo_name": "azerothcore/mod-fireworks-on-level",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-fireworks-on-level"
},
{
"key": "MODULE_NPC_ENCHANTER",
"repo_name": "azerothcore/mod-npc-enchanter",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-npc-enchanter"
},
{
"key": "MODULE_NPC_BUFFER",
"repo_name": "azerothcore/mod-npc-buffer",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-npc-buffer"
},
{
"key": "MODULE_PVP_TITLES",
"repo_name": "azerothcore/mod-pvp-titles",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-pvp-titles"
},
{
"key": "MODULE_CHALLENGE_MODES",
"repo_name": "ZhengPeiRu21/mod-challenge-modes",
"topic": "azerothcore-module",
"repo_url": "https://github.com/ZhengPeiRu21/mod-challenge-modes"
},
{
"key": "MODULE_TREASURE_CHEST_SYSTEM",
"repo_name": "zyggy123/Treasure-Chest-System",
"topic": "azerothcore-module",
"repo_url": "https://github.com/zyggy123/Treasure-Chest-System"
},
{
"key": "MODULE_ASSISTANT",
"repo_name": "noisiver/mod-assistant",
"topic": "azerothcore-module",
"repo_url": "https://github.com/noisiver/mod-assistant"
},
{
"key": "MODULE_STATBOOSTER",
"repo_name": "AnchyDev/StatBooster",
"topic": "azerothcore-module",
"repo_url": "https://github.com/AnchyDev/StatBooster"
},
{
"key": "MODULE_BG_SLAVERYVALLEY",
"repo_name": "Helias/mod-bg-slaveryvalley",
"topic": "azerothcore-module",
"repo_url": "https://github.com/Helias/mod-bg-slaveryvalley"
},
{
"key": "MODULE_REAGENT_BANK",
"repo_name": "ZhengPeiRu21/mod-reagent-bank",
"topic": "azerothcore-module",
"repo_url": "https://github.com/ZhengPeiRu21/mod-reagent-bank"
},
{
"key": "MODULE_ELUNA_TS",
"repo_name": "azerothcore/eluna-ts",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/eluna-ts"
},
{
"key": "MODULE_AZEROTHSHARD",
"repo_name": "azerothcore/mod-azerothshard",
"topic": "azerothcore-module",
"repo_url": "https://github.com/azerothcore/mod-azerothshard"
},
{
"key": "MODULE_LEVEL_GRANT",
"repo_name": "michaeldelago/mod-quest-count-level",
"topic": "azerothcore-module",
"repo_url": "https://github.com/michaeldelago/mod-quest-count-level"
},
{
"key": "MODULE_DUNGEON_RESPAWN",
"repo_name": "AnchyDev/DungeonRespawn",
"topic": "azerothcore-module",
"repo_url": "https://github.com/AnchyDev/DungeonRespawn"
},
{
"key": "MODULE_LUA_AH_BOT",
"repo_name": "mostlynick3/azerothcore-lua-ah-bot",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/mostlynick3/azerothcore-lua-ah-bot"
},
{
"key": "MODULE_ACCOUNTWIDE_SYSTEMS",
"repo_name": "Aldori15/azerothcore-eluna-accountwide",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/Aldori15/azerothcore-eluna-accountwide"
},
{
"key": "MODULE_ELUNA_SCRIPTS",
"repo_name": "Isidorsson/Eluna-scripts",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/Isidorsson/Eluna-scripts"
},
{
"key": "MODULE_TRANSMOG_AIO",
"repo_name": "DanieltheDeveloper/azerothcore-transmog-3.3.5a",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/DanieltheDeveloper/azerothcore-transmog-3.3.5a"
},
{
"key": "MODULE_HARDCORE_MODE",
"repo_name": "PrivateDonut/hardcore_mode",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/PrivateDonut/hardcore_mode"
},
{
"key": "MODULE_RECRUIT_A_FRIEND",
"repo_name": "55Honey/Acore_RecruitAFriend",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/55Honey/Acore_RecruitAFriend"
},
{
"key": "MODULE_EVENT_SCRIPTS",
"repo_name": "55Honey/Acore_eventScripts",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/55Honey/Acore_eventScripts"
},
{
"key": "MODULE_LOTTERY_LUA",
"repo_name": "zyggy123/lottery-lua",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/zyggy123/lottery-lua"
},
{
"key": "MODULE_HORADRIC_CUBE",
"repo_name": "TITIaio/Horadric-Cube-for-World-of-Warcraft",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/TITIaio/Horadric-Cube-for-World-of-Warcraft"
},
{
"key": "MODULE_GLOBAL_MAIL_BANKING_AUCTIONS",
"repo_name": "Aldori15/azerothcore-global-mail_banking_auctions",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/Aldori15/azerothcore-global-mail_banking_auctions"
},
{
"key": "MODULE_LEVEL_UP_REWARD",
"repo_name": "55Honey/Acore_LevelUpReward",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/55Honey/Acore_LevelUpReward"
},
{
"key": "MODULE_AIO_BLACKJACK",
"repo_name": "Manmadedrummer/AIO-Blackjack",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/Manmadedrummer/AIO-Blackjack"
},
{
"key": "MODULE_NPCBOT_EXTENDED_COMMANDS",
"repo_name": "Day36512/Npcbot_Extended_Commands",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/Day36512/Npcbot_Extended_Commands"
},
{
"key": "MODULE_ACTIVE_CHAT",
"repo_name": "Day36512/ActiveChat",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/Day36512/ActiveChat"
},
{
"key": "MODULE_MULTIVENDOR",
"repo_name": "Shadowveil-WotLK/AzerothCore-lua-MultiVendor",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/Shadowveil-WotLK/AzerothCore-lua-MultiVendor"
},
{
"key": "MODULE_EXCHANGE_NPC",
"repo_name": "55Honey/Acore_ExchangeNpc",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/55Honey/Acore_ExchangeNpc"
},
{
"key": "MODULE_DYNAMIC_TRADER",
"repo_name": "Day36512/Dynamic-Trader",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/Day36512/Dynamic-Trader"
},
{
"key": "MODULE_DISCORD_NOTIFIER",
"repo_name": "0xCiBeR/Acore_DiscordNotifier",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/0xCiBeR/Acore_DiscordNotifier"
},
{
"key": "MODULE_ZONE_CHECK",
"repo_name": "55Honey/Acore_Zonecheck",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/55Honey/Acore_Zonecheck"
},
{
"key": "MODULE_HARDCORE_MODE",
"repo_name": "HellionOP/Lua-HardcoreMode",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/HellionOP/Lua-HardcoreMode"
},
{
"key": "MODULE_SEND_AND_BIND",
"repo_name": "55Honey/Acore_SendAndBind",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/55Honey/Acore_SendAndBind"
},
{
"key": "MODULE_TEMP_ANNOUNCEMENTS",
"repo_name": "55Honey/Acore_TempAnnouncements",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/55Honey/Acore_TempAnnouncements"
},
{
"key": "MODULE_CARBON_COPY",
"repo_name": "55Honey/Acore_CarbonCopy",
"topic": "azerothcore-lua",
"repo_url": "https://github.com/55Honey/Acore_CarbonCopy"
}
]