module setup process

This commit is contained in:
uprightbass360
2025-11-17 02:23:53 -05:00
parent ea908dbbcf
commit d3484a3aea
30 changed files with 7685 additions and 430 deletions

View File

@@ -165,6 +165,14 @@ EOF
EOF
fi
# Capture module SQL ledger snapshot if available
local ledger_src="/modules-meta/module-sql-ledger.txt"
if [ -f "$ledger_src" ]; then
cp "$ledger_src" "$target_dir/module-sql-ledger.txt"
else
log " Module SQL ledger not found (modules/meta missing); snapshot not included in this backup"
fi
# Create completion marker to indicate backup is finished
touch "$target_dir/.backup_complete"

View File

@@ -34,6 +34,35 @@ Notes:
EOF
}
verify_databases_populated() {
local mysql_host="${CONTAINER_MYSQL:-ac-mysql}"
local mysql_port="${MYSQL_PORT:-3306}"
local mysql_user="${MYSQL_USER:-root}"
local mysql_pass="${MYSQL_ROOT_PASSWORD:-root}"
local db_auth="${DB_AUTH_NAME:-acore_auth}"
local db_world="${DB_WORLD_NAME:-acore_world}"
local db_characters="${DB_CHARACTERS_NAME:-acore_characters}"
if ! command -v mysql >/dev/null 2>&1; then
echo "⚠️ mysql client is not available to verify restoration status"
return 1
fi
local query="SELECT COUNT(*) FROM information_schema.tables WHERE table_schema IN ('$db_auth','$db_world','$db_characters');"
local table_count
if ! table_count=$(MYSQL_PWD="$mysql_pass" mysql -h "$mysql_host" -P "$mysql_port" -u "$mysql_user" -N -B -e "$query" 2>/dev/null); then
echo "⚠️ Unable to query MySQL at ${mysql_host}:${mysql_port} to verify restoration status"
return 1
fi
if [ "${table_count:-0}" -gt 0 ]; then
return 0
fi
echo "⚠️ MySQL is reachable but no AzerothCore tables were found"
return 1
}
case "${1:-}" in
-h|--help)
print_help
@@ -70,10 +99,17 @@ fi
echo "🔍 Checking restoration status..."
if [ -f "$RESTORE_SUCCESS_MARKER" ]; then
echo "✅ Backup restoration completed successfully"
cat "$RESTORE_SUCCESS_MARKER" || true
echo "🚫 Skipping database import - data already restored from backup"
exit 0
if verify_databases_populated; then
echo "✅ Backup restoration completed successfully"
cat "$RESTORE_SUCCESS_MARKER" || true
echo "🚫 Skipping database import - data already restored from backup"
exit 0
fi
echo "⚠️ Restoration marker found, but databases are empty - forcing re-import"
rm -f "$RESTORE_SUCCESS_MARKER" 2>/dev/null || true
rm -f "$RESTORE_SUCCESS_MARKER_TMP" 2>/dev/null || true
rm -f "$RESTORE_FAILED_MARKER" 2>/dev/null || true
fi
if [ -f "$RESTORE_FAILED_MARKER" ]; then
@@ -352,6 +388,15 @@ EOF
# Verify and apply missing updates
verify_and_update_restored_databases
if [ -x "/tmp/restore-and-stage.sh" ]; then
echo "🔧 Running restore-time module SQL staging..."
MODULES_DIR="/modules" \
RESTORE_SOURCE_DIR="$backup_path" \
/tmp/restore-and-stage.sh
else
echo " restore-and-stage helper not available; skipping automatic module SQL staging"
fi
exit 0
else
echo "$(date): Backup restoration failed - proceeding with fresh setup" > "$RESTORE_FAILED_MARKER"

View File

