From 7e9e6e1b4f5b43bfdc5c41d360a6107700c4b2ce Mon Sep 17 00:00:00 2001 From: uprightbass360 Date: Sun, 23 Nov 2025 16:05:00 -0500 Subject: [PATCH] setup hardening --- .env.template | 3 +- build.sh | 7 ++ docker-compose.yml | 116 +++++++++++++++--------- scripts/bash/db-guard.sh | 39 +++++--- scripts/bash/db-import-conditional.sh | 125 ++++++++++++++++++++------ scripts/bash/migrate-stack.sh | 29 ++++++ scripts/bash/seed-dbimport-conf.sh | 88 ++++++++++++++++++ scripts/bash/stage-modules.sh | 20 ++--- scripts/python/modules.py | 16 ++-- setup.sh | 20 ++++- update-latest.sh | 117 ++++++++++++++++++++++++ 11 files changed, 485 insertions(+), 95 deletions(-) create mode 100644 scripts/bash/seed-dbimport-conf.sh create mode 100755 update-latest.sh diff --git a/.env.template b/.env.template index 66c0a17..849c707 100644 --- a/.env.template +++ b/.env.template @@ -65,7 +65,7 @@ DB_GUARD_VERIFY_INTERVAL_SECONDS=86400 # ===================== # Module SQL staging # ===================== -MODULE_SQL_STAGE_PATH=${STORAGE_PATH_LOCAL}/module-sql-updates +STAGE_PATH_MODULE_SQL=${STORAGE_PATH_LOCAL}/module-sql-updates # ===================== # SQL Source Overlay @@ -180,6 +180,7 @@ DB_CHARACTER_SYNCH_THREADS=1 BACKUP_RETENTION_DAYS=3 BACKUP_RETENTION_HOURS=6 BACKUP_DAILY_TIME=09 +BACKUP_INTERVAL_MINUTES=60 # Optional comma/space separated schemas to include in automated backups BACKUP_EXTRA_DATABASES= BACKUP_HEALTHCHECK_MAX_MINUTES=1440 diff --git a/build.sh b/build.sh index 719920d..b741a98 100755 --- a/build.sh +++ b/build.sh @@ -137,11 +137,18 @@ generate_module_state(){ # Check if blocked modules were detected in warnings if echo "$validation_output" | grep -q "is blocked:"; then + # Gather blocked module keys for display + local blocked_modules + blocked_modules=$(echo "$validation_output" | grep -oE 'MODULE_[A-Za-z0-9_]+' | sort -u | tr '\n' ' ') + # Blocked modules detected - show warning and ask for confirmation echo warn "════════════════════════════════════════════════════════════════" warn "⚠️ BLOCKED MODULES DETECTED ⚠️" warn "════════════════════════════════════════════════════════════════" + if [ -n "$blocked_modules" ]; then + warn "Affected modules: ${blocked_modules}" + fi warn "Some enabled modules are marked as blocked due to compatibility" warn "issues. These modules will be SKIPPED during the build process." warn "" diff --git a/docker-compose.yml b/docker-compose.yml index ce4352d..4e709d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,11 @@ name: ${COMPOSE_PROJECT_NAME} + +x-logging: &logging-default + driver: json-file + options: + max-size: "10m" + max-file: "3" + services: # ===================== # Database Layer (db) @@ -40,8 +47,7 @@ services: - --innodb-log-file-size=${MYSQL_INNODB_LOG_FILE_SIZE} - --innodb-redo-log-capacity=${MYSQL_INNODB_REDO_LOG_CAPACITY} restart: unless-stopped - logging: - driver: "json-file" + logging: *logging-default healthcheck: test: ["CMD", "sh", "-c", "mysqladmin ping -h localhost -u ${MYSQL_USER} -p${MYSQL_ROOT_PASSWORD} --silent || exit 1"] interval: ${MYSQL_HEALTHCHECK_INTERVAL} @@ -68,11 +74,12 @@ services: - ${STORAGE_PATH}/config:/azerothcore/env/dist/etc - ${STORAGE_PATH}/logs:/azerothcore/logs - ${AC_SQL_SOURCE_PATH:-${STORAGE_PATH_LOCAL}/source/azerothcore-playerbots/data/sql}:/azerothcore/data/sql:ro - - ${MODULE_SQL_STAGE_PATH:-${STORAGE_PATH}/module-sql-updates}:/modules-sql + - ${STAGE_PATH_MODULE_SQL:-${STORAGE_PATH}/module-sql-updates}:/modules-sql - mysql-data:/var/lib/mysql-persistent - ${STORAGE_PATH}/modules:/modules - ${BACKUP_PATH}:/backups - ./scripts/bash/db-import-conditional.sh:/tmp/db-import-conditional.sh:ro + - ./scripts/bash/seed-dbimport-conf.sh:/tmp/seed-dbimport-conf.sh:ro - ./scripts/bash/restore-and-stage.sh:/tmp/restore-and-stage.sh:ro environment: AC_DATA_DIR: "/azerothcore/data" @@ -132,11 +139,12 @@ services: - ${STORAGE_PATH}/config:/azerothcore/env/dist/etc - ${STORAGE_PATH}/logs:/azerothcore/logs - ${AC_SQL_SOURCE_PATH:-${STORAGE_PATH_LOCAL}/source/azerothcore-playerbots/data/sql}:/azerothcore/data/sql:ro - - ${MODULE_SQL_STAGE_PATH:-${STORAGE_PATH}/module-sql-updates}:/modules-sql + - ${STAGE_PATH_MODULE_SQL:-${STORAGE_PATH}/module-sql-updates}:/modules-sql - mysql-data:/var/lib/mysql-persistent - ${STORAGE_PATH}/modules:/modules - ${BACKUP_PATH}:/backups - ./scripts/bash/db-import-conditional.sh:/tmp/db-import-conditional.sh:ro + - ./scripts/bash/seed-dbimport-conf.sh:/tmp/seed-dbimport-conf.sh:ro - ./scripts/bash/restore-and-stage.sh:/tmp/restore-and-stage.sh:ro - ./scripts/bash/db-guard.sh:/tmp/db-guard.sh:ro environment: @@ -326,7 +334,7 @@ services: profiles: ["client-data", "client-data-bots"] image: ${ALPINE_IMAGE} container_name: ac-volume-init - user: "${CONTAINER_USER}" + user: "0:0" volumes: - ${CLIENT_DATA_PATH:-${STORAGE_PATH}/client-data}:/azerothcore/data - client-data-cache:/cache @@ -356,22 +364,66 @@ services: volumes: - ${STORAGE_PATH}:/storage-root - ${STORAGE_PATH_LOCAL}:/local-storage-root + - ./scripts/bash/seed-dbimport-conf.sh:/tmp/seed-dbimport-conf.sh:ro command: - sh - -c - | - echo "🔧 Initializing storage directories with container user ownership..." + echo "🔧 Initializing storage directories with proper permissions..." mkdir -p /storage-root/config /storage-root/logs /storage-root/modules /storage-root/lua_scripts /storage-root/install-markers mkdir -p /storage-root/config/mysql/conf.d mkdir -p /storage-root/client-data mkdir -p /storage-root/backups - mkdir -p /storage-root/module-sql-updates - mkdir -p /storage-root/data - mkdir -p /storage-root/temp - mkdir -p /local-storage-root || true - chown -R ${CONTAINER_USER} /storage-root /local-storage-root 2>/dev/null || true - chmod -R 755 /storage-root /local-storage-root 2>/dev/null || true - echo "✅ Storage directories ready (owned by ${CONTAINER_USER})" + + # Copy core AzerothCore config template files (.dist) to config directory + echo "📄 Copying AzerothCore configuration templates..." + SOURCE_DIR="${SOURCE_DIR:-/local-storage-root/source/azerothcore-playerbots}" + if [ ! -d "$SOURCE_DIR" ] && [ -d "/local-storage-root/source/azerothcore-wotlk" ]; then + SOURCE_DIR="/local-storage-root/source/azerothcore-wotlk" + fi + + # Seed dbimport.conf with a shared helper (fallback to a simple copy if missing) + if [ -f "/tmp/seed-dbimport-conf.sh" ]; then + echo "🧩 Seeding dbimport.conf" + DBIMPORT_CONF_DIR="/storage-root/config" \ + DBIMPORT_SOURCE_ROOT="$SOURCE_DIR" \ + sh -c '. /tmp/seed-dbimport-conf.sh && seed_dbimport_conf' || true + else + if [ -f "$SOURCE_DIR/src/tools/dbimport/dbimport.conf.dist" ]; then + cp -n "$SOURCE_DIR/src/tools/dbimport/dbimport.conf.dist" /storage-root/config/ 2>/dev/null || true + if [ ! -f "/storage-root/config/dbimport.conf" ]; then + cp "$SOURCE_DIR/src/tools/dbimport/dbimport.conf.dist" /storage-root/config/dbimport.conf + echo " ✓ Created dbimport.conf" + fi + fi + fi + + # Copy authserver.conf.dist + if [ -f "$SOURCE_DIR/env/dist/etc/authserver.conf.dist" ]; then + cp -n "$SOURCE_DIR/env/dist/etc/authserver.conf.dist" /storage-root/config/ 2>/dev/null || true + if [ ! -f "/storage-root/config/authserver.conf" ]; then + cp "$SOURCE_DIR/env/dist/etc/authserver.conf.dist" /storage-root/config/authserver.conf + echo " ✓ Created authserver.conf" + fi + fi + + # Copy worldserver.conf.dist + if [ -f "$SOURCE_DIR/env/dist/etc/worldserver.conf.dist" ]; then + cp -n "$SOURCE_DIR/env/dist/etc/worldserver.conf.dist" /storage-root/config/ 2>/dev/null || true + if [ ! -f "/storage-root/config/worldserver.conf" ]; then + cp "$SOURCE_DIR/env/dist/etc/worldserver.conf.dist" /storage-root/config/worldserver.conf + echo " ✓ Created worldserver.conf" + fi + fi + mkdir -p /storage-root/config/temp + # Fix ownership of root directories and all contents + if [ "$(id -u)" -eq 0 ]; then + chown -R ${CONTAINER_USER} /storage-root /local-storage-root + chmod -R 755 /storage-root /local-storage-root + echo "✅ Storage permissions initialized" + else + echo "ℹ️ Running as $(id -u):$(id -g); assuming host permissions are already correct." + fi restart: "no" networks: - azerothcore @@ -400,8 +452,6 @@ services: - -c - | mkdir -p /cache - chown -R ${CONTAINER_USER} /azerothcore/data /cache 2>/dev/null || true - chmod -R 755 /azerothcore/data /cache 2>/dev/null || true if [ -f /tmp/scripts/bash/download-client-data.sh ]; then chmod +x /tmp/scripts/bash/download-client-data.sh 2>/dev/null || true bash /tmp/scripts/bash/download-client-data.sh @@ -434,9 +484,6 @@ services: - | echo "📦 Installing 7z + gosu for client data extraction..." apt-get update -qq && apt-get install -y p7zip-full gosu - echo "🔧 Normalizing client-data/cache ownership..." - chown -R ${CONTAINER_USER} /azerothcore/data /cache 2>/dev/null || true - chmod -R 755 /azerothcore/data /cache 2>/dev/null || true gosu ${CONTAINER_USER} bash -c ' set -e mkdir -p /cache @@ -478,8 +525,7 @@ services: ports: - "${AUTH_EXTERNAL_PORT}:${AUTH_PORT}" restart: unless-stopped - logging: - driver: "json-file" + logging: *logging-default networks: - azerothcore volumes: @@ -534,8 +580,7 @@ services: - ${STORAGE_PATH}/modules:/azerothcore/modules - ${STORAGE_PATH}/lua_scripts:/azerothcore/lua_scripts restart: unless-stopped - logging: - driver: "json-file" + logging: *logging-default networks: - azerothcore cap_add: ["SYS_NICE"] @@ -573,11 +618,7 @@ services: ports: - "${AUTH_EXTERNAL_PORT}:${AUTH_PORT}" restart: unless-stopped - logging: - driver: json-file - options: - max-size: "10m" - max-file: "3" + logging: *logging-default networks: - azerothcore volumes: @@ -613,8 +654,7 @@ services: ports: - "${AUTH_EXTERNAL_PORT}:${AUTH_PORT}" restart: unless-stopped - logging: - driver: "json-file" + logging: *logging-default networks: - azerothcore volumes: @@ -672,8 +712,7 @@ services: - ${STORAGE_PATH}/modules:/azerothcore/modules - ${STORAGE_PATH}/lua_scripts:/azerothcore/lua_scripts restart: unless-stopped - logging: - driver: "json-file" + logging: *logging-default networks: - azerothcore cap_add: ["SYS_NICE"] @@ -730,11 +769,7 @@ 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" + logging: *logging-default cap_add: ["SYS_NICE"] healthcheck: test: ["CMD", "sh", "-c", "ps aux | grep '[w]orldserver' | grep -v grep || exit 1"] @@ -823,8 +858,10 @@ services: - | apk add --no-cache bash curl docker-cli su-exec chmod +x /tmp/scripts/bash/auto-post-install.sh 2>/dev/null || true - echo "📥 Running post-install as ${CONTAINER_USER}" - su-exec ${CONTAINER_USER} bash /tmp/scripts/bash/auto-post-install.sh + echo "📥 Running post-install as root (testing mode)" + mkdir -p /install-markers + chown -R ${CONTAINER_USER} /azerothcore/config /install-markers 2>/dev/null || true + bash /tmp/scripts/bash/auto-post-install.sh restart: "no" networks: - azerothcore @@ -881,8 +918,7 @@ services: timeout: 10s retries: 3 start_period: 40s - logging: - driver: "json-file" + logging: *logging-default security_opt: - no-new-privileges:true networks: diff --git a/scripts/bash/db-guard.sh b/scripts/bash/db-guard.sh index 680cffc..8c93f62 100644 --- a/scripts/bash/db-guard.sh +++ b/scripts/bash/db-guard.sh @@ -24,6 +24,34 @@ STATUS_FILE="${DB_GUARD_STATUS_FILE:-/tmp/db-guard.status}" ERROR_FILE="${DB_GUARD_ERROR_FILE:-/tmp/db-guard.error}" MODULE_SQL_HOST_PATH="${MODULE_SQL_HOST_PATH:-/modules-sql}" +SEED_CONF_SCRIPT="${SEED_DBIMPORT_CONF_SCRIPT:-/tmp/seed-dbimport-conf.sh}" +if [ -f "$SEED_CONF_SCRIPT" ]; then + # shellcheck source=/dev/null + . "$SEED_CONF_SCRIPT" +elif ! command -v seed_dbimport_conf >/dev/null 2>&1; then + seed_dbimport_conf(){ + local conf="/azerothcore/env/dist/etc/dbimport.conf" + local dist="${conf}.dist" + mkdir -p "$(dirname "$conf")" + [ -f "$conf" ] && return 0 + if [ -f "$dist" ]; then + cp "$dist" "$conf" + else + warn "dbimport.conf missing and no dist available; writing minimal defaults" + cat > "$conf" </dev/null 2>&1; then + seed_dbimport_conf(){ + local conf="/azerothcore/env/dist/etc/dbimport.conf" + local dist="${conf}.dist" + mkdir -p "$(dirname "$conf")" + [ -f "$conf" ] && return 0 + if [ -f "$dist" ]; then + cp "$dist" "$conf" + else + echo "⚠️ dbimport.conf missing and no dist available; using localhost defaults" >&2 + cat > "$conf" </dev/null | head -n 1) - if [ -n "$latest_manual" ] && [ -f "$latest_manual" ]; then - echo "📦 Found manual backup: $(basename "$latest_manual")" - if timeout 10 head -20 "$latest_manual" >/dev/null 2>&1; then - echo "✅ Valid manual backup file: $(basename "$latest_manual")" - backup_path="$latest_manual" - break + latest_manual="" + if ls "$BACKUP_DIRS"/*.sql >/dev/null 2>&1; then + latest_manual=$(ls -1t "$BACKUP_DIRS"/*.sql | head -n 1) + if [ -n "$latest_manual" ] && [ -f "$latest_manual" ]; then + echo "📦 Found manual backup: $(basename "$latest_manual")" + if timeout 10 head -20 "$latest_manual" >/dev/null 2>&1; then + echo "✅ Valid manual backup file: $(basename "$latest_manual")" + backup_path="$latest_manual" + break + fi fi fi fi @@ -272,6 +296,7 @@ if [ -z "$backup_path" ]; then done fi +set -e echo "🔄 Final backup path result: '$backup_path'" if [ -n "$backup_path" ]; then echo "📦 Found backup: $(basename "$backup_path")" @@ -357,7 +382,7 @@ if [ -n "$backup_path" ]; then return 0 fi - ensure_dbimport_conf + seed_dbimport_conf cd /azerothcore/env/dist/bin echo "🔄 Running dbimport to apply any missing updates..." @@ -424,23 +449,73 @@ fi echo "🗄️ Creating fresh AzerothCore databases..." mysql -h ${CONTAINER_MYSQL} -u${MYSQL_USER} -p${MYSQL_ROOT_PASSWORD} -e " -CREATE DATABASE IF NOT EXISTS ${DB_AUTH_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -CREATE DATABASE IF NOT EXISTS ${DB_WORLD_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -CREATE DATABASE IF NOT EXISTS ${DB_CHARACTERS_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -CREATE DATABASE IF NOT EXISTS acore_playerbots DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +DROP DATABASE IF EXISTS ${DB_AUTH_NAME}; +DROP DATABASE IF EXISTS ${DB_WORLD_NAME}; +DROP DATABASE IF EXISTS ${DB_CHARACTERS_NAME}; +DROP DATABASE IF EXISTS ${DB_PLAYERBOTS_NAME:-acore_playerbots}; +CREATE DATABASE ${DB_AUTH_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE ${DB_WORLD_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE ${DB_CHARACTERS_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE ${DB_PLAYERBOTS_NAME:-acore_playerbots} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; SHOW DATABASES;" || { echo "❌ Failed to create databases"; exit 1; } echo "✅ Fresh databases created - proceeding with schema import" -ensure_dbimport_conf - echo "🚀 Running database import..." cd /azerothcore/env/dist/bin +seed_dbimport_conf + +maybe_run_base_import(){ + 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}" + + import_dir(){ + local db="$1" dir="$2" + [ -d "$dir" ] || return 0 + echo "🔧 Importing base schema for ${db} from $(basename "$dir")..." + for f in $(ls "$dir"/*.sql 2>/dev/null | LC_ALL=C sort); do + MYSQL_PWD="$mysql_pass" mysql -h "$mysql_host" -P "$mysql_port" -u "$mysql_user" "$db" < "$f" >/dev/null 2>&1 || true + done + } + + needs_import(){ + local db="$1" + local count + count="$(MYSQL_PWD="$mysql_pass" mysql -h "$mysql_host" -P "$mysql_port" -u "$mysql_user" -N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${db}';" 2>/dev/null || echo 0)" + [ "${count:-0}" -eq 0 ] && return 0 + local updates + updates="$(MYSQL_PWD="$mysql_pass" mysql -h "$mysql_host" -P "$mysql_port" -u "$mysql_user" -N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${db}' AND table_name='updates';" 2>/dev/null || echo 0)" + [ "${updates:-0}" -eq 0 ] + } + + if needs_import "${DB_WORLD_NAME:-acore_world}"; then + import_dir "${DB_WORLD_NAME:-acore_world}" "/azerothcore/data/sql/base/db_world" + fi + if needs_import "${DB_AUTH_NAME:-acore_auth}"; then + import_dir "${DB_AUTH_NAME:-acore_auth}" "/azerothcore/data/sql/base/db_auth" + fi + if needs_import "${DB_CHARACTERS_NAME:-acore_characters}"; then + import_dir "${DB_CHARACTERS_NAME:-acore_characters}" "/azerothcore/data/sql/base/db_characters" + fi +} + +maybe_run_base_import if ./dbimport; then echo "✅ Database import completed successfully!" - echo "$(date): Database import completed successfully" > "$RESTORE_STATUS_DIR/.import-completed" || echo "$(date): Database import completed successfully" > "$MARKER_STATUS_DIR/.import-completed" + import_marker_msg="$(date): Database import completed successfully" + if [ -w "$RESTORE_STATUS_DIR" ]; then + echo "$import_marker_msg" > "$RESTORE_STATUS_DIR/.import-completed" + elif [ -w "$MARKER_STATUS_DIR" ]; then + echo "$import_marker_msg" > "$MARKER_STATUS_DIR/.import-completed" 2>/dev/null || true + fi else echo "❌ Database import failed!" - echo "$(date): Database import failed" > "$RESTORE_STATUS_DIR/.import-failed" || echo "$(date): Database import failed" > "$MARKER_STATUS_DIR/.import-failed" + if [ -w "$RESTORE_STATUS_DIR" ]; then + echo "$(date): Database import failed" > "$RESTORE_STATUS_DIR/.import-failed" + elif [ -w "$MARKER_STATUS_DIR" ]; then + echo "$(date): Database import failed" > "$MARKER_STATUS_DIR/.import-failed" 2>/dev/null || true + fi exit 1 fi diff --git a/scripts/bash/migrate-stack.sh b/scripts/bash/migrate-stack.sh index 9732815..984f8de 100755 --- a/scripts/bash/migrate-stack.sh +++ b/scripts/bash/migrate-stack.sh @@ -213,6 +213,23 @@ ensure_host_writable "$LOCAL_STORAGE_ROOT" TARBALL="${TARBALL:-${LOCAL_STORAGE_ROOT}/images/acore-modules-images.tar}" ensure_host_writable "$(dirname "$TARBALL")" +# Resolve module SQL staging paths (local and remote) +resolve_path_relative_to_project(){ + local path="$1" root="$2" + if [[ "$path" != /* ]]; then + # drop leading ./ if present + path="${path#./}" + path="${root%/}/$path" + fi + echo "${path%/}" +} + +STAGE_SQL_PATH_RAW="$(read_env_value STAGE_PATH_MODULE_SQL "${STORAGE_PATH_LOCAL:-./local-storage}/module-sql-updates")" +# Expand any env references (e.g., ${STORAGE_PATH_LOCAL}) +STAGE_SQL_PATH_RAW="$(eval "echo \"$STAGE_SQL_PATH_RAW\"")" +LOCAL_STAGE_SQL_DIR="$(resolve_path_relative_to_project "$STAGE_SQL_PATH_RAW" "$PROJECT_ROOT")" +REMOTE_STAGE_SQL_DIR="$(resolve_path_relative_to_project "$STAGE_SQL_PATH_RAW" "$PROJECT_DIR")" + SCP_OPTS=(-P "$PORT") SSH_OPTS=(-p "$PORT") if [[ -n "$IDENTITY" ]]; then @@ -439,6 +456,18 @@ if [[ $SKIP_STORAGE -eq 0 ]]; then rm -f "$modules_tar" run_ssh "tar -xf '$REMOTE_TEMP_DIR/acore-modules.tar' -C '$REMOTE_STORAGE/modules' && rm '$REMOTE_TEMP_DIR/acore-modules.tar'" fi + + # Sync module SQL staging directory (STAGE_PATH_MODULE_SQL) + if [[ -d "$LOCAL_STAGE_SQL_DIR" ]]; then + echo "⋅ Syncing module SQL staging to remote" + run_ssh "rm -rf '$REMOTE_STAGE_SQL_DIR' && mkdir -p '$REMOTE_STAGE_SQL_DIR'" + sql_tar=$(mktemp) + tar -cf "$sql_tar" -C "$LOCAL_STAGE_SQL_DIR" . + ensure_remote_temp_dir + run_scp "$sql_tar" "$USER@$HOST:$REMOTE_TEMP_DIR/acore-module-sql.tar" + rm -f "$sql_tar" + run_ssh "tar -xf '$REMOTE_TEMP_DIR/acore-module-sql.tar' -C '$REMOTE_STAGE_SQL_DIR' && rm '$REMOTE_TEMP_DIR/acore-module-sql.tar'" + fi fi reset_remote_post_install_marker(){ diff --git a/scripts/bash/seed-dbimport-conf.sh b/scripts/bash/seed-dbimport-conf.sh new file mode 100644 index 0000000..432ec49 --- /dev/null +++ b/scripts/bash/seed-dbimport-conf.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# Ensure dbimport.conf exists with usable connection values. +set -euo pipefail 2>/dev/null || set -eu + +# Usage: seed_dbimport_conf [conf_dir] +# - conf_dir: target directory (defaults to DBIMPORT_CONF_DIR or /azerothcore/env/dist/etc) +seed_dbimport_conf() { + local conf_dir="${1:-${DBIMPORT_CONF_DIR:-/azerothcore/env/dist/etc}}" + local conf="${conf_dir}/dbimport.conf" + local dist="${conf}.dist" + local source_root="${DBIMPORT_SOURCE_ROOT:-${AC_SOURCE_DIR:-/local-storage-root/source/azerothcore-playerbots}}" + if [ ! -d "$source_root" ]; then + local fallback="/local-storage-root/source/azerothcore-wotlk" + if [ -d "$fallback" ]; then + source_root="$fallback" + fi + fi + local source_dist="${DBIMPORT_DIST_PATH:-${source_root}/src/tools/dbimport/dbimport.conf.dist}" + # Put temp dir inside the writable config mount so non-root can create files. + local temp_dir="${DBIMPORT_TEMP_DIR:-/azerothcore/env/dist/etc/temp}" + + mkdir -p "$conf_dir" "$temp_dir" + + # Prefer a real .dist from the source tree if it exists. + if [ -f "$source_dist" ]; then + cp -n "$source_dist" "$dist" 2>/dev/null || true + fi + + if [ ! -f "$conf" ]; then + if [ -f "$dist" ]; then + cp "$dist" "$conf" + else + echo "⚠️ dbimport.conf.dist not found; generating minimal dbimport.conf" >&2 + cat > "$conf" <> "$file" + fi + } + + local host="${CONTAINER_MYSQL:-${MYSQL_HOST:-localhost}}" + local port="${MYSQL_PORT:-3306}" + local user="${MYSQL_USER:-root}" + local pass="${MYSQL_ROOT_PASSWORD:-root}" + local db_auth="${DB_AUTH_NAME:-acore_auth}" + local db_world="${DB_WORLD_NAME:-acore_world}" + local db_chars="${DB_CHARACTERS_NAME:-acore_characters}" + local db_bots="${DB_PLAYERBOTS_NAME:-acore_playerbots}" + + set_conf "LoginDatabaseInfo" "${host};${port};${user};${pass};${db_auth}" "$conf" + set_conf "WorldDatabaseInfo" "${host};${port};${user};${pass};${db_world}" "$conf" + set_conf "CharacterDatabaseInfo" "${host};${port};${user};${pass};${db_chars}" "$conf" + set_conf "PlayerbotsDatabaseInfo" "${host};${port};${user};${pass};${db_bots}" "$conf" + set_conf "EnableDatabases" "${AC_UPDATES_ENABLE_DATABASES:-15}" "$conf" false + set_conf "Updates.AutoSetup" "${AC_UPDATES_AUTO_SETUP:-1}" "$conf" false + set_conf "Updates.ExceptionShutdownDelay" "${AC_UPDATES_EXCEPTION_SHUTDOWN_DELAY:-10000}" "$conf" false + set_conf "Updates.AllowedModules" "${DB_UPDATES_ALLOWED_MODULES:-all}" "$conf" + set_conf "Updates.Redundancy" "${DB_UPDATES_REDUNDANCY:-1}" "$conf" false + set_conf "Database.Reconnect.Seconds" "${DB_RECONNECT_SECONDS:-5}" "$conf" false + set_conf "Database.Reconnect.Attempts" "${DB_RECONNECT_ATTEMPTS:-5}" "$conf" false + set_conf "LoginDatabase.WorkerThreads" "${DB_LOGIN_WORKER_THREADS:-1}" "$conf" false + set_conf "WorldDatabase.WorkerThreads" "${DB_WORLD_WORKER_THREADS:-1}" "$conf" false + set_conf "CharacterDatabase.WorkerThreads" "${DB_CHARACTER_WORKER_THREADS:-1}" "$conf" false + set_conf "LoginDatabase.SynchThreads" "${DB_LOGIN_SYNCH_THREADS:-1}" "$conf" false + set_conf "WorldDatabase.SynchThreads" "${DB_WORLD_SYNCH_THREADS:-1}" "$conf" false + set_conf "CharacterDatabase.SynchThreads" "${DB_CHARACTER_SYNCH_THREADS:-1}" "$conf" false + set_conf "MySQLExecutable" "/usr/bin/mysql" "$conf" + set_conf "TempDir" "$temp_dir" "$conf" +} diff --git a/scripts/bash/stage-modules.sh b/scripts/bash/stage-modules.sh index a083a4f..cb53bfe 100755 --- a/scripts/bash/stage-modules.sh +++ b/scripts/bash/stage-modules.sh @@ -259,14 +259,14 @@ SENTINEL_FILE="$LOCAL_STORAGE_PATH/modules/.requires_rebuild" MODULES_META_DIR="$STORAGE_PATH/modules/.modules-meta" RESTORE_PRESTAGED_FLAG="$MODULES_META_DIR/.restore-prestaged" MODULES_ENABLED_FILE="$MODULES_META_DIR/modules-enabled.txt" -MODULE_SQL_STAGE_PATH="$(read_env MODULE_SQL_STAGE_PATH "$STORAGE_PATH/module-sql-updates")" -MODULE_SQL_STAGE_PATH="$(eval "echo \"$MODULE_SQL_STAGE_PATH\"")" -if [[ "$MODULE_SQL_STAGE_PATH" != /* ]]; then - MODULE_SQL_STAGE_PATH="$PROJECT_DIR/$MODULE_SQL_STAGE_PATH" +STAGE_PATH_MODULE_SQL="$(read_env STAGE_PATH_MODULE_SQL "$STORAGE_PATH/module-sql-updates")" +STAGE_PATH_MODULE_SQL="$(eval "echo \"$STAGE_PATH_MODULE_SQL\"")" +if [[ "$STAGE_PATH_MODULE_SQL" != /* ]]; then + STAGE_PATH_MODULE_SQL="$PROJECT_DIR/$STAGE_PATH_MODULE_SQL" fi -MODULE_SQL_STAGE_PATH="$(canonical_path "$MODULE_SQL_STAGE_PATH")" -mkdir -p "$MODULE_SQL_STAGE_PATH" -ensure_host_writable "$MODULE_SQL_STAGE_PATH" +STAGE_PATH_MODULE_SQL="$(canonical_path "$STAGE_PATH_MODULE_SQL")" +mkdir -p "$STAGE_PATH_MODULE_SQL" +ensure_host_writable "$STAGE_PATH_MODULE_SQL" HOST_STAGE_HELPER_IMAGE="$(read_env ALPINE_IMAGE "alpine:latest")" declare -A ENABLED_MODULES=() @@ -439,7 +439,7 @@ esac # Stage module SQL to core updates directory (after containers start) host_stage_clear(){ docker run --rm \ - -v "$MODULE_SQL_STAGE_PATH":/host-stage \ + -v "$STAGE_PATH_MODULE_SQL":/host-stage \ "$HOST_STAGE_HELPER_IMAGE" \ sh -c 'find /host-stage -type f -name "MODULE_*.sql" -delete' >/dev/null 2>&1 || true } @@ -447,7 +447,7 @@ host_stage_clear(){ host_stage_reset_dir(){ local dir="$1" docker run --rm \ - -v "$MODULE_SQL_STAGE_PATH":/host-stage \ + -v "$STAGE_PATH_MODULE_SQL":/host-stage \ "$HOST_STAGE_HELPER_IMAGE" \ sh -c "mkdir -p /host-stage/$dir && rm -f /host-stage/$dir/MODULE_*.sql" >/dev/null 2>&1 || true } @@ -461,7 +461,7 @@ copy_to_host_stage(){ local base_name base_name="$(basename "$file_path")" docker run --rm \ - -v "$MODULE_SQL_STAGE_PATH":/host-stage \ + -v "$STAGE_PATH_MODULE_SQL":/host-stage \ -v "$src_dir":/src \ "$HOST_STAGE_HELPER_IMAGE" \ sh -c "mkdir -p /host-stage/$core_dir && cp \"/src/$base_name\" \"/host-stage/$core_dir/$target_name\"" >/dev/null 2>&1 diff --git a/scripts/python/modules.py b/scripts/python/modules.py index 498483a..c016239 100755 --- a/scripts/python/modules.py +++ b/scripts/python/modules.py @@ -588,14 +588,16 @@ def handle_generate(args: argparse.Namespace) -> int: write_outputs(state, output_dir) if state.warnings: - warning_block = "\n".join(f"- {warning}" for warning in state.warnings) + module_keys_with_warnings = sorted( + {warning.split()[0].strip(":,") for warning in state.warnings if warning.startswith("MODULE_")} + ) + warning_lines = [] + if module_keys_with_warnings: + warning_lines.append(f"- Modules with warnings: {', '.join(module_keys_with_warnings)}") + warning_lines.extend(f"- {warning}" for warning in state.warnings) + warning_block = textwrap.indent("\n".join(warning_lines), " ") print( - textwrap.dedent( - f"""\ - ⚠️ Module manifest warnings detected: - {warning_block} - """ - ), + f"⚠️ Module manifest warnings detected:\n{warning_block}\n", file=sys.stderr, ) if state.errors: diff --git a/setup.sh b/setup.sh index 709c37c..04f0a5d 100755 --- a/setup.sh +++ b/setup.sh @@ -1529,8 +1529,24 @@ fi # Set build sentinel to indicate rebuild is needed local sentinel="$LOCAL_STORAGE_ROOT_ABS/modules/.requires_rebuild" mkdir -p "$(dirname "$sentinel")" - touch "$sentinel" - say INFO "Build sentinel created at $sentinel" + if touch "$sentinel" 2>/dev/null; then + say INFO "Build sentinel created at $sentinel" + else + say WARNING "Could not create build sentinel at $sentinel (permissions/ownership); forcing with sudo..." + if command -v sudo >/dev/null 2>&1; then + if sudo mkdir -p "$(dirname "$sentinel")" \ + && sudo chown -R "$(id -u):$(id -g)" "$(dirname "$sentinel")" \ + && sudo touch "$sentinel"; then + say INFO "Build sentinel created at $sentinel (after fixing ownership)" + else + say ERROR "Failed to force build sentinel creation at $sentinel. Fix permissions and rerun setup." + exit 1 + fi + else + say ERROR "Cannot force build sentinel creation (sudo unavailable). Fix permissions on $(dirname "$sentinel") and rerun setup." + exit 1 + fi + fi fi local default_source_rel="${LOCAL_STORAGE_ROOT}/source/azerothcore" diff --git a/update-latest.sh b/update-latest.sh new file mode 100755 index 0000000..6e0f6e5 --- /dev/null +++ b/update-latest.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# +# Safe wrapper to update to the latest commit on the current branch and run deploy. + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT_DIR" + +BLUE='\033[0;34m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m' +info(){ printf '%b\n' "${BLUE}ℹ️ $*${NC}"; } +ok(){ printf '%b\n' "${GREEN}✅ $*${NC}"; } +warn(){ printf '%b\n' "${YELLOW}⚠️ $*${NC}"; } +err(){ printf '%b\n' "${RED}❌ $*${NC}"; } + +FORCE_DIRTY=0 +DEPLOY_ARGS=() +SKIP_BUILD=0 +AUTO_DEPLOY=0 + +usage(){ + cat <<'EOF' +Usage: ./update-latest.sh [--force] [--help] [deploy args...] + +Updates the current git branch with a fast-forward pull, runs a fresh build, +and optionally runs ./deploy.sh with any additional arguments you provide +(e.g., --yes --no-watch). + +Options: + --force Skip the dirty-tree check (not recommended; you may lose changes) + --skip-build Do not run ./build.sh after updating + --deploy Auto-run ./deploy.sh after build (non-interactive) + --help Show this help + +Examples: + ./update-latest.sh --yes --no-watch + ./update-latest.sh --deploy --yes --no-watch + ./update-latest.sh --force --skip-build + ./update-latest.sh --force --deploy --remote --remote-host my.host --remote-user sam --yes +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --force) FORCE_DIRTY=1; shift;; + --skip-build) SKIP_BUILD=1; shift;; + --deploy) AUTO_DEPLOY=1; shift;; + --help|-h) usage; exit 0;; + *) DEPLOY_ARGS+=("$1"); shift;; + esac +done + +command -v git >/dev/null 2>&1 || { err "git is required"; exit 1; } + +if [ "$FORCE_DIRTY" -ne 1 ]; then + if [ -n "$(git status --porcelain)" ]; then + err "Working tree is dirty. Commit/stash or re-run with --force." + exit 1 + fi +fi + +current_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)" +if [ -z "$current_branch" ] || [ "$current_branch" = "HEAD" ]; then + err "Cannot update: detached HEAD or unknown branch." + exit 1 +fi + +if ! git ls-remote --exit-code --heads origin "$current_branch" >/dev/null 2>&1; then + err "Remote branch origin/$current_branch not found." + exit 1 +fi + +info "Fetching latest changes from origin/$current_branch" +git fetch --prune origin + +info "Fast-forwarding to origin/$current_branch" +if ! git merge --ff-only "origin/$current_branch"; then + err "Fast-forward failed. Resolve manually or rebase, then rerun." + exit 1 +fi + +ok "Repository updated to $(git rev-parse --short HEAD)" + +if [ "$SKIP_BUILD" -ne 1 ]; then + info "Running build.sh --yes" + if ! "$ROOT_DIR/build.sh" --yes; then + err "Build failed. Resolve issues and re-run." + exit 1 + fi + ok "Build completed" +else + warn "Skipping build (--skip-build set)" +fi + +# Offer to run deploy +if [ "$AUTO_DEPLOY" -eq 1 ]; then + info "Auto-deploy enabled; running deploy.sh ${DEPLOY_ARGS[*]:-(no extra args)}" + exec "$ROOT_DIR/deploy.sh" "${DEPLOY_ARGS[@]}" +fi + +if [ -t 0 ]; then + read -r -p "Run deploy.sh now? [y/N]: " reply + reply="${reply:-n}" + case "$reply" in + [Yy]*) + info "Running deploy.sh ${DEPLOY_ARGS[*]:-(no extra args)}" + exec "$ROOT_DIR/deploy.sh" "${DEPLOY_ARGS[@]}" + ;; + *) + ok "Update (and build) complete. Run ./deploy.sh ${DEPLOY_ARGS[*]} when ready." + exit 0 + ;; + esac +else + warn "Non-interactive mode and --deploy not set; skipping deploy." + ok "Update (and build) complete. Run ./deploy.sh ${DEPLOY_ARGS[*]} when ready." +fi