Files
moonwell-core/import-custom-sql.sh
2026-08-07 14:09:56 +04:00

379 lines
12 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
if [[ -f "$ENV_FILE" ]]; then
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
fi
IMPORT_MYTHICPLUS_SQL="${ACORE_IMPORT_MYTHICPLUS_SQL:-1}"
IMPORT_STORE_SQL="${ACORE_IMPORT_STORE_SQL:-1}"
IMPORT_ENCOUNTER_JOURNAL_SQL="${ACORE_IMPORT_ENCOUNTER_JOURNAL_SQL:-1}"
PLAYERBOTS_RUSSIAN_NAMES="${ACORE_PLAYERBOTS_RUSSIAN_NAMES:-1}"
usage() {
cat <<EOF
Usage: $(basename "$0")
Imports required custom SQL that lives outside the normal AzerothCore module paths.
Currently this bootstraps the MythicPlus and Store Lua packages, plus module strings
and playerbot localization data not covered by the normal module SQL import path
in this deployment.
EOF
}
log() {
printf '[import-custom-sql] %s\n' "$*"
}
mysql_query() {
local database="$1"
local sql="$2"
docker compose exec -T ac-database bash -lc \
"mysql -N -s -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \"$database\"" <<<"$sql"
}
mysql_exec() {
local database="${1:-}"
local sql="$2"
if [[ -n "$database" ]]; then
docker compose exec -T ac-database bash -lc \
"mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \"$database\"" <<<"$sql"
else
docker compose exec -T ac-database bash -lc \
"mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\"" <<<"$sql"
fi
}
table_exists() {
local database="$1"
local table_name="$2"
local result
result="$(mysql_query "information_schema" \
"SELECT COUNT(*) FROM tables WHERE table_schema = '${database}' AND table_name = '${table_name}';")"
[[ "$result" == "1" ]]
}
database_exists() {
local database="$1"
local result
result="$(mysql_query "information_schema" \
"SELECT COUNT(*) FROM schemata WHERE schema_name = '${database}';")"
[[ "$result" == "1" ]]
}
import_sql_file() {
local database="$1"
local file_path="$2"
if [[ ! -f "$file_path" ]]; then
printf 'SQL file not found: %s\n' "$file_path" >&2
exit 1
fi
log "importing $(basename "$file_path") into ${database}"
docker compose exec -T ac-database bash -lc \
"mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \"$database\"" < "$file_path"
}
require_integer_flag() {
local name="$1"
local value="$2"
if ! [[ "$value" =~ ^[01]$ ]]; then
printf '%s must be 0 or 1, got: %s\n' "$name" "$value" >&2
exit 1
fi
}
maybe_import_table() {
local database="$1"
local table_name="$2"
local file_path="$3"
if table_exists "$database" "$table_name"; then
log "table ${database}.${table_name} already exists, skipping"
return 0
fi
import_sql_file "$database" "$file_path"
}
import_mythicplus_sql() {
if [[ "$IMPORT_MYTHICPLUS_SQL" == "0" ]]; then
log "MythicPlus SQL import disabled, skipped"
return 0
fi
if [[ ! -d "$ROOT_DIR/sql/Mythic+" ]]; then
log "sql/Mythic+ directory not found, skipped"
return 0
fi
maybe_import_table "acore_characters" "character_mythic_history" \
"$ROOT_DIR/sql/Mythic+/characters/character_mythic_history.sql"
maybe_import_table "acore_characters" "character_mythic_keys" \
"$ROOT_DIR/sql/Mythic+/characters/character_mythic_keys.sql"
maybe_import_table "acore_characters" "character_mythic_rating" \
"$ROOT_DIR/sql/Mythic+/characters/character_mythic_rating.sql"
maybe_import_table "acore_characters" "character_mythic_vault" \
"$ROOT_DIR/sql/Mythic+/characters/character_mythic_vault.sql"
maybe_import_table "acore_characters" "character_mythic_weekly_affixes" \
"$ROOT_DIR/sql/Mythic+/characters/character_mythic_weekly_affixes.sql"
maybe_import_table "acore_world" "world_mythic_loot" \
"$ROOT_DIR/sql/Mythic+/world/world_mythic_loot.sql"
maybe_import_table "acore_world" "world_vault_loot" \
"$ROOT_DIR/sql/Mythic+/world/world_vault_loot.sql"
local mythicplus_bootstrap_ready
mythicplus_bootstrap_ready="$(mysql_query "acore_world" "
SELECT
CASE
WHEN EXISTS (SELECT 1 FROM creature_template WHERE entry = 900001)
AND EXISTS (SELECT 1 FROM item_template WHERE entry = 900100)
AND EXISTS (SELECT 1 FROM gameobject_template WHERE entry = 900000)
AND EXISTS (SELECT 1 FROM creature WHERE id = 900001)
AND EXISTS (SELECT 1 FROM gameobject WHERE id = 900000)
THEN 1
ELSE 0
END;
")"
if [[ "$mythicplus_bootstrap_ready" == "0" ]]; then
log "repairing MythicPlus world bootstrap entries"
mysql_exec "acore_world" "
DELETE FROM creature WHERE id = 900001;
DELETE FROM gameobject WHERE id1 = 900000;
DELETE FROM creature_template_locale WHERE entry = 900001;
DELETE FROM item_template_locale WHERE ID = 900100;
DELETE FROM creature_template_model WHERE CreatureID = 900001;
DELETE FROM creature_template WHERE entry = 900001;
DELETE FROM item_template WHERE entry = 900100;
DELETE FROM gameobject_template WHERE entry = 900000;
"
import_sql_file "acore_world" "$ROOT_DIR/sql/Mythic+/world/creature_and_keystones.sql"
else
log "MythicPlus creature bootstrap already exists, skipping"
fi
}
import_store_sql() {
local store_sql_file="$ROOT_DIR/sql/Store.sql"
local temp_sql=""
if [[ "$IMPORT_STORE_SQL" == "0" ]]; then
log "Store SQL import disabled, skipped"
return 0
fi
if [[ ! -d "$ROOT_DIR/lua_scripts/Store_System" ]]; then
log "lua_scripts/Store_System directory not found, skipped"
return 0
fi
if [[ ! -f "$store_sql_file" ]]; then
log "sql/Store.sql not found, skipped"
return 0
fi
if ! database_exists "store"; then
log "creating database store"
mysql_exec "" "CREATE DATABASE IF NOT EXISTS \`store\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
fi
# Unlike the rest of Store.sql, this is a repeatable migration. It ensures
# existing store databases receive the global switch without overwriting a
# value later changed by an administrator.
mysql_exec "store" "
CREATE TABLE IF NOT EXISTS \`store_config\` (
\`id\` TINYINT UNSIGNED NOT NULL,
\`enabled\` TINYINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT IGNORE INTO \`store_config\` (\`id\`, \`enabled\`) VALUES (1, 0);
"
# Store.sql is a one-time bootstrap, not a repeatable migration. It
# contains UPDATE and DELETE statements that would overwrite values
# maintained in the live store database. The canonical services table is
# created by the initial bootstrap, so its presence also protects
# installations created before this guard was introduced.
if database_exists "store" && table_exists "store" "store_services"; then
log "store database already initialized, skipping one-time Store.sql bootstrap"
return 0
fi
temp_sql="$(mktemp)"
{
printf 'CREATE DATABASE IF NOT EXISTS `store` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;\n'
printf 'USE `store`;\n'
awk '
BEGIN { skip = 0 }
skip {
if ($0 ~ /;[[:space:]]*$/) {
skip = 0
}
next
}
$0 ~ /^INSERT INTO `store_logs` / {
skip = 1
next
}
{
sub(/^INSERT INTO /, "INSERT IGNORE INTO ")
print
}
' "$store_sql_file"
} > "$temp_sql"
log "bootstrapping store database"
mysql_exec "" "$(cat "$temp_sql")"
rm -f "$temp_sql"
}
import_aoe_loot_sql() {
local aoe_loot_sql_file="$ROOT_DIR/modules/mod-aoe-loot/data/sql/db-world/base/aoe_loot_module_string.sql"
if [[ ! -f "$aoe_loot_sql_file" ]]; then
log "mod-aoe-loot module string SQL not found, skipped"
return 0
fi
if [[ "$(mysql_query "acore_world" "SELECT COUNT(*) FROM module_string WHERE module = 'mod-aoe-loot';")" != "0" ]] && \
[[ "$(mysql_query "acore_world" "SELECT COUNT(*) FROM module_string_locale WHERE module = 'mod-aoe-loot';")" != "0" ]]; then
log "mod-aoe-loot module strings already exist, skipping"
return 0
fi
import_sql_file "acore_world" "$aoe_loot_sql_file"
}
import_playerbots_ru_sql() {
local names_sql_file="$ROOT_DIR/modules/mod-playerbots/data/sql/characters/updates/2026_07_30_00_playerbots_russian_names.sql"
if [[ "$PLAYERBOTS_RUSSIAN_NAMES" == "0" ]]; then
log "Russian playerbot localization disabled, skipping name pools"
return 0
fi
if table_exists "acore_characters" "playerbots_names_ru" && \
table_exists "acore_characters" "playerbots_guild_names_ru"; then
log "Russian playerbot name pools already exist, skipping"
return 0
fi
import_sql_file "acore_characters" "$names_sql_file"
}
import_transmog_sql() {
log "Started importing Transmogrification SQL files"
local account_transmog_sql="$ROOT_DIR/sql/Transmogrification/auth.sql"
local character_transmog_sql="$ROOT_DIR/sql/Transmogrification/char.sql"
maybe_import_table "acore_auth" "account_transmog" "$account_transmog_sql"
maybe_import_table "acore_characters" "character_transmog" "$character_transmog_sql"
}
configure_server_motd() {
log "configuring MoonWell login message"
mysql_exec "acore_auth" "
REPLACE INTO motd (realmid, text)
VALUES (-1, 'Добро пожаловать на сервер MoonWell! Приятной игры!');
DELETE FROM motd_localized WHERE realmid = -1;
"
}
import_encounter_journal_sql() {
local schema_file="$ROOT_DIR/modules/mod-encounter-journal/data/sql/db-world/base/01_encounter_journal_schema.sql"
local sample_file="$ROOT_DIR/modules/mod-encounter-journal/data/sql/db-world/base/02_encounter_journal_sample.sql"
if [[ "$IMPORT_ENCOUNTER_JOURNAL_SQL" == "0" ]]; then
log "Encounter Journal SQL import disabled, skipped"
return 0
fi
if [[ ! -f "$schema_file" ]]; then
log "mod-encounter-journal schema SQL not found, skipped"
return 0
fi
# Schema file uses CREATE TABLE IF NOT EXISTS, but we still gate on the
# canonical tier table to avoid noisy re-imports on every bootstrap.
if ! table_exists "acore_world" "custom_ej_tier"; then
import_sql_file "acore_world" "$schema_file"
else
log "custom_ej_* schema already present, skipping"
fi
# Sample data only on a fresh install (no rows in any custom_ej_* table).
# Re-running it would clobber hand-edited rows in the 9000-9999 id range.
if [[ -f "$sample_file" ]]; then
local total
total="$(mysql_query "acore_world" "
SELECT (SELECT COUNT(*) FROM custom_ej_tier)
+ (SELECT COUNT(*) FROM custom_ej_instance)
+ (SELECT COUNT(*) FROM custom_ej_encounter)
+ (SELECT COUNT(*) FROM custom_ej_creature)
+ (SELECT COUNT(*) FROM custom_ej_section)
+ (SELECT COUNT(*) FROM custom_ej_loot);
")"
if [[ "$total" == "0" ]]; then
import_sql_file "acore_world" "$sample_file"
else
log "custom_ej_* tables already populated, skipping sample data"
fi
fi
}
if (($# > 0)); then
case "$1" in
-h|--help)
usage
exit 0
;;
*)
printf 'Unknown option: %s\n\n' "$1" >&2
usage >&2
exit 1
;;
esac
fi
require_integer_flag "ACORE_IMPORT_MYTHICPLUS_SQL" "$IMPORT_MYTHICPLUS_SQL"
require_integer_flag "ACORE_IMPORT_STORE_SQL" "$IMPORT_STORE_SQL"
require_integer_flag "ACORE_IMPORT_ENCOUNTER_JOURNAL_SQL" "$IMPORT_ENCOUNTER_JOURNAL_SQL"
require_integer_flag "ACORE_PLAYERBOTS_RUSSIAN_NAMES" "$PLAYERBOTS_RUSSIAN_NAMES"
cd "$ROOT_DIR"
if ! docker compose ps --status running --services | grep -qx 'ac-database'; then
printf 'ac-database is not running. Start the database first.\n' >&2
exit 1
fi
import_mythicplus_sql
import_aoe_loot_sql
import_playerbots_ru_sql
import_encounter_journal_sql
import_store_sql
import_transmog_sql
configure_server_motd
log "custom SQL bootstrap completed"