@@ -477,84 +477,11 @@ load_sql_helper(){
err "SQL helper not found; expected manage-modules-sql.sh to be available"
}
stage_module_sql_files(){
# Stage SQL files to AzerothCore's native update directory structure
# This replaces manual SQL execution with AzerothCore's built-in updater
local staging_dir="${MODULE_STAGING_DIR:-$MODULES_ROOT}"
local sql_manifest="$STATE_DIR/.sql-manifest.json"
if [ ! -f "$sql_manifest" ]; then
info "No SQL manifest found - no SQL files to stage"
return 0
fi
# Check if manifest has any modules with SQL
local module_count
module_count=$(python3 -c "import json; data=json.load(open('$sql_manifest')); print(len(data.get('modules', [])))" 2>/dev/null || echo "0")
if [ "$module_count" = "0" ]; then
info "No modules with SQL files to stage"
return 0
fi
info "Staging SQL for $module_count module(s)"
# Read each module from manifest and stage its SQL
local modules_json
modules_json=$(python3 -c "import json; data=json.load(open('$sql_manifest')); print('\n'.join(m['name'] for m in data['modules']))" 2>/dev/null || echo "")
if [ -z "$modules_json" ]; then
warn "Failed to parse SQL manifest"
return 1
fi
local staged_count=0
while IFS= read -r module_name; do
if [ -z "$module_name" ]; then
continue
fi
local module_path="$staging_dir/$module_name"
local acore_modules="/azerothcore/modules/$module_name"
if [ ! -d "$module_path" ]; then
warn "Module path not found: $module_path"
continue
fi
# Call stage-module-sql.sh for this module
local stage_script="${PROJECT_ROOT}/scripts/bash/stage-module-sql.sh"
if [ ! -f "$stage_script" ]; then
# Try container location
stage_script="/scripts/bash/stage-module-sql.sh"
fi
if [ -f "$stage_script" ]; then
if "$stage_script" \
--module-name "$module_name" \
--module-path "$module_path" \
--acore-path "$acore_modules"; then
((staged_count++))
fi
else
warn "SQL staging script not found: $stage_script"
fi
done <<< "$modules_json"
if [ "$staged_count" -gt 0 ]; then
ok "Staged SQL for $staged_count module(s)"
info "SQL will be applied by AzerothCore's updater on next server startup"
fi
return 0
}
execute_module_sql(){
# Legacy function - now calls staging instead of direct execution
SQL_EXECUTION_FAILED=0
stage_module_sql_files || SQL_EXECUTION_FAILED=1
}
# REMOVED: stage_module_sql_files() and execute_module_sql()
# These functions were part of build-time SQL staging that created files in
# /azerothcore/modules/*/data/sql/updates/ which are NEVER scanned by AzerothCore's DBUpdater.
# Module SQL is now staged at runtime by stage-modules.sh which copies files to
# /azerothcore/data/sql/updates/ (core directory) where they ARE scanned and processed.
track_module_state(){
echo 'Checking for module changes that require rebuild...'
@@ -655,18 +582,11 @@ main(){
remove_disabled_modules
install_enabled_modules
manage_configuration_files
info "SQL staging gate: MODULES_SKIP_SQL=${MODULES_SKIP_SQL:-0}"
if [ "${MODULES_SKIP_SQL:-0}" = "1" ]; then
info "Skipping module SQL staging (MODULES_SKIP_SQL=1)"
else
info "Staging module SQL files for AzerothCore updater"
execute_module_sql
fi
track_module_state
# NOTE: Module SQL staging is now handled at runtime by stage-modules.sh
# which copies SQL files to /azerothcore/data/sql/updates/ after containers start.
# Build-time SQL staging has been removed as it created files that were never processed.
if [ "${SQL_EXECUTION_FAILED:-0}" = "1" ]; then
warn "Module SQL execution reported issues; review logs above."
fi
track_module_state
echo 'Module management complete.'

View File

@@ -75,6 +75,13 @@ for db in "${dbs[@]}"; do
echo "[manual] ✅ ${db}"
done
ledger_src="/modules-meta/module-sql-ledger.txt"
if [ -f "${ledger_src}" ]; then
cp "${ledger_src}" "${TARGET_DIR}/module-sql-ledger.txt"
else
echo "[manual] Module SQL ledger not found; snapshot not included"
fi
size="$(du -sh "${TARGET_DIR}" | cut -f1)"
cat > "${TARGET_DIR}/manifest.json" <<EOF
{

103
scripts/bash/restore-and-stage.sh Executable file
View File

@@ -0,0 +1,103 @@
#!/bin/bash
# Refresh the module SQL ledger after a database restore so the runtime staging
# flow knows exactly which files to copy into /azerothcore/data/sql/updates/*.
set -euo pipefail
info(){ echo "🔧 [restore-stage] $*"; }
warn(){ echo "⚠️ [restore-stage] $*" >&2; }
MODULES_DIR="${MODULES_DIR:-/modules}"
RESTORE_SOURCE_DIR="${RESTORE_SOURCE_DIR:-}"
MODULES_META_DIR="${MODULES_DIR}/.modules-meta"
LEDGER_FILE="${MODULES_META_DIR}/module-sql-ledger.txt"
RESTORE_FLAG="${MODULES_META_DIR}/.restore-prestaged"
SNAPSHOT_FILE=""
ensure_modules_dir(){
if [ ! -d "$MODULES_DIR" ]; then
warn "Modules directory not found at ${MODULES_DIR}; skipping restore-time staging prep."
exit 0
fi
}
hash_sql_file(){
local sql_file="$1"
if command -v sha1sum >/dev/null 2>&1; then
sha1sum "$sql_file" | awk '{print $1}'
elif command -v md5sum >/dev/null 2>&1; then
md5sum "$sql_file" | awk '{print $1}'
else
return 1
fi
}
collect_sql_files(){
local db_type="$1" legacy="$2"
local -a patterns=(
"$MODULES_DIR"/*/data/sql/"$db_type"/*.sql
"$MODULES_DIR"/*/data/sql/"$db_type"/base/*.sql
"$MODULES_DIR"/*/data/sql/"$db_type"/updates/*.sql
"$MODULES_DIR"/*/data/sql/"$legacy"/*.sql
"$MODULES_DIR"/*/data/sql/"$legacy"/base/*.sql
"$MODULES_DIR"/*/data/sql/"$legacy"/updates/*.sql
)
declare -A seen=()
local -a files=()
for pattern in "${patterns[@]}"; do
for path in $pattern; do
[ -f "$path" ] || continue
if [ -z "${seen[$path]:-}" ]; then
seen["$path"]=1
files+=("$path")
fi
done
done
if [ ${#files[@]} -eq 0 ]; then
return 0
fi
printf '%s\n' "${files[@]}" | sort
}
rebuild_ledger(){
local tmp_file
tmp_file="$(mktemp)"
for db_type in db-world db-characters db-auth; do
local legacy=""
case "$db_type" in
db-world) legacy="world" ;;
db-characters) legacy="characters" ;;
db-auth) legacy="auth" ;;
esac
while IFS= read -r sql_file; do
[ -n "$sql_file" ] || continue
[ -f "$sql_file" ] || continue
local module_name base_name hash
module_name="$(echo "$sql_file" | sed 's|.*/modules/||' | cut -d'/' -f1)"
base_name="$(basename "$sql_file" .sql)"
if ! hash="$(hash_sql_file "$sql_file")"; then
continue
fi
printf '%s|%s|%s|%s\n' "$db_type" "$module_name" "$base_name" "$hash" >> "$tmp_file"
done < <(collect_sql_files "$db_type" "$legacy")
done
sort -u "$tmp_file" > "$LEDGER_FILE"
rm -f "$tmp_file"
}
ensure_modules_dir
mkdir -p "$MODULES_META_DIR" 2>/dev/null || true
if [ -n "$RESTORE_SOURCE_DIR" ] && [ -f "${RESTORE_SOURCE_DIR}/module-sql-ledger.txt" ]; then
SNAPSHOT_FILE="${RESTORE_SOURCE_DIR}/module-sql-ledger.txt"
info "Snapshot found in backup (${SNAPSHOT_FILE}); syncing to host ledger."
cp "$SNAPSHOT_FILE" "$LEDGER_FILE"
else
warn "Module SQL snapshot not found in backup; rebuilding ledger from module sources."
rebuild_ledger
fi
touch "$RESTORE_FLAG"
echo "restore_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" > "$RESTORE_FLAG"
info "Ledger ready at ${LEDGER_FILE}; runtime staging will copy SQL before worldserver starts."
info "Flagged ${RESTORE_FLAG} to force staging on next ./scripts/bash/stage-modules.sh run."

View File

@@ -1,297 +0,0 @@
#!/bin/bash
# Stage Module SQL Files
# Copies module SQL to AzerothCore's native update directory structure
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
# Icons
ICON_SUCCESS="✅"
ICON_WARNING="⚠️"
ICON_ERROR="❌"
ICON_INFO=""
# Default values
MODULE_NAME=""
MODULE_PATH=""
ACORE_PATH=""
MANIFEST_PATH=""
DRY_RUN=0
usage() {
cat <<'EOF'
Usage: ./stage-module-sql.sh [options]
Stage module SQL files into AzerothCore's native update directory structure.
Options:
--module-name NAME Module name (e.g., mod-aoe-loot)
--module-path PATH Path to module repository
--acore-path PATH Path to AzerothCore modules directory
--manifest PATH Path to SQL manifest JSON (optional)
--dry-run Show what would be staged without doing it
-h, --help Show this help
Examples:
./stage-module-sql.sh \
--module-name mod-aoe-loot \
--module-path /staging/mod-aoe-loot \
--acore-path /azerothcore/modules/mod-aoe-loot
EOF
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--module-name) MODULE_NAME="$2"; shift 2;;
--module-path) MODULE_PATH="$2"; shift 2;;
--acore-path) ACORE_PATH="$2"; shift 2;;
--manifest) MANIFEST_PATH="$2"; shift 2;;
--dry-run) DRY_RUN=1; shift;;
-h|--help) usage; exit 0;;
*) echo "Unknown option: $1"; usage; exit 1;;
esac
done
# Validate arguments
if [ -z "$MODULE_NAME" ] || [ -z "$MODULE_PATH" ] || [ -z "$ACORE_PATH" ]; then
echo -e "${RED}${ICON_ERROR} Missing required arguments${NC}"
usage
exit 1
fi
if [ ! -d "$MODULE_PATH" ]; then
echo -e "${RED}${ICON_ERROR} Module path does not exist: $MODULE_PATH${NC}"
exit 1
fi
# Logging functions
info() {
echo -e "${BLUE}${ICON_INFO}${NC} $*"
}
ok() {
echo -e "${GREEN}${ICON_SUCCESS}${NC} $*"
}
warn() {
echo -e "${YELLOW}${ICON_WARNING}${NC} $*"
}
err() {
echo -e "${RED}${ICON_ERROR}${NC} $*"
}
# Generate timestamp-based filename
generate_sql_timestamp() {
# Format: YYYYMMDD_HH
# Uses current time to ensure unique, sequential naming
date +"%Y%m%d_%H"
}
# Validate SQL file
validate_sql_file() {
local sql_file="$1"
if [ ! -f "$sql_file" ]; then
return 1
fi
# Basic validation - file should not be empty
if [ ! -s "$sql_file" ]; then
warn "SQL file is empty: $(basename "$sql_file")"
return 1
fi
# Check for shell commands (security check)
if grep -qE '^\s*(system|exec|shell)' "$sql_file"; then
err "SQL file contains suspicious shell commands: $(basename "$sql_file")"
return 1
fi
return 0
}
# Discover SQL files in module
discover_module_sql() {
local module_path="$1"
local sql_base="$module_path/data/sql"
if [ ! -d "$sql_base" ]; then
# No SQL directory, not an error
return 0
fi
# Search in base/, updates/, and custom/ directories
local -A sql_files
# Support both underscore (db_world) and hyphen (db-world) naming conventions
local -A db_variants=(
["db_auth"]="db_auth db-auth"
["db_world"]="db_world db-world"
["db_characters"]="db_characters db-characters"
["db_playerbots"]="db_playerbots db-playerbots"
)
for canonical_type in db_auth db_world db_characters db_playerbots; do
for variant in ${db_variants[$canonical_type]}; do
# Check base/
if [ -d "$sql_base/base/$variant" ]; then
while IFS= read -r -d '' file; do
sql_files["$canonical_type"]+="$file"$'\n'
done < <(find "$sql_base/base/$variant" -name "*.sql" -type f -print0 2>/dev/null)
fi
# Check updates/
if [ -d "$sql_base/updates/$variant" ]; then
while IFS= read -r -d '' file; do
sql_files["$canonical_type"]+="$file"$'\n'
done < <(find "$sql_base/updates/$variant" -name "*.sql" -type f -print0 2>/dev/null)
fi
# Check custom/
if [ -d "$sql_base/custom/$variant" ]; then
while IFS= read -r -d '' file; do
sql_files["$canonical_type"]+="$file"$'\n'
done < <(find "$sql_base/custom/$variant" -name "*.sql" -type f -print0 2>/dev/null)
fi
# ALSO check direct db-type directories (legacy format used by many modules)
if [ -d "$sql_base/$variant" ]; then
while IFS= read -r -d '' file; do
sql_files["$canonical_type"]+="$file"$'\n'
done < <(find "$sql_base/$variant" -name "*.sql" -type f -print0 2>/dev/null)
fi
done
done
# Print discovered files
for db_type in "${!sql_files[@]}"; do
echo "$db_type"
echo "${sql_files[$db_type]}"
done
}
# Stage single SQL file
stage_sql_file() {
local source_file="$1"
local target_dir="$2"
local module_name="$3"
local counter="$4"
# Validate source file
if ! validate_sql_file "$source_file"; then
return 1
fi
# Generate target filename
local timestamp
timestamp=$(generate_sql_timestamp)
local basename
basename=$(basename "$source_file" .sql)
local target_file="$target_dir/${timestamp}_${counter}_${module_name}_${basename}.sql"
# Create target directory
if [ "$DRY_RUN" = "0" ]; then
mkdir -p "$target_dir"
fi
# Copy file
if [ "$DRY_RUN" = "1" ]; then
info "Would stage: $(basename "$source_file") -> $(basename "$target_file")"
else
if cp "$source_file" "$target_file"; then
ok "Staged: $(basename "$target_file")"
else
err "Failed to stage: $(basename "$source_file")"
return 1
fi
fi
return 0
}
# Stage all SQL for module
stage_module_sql() {
local module_name="$1"
local module_path="$2"
local acore_path="$3"
info "Staging SQL for module: $module_name"
# Discover SQL files
local sql_discovery
sql_discovery=$(discover_module_sql "$module_path")
if [ -z "$sql_discovery" ]; then
info "No SQL files found in module"
return 0
fi
# Parse discovery output
local current_db=""
local counter=1
local staged_count=0
while IFS= read -r line; do
if [ -z "$line" ]; then
continue
fi
# Check if this is a database type line
if [[ "$line" =~ ^db_(auth|world|characters|playerbots)$ ]]; then
current_db="$line"
counter=1
continue
fi
# This is a file path
if [ -n "$current_db" ] && [ -f "$line" ]; then
# AzerothCore expects db_world, db_auth, etc. (WITH db_ prefix)
local target_dir="$acore_path/data/sql/updates/$current_db"
if stage_sql_file "$line" "$target_dir" "$module_name" "$counter"; then
((staged_count++))
((counter++))
fi
fi
done <<< "$sql_discovery"
if [ "$staged_count" -gt 0 ]; then
ok "Staged $staged_count SQL file(s) for $module_name"
else
warn "No SQL files staged for $module_name"
fi
return 0
}
# Main execution
main() {
echo
info "Module SQL Staging"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo
if [ "$DRY_RUN" = "1" ]; then
warn "DRY RUN MODE - No files will be modified"
echo
fi
stage_module_sql "$MODULE_NAME" "$MODULE_PATH" "$ACORE_PATH"
echo
ok "SQL staging complete"
echo
}
main "$@"

