Merge remote-tracking branch 'upstream/master' into test-staging

# Conflicts:
#	.github/workflows/windows_build.yml
#	src/server/game/Handlers/CharacterHandler.cpp
This commit is contained in:
Keleborn
2026-07-17 15:15:37 -07:00
156 changed files with 2834 additions and 948 deletions

View File

@@ -225,9 +225,21 @@ LABEL description="AzerothCore client-data"
ENV DATAPATH=/azerothcore/env/dist/data
RUN apt-get update && \
apt-get install -y curl unzip && \
apt-get install -y curl unzip adduser && \
rm -rf /var/lib/apt/lists/*
ARG USER_ID=1000
ARG GROUP_ID=1000
ARG DOCKER_USER=acore
# To use GID/UID 1000 in ubuntu > 23.04 the existing user must be deleted
# See https://bugs.launchpad.net/cloud-images/+bug/2005129
RUN userdel --remove ubuntu \
&& addgroup --gid "$GROUP_ID" "$DOCKER_USER" \
&& adduser --disabled-password --gecos '' --uid "$USER_ID" --gid "$GROUP_ID" "$DOCKER_USER" \
&& passwd -d "$DOCKER_USER" \
&& chown -R "$DOCKER_USER:$DOCKER_USER" /azerothcore
COPY --chown=$DOCKER_USER:$DOCKER_USER apps apps
VOLUME /azerothcore/env/dist/data

View File

@@ -12,15 +12,15 @@ fi
UBUNTU_VERSION=$(lsb_release -sr);
case $UBUNTU_VERSION in
"22.04")
;;
"24.04")
;;
"26.04")
;;
*)
echo "########## ########## ##########"
echo ""
echo " using unsupported Ubuntu version " $UBUNTU_VERSION
echo " please update to Ubuntu 22.04 or later"
echo " please update to Ubuntu 24.04 or later"
echo ""
echo "########## ########## ##########"
;;
@@ -30,21 +30,32 @@ $SUDO apt update
# shared deps
DEBIAN_FRONTEND="noninteractive" $SUDO \
apt-get -y install ccache clang cmake curl google-perftools libmysqlclient-dev make unzip jq screen tmux \
libreadline-dev libncurses5-dev libncursesw5-dev libbz2-dev git gcc g++ libssl-dev \
apt-get -y install ccache clang cmake curl make unzip jq screen tmux \
libreadline-dev libbz2-dev git gcc g++ libssl-dev \
libncurses-dev libboost-all-dev gdb gdbserver expect
# version-specific deps
if [[ "$UBUNTU_VERSION" == "26.04" ]]; then
DEBIAN_FRONTEND="noninteractive" $SUDO \
apt-get -y install libgoogle-perftools-dev default-libmysqlclient-dev
else
DEBIAN_FRONTEND="noninteractive" $SUDO \
apt-get -y install google-perftools libmysqlclient-dev libncurses5-dev libncursesw5-dev
fi
VAR_PATH="$CURRENT_PATH/../../../../var"
# Do not install MySQL if we are in docker (It will be used a docker container instead) or we are explicitly skipping it.
if [[ $DOCKER != 1 && $SKIP_MYSQL_INSTALL != 1 ]]; then
# run noninteractive install for MYSQL 8.4 LTS
wget https://dev.mysql.com/get/mysql-apt-config_0.8.35-1_all.deb -P "$VAR_PATH"
# resolve expired key issue
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A8D3785C
DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.35-1_all.deb"
$SUDO apt-get update
if [[ "$UBUNTU_VERSION" == "24.04" ]]; then
# run noninteractive install for MYSQL 8.4 LTS
wget https://dev.mysql.com/get/mysql-apt-config_0.8.35-1_all.deb -P "$VAR_PATH"
# resolve expired key issue
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A8D3785C
DEBIAN_FRONTEND="noninteractive" $SUDO dpkg -i "$VAR_PATH/mysql-apt-config_0.8.35-1_all.deb"
$SUDO apt-get update
fi
DEBIAN_FRONTEND="noninteractive" $SUDO apt-get install -y mysql-server
fi

View File

@@ -45,6 +45,174 @@ if [ ! -f "$BINARY" ]; then
exit 1
fi
function config_value() {
local key="$1"
if [ -z "${CONFIG_ABS:-}" ] || [ ! -f "$CONFIG_ABS" ]; then
return 1
fi
awk -F= -v key="$key" '
{
configKey = $1
gsub(/^[[:space:]]+|[[:space:]]+$/, "", configKey)
}
configKey == key {
value = $0
sub(/^[^=]*=/, "", value)
sub(/[[:space:]]*[#;].*$/, "", value)
gsub(/"/, "", value)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
print value
exit
}
' "$CONFIG_ABS"
}
function command_contains_config() {
local command_line="$1"
if [ -z "${CONFIG_ABS:-}" ]; then
return 0
fi
case "$command_line" in
*" -c $CONFIG_ABS"*|*" -c \"$CONFIG_ABS\""*)
return 0
;;
esac
return 1
}
function find_existing_instances() {
local line pid command_line
while IFS= read -r line; do
pid="${line%% *}"
command_line="${line#* }"
[ -z "$pid" ] && continue
[ "$pid" = "$$" ] && continue
[ "$pid" = "$PPID" ] && continue
case "$command_line" in
*"/starter "*|*"pgrep "*)
continue
;;
esac
if command_contains_config "$command_line"; then
printf '%s\n' "$line"
fi
done < <(pgrep -fa "$EXECPATH" 2>/dev/null || true)
}
function listening_on_port() {
local port="$1"
command -v ss >/dev/null 2>&1 || return 1
ss -H -ltn "sport = :$port" 2>/dev/null | grep -q .
}
function guarded_ports() {
local port soap_enabled soap_port
if [ "$BINFILE" != "worldserver" ]; then
return 0
fi
port="$(config_value "WorldServerPort" || true)"
if [ -n "$port" ]; then
printf '%s\n' "$port"
fi
soap_enabled="$(config_value "SOAP.Enabled" || true)"
soap_port="$(config_value "SOAP.Port" || true)"
if [ "$soap_enabled" = "1" ] && [ -n "$soap_port" ]; then
printf '%s\n' "$soap_port"
fi
}
function find_listening_guard_ports() {
local port
while IFS= read -r port; do
[ -z "$port" ] && continue
if listening_on_port "$port"; then
printf '%s\n' "$port"
fi
done < <(guarded_ports)
}
function wait_for_startup_guard() {
local timeout="${AC_STARTUP_GUARD_TIMEOUT:-90}"
local interval="${AC_STARTUP_GUARD_INTERVAL:-2}"
local elapsed=0
local instances ports
local sleep_for
local warned=0
if [ "${AC_STARTUP_SKIP_GUARD:-0}" = "1" ]; then
return 0
fi
case "$timeout" in
''|*[!0-9]*)
echo "Startup guard: invalid AC_STARTUP_GUARD_TIMEOUT '$timeout', using 90."
timeout=90
;;
esac
case "$interval" in
''|*[!0-9]*)
echo "Startup guard: invalid AC_STARTUP_GUARD_INTERVAL '$interval', using 2."
interval=2
;;
esac
if [ "$interval" -lt 1 ]; then
echo "Startup guard: AC_STARTUP_GUARD_INTERVAL must be at least 1, using 1."
interval=1
fi
while true; do
instances="$(find_existing_instances)"
ports="$(find_listening_guard_ports)"
if [ -z "$instances" ] && [ -z "$ports" ]; then
return 0
fi
if [ "$elapsed" -ge "$timeout" ]; then
echo "Startup guard: refusing to start duplicate $BINFILE after waiting ${timeout}s."
if [ -n "$instances" ]; then
echo "Startup guard: existing matching process(es):"
echo "$instances"
fi
if [ -n "$ports" ]; then
echo "Startup guard: configured port(s) still listening: $(echo "$ports" | paste -sd ',' -)"
fi
exit 0
fi
if [ "$warned" -eq 0 ]; then
echo "Startup guard: waiting for existing $BINFILE instance or configured port(s) to clear..."
warned=1
fi
sleep_for="$interval"
if [ $((elapsed + sleep_for)) -gt "$timeout" ]; then
sleep_for=$((timeout - elapsed))
fi
[ "$sleep_for" -le 0 ] && continue
sleep "$sleep_for"
elapsed=$((elapsed + sleep_for))
done
}
# Create crashes directory if it doesn't exist
mkdir -p "$CRASHES_PATH"
@@ -55,6 +223,8 @@ cd "$BINPATH" || {
EXECPATH=$(realpath "$BINFILE")
wait_for_startup_guard
if [ "$GDB_ENABLED" -eq 1 ]; then
echo "Starting $EXECPATH with GDB enabled"
@@ -148,4 +318,4 @@ else
exec "$EXECPATH" ${CONFIG_ABS:+-c "$CONFIG_ABS"}
fi
fi
fi
fi

View File

@@ -50,6 +50,86 @@ teardown() {
[[ "$output" =~ "Test server starting" ]]
}
@test "starter: should refuse duplicate binary/config start" {
cat > "$TEST_DIR/bin/duplicate-server" << 'EOF'
#!/usr/bin/env bash
sleep 30
EOF
chmod +x "$TEST_DIR/bin/duplicate-server"
"$TEST_DIR/bin/duplicate-server" -c "$TEST_DIR/test-server.conf" &
local duplicate_pid=$!
AC_STARTUP_GUARD_TIMEOUT=1 AC_STARTUP_GUARD_INTERVAL=1 run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "duplicate-server" "" "$TEST_DIR/test-server.conf" "" "" 0
kill "$duplicate_pid" 2>/dev/null || true
wait "$duplicate_pid" 2>/dev/null || true
debug_on_failure
[ "$status" -eq 0 ]
[[ "$output" =~ "Startup guard: refusing to start duplicate duplicate-server" ]]
[[ "$output" =~ "existing matching process" ]]
}
@test "starter: should refuse occupied configured worldserver ports" {
if ! command -v nc >/dev/null 2>&1; then
skip "nc is required for this test"
fi
cat > "$TEST_DIR/bin/worldserver" << 'EOF'
#!/usr/bin/env bash
echo "worldserver should not start"
EOF
chmod +x "$TEST_DIR/bin/worldserver"
local listen_port=40123
cat > "$TEST_DIR/worldserver.conf" << EOF
WorldServerPort = $listen_port
SOAP.Enabled = 0
EOF
nc -l 127.0.0.1 "$listen_port" >/dev/null &
local listener_pid=$!
AC_STARTUP_GUARD_TIMEOUT=1 AC_STARTUP_GUARD_INTERVAL=1 run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "worldserver" "" "$TEST_DIR/worldserver.conf" "" "" 0
kill "$listener_pid" 2>/dev/null || true
wait "$listener_pid" 2>/dev/null || true
debug_on_failure
[ "$status" -eq 0 ]
[[ "$output" =~ "Startup guard: refusing to start duplicate worldserver" ]]
[[ "$output" =~ "configured port(s) still listening: $listen_port" ]]
}
@test "starter: should parse config keys literally for startup guard" {
if ! command -v nc >/dev/null 2>&1; then
skip "nc is required for this test"
fi
cat > "$TEST_DIR/bin/worldserver" << 'EOF'
#!/usr/bin/env bash
echo "worldserver started"
EOF
chmod +x "$TEST_DIR/bin/worldserver"
local listen_port=40124
cat > "$TEST_DIR/worldserver.conf" << EOF
WorldServerPort = 40125
SOAPXEnabled = 1
SOAPXPort = $listen_port
SOAP.Enabled = 0
EOF
nc -l 127.0.0.1 "$listen_port" >/dev/null &
local listener_pid=$!
AC_STARTUP_GUARD_TIMEOUT=1 AC_STARTUP_GUARD_INTERVAL=1 run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "worldserver" "" "$TEST_DIR/worldserver.conf" "" "" 0
kill "$listener_pid" 2>/dev/null || true
wait "$listener_pid" 2>/dev/null || true
debug_on_failure
[ "$status" -eq 0 ]
[[ "$output" =~ "worldserver started" ]]
[[ ! "$output" =~ "Startup guard: refusing" ]]
}
# ===== SIMPLE RESTARTER TESTS =====
@test "simple-restarter: should fail with missing parameters" {