View File

@@ -17,6 +17,97 @@ show_staging_step(){
printf '%b\n' "${YELLOW}🔧 ${step}: ${message}...${NC}"
}
ensure_host_writable(){
local target="$1"
[ -n "$target" ] || return 0
if [ -d "$target" ] || mkdir -p "$target" 2>/dev/null; then
local uid gid
uid="$(id -u)"
gid="$(id -g)"
if ! chown -R "$uid":"$gid" "$target" 2>/dev/null; then
if command -v docker >/dev/null 2>&1; then
local helper_image
helper_image="$(read_env ALPINE_IMAGE "alpine:latest")"
docker run --rm \
-u 0:0 \
-v "$target":/workspace \
"$helper_image" \
sh -c "chown -R ${uid}:${gid} /workspace" >/dev/null 2>&1 || true
fi
fi
chmod -R u+rwX "$target" 2>/dev/null || true
fi
}
seed_sql_ledger_if_needed(){
local sentinel="$1" ledger="$2"
mkdir -p "$(dirname "$ledger")" 2>/dev/null || true
local need_seed=0
local reason=""
if [ ! -f "$ledger" ] || [ ! -s "$ledger" ]; then
need_seed=1
reason="Module SQL ledger missing; rebuilding."
elif [ -f "$sentinel" ] && [ "$sentinel" -nt "$ledger" ]; then
need_seed=1
reason="Database restore detected; seeding module SQL ledger."
fi
if [ "$need_seed" -ne 1 ]; then
touch "$ledger" 2>/dev/null || true
return 0
fi
echo "${reason}"
local tmp_file="${ledger}.tmp"
> "$tmp_file"
shopt -s nullglob
for db_type in db-world db-characters db-auth; do
local legacy_name=""
case "$db_type" in
db-world) legacy_name="world" ;;
db-characters) legacy_name="characters" ;;
db-auth) legacy_name="auth" ;;
esac
local search_paths=(
"$MODULES_DIR"/*/data/sql/"$db_type"
"$MODULES_DIR"/*/data/sql/"$db_type"/base
"$MODULES_DIR"/*/data/sql/"$db_type"/updates
"$MODULES_DIR"/*/data/sql/"$legacy_name"
"$MODULES_DIR"/*/data/sql/"$legacy_name"/base
)
for module_dir in "${search_paths[@]}"; do
for sql_file in "$module_dir"/*.sql; do
[ -e "$sql_file" ] || continue
local module_name
module_name="$(echo "$sql_file" | sed 's|.*/modules/||' | cut -d'/' -f1)"
local base_name
base_name="$(basename "$sql_file" .sql)"
local hash_cmd=""
if command -v sha1sum >/dev/null 2>&1; then
hash_cmd="sha1sum"
elif command -v md5sum >/dev/null 2>&1; then
hash_cmd="md5sum"
fi
local file_hash=""
if [ -n "$hash_cmd" ]; then
file_hash=$($hash_cmd "$sql_file" | awk '{print $1}')
fi
[ -n "$file_hash" ] || continue
printf '%s|%s|%s|%s\n' "$db_type" "$module_name" "$base_name" "$file_hash" >> "$tmp_file"
done
done
done
shopt -u nullglob
sort -u "$tmp_file" > "$ledger"
rm -f "$tmp_file"
}
sync_local_staging(){
local src_root="$LOCAL_STORAGE_PATH"
local dest_root="$STORAGE_PATH"
@@ -53,6 +144,10 @@ sync_local_staging(){
return
fi
# Ensure both source and destination trees are writable by the host user.
ensure_host_writable "$src_modules"
ensure_host_writable "$dest_modules"
if command -v rsync >/dev/null 2>&1; then
# rsync may return exit code 23 (permission warnings) in WSL2 - these are harmless
rsync -a --delete "$src_modules"/ "$dest_modules"/ || {
@@ -229,6 +324,34 @@ if [[ "$LOCAL_STORAGE_PATH" != /* ]]; then
fi
LOCAL_STORAGE_PATH="$(canonical_path "$LOCAL_STORAGE_PATH")"
SENTINEL_FILE="$LOCAL_STORAGE_PATH/modules/.requires_rebuild"
MODULES_META_DIR="$STORAGE_PATH/modules/.modules-meta"
MODULES_SQL_LEDGER_HOST="$MODULES_META_DIR/module-sql-ledger.txt"
RESTORE_PRESTAGED_FLAG="$MODULES_META_DIR/.restore-prestaged"
MODULES_ENABLED_FILE="$MODULES_META_DIR/modules-enabled.txt"
declare -A ENABLED_MODULES=()
load_enabled_modules(){
ENABLED_MODULES=()
if [ -f "$MODULES_ENABLED_FILE" ]; then
while IFS= read -r enabled_module; do
enabled_module="$(echo "$enabled_module" | tr -d '\r')"
[ -n "$enabled_module" ] || continue
ENABLED_MODULES["$enabled_module"]=1
done < "$MODULES_ENABLED_FILE"
fi
}
module_is_enabled(){
local module_dir="$1"
if [ ${#ENABLED_MODULES[@]} -eq 0 ]; then
return 0
fi
if [ -n "${ENABLED_MODULES[$module_dir]:-}" ]; then
return 0
fi
return 1
}
# Define module mappings (from rebuild-with-modules.sh)
declare -A MODULE_REPO_MAP=(
@@ -347,9 +470,12 @@ fi
# Stage the services
show_staging_step "Service Orchestration" "Preparing realm services"
sync_local_staging
echo "🎬 Staging services with profile: services-$TARGET_PROFILE"
echo "⏳ Pulling images and starting containers; this can take several minutes on first run."
load_enabled_modules
# Stop any currently running services
echo "🛑 Stopping current services..."
docker compose \
@@ -392,6 +518,11 @@ stage_module_sql_to_core() {
return 0
fi
if [ -f "$RESTORE_PRESTAGED_FLAG" ]; then
echo "↻ Restore pipeline detected (flag: $RESTORE_PRESTAGED_FLAG); re-staging module SQL so worldserver can apply updates."
rm -f "$RESTORE_PRESTAGED_FLAG" 2>/dev/null || true
fi
echo "📦 Staging module SQL files to core updates directory..."
# Create core updates directories inside container
@@ -403,52 +534,220 @@ stage_module_sql_to_core() {
# Stage SQL from all modules
local staged_count=0
local timestamp=$(date +"%Y_%m_%d_%H%M%S")
local total_skipped=0
local total_failed=0
local RESTORE_SENTINEL="$LOCAL_STORAGE_PATH/mysql-data/.restore-completed"
ensure_host_writable "$MODULES_META_DIR"
seed_sql_ledger_if_needed "$RESTORE_SENTINEL" "$MODULES_SQL_LEDGER_HOST"
docker exec ac-worldserver bash -c "find /azerothcore/data/sql/updates -name '*_MODULE_*.sql' -delete" >/dev/null 2>&1 || true
# Find all modules with SQL files
shopt -s nullglob
for db_type in db-world db-characters db-auth; do
local core_dir=""
local legacy_name=""
case "$db_type" in
db-world) core_dir="db_world" ;;
db-characters) core_dir="db_characters" ;;
db-auth) core_dir="db_auth" ;;
db-world)
core_dir="db_world"
legacy_name="world" # Some modules use 'world' instead of 'db-world'
;;
db-characters)
core_dir="db_characters"
legacy_name="characters"
;;
db-auth)
core_dir="db_auth"
legacy_name="auth"
;;
esac
# Copy SQL files from each module
docker exec ac-worldserver bash -c "
counter=0
for module_dir in /azerothcore/modules/*/data/sql/$db_type; do
if [ -d \"\$module_dir\" ]; then
module_name=\$(basename \$(dirname \$(dirname \$module_dir)))
for sql_file in \"\$module_dir\"/*.sql; do
if [ -f \"\$sql_file\" ]; then
base_name=\$(basename \"\$sql_file\" .sql)
target_name=\"${timestamp}_\${counter}_MODULE_\${module_name}_\${base_name}.sql\"
cp \"\$sql_file\" \"/azerothcore/data/sql/updates/$core_dir/\$target_name\"
echo \" ✓ Staged \$module_name/$db_type/\$(basename \$sql_file)\"
counter=\$((counter + 1))
fi
done
docker exec ac-worldserver bash -c "mkdir -p /azerothcore/data/sql/updates/$core_dir" >/dev/null 2>&1 || true
local counter=0
local skipped=0
local failed=0
local search_paths=(
"$MODULES_DIR"/*/data/sql/"$db_type"
"$MODULES_DIR"/*/data/sql/"$db_type"/base
"$MODULES_DIR"/*/data/sql/"$db_type"/updates
"$MODULES_DIR"/*/data/sql/"$legacy_name"
"$MODULES_DIR"/*/data/sql/"$legacy_name"/base
)
for module_dir in "${search_paths[@]}"; do
for sql_file in "$module_dir"/*.sql; do
[ -e "$sql_file" ] || continue
if [ ! -f "$sql_file" ] || [ ! -s "$sql_file" ]; then
echo " ⚠️ Skipped empty or invalid: $(basename "$sql_file")"
skipped=$((skipped + 1))
continue
fi
if grep -qE '^[[:space:]]*(system|exec|shell|!)' "$sql_file" 2>/dev/null; then
echo " ❌ Security: Rejected $(basename "$(dirname "$module_dir")")/$(basename "$sql_file") (contains shell commands)"
failed=$((failed + 1))
continue
fi
local module_name
module_name="$(echo "$sql_file" | sed 's|.*/modules/||' | cut -d'/' -f1)"
local base_name
base_name="$(basename "$sql_file" .sql)"
local update_identifier="MODULE_${module_name}_${base_name}"
if ! module_is_enabled "$module_name"; then
echo " ⏭️ Skipped $module_name/$db_type/$(basename "$sql_file") (module disabled)"
skipped=$((skipped + 1))
continue
fi
local hash_cmd=""
if command -v sha1sum >/dev/null 2>&1; then
hash_cmd="sha1sum"
elif command -v md5sum >/dev/null 2>&1; then
hash_cmd="md5sum"
fi
local file_hash=""
if [ -n "$hash_cmd" ]; then
file_hash=$($hash_cmd "$sql_file" | awk '{print $1}')
fi
local ledger_key="$db_type|$module_name|$base_name"
local target_name="MODULE_${module_name}_${base_name}.sql"
if docker cp "$sql_file" "ac-worldserver:/azerothcore/data/sql/updates/$core_dir/$target_name" >/dev/null; then
echo " ✓ Staged $module_name/$db_type/$(basename "$sql_file")"
counter=$((counter + 1))
if [ -n "$file_hash" ]; then
local tmp_file="${MODULES_SQL_LEDGER_HOST}.tmp"
grep -Fv "${ledger_key}|" "$MODULES_SQL_LEDGER_HOST" > "$tmp_file" 2>/dev/null || true
printf '%s|%s\n' "$ledger_key" "$file_hash" >> "$tmp_file"
mv "$tmp_file" "$MODULES_SQL_LEDGER_HOST" 2>/dev/null || true
fi
else
echo " ❌ Failed to copy: $module_name/$(basename "$sql_file")"
failed=$((failed + 1))
fi
done
echo \$counter
" 2>/dev/null | tee /tmp/stage-sql-output.txt || true
done
staged_count=$((staged_count + counter))
total_skipped=$((total_skipped + skipped))
total_failed=$((total_failed + failed))
local count=$(tail -1 /tmp/stage-sql-output.txt 2>/dev/null || echo "0")
staged_count=$((staged_count + count))
done
shopt -u nullglob
echo ""
if [ "$staged_count" -gt 0 ]; then
echo "✅ Staged $staged_count module SQL files to core updates directory"
[ "$total_skipped" -gt 0 ] && echo "⚠️ Skipped $total_skipped empty/invalid file(s)"
[ "$total_failed" -gt 0 ] && echo "❌ Failed to stage $total_failed file(s)"
echo "🔄 Restart worldserver to apply: docker restart ac-worldserver"
else
echo " No module SQL files found to stage"
fi
}
get_module_dbc_path(){
local module_name="$1"
local manifest_file="$PROJECT_DIR/config/module-manifest.json"
if [ ! -f "$manifest_file" ]; then
return 1
fi
if command -v jq >/dev/null 2>&1; then
local dbc_path
dbc_path=$(jq -r ".modules[] | select(.name == \"$module_name\") | .server_dbc_path // empty" "$manifest_file" 2>/dev/null)
if [ -n "$dbc_path" ]; then
echo "$dbc_path"
return 0
fi
fi
return 1
}
stage_module_dbc_files(){
show_staging_step "Module DBC Staging" "Deploying binary DBC files to server"
if ! docker ps --format '{{.Names}}' | grep -q "ac-worldserver"; then
echo "⚠️ Worldserver container not found, skipping module DBC staging"
return 0
fi
echo "📦 Staging module DBC files to server data directory..."
echo " (Using manifest 'server_dbc_path' field to locate server-side DBC files)"
local staged_count=0
local skipped=0
local failed=0
shopt -s nullglob
for module_path in "$MODULES_DIR"/*; do
[ -d "$module_path" ] || continue
local module_name="$(basename "$module_path")"
# Skip disabled modules
if ! module_is_enabled "$module_name"; then
continue
fi
# Get DBC path from manifest
local dbc_path
if ! dbc_path=$(get_module_dbc_path "$module_name"); then
# No server_dbc_path defined in manifest - skip this module
continue
fi
local dbc_dir="$module_path/$dbc_path"
if [ ! -d "$dbc_dir" ]; then
echo " ⚠️ $module_name: DBC directory not found at $dbc_path"
skipped=$((skipped + 1))
continue
fi
for dbc_file in "$dbc_dir"/*.dbc; do
[ -e "$dbc_file" ] || continue
if [ ! -f "$dbc_file" ] || [ ! -s "$dbc_file" ]; then
echo " ⚠️ Skipped empty or invalid: $module_name/$(basename "$dbc_file")"
skipped=$((skipped + 1))
continue
fi
local dbc_filename="$(basename "$dbc_file")"
# Copy to worldserver DBC directory
if docker cp "$dbc_file" "ac-worldserver:/azerothcore/data/dbc/$dbc_filename" >/dev/null 2>&1; then
echo " ✓ Staged $module_name$dbc_filename"
staged_count=$((staged_count + 1))
else
echo " ❌ Failed to copy: $module_name/$dbc_filename"
failed=$((failed + 1))
fi
done
done
shopt -u nullglob
echo ""
if [ "$staged_count" -gt 0 ]; then
echo "✅ Staged $staged_count module DBC files to server data directory"
[ "$skipped" -gt 0 ] && echo "⚠️ Skipped $skipped file(s) (no server_dbc_path in manifest)"
[ "$failed" -gt 0 ] && echo "❌ Failed to stage $failed file(s)"
echo "🔄 Restart worldserver to load new DBC data: docker restart ac-worldserver"
else
echo " No module DBC files found to stage (use 'server_dbc_path' in manifest to enable)"
fi
}
# Stage module SQL (this will also start the containers)
stage_module_sql_to_core
# Stage module DBC files
stage_module_dbc_files
printf '\n%b\n' "${GREEN}⚔️ Realm staging completed successfully! ⚔️${NC}"
printf '%b\n' "${GREEN}🏰 Profile: services-$TARGET_PROFILE${NC}"
printf '%b\n' "${GREEN}🗡️ Your realm is ready for adventure!${NC}"

View File

@@ -33,7 +33,7 @@ info() {
ok() {
echo -e "${GREEN}${ICON_SUCCESS}${NC} $*"
((TESTS_PASSED++))
((TESTS_PASSED+=1))
}
warn() {
@@ -42,11 +42,11 @@ warn() {
err() {
echo -e "${RED}${ICON_ERROR}${NC} $*"
((TESTS_FAILED++))
((TESTS_FAILED+=1))
}
test_header() {
((TESTS_TOTAL++))
((TESTS_TOTAL+=1))
echo ""
echo -e "${BOLD}${ICON_TEST} Test $TESTS_TOTAL: $*${NC}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -192,7 +192,6 @@ fi
# Test 7: Verify new scripts exist and are executable
test_header "New Script Verification"
scripts=(
"scripts/bash/stage-module-sql.sh"
"scripts/bash/verify-sql-updates.sh"
"scripts/bash/backup-status.sh"
"scripts/bash/db-health-check.sh"
@@ -214,11 +213,17 @@ done
# Test 8: Test backup-status.sh (without running containers)
test_header "Backup Status Script Test"
if ./scripts/bash/backup-status.sh 2>&1 | head -10 | grep -q "BACKUP STATUS"; then
ok "backup-status.sh executes successfully"
backup_status_log="$(mktemp)"
if ./scripts/bash/backup-status.sh >"$backup_status_log" 2>&1; then
if grep -q "BACKUP STATUS" "$backup_status_log"; then
ok "backup-status.sh executes successfully"
else
err "backup-status.sh output missing 'BACKUP STATUS' marker"
fi
else
err "backup-status.sh failed to execute"
fi
rm -f "$backup_status_log"
# Test 9: Test db-health-check.sh help
test_header "Database Health Check Script Test"
@@ -231,11 +236,11 @@ fi
# Test 10: Check modified scripts for new functionality
test_header "Modified Script Verification"
# Check manage-modules.sh has staging function
if grep -q "stage_module_sql_files()" scripts/bash/manage-modules.sh; then
ok "manage-modules.sh contains SQL staging function"
# Check stage-modules.sh has runtime SQL staging function
if grep -q "stage_module_sql_to_core()" scripts/bash/stage-modules.sh; then
ok "stage-modules.sh contains runtime SQL staging function"
else
err "manage-modules.sh missing SQL staging function"
err "stage-modules.sh missing runtime SQL staging function"
fi
# Check db-import-conditional.sh has playerbots support
@@ -251,6 +256,13 @@ else
warn "db-import-conditional.sh may have incorrect EnableDatabases value"
fi
# Check for restore marker safety net
if grep -q "verify_databases_populated" scripts/bash/db-import-conditional.sh; then
ok "db-import-conditional.sh verifies live MySQL state before honoring restore markers"
else
err "db-import-conditional.sh missing restore marker safety check"
fi
# Check for post-restore verification
if grep -q "verify_and_update_restored_databases" scripts/bash/db-import-conditional.sh; then
ok "db-import-conditional.sh has post-restore verification"
@@ -258,7 +270,17 @@ else
err "db-import-conditional.sh missing post-restore verification"
fi
# Test 11: Docker Compose configuration check
# Test 11: Restore + Module Staging Automation
test_header "Restore + Module Staging Automation"
if grep -q "restore-and-stage.sh" docker-compose.yml && \
grep -q ".restore-prestaged" scripts/bash/restore-and-stage.sh && \
grep -q "module-sql-ledger" scripts/bash/restore-and-stage.sh; then
ok "restore-and-stage.sh wired into compose, refreshes ledger snapshot, and flags staging"
else
err "restore-and-stage.sh missing compose wiring or ledger/flag handling"
fi
# Test 12: Docker Compose configuration check
test_header "Docker Compose Configuration Check"
if [ -f docker-compose.yml ]; then
ok "docker-compose.yml exists"